Hi Thirion, Being new to OpenJPA, you sure have a long, complicated note... :-) Seriously, since most of us are attempting to monitor these forums on "our own time", posting smaller questions/issues might get a faster, even more accurate response. I do appreciate the detail you provided. It was just a bit much to digest in all one sitting... Just a suggestion...
Given that, I read through your note and picked out a few things that caught my eye. Maybe the information will help you make some progress... Re: openjpa-1979. Thanks for "testing" the patch for this JIRA. This looks to be quite old and should probably be included into trunk. If you could post a comment to this JIRA indicating the test scenario you verified the patch with, then maybe we could get this patch integrated. I'm a little confused as to why this "code" field is accessed differently. Is the field defined in the database as a 2 character field? And, sometimes you want to reference this as 2 chars and other times you want to reference this as an integer? Why complicate your O/R Mapping and just decide on a format and then use special code in those areas of the application that require a different format? It almost looks like you are attempting to use this "code" field as a FK to another table... Why not just separate these two concerns? I agree that the documentation for Factory/Externalizer is not the most complete. But, there are a couple of simple examples in that section. I would also suggest looking at our suite of JUnits, specifically the ones in the openjpa-persistence-jdbc module. There are several examples (albeit maybe not as complicated as your scenario), but at least they do show some working examples of the features. Normally, the Factory/Externalizer are used for Queries as well... There is an open issue with Factory/Externalizer not working with primary keys all the time, but this doesn't look to be the case. Here again, following up with a specific scenario and testcase might help speed along resolution. The use of Strategies or Custom Field Mappings might also be an alternative. (Sorry, I haven't taken the time to figure out which approach would work best for your scenario.) Here again, I would suggest searching our JUnits for more examples. Of course, if you find an area that you could contribute additions to, we would probably accept them with open arms... :-) Good luck, Kevin On Thu, May 17, 2012 at 2:52 AM, Thirion <thirion.franc...@gmail.com> wrote: > Hi. I’m new to OpenJPA (and JPA in general) so please forgive me if I’m > asking stupid questions. > > I have a legacy database that I cannot change that has some strange mapping > for logical relationships and I’m struggling to get the custom mapping done > correctly. > > I have a table with description text that I need to reference using a value > for ‘code’. The code value alone isn’t enough to uniquely identify the > record, so I need to add a description type and language. So basically > this > table contains descriptions for many types (like Title, Province, etc.) for > many languages. > > I’ve gotten OpenJPA’s Constant Joins to work using the following: (I > checked out version 2.2.0 and applied a patch that I found on the forum > [ > https://issues.apache.org/jira/browse/OPENJPA-1979?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel > ] > to fix the Constant Joins issue [the patch was for an earlier version, but > still worked for me]) > > @Entity > @Table(name = "test") > public class Test implements Serializable { > > @ManyToOne > @JoinColumns({ > @JoinColumn(name = "CODE", referencedColumnName = "TABLE_CODE"), > @JoinColumn(name = "table_descriptions.TABLE_NO", > referencedColumnName = "2"), > @JoinColumn(name = "table_descriptions.LANGUAGE", > referencedColumnName = "1") > }) > private TableDescription code; > > ... > } > This works for uni-directional and bidirectional relationships. > > The problem I’m having is that some of the tables that look up to this > description table use Integer values for the value of ‘code’, and the > ‘code’ > value in the description table is a 2 char string (01, 02 etc.) > > So, I can get entity to find and store the correct value using: > @ManyToOne > //@Column(name="CODE") > @JoinColumn(name="CODE", referencedColumnName="TABLE_CODE") > @Externalizer(value = "Test.codeExternalizer") > @Factory(value = "Test.codeFactory") > private TableDescription code; > > @Transient > private static EntityManagerFactory emf; > > With the Externalizer (as an inner class) looking like this: > public static String codeExternalizer(TableDescription val) { > if (val == null) { > return null; > } > > return val.getTableCode().toString(); > } > > And the Factory looking like this: > public static TableDescription codeFactory(String val) { > if (val == null) { > return null; > } > > while (val.length() < 2) { > val = "0" + val; > } > > if (emf == null) { > emf = Persistence.createEntityManagerFactory("EjbH2TestWebPU"); > } > EntityManager em = emf.createEntityManager(); > TypedQuery<TableDescription> q = em.createQuery( > "SELECT o FROM TableDescription as o WHERE o.tableCode = ?1 > AND o.tableNo = ?2 AND o.language = ?3", TableDescription.class); > q.setParameter(1, val); > q.setParameter(2, 1); //f this is specific to each field that does a > lookup, indicating what type of description it’s looking for. > q.setParameter(3, 1); //f this is only 1 (English), but could be the > currently logged in user's language. > TableDescription result = null; > try { > result = q.getSingleResult(); > } catch (javax.persistence.NoResultException nre) { > System.err.println("no value for: " + val); > } catch (javax.persistence.NonUniqueResultException notUnique) { > System.err.println("more than one value for: " + val); > } catch (Exception e) { > System.err.println("An error occured getting value for: " + val > + ": " + e.getMessage()); > } > em.close(); > em = null; > return result; > } > > > Firstly, is this the right way to use Externalizer and Factory? I can’t > find one example of a working app that uses these annotations. > > The above code works when I get an instance of my entity and call getCode() > and setCode(), but when doing a JPQL query it still returns the underlying > String/Integer value instead of the related entity. > > I’m also getting an error when I need a bidirectional relationship and use > the mappedBy value for the OneToMany annotation for the collection side of > the relationship like this: > > @Entity > @Table(name = "table_descriptions") > public class TableDescription implements Serializable { > > @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = > "code") > private Collection<Test> tests; > ... > } > > When I try to do this, I get the following Exception when using @Column > (commented out in the code above): > Collection field "entity.TableDescription.tests" declares that it is mapped > by "entity.Test.code", but this is not a valid inverse relation. > > And the following Exception when using @JoinColumn: > "entity.Test.code" has columns with targets, but OpenJPA does not support > any joins on this mapping in this context > > I have another scenario that doesn’t use Constant Joins, but the foreign > key > also needs to be prepended with zeros before the value is compared to the > related table’s column. I also use the Externalizer/Factory setup and get > the same problem with bidirectional relationships. > > So, what is the correct way of using Externalizer/Factory so that > bidirectional relationships can work? > > The manual for OpenJPA is very sparse on these topics, listing only the > annotations and the method signatures. I can’t find any sample code that > shows the body of these methods. > > > I’ve seen the forums refer to @Strategy as a possible sollution for > mapping. > I’ve tried the following but now I get an exception that it can’t load the > class (related to an old post I found: > http://openjpa.208410.n2.nabble.com/Using-custom-ValueHandler-td210193.html > ): > > @Strategy("entity.CodeStrategy") > private TableDescription code; > > and the CodeStrategy class looks like this: > public class CodeStrategy2 extends AbstractValueHandler { > > @Transient > private static EntityManagerFactory emf; > > @Override > public org.apache.openjpa.jdbc.schema.Column[] map(ValueMapping vm, > String name, ColumnIO io, boolean adapt) { > > org.apache.openjpa.jdbc.schema.Table table = new > org.apache.openjpa.jdbc.schema.Table(); > table.setIdentifier(DBIdentifier.newTable("test")); > DBIdentifier codeDbId = DBIdentifier.newColumn("code"); > > org.apache.openjpa.jdbc.schema.Column codeCol = new > org.apache.openjpa.jdbc.schema.Column(); > > codeCol.setJavaType(JavaTypes.STRING); > > return new org.apache.openjpa.jdbc.schema.Column[]{codeCol}; > } > > @Override > public Object toDataStoreValue(ValueMapping vm, Object val, JDBCStore > store) { > if (val == null) { > return null; > } > > if (val instanceof TableDescription) { > return ((TableDescription) val).getTableCode(); > } else { > throw new IllegalArgumentException("Code is not an instance of > TableDescription: " + val); > } > } > > @Override > public boolean objectValueRequiresLoad(ValueMapping vm) { > return true; > } > > @Override > public Object toObjectValue(ValueMapping vm, Object value, > OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration > fetch) > throws SQLException { > if (value == null) { > return null; > } > > //f do a select statement to get it from the db. > String val = value.toString(); > while (val.length() < 2) { > val = "0" + val; > } > > //f can I use StoreContext to do this instead? What is the best > way? > > if (emf == null) { > emf = Persistence.createEntityManagerFactory("EjbH2TestWebPU"); > } > EntityManager em = emf.createEntityManager(); > TypedQuery<TableDescription> q = em.createQuery( > "SELECT o FROM TableDescription as o WHERE o.tableCode = ?1 > AND o.tableNo = ?2 AND o.language = ?3", TableDescription.class); > q.setParameter(1, val); > q.setParameter(2, 1); //f this is specific to each field that does a > lookup > q.setParameter(3, 1); //f this is only 1 (English), but could be the > currently logged in user's language. > TableDescription result = null; > try { > result = q.getSingleResult(); > } catch (javax.persistence.NoResultException nre) { > System.err.println("no value for: " + val); > } catch (javax.persistence.NonUniqueResultException notUnique) { > System.err.println("more than one value for: " + val); > } catch (Exception e) { > System.err.println("An error occured getting value for: " + val > + ": " + e.getMessage()); > } > em.close(); > em = null; > return result; > } > } > > In the post mentioned above they suggest a patch for the classpath issue > (which didn’t work for me), or adding your ValueHandler code in the OpenJpa > jar file. Since it references another entity of mine, I would have to add > my whole app in there, which won’t work. > > Ideally I want all my Strategy value handlers as inner classes of the > entity > like I did for my Externalizers. > > Are there any tutorials/walkthroughs/sample projects that I can use to > learn > how this works? > Thanks in advance for any help on this > > > > -- > View this message in context: > http://openjpa.208410.n2.nabble.com/Custom-Mapping-with-Externalizer-Factory-Strategy-tp7563345.html > Sent from the OpenJPA Users mailing list archive at Nabble.com. >