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.
>

Reply via email to