jpa-spec
  1. jpa-spec
  2. JPA_SPEC-61

Retrieving primary key of lazy relationship

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Labels:
      None
    • Environment:

      JPA 2.0

      Description

      My server passes entities to clients as XML representations, simply by marshalling it using JAXB. This works well but there is one case where it fails: LAZY relationships. As I just want to include a URL for each reference (so clients can decide on their own whether to load this in turn), I need access to the primary key. But, as it is a LAZY reference, the attribute itself is NULL, and if I use the getter(), then the complete entity is instantiated – which I do not need and do not want. So it would be really great, if there would be solution, which makes JAXB and JPA work more closely. Like an API to get the primary keys of LAZY relationships, or even better, an automatic integration.

        Activity

        Hide
        mkeith added a comment -

        Markus,

        With JPA 2.1 you could create an entity fetch graph for the related object and only load the PK. You would effectively have an unloaded object (except that it would have the PK) with its remaining state set to lazily load. There are some differences between this and having the whole object be lazily loaded since if the object ends up getting triggered then, depending upon the implementation, each of the attributes may end up getting loaded separately, but it could be configured to be fairly close I think. Of course, this would be a bit of a pain if you needed to do this for every lazy loaded attribute, but the advantage would be that it would also work pretty well for collection-oriented relationships.

        -Mike

        Show
        mkeith added a comment - Markus, With JPA 2.1 you could create an entity fetch graph for the related object and only load the PK. You would effectively have an unloaded object (except that it would have the PK) with its remaining state set to lazily load. There are some differences between this and having the whole object be lazily loaded since if the object ends up getting triggered then, depending upon the implementation, each of the attributes may end up getting loaded separately, but it could be configured to be fairly close I think. Of course, this would be a bit of a pain if you needed to do this for every lazy loaded attribute, but the advantage would be that it would also work pretty well for collection-oriented relationships. -Mike
        Hide
        mkarg added a comment -

        Unfortunately didn't find a tutorial on this on the web.

        Currently I am doing this...

        Foo f = em.find(Foo.class, myFooPK);
        jaxbMarshaller.marshal(f);

        ...to get the root. Can you provide an example how to use that "fetch graph" thing to get the PKs of f's LAZY relations?

        Thanks!
        -Markus

        Show
        mkarg added a comment - Unfortunately didn't find a tutorial on this on the web. Currently I am doing this... Foo f = em.find(Foo.class, myFooPK); jaxbMarshaller.marshal(f); ...to get the root. Can you provide an example how to use that "fetch graph" thing to get the PKs of f's LAZY relations? Thanks! -Markus
        Hide
        mkeith added a comment -

        From your first comment it sounded like you already had something (e.g. an XMLAdapter or some such thing?) on the relationship attribute to control marshalling a URL instead of the target Bar related object. Assuming that is the case (you need to stop the JAXB marshaller from continuing to traverse the Bar object) then you just need to access the PK of the Bar, right? If this is your usecase then you could do the following:

        Define a named entity graph for Bar:
        @Entity
        public class Bar {
        @Id long id;
        public int getId()

        { return id; }


        ...
        }

        @XmlRootElement
        @Entity
        public class Foo

        { ... @XmlJavaTypeAdapter(MyBarToUrlAdapter.class) @OneToOne Bar bar; ... }

        and then to read the Foo instance, you would do:

        Map<String,Object> props = new HashMap<String,Object>();
        props.put("javax.persistence.fetchgraph", em.createEntityGraph(Bar.class));
        Foo f = em.find(Foo.class, myFooPK, props);
        ...

        From within your adapter, or whatever you use to convert a Bar to a URL, you could get the id of the bar by calling getId() and the rest of the object will likely not be loaded (likely, because lazy does not imply not loaded). If this doesn't describe your problem then we can take this discussion offline.

        Show
        mkeith added a comment - From your first comment it sounded like you already had something (e.g. an XMLAdapter or some such thing?) on the relationship attribute to control marshalling a URL instead of the target Bar related object. Assuming that is the case (you need to stop the JAXB marshaller from continuing to traverse the Bar object) then you just need to access the PK of the Bar, right? If this is your usecase then you could do the following: Define a named entity graph for Bar: @Entity public class Bar { @Id long id; public int getId() { return id; } ... } @XmlRootElement @Entity public class Foo { ... @XmlJavaTypeAdapter(MyBarToUrlAdapter.class) @OneToOne Bar bar; ... } and then to read the Foo instance, you would do: Map<String,Object> props = new HashMap<String,Object>(); props.put("javax.persistence.fetchgraph", em.createEntityGraph(Bar.class)); Foo f = em.find(Foo.class, myFooPK, props); ... From within your adapter, or whatever you use to convert a Bar to a URL, you could get the id of the bar by calling getId() and the rest of the object will likely not be loaded (likely, because lazy does not imply not loaded). If this doesn't describe your problem then we can take this discussion offline.
        Hide
        arjan tijms added a comment -

        It would be great if there was a way to just grab the PK of any entity, regardless of how it was loaded.

        After all, we know the PK must be there as JPA uses it to lazy load the associated entity. It's just that the default mapping that we normally do with JPA doesn't give us access to this PK.

        Maybe PersistenceUnitUtil would be an ideal place to put a utility method that can do this. It already contains functionality that's in the same category.

        E.g.

        Given

        @Entity
        public class Foo {
        
            @Id
            Long id;
        
            @OneToOne(fetch = LAZY)
            Bar bar;
        
            // + getters/setters
        }
        
        @Entity
        public class Bar {
        
            @Id
            Long id;
        
            // + getters/setters
        }
        

        We could then grab the PK of Bar given an instance of Foo and an EntityManager em, as follows:

        PersistenceUnitUtil persistenceUnitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
        
        Long barId;
        if (persistenceUnitUtil.isLoaded(foo, "bar") {
            barId = foo.getBar().getId();
        } else {
            barId = persistenceUnitUtil.getId(foo, "bar");
        }
        

        Note that the if/else is just for the example here. PersistenceUnitUtil#getId should be able to grab the Id of a given relation independent of its loaded status.

        An alternative would be to explicitly map an extra instance field in Foo to the FK column in the table to which Foo is mapped, and then use a proprietary "read only" annotation, e.g.

        @Entity
        public class Foo {
        
            @Id
            Long id;
        
            @OneToOne(fetch = LAZY)
            Bar bar;
        
            @Column(name = "bar_id")
            @ReadOnly // made-up proprietary annotation, but many providers have something like this
            Long barId;
        
            // + getters/setters
        }
        

        I've seen this workaround actually being used in practice, but it's not so nice of course.

        Show
        arjan tijms added a comment - It would be great if there was a way to just grab the PK of any entity, regardless of how it was loaded. After all, we know the PK must be there as JPA uses it to lazy load the associated entity. It's just that the default mapping that we normally do with JPA doesn't give us access to this PK. Maybe PersistenceUnitUtil would be an ideal place to put a utility method that can do this. It already contains functionality that's in the same category. E.g. Given @Entity public class Foo { @Id Long id; @OneToOne(fetch = LAZY) Bar bar; // + getters/setters } @Entity public class Bar { @Id Long id; // + getters/setters } We could then grab the PK of Bar given an instance of Foo and an EntityManager em , as follows: PersistenceUnitUtil persistenceUnitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil(); Long barId; if (persistenceUnitUtil.isLoaded(foo, "bar" ) { barId = foo.getBar().getId(); } else { barId = persistenceUnitUtil.getId(foo, "bar" ); } Note that the if/else is just for the example here. PersistenceUnitUtil#getId should be able to grab the Id of a given relation independent of its loaded status. An alternative would be to explicitly map an extra instance field in Foo to the FK column in the table to which Foo is mapped, and then use a proprietary "read only" annotation, e.g. @Entity public class Foo { @Id Long id; @OneToOne(fetch = LAZY) Bar bar; @Column(name = "bar_id" ) @ReadOnly // made-up proprietary annotation, but many providers have something like this Long barId; // + getters/setters } I've seen this workaround actually being used in practice, but it's not so nice of course.
        Hide
        c.beikov added a comment -

        @arjan: Which persistence provider are you using? In hibernate this is no problem and I also don't see why other persistence providers would load the entity just because you access the id. I don't know if the spec says anything about the behavior in that case but it would definitely be nice for portability reasons to have a defined behavior.

        Show
        c.beikov added a comment - @arjan: Which persistence provider are you using? In hibernate this is no problem and I also don't see why other persistence providers would load the entity just because you access the id. I don't know if the spec says anything about the behavior in that case but it would definitely be nice for portability reasons to have a defined behavior.

          People

          • Assignee:
            Unassigned
            Reporter:
            mkarg
          • Votes:
            2 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated: