Enterprise Java Development@TOPIC@

Java Persistence API: Relationship Exercise

Mapping Entity Relationships to the Database

Revision: v2013-08-19

Built on: 2014-03-07 00:06 EST

Abstract

This document contains instructions for exercises to map Java class relations to the database using JPA. It covers the core and corner mapping concepts as well as demonstrates issues that can occur with JPA relationship mappings.


Purpose
1. JPA Entity Exercise Setup
1.1. Setup Maven Project
2. Mapping One-to-One Relationships
2.1. Setup
2.2. One-to-One Uni-directional Relationships
2.2.1. One-to-One Uni-directional Using a Foreign Key
2.2.2. One-to-One Uni-directional Using a Join Table
2.2.3. One-to-One Uni-directional Using a Primary Key Join
2.2.4. One-to-One Uni-directional Using MapsId
2.2.5. One-to-One Uni-directional Using Composite Primary/Foreign Keys
2.3. Mapping One-to-One Bi-directional Relationships
2.3.1. One-to-One Bi-directional Joined By Primary Key
2.3.2. One-to-One Bi-directional 0..1 Owner Relationship
2.3.3. One-to-One Bi-directional 0..1 Inverse Relationship
2.4. One-to-One EntityManager Automated Actions
2.4.1. One-to-One Using Cascades From Owner
2.4.2. One-to-One Using Cascades From Inverse
2.4.3. One-to-One Using Orphan Removal
2.5. Summary
3. Mapping One-to-Many Uni-directional Relationships
3.1. Setup
3.2. One-to-Many Uni-directional
3.2.1. One-to-Many Uni-directional with Join Table
3.2.2. One-to-Many Uni-directional using Foreign Key Join (from Child Table)
3.2.3. One-to-Many Uni-directional Mapping of Simple Types using ElementCollection
3.2.4. One-to-Many Uni-directional Mapping of Embeddable Type using ElementCollection
3.3. One-to-Many Provider Actions
3.3.1. One-to-Many Orphan Removal
3.4. Summary
4. JPA Collections
4.1. Setup
4.2. Entity Identity
4.2.1. Instance Id
4.2.2. Primary Key Id
4.2.3. Switching Ids
4.2.4. Business Id
4.3. Collection Ordering
4.3.1. Ordered Collections
4.4. Collection Interfaces
4.4.1. Using Maps with Collections
4.5. Summary
5. Mapping Many-to-One, Uni-directional Relationships
5.1. Setup
5.2. Many-to-One Uni-directional
5.2.1. Many-to-One Uni-directional Using a Foreign Key
5.2.2. Many-to-One Uni-directional Using a Compound Foreign Key
5.2.3. Many-to-One Uni-directional Using a MapsId
5.3. Summary
6. Mapping One-to-Many/Many-to-One Bi-directional Relationships
6.1. Setup
6.2. One-to-Many Bi-directional using Foreign Key Join
6.3. One-to-Many Bi-directional using Join Table
6.4. One-to-Many Bi-directional using Derived Composite Primary
6.5. Summary
7. Mapping Many-to-Many Relationships
7.1. Setup
7.2. Many-to-Many Uni-directional
7.3. Many-to-Many Bi-directional
7.4. Summary

The setup for this exercise will use a maven archetype process to build you the shell of a JPA module to perform these exercises. The module can be created anywhere, but if you create it within a directory that already has a pom.xml, the archetype will attempt to integrate the new module as a sub-module of the existing parent.

In this chapter we will work thru several ways to relate two entities in a one-to-one relationship. As the name implies each side of the relationship has no more than one instance of the other. That sounds easy -- and it is if we keep in mind that this is a unique relationship (i.e., no other instance has it) from both sides.

The notion of a uni-directional relationship is solely a characterization of what the Java class at either end of the relationship knows about the other. For uni-directional relationships only one class references the other while the other passively participates in the relationship.

In this first case we are going to model the relationship from the owning side of the relationship as a foreign key in the owning entity's table.

  1. Create the following entity class in your src/main tree to represent the passive side of the relationship. I am calling this "passive" (or "ignorant") because it will know nothing of the relationships we will form within this section. This is different than the "inverse" side we will address in the bi-directional case.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * Target of uni-directional relationship
     */
    @Entity
    @Table(name="RELATIONEX_PERSON")
    public class Person {
        @Id @GeneratedValue
        private int id;
        private String name;
        
        public int getId() { return id; }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name; 
        }
    }

    Notice there is no reference to the owning Player class within this entity. This fact alone makes it uni-directional

  2. Create the following entity class in your src/main tree to represent the owning side of the relationship. It is currently incomplete.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * Provides example of one-to-one unidirectional relationship 
     * using foreign key.
     */
    @Entity
    @Table(name="RELATIONEX_PLAYER")
    public class Player {
        public enum Position { DEFENSE, OFFENSE, SPECIAL_TEAMS};
        @Id @GeneratedValue
        private int id;
        @Enumerated(EnumType.STRING)
        @Column(length=16)
        private Position position;
        
        //@OneToOne
        private Person person;
        
        public int getId() { return id; }
        public Person getPerson() { return person; }
        public void setPerson(Person person) {
            this.person = person;
        }
        public Position getPosition() { return position; }
        public void setPosition(Position position) {
            this.position = position;
        }
    }
  3. Add the two entity classes to the persistence unit housed in src/test tree

    
    
        <persistence-unit name="relationEx-test">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            ...
            <class>myorg.relex.one2one.Person</class>
            <class>myorg.relex.one2one.Player</class>
            ...
        </persistence-unit>            
  4. Attempt to build the module and note the error that results. The error is stating the provider does not know how to map the non-serializable Person class to a column within the Player table.

    org.hibernate.MappingException: Could not determine type for: myorg.relex.one2one.Person, at table: 
    RELATIONEX_PLAYER, for columns: [org.hibernate.mapping.Column(person). 
    

    If you look back at the Class mapping topic, we were able to map a serialized relationship to a BLOB column. That is what we are accidentally doing here if we leave off the @XxxToXxx relationship specification.

  5. Add a JPA @OneToOne relationship mapping from the Player to Person. Also include a definitions to...

    
    
        @OneToOne(optional=false,fetch=FetchType.EAGER)
        @JoinColumn(name="PERSON_ID")
        private Person person;
  6. Build the module and observe the database schema generated.

       create table RELATIONEX_PERSON (
            id integer generated by default as identity,
            name varchar(255),
            primary key (id)
        );
    
        create table RELATIONEX_PLAYER (
            id integer generated by default as identity,
            position varchar(16),
            PERSON_ID integer not null,
            primary key (id),
            unique (PERSON_ID)
        );
    
        alter table RELATIONEX_PLAYER 
            add constraint FK58E275714BE1E366 
            foreign key (PERSON_ID) 
            references RELATIONEX_PERSON;
    
  7. Add the following test method to your existing JUnit test case. It is currently incomplete.

    
    
        @Test
        public void testOne2OneUniFK() {
            log.info("*** testOne2OneUniFK ***");
            Person person = new Person();
            person.setName("Johnny Unitas");
            Player player = new Player();
            player.setPerson(person);
            player.setPosition(Player.Position.OFFENSE);
            //em.persist(person);
            em.persist(player); //provider will propagate person.id to player.FK
            
            //clear the persistence context and get new instances
            em.flush(); em.clear();
            Player player2 = em.find(Player.class, player.getId());
            assertEquals("unexpected position", player.getPosition(), player2.getPosition());
            assertEquals("unexpected name", player.getPerson().getName(), player2.getPerson().getName());
        }
  8. Attempt to re-build the module and note the error.

    ./target/surefire-reports/myorg.relex.One2OneTest.txt
    ::::::::::::::
    -------------------------------------------------------------------------------
    Test set: myorg.relex.One2OneTest
    -------------------------------------------------------------------------------
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.874 sec <<< FAILURE!
    testOne2OneUniFK(myorg.relex.One2OneTest)  Time elapsed: 0.171 sec  <<< ERROR!
    java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - 
    save the transient instance before flushing: myorg.relex.one2one.Player.person -> myorg.relex.one2one.Person
            at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1358)
            at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1289)
            at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1295)
            at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:976)
            at myorg.relex.One2OneTest.testOne2OneUniFK(One2OneTest.java:29)
    

    The provider is stating that our test case is trying to persist the Player when the reference to the Person references an unmanaged Person object. We need add a persist of the Person prior to hitting the call to flush.

  9. Update the test method to persist both the Person and Player prior to the flush.

    
    
            em.persist(person);
            em.persist(player);
  10. Rebuild and observe the results of the test method. Note the Person and Player being persisted and the PERSON_ID of the Player being set to the generated primary key value of the Person. During the find(), the Person and Player are both obtained through a database join. Since the Person is required for the Player and we requested an EAGER fetch type, a database inner join is performed between the Player and Person tables.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniFK
    ...
    -*** testOne2OneUniFK ***
    Hibernate: 
        insert 
        into
            RELATIONEX_PERSON
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_PLAYER
            (id, PERSON_ID, position) 
        values
            (null, ?, ?)
    Hibernate: 
        select
            player0_.id as id2_1_,
            player0_.PERSON_ID as PERSON3_2_1_,
            player0_.position as position2_1_,
            person1_.id as id1_0_,
            person1_.name as name1_0_ 
        from
            RELATIONEX_PLAYER player0_ 
        inner join
            RELATIONEX_PERSON person1_ 
                on player0_.PERSON_ID=person1_.id 
        where
            player0_.id=?
    

    If we made the Person optional the database query is converted from an inner join to an outer join -- allowing Players without a Person to be returned.

    
    
        @OneToOne(optional=true,fetch=FetchType.EAGER)
        @JoinColumn(name="PERSON_ID")
        private Person person;
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniFK
    ...
    Hibernate: 
        select
            player0_.id as id2_1_,
            player0_.PERSON_ID as PERSON3_2_1_,
            player0_.position as position2_1_,
            person1_.id as id1_0_,
            person1_.name as name1_0_ 
        from
            RELATIONEX_PLAYER player0_ 
        left outer join
            RELATIONEX_PERSON person1_ 
                on player0_.PERSON_ID=person1_.id 
        where
            player0_.id=?
    

    Also note if we modified the fetch specification to LAZY, the join is removed entirely and replaced with a single select of the Player table during the find() and then a follow-up select of the Person table once we got to the player.getPerson().getName() calls.

    
    
        @OneToOne(optional=false,fetch=FetchType.LAZY)
        @JoinColumn(name="PERSON_ID")
        private Person person;
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniFK
    ...
    Hibernate: 
        select
            player0_.id as id2_0_,
            player0_.PERSON_ID as PERSON3_2_0_,
            player0_.position as position2_0_ 
        from
            RELATIONEX_PLAYER player0_ 
        where
            player0_.id=?
    Hibernate: <<<=== caused by player.getPerson().getName() 
        select
            person0_.id as id1_0_,
            person0_.name as name1_0_ 
        from
            RELATIONEX_PERSON person0_ 
        where
            person0_.id=?
    

    If we comment out the calls to getPerson.getName(), only a single select on the Player is performed and the Person is never retrieved. That is the performance power of LAZY load.

    
    
            //assertEquals("unexpected name", player.getPerson().getName(), player2.getPerson().getName());
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniFK
    ...
    Hibernate: 
        select
            player0_.id as id2_0_,
            player0_.PERSON_ID as PERSON3_2_0_,
            player0_.position as position2_0_ 
        from
            RELATIONEX_PLAYER player0_ 
        where
            player0_.id=?
    
  11. Add the following code to the test method to perform a query of the two tables using SQL in order to verify the expected mappings and values

    
    
            Object[] cols = (Object[]) em.createNativeQuery(
                    "select person.id person_id, person.name, " +
                           "player.id player_id, player.person_id player_person_id " +
                    "from RELATIONEX_PLAYER player " +
                    "join RELATIONEX_PERSON person on person.id = player.person_id " +
                    "where player.id = ?1")
                    .setParameter(1, player.getId())
                    .getSingleResult();
            log.info("row=" + Arrays.toString(cols));
            assertEquals("unexpected person_id", person.getId(), ((Number)cols[0]).intValue());
            assertEquals("unexpected person_name", person.getName(), (String)cols[1]);
            assertEquals("unexpected player_id", player.getId(), ((Number)cols[2]).intValue());
            assertEquals("unexpected player_person_id", person.getId(), ((Number)cols[3]).intValue());
  12. Rebuild the module to verify the SQL mappings is what we expected.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniFK
    ...
    Hibernate: 
        select
            person.id person_id,
            person.name,
            player.id player_id,
            player.person_id player_person_id 
        from
            RELATIONEX_PLAYER player 
        join
            RELATIONEX_PERSON person 
                on person.id = player.person_id 
        where
            player.id = ?
     -row=[1, Johnny Unitas, 1, 1]
    
  13. Add the following delete logic to the test method to remove the Person object. It is currently incomplete.

    
    
            //em.remove(player2);
            em.remove(player2.getPerson());
            em.flush();
            assertNull("person not deleted", em.find(Person.class, person.getId()));
            assertNull("player not deleted", em.find(Player.class, player.getId()));
  14. Attempt to re-build the module and note the error that occurs. The problem is we have attempted to delete the Person row from the database while a foreign key from the Player was still referencing it.

    .Hibernate: 
        delete 
        from
            RELATIONEX_PERSON 
        where
            id=?
    
    /target/surefire-reports/myorg.relex.One2OneTest.txt
    ::::::::::::::
    -------------------------------------------------------------------------------
    Test set: myorg.relex.One2OneTest
    -------------------------------------------------------------------------------
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.551 sec <<< FAILURE!
    testOne2OneUniFK(myorg.relex.One2OneTest)  Time elapsed: 1.103 sec  <<< ERROR!
    javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Referential integrity constraint violation:
     "FK58E275714BE1E366: PUBLIC.RELATIONEX_PLAYER FOREIGN KEY(PERSON_ID) REFERENCES PUBLIC.RELATIONEX_PERSON(ID) (1)"; SQL statement:
    delete from RELATIONEX_PERSON where id=? [23503-168]
            at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1361)
            at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1289)
            at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1295)
            at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:976)
            at myorg.relex.One2OneTest.testOne2OneUniFK(One2OneTest.java:37)
    
  15. Fix the problem by deleting the Player prior to the Person.

            em.remove(player2);
            em.remove(player2.getPerson());
    
  16. Rebuild the module and note the success of the test method and the sensible delete order within the database.

    Hibernate: 
        delete 
        from
            RELATIONEX_PLAYER 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_PERSON 
        where
            id=?
    

We have finished a pass at the first way to hook up a one-to-one, uni-directional relationship by using a foreign key. With that, we also showed the database impact of making the relationship optional and modifying the fetch type. We also purposely created errors common to persisting and deleting obejcts with foreign key references.

Next we are going to realize the one-to-one uni-directional relationship from the dependent to parent entity using a join table. The implementation of the dependent entity is identical to what we did in the FK-join except for changing the @JoinColumn to a @JoinTable

  1. Add the following entity class to your src/main tree. The comments make it incomplete and use a default mapping for the @OneToOne relationship.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * Provides example of one-to-one unidirectional relationship 
     * using join table.
     */
    @Entity
    @Table(name="RELATIONEX_MEMBER")
    public class Member {
        public enum Role { PRIMARY, SECONDARY};
        @Id @GeneratedValue
        private int id;
        
        @OneToOne(optional=false,fetch=FetchType.EAGER)
        /*@JoinTable(name="RELATIONEX_MEMBER_PERSON",
            joinColumns={
                @JoinColumn(name="MEMBER_ID", referencedColumnName="ID"),
            }, inverseJoinColumns={
                @JoinColumn(name="PERSON_ID", referencedColumnName="ID"),
            }
        )*/
        private Person person;
        @Enumerated(EnumType.STRING)
        @Column(length=16)
        private Role role;
        
        protected Member() {}
        public Member(Person person) {
            this.person = person;
        }
        
        public int getId() { return id; }
        public Person getPerson() { return person; }
        public Role getRole() { return role; }
        public void setRole(Role role) {
            this.role = role;
        }
    }
  2. Add the entity to the persistence unit

    
    
            <class>myorg.relex.one2one.Member</class>
  3. Build the module and observe the generated database schema. Notice the default mapping for the relationship is a foreign key join.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_MEMBER (
            id integer generated by default as identity,
            role varchar(16),
            person_id integer not null,
            primary key (id),
            unique (person_id)
        );
    ...
        alter table RELATIONEX_MEMBER 
            add constraint FK5366652A4BE1E366 
            foreign key (person_id) 
            references RELATIONEX_PERSON;
    
  4. Update the mapping to use a a join table using the @JoinTable annotation. The name of the join table is required in this case, but leave the rest of the mapping defaulted at this point.

    
    
        @OneToOne(optional=false,fetch=FetchType.EAGER)
        @JoinTable(name="RELATIONEX_MEMBER_PERSON")/*,
            joinColumns={
                @JoinColumn(name="MEMBER_ID", referencedColumnName="ID"),
            }, inverseJoinColumns={
                @JoinColumn(name="PERSON_ID", referencedColumnName="ID"),
            }
        )*/
        private Person person;
  5. Re-build the module and observe the generated database schema for our new @JoinTable relationship.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_PERSON (
            id integer generated by default as identity,
            name varchar(255),
            primary key (id)
        );
    ...
        create table RELATIONEX_MEMBER (
            id integer generated by default as identity,
            role varchar(16),
            primary key (id)
        );
    
        create table RELATIONEX_MEMBER_PERSON (
            person_id integer not null,
            id integer not null,
            primary key (id),
            unique (person_id)
        );
    ...
        alter table RELATIONEX_MEMBER_PERSON 
            add constraint FK3D65E40A13E64581 
            foreign key (id) 
            references RELATIONEX_MEMBER;
    
        alter table RELATIONEX_MEMBER_PERSON 
            add constraint FK3D65E40A4BE1E366 
            foreign key (person_id) 
            references RELATIONEX_PERSON;
    

    Note...

  6. Finish the @JoinTable mapping by making the join table column mapping explicit.

    
    
        @JoinTable(name="RELATIONEX_MEMBER_PERSON",
            joinColumns={
                @JoinColumn(name="MEMBER_ID", referencedColumnName="ID"),
            }, inverseJoinColumns={
                @JoinColumn(name="PERSON_ID", referencedColumnName="ID"),
            }
        )
        private Person person;
  7. Re-build the module and note the generated database schema for the join table. The columns now have the custom names we assigned.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_MEMBER_PERSON (
            PERSON_ID integer not null,
            MEMBER_ID integer not null,
            primary key (MEMBER_ID),
            unique (PERSON_ID)
        );    
    
  8. Add the following test method to you existing one-to-one test case.

    
    
        @Test
        public void testOne2OneUniJoinTable() {
            log.info("*** testOne2OneUniJoinTable ***");
            Person person = new Person();
            person.setName("Joe Smith");
            Member member = new Member(person);
            member.setRole(Member.Role.SECONDARY);
            em.persist(person);
            em.persist(member); //provider will propagate person.id to player.FK
            
            //clear the persistence context and get new instances
            em.flush(); em.clear();
            Member member2 = em.find(Member.class, member.getId());
            assertEquals("unexpected role", member.getRole(), member2.getRole());
            assertEquals("unexpected name", member.getPerson().getName(), member2.getPerson().getName());
        }        
  9. Build the module, run the new test method, and observe the database output. Notice the extra insert for the join table

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniJoinTable
    ...
     -*** testOne2OneUniJoinTable ***
    Hibernate: 
        insert 
        into
            RELATIONEX_PERSON
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_MEMBER
            (id, role) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_MEMBER_PERSON
            (PERSON_ID, MEMBER_ID) 
        values
            (?, ?)
    Hibernate: 
        select
            member0_.id as id3_1_,
            member0_.role as role3_1_,
            member0_1_.PERSON_ID as PERSON1_4_1_,
            person1_.id as id1_0_,
            person1_.name as name1_0_ 
        from
            RELATIONEX_MEMBER member0_ 
        left outer join
            RELATIONEX_MEMBER_PERSON member0_1_ 
                on member0_.id=member0_1_.MEMBER_ID 
        inner join
            RELATIONEX_PERSON person1_ 
                on member0_1_.PERSON_ID=person1_.id 
        where
            member0_.id=?
    
    

    If you make the relationship optional then the inner join to the Person changes to a left outer join -- allowing us to locate Members that have no Person related.

    
    
        @OneToOne(optional=true,fetch=FetchType.EAGER)
        @JoinTable(name="RELATIONEX_MEMBER_PERSON",
    Hibernate: 
        select
            member0_.id as id3_1_,
            member0_.role as role3_1_,
            member0_1_.PERSON_ID as PERSON1_4_1_,
            person1_.id as id1_0_,
            person1_.name as name1_0_ 
        from
            RELATIONEX_MEMBER member0_ 
        left outer join
            RELATIONEX_MEMBER_PERSON member0_1_ 
                on member0_.id=member0_1_.MEMBER_ID 
        left outer join
            RELATIONEX_PERSON person1_ 
                on member0_1_.PERSON_ID=person1_.id 
        where
            member0_.id=?
    

    If you change from EAGER to LAZY fetch type, the provider then has the option of skipping the two extra tables until the Person is actually needed. Note, however, in the provided output that the provider joined with at least the join table so that it could build a lightweight reference to the Person.

    
    
        @OneToOne(optional=false,fetch=FetchType.LAZY)
        @JoinTable(name="RELATIONEX_MEMBER_PERSON",
    Hibernate: 
        select
            member0_.id as id3_0_,
            member0_.role as role3_0_,
            member0_1_.PERSON_ID as PERSON1_4_0_ 
        from
            RELATIONEX_MEMBER member0_ 
        left outer join
            RELATIONEX_MEMBER_PERSON member0_1_ 
                on member0_.id=member0_1_.MEMBER_ID 
        where
            member0_.id=?
    

    Using LAZY fetch mode, the provider is able to postpone getting the parent object until it is actually requested.

    Hibernate: 
        select
            person0_.id as id1_0_,
            person0_.name as name1_0_ 
        from
            RELATIONEX_PERSON person0_ 
        where
            person0_.id=?
    
  10. Add the following test of the SQL structure to the test method. Here we can assert what we believe the mapping and values should be in the database when forming the one-to-one relationship using the join table.

    
    
            Object[] cols = (Object[]) em.createNativeQuery(
                    "select person.id person_id, person.name, " +
                           "member.id member_id, member.role member_role, " +
                           "link.member_id link_member, link.person_id link_person " +
                    "from RELATIONEX_MEMBER member " +
                    "join RELATIONEX_MEMBER_PERSON link on link.member_id = member.id " +
                    "join RELATIONEX_PERSON person      on link.person_id = person.id " +
                    "where member.id = ?1")
                    .setParameter(1, member.getId())
                    .getSingleResult();
            log.info("row=" + Arrays.toString(cols));
            assertEquals("unexpected person_id", person.getId(), ((Number)cols[0]).intValue());
            assertEquals("unexpected person_name", person.getName(), (String)cols[1]);
            assertEquals("unexpected member_id", member.getId(), ((Number)cols[2]).intValue());
            assertEquals("unexpected member_role", member.getRole().name(), (String)cols[3]);
            assertEquals("unexpected link_member_id", member.getId(), ((Number)cols[4]).intValue());
            assertEquals("unexpected link_person_id", person.getId(), ((Number)cols[5]).intValue());
  11. Re-build the module run the test method of interest, and note the success of our assertions on the schema and the produced values.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniJoinTable
    ...
    Hibernate: 
        select
            person.id person_id,
            person.name,
            member.id member_id,
            member.role member_role,
            link.member_id link_member,
            link.person_id link_person 
        from
            RELATIONEX_MEMBER member 
        join
            RELATIONEX_MEMBER_PERSON link 
                on link.member_id = member.id 
        join
            RELATIONEX_PERSON person      
                on link.person_id = person.id 
        where
            member.id = ?
     -row=[1, Joe Smith, 1, SECONDARY, 1, 1]
    
  12. Add the following cleanup to the test method.

            em.remove(member2);
            em.remove(member2.getPerson());
            em.flush();
            assertNull("person not deleted", em.find(Person.class, person.getId()));
            assertNull("member not deleted", em.find(Member.class, member.getId()));
    
  13. Re-build, not the successful results of our assertions, and the database output. A row is deleted from the Member and join table when the Member is deleted. Person row is deleted when we finally delete the person.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniJoinTable
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_MEMBER_PERSON 
        where
            MEMBER_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_MEMBER 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_PERSON 
        where
            id=?
    ...        
    

We have completed our one-to-one, uni-directional relationship implemented through a join table. It required an extra table, and some more verbose mappings -- but not any structural change to the dependent entity class.

Next we will attempt to remove the separate foreign key column from the dependent table or the separate join table mapping the dependent and parent tables. We will instead map the dependent to the parent using a join of their primary key values. This means that the primary keys of both entities/tables must be the same value. The parent's primary key can be automatically generated -- but the dependent's primary key value must be based on the parent's value. As you will see, that will cause a slight complication in ordering the persists of the two entities.

  1. Add the following entity class to your src/main tree to implement a one-to-one, uni-directional, primary key join. In this entity class, we have replaced the @JoinColumn with a @PrimaryKeyJoinColumn specification. This tells the provider not to create a separate foreign key column in the database and to reuse the primary key column to form the relation to the Person.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * Provides example of one-to-one unidirectional relationship 
     * using a primary key join.
     */
    @Entity
    @Table(name="RELATIONEX_EMPLOYEE")
    public class Employee {
        @Id //pk value must be assigned, not generated
        private int id;
        
        @OneToOne(optional=false,fetch=FetchType.EAGER)
        @PrimaryKeyJoinColumn //informs provider the FK derived from PK
        private Person person;
        @Temporal(TemporalType.DATE)
        private Date hireDate;
        
        protected Employee() {}
        public Employee(Person person) {
            this.person = person;
            if (person != null) { id = person.getId(); }
        }
        public int getId() { return person.getId(); }
        public Person getPerson() { return person; }
        public Date getHireDate() { return hireDate; }
        public void setHireDate(Date hireDate) {
            this.hireDate = hireDate;
        }
    }

    Note...

  2. Add the new entity class to the persistence unit.

    
    
            <class>myorg.relex.one2one.Employee</class>
  3. Build the module and observe the database schema generated. Notice the Employee table does not have a separate foreign key column and its primary key is assigned the duties of the foreign key.

        create table RELATIONEX_EMPLOYEE (
            id integer not null,
            hireDate date,
            primary key (id)
        );
    
        create table RELATIONEX_PERSON (
            id integer generated by default as identity,
            name varchar(255),
            primary key (id)
        );
    
        alter table RELATIONEX_EMPLOYEE 
            add constraint FK813A593E1907563C 
            foreign key (id) 
            references RELATIONEX_PERSON;
    
  4. Add the following test method to your existing one-to-one test case. It is incomplete at this point and will cause an error.

    
    
        @Test
        public void testOne2OneUniPKJ() {
            log.info("*** testOne2OneUniPKJ ***");
            Person person = new Person();
            person.setName("Ozzie Newsome");
            //em.persist(person);
            //em.flush(); //generate the PK for the person
            
            Employee employee = new Employee(person);//set PK/FK -- provider will not auto propagate
            employee.setHireDate(new GregorianCalendar(1996, Calendar.JANUARY, 1).getTime());
            em.persist(person);
            em.persist(employee);
            
            //clear the persistence context and get new instances
            em.flush(); em.clear();
            Employee employee2 = em.find(Employee.class, employee.getPerson().getId());
            log.info("calling person...");
            assertEquals("unexpected name", employee.getPerson().getName(), employee2.getPerson().getName());
        }
  5. Attempt to build and execute the new test method and observe the results. The problem is the the primary key is not being set and the required foreign key is being realized by the unset primary key.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniPKJ
    ...
     -*** testOne2OneUniPKJ ***
    Hibernate: 
        insert 
        into
            RELATIONEX_PERSON
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_EMPLOYEE
            (hireDate, id) 
        values
            (?, ?)
     -SQL Error: 23506, SQLState: 23506
     -Referential integrity constraint violation: "FK813A593E1907563C: PUBLIC.RELATIONEX_EMPLOYEE FOREIGN KEY(ID) 
     REFERENCES PUBLIC.RELATIONEX_PERSON(ID) (0)"; SQL statement:
    insert into RELATIONEX_EMPLOYEE (hireDate, id) values (?, ?) [23506-168]
    
  6. Move the persistence of the parent entity so that it is in place prior to being assigned to the dependent entity. That way the dependent entity will be receiving the primary key value in time for it to be persisted.

    
    
            em.persist(person);
            em.flush(); //generate the PK for the person
            
            Employee employee = new Employee(person);//set PK/FK -- provider will not auto propagate
            employee.setHireDate(new GregorianCalendar(1996, Calendar.JANUARY, 1).getTime());
            //em.persist(person);
            em.persist(employee);
  7. Re-build the module and re-run the test method. It should now be able to persist both entities and successfully pull them back.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniPKJ
    ...
     -*** testOne2OneUniPKJ ***
    Hibernate: 
        insert 
        into
            RELATIONEX_PERSON
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_EMPLOYEE
            (hireDate, id) 
        values
            (?, ?)
    ...
    

    Notice that -- in the primary key join case -- the query to the database uses two separate selects rather than a single select with a join as done with the FK-join case. We can tell the fetch mode is EAGER by the fact that the select for the parent table occurs prior to making a call to the parent.

    Hibernate: 
        select
            employee0_.id as id6_0_,
            employee0_.hireDate as hireDate6_0_ 
        from
            RELATIONEX_EMPLOYEE employee0_ 
        where
            employee0_.id=?
    Hibernate: 
        select
            person0_.id as id1_0_,
            person0_.name as name1_0_ 
        from
            RELATIONEX_PERSON person0_ 
        where
            person0_.id=?
     -calling person...
    ...
    

    If you change the relationship to optional/EAGER, the select changes to a single outer join.

    
    
        @OneToOne(optional=true,fetch=FetchType.EAGER)
        @PrimaryKeyJoinColumn //informs provider the FK derived from PK
        private Person person;
    Hibernate: 
        select
            employee0_.id as id6_1_,
            employee0_.hireDate as hireDate6_1_,
            person1_.id as id1_0_,
            person1_.name as name1_0_ 
        from
            RELATIONEX_EMPLOYEE employee0_ 
        left outer join
            RELATIONEX_PERSON person1_ 
                on employee0_.id=person1_.id 
        where
            employee0_.id=?
     -calling person...
    

    If you change the relationship to required/LAZY you will notice by the location of "calling person..." -- the second select occurs at the point where the parent is being dereferenced and called.

    
    
        @OneToOne(optional=false,fetch=FetchType.LAZY)
        @PrimaryKeyJoinColumn //informs provider the FK derived from PK
        private Person person;
    Hibernate: 
        select
            employee0_.id as id6_0_,
            employee0_.hireDate as hireDate6_0_ 
        from
            RELATIONEX_EMPLOYEE employee0_ 
        where
            employee0_.id=?
     -calling person...
    Hibernate: 
        select
            person0_.id as id1_0_,
            person0_.name as name1_0_ 
        from
            RELATIONEX_PERSON person0_ 
        where
            person0_.id=?
    

    One odd thing of note -- if we change the relationship to optional/LAZY, the provider performs the same type of query as when it was required/EAGER.

    
    
        @OneToOne(optional=true,fetch=FetchType.LAZY)
        @PrimaryKeyJoinColumn //informs provider the FK derived from PK
        private Person person;
    Hibernate: 
        select
            employee0_.id as id6_0_,
            employee0_.hireDate as hireDate6_0_ 
        from
            RELATIONEX_EMPLOYEE employee0_ 
        where
            employee0_.id=?
    Hibernate: 
        select
            person0_.id as id1_0_,
            person0_.name as name1_0_ 
        from
            RELATIONEX_PERSON person0_ 
        where
            person0_.id=?
     -calling person...
    
  8. Add the following to your test method to verify the tables, columns, and values we expect at the raw SQL level.

    
    
            Object[] cols = (Object[]) em.createNativeQuery(
                    "select person.id person_id, person.name, " +
                           "employee.id employee_id " +
                    "from RELATIONEX_EMPLOYEE employee " +
                    "join RELATIONEX_PERSON person on person.id = employee.id " +
                    "where employee.id = ?1")
                    .setParameter(1, employee.getId())
                    .getSingleResult();
            log.info("row=" + Arrays.toString(cols));
            assertEquals("unexpected person_id", person.getId(), ((Number)cols[0]).intValue());
            assertEquals("unexpected person_name", person.getName(), (String)cols[1]);
            assertEquals("unexpected employee_id", employee.getId(), ((Number)cols[2]).intValue());
  9. Rebuild the module and execute the test method to verify the assertions about the raw SQL structure and values.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniPKJ
    ...
    Hibernate: 
        select
            person.id person_id,
            person.name,
            employee.id employee_id 
        from
            RELATIONEX_EMPLOYEE employee 
        join
            RELATIONEX_PERSON person 
                on person.id = employee.id 
        where
            employee.id = ?
     -row=[1, Ozzie Newsome, 1]
    
  10. Add the following cleanup logic and to test the ability to delete the entities and their relationships.

    
    
            em.remove(employee2);
            em.remove(employee2.getPerson());
            em.flush();
            assertNull("person not deleted", em.find(Person.class, person.getId()));
            assertNull("employee not deleted", em.find(Employee.class, employee.getId()));
  11. Re-build the module and verify the ability to delete the dependent and parent entities.

    Hibernate: 
        delete 
        from
            RELATIONEX_EMPLOYEE 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_PERSON 
        where
            id=?
    

You have finished modeling a one-to-one, uni-directional relationship using a primary key join. Using this technique saved the dependent of using a separate foreign key column but created the requirement that the parent entity be persisted first. We also saw how changing the required and fetch mode could impact the underlying quieries to the database. In the next section we will show how a new feature in JPA 2.0 can ease the propagation of the parent primary key to the dependent entity.

JPA 2.0 added a new annotation called @MapsId that can ease the propagation of the parent primary key to the dependent entity. There are several uses of @MapsId. We will first look at its capability to identify the foreign key of a dependent entity as being the source of the primary key value. We saw in the FK-join case where the provider automatically propagates FK values to dependent entities but not PK-joins. Rather than saying the PK realizes the FK. @MapsId seems to state the FK realizes the PK. Lets take a concrete look...

  1. Add the following entity class to your src/main tree. It is incomplete at this point in time.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * This class demonstrates a one-to-one, uni-directional relationship
     * where the foreign key is used to define the primary key with the
     * use of @MapsId
     */
    @Entity
    @Table(name="RELATIONEX_COACH")
    public class Coach {
        public enum Type {HEAD, ASSISTANT };
        @Id //provider sets to FK value with help from @MapsId 
        private int id;
        @OneToOne(optional=false, fetch=FetchType.EAGER)
    //    @MapsId //informs provider the PK is derived from FK
        private Person person;
        @Enumerated(EnumType.STRING) @Column(length=16)
        private Type type;
        public Coach() {}   
        public Coach(Person person) {
            this.person = person;
        }
        
        public int getId() { return person==null ? 0 : person.getId(); }
        public Person getPerson() { return person; }
        public Type getType() { return type; }
        public void setType(Type type) {
            this.type = type;
        }
    }
  2. Add the entity class to the persistence unit.

    
    
            <class>myorg.relex.one2one.Coach</class>
  3. Rebuild the module and take a look at the generated database schema for what was initially defined above. Notice how the dependent table has been define to have both a primary key and a separate foreign key. Lets fix that so there is only a single column to represent the two purposes like what we did for the PK-join case.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_COACH (
            id integer not null,
            type varchar(16),
            person_id integer not null,
            primary key (id),
            unique (person_id)
        );
    ...
        alter table RELATIONEX_COACH 
            add constraint FK75C513EA4BE1E366 
            foreign key (person_id) 
            references RELATIONEX_PERSON;
    
  4. Update the dependent entity class to inform the provider to derive the primary key value from the assigned foreign key relationship using the @MapsId annotation.

    
    
        @Id //provider sets to FK value with help from @MapsId 
        private int id;
        @OneToOne(optional=false, fetch=FetchType.EAGER)
        @MapsId //informs provider the PK is derived from FK
        private Person person;

    If you look back over the entire class design you should notice that the class provides no way to ever assign the @Id except through @MapsId.

  5. Rebuild the module and review the generated database schema. Notice how the provider is now using the column named after the foreign key as the primary key and has eliminated the separate primary key.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
       create table RELATIONEX_COACH (
            type varchar(16),
            person_id integer not null,
            primary key (person_id),
            unique (person_id)
        );
    ...
        alter table RELATIONEX_COACH 
            add constraint FK75C513EA4BE1E366 
            foreign key (person_id) 
            references RELATIONEX_PERSON;
    
  6. Add the following test method to your existing one-to-one test case. Notice the design of the test method persists the parent and dependent class together -- without having to worry about deriving the parent primary key first. That is very convenient.

    
    
        @Test
        public void testOne2OneUniMapsId() {
            log.info("*** testOne2OneUniMapsId ***");
            Person person = new Person();
            person.setName("John Harbaugh");
            Coach coach = new Coach(person);
            coach.setType(Coach.Type.HEAD);
            em.persist(person);
            em.persist(coach); //provider auto propagates person.id to coach.FK mapped to coach.PK 
            
            //flush commands to database, clear cache, and pull back new instance
            em.flush(); em.clear();
            Coach coach2 = em.find(Coach.class, coach.getId());
            log.info("calling person...");
            assertEquals("unexpected name", coach.getPerson().getName(), coach2.getPerson().getName());
        }
  7. Re-build the module and run the the new test method. Notice the provider issues two separate selects; one select each for the dependent and parent entity.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniMapsId
    ...
     -*** testOne2OneUniMapsId ***
    Hibernate: 
        insert 
        into
            RELATIONEX_PERSON
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_COACH
            (type, person_id) 
        values
            (?, ?)
    Hibernate: 
        select
            coach0_.person_id as person2_5_0_,
            coach0_.type as type5_0_ 
        from
            RELATIONEX_COACH coach0_ 
        where
            coach0_.person_id=?
    Hibernate: 
        select
            person0_.id as id1_0_,
            person0_.name as name1_0_ 
        from
            RELATIONEX_PERSON person0_ 
        where
            person0_.id=?
     -calling person...
    
  8. Add the following assertions about the SQL structure and values.

    
    
            Object[] cols = (Object[]) em.createNativeQuery(
                    "select person.id person_id, person.name, " +
                           "coach.person_id coach_id " +
                    "from RELATIONEX_COACH coach " +
                    "join RELATIONEX_PERSON person on person.id = coach.person_id " +
                    "where coach.person_id = ?1")
                    .setParameter(1, coach.getId())
                    .getSingleResult();
            log.info("row=" + Arrays.toString(cols));
            assertEquals("unexpected person_id", person.getId(), ((Number)cols[0]).intValue());
            assertEquals("unexpected person_name", person.getName(), (String)cols[1]);
            assertEquals("unexpected coach_id", coach.getId(), ((Number)cols[2]).intValue());
  9. Rebuild the module, re-run the test method, and observe the results of the new assertions.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniMapsId
    ...
    Hibernate: 
        select
            person.id person_id,
            person.name,
            coach.person_id coach_id 
        from
            RELATIONEX_COACH coach 
        join
            RELATIONEX_PERSON person 
                on person.id = coach.person_id 
        where
            coach.person_id = ?
     -row=[1, John Harbaugh, 1]
    
  10. Add cleanup logic and assertions of the removal of the two entity rows.

    
    
            em.remove(coach2);
            em.remove(coach2.getPerson());
            em.flush();
            assertNull("person not deleted", em.find(Person.class, person.getId()));
            assertNull("coach not deleted", em.find(Coach.class, coach.getId()));
  11. Re-build the module, re-run the test method, and note the successful deletion of the two entity rows.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniMapsId
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_COACH 
        where
            person_id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_PERSON 
        where
            id=?
    

You have completed implementing a one-to-one, uni-directional relationship using a @MapsId to derive the primary key of the dependent entity from the foreign key to the parent entity. This allowed the persist() of the two entities to occur without worrying about a sequencing them in separate actions to the database.

This section will cover cases where one wants to map a one-to-one primary key join to a parent entity that uses a composite primary key. The dependent entity may use either an @IdClass/@PrimaryKeyJoin or an @EmbeddedId/@MapsId to realize this relationship and identity.

  1. To get started, put the following parent class in place in your src/main tree.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class represents the passive side of a one-to-one
     * uni-directional relationship where the parent uses
     * a composite primary key that must be represented in 
     * the dependent entity's relationship.
     */
    @Entity
    @Table(name="RELATIONEX_SHOWEVENT")
    @IdClass(ShowEventPK.class)
    public class ShowEvent {
        @Id
        @Temporal(TemporalType.DATE)
        private Date date;
        @Id
        @Temporal(TemporalType.TIME)
        private Date time;
        @Column(length=20)
        private String name;
        
        public ShowEvent() {}
        public ShowEvent(Date date, Date time) {
            this.date = date;
            this.time = time;
        }
        public Date getDate() { return date; }
        public Date getTime() { return time; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }

    The above entity class uses two properties to form its primary key -- thus it requires a composite primary key to represent the PK within JPA.

  2. Put the following composite primary key in place. It is defined as @Embeddable so that it can be used both as an @IdClass and an @EmbeddableId.

    
    
    package myorg.relex.one2one;
    import java.io.Serializable;
    import java.util.Date;
    import javax.persistence.Embeddable;
    /**
     * This class will be used as an IdClass for the ShowEvent
     * entity.
     */
    @Embeddable
    public class ShowEventPK implements Serializable {
        private static final long serialVersionUID = 1L;
        private Date date;
        private Date time;
        
        protected ShowEventPK(){}
        public ShowEventPK(Date date, Date time) {
            this.date = date;
            this.time = time;
        }
        
        public Date getDate() { return date; }
        public Date getTime() { return time; }
        
        @Override
        public int hashCode() { return date.hashCode() + time.hashCode(); }
        @Override
        public boolean equals(Object obj) {
            try {
                return date.equals(((ShowEventPK)obj).date) &&
                        time.equals(((ShowEventPK)obj).time);
            } catch (Exception ex) { return false; }
        }
    }
  3. Add the above parent entity to the persistence unit.

    
    
                <class>myorg.relex.one2one.ShowEvent</class>
        

Continue on with mapping the dependent entity using an @IdClass and @EmbeddedId. You will find the @IdClass technique acts much like the @PrimaryKeyJoin we performed earlier. The @EmbeddedId technique acts much like the @MapsId case as well.

This sub-section will map the dependent class to the parent using an @IdClass.

  1. Put the following dependent entity class in you src/main tree. It is incomplete at this point and will generate the default mapping for the relationship to the class using the composite PK. Since we eventually want to derive the primary key(s) for this dependent entity from the parent entity -- we also model the same properties as @Id and use and @IdClass to represent the PK within JPA. At this point -- the composite identity of the dependent entity is independent of the relationship.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example of a the owning entity of a
     * one-to-one, uni-directional relationship where the dependent's
     * primary key is derived from the parent and the parent uses
     * a composite primary key.
     */
    @Entity
    @Table(name="RELATIONEX_SHOWTICKETS")
    @IdClass(ShowEventPK.class)
    public class ShowTickets {
        @Id 
        @Temporal(TemporalType.DATE)
        @Column(name="TICKET_DATE")
        private Date date;
        @Id
        @Temporal(TemporalType.TIME)
        @Column(name="TICKET_TIME")
        private Date time;
        
        @OneToOne(optional=false, fetch=FetchType.EAGER)
        /*
        @PrimaryKeyJoinColumns({
            @PrimaryKeyJoinColumn(name="TICKET_DATE", referencedColumnName="date"),
            @PrimaryKeyJoinColumn(name="TICKET_TIME", referencedColumnName="time"),
        })
        */
        private ShowEvent show;
        
        @Column(name="TICKETS")
        private int ticketsLeft;
        public ShowTickets() {}
        public ShowTickets(ShowEvent show) {
            this.date = show.getDate();
            this.time = show.getTime();
            this.show = show;
        }
        public Date getDate() { return show==null ? null : show.getDate(); }
        public Date getTime() { return show==null ? null : show.getTime(); }
        public ShowEvent getShow() { return show; }
        public int getTicketsLeft() { return ticketsLeft; }
        public void setTicketsLeft(int ticketsLeft) {
            this.ticketsLeft = ticketsLeft;
        }
    }
  2. Add the dependent entity class to the persistence unit.

    
    
            <class>myorg.relex.one2one.ShowTickets</class>
  3. Build the module and observe the database schema generated for the entity classes involved. Notice how the dependent table has seemingly duplicate columns. There is a TICKET_DATE/TIME set of columns that represent the dependent entity's composite primary key. There is also a show_date/time set of columns to reference the parent entity -- which also uses a composite primary key. If the referenced entity of a foreign relationship uses a composite primary key -- then the value of the foreign key also expresses a composite set of properties.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_SHOWEVENT (
            date date not null,
            time time not null,
            name varchar(20),
            primary key (date, time)
        );
    
        create table RELATIONEX_SHOWTICKETS (
            TICKET_DATE date not null,
            TICKET_TIME time not null,
            TICKETS integer,
            show_date date not null,
            show_time time not null,
            primary key (TICKET_DATE, TICKET_TIME),
            unique (show_date, show_time)
        );
    ...
        alter table RELATIONEX_SHOWTICKETS 
            add constraint FK93AB7C9AE3196D0 
            foreign key (show_date, show_time) 
            references RELATIONEX_SHOWEVENT;
    
  4. Update the relationship with a default mapping for a @PrimaryKeyJoin.

    
    
        @OneToOne(optional=false, fetch=FetchType.EAGER)
        @PrimaryKeyJoinColumn /*s({
            @PrimaryKeyJoinColumn(name="TICKET_DATE", referencedColumnName="date"),
            @PrimaryKeyJoinColumn(name="TICKET_TIME", referencedColumnName="time"),
        })*/
        private ShowEvent show;
  5. Re-build the module and observe how the default mapping of the @PrimaryKeyJoin was realized when using the composite primary key.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
       create table RELATIONEX_SHOWTICKETS (
            TICKET_DATE date not null,
            TICKET_TIME time not null,
            TICKETS integer,
            primary key (TICKET_DATE, TICKET_TIME)
        );
    ...
        alter table RELATIONEX_SHOWTICKETS 
            add constraint FK93AB7C9A1C31D972 
            foreign key (TICKET_DATE, TICKET_TIME) 
            references RELATIONEX_SHOWEVENT;
    

    In this case, the provider was able to generate default mappings that are exactly what we would have created manually. You could have enabled the following custom mappings to explicitly map primary key column values from the dependent table columns to the parent table columns.

    
    
        @OneToOne(optional=false, fetch=FetchType.EAGER)
        @PrimaryKeyJoinColumns({
            @PrimaryKeyJoinColumn(name="TICKET_DATE", referencedColumnName="date"),
            @PrimaryKeyJoinColumn(name="TICKET_TIME", referencedColumnName="time"),
        })
        private ShowEvent show;

    Note there can only be a single @PrimaryKeyJoin annotated against a method. Multiple @PrimaryKeyJoin columns must be wrapped within a @PrimaryKeyJoinColumns annotation to work.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
          TICKET_DATE date not null,
            TICKET_TIME time not null,
            TICKETS integer,
            primary key (TICKET_DATE, TICKET_TIME)
        );
    ...
        alter table RELATIONEX_SHOWTICKETS 
            add constraint FK93AB7C9A1C31D972 
            foreign key (TICKET_DATE, TICKET_TIME) 
            references RELATIONEX_SHOWEVENT;
    
  6. Add the following test method to your one-to-one test case. Although the @IdClass/@PrimaryKeyJoin is very similar to the @Id/PrimaryKeyJoin covered earlier, this approach is being simplified by the fact the primary key of the parent is not dynamically generated. The relationship assembly can occur as soon as the we derive the natural key values for the parent entity.

    
    
        @Test
        public void testOne2OneUniIdClass() {
            log.info("*** testOneToOneUniIdClass ***");
            Date showDate = new GregorianCalendar(1975+new Random().nextInt(100),
                    Calendar.JANUARY, 1).getTime();
            Date showTime = new GregorianCalendar(0, 0, 0, 0, 0, 0).getTime();
            ShowEvent show = new ShowEvent(showDate, showTime);
            show.setName("Rocky Horror");
            ShowTickets tickets = new ShowTickets(show); //parent already has natural PK by this point
            tickets.setTicketsLeft(300);
            em.persist(show);
            em.persist(tickets);  
            
            //flush commands to database, clear cache, and pull back new instance
            em.flush(); em.clear();
            ShowTickets tickets2 = em.find(ShowTickets.class, new ShowEventPK(tickets.getDate(), tickets.getTime()));
            log.info("calling parent...");
            assertEquals("unexpected name", tickets.getShow().getName(), tickets2.getShow().getName());
        }
  7. Re-build the module and note the creation of the parent and dependent entities.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniIdClass
    ...
     -*** testOne2OneUniIdClass ***
    Hibernate: 
        insert 
        into
            RELATIONEX_SHOWEVENT
            (name, date, time) 
        values
            (?, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_SHOWTICKETS
            (TICKETS, TICKET_DATE, TICKET_TIME) 
        values
            (?, ?, ?)
    

    The provider uses a set of selects to fully assemble our object tree for use.

    Hibernate: 
        select
            showticket0_.TICKET_DATE as TICKET1_8_0_,
            showticket0_.TICKET_TIME as TICKET2_8_0_,
            showticket0_.TICKETS as TICKETS8_0_ 
        from
            RELATIONEX_SHOWTICKETS showticket0_ 
        where
            showticket0_.TICKET_DATE=? 
            and showticket0_.TICKET_TIME=?
    Hibernate: 
        select
            showevent0_.date as date7_0_,
            showevent0_.time as time7_0_,
            showevent0_.name as name7_0_ 
        from
            RELATIONEX_SHOWEVENT showevent0_ 
        where
            showevent0_.date=? 
            and showevent0_.time=?
     -calling parent...
    
  8. Add the following to the test method to verify our assertions about the structure of the database tables and their values related to this example.

    
    
            Object[] cols = (Object[]) em.createNativeQuery(
                    "select show.date show_date, show.time show_time, " +
                           "tickets.ticket_date ticket_date, tickets.ticket_time ticket_time, tickets.tickets " +
                    "from RELATIONEX_SHOWEVENT show " +
                    "join RELATIONEX_SHOWTICKETS tickets on show.date = tickets.ticket_date and show.time = tickets.ticket_time " +
                    "where tickets.ticket_date = ?1 and tickets.ticket_time = ?2")
                    .setParameter(1, tickets.getShow().getDate(), TemporalType.DATE)
                    .setParameter(2, tickets.getShow().getTime(), TemporalType.TIME)
                    .getSingleResult();
            log.info("row=" + Arrays.toString(cols));
            assertEquals("unexpected show_date", tickets2.getShow().getDate(), (Date)cols[0]);
            assertEquals("unexpected show_time", tickets2.getShow().getTime(), (Date)cols[1]);
            assertEquals("unexpected ticket_date", tickets2.getDate(), (Date)cols[2]);
            assertEquals("unexpected ticket_time", tickets2.getTime(), (Date)cols[3]);
            assertEquals("unexpected ticketsLeft", tickets2.getTicketsLeft(), ((Number)cols[4]).intValue());
  9. Re-build the module and observe the success of the SQL portion of the test method.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniIdClass
    ...
    Hibernate: 
        select
            show.date show_date,
            show.time show_time,
            tickets.ticket_date ticket_date,
            tickets.ticket_time ticket_time,
            tickets.tickets 
        from
            RELATIONEX_SHOWEVENT show 
        join
            RELATIONEX_SHOWTICKETS tickets 
                on show.date = tickets.ticket_date 
                and show.time = tickets.ticket_time 
        where
            tickets.ticket_date = ? 
            and tickets.ticket_time = ?
     -row=[2033-01-01, 00:00:00, 2033-01-01, 00:00:00, 300]
    
  10. Add the following cleanup logic and assertions to verify the rows have been deleted for the dependent and parent entities.

    
    
            em.remove(tickets2);
            em.remove(tickets2.getShow());
            em.flush();
            assertNull("tickets not deleted", em.find(ShowEvent.class, 
                    new ShowEventPK(show.getDate(), show.getTime())));
            assertNull("show not deleted", em.find(ShowTickets.class, 
                    new ShowEventPK(tickets.getDate(), tickets.getTime())));
  11. Re-build the module and observe the successful results of the completed test method.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniIdClass
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_SHOWTICKETS 
        where
            TICKET_DATE=? 
            and TICKET_TIME=?
    Hibernate: 
        delete 
        from
            RELATIONEX_SHOWEVENT 
        where
            date=? 
            and time=?
    

You have completed mapping a one-to-one uni-directional relationship that is based on a composite primary in the parent and the composite key mapped in the dependent table as an @IdClass.

In this second example of @MapsId, we will be informing the provider that the primary key for the dependent table is realized by the foreign key and, in this case, is a composite primary key. We must use an @EmbeddedId in order for this to work correctly.

  1. Add the following entity class to your src/main tree. It is not complete at this point and schema generation will show there bring a separate primary and foreign key.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example of a the owning entity of a
     * one-to-one, uni-directional relationship where the dependent's
     * primary key is derived from the parent, the parent uses
     * a composite primary key, and the dependent used an @EmeddedId
     * and @MapsId.
     */
    @Entity
    @Table(name="RELATIONEX_BOXOFFICE")
    public class BoxOffice {
        @EmbeddedId 
        private ShowEventPK pk; //will be set by provider with help of @MapsId
        
        @OneToOne(optional=false, fetch=FetchType.EAGER)
    //    @MapsId //provider maps this composite FK to @EmbeddedId PK value
        private ShowEvent show;
        
        @Column(name="TICKETS")
        private int ticketsLeft;
        protected BoxOffice() {}
        public BoxOffice(ShowEvent show) {
            this.show = show;
        }
        public Date getDate() { return show==null ? null : show.getDate(); }
        public Date getTime() { return show==null ? null : show.getTime(); }
        public ShowEvent getShow() { return show; }
        public int getTicketsLeft() { return ticketsLeft; }
        public void setTicketsLeft(int ticketsLeft) {
            this.ticketsLeft = ticketsLeft;
        }
    }
  2. Add the dependent entity class to the persistence unit.

    
    
            <class>myorg.relex.one2one.BoxOffice</class>
  3. Build the module and observe the generated schema. Notice the separate use of date/time for the primary key and show_date/time for the foreign key.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_BOXOFFICE (
            date timestamp not null,
            time timestamp not null,
            TICKETS integer,
            show_date date not null,
            show_time time not null,
            primary key (date, time),
            unique (show_date, show_time)
        );
        alter table RELATIONEX_BOXOFFICE 
            add constraint FK64CED797E3196D0 
            foreign key (show_date, show_time) 
            references RELATIONEX_SHOWEVENT;
    
  4. Update the dependent table mapping so that the foreign key is used to realize the primary key for the entity. Notice also the class provides no way to set the @EmbeddedId exception thru the @MapsId on the foreign key.

    
    
        @OneToOne(optional=false, fetch=FetchType.EAGER)
        @MapsId //provider maps this composite FK to @EmbeddedId PK value
        private ShowEvent show;
  5. Re-build the module and observe the generated database schema. Note the primary key has now been mapped to the show_date/time foreign key columns.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_BOXOFFICE (
            TICKETS integer,
            show_date date,
            show_time time not null,
            primary key (show_date, show_time),
            unique (show_date, show_time)
        );
        alter table RELATIONEX_BOXOFFICE 
            add constraint FK64CED797E3196D0 
            foreign key (show_date, show_time) 
            references RELATIONEX_SHOWEVENT;
    
  6. Add the following test method to your one-to-one test case.

    
    
        @Test
        public void testOne2OneUniEmbeddedId() {
            log.info("*** testOne2OneUniEmbedded ***");
            Date showDate = new GregorianCalendar(1975+new Random().nextInt(100),
                    Calendar.JANUARY, 1).getTime();
            Date showTime = new GregorianCalendar(0, 0, 0, 0, 0, 0).getTime();
            ShowEvent show = new ShowEvent(showDate, showTime);
            show.setName("Rocky Horror");
            BoxOffice boxOffice = new BoxOffice(show);
            boxOffice.setTicketsLeft(500);
            em.persist(show);
            em.persist(boxOffice); //provider auto propagates parent.cid to dependent.FK mapped to dependent.cid 
            
            //flush commands to database, clear cache, and pull back new instance
            em.flush(); em.clear();
            BoxOffice boxOffice2 = em.find(BoxOffice.class, new ShowEventPK(boxOffice.getDate(), boxOffice.getTime()));
            log.info("calling parent...");
            assertEquals("unexpected name", boxOffice.getShow().getName(), boxOffice2.getShow().getName());
        }
  7. Re-build the module and run the test method above.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneUniEmbeddedId
    ...
     -*** testOne2OneUniEmbedded ***
    Hibernate: 
        insert 
        into
            RELATIONEX_SHOWEVENT
            (name, date, time) 
        values
            (?, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_BOXOFFICE
            (TICKETS, show_date, show_time) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            boxoffice0_.show_date as show2_9_0_,
            boxoffice0_.show_time as show3_9_0_,
            boxoffice0_.TICKETS as TICKETS9_0_ 
        from
            RELATIONEX_BOXOFFICE boxoffice0_ 
        where
            boxoffice0_.show_date=? 
            and boxoffice0_.show_time=?
    Hibernate: 
        select
            showevent0_.date as date7_0_,
            showevent0_.time as time7_0_,
            showevent0_.name as name7_0_ 
        from
            RELATIONEX_SHOWEVENT showevent0_ 
        where
            showevent0_.date=? 
            and showevent0_.time=?
     -calling parent...
  8. Add the following to verify our assertions about the SQL structure and values underlying the JPA abstraction.

    
    
            Object[] cols = (Object[]) em.createNativeQuery(
                    "select show.date show_date, show.time show_time, " +
                           "tickets.show_date ticket_date, tickets.show_time ticket_time, tickets.tickets " +
                    "from RELATIONEX_SHOWEVENT show " +
                    "join RELATIONEX_BOXOFFICE tickets on show.date = tickets.show_date and show.time = tickets.show_time " +
                    "where tickets.show_date = ?1 and tickets.show_time = ?2")
                    .setParameter(1, boxOffice.getShow().getDate(), TemporalType.DATE)
                    .setParameter(2, boxOffice.getShow().getTime(), TemporalType.TIME)
                    .getSingleResult();
            log.info("row=" + Arrays.toString(cols));
            assertEquals("unexpected show_date", boxOffice2.getShow().getDate(), (Date)cols[0]);
            assertEquals("unexpected show_time", boxOffice2.getShow().getTime(), (Date)cols[1]);
            assertEquals("unexpected ticket_date", boxOffice2.getDate(), (Date)cols[2]);
            assertEquals("unexpected ticket_time", boxOffice2.getTime(), (Date)cols[3]);
            assertEquals("unexpected ticketsLeft", boxOffice2.getTicketsLeft(), ((Number)cols[4]).intValue());
  9. Re-build the module and re-run the test method to verify the underlying SQL structure is how assume it to be.

    Hibernate: 
        select
            show.date show_date,
            show.time show_time,
            tickets.show_date ticket_date,
            tickets.show_time ticket_time,
            tickets.tickets 
        from
            RELATIONEX_SHOWEVENT show 
        join
            RELATIONEX_BOXOFFICE tickets 
                on show.date = tickets.show_date 
                and show.time = tickets.show_time 
        where
            tickets.show_date = ? 
            and tickets.show_time = ?
     -row=[1994-01-01, 00:00:00, 1994-01-01, 00:00:00, 500]
    
  10. Add the following removal logic to test that we can remove the two entities and their associated rows.

    
    
            em.remove(boxOffice2);
            em.remove(boxOffice2.getShow());
            em.flush();
            assertNull("tickets not deleted", em.find(ShowEvent.class, 
                    new ShowEventPK(show.getDate(), show.getTime())));
            assertNull("show not deleted", em.find(BoxOffice.class, 
                    new ShowEventPK(boxOffice.getDate(), boxOffice.getTime())));
  11. Observe the output of the deletes. It is consistent with before.

    Hibernate: 
        delete 
        from
            RELATIONEX_BOXOFFICE 
        where
            show_date=? 
            and show_time=?
    Hibernate: 
        delete 
        from
            RELATIONEX_SHOWEVENT 
        where
            date=? 
            and time=?
    

You have not completed the mapping of a one-to-one, uni-directional relationship using a composite key and realized through the use of an @EmbeddedId and @MapsId.

In this chapter, we have so far only addressed uni-directional relationships -- where only one side of the relationship was aware of the other at the Java class level. We can also make our relationships bi-directional for easy navigation to/from either side. This requires no change the database and is only a change at the Java and mapping levels.

In bi-directional relationships, it is important to understand there are two sides/types to the relationship; owning side and inverse side.

Lets start the discussion of one-to-one bi-directional using a set of entities that are pretty much joined at the hip. Their properties have been mapped to separate database tables and Java entity classes, but they will never reference a different instance. For this reason we will assign them a common primary key, join them by that common primary key value, and propagate the primary key to the dependent class using @MapsId.

  1. Add the following inverse side entity class to your src/main tree. It is currently incomplete and will soon cause an error.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * This class provides an example of the inverse side of a
     * one-to-one bi-directional relationship.
     */
    @Entity
    @Table(name="RELATIONEX_APPLICANT")
    public class Applicant {
        @Id @GeneratedValue
        private int id;
        @Column(length=32)
        private String name;
    //  @OneToOne(mappedBy="applicant", //identifies property on owning side
    //          fetch=FetchType.LAZY)
    //  @Transient
        private Application application;
        public Applicant(){}
        public Applicant(int id) {
            this.id = id;
        }
        
        public int getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        public Application getApplication() { return application; }
        public void setApplication(Application application) {
            this.application = application;
        }
    }
  2. Add the following owning side entity class to your src/main tree. It is currently incomplete and will not yet generate the desired primary key mapping we desire in this case.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example of the owning side
     * of a one-to-one, bi-directional relationship.
     */
    @Entity
    @Table(name="RELATIONEX_APPLICATION")
    public class Application {
        @Id
        private int id;
    //    @MapsId //foreign key realizes primary key
        @OneToOne(//lack of mappedBy identifies this as owning side 
                  optional=false, fetch=FetchType.EAGER)
        private Applicant applicant;
        
        @Temporal(TemporalType.DATE)
        private Date desiredStartDate;
        protected Application() {}
        public Application(Applicant applicant) {
            this.applicant = applicant;
            if (applicant != null) { 
                applicant.setApplication(this); //must maintain inverse side 
            }
        }
        
        public int getId() { return id; }
        public Applicant getApplicant() { return applicant; }
        public Date getDesiredStartDate() { return desiredStartDate; }
        public void setDesiredStartDate(Date desiredStartDate) {
            this.desiredStartDate = desiredStartDate;
        }
    }
  3. Add the two entity classes to your persistence unit.

    
    
            <class>myorg.relex.one2one.Applicant</class>
            <class>myorg.relex.one2one.Application</class>
  4. Attempt to build the module and note the error from the provider attempting to map the Application entity properties.

    $ mvn clean process-test-classes
    ...
    org.hibernate.MappingException: Could not determine type for: myorg.relex.one2one.Application, at table: 
    RELATIONEX_APPLICANT, for columns: [org.hibernate.mapping.Column(application)]
    

    The error occurs because...

  5. Lets initially get beyond the error by marking the property as @Transient. This will allow the Java attribute to exist in memory but will not have any mapping to the database. That may be what we ultimately want for some cases, but not here. We are only using @Transient to get back to a stable state while we work through any other mapping issues in front of us.

    
    
        @Transient
        private Application application;
  6. Re-build the module and observe the generated database schema so far. Notice the expected uni-direction behavior has been recreated with our current mapping.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_APPLICANT (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
    
        create table RELATIONEX_APPLICATION (
            id integer not null,
            desiredStartDate date,
            applicant_id integer not null,
            primary key (id),
            unique (applicant_id)
        );
    ...
        alter table RELATIONEX_APPLICATION 
            add constraint FK8B404CA01EF7E92E 
            foreign key (applicant_id) 
            references RELATIONEX_APPLICANT;
    

    Currently we are seeing...

  7. Fix the mapping from the owning/dependent Application to the inverse/parent Applicant entity by adding @MapsId to the owning side definition.

    
    
        @MapsId //foreign key realizes primary key
        @OneToOne(//lack of mappedBy identifies this as owning side 
                  optional=false, fetch=FetchType.EAGER)
        private Applicant applicant;
  8. Re-build the module and observe the generated database schema so far. Notice the former ID primary key column for the owning/dependent Application entity table was removed and its role taken by the APPLICANT_ID foreign key column because of the @MapsId annotation.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_APPLICANT (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
    
        create table RELATIONEX_APPLICATION (
            desiredStartDate date,
            applicant_id integer not null,
            primary key (applicant_id),
            unique (applicant_id)
        );
    ...
        alter table RELATIONEX_APPLICATION 
            add constraint FK8B404CA01EF7E92E 
            foreign key (applicant_id) 
            references RELATIONEX_APPLICANT;
    
  9. Attempt to fix the parent entity by replacing the @Transient specification with a @OneToOne relationship mapping. However, in doing it exactly this way we are causing an error with the database mapping we will soon see...

    
    
        @OneToOne(
    //          mappedBy="applicant", //identifies property on owning side
                fetch=FetchType.LAZY)
    //  @Transient
        private Application application;
  10. Re-build the module and observe the generated database schema. Notice that our "inverse" Applicant entity table has inherited an unwanted database column ("application_applicant_id") and foreign key to the "owning" Application entity table. That circular reference is not a bi-directional relationship -- it is two, independent uni-directional relationships.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
      create table RELATIONEX_APPLICANT (
            id integer generated by default as identity,
            name varchar(32),
            application_applicant_id integer,
            primary key (id)
        );
    
        create table RELATIONEX_APPLICATION (
            desiredStartDate date,
            applicant_id integer not null,
            primary key (applicant_id),
            unique (applicant_id)
        );
    ...
        alter table RELATIONEX_APPLICANT 
            add constraint FK8C43FE52AB28790B 
            foreign key (application_applicant_id) 
            references RELATIONEX_APPLICATION;
    
        alter table RELATIONEX_APPLICATION 
            add constraint FK8B404CA01EF7E92E 
            foreign key (applicant_id) 
            references RELATIONEX_APPLICANT;
    
  11. Fix the mistaken mapping by making the parent entity the inverse side of the relationship using the property "mappedBy".

    
    
        @OneToOne(
                mappedBy="applicant", //identifies property on owning side
                fetch=FetchType.LAZY)
        private Application application;
  12. Re-build the module and observe the generated database schema. We now have the database schema we need to implement a one-to-one, bi-directional relationship realized through a common, generated primary key value.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_APPLICANT (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
       create table RELATIONEX_APPLICATION (
            desiredStartDate date,
            applicant_id integer not null,
            primary key (applicant_id),
            unique (applicant_id)
        );
    ...
       alter table RELATIONEX_APPLICATION 
            add constraint FK8B404CA01EF7E92E 
            foreign key (applicant_id) 
            references RELATIONEX_APPLICANT;
    

    We now have...

  13. Add the following test method to your existing one-to-one test case.

    
    
        @Test
        public void testOne2OneBiPKJ() {
            log.info("*** testOne2OneBiPKJ() ***");
            Applicant applicant = new Applicant();
            applicant.setName("Jason Garret");
            Application application = new Application(applicant);
            application.setDesiredStartDate(new GregorianCalendar(2008, Calendar.JANUARY, 1).getTime());
            em.persist(applicant);   //provider will generate a PK
            em.persist(application); //provider will propogate parent.PK to dependent.FK/PK
         }
  14. Build the module, run the new test method, and notice the database output shows a good bit of what we expect from our uni-directional experience.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiPKJ 
    ...
     -*** testOne2OneBiPKJ() ***
    
    Hibernate: 
        insert 
        into
            RELATIONEX_APPLICANT
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_APPLICATION
            (desiredStartDate, applicant_id) 
        values
            (?, ?)
    
  15. Add the following to the test method to verify the actions to the database when the entities are being found from the owning/dependent side of the relationship. This should be similar to our uni-directional case since we are using the entity class with the foreign key in the find(). However, our mapping seems to cause some additional database interaction.

    
    
            em.flush(); em.clear();
            log.info("finding dependent...");
            Application application2 = em.find(Application.class, application.getId());
            log.info("found dependent...");
            assertTrue("unexpected startDate", 
                    application.getDesiredStartDate().equals(
                    application2.getDesiredStartDate()));
            log.info("calling parent...");
            assertEquals("unexpected name", application.getApplicant().getName(), application2.getApplicant().getName());
  16. Re-build the module, run the new test method, and notice the database output contains three select statements, including an extra select for the owning side after both the owning and inverse sides have been retrieved during the EAGER fetch.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiPKJ 
    ...
    -finding dependent...
    Hibernate: 
        select
            applicatio0_.applicant_id as applicant2_11_0_,
            applicatio0_.desiredStartDate as desiredS1_11_0_ 
        from
            RELATIONEX_APPLICATION applicatio0_ 
        where
            applicatio0_.applicant_id=?
    Hibernate: 
        select
            applicant0_.id as id10_0_,
            applicant0_.name as name10_0_ 
        from
            RELATIONEX_APPLICANT applicant0_ 
        where
            applicant0_.id=?
    Hibernate: 
        select
            applicatio0_.applicant_id as applicant2_11_0_,
            applicatio0_.desiredStartDate as desiredS1_11_0_ 
        from
            RELATIONEX_APPLICATION applicatio0_ 
        where
            applicatio0_.applicant_id=?
     -found dependent...
     -calling parent...
    
  17. Add the following to the test method to verify the actions to the database when the entities are being found from the inverse/parent side of the relationship. This is something we could not do in the uni-directional case since the only one side of the relationship knew about the other.

    
    
            em.flush(); em.clear();
            log.info("finding parent...");
            Applicant applicant2 = em.find(Applicant.class, applicant.getId());
            log.info("found parent...");
            assertEquals("unexpected name", applicant.getName(), applicant2.getName());
            log.info("calling dependent...");
            assertTrue("unexpected startDate", 
                    applicant.getApplication().getDesiredStartDate().equals(
                    applicant2.getApplication().getDesiredStartDate()));
  18. Re-build the module, run the test method, and notice the database output shows the inverse/parent being obtained first by primary key and then the owning/dependent entity being obtained through its foreign key/primary key.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiPKJ 
    ...
     -finding parent...
    Hibernate: 
        select
            applicant0_.id as id10_0_,
            applicant0_.name as name10_0_ 
        from
            RELATIONEX_APPLICANT applicant0_ 
        where
            applicant0_.id=?
    Hibernate: 
        select
            applicatio0_.applicant_id as applicant2_11_0_,
            applicatio0_.desiredStartDate as desiredS1_11_0_ 
        from
            RELATIONEX_APPLICATION applicatio0_ 
        where
            applicatio0_.applicant_id=?
     -found parent...
     -calling dependent...
    
  19. Add the following to the test method to verify delete actions.

    
    
            em.remove(applicant2.getApplication());
            em.remove(applicant2);
            em.flush();
            assertNull("applicant not deleted", em.find(Applicant.class, applicant2.getId()));
            assertNull("application not deleted", em.find(Application.class, applicant2.getApplication().getId()));
  20. Re-build the module and notice the successful deletion.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiPKJ 
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_APPLICATION 
        where
            applicant_id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_APPLICANT 
        where
            id=?
    

The previous case dealt with a 1:1 relationship where the entities were tightly coupled with one another -- sharing the same primary key. In this case we will look at 0..1 relationship that must provide the flexibility to be optional as well as re-assigned.

  1. Add the following inverse/parent entity class to your src/main tree. It is currently incomplete and has a common error that will cause mapping issues in a short while.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * This class is an example of the inverse/parent side of a one-to-one, 
     * bi-directional relationship that allows 0..1 and changing related entities.
     */
    @Entity(name="RelationAuto")
    @Table(name="RELATIONEX_AUTO")
    public class Auto {
        public enum Type { CAR, TRUCK };
        
        @Id @GeneratedValue
        private int id;
        @Enumerated(EnumType.STRING)
        @Column(length=10)
        private Type type;
        
        @OneToOne(
    //          mappedBy="auto", 
                optional=true, fetch=FetchType.LAZY)
        private Driver driver;
        
        public Auto() {}
        public int getId() { return id;}
        public Type getType() { return type; }
        public void setType(Type type) {
            this.type = type;
        }
        public Driver getDriver() { return driver; }
        public void setDriver(Driver driver) {
            this.driver = driver;
        }
    }
  2. Add the following owning/dependent entity class to the src/main tree.

    package myorg.relex.one2one;
    
    import javax.persistence.*;
    
    /**
     * This class provides an example of the owning/dependent side of a one-to-one
     * relationship where the inverse/parent represents a 0..1 or changing relation.
     */
    @Entity
    @Table(name="RELATIONEX_DRIVER")
    public class Driver {
        @Id @GeneratedValue
        private int id;
        @Column(length=20)
        private String name;
        
        @OneToOne(
                optional=false,    //we must have the auto for this driver
                fetch=FetchType.EAGER)
        private Auto auto;
        
        protected Driver() {}
        public Driver(Auto auto) {
            this.auto = auto;
        }
        
        public int getId() { return id; }
    
        public Auto getAuto() { return auto; }
        public void setAuto(Auto auto) { //drivers can switch Autos
            this.auto = auto;
        }
    
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
    
  3. Add the two entity classes to the persistence unit

    
    
            <class>myorg.relex.one2one.Auto</class>
            <class>myorg.relex.one2one.Driver</class>
  4. Build the module and observe the generated database schema. We have repeated the error from the previous section where two uni-directional relationships where defined instead of a single bi-directional relationship. We should have no foreign key relationship from the inverse/parent entity table (AUTO) to the owning/dependent entity table (DRIVER).

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_AUTO (
            id integer generated by default as identity,
            type varchar(10),
            driver_id integer,     <!!!!==== this should not bet here
            primary key (id)
        );
    ...
        create table RELATIONEX_DRIVER (
            id integer generated by default as identity,
            name varchar(20),
            auto_id integer not null,
            primary key (id),
            unique (auto_id)
        );
    ...
        alter table RELATIONEX_AUTO <!!!!==== this should not bet here 
            add constraint FK3558203FB3D04E86 
            foreign key (driver_id) 
            references RELATIONEX_DRIVER;
    ...
        alter table RELATIONEX_DRIVER 
            add constraint FK44C072B81E349026 
            foreign key (auto_id) 
            references RELATIONEX_AUTO;
    
  5. Correct the relationship specification in the inverse/parent entity class by adding a "mappedBy" property that references the incoming property from the owning/dependent entity.

    
    
        @OneToOne(
                mappedBy="auto", 
                optional=true, fetch=FetchType.LAZY)
        private Driver driver;
  6. Re-build the module and notice the correct schema produced.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_AUTO (
            id integer generated by default as identity,
            type varchar(10),
            primary key (id)
        );
    ...
        create table RELATIONEX_DRIVER (
            id integer generated by default as identity,
            name varchar(20),
            auto_id integer not null,
            primary key (id),
            unique (auto_id)
        );
    ...
        alter table RELATIONEX_DRIVER 
            add constraint FK44C072B81E349026 
            foreign key (auto_id) 
            references RELATIONEX_AUTO;
    

    We now have...

  7. Add the following as a new test method in your existing one-to-one test case. It currently has a persistence ordering problem that will cause an error in a following step.

    
    
        @Test
        public void testOne2OneBiOwningOptional() {
            log.info("*** testOne2OneBiOwningOptional() ***");
            Auto auto = new Auto();           //auto is inverse/parent side
            auto.setType(Auto.Type.CAR);
            Driver driver = new Driver(auto); //driver is owning/dependent side
            driver.setName("Danica Patrick");
            auto.setDriver(driver); //application must maintain inverse side
            em.persist(driver);
            em.persist(auto);
        }

    The relationship owner/dependent entity is being persisted before the inverse/parent entity. This means the inverse/parent will be transient and not have a primary key value when the owning/dependent entity is persisted -- thus cause an issue persisting the relationship.

  8. Attempt to build the module, execute the new test method, and observe the error.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiOwningOptional
    ...
     -*** testOne2OneBiOwningOptional() ***
    Hibernate: 
        insert 
        into
            RELATIONEX_DRIVER
            (id, auto_id, name) 
        values
            (null, ?, ?)
     -SQL Error: 23502, SQLState: 23502
     -NULL not allowed for column "AUTO_ID"; SQL statement:
    insert into RELATIONEX_DRIVER (id, auto_id, name) values (null, ?, ?) [23502-168]
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@25824994
     -tearDown() complete, em=org.hibernate.ejb.EntityManagerImpl@25824994
     -closing entity manager factory
     -HHH000030: Cleaning up connection pool [jdbc:h2:tcp://localhost:9092/h2db/ejava]
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.23 sec <<< FAILURE!
    
    Results :
    
    Tests in error: 
      testOne2OneBiOwningOptional(myorg.relex.One2OneTest): org.hibernate.exception.ConstraintViolationException: 
      NULL not allowed for column "AUTO_ID"; SQL statement:(..)
    
  9. Correct the ordering of the persist() requests so the inverse/parent entity is persisted prior to the owning/dependent entity.

    
    
            em.persist(auto);
            em.persist(driver);
  10. Re-build the module, execute the new test method, and observe the successful persist() of both entities.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiOwningOptional
    ...
     -*** testOne2OneBiOwningOptional() ***
    Hibernate: 
        insert 
        into
            RELATIONEX_AUTO
            (id, type) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_DRIVER
            (id, auto_id, name) 
        values
            (null, ?, ?)
    
  11. Add the following to access the pair through the owning/dependent side of the relation.

    
    
            em.flush(); em.clear();
            log.info("finding dependent...");
            Driver driver2 = em.find(Driver.class, driver.getId());
            log.info("found dependent...");
            assertEquals("unexpected name", driver.getName(), driver2.getName());
            log.info("calling parent...");
            assertEquals("unexpected name", driver.getAuto().getType(), driver2.getAuto().getType());
  12. Re-build the module, re-run the test method, and observe the database activity to obtain the entities from the owning/dependent-side. For some reason, the provider makes two selects to fully resolve both the owning and inverse sides of the relationship using EAGER fetch mode from the inverse side. If you look closely at the where clauses...

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiOwningOptional
    ...
     -finding dependent...
    Hibernate: 
        select
            driver0_.id as id13_1_,
            driver0_.auto_id as auto3_13_1_,
            driver0_.name as name13_1_,
            auto1_.id as id12_0_,
            auto1_.type as type12_0_ 
        from
            RELATIONEX_DRIVER driver0_ 
        inner join         <!!!!==== Auto is a required relation for Driver
            RELATIONEX_AUTO auto1_ 
                on driver0_.auto_id=auto1_.id 
        where
            driver0_.id=?  <!!!!==== looking for Driver we asked for in find()
    Hibernate:     
    select
            driver0_.id as id13_1_,
            driver0_.auto_id as auto3_13_1_,
            driver0_.name as name13_1_,
            auto1_.id as id12_0_,
            auto1_.type as type12_0_ 
        from
            RELATIONEX_DRIVER driver0_ 
        inner join
            RELATIONEX_AUTO auto1_ 
                on driver0_.auto_id=auto1_.id 
        where
            driver0_.auto_id=?  <!==== looking for Auto associated with Driver
     -found dependent...
     -calling parent...
    
  13. Add the additional tests to verify access to the entity pair from the inverse/parent side of the relationship.

            em.flush(); em.clear();
            log.info("finding parent...");
            Auto auto2 = em.find(Auto.class, auto.getId());
            log.info("found parent...");
            assertEquals("unexpected type", auto.getType(), auto.getType());
            log.info("calling dependent...");
            assertEquals("unexpected name", auto.getDriver().getName(), auto2.getDriver().getName()); 
    
  14. Re-build the module, re-run the test method, and observe the database output from the additional steps.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiOwningOptional
    ...
     -finding parent...
    Hibernate: 
        select
            auto0_.id as id12_0_,
            auto0_.type as type12_0_ 
        from
            RELATIONEX_AUTO auto0_ 
        where
            auto0_.id=?
    Hibernate: 
        select
            driver0_.id as id13_1_,
            driver0_.auto_id as auto3_13_1_,
            driver0_.name as name13_1_,
            auto1_.id as id12_0_,
            auto1_.type as type12_0_ 
        from
            RELATIONEX_DRIVER driver0_ 
        inner join
            RELATIONEX_AUTO auto1_ 
                on driver0_.auto_id=auto1_.id 
        where
            driver0_.auto_id=?
     -found parent...
     -calling dependent...
    
  15. Add the following to test the 0..1, optional aspects of the Driver relative to the Auto. In this case we are deleting the Driver and should not get one back during the next pull from the database.

            Auto truck = new Auto();
            truck.setType(Auto.Type.TRUCK);
            em.persist(truck);
            driver = em.find(Driver.class, driver.getId()); //get the managed instance
            driver.setAuto(truck);
            truck.setDriver(driver);
            
            em.flush(); em.clear();
            Auto auto3 = em.find(Auto.class, auto.getId());
            Driver driver3 = em.find(Driver.class, driver.getId());
            Auto truck3 = em.find(Auto.class, truck.getId());
            assertNull("driver not removed from auto", auto3.getDriver());
            assertEquals("driver not assigned to truck", truck.getId(), driver3.getAuto().getId());
            assertEquals("truck not assigned to driver", driver.getId(), truck3.getDriver().getId());
    
  16. Re-build the module, re-run the test method, and observe the database output from the additional steps. The Driver has been updated to reference a different Auto object. That means the existing Auto object no longer has a Driver in the database.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiOwningOptional
    ...
    Hibernate: 
        insert 
        into
            RELATIONEX_AUTO
            (id, type) 
        values
            (null, ?)
    Hibernate: 
        update
            RELATIONEX_DRIVER 
        set
            auto_id=?,
            name=? 
        where
            id=?
    ...
    
  17. Add the final cleanup and cleanup sanity checks for this example.

            em.remove(truck3.getDriver());
            em.remove(truck3);
            em.remove(auto3);
            em.flush();
            assertNull("driver not deleted", em.find(Driver.class, truck3.getDriver().getId()));
            assertNull("auto not deleted", em.find(Auto.class, auto.getId()));
            assertNull("truck not deleted", em.find(Auto.class, truck.getId()));
    
  18. Re-build the module, re-run the test method, and notice from the successful delete of the objects using the supplied ordering.

You have finished implementing a one-to-one, bi-directional relationship with 0..1 semantics from the inverse to owning side (i.e., owning side optional and/or changeable). In the next section we will quickly run through an example where we make the owning side mandatory and the inverse side optional.

The previous case the owning/dependent side of the relationship was optional and the inverse/parent side was mandatory. In this quick example we want to switch the database mapping so the owning/dependent side is optional. We will use a copy of the entity classes we used last time except switch owning/inverse sides.

  1. Copy the Auto entity class to Auto2 and change the relationship ownership from inverse to owning. Also assign the entity to a new database table (AUTO2). and be sure to update all references to the Driver class to Driver2.

    
    
    @Entity(name="RelationAuto2")
    @Table(name="RELATIONEX_AUTO2")
    public class Auto2 {
    ...
        @OneToOne(
                optional=true, fetch=FetchType.LAZY)
        private Driver2 driver;    
    ...
        public Driver2 getDriver() { return driver; }
        public void setDriver(Driver2 driver) {
            this.driver = driver;
        }
  2. Copy the Driver entity class to Driver2 and change the relationship ownership from owning to inverse. Also assigned the entity to a new database table (DRIVER2) and be sure to update all references of the Auto class to Auto2.

    
    
    @Entity
    @Table(name="RELATIONEX_DRIVER2")
    public class Driver2 {
    ...
        @OneToOne(mappedBy="driver",//driver is now the inverse side
                optional=false,    //we must have the auto for this driver
                fetch=FetchType.EAGER)
        private Auto2 auto;
        
        protected Driver2() {}
        public Driver2(Auto2 auto) {
            this.auto = auto;
        }    
    ...
        public Auto2 getAuto() { return auto; }
        public void setAuto(Auto2 auto) { //drivers can switch Autos
            this.auto = auto;
        }
    ...    
  3. Add the two new entities to the persistence unit.

    
    
            <class>myorg.relex.one2one.Auto2</class>
            <class>myorg.relex.one2one.Driver2</class>
  4. Build the module and observe the generated database schema. I have included both versions below. Notice...

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_AUTO (
            id integer generated by default as identity,
            type varchar(10),
            primary key (id)
        );
    
        create table RELATIONEX_AUTO2 (
            id integer generated by default as identity,
            type varchar(10),
            driver_id integer,
            primary key (id)
        );
    ...
        create table RELATIONEX_DRIVER (
            id integer generated by default as identity,
            name varchar(20),
            auto_id integer not null,
            primary key (id),
            unique (auto_id)
        );
    
        create table RELATIONEX_DRIVER2 (
            id integer generated by default as identity,
            name varchar(20),
            primary key (id)
        );
    ...
        alter table RELATIONEX_AUTO2 
            add constraint FK75ABE7D3B3D04E86 
            foreign key (driver_id) 
            references RELATIONEX_DRIVER;
    ...
        alter table RELATIONEX_DRIVER 
            add constraint FK44C072B81E349026 
            foreign key (auto_id) 
            references RELATIONEX_AUTO;
    
  5. Copy the previous test method and change all Auto class references to Auto2 and all Driver references to Driver2. You might find it easier to copy the test method in blocks starting with the creates.

    
    
        @Test
        public void testOne2OneBiInverseOptional() {
            log.info("*** testOne2OneBiInverseOptional() ***");
            Auto2 auto = new Auto2();           //auto is owning/dependent side
            auto.setType(Auto2.Type.CAR);
            Driver2 driver = new Driver2(auto); //driver is inverse/parent side
            driver.setName("Danica Patrick");
            auto.setDriver(driver);           //owning side must be set
            em.persist(auto);
            em.persist(driver);
            em.flush();
        }
  6. If you build and run the test method for just the persist() portion you should notice the following results.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
     -*** testOne2OneBiInverseOptional() ***
    Hibernate: 
        insert 
        into
            RELATIONEX_AUTO2
            (id, driver_id, type) 
        values
            (null, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_DRIVER2
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        update
            RELATIONEX_AUTO2 
        set
            driver_id=?,
            type=? 
        where
            id=?
     

    If we reversed to persist of driver and auto...

    
    
            em.persist(driver);
            em.persist(auto);
            em.flush();

    ...we could avoid the extra update call since the foreign key value value for Driver would be known at the time the Auto was persisted in this second case.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
     -*** testOne2OneBiInverseOptional() ***
    Hibernate: 
        insert 
        into
            RELATIONEX_DRIVER2
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_AUTO2
            (id, driver_id, type) 
        values
            (null, ?, ?)
    
  7. Add the following to your test method to obtain the entity pair from the inverse/parent side. Note to change the types from Driver to Driver2 as well as the logging and any comments dealing with inverse, parent, and dependent.

    
    
            em.flush(); em.clear();
            log.info("finding parent...");
            Driver2 driver2 = em.find(Driver2.class, driver.getId());
            log.info("found parent...");
            assertEquals("unexpected name", driver.getName(), driver2.getName());
            log.info("calling dependent...");
            assertEquals("unexpected name", driver.getAuto().getType(), driver2.getAuto().getType());
  8. Re-build the module, re-run the test method, and observe the actions taken with the database when accessing the relationship from the inverse/parent side of the relationship. Notice how the joins have been replaced by multiple selects and we have eliminated the redundancy of database calls using this combination.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
     -finding parent...
    Hibernate: 
        select
            driver2x0_.id as id15_0_,
            driver2x0_.name as name15_0_ 
        from
            RELATIONEX_DRIVER2 driver2x0_ 
        where
            driver2x0_.id=?
    Hibernate: 
        select
            auto2x0_.id as id14_0_,
            auto2x0_.driver_id as driver3_14_0_,
            auto2x0_.type as type14_0_ 
        from
            RELATIONEX_AUTO2 auto2x0_ 
        where
            auto2x0_.driver_id=?
     -found parent...
     -calling dependent...
    
  9. Add the following to obtain the pair of entities from the owning side. Note to change all Auto references to Auto2 and update comments and log statements referencing parent, inverse, and dependent.

    
    
            em.flush(); em.clear();
            log.info("finding dependent...");
            Auto2 auto2 = em.find(Auto2.class, auto.getId());
            log.info("found dependent...");
            assertEquals("unexpected type", auto.getType(), auto.getType());
            log.info("calling parent...");
            assertEquals("unexpected name", auto.getDriver().getName(), auto2.getDriver().getName());
  10. Re-build the module, re-run the test method, and observe the actions taken with the database when accessing the entities from the owning side of the relation. The owning/dependent entity is first located by itself due to the LAZY and optional specification of the Auto. The inverse/parent entity is located and then its reference back to the Auto is independently populated with an extra call.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
     -finding dependent...
    Hibernate: 
        select
            auto2x0_.id as id14_0_,
            auto2x0_.driver_id as driver3_14_0_,
            auto2x0_.type as type14_0_ 
        from
            RELATIONEX_AUTO2 auto2x0_ 
        where
            auto2x0_.id=?
     -found dependent...
     -calling parent...
    Hibernate: 
        select
            driver2x0_.id as id15_0_,
            driver2x0_.name as name15_0_ 
        from
            RELATIONEX_DRIVER2 driver2x0_ 
        where
            driver2x0_.id=?
    Hibernate: 
        select
            auto2x0_.id as id14_0_,
            auto2x0_.driver_id as driver3_14_0_,
            auto2x0_.type as type14_0_ 
        from
            RELATIONEX_AUTO2 auto2x0_ 
        where
            auto2x0_.driver_id=?
    
  11. Add the following to the test method to test changing the Driver from one Auto to another and demonstrating the database interactions that occur now that the relationship is on the Auto side and not the Driver side. Note that with the change in database mapping we must manually clear the relationship from the previous Auto before assigning the Driver to a new Auto. That has not been done below and will cause an error.

    
    
            Auto2 truck = new Auto2();
            truck.setType(Auto2.Type.TRUCK);
            em.persist(truck);
            driver = em.find(Driver2.class, driver.getId()); //get the managed instance
            driver.setAuto(truck);
    //        auto2.setDriver(null);  //must remove reference to former driver
            truck.setDriver(driver);//prior to assigning to new driver for 1:1
            
            em.flush(); em.clear();
            Auto2 auto3 = em.find(Auto2.class, auto.getId());
            Driver2 driver3 = em.find(Driver2.class, driver.getId());
            Auto2 truck3 = em.find(Auto2.class, truck.getId());
            assertNull("driver not removed from auto", auto3.getDriver());
            assertEquals("driver not assigned to truck", truck.getId(), driver3.getAuto().getId());
            assertEquals("truck not assigned to driver", driver.getId(), truck3.getDriver().getId());

    Remember what happened in the previous case. We created a new Auto instance and then updated the Driver.FK to reference that instance. Since the first Auto was no longer referenced, it had no Driver.

  12. Re-build the module, re-run the test method, and observe the difference in database interactions. We again create a new Auto instance and assign it to the Driver. However, with the new database mapping the assignment is within the new Auto. The first Auto instance is still holding holding onto its reference at this point and causing the provider to fail. This is the uniqueness constraint I was talking about earlier when we reviewed the database schema.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
    Hibernate: 
        insert  <!!!!==== create new Auto instance 
        into
            RELATIONEX_AUTO2
            (id, driver_id, type) 
        values
            (null, ?, ?)
    Hibernate:  <!!!!==== relate new Auto to existing Driver  
        update
            RELATIONEX_AUTO2 
        set
            driver_id=?,
            type=? 
        where
            id=?
    ...
    Tests in error: 
      testOne2OneBiInverseOptional(myorg.relex.One2OneTest): org.hibernate.HibernateException: 
      More than one row with the given identifier was found: 1, for class: myorg.relex.one2one.Auto2
    
  13. Update the test method to clear the Driver reference from the first Auto prior to assigning the Driver to the new Auto. This is required because we have a 1:1 relationship and only a single Driver can be referenced by a single Auto. Before we did not do this because the truck.setDriver() was updating the owning side. Now it is updating the inverse side -- which is not of interest to the JPA provider.

    
    
            auto2.setDriver(null);  //must remove reference to former driver
            truck.setDriver(driver);//prior to assigning to new driver for 1:1
  14. Re-build the module, re-run the updated test method, and observe the new interactions with the database that allow the modification to complete. The new owning/dependent entity instance (truck) is created, the first owning/dependent entity instance (auto) is cleared of its driver, and then the new owning/dependent entity instance (truck) us updated to reference the driver.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
    Hibernate: 
        insert   <!!!!== new Auto (truck) is created 
        into
            RELATIONEX_AUTO2
            (id, driver_id, type) 
        values
            (null, ?, ?)
    Hibernate:   <!!!!== existing Auto(auto) is cleared of reference to Driver 
        update
            RELATIONEX_AUTO2 
        set
            driver_id=?,
            type=? 
        where
            id=?
    Hibernate: 
        update   <!!!!== new Auto (truck) updated to reference Driver
            RELATIONEX_AUTO2 
        set
            driver_id=?,
            type=? 
        where
            id=?
    

    We could eliminate one of the database updates by moving the persist() of the truck to after the driver was set.

    
    
            Auto2 truck = new Auto2();
            truck.setType(Auto2.Type.TRUCK);
            driver = em.find(Driver2.class, driver.getId()); //get the managed instance
            driver.setAuto(truck);
            auto2.setDriver(null);  //must remove reference to former driver
            truck.setDriver(driver);//prior to assigning to new driver for 1:1
            em.persist(truck);
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
    Hibernate: 
        insert 
        into
            RELATIONEX_AUTO2
            (id, driver_id, type) 
        values
            (null, ?, ?)
    Hibernate: 
        update
            RELATIONEX_AUTO2 
        set
            driver_id=?,
            type=? 
        where
            id=?
    
  15. Add the following cleanup calls and verification tests to the test method.

    
    
            em.remove(truck3.getDriver());
            em.remove(truck3);
            em.remove(auto3);
            em.flush();
            assertNull("driver not deleted", em.find(Driver.class, truck3.getDriver().getId()));
            assertNull("auto not deleted", em.find(Auto.class, auto.getId()));
            assertNull("truck not deleted", em.find(Auto.class, truck.getId()));
  16. Re-build the module, re-run the test method, and notice the interaction that occurs with the database. The provider allows the Driver to be deleted first -- but first clears the Auto of the foreign key reference and then moves on to the rest of the deletes.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
    Hibernate: 
        update
            RELATIONEX_AUTO2 
        set
            driver_id=?,
            type=? 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_DRIVER2 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_AUTO2 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_AUTO2 
        where
            id=?
    

    We can get rid of the extra database update if we rearrange the deletes to remove the owning/dependent entities first.

    
    
            em.remove(truck3);
            em.remove(auto3);
            em.remove(truck3.getDriver());
            em.flush();
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneBiInverseOptional
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_AUTO2 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_AUTO2 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_DRIVER2 
        where
            id=?
    

You have finished mapping a one-to-one, bi-directional relationship that uses a 0..1 relationship for the inverse/parent side. This caused the foreign key to be moved to the optional (Auto) side of the relationship where the it was allowed to be nullable and had to be kept unique.

In this section will will go through some automated actions your EntityManager provider can do for your application behind the scenes. These actions automate what you would otherwise need to do in extra EntityManager calls or additional tracking of entity use. The first case is broad in scope and applies to cascading actions that occur during the other CRUD actions. The second case is confined to the removal of orphaned parent entities.

In this example we will demonstrate how cascades can be setup and automated from the owning side of a relationship.

  1. Put the following entity class in place in your src/main tree. This class will be the passive/ignorant side of a one-to-one, uni-directional relationship.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example of a recipient of cascade actions. 
     */
    @Entity
    @Table(name="RELATIONEX_LICENSE")
    public class License {
        @Id @GeneratedValue
        private int id;
        @Temporal(TemporalType.DATE)
        private Date renewal;
        
        public int getId() { return id; }
        public Date getRenewal() { return renewal; }
        public void setRenewal(Date renewal) {
            this.renewal = renewal;
        }
    }
  2. Put the following entity clas in place in your src/main tree. This class will be the owning side of a one-to-one, uni-directional relationship. It is currently incomplete and will need to be updated later.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example initiation of cascade actions to a 
     * related entity.
     */
    @Entity
    @Table(name="RELATIONEX_LICAPP")
    public class LicenseApplication {
        @Id @GeneratedValue
        private int id;
        @Temporal(TemporalType.TIMESTAMP)
        private Date updated;
        
        @OneToOne(optional=false, fetch=FetchType.EAGER, 
                cascade={
    //              CascadeType.PERSIST,
    //              CascadeType.DETACH,
    //              CascadeType.REMOVE,
    //              CascadeType.REFRESH,
    //              CascadeType.MERGE
                })
        private License license;
        
        public LicenseApplication() {}
        public LicenseApplication(License license) {
            this.license = license;
        }
        
        public int getId() { return id; }
        public License getLicense() { return license; }
        public Date getUpdated() { return updated; }
        public void setUpdated(Date updated) {
            this.updated = updated;
        }
    }
  3. Add the two new entities to the persistence unit.

    
    
            <class>myorg.relex.one2one.License</class>
            <class>myorg.relex.one2one.LicenseApplication</class>
  4. Build the module and verify the database schema created for this exercise is as follows. The specific schema has very little to do with implementing the JPA cascades, but it is of general interest to any JPA mapping exercise.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
       create table RELATIONEX_LICAPP (
            id integer generated by default as identity,
            updated timestamp,
            license_id integer not null,
            primary key (id),
            unique (license_id)
        );
    
        create table RELATIONEX_LICENSE (
            id integer generated by default as identity,
            renewal date,
            primary key (id)
        );
    ...
        alter table RELATIONEX_LICAPP 
            add constraint FK51E55C6BE67289CE 
            foreign key (license_id) 
            references RELATIONEX_LICENSE;
    
  5. Put the following test method in place in your existing one-to-one test case.

    
    
        @Test
        public void testOne2OneCascadeFromOwner() {
            log.info("*** testOne2OneCascadeFromOwner ***");
            License license = new License();
            license.setRenewal(new GregorianCalendar(2012,1,1).getTime());
            LicenseApplication licapp = new LicenseApplication(license);
            licapp.setUpdated(new Date());
            em.persist(licapp);
            em.flush();
        }
  6. Re-build the module, attempt to run the new unit test, and observe the reported error.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
     -*** testOne2OneCascadeFromOwner ***
    Hibernate: 
        insert 
        into
            RELATIONEX_LICAPP
            (id, license_id, updated) 
        values
            (null, ?, ?)
     -SQL Error: 23502, SQLState: 23502
     -NULL not allowed for column "LICENSE_ID"; SQL statement:
    insert into RELATIONEX_LICAPP (id, license_id, updated) values (null, ?, ?) [23502-168]
    

    The problem is the test method only persisted the licapp and not the license that it references. We could fix this by adding a call to em.persist(license) prior to calling em.persist(licapp), but lets solve this with cascades instead.

  7. It is not always appropriate for the dependent entity to create the missing parent, but in this case we are going to rationalize this it is appropriate -- especially when the instance is there and all we want to to automate the persistence of the overall (small) object tree. Update the relationship in the licapp to cascade persist calls to the related License entity.

    
    
    @Entity
    @Table(name="RELATIONEX_LICAPP")
    public class LicenseApplication {
    ...
        @OneToOne(optional=false, fetch=FetchType.EAGER, 
                cascade={
                    CascadeType.PERSIST
                })
        private License license;
  8. Re-build the module, re-run the test method and observe that the error has gone away and the license is now being automatically persisted with the licapp.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
     -*** testOne2OneCascadeFromOwner ***
    Hibernate: 
        insert 
        into
            RELATIONEX_LICENSE
            (id, renewal) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_LICAPP
            (id, license_id, updated) 
        values
            (null, ?, ?)
    
  9. Put the following test code in place to demonstrate behavior of cascading a call to detach(). This section of the test looks to detach the existing entities and then instantiate new instances within the local cache.

    
    
            assertTrue("licapp was not managed???", em.contains(licapp));
            assertTrue("license was not managed???", em.contains(license));
            em.detach(licapp);
            assertFalse("licapp still managed", em.contains(licapp));
            assertFalse("license still managed", em.contains(license));
            licapp = em.find(LicenseApplication.class, licapp.getId());
            license = licapp.getLicense();
  10. Re-build the module, attempt to re-run the unit test, and observe the following error when asserting the detached state.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
    $ more `find . -name *.txt | grep reports`
    -------------------------------------------------------------------------------
    Test set: myorg.relex.One2OneTest
    -------------------------------------------------------------------------------
    Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 7.046 sec <<< FAILURE!
    testOne2OneCascadeFromOwner(myorg.relex.One2OneTest)  Time elapsed: 0.724 sec  <<< FAILURE!
    java.lang.AssertionError: license still managed
            at org.junit.Assert.fail(Assert.java:93)
            at org.junit.Assert.assertTrue(Assert.java:43)
            at org.junit.Assert.assertFalse(Assert.java:68)
            at myorg.relex.One2OneTest.testOne2OneCascadeFromOwner(One2OneTest.java:529)
    
  11. Fix the immediate error by enabling cascade=DETACH from the licapp to the license entity.

    
    
    @Entity
    @Table(name="RELATIONEX_LICAPP")
    public class LicenseApplication {
    ...
        @OneToOne(optional=false, fetch=FetchType.EAGER, 
                cascade={
                    CascadeType.PERSIST,
                    CascadeType.DETACH
                })
        private License license;
  12. Re-build the module, re-run the test method, and observe how the change allowed the detach() to propagate down to the license entity.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
    [INFO] BUILD SUCCESS
    
  13. Add the following to your test method in order to demonstrate how to cascade refresh() to relationships. The strategy derives a new renewal and modified date/timestamp for the license and licapp entities, updates the database directly through a bulk query, and then synchronizes the entity state with the database using refresh().

    
    
            Date newDate = new GregorianCalendar(2014, 1, 1).getTime();
            Date newUpdate = new Date(licapp.getUpdated().getTime()+1);
            assertEquals("unexpected update count", 1, 
              em.createQuery("update License lic set lic.renewal=:renewal where lic.id=:id")
                .setParameter("renewal", newDate, TemporalType.DATE)
                .setParameter("id", license.getId())
                .executeUpdate());
            assertEquals("unexpected update count", 1, 
                    em.createQuery("update LicenseApplication licapp set licapp.updated=:updated where licapp.id=:id")
                    .setParameter("updated", newUpdate, TemporalType.TIMESTAMP)
                    .setParameter("id", licapp.getId())
                    .executeUpdate());
            assertFalse("unexpected updated value prior to refresh", 
                    licapp.getUpdated().getTime() == newUpdate.getTime());
            assertFalse("unexpected renewal value prior to refresh", 
                    license.getRenewal().getTime() == newDate.getTime());
            log.info("database updated");
            em.refresh(licapp);
            log.info("entities refreshed");
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
            assertTrue(String.format("licapp not refreshed, exp=%s, act=%s", df.format(newUpdate), df.format(licapp.getUpdated())), 
                    licapp.getUpdated().getTime() == newUpdate.getTime());
            assertTrue(String.format("license not refreshed, exp=%s, act=%s", df.format(newDate), df.format(license.getRenewal())), 
                    license.getRenewal().getTime() == newDate.getTime());
  14. Re-build the module and attempt to run the additional tests. This will fail since we only synchonized the licapp with the database state.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
     -database updated
    Hibernate: 
        select
            licenseapp0_.id as id17_0_,
            licenseapp0_.license_id as license3_17_0_,
            licenseapp0_.updated as updated17_0_ 
        from
            RELATIONEX_LICAPP licenseapp0_ 
        where
            licenseapp0_.id=?
     -entities refreshed
    ...
    Failed tests:   testOne2OneCascadeFromOwner(myorg.relex.One2OneTest): 
     license not refreshed, exp=2014-02-01 00:00:00.000-0500, act=2012-02-01 00:00:00.000-0500
    
  15. Correct the issue by adding cascade=REFRESH to the relationship in the licapp entity.

    
    
    @Entity
    @Table(name="RELATIONEX_LICAPP")
    public class LicenseApplication {
    ...
        @OneToOne(optional=false, fetch=FetchType.EAGER, 
                cascade={
                    CascadeType.PERSIST,
                    CascadeType.DETACH,
                    CascadeType.REFRESH
                })
        private License license;
  16. Re-build the module, re-run the test method, and observe the successful results for the refresh() being cascaded to the license. Notice how both entities are re-fetched from the database.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
     -database updated
    Hibernate: 
        select
            license0_.id as id16_0_,
            license0_.renewal as renewal16_0_ 
        from
            RELATIONEX_LICENSE license0_ 
        where
            license0_.id=?
    Hibernate: 
        select
            licenseapp0_.id as id17_1_,
            licenseapp0_.license_id as license3_17_1_,
            licenseapp0_.updated as updated17_1_,
            license1_.id as id16_0_,
            license1_.renewal as renewal16_0_ 
        from
            RELATIONEX_LICAPP licenseapp0_ 
        inner join
            RELATIONEX_LICENSE license1_ 
                on licenseapp0_.license_id=license1_.id 
        where
            licenseapp0_.id=?
     -entities refreshed
    ...
    [INFO] BUILD SUCCESS
    
  17. Add the following to your test method to demonstrate the ability to cascade merge() calls thru relationships. The code updates two detached entities, merges them using the entity manager, and then checks the state of the resultant managed entities.

    
    
            em.detach(licapp);
            newDate = new GregorianCalendar(2016, 1, 1).getTime();
            newUpdate = new Date(licapp.getUpdated().getTime()+1);
            assertFalse("licapp still managed", em.contains(licapp));
            assertFalse("license still managed", em.contains(licapp.getLicense()));
            licapp.setUpdated(newUpdate);
            licapp.getLicense().setRenewal(newDate);
            log.info("merging changes to detached entities");
            licapp=em.merge(licapp);
            em.flush();
            log.info("merging complete");
            assertTrue("merged licapp not managed", em.contains(licapp));
            assertTrue("merged licapp.license not managed", em.contains(licapp.getLicense()));
            assertTrue(String.format("licapp not merged, exp=%s, act=%s", df.format(newUpdate), df.format(licapp.getUpdated())), 
                    licapp.getUpdated().getTime() == newUpdate.getTime());
            assertTrue(String.format("license not merged, exp=%s, act=%s", df.format(newDate), df.format(license.getRenewal())), 
                    licapp.getLicense().getRenewal().getTime() == newDate.getTime());
  18. Re-build the module, re-run the test method, and observe the error that occurs. The problem is that only changes to the licapp are considered during the merge. Any changes to license is ignored.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
     -merging changes to detached entities
    Hibernate: 
        select
            licenseapp0_.id as id17_0_,
            licenseapp0_.license_id as license3_17_0_,
            licenseapp0_.updated as updated17_0_ 
        from
            RELATIONEX_LICAPP licenseapp0_ 
        where
            licenseapp0_.id=?
    Hibernate: 
        select
            license0_.id as id16_0_,
            license0_.renewal as renewal16_0_ 
        from
            RELATIONEX_LICENSE license0_ 
        where
            license0_.id=?
    Hibernate: 
        update
            RELATIONEX_LICAPP 
        set
            license_id=?,
            updated=? 
        where
            id=?
     -merging complete
    ...
    Failed tests:   testOne2OneCascadeFromOwner(myorg.relex.One2OneTest): license not merged, exp=2016-02-01 00:00:00.000-0500, act=2016-02-01 00:00:00.000-0500
    
  19. Attempt to fix the issue by adding cascade=MERGE to the relationship defined in licapp.

    
    
    @Entity
    @Table(name="RELATIONEX_LICAPP")
    public class LicenseApplication {
    ...
        @OneToOne(optional=false, fetch=FetchType.EAGER, 
                cascade={
                    CascadeType.PERSIST,
                    CascadeType.DETACH,
                    CascadeType.REFRESH,
                    CascadeType.MERGE
                })
  20. Re-build the module, re-run the test method, and observe how the test now passes. Updates from licapp and license are issued to the database.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
     -merging changes to detached entities
    Hibernate: 
        select
            licenseapp0_.id as id17_1_,
            licenseapp0_.license_id as license3_17_1_,
            licenseapp0_.updated as updated17_1_,
            license1_.id as id16_0_,
            license1_.renewal as renewal16_0_ 
        from
            RELATIONEX_LICAPP licenseapp0_ 
        inner join
            RELATIONEX_LICENSE license1_ 
                on licenseapp0_.license_id=license1_.id 
        where
            licenseapp0_.id=?
    Hibernate: 
        update
            RELATIONEX_LICENSE 
        set
            renewal=? 
        where
            id=?
    Hibernate: 
        update
            RELATIONEX_LICAPP 
        set
            license_id=?,
            updated=? 
        where
            id=?
     -merging complete
    
  21. Add the following to your test method to demonstrate cascades for the remove() method. In this code we attempt to remove just the licapp but expect both the licapp and related license to be deleted from the database when complete.

            em.remove(licapp);
            em.flush();
            assertNull("licapp not deleted", em.find(LicenseApplication.class, licapp.getId()));
            assertNull("licapp.license not deleted", em.find(License.class, licapp.getLicense().getId()));
    
  22. Re-build the module, attempt to run the updated test method, and observe the following error. The trouble is only the licapp is deleted from the database and the license is being ignored.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_LICAPP 
        where
            id=?
    ...
    Failed tests:   testOne2OneCascadeFromOwner(myorg.relex.One2OneTest): licapp.license not deleted
    
  23. Attempt to fix the problem by adding cascade=DELETE to the relationship defined within licapp.

    
    
    @Entity
    @Table(name="RELATIONEX_LICAPP")
    public class LicenseApplication {
    ...
        @OneToOne(optional=false, fetch=FetchType.EAGER, 
                cascade={
                    CascadeType.PERSIST,
                    CascadeType.DETACH,
                    CascadeType.REMOVE,
                    CascadeType.REFRESH,
                    CascadeType.MERGE
                })
        private License license;
  24. Re-build the module, re-run the test method, and observe how the tests now pass. Both the licapp and license are being deleted during the call to remove().

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromOwner
    ...
    Hibernate: 
        delete 
        from
            RELATIONEX_LICAPP 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_LICENSE 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished implementing cascade functionality from the owning side of a one-to-one relationship. As you can see, the cascade capability can save extract calls to the EntityManager when there is a large object graph to be persisted or acted on in other ways. However, as you can also guess -- not every call should be cascaded at all times. Use this capability wisely as you can encounter many situations where the cascade could lead to severe issues or at least poorer performance than desired.

In this example we will demonstrate how cascades can be setup and automated from the inverse side of a relationship.

  1. Add the following inverse/parent entity to your src/main source tree. It is currently incomplete and will cause errors later when we attempt to cascade entity manager commands from the inverse to owning side.

    
    
    package myorg.relex.one2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This entity class provides an example of cascades being originated from
     * the inverse side of a bi-directional relationship.
     */
    @Entity
    @Table(name="RELATIONEX_TICKET")
    public class Ticket {
        @Id @GeneratedValue
        private int id;
        
        @OneToOne(mappedBy="ticket", fetch=FetchType.EAGER,
                cascade={
    //          CascadeType.PERSIST,
    //          CascadeType.DETACH,
    //          CascadeType.REFRESH,
    //          CascadeType.MERGE,
    //          CascadeType.REMOVE
            })
        private Passenger passenger;
        
        @Temporal(TemporalType.DATE)
        Date date;
        public Ticket(){}
        public Ticket(int id) { this.id = id; }
        public int getId() { return id; }
        public Passenger getPassenger() { return passenger; }
        public void setPassenger(Passenger passenger) {
            this.passenger = passenger;
        }
        public Date getDate() { return date; }
        public void setDate(Date date) {
            this.date = date;
        }   
    }
  2. Add the following owning/dependent entity class to your src/main tree. Although this class will own the foreign key and JPA relation, it is designed to not initiate any cascade calls from the owning side to the inverse side.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * This entity class provides an example of the owning side of a 
     * bi-directional relation where all cascades are being initiated
     * from the inverse side (i.e., not from here).
     */
    @Entity
    @Table(name="RELATIONEX_PASSENGER")
    public class Passenger {
        @Id @GeneratedValue
        private int id;
        
        @OneToOne(optional=false)
        private Ticket ticket;
        @Column(length=32, nullable=false)
        private String name;
        
        protected Passenger() {}
        public Passenger(int id) { this.id = id; }
        public Passenger(Ticket ticket, String name) {
            this.ticket = ticket;
            this.name = name;
        }
        
        public int getId() { return id; }
        public Ticket getTicket() { return ticket; }
        public void setTicket(Ticket ticket) {
            this.ticket = ticket;
        }
        
        public String getName() { return name;}
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the two entity classes to the persistence unit.

    
    
            <class>myorg.relex.one2one.Ticket</class>
            <class>myorg.relex.one2one.Passenger</class>
  4. Add the following test method to your existing one-to-one JUnit test case. At this point in time we are testing the ability to cascade the PERSIST call from the inverse/parent side of the relation to the owning/dependent side.

    
    
        @Test
        public void testOne2OneCascadeFromInverse() {
            log.info("*** testOne2OneCascadeFromInverse ***");
            Ticket ticket = new Ticket();
            ticket.setDate(new GregorianCalendar(2013, Calendar.MARCH, 16).getTime());
            Passenger passenger = new Passenger(ticket, "Fred"); //set inverse side
            ticket.setPassenger(passenger);                //set the owning side
            em.persist(ticket);                            //persist from inverse side
            em.flush();
            assertTrue("ticket not managed", em.contains(ticket));
            assertTrue("passenger not managed", em.contains(passenger));
        }
  5. Build the module and attempt to run the new unit test. The test will fail at this point because the inverse side has not been configured to cascade the PERSIST call to the owning side.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -*** testOne2OneCascadeFromInverse ***
    Hibernate: 
        insert 
        into
            RELATIONEX_TICKET
            (id, date) 
        values
            (null, ?)
    ...
      testOne2OneCascadeFromInverse(myorg.relex.One2OneTest): org.hibernate.TransientObjectException: 
      object references an unsaved transient instance - save the transient instance before flushing: 
      myorg.relex.one2one.Ticket.passenger -> myorg.relex.one2one.Passenger
    
  6. Fix the inverse/parent side of the relationship by adding a cascade of PERSIST.

    
    
        @OneToOne(mappedBy="ticket", fetch=FetchType.EAGER,
                cascade={
              CascadeType.PERSIST,
    //          CascadeType.DETACH,
    //          CascadeType.REFRESH,
    //          CascadeType.MERGE,
    //          CascadeType.REMOVE
            })
        private Passenger passenger;
  7. Rebuild the module and re-run the unit test to verify the persist is now being cascaded from the inverse to owning side of the relationship.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -*** testOne2OneCascadeFromInverse ***
    Hibernate: 
        insert 
        into
            RELATIONEX_TICKET
            (id, date) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_PASSENGER
            (id, name, ticket_id) 
        values
            (null, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  8. Add the following code snippet to the test method to verify the capability to cascade a DETACH from the inverse to owning side of a relationship.

    
    
            log.debug("detach both instances from the persistence context");
            em.detach(ticket);
            assertFalse("ticket managed", em.contains(ticket));
            assertFalse("passenger managed", em.contains(passenger));
  9. Rebuild and attempt to re-run the test method. The test method will fail because the inverse side is not configured to cascade the DETACH.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
    -detach both instances from the persistence context
    ...
    Failed tests:   testOne2OneCascadeFromInverse(myorg.relex.One2OneTest): passenger managed
    
  10. Fix the inverse side by adding a cascade of DETACH to the relationship.

    
    
        @OneToOne(mappedBy="ticket", fetch=FetchType.EAGER,
                cascade={
                CascadeType.PERSIST,
                CascadeType.DETACH,
    //          CascadeType.REFRESH,
    //          CascadeType.MERGE,
    //          CascadeType.REMOVE
            })
        private Passenger passenger;
  11. Rebuild and re-run the test method with the correction in place. This should now pass.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -detach both instances from the persistence context
    ...
    [INFO] BUILD SUCCESS
    
  12. Add the following code snippet to your test method to demonstrate the capability to cascade a REFRESH.

    
    
            log.debug("perform a bulk update to both objects");
            ticket = em.find(Ticket.class, ticket.getId());
            Date newDate=new GregorianCalendar(2013, Calendar.APRIL, 1).getTime();
            String newName = "Frederick";
            em.createQuery("update Ticket t set t.date=:date")
              .setParameter("date", newDate,TemporalType.DATE)
              .executeUpdate();
            em.createQuery("update Passenger p set p.name=:name where p.name='Fred'")
               .setParameter("name", newName)
               .executeUpdate();
            assertFalse("unexpected date", newDate.equals(ticket.getDate()));
            assertFalse("unexpected name", newName.equals(ticket.getPassenger().getName()));
            em.refresh(ticket);
            assertTrue("date not refreshed", newDate.equals(ticket.getDate()));
            assertTrue("name not refreshed", newName.equals(ticket.getPassenger().getName()));
  13. Rebuild the module and attempt to re-run the test method. It should fail at this point because your inverse side is not configured to cascade the REFRESH. Notice how only the state for the ticket was retrieved from the database during the refresh() call.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
    -perform a bulk update to both objects
    Hibernate: 
        select
            ticket0_.id as id18_1_,
            ticket0_.date as date18_1_,
            passenger1_.id as id19_0_,
            passenger1_.name as name19_0_,
            passenger1_.ticket_id as ticket3_19_0_ 
        from
            RELATIONEX_TICKET ticket0_ 
        left outer join
            RELATIONEX_PASSENGER passenger1_ 
                on ticket0_.id=passenger1_.ticket_id 
        where
            ticket0_.id=?
    Hibernate: 
        update
            RELATIONEX_TICKET 
        set
            date=?
    Hibernate: 
        update
            RELATIONEX_PASSENGER 
        set
            name=? 
        where
            name='Fred'
    Hibernate: 
        select
            ticket0_.id as id18_0_,
            ticket0_.date as date18_0_ 
        from
            RELATIONEX_TICKET ticket0_ 
        where
            ticket0_.id=?
    ...
    Failed tests:   testOne2OneCascadeFromInverse(myorg.relex.One2OneTest): name not refreshed
    
  14. Fix the issue by configuring the inverse side to cascade the REFRESH so that both entities will be updated with the state of the database when the inverse side is refreshed.

    
    
        @OneToOne(mappedBy="ticket", fetch=FetchType.EAGER,
                cascade={
                CascadeType.PERSIST,
                CascadeType.DETACH,
                CascadeType.REFRESH,
    //          CascadeType.MERGE,
    //          CascadeType.REMOVE
            })
        private Passenger passenger;
  15. Rebuild the module and re-run the test method. It should pass at this point. Notice how both the state for the inverse and owning side is retrieved from the database during the refresh of the inverse side.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -perform a bulk update to both objects
    Hibernate: 
        select
            ticket0_.id as id18_1_,
            ticket0_.date as date18_1_,
            passenger1_.id as id19_0_,
            passenger1_.name as name19_0_,
            passenger1_.ticket_id as ticket3_19_0_ 
        from
            RELATIONEX_TICKET ticket0_ 
        left outer join
            RELATIONEX_PASSENGER passenger1_ 
                on ticket0_.id=passenger1_.ticket_id 
        where
            ticket0_.id=?
    Hibernate: 
        update
            RELATIONEX_TICKET 
        set
            date=?
    Hibernate: 
        update
            RELATIONEX_PASSENGER 
        set
            name=? 
        where
            name='Fred'
    Hibernate: 
        select
            passenger0_.id as id19_0_,
            passenger0_.name as name19_0_,
            passenger0_.ticket_id as ticket3_19_0_ 
        from
            RELATIONEX_PASSENGER passenger0_ 
        where
            passenger0_.id=?
    Hibernate: 
        select
            ticket0_.id as id18_1_,
            ticket0_.date as date18_1_,
            passenger1_.id as id19_0_,
            passenger1_.name as name19_0_,
            passenger1_.ticket_id as ticket3_19_0_ 
        from
            RELATIONEX_TICKET ticket0_ 
        left outer join
            RELATIONEX_PASSENGER passenger1_ 
                on ticket0_.id=passenger1_.ticket_id 
        where
            ticket0_.id=?
    Hibernate: 
        select
            passenger0_.id as id19_1_,
            passenger0_.name as name19_1_,
            passenger0_.ticket_id as ticket3_19_1_,
            ticket1_.id as id18_0_,
            ticket1_.date as date18_0_ 
        from
            RELATIONEX_PASSENGER passenger0_ 
        inner join
            RELATIONEX_TICKET ticket1_ 
                on passenger0_.ticket_id=ticket1_.id 
        where
            passenger0_.ticket_id=?
    ...
    [INFO] BUILD SUCCESS
    
  16. Add the following code snippet to your test method to demonstrate the ability to cascade a MERGE from the inverse side.

    
    
            log.debug("merging changes from inverse side");
            Ticket ticket2 = new Ticket(ticket.getId());
            ticket2.setDate(new GregorianCalendar(2014, Calendar.APRIL, 1).getTime());
            Passenger passenger2 = new Passenger(passenger.getId());
            passenger2.setName("Rick");
            ticket2.setPassenger(passenger2);
            passenger2.setTicket(ticket2);
            assertFalse("unexpected date", ticket2.getDate().equals(ticket.getDate()));
            assertFalse("unexpected name", ticket2.getPassenger().getName().equals(ticket.getPassenger().getName()));
            ticket=em.merge(ticket2);
            em.flush();
            assertTrue("date not merged", ticket2.getDate().equals(ticket.getDate()));
            assertTrue("name not merged", ticket2.getPassenger().getName().equals(ticket.getPassenger().getName()));
  17. Rebuild the module and attempt to run the updated test method. This will fail because the inverse side is not yet configured to cascade the MERGE. Notice how only the inverse side is being updated and not the owning entity.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -merging changes from inverse side
    Hibernate: 
        select
            passenger0_.id as id19_1_,
            passenger0_.name as name19_1_,
            passenger0_.ticket_id as ticket3_19_1_,
            ticket1_.id as id18_0_,
            ticket1_.date as date18_0_ 
        from
            RELATIONEX_PASSENGER passenger0_ 
        inner join
            RELATIONEX_TICKET ticket1_ 
                on passenger0_.ticket_id=ticket1_.id 
        where
            passenger0_.ticket_id=?
     -tearDown() started, em=org.hibernate.ejb.EntityManagerImpl@5dfadfd6
    Hibernate: 
        update
            RELATIONEX_TICKET 
        set
            date=? 
        where
            id=?
    ...
    Failed tests:   testOne2OneCascadeFromInverse(myorg.relex.One2OneTest): name not merged
    
  18. Fix the issue by configuring the inverse side to cascade the MERGE.

    
    
        @OneToOne(mappedBy="ticket", fetch=FetchType.EAGER,
                cascade={
                CascadeType.PERSIST,
                CascadeType.DETACH,
                CascadeType.REFRESH,
                CascadeType.MERGE,
    //          CascadeType.REMOVE
            })
        private Passenger passenger;
  19. Rebuild the module and re-run the test method. The test method should now pass because to MERGE is bing cascaded from the inverse to owning side of the relation. Notice how both sides are being updated.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
    Hibernate: 
        update
            RELATIONEX_PASSENGER 
        set
            name=?,
            ticket_id=? 
        where
            id=?
    Hibernate: 
        update
            RELATIONEX_TICKET 
        set
            date=? 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    
  20. Add the following code snippet to your test method to demonstrate the cascade from the inverse side.

    
    
            log.debug("delete the entities from the inverse side");
            assertNotNull("ticket not found", em.find(Ticket.class, ticket.getId()));
            assertNotNull("passenger not found", em.find(Passenger.class, ticket.getPassenger().getId()));
            em.remove(ticket);
            em.flush();
            assertNull("ticket not removed", em.find(Ticket.class, ticket.getId()));
            assertNull("passenger not removed", em.find(Passenger.class, ticket.getPassenger().getId()));
  21. Rebuild the module and attempt to run the updated test method. This will fail because the inverse side is not configured to cascade the DELETE. Notice how only the inverse side is being deleted from the database during the call to remove.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -delete the entities from the inverse side
    Hibernate: 
        delete 
        from
            RELATIONEX_TICKET 
        where
            id=?
     -SQL Error: 23503, SQLState: 23503
     -Referential integrity constraint violation: "FKD08633EA1BCEB406: PUBLIC.RELATIONEX_PASSENGER FOREIGN KEY(TICKET_ID) 
     REFERENCES PUBLIC.RELATIONEX_TICKET(ID) (1)"; SQL statement:
    delete from RELATIONEX_TICKET where id=? [23503-168]
    
  22. Fix the issue by configuring the inverse side to cascade the DELETE.

    
    
        @OneToOne(mappedBy="ticket", fetch=FetchType.EAGER,
                cascade={
                CascadeType.PERSIST,
                CascadeType.DETACH,
                CascadeType.REFRESH,
                CascadeType.MERGE,
                CascadeType.REMOVE
            })
        private Passenger passenger;
  23. Rebuild the module and re-run the test method. The test method should pass this time because the DELETE is now configured to be cascaded from the inverse to owning side. Notice how both sides of the relationship are being deleted from the database, starting with the owning/dependent side to prevent a foreign key constraint violation.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOne2OneCascadeFromInverse
    ...
     -delete the entities from the inverse side
    Hibernate: 
        delete 
        from
            RELATIONEX_PASSENGER 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_TICKET 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    

You have now finished demonstrating how entity manager actions can be automatically cascaded from the inverse/parent side of a relationship to the owning/dependent side of a bi-directional relationship. This is not always an appropriate or desired behavior but it is notable that cascade direction and relationship ownership are independent of one another. You can configure cascades to be originated from the owning or inverse side of a relationship.

Orphan removal can be handy when the target of a OneToXxx relationship becomes unreferenced by its referencing entity and the target entity only exists to support that referencing entity. This is different from cascade=DELETE. In this case the referencing entity is not being deleted. It is de-referencing the parent. The basic rules are as follows.

  1. Put the following entity class in your src/main tree. This entity class represents a parent entity that exists solely for the use of a dependent entity that will be related to it in a follow-on step.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * This entity class provides an example of an entity that
     * will get deleted when no longer referenced by its dependent
     * entity in a one-to-one relation. This is called orphan removal.
     */
    @Entity
    @Table(name="RELATIONEX_RESIDENCE")
    public class Residence {
        @Id @GeneratedValue
        private int id;
        @Column(length=16, nullable=false)
        private String city;
        @Column(length=2, nullable=false)
        private String state;
        
        protected Residence() {}   
        public Residence(int id) { this.id = id; }
        public Residence(String city, String state) {
            this.city = city;
            this.state = state;
        }
        public int getId() { return id; }
        public String getCity() { return city; }
        public void setCity(String city) {
            this.city = city;
        }
        
        public String getState() { return state;}
        public void setState(String state) {
            this.state = state;
        }
    }
  2. Put the following entity class in your src/main tree. This entity provides an example of a dependent entity with a parent that only exists to support this instance. If this instance ceases to reference the parent -- the parent will be removed by the provider.

    
    
    package myorg.relex.one2one;
    import javax.persistence.*;
    /**
     * This entity class provides an example of a dependent with a relationship to a parent entity that
     * should only exist to support this entity. When this entity ceases to reference the parent, it 
     * will become "orphaned" and subject to orphanRemoval by the provider.
     */
    @Entity
    @Table(name="RELATIONEX_ATTENDEE")
    public class Attendee {
        @Id @GeneratedValue
        private int id;
        
        //orphanRemoval will take care of dereference and DELETE from dependent Attendee 
        @OneToOne(cascade=CascadeType.PERSIST, orphanRemoval=true)
        private Residence residence;
        
        private String name;
        public int getId() { return id; }
        public Residence getResidence() { return residence; }
        public void setResidence(Residence residence) {
            this.residence = residence;
        }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the two entity classes to the persistence unit using the persistence.xml.

    
    
            <class>myorg.relex.one2one.Residence</class>
            <class>myorg.relex.one2one.Attendee</class>
  4. Add the following test method to your existing one-to-one JUnit test case. At this point in time, the test just creates an instance of the two related entities. Consistent with the concept that the parent is only there to support the parent -- we have maintained no reference to the parent except through the dependent entity.

    
    
        @Test
        public void testOrphanRemoval() {
            log.info("*** testOrphanRemoval ***");
            
            log.debug("start by verifying the state of the database");
            int startCount = em.createQuery("select count(r) from Residence r", Number.class)
                                  .getSingleResult().intValue();
            log.debug("create a new attendee and residence");
            Attendee attendee = new Attendee();
            attendee.setName("jones");
            attendee.setResidence(new Residence("Columbia", "MD"));
            em.persist(attendee);
            em.flush();
            
            log.debug("verify we have a new residence in the database");
            assertEquals("unexpected number of residences", startCount+1,
                    em.createQuery("select count(r) from Residence r", Number.class)
                      .getSingleResult().intValue());
            log.debug("verify we can find our new instance");
            int originalId=attendee.getResidence().getId();
            assertNotNull("could not find residence", em.find(Residence.class, originalId));
        }
  5. Build the module and run the new test method. Notice how the two entities are inserted into the database and the foreign key column for the dependent entity table is set to reference the parent.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOrphanRemoval
    ...
     -start by verifying the state of the database
    Hibernate: 
        select
            count(residence0_.id) as col_0_0_ 
        from
            RELATIONEX_RESIDENCE residence0_ limit ?
     -create a new attendee and residence
    Hibernate: 
        insert 
        into
            RELATIONEX_RESIDENCE
            (id, city, state) 
        values
            (null, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_ATTENDEE
            (id, name, residence_id) 
        values
            (null, ?, ?)
     -verify we have a new residence in the database
    Hibernate: 
        select
            count(residence0_.id) as col_0_0_ 
        from
            RELATIONEX_RESIDENCE residence0_ limit ?
     -verify we can find our new instance
    
  6. Add the following to your test method to orphan your original parent.

    
    
            log.debug("have attendee change residence");
            //ISSUE: https://hibernate.atlassian.net/browse/HHH-6484
            //MORE: https://hibernate.atlassian.net/browse/HHH-5559
    //      attendee.setResidence(null);
    //      em.flush();
            attendee.setResidence(new Residence("Baltimore", "MD"));
            em.flush();
            log.debug("verify we have the same number of residences");
            assertEquals("unexpected number of residences", startCount+1,
                    em.createQuery("select count(r) from Residence r", Number.class)
                      .getSingleResult().intValue());
            
            log.debug("verify the new instance replaced the original instance");
            assertNull("found original residence", em.find(Residence.class, originalId));
            assertNotNull("could not find new residence", em.find(Residence.class, attendee.getResidence().getId()));
  7. Rebuild the module and re-run the test method to exercise the additional test snippet. There is a small debate about what should happen. Ideally the older, orphaned parent should get deleted when the new parent takes its place as documented in the following trouble tickets( HHH-6484 and HHH-5559). However, that does not end up being the case

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOrphanRemoval
    ...
     -have attendee change residence
    Hibernate: 
        insert 
        into
            RELATIONEX_RESIDENCE
            (id, city, state) 
        values
            (null, ?, ?)
    Hibernate: 
        update
            RELATIONEX_ATTENDEE 
        set
            name=?,
            residence_id=? 
        where
            id=?
     -verify we have the same number of residences
    Hibernate: 
        select
            count(residence0_.id) as col_0_0_ 
        from
            RELATIONEX_RESIDENCE residence0_ limit ?
    ...
    Failed tests:   testOrphanRemoval(myorg.relex.One2OneTest): unexpected number of residences expected:<1> but was:<2>
    
  8. Uncomment setting the parent reference to null and calling em.flush() on the session. This will cause the desired behavior with the extra steps/calls.

    
    
            attendee.setResidence(null);
            em.flush();
            attendee.setResidence(new Residence("Baltimore", "MD"));
  9. Rebuild the module and re-run the test method with the above lines uncommented. This should cause the older, orphaned parent to be deleted. The assignment to the new parent is an independent step.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOrphanRemoval
    ...
     -have attendee change residence
    Hibernate: 
        update
            RELATIONEX_ATTENDEE 
        set
            name=?,
            residence_id=? 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_RESIDENCE 
        where
            id=?
    Hibernate: 
        insert 
        into
            RELATIONEX_RESIDENCE
            (id, city, state) 
        values
            (null, ?, ?)
    Hibernate: 
        update
            RELATIONEX_ATTENDEE 
        set
            name=?,
            residence_id=? 
        where
            id=?
     -verify we have the same number of residences
    Hibernate: 
        select
            count(residence0_.id) as col_0_0_ 
        from
            RELATIONEX_RESIDENCE residence0_ limit ?
     -verify the new instance replaced the original instance
    Hibernate: 
        select
            residence0_.id as id18_0_,
            residence0_.city as city18_0_,
            residence0_.state as state18_0_ 
        from
            RELATIONEX_RESIDENCE residence0_ 
        where
            residence0_.id=?
    
  10. Add the following statements to verify a simple nulling of the parent. With the work-around above, there should be no surprise here that this works. However, the flush() is unnecessary because it appears to be happening automatically because of the follow-on query.

    
    
            log.debug("remove reference to the current residence");
            attendee.setResidence(null);
            //em.flush(); -- note flush is done during follow-on query
            
            log.debug("verify all residences created during this test have been deleted");
            assertEquals("unexpected number of residences", startCount,
                    em.createQuery("select count(r) from Residence r", Number.class)
                      .getSingleResult().intValue());
  11. Rebuild the module and re-run the test method with the final exercise of the orphan management capability. Notice how the query made the manual call to em.flush() unnecessary.

    $ mvn clean test -Dtest=myorg.relex.One2OneTest#testOrphanRemoval
    ...
     -remove reference to the current residence
     -verify all residences created during this test have been deleted
    Hibernate: 
        update
            RELATIONEX_ATTENDEE 
        set
            name=?,
            residence_id=? 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_RESIDENCE 
        where
            id=?
    Hibernate: 
        select
            count(residence0_.id) as col_0_0_ 
        from
            RELATIONEX_RESIDENCE residence0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    

You have finished working with orphanRemoval and found that the provider will automatically delete the parent entity when the reference from the dependent is nulled out and the session is flushed to the database. You saw how a simple replace did not cause the behavior and noticed there were a few trouble tickets written against that lack of behavior that causes your business logic to take extra steps to have the orphan removed.

Mapping one-to-many, uni-directional relationships require some help since the owning side is a single item and we must model relationships to many entities. To do this we can either use a join table (which forms a one-to-one, uni-directional relationship with the many side) or insert a foreign key into the table representing the many side. We will take a look at the join table approach first.

In this section we will demonstrate how to form a one-to-many uni-directional relationship using a join table. The join table is necessary since the many side is unaware of the relationship and "many" cannot be represented by a single row in the owning table.

  1. Place the following entity class in your src/main tree. This class provides an example of the many side of a one-to-many, uni-directional relation. Because of this, this entity class has no reference to the owning/parent entity class.

    
    
    package myorg.relex.one2many;
    import javax.persistence.*;
    /**
     * This class provides an example of an entity class on the many side of a one-to-many, 
     * uni-directional relationship that will be referenced through a JoinTable.
     */
    @Entity
    @Table(name="RELATIONEX_RIDER")
    public class Rider {
        @Id @GeneratedValue
        private int id;
        @Column(length=32)
        private String name;
        
        public Rider() {}   
        public Rider(int id) {
            this.id = id;
        }
        public int getId() { return id; }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  2. Place the following entity class in your src/main tree. This class provides an example of the one side of a one-to-many, uni-directional relation. The implementation is incomplete at the moment and relies on defaults to complete the relation.

    
    
    package myorg.relex.one2many;
    import java.util.List;
    import javax.persistence.*;
    /**
     * This entity class provides an example of the one side of a one-to-many, uni-directional relation
     * that is realized through a JoinTable.
     */
    @Entity
    @Table(name="RELATIONEX_BUS")
    public class Bus {
        @Id
        private int number;
        @OneToMany
    //  @JoinTable(
    //          name="RELATIONEX_BUS_RIDER",
    //          joinColumns={@JoinColumn(name="BUS_NO")},
    //          inverseJoinColumns={@JoinColumn(name="RIDER_ID")}
    //  )
        private List<Rider> passengers;
        protected Bus() {}
        public Bus(int number) {
            this.number = number;
        }
        public int getNumber() { return number; }
        public List<Rider> getPassengers() {
            if (passengers==null) { passengers = new ArrayList<Rider>(); }
            return passengers; 
        }
        public void setPassengers(List<Rider> passengers) {
            this.passengers = passengers;
        }
    }
  3. Add the two entity classes to the persistence unit.

    
    
            <class>myorg.relex.one2many.Rider</class>
            <class>myorg.relex.one2many.Bus</class>
  4. Build the module and look at the default database schema generated for the two entities and relationship

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_BUS (
            number integer not null,
            primary key (number)
        );
    
        create table RELATIONEX_BUS_RELATIONEX_RIDER (
            RELATIONEX_BUS_number integer not null,
            passengers_id integer not null,
            unique (passengers_id)
        );
    ...
        create table RELATIONEX_RIDER (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
    ...
        alter table RELATIONEX_BUS_RELATIONEX_RIDER 
            add constraint FK3F295C59773EE4ED 
            foreign key (RELATIONEX_BUS_number) 
            references RELATIONEX_BUS;
    
        alter table RELATIONEX_BUS_RELATIONEX_RIDER 
            add constraint FK3F295C5994D90F30 
            foreign key (passengers_id) 
            references RELATIONEX_RIDER;
    

    Note that...

  5. Fill in the relationship mapping with non-default values.

    
    
        @OneToMany
        @JoinTable(
                name="RELATIONEX_BUS_RIDER",
                joinColumns={@JoinColumn(name="BUS_NO")},
                inverseJoinColumns={@JoinColumn(name="RIDER_ID")}
        )
        private List<Rider> passengers;
  6. Rebuild the module and re-generate the database schema for the entities and relationship. Nothing will functionally change with the relationship except that we will be more in control of the database table and column names chosen in case we have to map to a legacy database.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_BUS (
            number integer not null,
            primary key (number)
        );
    
        create table RELATIONEX_BUS_RIDER (
            BUS_NO integer not null,
            RIDER_ID integer not null,
            unique (RIDER_ID)
        );
    ...
        create table RELATIONEX_RIDER (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
    ...
        alter table RELATIONEX_BUS_RIDER 
            add constraint FKE78EAB2B869BB455 
            foreign key (BUS_NO) 
            references RELATIONEX_BUS;
    
        alter table RELATIONEX_BUS_RIDER 
            add constraint FKE78EAB2B3961502F 
            foreign key (RIDER_ID) 
            references RELATIONEX_RIDER;
    
  7. Add the following test method to your existing one-to-many JUnit test case to demonstrate the capabilities of a one-to-many, uni-directional relationship mapped using a join table.

    
    
        @Test
        public void testOneToManyUniJoinTable() {
            log.info("*** testOneToManyUniJoinTable ***");
            Bus bus = new Bus(302);
            em.persist(bus);
            List<Rider> riders = new ArrayList<Rider>();
            for (int i=0; i<2; i++) {
                Rider rider = new Rider();
                rider.setName("rider" + i);
                em.persist(rider);
                riders.add(rider);
            }
            log.debug("relating entities");
            bus.getPassengers().addAll(riders);
            em.flush(); em.clear();
            
            log.debug("verify we have expected objects");
            Bus bus2 = em.find(Bus.class, bus.getNumber());
            assertNotNull("bus not found", bus2);
            for (Rider r: bus.getPassengers()) {
                assertNotNull("rider not found", em.find(Rider.class, r.getId()));
            }
            log.debug("verify they are related");
            assertEquals("unexpected number of riders", bus.getPassengers().size(), bus2.getPassengers().size());
        }
  8. Rebuild the module and run the new test method. All entity instances will be created and then the relationships added.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniJoinTable
    ...
     -relating entities
    Hibernate: 
        insert 
        into
            RELATIONEX_BUS_RIDER
            (BUS_NO, RIDER_ID) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_BUS_RIDER
            (BUS_NO, RIDER_ID) 
        values
            (?, ?)
    
    
  9. Notice that since we are not requiring an EAGER fetch, the parent object can be retrieved without a join of the JoinTable and child/many table.

     -verify we have expected objects
    Hibernate: 
        select
            bus0_.number as number23_0_ 
        from
            RELATIONEX_BUS bus0_ 
        where
            bus0_.number=?
    
  10. Notice that once we needed the collection a join of the JoinTable and child table was performed to provide the result.

    
    
            log.debug("verify they are related");
            assertEquals("unexpected number of riders", bus.getPassengers().size(), bus2.getPassengers().size());
     -verify they are related
    Hibernate: 
        select
            passengers0_.BUS_NO as BUS1_23_1_,
            passengers0_.RIDER_ID as RIDER2_1_,
            rider1_.id as id22_0_,
            rider1_.name as name22_0_ 
        from
            RELATIONEX_BUS_RIDER passengers0_ 
        inner join
            RELATIONEX_RIDER rider1_ 
                on passengers0_.RIDER_ID=rider1_.id 
        where
            passengers0_.BUS_NO=?
    
  11. Add the following code snippet to the test method to attempt to remove one of the child objects. There is an error with code because it attempts to remove the child object without removing the relationship to the parent.

    
    
            log.debug("remove one of the child objects");
            em.remove(bus2.getPassengers().get(0)); //ouch!!! this will violate a FK-constraint
            em.flush();
  12. Rebuild the module and attempt to re-run the test method. Not the foreign key constraint violatation with the current implementation of the test method.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniJoinTable
    ...
     -remove one of the child objects
    Hibernate: 
        delete 
        from
            RELATIONEX_RIDER 
        where
            id=?
     -SQL Error: 23503, SQLState: 23503
     -Referential integrity constraint violation: "FKE78EAB2B3961502F: PUBLIC.RELATIONEX_BUS_RIDER FOREIGN KEY(RIDER_ID) REFERENCES PUBLIC.RELATIONEX_RIDER(ID) (1)"; SQL statement:
    delete from RELATIONEX_RIDER where id=? [23503-168]
    
  13. Update the test method to remove the relationship prior to removing the child object. This should satisfy all constraints.

    
    
            log.debug("remove one of the child objects");
            Rider rider = bus2.getPassengers().get(0);
            log.debug("removing the relationship");
            assertTrue("ride not found in relation", bus2.getPassengers().remove(rider));
            em.flush();
            log.debug("removing the object");
            em.remove(rider); 
            em.flush();
  14. Rebuild the module and re-run the test method. The provider now knows to delete the relationship prior to deleting the child object. However, it is of some interest that the provider chose to delete all relationships and re-add only teh remaining ones rather than remove a specific one.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniJoinTable
    ...
     -remove one of the child objects
     -removing the relationship
    Hibernate: 
        delete 
        from
            RELATIONEX_BUS_RIDER 
        where
            BUS_NO=?
    Hibernate: 
        insert 
        into
            RELATIONEX_BUS_RIDER
            (BUS_NO, RIDER_ID) 
        values
            (?, ?)
     -removing the object
    Hibernate: 
        delete 
        from
            RELATIONEX_RIDER 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished a brief section on implementing a one-to-many, uni-directional relationship using a join table. You should have noticed that the join table is the default mapping for such a construct and in the following section we will eliminate the join table and insert a foreign key into the child table.

In this section we will demonstrate how to form a one-to-many uni-directional relationship without a join table. In this case the @JoinColumn in the owning/one side actually maps the relationship to a foreign key in the dependent entity table.

  1. Place the following entity class in your src/main tree. It provides an example of the many side of a one-to-many, uni-directional relationship that we will map using a foreign key in the table for this entity class.

    
    
    package myorg.relex.one2many;
    import javax.persistence.*;
    /**
     * This class provides an example of the many side of a one-to-many, uni-directional relationship
     * mapped using a foreign key in the child entity table. Note that all mapping will be from the one/owning
     * side and no reference to the foreign key exists within this class.
     */
    @Entity
    @Table(name="RELATIONEX_STOP")
    public class Stop {
        @Id @GeneratedValue
        private int id;
        @Column(length=16)
        private String name;
        public int getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  2. Place the following entity class in your src/main tree. It provides an example of the one side of a one-to-many, uni-directional relationship that will define a foreign key mapping in the remote child table. The @JoinColumn referenced in the @OneToMany relationship is referring to a column in the child's table and not the parent's table.

    
    
    package myorg.relex.one2many;
    import java.util.ArrayList;
    import java.util.List;
    import javax.persistence.*;
    /**
     * This class provides an example of the one side of a one-to-many, uni-directional relationship 
     * mapped using a foreign key inserted into the child/many table. The @JoinColumn is referencing 
     * the child table and not this entity's table.
     */
    @Entity
    @Table(name="RELATIONEX_ROUTE")
    public class Route {
        @Id 
        private int number;
        
        @OneToMany
        @JoinColumn
        private List<Stop> stops;
        protected Route() {}
        public Route(int number) {
            this.number = number;
        }
        public int getNumber() { return number; }
        public List<Stop> getStops() {
            if (stops == null) {  stops = new ArrayList<Stop>(); }
            return stops;
        }
        public void setStops(List<Stop> stops) {
            this.stops = stops;
        }
    }
  3. Add the two new entity classes to the persistence unit.

    
    
            <class>myorg.relex.one2many.Stop</class>
            <class>myorg.relex.one2many.Route</class>
  4. Build the module and inspect the default database schema generated for the two entities and their one-to-many, uni-directional relationship.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_ROUTE (
            number integer not null,
            primary key (number)
        );
    ...
        create table RELATIONEX_STOP (
            id integer generated by default as identity,
            name varchar(16),
            stops_number integer,
            primary key (id)
        );
    ...
        alter table RELATIONEX_STOP 
            add constraint FK35604A92586DC195 
            foreign key (stops_number) 
            references RELATIONEX_ROUTE;
    

    Notice that...

  5. Add the following test method to your existig one-to-many JUnit test case.-

    
    
        @Test
        public void testOneToManyUniFK() {
            log.info("*** testOneToManyUniFK ***");
            Route route = new Route(302);
            em.persist(route);
            List<Stop> stops = new ArrayList<Stop>();
            for (int i=0; i<2; i++) {
                Stop stop = new Stop();
                stop.setName("stop" + i);
                em.persist(stop);
                stops.add(stop);
            }
            log.debug("relating entities");
            route.getStops().addAll(stops);
            em.flush(); em.clear();
            
            log.debug("verify we have expected objects");
            Route route2 = em.find(Route.class, route.getNumber());
            assertNotNull("route not found", route2);
            for (Stop s: route.getStops()) {
                assertNotNull("stop not found", em.find(Stop.class, s.getId()));
            }
            log.debug("verify they are related");
            assertEquals("unexpected number of stops", route.getStops().size(), route2.getStops().size());
        }
  6. Rebuild and module and run the new test method to demonstrate creating the entities and relating them through the one-to-many, uni-directional foreign key from the child table. In this case the provider simply updates a foreign key column in the child table rather than inserting a row in a separate join table.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniFK
    ...
     -relating entities
    Hibernate: 
        update
            RELATIONEX_STOP 
        set
            stops_number=? 
        where
            id=?
    Hibernate: 
        update
            RELATIONEX_STOP 
        set
            stops_number=? 
        where
            id=?
    
  7. Notice how there is no longer a join required when obtaining a list of all children

     -verify we have expected objects
    ...
     -verify they are related
    Hibernate: 
        select
            stops0_.stops_number as stops3_25_1_,
            stops0_.id as id1_,
            stops0_.id as id24_0_,
            stops0_.name as name24_0_ 
        from
            RELATIONEX_STOP stops0_ 
        where
            stops0_.stops_number=?
    
  8. Add the following snippet to verify we can delete a child entity. Note that we have more flexibility in this @JoinColumn case than we did with the previous @JoinTable case. Since there is no separate foreign key referencing the child row and the foreign key is part of the child row -- a delete of the child will automatically remove the relationship.

    
    
            log.debug("remove one of the child objects");
            log.debug("removing the object and relationship");
            em.remove(route2.getStops().get(0)); 
            em.flush();
  9. Rebuild the module and re-run the unit test to verify we can delete one of the child objects. Note that we only delete a single row from the child table. In the previous case the provider deleted all instances referencing the parent from the join table and re-added them.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniFK
    ...
     -remove one of the child objects
     -removing the object and relationship
    Hibernate: 
        delete 
        from
            RELATIONEX_STOP 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished implementing a one-to-many, uni-directional relationship using a @JoinColumn from the child table. This case was designed to solve the common situation in Java and databases where the database is mapped using a simple foreign key in the child table and Java is mapped using only a collection in the parent/one entity. The child/member Java entity has no direct knowledge of which parent entities it will be associated with.

In this section we will demonstrate how to map a collection of a simple data type to a database using a parent/dependent set of tables and a foreign key. This case is similar to the one-to-many, uni-directional relationship with a @JoinColumn where all mapping is being done from the owning/one side. However, in this case, the child/many side is not an entity and does not have a primary key.

  1. Place the following entity class in your src/main tree. The class models a collection of alias strings we will want mapped to a separate dependent/child table. However, the class is currently incomplete and will initially define a mapping we do not want.

    
    
    package myorg.relex.one2many;
    import java.util.HashSet;
    import java.util.Set;
    import javax.persistence.*;
    /**
     * This class provides an example of the owning side of a collection of base data types.
     * In this case we want a unique set of strings (aliases) mapped to this entity using
     * a separate dependent table and a foreign key relationship.
     */
    @Entity
    @Table(name="RELATIONEX_SUSPECT")
    public class Suspect {
        @Id @GeneratedValue
        private int id;
        @Column(length=32)
        private String name;
            
    //  @ElementCollection
    //  @CollectionTable(
    //          name="RELATIONEX_SUSPECT_ALIASES",
    //          joinColumns=@JoinColumn(name="SUSPECT_ID"), 
    //          uniqueConstraints=@UniqueConstraint(columnNames={"SUSPECT_ID", "ALIAS"}))
    //  @Column(name="ALIAS", length=32)
        @Lob
        private Set<String> aliases;
        public int getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        public Set<String> getAliases() {
            if (aliases==null) { aliases = new HashSet<String>(); }
            return aliases;
        }
        public void setAliases(Set<String> aliases) {
            this.aliases = aliases;
        }
    }
  2. Add the new entity class to the persistence unit

    
    
            <class>myorg.relex.one2many.Suspect</class>
  3. Build the module and look at the database schema generated. Notice how the set of strings was mapped to a single column within the parent table of type blob where the set will be serialized into that column when saved.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
       create table RELATIONEX_SUSPECT (
            id integer generated by default as identity,
            aliases blob,
            name varchar(32),
            primary key (id)
        );
    
  4. Fix the mapping such that the list of aliases are kept in a separate dependent/child table with a value column containing the property value and a foreign key back to the parent entity.

        @ElementCollection
    //  @CollectionTable(
    //          name="RELATIONEX_SUSPECT_ALIASES",
    //          joinColumns=@JoinColumn(name="SUSPECT_ID"), 
    //          uniqueConstraints=@UniqueConstraint(columnNames={"SUSPECT_ID", "ALIAS"}))
    //  @Column(name="ALIAS", length=32)
        private Set<String> aliases;
    
  5. Rebuild the module and observe the new database schema generated.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_SUSPECT (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
    ...
        create table Suspect_aliases (
            Suspect_id integer not null,
            aliases varchar(255)
        );
    ...
        alter table Suspect_aliases 
            add constraint FK9AC56596DE29C9CF 
            foreign key (Suspect_id) 
            references RELATIONEX_SUSPECT;
    

    Notice that...

  6. Update the table mapping to control the table and column properties of the child table.

    
    
        @ElementCollection
        @CollectionTable(
                name="RELATIONEX_SUSPECT_ALIASES",
                joinColumns=@JoinColumn(name="SUSPECT_ID"), 
                uniqueConstraints=@UniqueConstraint(columnNames={"SUSPECT_ID", "ALIAS"}))
        @Column(name="ALIAS", length=32)
        private Set<String> aliases;
  7. Rebuild the module and notice the change in database schema generated.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
       create table RELATIONEX_SUSPECT (
            id integer generated by default as identity,
            name varchar(32),
            primary key (id)
        );
    
       create table RELATIONEX_SUSPECT_ALIASES (
            SUSPECT_ID integer not null,
            ALIAS varchar(32),
            unique (SUSPECT_ID, ALIAS)
        );
    ...
        alter table RELATIONEX_SUSPECT_ALIASES 
            add constraint FK3FD160E6DE29C9CF 
            foreign key (SUSPECT_ID) 
            references RELATIONEX_SUSPECT;
    

    Notice that...

  8. Add a new test method to your existing one-to-many JUnit test case to demonstrate mapping of simple value collections to parent/dependent tables using a foreign key relationship.

    
    
        @Test
        public void testOneToManyUniElementCollection() {
            log.info("*** testOneToManyUniElementCollection ***");
            Suspect suspect = new Suspect();
            suspect.setName("william");
            em.persist(suspect);
            suspect.getAliases().add("bill");
            suspect.getAliases().add("billy");
            em.flush(); em.clear();
            
            log.debug("verify we have expected objects");
            Suspect suspect2 = em.find(Suspect.class,  suspect.getId());
            assertNotNull("suspect not found", suspect2);
            for (String a: suspect.getAliases()) {
                assertNotNull("alias not found", suspect2.getAliases().contains(a));
            }
        }
  9. Rebuild the module and run the new test method to demonstrate the construction of the object graph and the mapping to parent/dependent tables.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniElementCollection
    ...
     -*** testOneToManyUniElementCollection ***
    Hibernate: 
        insert 
        into
            RELATIONEX_SUSPECT
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_SUSPECT_ALIASES
            (SUSPECT_ID, ALIAS) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_SUSPECT_ALIASES
            (SUSPECT_ID, ALIAS) 
        values
            (?, ?)
    
     -verify we have expected objects
    Hibernate: 
        select
            suspect0_.id as id26_0_,
            suspect0_.name as name26_0_ 
        from
            RELATIONEX_SUSPECT suspect0_ 
        where
            suspect0_.id=?
    Hibernate: 
        select
            aliases0_.SUSPECT_ID as SUSPECT1_26_0_,
            aliases0_.ALIAS as ALIAS0_ 
        from
            RELATIONEX_SUSPECT_ALIASES aliases0_ 
        where
            aliases0_.SUSPECT_ID=?
    
  10. Add the following code snippet to demonstrate we can remove a row from the dependent/child table by removing the value from the collecion.

    
    
            log.debug("remove one of the child objects");
            String alias = suspect2.getAliases().iterator().next();
            assertTrue("alias not found", suspect2.getAliases().remove(alias)); 
            em.flush();
  11. Rebuild the module and re-run the test method to demonstrate the removal of an element from the collection. Note how the provider is removing a row from the database table.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniElementCollection
    ...
     -remove one of the child objects
    Hibernate: 
        delete 
        from
            RELATIONEX_SUSPECT_ALIASES 
        where
            SUSPECT_ID=? 
            and ALIAS=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished mapping a collection of simple types to a set of parent and dependent tables linked through a foreign key. The value within the collection was mapped directly to a column within the dependent table. By mapping the collection to a separate table instead of storing a serialized block into a single column -- we gain the ability to use values in the collection within future database queries.

In the previous section we demonstrated how we could map a collection of simple data values to a set of parent/dependent tables and link them through a foreign key. In this section we are going to add a small complexity where the simple value is now an embeddable type and will map to multiple columns in the dependent entity table.

  1. Put the following entity class into your src/main tree. This class provides and example of an embeddable non-entity class that will be mapped using an @ElementCollection mapping.

    
    
    package myorg.relex.one2many;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class is an example of a non-entity class that will be mapped to a dependent table
     * and form the many side of an @ElementCollection.
     */
    @Embeddable
    public class Produce {
        public static enum Color { RED, GREEN, YELLOW }
        @Column(length=16)
        private String name;
        
        @Enumerated(EnumType.STRING)
        @Column(length=10)
        private Color color;
        
        @Temporal(TemporalType.DATE)
        private Date expires;
        
        public Produce() {}
        public Produce(String name, Color color, Date expires) {
            this.name = name;
            this.color = color;
            this.expires = expires;
        }
        public String getName() { return name; }
        public Color getColor() { return color; }
        public Date getExpires() { return expires; }
    }
  2. Put the following entity class in place in your src/main tree. This class provides and example of using an @ElementCollection to persist a list of embeddable, non-entity classes within a dependent/child table linked to this entity table using a foreign key. The class defines some of the CollectionTable mappings but leaves some of the default values to what is defined within the Embeddable class.

    
    
    package myorg.relex.one2many;
    import java.util.ArrayList;
    import java.util.List;
    import javax.persistence.*;
    /**
     * This entity class provides an example of mapping a collection of non-entity/embeddable class instances
     * to a dependent/child table and relating the child table to this entity table using a foreign key. 
     */
    @Entity
    @Table(name="RELATIONEX_BASKET")
    public class Basket {
        @Id @GeneratedValue
        private int id;
        
        @ElementCollection
        @CollectionTable(
                name="RELATIONEX_BASKET_PRODUCE",
                joinColumns=@JoinColumn(name="BASKET_ID"))
    //  @AttributeOverrides({
    //      @AttributeOverride(name="name", column=@Column(name="ITEM_NAME")),
    //      @AttributeOverride(name="color", column=@Column(name="ITEM_COLOR"))
    //  })
        private List<Produce> contents;
        
        @Column(length=16)
        private String name;
        public int getId() { return id; }
        public List<Produce> getContents() {
            if (contents == null) { contents = new ArrayList<Produce>(); }
            return contents;
        }
        public void setContents(List<Produce> contents) {
            this.contents = contents;
        }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the two entity classes to the persistence unit.

    
    
            <class>myorg.relex.one2many.Basket</class>
            <class>myorg.relex.one2many.Produce</class>
  4. Build the module and observe the generated schema produced. Notice how the @Column mappings from the @Embeddable class show up in the resultant schema.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_BASKET (
            id integer generated by default as identity,
            name varchar(16),
            primary key (id)
        );
    
        create table RELATIONEX_BASKET_PRODUCE (
            BASKET_ID integer not null,
            color varchar(10),
            expires date,
            name varchar(16)
        );
    ...
        alter table RELATIONEX_BASKET_PRODUCE 
            add constraint FKA97DDDD776CDC1E5 
            foreign key (BASKET_ID) 
            references RELATIONEX_BASKET;
    
  5. Assume we wish to control the schema from the parent class. Update the parent class to control the column names for the product name and color but not the expiration date. You can do this using an @AttributeOverride for each property you wish to change. However multiple @AttributeOverride instances must be wrapped within an array and defined within an @AttributeOverrides instance.

    
    
        @ElementCollection
        @CollectionTable(
                name="RELATIONEX_BASKET_PRODUCE",
                joinColumns=@JoinColumn(name="BASKET_ID"))
        @AttributeOverrides({
            @AttributeOverride(name="name", column=@Column(name="ITEM_NAME")),
            @AttributeOverride(name="color", column=@Column(name="ITEM_COLOR"))
        })
        private List<Produce> contents;
  6. Rebuild the module and observe the updated database schema. Notice how the name of the columns specified in the parent definition of the mapping was used over the default name. It also eliminated the @Column length. We could fix that in the @Column definition of the @AttributeOverride as well.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_BASKET (
            id integer generated by default as identity,
            name varchar(16),
            primary key (id)
        );
    
        create table RELATIONEX_BASKET_PRODUCE (
            BASKET_ID integer not null,
            ITEM_COLOR varchar(255),
            expires date,
            ITEM_NAME varchar(255)
        );
    ...
        alter table RELATIONEX_BASKET_PRODUCE 
            add constraint FKA97DDDD776CDC1E5 
            foreign key (BASKET_ID) 
            references RELATIONEX_BASKET;
    
  7. Add the following test method to your existing one-to-many JUnit test case.

    
    
        @Test
        public void testOneToManyUniEmbeddableElementCollection() {
            log.info("*** testOneToManyUniEmbeddableElementCollection ***");
            Basket basket = new Basket();
            basket.setName("assorted fruit");
            em.persist(basket);
            basket.getContents().add(new Produce("apple", Produce.Color.RED, new Date(System.currentTimeMillis()+(3600000*24*30))));
            basket.getContents().add(new Produce("banana", Produce.Color.YELLOW, new Date(System.currentTimeMillis()+(3600000*24*14))));
            em.flush(); em.clear();
            
            log.debug("verify we have expected objects");
            Basket basket2 = em.find(Basket.class, basket.getId());
            assertNotNull("basket not found", basket2);
            assertEquals("unexpected contents", basket.getContents().size(), basket2.getContents().size());
  8. Build the module and run the new test method to demonstrate the building of the object graph and the mapping to the database.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniEmbeddableElementCollection
    ...
     -*** testOneToManyUniEmbeddableElementCollection ***
    Hibernate: 
        insert 
        into
            RELATIONEX_BASKET
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_BASKET_PRODUCE
            (BASKET_ID, ITEM_COLOR, expires, ITEM_NAME) 
        values
            (?, ?, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_BASKET_PRODUCE
            (BASKET_ID, ITEM_COLOR, expires, ITEM_NAME) 
        values
            (?, ?, ?, ?)
    
     -verify we have expected objects
    Hibernate: 
        select
            basket0_.id as id27_0_,
            basket0_.name as name27_0_ 
        from
            RELATIONEX_BASKET basket0_ 
        where
            basket0_.id=?
    Hibernate: 
        select
            contents0_.BASKET_ID as BASKET1_27_0_,
            contents0_.ITEM_COLOR as ITEM2_0_,
            contents0_.expires as expires0_,
            contents0_.ITEM_NAME as ITEM4_0_ 
        from
            RELATIONEX_BASKET_PRODUCE contents0_ 
        where
            contents0_.BASKET_ID=?
    
  9. Add the following code snippet to verify we can delete one of the embeddable instances from the collection and the database.

    
    
            log.debug("remove one of the child objects");
            Produce produce = basket2.getContents().get(0);
            assertTrue("produce not found", basket2.getContents().remove(produce));
            em.flush();
  10. Rebuild the module and re-run the test method to observe how the provider will be deleting our embeddable instance. Notice, in this case, the provider is deleting all related instances and re-adding the remaining instance(s) that are still associated to the parent. That is likely due to the fact that the instance has no primary key and no field combinations were labelled as unique. The provider has no way to tell one instance from another -- so it must delete and re-add.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniEmbeddableElementCollection
    ...
     -remove one of the child objects
    Hibernate: 
        delete 
        from
            RELATIONEX_BASKET_PRODUCE 
        where
            BASKET_ID=?
    Hibernate: 
        insert 
        into
            RELATIONEX_BASKET_PRODUCE
            (BASKET_ID, ITEM_COLOR, expires, ITEM_NAME) 
        values
            (?, ?, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    

You have finished mapping a collection of embeddable, non-entity class instances to a dependent table related to the parent table using a foreign key. You were also able to control the table and column properties through @Column definitions directly applied to the embeddable class and override them in the parent class use @AttributeOverride(s).

In this chapter we will assume what was covered in the one-to-one CASCADE portion is suitable for now and focus this section on orphanRemoval.

Orphan removal is specific to one-to-one and one-to-many relationships. For a one-to-many relationship, this capability allows members of a collection to be removed once they stop being members of that collection. Thus the child member is only there to support the parent entity.

  1. Put the following child entity class in your src/main tree. This class provides an example many side of a one-to-many relation. Thus it has no reference to the one side.

    
    
    package myorg.relex.one2many;
    import javax.persistence.*;
    /**
     * This class is an example of the many side of a one-to-many, uni-directional relationship 
     * which uses orphanRemoval of target entities on the many side. This entity exists for the 
     * sole use of the one side of the relation.
     */
    @Entity
    @Table(name="RELATIONEX_TODO")
    public class Todo {
        @Id @GeneratedValue
        private int id;
        
        @Column(length=32)
        private String title;
        
        public Todo() {}
        public Todo(String title) {
            this.title = title;
        }
        public int getId() { return id; }
        public String getTitle() { return title; }
        public void setTitle(String title) {
            this.title = title;
        }
    }
  2. Place the following parent entity in your src/main tree. This class provides an example of the one/owning side of a one-to-many, uni-directional relationship. The relationship is realized through a foreign key from the child entity table to the parent. The parent has enabled cascade.PERSIST to allow for easy storage of the child entities but its currently incomplete when it comes to orphan removal. We will fix in a moment.

    package myorg.relex.one2many;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.persistence.*;
    /**
     * This class provides an example owning entity in a one-to-many, uni-directional relationship 
     * where the members of the collection are subject to orphanRemoval when they are removed from the 
     * collection. 
     */
    @Entity
    @Table(name="RELATIONEX_TODOLIST")
    public class TodoList {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(cascade={CascadeType.PERSIST}
    //            ,orphanRemoval=true
            )
        @JoinColumn
        private List<Todo> todos;
    
        public int getId() { return id; }
    
        public List<Todo> getTodos() {
            if (todos==null) {
                todos = new ArrayList<Todo>();
            }
            return todos;
        }
        public void setTodos(List<Todo> todos) {
            this.todos = todos;
        }
    }
    
  3. Add the new entity classes to your persistence unit.

    
    
            <class>myorg.relex.one2many.Todo</class>
            <class>myorg.relex.one2many.TodoList</class>
  4. Add the following test method to your JUnit test case. This portion of the test will simply create and parent and first child entity.

    
    
        @Test
        public void testOneToManyUniOrphanRemoval() {
            log.info("*** testOneToManyUniEmbeddableElementCollection ***");
            //check how many child entities exist to start with
            int startCount = em.createQuery("select count(t) from Todo t", Number.class).getSingleResult().intValue();
            log.debug("create new TODO list with first entry");
            TodoList list = new TodoList();
            list.getTodos().add(new Todo("get up"));
            em.persist(list);
            em.flush();
        }
  5. Build the module and run the new test method. Note the creation of the parent, child, and the relating of the child to the parent.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniOrphanRemoval
    ...
    -*** testOneToManyUniEmbeddableElementCollection ***
    Hibernate: 
        select
            count(todo0_.id) as col_0_0_ 
        from
            RELATIONEX_TODO todo0_ limit ?
     -create new TODO list with first entry
    Hibernate: 
        insert 
        into
            RELATIONEX_TODOLIST
            (id) 
        values
            (null)
    Hibernate: 
        insert 
        into
            RELATIONEX_TODO
            (id, title) 
        values
            (null, ?)
    Hibernate: 
        update
            RELATIONEX_TODO 
        set
            todos_id=? 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    
  6. Add the following lines to the unit test to verify a child instance was created.

    
    
            log.debug("verifying we have new child entity");
            assertEquals("new child not found", startCount +1,
                em.createQuery("select count(t) from Todo t", Number.class).getSingleResult().intValue());      
  7. Rebuild the module and re-run the test method. Notice we get the expected additional child entity in the table.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniOrphanRemoval
    ...
     -verifying we have new child entity
    Hibernate: 
        select
            count(todo0_.id) as col_0_0_ 
        from
            RELATIONEX_TODO todo0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    
  8. Add the following lines to your test method. This will remove the child entity from the parent collection and test whether it was subjected to an orphan removal.

    
    
            log.debug("removing child from list");
            list.getTodos().clear();
            em.flush();
            assertEquals("orphaned child not deleted", startCount,
                    em.createQuery("select count(t) from Todo t", Number.class).getSingleResult().intValue());
  9. Rebuild the module and re-run the test method. Notice how the test currently fails. This is because we never enabled orphanRemoval on the relationship.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniOrphanRemoval
    ...
     -removing child from list
    Hibernate: 
        update
            RELATIONEX_TODO 
        set
            todos_id=null 
        where
            todos_id=?
    Hibernate: 
        select
            count(todo0_.id) as col_0_0_ 
        from
            RELATIONEX_TODO todo0_ limit ?
    ...
    Failed tests:   testOneToManyUniOrphanRemoval(myorg.relex.One2ManyTest): orphaned child not deleted expected:<0> but was:<1>
    ...
    [INFO] BUILD FAILURE
    
  10. Enable orphanRemoval on the relationship on the parent side.

    
    
        @OneToMany(cascade={CascadeType.PERSIST}
                ,orphanRemoval=true 
            )
        @JoinColumn
        private List<Todo> todos;
  11. Rebuild the module and re-run the test method. Notice how the child entity is removed shortly after it was removed from the parent collection.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyTest#testOneToManyUniOrphanRemoval
    ...
    -removing child from list
    Hibernate: 
        update
            RELATIONEX_TODO 
        set
            todos_id=null 
        where
            todos_id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_TODO 
        where
            id=?
    Hibernate: 
        select
            count(todo0_.id) as col_0_0_ 
        from
            RELATIONEX_TODO todo0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    

You have finished taking a look at orphanRemoval within the context of a one-to-many, uni-directional relationship. With this capability, entities removed from a parent collection are automatically deleted from the database.

In this chapter we will take a closer look at the collections used within a relationship and how we can better map them to the business need. We will primarily look at collection ordering and access.

This section will focus on how Java and JPA determine the identity of an entity and when one instance equals another. To demonstrate the concepts, please the following artifacts in place.

  1. Place the following mapped superclass in place in your src/main tree. Mapped superclasses are POJO base classes for entities that are not themselves entities. The reason we did not make this class an entity is because it is abstract and will never exist within our example tree without a subclass representing the entity. Each instance of the mapped superclass will be assigned an instanceId, a database primary key (when persisted), and a business Id (name).

    
    
    package myorg.relex.collection;
    import java.util.Date;
    import java.util.concurrent.atomic.AtomicInteger;
    import javax.persistence.Column;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.MappedSuperclass;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    import javax.persistence.Transient;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    /**
     * This class is used as a common base implementation by several implementations
     * of hashCode/equals.
     */
    @MappedSuperclass
    public abstract class Ship {
        @Transient
        protected final Log log = LogFactory.getLog(getClass());
        private static AtomicInteger instanceId = new AtomicInteger();
        @Transient
        private int oid = instanceId.getAndAdd(1);
        
        @Id
        @GeneratedValue
        protected int id;
        
        @Column(length = 16)
        protected String name; //businessId
        
        @Temporal(TemporalType.TIMESTAMP)
        protected Date created;
        
        public int getId() { return id; }
        public Ship setId(int id) {
            this.id = id;
            return this;
        }
        public String getName() { return name; }
        public Ship setName(String name) {
            this.name = name;
            return this;
        }
        public Date getCreated() { return created; }
        public Ship setCreated(Date created) {
            this.created = created;
            return this;
        }
        
        public abstract int peekHashCode();
        protected int objectHashCode() {
            return super.hashCode();
        }
        
        @Override
        public int hashCode() {
            return logHashCode(peekHashCode());
        }
        
        public int logHashCode(int hashCode) {
            log.info(toString() +
                    ".hashCode=" + hashCode);
            return hashCode;
        }
        
        public boolean logEquals(Object obj, boolean equals) {
            log.info(new StringBuilder()
                .append(toString())
                .append(".equals(id=")
                .append(obj==null?null : ((Ship)obj).id + ",oid=" + ((Ship)obj).oid)
                .append(")=")
                .append(equals));
            return equals;
        }
        
        public String toString() {
            return getClass().getSimpleName() + "(id=" + id + ",oid=" + oid + ")";
        }
    }
  2. Place the following entity class in you src/main tree. This class will be used to represent a parent/one end of a one-to-many relationship. It is currently incomplete. We will add more to it later.

    
    
    package myorg.relex.collection;
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import javax.persistence.*;
    /**
     * This class provides an example one/parent entity with a relationship to many child/dependent
     * objects -- with the members in each collection based on a different hashCode/equals method.
     */
    @Entity
    @Table(name="RELATIONEX_FLEET")
    public class Fleet {
        @Id @GeneratedValue
        private int id;
        @Column(length=16)
        private String name;
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public String getName() { return name;}
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add only the entity class to the persistence unit. Do not add the mapped superclass.

    
    
            <class>myorg.relex.collection.Fleet</class>

In this section we will demonstrate how using the default java.lang.Object hashCode and equals methods is used within Java collections and impacts JPA code. This technique works when working with a single instance that represents a real object. If two objects are of the same class but different instances -- then they will have a different hashCode identity and equals will be returned as false even if every Java attribute they host has an equivalent value.

  1. Add the following entity class to your src/main tree. This class represents an entity that implements hashCode and equals using the default java.lang.Object hashCode/equals implementation except it will print some debug when these methods are called. Notice that it extends the managed superclass you added earlier.

    
    
    package myorg.relex.collection;
    import javax.persistence.*;
    /**
     * This class is provides an example of an entity that implements hashCode/equals 
     * using the default java.lang.Object implementation. Note this implementation is instance-specific. 
     * No other instance will report the same value even if they represent the same row in the DB.
     */
    @Entity
    @Table(name="RELATIONEX_SHIP")
    public class ShipByDefault extends Ship {
        @Override
        public int peekHashCode() {
            return super.objectHashCode();
        }
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return logEquals(obj, true); }
                boolean equals = super.equals(obj);
                return logEquals(obj, equals);
            } catch (Exception ex) {
                return logEquals(obj, false);
            }
        }
    }
  2. Add the entity class to your persistence unit.

    
    
            <class>myorg.relex.collection.ShipByDefault</class>
  3. Add the following test method and initial code to your collections JUnit test case. This test provides a simple demonstration how two instances with the same values will report they are different when using the default java.lang.Object implementations of hashCode and equals.

    
    
        @Test
        public void testByDefault() {
            log.info("*** testByDefault ***");
            
            Ship ship1 = new ShipByDefault();
            Ship ship2 = new ShipByDefault();
            assertFalse("unexpected hashCode", ship1.hashCode() == ship2.hashCode());
            assertFalse("unexpected equality", ship1.equals(ship2));
        }
  4. Build the module, run the new JUnit test case, and observe the results. Notice how the two instances have the same databaseId (unassigned at this point) but a different instanceId, significantly different hashCodes and an equals that does not match.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByDefault
    ...
     -*** testByDefault ***
     -ShipByDefault(id=0,oid=4).hashCode=1215713589
     -ShipByDefault(id=0,oid=5).hashCode=1100908089
     -ShipByDefault(id=0,oid=4).equals(id=0,oid=5)=false
    ...
    [INFO] BUILD SUCCESS
    
  5. Add the following lines of code to the existing test method to persist the entity and attempt to retrieve it while still in the cache.

    
    
            log.debug("persisting entity");
            em.persist(ship1);
            em.flush();
            Ship ship3 = em.find(ShipByDefault.class, ship1.getId());
            assertTrue("unexpected hashCode", ship1.hashCode() == ship3.hashCode());
            assertTrue("unexpected inequality", ship1.equals(ship3));
  6. Rebuild the module, re-run the test method and observe the equality that occurs. The two variable instances have the same hashCode and are equal because they reference the same entity instance.

     -persisting entity
    Hibernate: 
        insert 
        into
            RELATIONEX_SHIP
            (id, created, name) 
        values
            (null, ?, ?)
     -ShipByDefault(id=1,oid=4).hashCode=1341189399
     -ShipByDefault(id=1,oid=4).hashCode=1341189399
     -ShipByDefault(id=1,oid=4).equals(id=1,oid=4)=true
    ...
    [INFO] BUILD SUCCESS
    
  7. Add the following lines of code to your existing test method to show how the equality of the instances depends on whether the cache is still in place.

    
    
            log.debug("getting new instance of entity");
            em.clear();
            Ship ship4 = em.find(ShipByDefault.class, ship1.getId());
            assertFalse("unexpected hashCode", ship1.hashCode() == ship4.hashCode());
            assertFalse("unexpected equality", ship1.equals(ship4));
  8. Rebuild the module, re-run the test method, and observe the fact we now have inequality now that we have different instances. We can be sure they are different instances -- even though they both represent the same database Id -- by the value printed for the oid.

     -getting new instance of entity
    Hibernate: 
        select
            shipbydefa0_.id as id29_0_,
            shipbydefa0_.created as created29_0_,
            shipbydefa0_.name as name29_0_ 
        from
            RELATIONEX_SHIP shipbydefa0_ 
        where
            shipbydefa0_.id=?
     -ShipByDefault(id=1,oid=4).hashCode=368668382
     -ShipByDefault(id=1,oid=6).hashCode=346534810
     -ShipByDefault(id=1,oid=4).equals(id=1,oid=6)=false
    ...
    [INFO] BUILD SUCCESS
    

You have finished demonstrating how entities using the default java.lang.Object implementation of hashCode and equals identify themselves as equal only if they are referencing the same instance. This works as long as the instance is available to be referenced but would not work in cases where we want the identity to span instances that might share the same properties. In the next section we will look at factoring in database Id into the hashCode and equality implementations.

In this section we will will demonstrate an attempt at modeling the hashCode and equals property through the database-assigned primary key. After all -- this value is meant to be our Id for the entity.

  1. Add the following entity class to your src/main tree. This class will base its hashCode and equals solely on the assigned (or unassigned) primary key.

    
    
    package myorg.relex.collection;
    import javax.persistence.*;
    /**
     * This class is provides an example of an entity that implements hashCode/equals 
     * using its database assigned primary key. Note the PK is not assigned until the 
     * entity is inserted into the database -- so there will be a period of time prior
     * to persist() when all instances of this class report the same hashCode/equals. 
     */
    @Entity
    @Table(name="RELATIONEX_SHIP")
    public class ShipByPK extends Ship {
        @Override
        public int peekHashCode() {
            return id;
        }
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return logEquals(obj, true); }
                boolean equals = id==((ShipByPK)obj).id;
                return logEquals(obj, equals);
            } catch (Exception ex) {
                return logEquals(obj, false);
            }
        }
    }
  2. Add the entity class to your persistence unit.

    
    
            <class>myorg.relex.collection.ShipByPK</class>
  3. Add the following test method to your existing unit test. This test will demonstrate how we can get two instances to logically represent the same thing.

    
    
        @Test
        public void testByPK() {
            log.info("*** testByPK ***");
            
            Ship ship1 = new ShipByPK();
            Ship ship2 = new ShipByPK();
            assertTrue("unexpected hashCode", ship1.hashCode() == ship2.hashCode());
            assertTrue("unexpected equality", ship1.equals(ship2));
        }
  4. Rebuild the module and run the new test method. Notice how two object instances with the same database primary key value can easily report the same hashCode and report they are equal.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByPK
    ...
     -*** testByPK ***
     -ShipByPK(id=0,oid=4).hashCode=0
     -ShipByPK(id=0,oid=5).hashCode=0
     -ShipByPK(id=0,oid=4).equals(id=0,oid=5)=true
    ...
    [INFO] BUILD SUCCESS
    
  5. Add the following lines of code to your existing test method. This code will demonstrate how an earlier unmanaged instance and a newly found managed instance will report they are the same.

    
    
            log.debug("persisting entity");
            em.persist(ship1);
            em.flush();
            em.clear();
            log.debug("getting new instance of entity");
            Ship ship4 = em.find(ShipByPK.class, ship1.getId());
            assertTrue("unexpected hashCode", ship1.hashCode() == ship4.hashCode());
            assertTrue("unexpected equality", ship1.equals(ship4));
  6. Rebuild the module and re-run the test method. Notice how the common primary key value causes the two instances to be equal.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByPK
    ...
     -getting new instance of entity
    Hibernate: 
        select
            shipbypk0_.id as id29_0_,
            shipbypk0_.created as created29_0_,
            shipbypk0_.name as name29_0_ 
        from
            RELATIONEX_SHIP shipbypk0_ 
        where
            shipbypk0_.id=?
     -ShipByPK(id=1,oid=4).hashCode=1
     -ShipByPK(id=1,oid=6).hashCode=1
     -ShipByPK(id=1,oid=4).equals(id=1,oid=6)=true
    ...
    [INFO] BUILD SUCCESS
  7. Add the following lines of code to your existing test method. This will demonstrate that even though the two instances report they are equal, the provider still treats them as being distinct and not interchangeable.

    
    
            log.debug("check if entity manager considers them the same");
            assertFalse("em contained first entity", em.contains(ship1));
            assertTrue("em did not contained second entity", em.contains(ship4));
  8. Rebuild the module and re-run the test method. Note how the entity manager is able to tell the two instances apart and is not making any calls to hashCode or equals to determine if they are contained in the persistence context. This is helpful because we don't get confused by which instance is actually currently managed.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByPK
    ...
     -check if entity manager considers them the same
    ...
    [INFO] BUILD SUCCESS
    
  9. Up to now, we have been showing all good things about the databaseId approach. Add the following code to your existing test method to demonstrate an issue with the technique. In the following code we attempt to create two separate, logical instances and add them to a Set. Since elements of sets are unique and the implementation of the class is based off of a currently uninitialized primary key -- only the second entry is added to the set.

    
    
            Set<Ship> ships = new HashSet<Ship>();
            Ship ship5 = new ShipByPK().setName("one");
            Ship ship6 = new ShipByPK().setName("two");
            log.debug("add first ship to the set");
            assertTrue("first entity not accepted into set", ships.add(ship5));
            log.debug("add second ship to the set");
            assertFalse("second entity accepted into set", ships.add(ship6));
            assertEquals("unexpected set.size", 1, ships.size());
            log.debug("ships=" + ships);
  10. Rebuild the module, re-run the test method, and note the final contents of the Set only contains the first entity. Since the first entity reported it equaled the second entity -- the second entity was not added to the set. This can be an issue if we want to model a relationship as a unique Set prior to the entities being persisted to the database.

     -add first ship to the set
     -ShipByPK(id=0,oid=7).hashCode=0
     -add second ship to the set
     -ShipByPK(id=0,oid=8).hashCode=0
     -ShipByPK(id=0,oid=8).equals(id=0,oid=7)=true
     -ships=[ShipByPK(id=0,oid=7)]
    ...
    [INFO] BUILD SUCCESS
    

You have finished demonstrating a potential option for deriving hashCode and equals that would make two separate instances logically presenting the same thing to be reported as equal. However, this solution -- as demonstrated -- has issues. It only works for persisted entities that already have their database identity assigned. This can be a serious issue for entity classes with a @GeneratedValue for a primary key and parents that house those entities within Sets. In the next section we will look at a potential hybrid solution.

In this section we will demonstrate an option for deriving hashCode and equals that will report two instances for the same logical object and attempt to compensate for addition to a set prior to being assigned a primary key.

  1. Add the following class to your src/main tree. This class will default to the java.lang.Object approach prior to being given a primary key -- and then switch to the primary key from that point forward. It sounds good -- but will also have some issues we will demonstrate.

    Is changing result of hashCode/equals legal?

    The Internet is not short on discussion of hashCode/equals and whether its value and result can be changed during the lifetime of an object. The java.lang.Object.hashCode javadoc states that "...the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified". The first part of that phrase makes the following solution suspect. The last part of the phrase at least makes it a legal option.

    
    
    package myorg.relex.collection;
    import javax.persistence.*;
    /**
     * This class is provides an example of an entity that implements hashCode/equals 
     * using its database assigned primary key if it exists and defaults to the 
     * java.lang.Object definition if not yet assigned.  Note that this technique causes
     * a change in hashCode/equals after the persist() takes place -- invalidating anything
     * previously cached for the identity.
     */
    @Entity
    @Table(name="RELATIONEX_SHIP")
    public class ShipBySwitch extends Ship {
        @Override
        public int peekHashCode() {
            return id==0 ? super.objectHashCode() : id;
        }
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return logEquals(obj, true); }
                boolean equals = (id==0) ? super.equals(obj) :
                    id==((ShipBySwitch)obj).id;
                return logEquals(obj, equals);
            } catch (Exception ex) {
                return logEquals(obj, false);
            }
        }
    }
  2. Add the entity class to your persistence unit.

    
    
            <class>myorg.relex.collection.ShipBySwitch</class>
  3. Add the following test method to your existing JUnit test case. It will be used demonstrate the benefits and issues with having an object switching hashCode values and equals results.

    
    
        @Test
        public void testBySwitch() {
            log.info("*** testBySwitch ***");
            Ship ship1 = new ShipBySwitch().setName("one");
            Ship ship2 = new ShipBySwitch().setName("two");
            assertFalse("unexpected hashCode", ship1.hashCode() == ship2.hashCode());
            assertFalse("unexpected equality", ship1.equals(ship2));
        }
  4. Rebuild the module and run the new test method. Notice how the two instances are immediately determined to be different during the pre-persist state by the fact they are two different instances. If we wanted them to be the same -- we could have switched to comparing object properties.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testBySwitch
    ...
     -*** testBySwitch ***
     -ShipBySwitch(id=0,oid=4).hashCode=734740843
     -ShipBySwitch(id=0,oid=5).hashCode=744458212
     -ShipBySwitch(id=0,oid=4).equals(id=0,oid=5)=false
    ...
    [INFO] BUILD SUCCESS
    
  5. Add the following lines of code to your existing method. This will demonstrate how to the two instances can be added to a set -- unlike before.

    
    
            Set<Ship> ships = new HashSet<Ship>();
            log.debug("add first ship to the set");
            assertTrue("first entity not accepted into set", ships.add(ship1));
            log.debug("add second ship to the set");
            assertTrue("second entity not accepted into set", ships.add(ship2));
            assertEquals("unexpected set.size", 2, ships.size());
            log.debug("ships=" + ships);
  6. Rebuild the module and re-run the test method. Note this time around we end up with two instances in the set.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testBySwitch
    ...
     -add first ship to the set
     -ShipBySwitch(id=0,oid=4).hashCode=434276434
     -add second ship to the set
     -ShipBySwitch(id=0,oid=5).hashCode=1226345699
     -ships=[ShipBySwitch(id=0,oid=4), ShipBySwitch(id=0,oid=5)]
    ...
    [INFO] BUILD SUCCESS
    
  7. Add the following lines of code to your existing test method. This should demonstrate how the object shifts from using the instanceId to the databaseId once it has been assigned.

    
    
            em.persist(ship1);
            em.flush();
            em.clear();
            log.debug("getting new instance of entity");
            Ship ship4 = em.find(ShipBySwitch.class, ship1.getId());
            assertTrue("unexpected hashCode", ship1.hashCode() == ship4.hashCode());
            assertTrue("unexpected equality", ship1.equals(ship4));
  8. Rebuild the module and re-run the updated test method. Notice how the previously managed from the persist() and the newly managed instance from the find() report they represent the same object.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testBySwitch
    ...
     -getting new instance of entity
    Hibernate: 
        select
            shipbyswit0_.id as id29_0_,
            shipbyswit0_.created as created29_0_,
            shipbyswit0_.name as name29_0_ 
        from
            RELATIONEX_SHIP shipbyswit0_ 
        where
            shipbyswit0_.id=?
     -ShipBySwitch(id=1,oid=4).hashCode=1
     -ShipBySwitch(id=1,oid=6).hashCode=1
     -ShipBySwitch(id=1,oid=4).equals(id=1,oid=6)=true
    ...
    [INFO] BUILD SUCCESS
    
  9. Add the following of code to the existing unit test. This will attempt to find the entity that we know exists in the set.

    
    
            log.debug("set=" + ships);
            log.debug("checking set for entity");
            assertFalse("set found changed entity after persist", ships.contains(ship1));
  10. Rebuild the module and re-run the test method. Notice the entity can no longer be found in the set. This is because the hashCode has changed from when it was originally inserted into the set. This can't be good. Lets stop here with this solution.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testBySwitch
    ...
     -set=[ShipBySwitch(id=1,oid=4), ShipBySwitch(id=0,oid=5)]
     -checking set for entity
     -ShipBySwitch(id=1,oid=4).hashCode=1
    ...
    [INFO] BUILD SUCCESS

You have finished trying out a technique where we shift the hashCode/equals implementation based on a change in state. The javadoc for java.lang.Object states this is legal for the instance to do this, but this technique obviously does not work when the hashCode is stored separate from the entity -- like in a HashedSet. This technique represents the last automatic calculation of hashCode/equals we will try -- and they all had some type of deficiency. We will next look at using business identity within the entity properties to derive the hashCode and equals.

In this section we will look at one last identity mechanism. It is based on a "business identity". In this approach we model our entity with enough properties such that they may be able to uniquely identity the entity without a database Id. It is possible the designer could use these field(s) as the primary key or just make them the implementation basis for hashCode and equals. In the following example we will use both a database-assigned primary key and separate identifying business properties.

  1. Add the following entity class to your src/main tree. This entity class derives its hashCode and equals using a name and createTime property. It may be possible that two entities have the same name -- but no two entities should be created with the same name in the same millisecond.

    
    
    package myorg.relex.collection;
    import javax.persistence.*;
    /**
     * This class is provides an example of an entity that implements hashCode/equals 
     * using its business identity. Note that it is not always easy to derive a business Id
     * for an entity class.
     */
    @Entity
    @Table(name="RELATIONEX_SHIP")
    public class ShipByBusinessId extends Ship {
        @Override
        public int peekHashCode() {
            return (name==null ? 0 : name.hashCode()) + 
                   (created==null ? 0 : (int)created.getTime());
        }
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return logEquals(obj, true); }
                boolean equals = name.equals(((ShipByBusinessId)obj).name) &&
                        created.getTime() == (((ShipByBusinessId)obj).created.getTime());
                return logEquals(obj, equals);
            } catch (Exception ex) {
                return logEquals(obj, false);
            }
        }
        
        @Override
        public String toString() {
            return super.toString() + 
                    ", name=" + name + 
                    ", created=" + (created==null ? 0 : created.getTime()); 
        }
    }
  2. Add the new entity class to your persistence unit.

    
    
            <class>myorg.relex.collection.ShipByBusinessId</class>
  3. Add the following test method to your existing JUnit test case. The first part of the test simply verifies we can determine the two instances are different. Note that since we are factoring in the createTime into the businessId -- a delay is inserted to make sure we get at least one millisecond different in createTime. Depending on how our entities are created -- this may not be necessary.

    
    
        @Test
        public void testByBusinessId() {
            log.info("*** testByBusinessId ***");
            Ship ship1 = new ShipByBusinessId().setName("one").setCreated(new Date());
            try { Thread.sleep(1);} catch (InterruptedException e) {}
            Ship ship2 = new ShipByBusinessId().setName("two").setCreated(new Date());
            assertFalse("unexpected hashCode", ship1.hashCode() == ship2.hashCode());
            assertFalse("unexpected equality", ship1.equals(ship2));
        }
  4. Build the model and run the new test method. Note that we are factoring in both name and createTime into the hashCode/equals and ignoring the databaseId and instanceId.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByBusinessId
    ...
     -*** testByBusinessId ***
     -ShipByBusinessId(id=0,oid=4), name=one, created=1364958763502.hashCode=-840726444
     -ShipByBusinessId(id=0,oid=5), name=two, created=1364958763503.hashCode=-840721349
     -ShipByBusinessId(id=0,oid=4), name=one, created=1364958763502.equals(id=0,oid=5)=false
    ...
    [INFO] BUILD SUCCESS
    
  5. Add the following to your test method. Since we have a unique identities at this point, both instances will be placed into the set. We will also notice later that since the hashCode/equals does not change -- the set will be usable after the calls to persist() complete.

    
    
            Set<Ship> ships = new HashSet<Ship>();
            log.debug("add first ship to the set");
            assertTrue("first entity not accepted into set", ships.add(ship1));
            log.debug("add second ship to the set");
            assertTrue("second entity not accepted into set", ships.add(ship2));
            assertEquals("unexpected set.size", 2, ships.size());
            log.debug("ships=" + ships);
  6. Rebuild the module and re-run the test method. Of no surprise -- we get both instances inserted into the set.

    
    
    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByBusinessId
    ...
     -add first ship to the set
     -ShipByBusinessId(id=0,oid=4), name=one, created=1364959025047.hashCode=-840464899
     -add second ship to the set
     -ShipByBusinessId(id=0,oid=5), name=two, created=1364959025048.hashCode=-840459804
     -ships=[ShipByBusinessId(id=0,oid=4), name=one, created=1364959025047, ShipByBusinessId(id=0,oid=5), name=two, created=1364959025048]
    ...
    [INFO] BUILD SUCCESS
  7. Add the following lines to your test method. In this section we will be determining whether a managed and unmanaged instance will have the same Id. Note the extra effort to zero out the databaseId for the unmanaged instance.

    
    
            em.persist(ship1);
            em.flush();
            em.clear();
            log.debug("getting new instance of entity");
            Ship ship4 = em.find(ShipByBusinessId.class, ship1.getId());
            ship1.setId(0); //making sure that databaseId not used in hashCode/equals
            assertTrue("unexpected hashCode", ship1.hashCode() == ship4.hashCode());
            assertTrue("unexpected equality", ship1.equals(ship4));
  8. Rebuild the module and re-run the test method. Note how the instances match even when one does not have the databaseId to work with.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testByBusinessId
    ...
    -getting new instance of entity
    Hibernate: 
        select
            shipbybusi0_.id as id29_0_,
            shipbybusi0_.created as created29_0_,
            shipbybusi0_.name as name29_0_ 
        from
            RELATIONEX_SHIP shipbybusi0_ 
        where
            shipbybusi0_.id=?
     -ShipByBusinessId(id=0,oid=4), name=one, created=1364959720863.hashCode=-839769083
     -ShipByBusinessId(id=1,oid=6), name=one, created=1364959720863.hashCode=-839769083
     -ShipByBusinessId(id=0,oid=4), name=one, created=1364959720863.equals(id=1,oid=6)=true
    ...
    [INFO] BUILD SUCCESS
    
  9. Add the following lines to your test method. This is where the hashCode/equals basis switch failed us last time.

    
    
            log.debug("set=" + ships);
            log.debug("checking set for entity");
            assertTrue("entity not found after persist", ships.contains(ship1));
  10. Rebuild your module and re-run the test method. This time around you should notice the entity is able to be found within the set.

    
    
    -set=[ShipByBusinessId(id=0,oid=5), name=two, created=1364959903295, ShipByBusinessId(id=0,oid=4), name=one, created=1364959903294]
     -checking set for entity
     -ShipByBusinessId(id=0,oid=4), name=one, created=1364959903294.hashCode=-839586652
    ...
    [INFO] BUILD SUCCESS

You have finished working through the business identity solution for calculating hashCode and equals. This technique has the benefit of being stable from the time the instance was created in memory and through persistence into the database. It was a bit harder to calculate because we needed to find enough stable entity properties persisted to the database so we could derive the values. If these values are actually unique -- we could have considered using them for the primary key but that would have added database complexity/expense.

In this section we will demonstrate the use of ordering a collection mapped with JPA. The previous sections mapped the many aspects of the collection but did not represent any specific ordering within the collection.

  1. Put the following class in your src/main tree. We will use this entity as something we wish to sort within our application. It is currently incomplete and does not sort without some external help.

    
    
    package myorg.relex.collection;
    import java.util.Comparator;
    import javax.persistence.*;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    /**
     * This class represents an example entity that has an order in its parent's list.
     */
    @Entity
    @Table(name="RELATIONEX_SEGMENT")
    public class Segment {// implements Comparable<Segment>{
        private static final Log log = LogFactory.getLog(Segment.class);
        @Id @GeneratedValue
        private int id;
        
        private int number; //a one-up sequence used to order a route
        
        @Column(name="TO", length=16)
        private String to;
        @Column(name="FM", length=16)
        private String from;
        
        
        public int getId() { return id; }
        public int getNumber() { return number; }
        public Segment setNumber(int number) {
            this.number = number;
            return this;
        }
        
        public String getTo() { return to; }
        public Segment setTo(String to) {
            this.to = to;
            return this;
        }
        
        public String getFrom() { return from; }
        public Segment setFrom(String from) {
            this.from = from;
            return this;
        }
        /*
        @Override
        public int compareTo(Segment rhs) {
            if (this == rhs) { return 0; }
            int result = number - rhs.number;
            log.debug(getClass().getSimpleName() + toString() + 
                    ".compareTo" + rhs.toString() + 
                    "=" + result
                    );
            return result;
        }
        */
        
        @Override
        public String toString() {
            return "(id=" + id + ",number=" + number + ")";
        }
    }
  2. Put the following entity class in your src/main tree. This class will be the owning side of a one-to-many relationship of what should be ordered children entities. It is currently incomplete and we will modify in the following steps.

    
    
    package myorg.relex.collection;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.LinkedList;
    import java.util.List;
    import javax.persistence.*;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    /**
     * This entity class provides an example of an ordered list of child entities ordered by a business property
     * in the child entity.
     */
    @Entity
    @Table(name="RELATIONEX_PATH")
    public class Path {
        private static final Log log = LogFactory.getLog(Path.class);
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
        @JoinColumn
    //    @OrderBy("number ASC")
        private List<Segment> segments;
        
        @Column(length=16)
        private String name;
        public int getId() { return id; }
        public List<Segment> getSegments() {
            if (segments==null) { segments = new LinkedList<Segment>(); }
            return segments; 
        }
        //private class SegmentComparator implements Comparator<Segment>
        
        public Path addSegment(Segment segment) {
            getSegments().add(segment);
    /*      
            Collections.sort(segments, new Comparator<Segment>() {
                @Override
                public int compare(Segment lhs, Segment rhs) {
                    if (lhs == rhs || lhs==null && rhs == null) { return 0; }
                    if (lhs != null && rhs == null) { return 1; }
                    if (lhs == null && rhs != null) { return -1; }
                    int result = lhs.getNumber() - rhs.getNumber();
                    log.debug(lhs.getClass().getSimpleName() + lhs.toString() + 
                            ".compareTo" + rhs.toString() + 
                            "=" + result
                            );
                    return result;
                }});
    */          
    //      Collections.sort(segments);
            return this;
        }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the two entity classes to the persistence unit.

    
    
            <class>myorg.relex.collection.Path</class>
            <class>myorg.relex.collection.Segment</class>
  4. Add the following test method to your existing JUnit test case. This test will verify we can add several elements to a list within the parent entity and have the list maintain the entry order.

    
    
        @Test
        public void testOrderBy() {
            log.info("*** testOrderBy ***");
            
            Segment s1 = new Segment().setNumber(1).setFrom("A").setTo("B");
            Segment s2 = new Segment().setNumber(2).setFrom("B").setTo("C");
            Segment s3 = new Segment().setNumber(3).setFrom("C").setTo("D");
            Path path = new Path();
            path.addSegment(s2).addSegment(s3).addSegment(s1);
            log.debug("path.segments=" + path.getSegments());
            Iterator<Segment> itr = path.getSegments().iterator();
            assertEquals(2, itr.next().getNumber());
            assertEquals(3, itr.next().getNumber());
            assertEquals(1, itr.next().getNumber());
        }
  5. Build the module and run the new unit test. Notice the list maintained the order we entered the elements even though it may not be the way we wish to have them ordered later.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testOrderBy
    ...
     -*** testOrderBy ***
     -path.segments=[(id=0,number=2), (id=0,number=3), (id=0,number=1)]
    ...
    [INFO] BUILD SUCCESS
    
  6. Add the following to the test method. This will store the entities and get a fresh instance from the database.

    
    
            log.debug("getting new path instance from database");
            em.persist(path);
            em.flush(); em.clear();
            Path path2 = em.find(Path.class, path.getId());
            itr = path2.getSegments().iterator();
            log.debug("path2.segments=" + path2.getSegments());
  7. Re-build the module and re-run the test method. Notice how the provider queried for the child entities without any regard for order. They come out in a random order (even though it appears somewhat predictable here).

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testOrderBy
    ...
     -path.segments=[(id=0,number=2), (id=0,number=3), (id=0,number=1)]
     -getting new path instance from database
    ...
    Hibernate: 
        select
            path0_.id as id30_1_,
            path0_.name as name30_1_,
            segments1_.segments_id as segments5_30_3_,
            segments1_.id as id3_,
            segments1_.id as id31_0_,
            segments1_.FM as FM31_0_,
            segments1_.number as number31_0_,
            segments1_.TO as TO31_0_ 
        from
            RELATIONEX_PATH path0_ 
        left outer join
            RELATIONEX_SEGMENT segments1_ 
                on path0_.id=segments1_.segments_id 
        where
            path0_.id=?
     -path2.segments=[(id=1,number=2), (id=2,number=3), (id=3,number=1)]
    ...
    [INFO] BUILD SUCCESS
    
  8. Lets add a requirement the Segments be ordered by a business property; number. We can make this happen by adding the following metadata to the list within the parent entity. Add an @OrderBy("number ASC") to the collection. Note that we can order in an ASCending or DESCending order. The default is ASC.

    
    
        @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
        @JoinColumn
        @OrderBy("number ASC")
        private List<Segment> segments;
  9. Add the following assertions that verify the returned collection has a list of elements ordered by the specified business property.

            log.debug("path2.segments=" + path2.getSegments());
            ...
            assertEquals(1, itr.next().getNumber());
            assertEquals(2, itr.next().getNumber());
            assertEquals(3, itr.next().getNumber());
    
  10. Rebuild the module and and re-run the unit test. Notice the provider has added an "order by" to the SQL query and our list comes back in a stable, predictable order by number.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testOrderBy
    ...
    Hibernate: 
        select
            path0_.id as id30_1_,
            path0_.name as name30_1_,
            segments1_.segments_id as segments5_30_3_,
            segments1_.id as id3_,
            segments1_.id as id31_0_,
            segments1_.FM as FM31_0_,
            segments1_.number as number31_0_,
            segments1_.TO as TO31_0_ 
        from
            RELATIONEX_PATH path0_ 
        left outer join
            RELATIONEX_SEGMENT segments1_ 
                on path0_.id=segments1_.segments_id 
        where
            path0_.id=? 
        order by
            segments1_.number asc
     -path2.segments=[(id=3,number=1), (id=1,number=2), (id=2,number=3)]
    ...
    [INFO] BUILD SUCCESS
    
  11. Thats great that we can get the list ordered in a way we want when pulled from the database. However -- what if we wanted things ordered without a round-trip to the database. Java Lists are meant to be sorted so lets add a few extra steps to this section.

  12. Change the behavior of adding an element to the collection. Add a sort and a Comparator so adding a new entry causes the list to be re-sorted according to the business property.

        public Path addSegment(Segment segment) {
            getSegments().add(segment);
            Collections.sort(segments, new Comparator<Segment>() {
                @Override
                public int compare(Segment lhs, Segment rhs) {
                    if (lhs == rhs || lhs==null && rhs == null) { return 0; }
                    if (lhs != null && rhs == null) { return 1; }
                    if (lhs == null && rhs != null) { return -1; }
                    int result = lhs.getNumber() - rhs.getNumber();
                    log.debug(lhs.getClass().getSimpleName() + lhs.toString() + 
                            ".compareTo" + rhs.toString() + 
                            "=" + result
                            );
                    return result;
                }});
            return this;
        }
    
  13. Change the order of the assert statements in the first section of the unit test. We should now have a list that is sorted by business property before being stored in the database.

    
    
            log.debug("path.segments=" + path.getSegments());
            Iterator<Segment> itr = path.getSegments().iterator();
            assertEquals(1, itr.next().getNumber());
            assertEquals(2, itr.next().getNumber());
            assertEquals(3, itr.next().getNumber());
  14. Rebuild the module, re-run the unit test, and notice how the elements of the list are ordered prior to being pulled back. We didn't save the provider or database any work -- but we did make our abstraction of the list more consistent.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testOrderBy
    ...
     -*** testOrderBy ***
     -Segment(id=0,number=3).compareTo(id=0,number=2)=1
     -Segment(id=0,number=3).compareTo(id=0,number=2)=1
     -Segment(id=0,number=1).compareTo(id=0,number=3)=-2
     -Segment(id=0,number=1).compareTo(id=0,number=3)=-2
     -Segment(id=0,number=1).compareTo(id=0,number=2)=-1
     -path.segments=[(id=0,number=1), (id=0,number=2), (id=0,number=3)]
    ...
    [INFO] BUILD SUCCESS
    
  15. Add the following to your child entity class. We are going to make the entities be able to order themselves. This works find if there is a standard, comparable property (or set of properties) and can clean up the parent classes from having to define sort information.

    
    
    public class Segment implements Comparable<Segment>{
    ...
    
    
    ...
        @Override
        public int compareTo(Segment rhs) {
            if (this == rhs) { return 0; }
            int result = number - rhs.number;
            log.debug(getClass().getSimpleName() + toString() + 
                    ".compareTo" + rhs.toString() + 
                    "=" + result
                    );
            return result;
        }
  16. Change the implementation of the parent to the following. All we did was move the burden of the compare from the container to the elements within the container.

    
    
        public Path addSegment(Segment segment) {
            getSegments().add(segment);
            Collections.sort(segments);
            return this;
        }
  17. Rebuild the module and re-run the test method. Observe the ordering is still preserved with the new approach.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testOrderBy
    ...
     -*** testOrderBy ***
     -Segment(id=0,number=3).compareTo(id=0,number=2)=1
     -Segment(id=0,number=3).compareTo(id=0,number=2)=1
     -Segment(id=0,number=1).compareTo(id=0,number=3)=-2
     -Segment(id=0,number=1).compareTo(id=0,number=3)=-2
     -Segment(id=0,number=1).compareTo(id=0,number=2)=-1
     -path.segments=[(id=0,number=1), (id=0,number=2), (id=0,number=3)]
    

You have finished looking at ordered collections. You saw how a list could be ordered by the provider when queried by the database. You also saw how implementing the Java Comparator or Comparable interface could provide sorting outside of the scope of the database query.

In this section we will demonstrate the use of different collection interfaces that can be used with JPA.

In this section we will demonstrate the mapping of a collection using a java.util.Map interface. This technique is useful for un-ordered collections with elements that are normally accessed by an entity property from the parent/one side of the relationship. Note that access to any entity by any property is always available through the EntityManager and JPAQL.

  1. Add the following class to your src/main tree. This entity class will be referenced via a Map from its parent in a one-to-many, uni-directional relationship.

    
    
    package myorg.relex.collection;
    import javax.persistence.*;
    /**
     * This class is an example of an entity that will be referenced from the parent in its relationship
     * through a Map which uses a value unique to that parent.
     */
    @Entity
    @Table(name="RELATIONEX_POSITION")
    public class Position {
        @Id @GeneratedValue
        private int id;
        
        @Column(length=12, nullable=false)
        private String position; //this is not unique within this table
        
        @Column(length=32, nullable=false, unique=true)
        private String player; //this is unique within the table
        
        protected Position() {}
        public Position(String position, String player) {
            this.position = position;
            this.player = player;
        }
        public int getId() { return id; }
        public String getPosition() { return position; }
        public void setPosition(String position) { this.position = position; }
        public String getPlayer() { return player; }
        public void setPlayer(String player) {
            this.player = player;
        }
        
        @Override
        public int hashCode() {
            return position==null?0:position.hashCode() + player==null?0:player.hashCode(); 
        }
        
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return true; }
                Position rhs = (Position) obj;
                if (position==null || player==null) { return false; }
                return position.equals(rhs.position) && player.equals(rhs.player);
            } catch (Exception ex) { return false; }
        }
    }
  2. Put the following class in your src/main tree. This entity class implements the one-to-many, uni-directional relationship as a Map. Since the parent uses a Map keyed by an entity property, there can only be a relationship to children from a common parent where the children have different property values. Since the property being used is not unique within the child table -- then not all children will be allowed to be associated with this parent entity at the same time.

    
    
    package myorg.relex.collection;
    import java.util.HashMap;
    import java.util.Map;
    import javax.persistence.*;
    /**
     * This class provides an example of a parent that uses a Map to reference child members.
     */
    @Entity
    @Table(name="RELATIONEX_LINEUP")
    public class Lineup {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany
        @MapKey(name="position")
        @JoinColumn(name="LINEUP_ID")
        private Map<String, Position> positions;
        
        @Column(length=10)
        private String team;
        public int getId() { return id; }
        public Map<String, Position> getPositions() {
            if (positions==null) { positions = new HashMap<String, Position>(); }
            return positions; 
        }
        public Lineup addPosition(Position position) {
            if (position==null) { return this; }
            getPositions().put(position.getPosition(), position);
            return this;
        }
        public String getTeam() { return team; }
        public void setTeam(String team) {
            this.team = team;
        }
    }
  3. Add the entity classes to the persistence unit.

    
    
            <class>myorg.relex.collection.Position</class>
            <class>myorg.relex.collection.Lineup</class>
  4. Add the following test method to your existing JUnit test method.

    
    
        @Test
        public void testMap() {
            log.info("*** testMap ***");
            Position players[] = new Position[] {
                    new Position("1st", "who"),
                    new Position("2nd", "what"),
                    new Position("3rd", "idontknow"),
                    new Position("1st", "whom"),
                    new Position("1st", "whoever")
            };
            log.debug("persisting players");
            for (Position p: players) {
                em.persist(p);
            }
            Lineup lineup = new Lineup();
            lineup.setTeam("today");
            lineup.addPosition(players[0]);
            lineup.addPosition(players[1]);
            lineup.addPosition(players[2]);
            log.debug("persisting lineup");
            em.persist(lineup);
        }
  5. Build the module and run the new unit test method. Nothing significant at this point to observe other than to possibly notice the foreign key assignments with some of the child table rows.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testMap
    ...
     -*** testMap ***
     -persisting players
    ...
    [INFO] BUILD SUCCESS
    
  6. Add the following lines to the test method. This will verify the expected entities from the child table are associated with the parent and accessible through the map using a property of the child entity.

    
    
            log.debug("getting new lineup instance");
            em.flush(); em.clear();
            Lineup lineup2 = em.find(Lineup.class, lineup.getId());
            assertEquals("unexpected size", lineup.getPositions().size(), lineup2.getPositions().size());
            for (int i=0; i<lineup.getPositions().size(); i++) {
                assertNotNull(players[i].getPlayer() + " not found", lineup2.getPositions().get(players[i].getPosition()));
            }
  7. Rebuild the module and re-run the test method. Nothing unique occurs with the database, but observe the asserts within the test case should be passing.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testMap
    ...
     -getting new lineup instance
    ...
    [INFO] BUILD SUCCESS
    
  8. Add the following lines to your unit test. This will attempt to replace an entry in the Map with a new entry. This should remove the association with the former entity and form an relationship with the new entity since they share the same entity property.

    
    
            log.debug("adding new player for position");
            lineup2.addPosition(players[3]);
            assertEquals("number of positions changed", lineup.getPositions().size(), lineup2.getPositions().size());
            em.flush();
  9. Rebuild the module and re-run the test method. Notice the foreign key being set to null for the former entity and the foreign key being set for the new entity being added to the Map.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testMap
    ...
     -adding new player for position
    Hibernate: 
        update
            RELATIONEX_POSITION 
        set
            LINEUP_ID=null 
        where
            LINEUP_ID=? 
            and id=?
    Hibernate: 
        update
            RELATIONEX_POSITION 
        set
            LINEUP_ID=? 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    
  10. Add the following lines to your test method. They will verify the foreign key state of each row in the child table using the known state of the unit test.

            log.debug("checking positions");
            @SuppressWarnings("unchecked")
            List<Object[]> rows = em.createNativeQuery("select ID, LINEUP_ID from RELATIONEX_POSITION").getResultList();
            for (Object[] val : rows) {
                int id = (Integer)val[0];
                Integer lineupId = (Integer)val[1];
                if (id==players[1].getId() || id==players[2].getId() || id==players[3].getId()) {
                    assertNotNull("unexpected lineupId", lineupId);
                } else {
                    assertNull("lineupId was assigned for " + id, lineupId);
                }
            }
    
  11. Rebuild the module and re-run the unit test. The final asserts should pass.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.CollectionTest#testMap
    ...
     -checking positions
    Hibernate: 
        select
            ID,
            LINEUP_ID 
        from
            RELATIONEX_POSITION
    ...
    [INFO] BUILD SUCCESS
    
  12. The following lists the final state of the child table at the completion of the unit test.

    SELECT * FROM RELATIONEX_POSITION;
    ID      PLAYER      POSITION    LINEUP_ID  
    1       who         1st         null
    2       what        2nd         1
    3       idontknow   3rd         1
    4       whom        1st         1
    5       whoever     1st         null
    

You have completed a brief look at collection types used by JPA. In this exercise you used a Map which permitted a relationship with child entities that had unique values for the @MapKey. Foreign keys are created when we add a child entity to the Map and removed when we remove or overwrite entries in the Map. Other types of collections supported by JPA include Set, List, and Collection.

Now that we are done the tour into one-to-many relations and collections themselves, we can turn our attention on the many side and aspects associated with implementing a many-to-one relationship. Our first stab at this will stick to the uni-directional case. Many to one, uni-directional relationships are especially appropriate when the many side is huge and should best be obtained through a query rather than a through a collection in the parent entity. However, many things we learn here will apply to the one-to-many/many-to-one bi-directional case.

Many-to-one relationships map easily to the database because that is exactly how relationships are commonly formed in the database -- a foreign key from the many/child table to the one/parent table. We will start simple and then get into some interesting cases.

In this section you will create a many-to-one, uni-directional relationship where the child entity references the parent entity using a separate foreign key.

  1. Put the following class in your src/main tree. It provides an example of the inverse side of a many-to-one, uni-directional relationship -- which means it will have no reference to the relationship.

    
    
    package myorg.relex.many2one;
    import javax.persistence.*;
    /**
     * This class provides an example one/parent entity in a many-to-one, uni-directional relationship.
     * For that reason -- this class will not have any reference to the many entities that may possibly 
     * reference it. These many/child objects must be obtained through the entity manager using a find or query.
     */
    @Entity
    @Table(name="RELATIONEX_STATE")
    public class State {
        @Id @Column(length=2)
        private String id;
        
        @Column(length=20, nullable=false)
        private String name;
        
        protected State() {}
        public State(String id, String name) {
            this.id = id;
            this.name = name;
        }
        
        public String getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  2. Put the following class in your src/main tree. This entity class provides an example of the owning side of a many-to-one, uni-directional relationship materialized through a foreign key column in the entity class table. It currently relies on defaults that we will experiment with and change.

    
    
    package myorg.relex.many2one;
    import javax.persistence.*;
    /**
     * This class provides an example of the owning side of a many-to-one, uni-directional relationship
     * that is realized through a foreign key from the child to the parent entity.
     */
    @Entity
    @Table(name="RELATIONEX_STATERES")
    public class StateResident {
        @Id @GeneratedValue
        private int id;
        
        @ManyToOne(
    //          optional=false, 
    //          fetch=FetchType.EAGER
            )
    //  @JoinColumn(
    //          name="STATE_ID", 
    //          nullable=false
    //      )
        private State state;
        
        @Column(length=32)
        private String name;
        
        protected StateResident() {}
        public StateResident(State state) {
            this.state = state;
        }
        public int getId() { return id; }
        public State getState() { return state; }
        public void setState(State state) {
            this.state = state;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the two entity classes to your persistence unit.

    
    
            <class>myorg.relex.many2one.State</class>
            <class>myorg.relex.many2one.StateResident</class>
  4. Generate the database schema for the two entity classes and the relationship. Notice the provider automatically generated a foreign key column and named it after the entity table the column references.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_STATE (
            id varchar(12) not null,
            name varchar(20) not null,
            primary key (id)
        );
    
        create table RELATIONEX_STATERES (
            id integer generated by default as identity,
            name varchar(32),
            state_id varchar(12), <!------- GENERATED FK Column
            primary key (id)
        );
    ...
        alter table RELATIONEX_STATERES 
            add constraint FK88A9D0FF4006DFB7 
            foreign key (state_id) 
            references RELATIONEX_STATE;
    
  5. Change the child entity to make use of a foreign key column explicit -- with a specific name and nullable constraint.

    
    
        @ManyToOne
        @JoinColumn(
                name="STATE_ID", 
                nullable=false
            )
        private State state;
  6. Regenerate the database schema and note the changes made to the schema using the more explicit declaration of the foreign key column. It now

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_STATERES (
            id integer generated by default as identity,
            name varchar(32),
            STATE_ID varchar(12) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_STATERES 
            add constraint FK88A9D0FF4006DFB7 
            foreign key (STATE_ID) 
            references RELATIONEX_STATE;
    ...
    
  7. Note there are two ways to get the provider to recognize whether the foreign key column is required. We demonstrated the table/column-centric approach above. The following uses a relationship-centric approach. You can optionally change your relationship definition to the following to show that the foreign key column is defined to be non-null in both cases.

    
    
        @ManyToOne(
                optional=false)
        @JoinColumn(
                name="STATE_ID"//, 
    //          nullable=false
            )
        private State state;
        create table RELATIONEX_STATERES (
            id integer generated by default as identity,
            name varchar(32),
            STATE_ID varchar(12) not null,
            primary key (id)
        );
    
  8. Create the following test method in your JUnit test case. This test method will create an instance of both the one and many side, relate them, and persist them. The flush() is only there so we can control/see the specific database commands and is not a required call within the body of the transaction. Note we arranged the order of the persists and left off any cascade definitions to show a point here that will initially cause an error.

    
    
        @Test
        public void testManyToOneUniFK() {
            log.info("*** testManyToOneUniFK ***");
            
            State state = new State("MD", "Maryland");
            StateResident res = new StateResident(state);
            res.setName("joe");
            log.debug("persisting child");
            em.persist(res);
            log.debug("persisting parent");
            em.persist(state);
            em.flush();
        }
  9. Build the module and run the new test method. Notice this produces a foreign key constraint error because the child is being persisted to the database prior to the parent being managed.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniFK
    ...
     -*** testManyToOneUniFK ***
     -persisting child
    Hibernate: 
        select
            state_.id,
            state_.name as name28_ 
        from
            RELATIONEX_STATE state_ 
        where
            state_.id=?
    Hibernate: 
        insert 
        into
            RELATIONEX_STATERES
            (id, name, STATE_ID) 
        values
            (null, ?, ?)
    ...
    Tests in error: 
      testManyToOneUniFK(myorg.relex.Many2OneTest): org.hibernate.exception.ConstraintViolationException: NULL not allowed for column "STATE_ID"; SQL statement:(..)
    ...
    [INFO] BUILD FAILURE
    

    We could attempt to fix this with a cascade.PERSIST from the child to the parent, but that just seems wrong. The parent should exist prior to assigning children. There may be additional business rules that must go on to create the parent that is outside the scope of creating the child and child/parent relationship.

  10. Re-order the creates to better simulate the parent being created prior to adding the child entities.

    
    
            log.debug("persisting parent");
            em.persist(state);
            log.debug("persisting child");
            em.persist(res);
            em.flush();
  11. Rebuild the module and re-run the test method. With the re-ordered persist() calls the provider is able to successfully store our parent, child, and relationship.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniFK
    ...
     -persisting parent
     -persisting child
    Hibernate: 
        insert 
        into
            RELATIONEX_STATE
            (name, id) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_STATERES
            (id, name, STATE_ID) 
        values
            (null, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  12. Add the following lines to your test method. This will verify we can obtain the child and its associated parent.

    
    
            log.debug("getting new instances");
            em.clear();
            StateResident res2 = em.find(StateResident.class, res.getId());
            log.debug("checking child");
            assertEquals("unexpected child data", res.getName(), res2.getName());
            log.debug("checking parent");
            assertEquals("unexpected parent data", state.getName());
  13. Rebuild the module and re-run the test method will the extra calls to retrieve a fresh instance. Notice, under these conditions, the provider chose to use an EAGER fetch during the find() and there was no LAZY load during the calls to the child and parent.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniFK
    ...
     -getting new instances
    Hibernate: 
        select
            stateresid0_.id as id29_1_,
            stateresid0_.name as name29_1_,
            stateresid0_.STATE_ID as STATE3_29_1_,
            state1_.id as id28_0_,
            state1_.name as name28_0_ 
        from
            RELATIONEX_STATERES stateresid0_ 
        inner join                           <!=== EAGER fetch of parent
            RELATIONEX_STATE state1_ 
                on stateresid0_.STATE_ID=state1_.id 
        where
            stateresid0_.id=?
     -checking child
     -checking parent
    ...
    [INFO] BUILD SUCCESS
    
  14. Optionally change the fetch type for the parent to a LAZY fetch. Although the provider is permitted to ignore requests for LAZY fetch (but must honor requests for EAGER), we do get a shallower query during the find() for the child and then a second query for the parent when it is accessed.

        @ManyToOne(
                optional=false, 
                fetch=FetchType.LAZY
            )
        @JoinColumn(name="STATE_ID")
        private State state;
    
     -getting new instances
    Hibernate: 
        select
            stateresid0_.id as id29_0_,
            stateresid0_.name as name29_0_,
            stateresid0_.STATE_ID as STATE3_29_0_ 
        from
            RELATIONEX_STATERES stateresid0_ 
        where
            stateresid0_.id=?
     -checking child
     -checking parent
    Hibernate: 
        select
            state0_.id as id28_0_,
            state0_.name as name28_0_ 
        from
            RELATIONEX_STATE state0_ 
        where
            state0_.id=?
    ...
    [INFO] BUILD SUCCESS
    
  15. Add another child object for the same parent.

            log.debug("add more residents");
            StateResident resB = new StateResident(res2.getState());
            em.persist(resB);
            em.flush();
    
  16. Rebuild and re-run the test method. Observe the provider issues a database insert for only the new child entity with its foreign key already set to the existing parent.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniFK
    ...
     -add more residents
    Hibernate: 
        insert 
        into
            RELATIONEX_STATERES
            (id, name, STATE_ID) 
        values
            (null, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  17. Add the following lines to your unit test. This will clear the cache of all instances and query for new instances. Note that we need to make use of the query since this is a uni-directional relationship and the parent has no knowledge of the relationship.

            log.debug("getting new instances of residences");
            em.clear();
            List<StateResident> residents = em.createQuery(
                        "select r from StateResident r " +
                        "where r.state.id=:stateId", 
                        StateResident.class)
                    .setParameter("stateId", res.getState().getId())
                    .getResultList();
            assertEquals("unexpected number of residents", 2, residents.size());
    
  18. Rebuild the module and re-run the test method. Notice since we are using a LAZY fetch, only the child entities are returned from the database.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniFK
    ...
     -getting new instances of residences
    Hibernate: 
        select
            stateresid0_.id as id29_,
            stateresid0_.name as name29_,
            stateresid0_.STATE_ID as STATE3_29_ 
        from
            RELATIONEX_STATERES stateresid0_ 
        where
            stateresid0_.STATE_ID=?
    ...
    [INFO] BUILD SUCCESS
    
  19. If you change the relationship back to an EAGER fetch, you will notice the provider still initially queries for the child entities and then issues a second query for parents matching specific values.

        @ManyToOne(
                optional=false, 
                fetch=FetchType.EAGER
            )
        @JoinColumn(name="STATE_ID")
        private State state;
    
     -getting new instances of residences
    Hibernate: 
        select
            stateresid0_.id as id29_,
            stateresid0_.name as name29_,
            stateresid0_.STATE_ID as STATE3_29_ 
        from
            RELATIONEX_STATERES stateresid0_ 
        where
            stateresid0_.STATE_ID=?
    Hibernate: 
        select
            state0_.id as id28_0_,
            state0_.name as name28_0_ 
        from
            RELATIONEX_STATE state0_ 
        where
            state0_.id=?
    ...
    [INFO] BUILD SUCCESS
    
  20. Add the following lines to your test method. This will verify that all managed children (the many) reference a common parent (the one) instance. We are implementing the check in terms of a change to one parent and attempting to observe that change in another child.

    
    
            log.debug("changing state/data of common parent");
            residents.get(0).getState().setName("Home State");
            assertEquals("unexpected difference in parent data", 
                    residents.get(0).getState().getName(),
                    residents.get(1).getState().getName());
  21. Rebuild the module and re-run the test method. Observe our assertion passes and we have a single update to the database.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniFK
    ...
     -changing state/data of common parent
    Hibernate: 
        update
            RELATIONEX_STATE 
        set
            name=? 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished going through a many-to-one, uni-directional relationship mapped using a single foreign key column in the child entity table. The parent maintains no collection or reference to the children. All references are maintained by the entity and obtained through a find() or query() of the children. This is quite appropriate when the size of the many can be quite large and is best to be queried for where paging can be leveraged or other types of queries to reduce the total count returned.

In the next section we will complicate the situation slightly by using a parent entity with a compound primary key -- which ripples over to the foreign key used by the child.

It this section we will create a new complication to the foreign key join by making the primary key of the parent entity a compound primary key (i.e, multiple columns). This will cause the related child entity to map more than one column as its foreign key to the parent.

In this example we will use an embeddable primary key class as an @EmbeddedId in the parent entity.

  1. Place the following embeddable primary key class in your src/main tree. Notice that it models two primary key values; number and street. It currently defines no specific mappings for the two properties, but we will experiment with changes later.

    
    
    package myorg.relex.many2one;
    import java.io.Serializable;
    import javax.persistence.*;
    /**
     * This class provides an example compound primary key value that will be used in a many-to-one,
     * uni-directional relationship.
     */
    @Embeddable
    public class HousePK implements Serializable {
        private static final long serialVersionUID = 5213787609029123676L;
    //  @Column(name="NO")
        private int number;
    //  @Column(name="STR", length=50)
        private String street;
        
        public HousePK() {}
        public HousePK(int number, String street) {
            this.number = number;
            this.street = street;
        }
        
        public int getNumber() { return number; }
        public void setNumber(int number) {
            this.number = number;
        }
        
        public String getStreet() { return street; }
        public void setStreet(String street) {
            this.street = street;
        }
        @Override
        public int hashCode() {
            return number + street==null?0:street.hashCode();
        }
        
        @Override
        public boolean equals(Object obj) {
            try {
                if (this==obj) { return true; }
                HousePK rhs = (HousePK)obj;
                if (street==null && rhs.street != null) { return false; }
                return number==rhs.number && street.equals(rhs.street);
            } catch (Exception ex) { return false; }
        }   
    }

    Note, as a primary key class, it ...

  2. Put the following parent entity class in your src/main tree. This class provides an example of the parent/one/inverse side of a many-to-one, uni-directional relationship. In that role, it has no reference to the child entity. However, since this is a compound primary/foreign key example, it defines its primary key in terms of the primary key class you added above. There were two choices in implementing the compound primary key; @IdClass or @EmbeddedId. With the @IdClass approach -- the entity would have modeled the number and street properties as properties of this class. With the @EmbeddedId approach, the entity models the properties as an opaque set encapsulated by the primary key class.

    
    
    package myorg.relex.many2one;
    import javax.persistence.*;
    /**
     * This class provides an example of a parent/inverse side of a many-to-one, uni-directional relationship where
     * the parent and foreign key must use a compound value.
     */
    @Entity
    @Table(name="RELATIONEX_HOUSE")
    public class House {
        @EmbeddedId
    //  @AttributeOverrides({
    //      @AttributeOverride(name="street", column=@Column(name="STREET_NAME", length=20)),
    //  })
        private HousePK id;
        
        @Column(length=16, nullable=false)
        private String name;
        
        protected House() {}
        public House(HousePK id, String name) {
            this.id = id;
            this.name = name;
        }
        
        public HousePK getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the following child entity class to your src/main tree. This class is the owning side of the many-to-one relation and therefore has a reference to the one/parent side and a mapping of the relationship to the database. We currently have the class implemented to accepts all defaults and will be soon making changes after we see what default schema is applied.

    
    
    package myorg.relex.many2one;
    import javax.persistence.*;
    /**
     * This class provides an example of the owning/child side of a many-to-one, uni-directional relationship
     * where the parent uses a (embedded) compound primary key.
     */
    @Entity
    @Table(name="RELATIONEX_OCCUPANT")
    public class Occupant {
        @Id @GeneratedValue
        private int id;
        
        @ManyToOne(optional=false)
    //  @JoinColumns({
    //          @JoinColumn(name="RES_NUM", referencedColumnName="NUMBER"),
    //          @JoinColumn(name="RES_STR", referencedColumnName="STREET_NAME")
    //  })
        private House residence;
        
        @Column(length=16, nullable=false)
        private String name;
        protected Occupant(){}
        public Occupant(String name, House residence) {
            this.name = name;
            this.residence = residence;
        }
        
        public int getId() { return id; }
        public House getResidence() { return residence; }
        public void setResidence(House residence) {
            this.residence = residence;
        }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  4. Add the new entity classes to the persistence unit. Do *not* list the primary key class.

    
    
            <class>myorg.relex.many2one.House</class>
            <class>myorg.relex.many2one.Occupant</class>
  5. Generate schema for the model and observe the schema generated for the new entities. Notice how the default mapping properties of the primary key class was used within the parent entity table and the child table created values derived from the parent column name and parent reference variable name (i.e., (parentVarName)_(PARENT_COLUMNNAME)).

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_HOUSE (
            number integer not null,
            street varchar(255) not null,
            name varchar(16) not null,
            primary key (number, street)
        );
    ...
        create table RELATIONEX_OCCUPANT (
            id integer generated by default as identity,
            name varchar(16) not null,
            residence_number integer not null,
            residence_street varchar(255) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_OCCUPANT 
            add constraint FK6957B84D35A694BB 
            foreign key (residence_number, residence_street) 
            references RELATIONEX_HOUSE;
    
  6. Make the following annotation change to the primary key class and observe how that impacts the schema generated.

    
    
        @Column(name="NO")
        private int number;
        @Column(name="STR", length=50)
        private String street;

    The parent entity table inherited the column definitions from the primary key class. Since the parent column names changed, the default child entity table foreign key column names changed to match.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_HOUSE (
            NO integer not null,
            STR varchar(50) not null,
            name varchar(16) not null,
            primary key (NO, STR)
        );
    ...
        create table RELATIONEX_OCCUPANT (
            id integer generated by default as identity,
            name varchar(16) not null,
            residence_NO integer not null,
            residence_STR varchar(50) not null,
            primary key (id)
        );
    ...
       alter table RELATIONEX_OCCUPANT 
            add constraint FK6957B84D81D611CF 
            foreign key (residence_NO, residence_STR) 
            references RELATIONEX_HOUSE;    
    
  7. Create a mapping override in the parent entity so one of the columns is defined by an entity override and the remaining property is still defined by the primary key class. You do not need to make any changes to the primary key class here.

    
    
    public class House {
        @EmbeddedId
        @AttributeOverrides({
            @AttributeOverride(name="street", column=@Column(name="STREET_NAME", length=20)),
        })
        private HousePK id;

    Notice how the parent entity table primary key column definition is a blend of specifications from the primary key class and overrides from the parent entity class.

        create table RELATIONEX_HOUSE (
            NO integer not null,
            STREET_NAME varchar(20) not null,
            name varchar(16) not null,
            primary key (NO, STREET_NAME)
        );
    ...
        create table RELATIONEX_OCCUPANT (
            id integer generated by default as identity,
            name varchar(16) not null,
            residence_NO integer not null,
            residence_STREET_NAME varchar(20) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_OCCUPANT 
            add constraint FK6957B84D8AD189E5 
            foreign key (residence_NO, residence_STREET_NAME) 
    

    Notice how the child entity table foreign key columns, by default, change to match the parent primary key column names.

  8. Add column specifications for the child entity table. You will notice that we are defining a @JoinColumn for each foreign key mapping we wish to override and we must wrap multiple @JoinColumn annotations within a @JoinColumns array

    Note too the referencedColumnName must match what the parent entity table is currently using. That means if you are changing values or overrides with the parent primary key column *AND* you are attempting to override the default foreign key column properties -- these must match.

    
    
    public class Occupant {
    ...
        @ManyToOne(optional=false)
        @JoinColumns({
                @JoinColumn(name="RES_NUM", referencedColumnName="NO"),
                @JoinColumn(name="RES_STR", referencedColumnName="STREET_NAME")
        })
        private House residence;
        create table RELATIONEX_HOUSE (
            NO integer not null,
            STREET_NAME varchar(20) not null,
            name varchar(16) not null,
            primary key (NO, STREET_NAME)
        );
    ...
        create table RELATIONEX_OCCUPANT (
            id integer generated by default as identity,
            name varchar(16) not null,
            RES_NUM integer not null,
            RES_STR varchar(20) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_OCCUPANT 
            add constraint FK6957B84D739A6436 
            foreign key (RES_NUM, RES_STR) 
            references RELATIONEX_HOUSE;
    
  9. Add the following test method to your JUnit test case. This test will create and instance of the parent and child, relate them, and persist them. Learning from the lessons of the previous section, we made sure the parent existed prior to persisting the child.

    
    
        @Test
        public void testManyToOneUniCompoundFK() {
            log.info("*** testManyToOneUniCompoundFK ***");
            
            House house = new House(new HousePK(1600,"PA Ave"),"White House");
            Occupant occupant = new Occupant("bo", house);
            log.debug("persisting parent");
            em.persist(house);
            log.debug("persisting child");
            em.persist(occupant);
            em.flush();
  10. Build the module and run the new test method. Notice the extra columns used to persist the primary key of the parent and the foreign key of the child.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniCompoundFK
    ...
     -*** testManyToOneUniCompoundFK ***
     -persisting parent
     -persisting child
    Hibernate: 
        insert 
        into
            RELATIONEX_HOUSE
            (name, NO, STREET_NAME) 
        values
            (?, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_OCCUPANT
            (id, name, RES_NUM, RES_STR) 
        values
            (null, ?, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  11. Add the following lines to your test method to verify we can obtain a reference to the child and parent through a find().

    
    
            log.debug("getting new instances");
            em.clear();
            Occupant occupant2 = em.find(Occupant.class, occupant.getId());
            log.debug("checking child");
            assertEquals("unexpected child data", occupant.getName(), occupant2.getName());
            log.debug("checking parent");
            assertEquals("unexpected parent data", house.getName(), occupant2.getResidence().getName());
  12. Rebuild the module and re-run the test method. As in the previous section, the find, by default, performs an EAGER fetch on the parent. This time, however, the join uses both primary/foreign columns.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniCompoundFK
    ...
    -getting new instances
    Hibernate: 
        select
            occupant0_.id as id31_1_,
            occupant0_.name as name31_1_,
            occupant0_.RES_NUM as RES3_31_1_,
            occupant0_.RES_STR as RES4_31_1_,
            house1_.NO as NO30_0_,
            house1_.STREET_NAME as STREET2_30_0_,
            house1_.name as name30_0_ 
        from
            RELATIONEX_OCCUPANT occupant0_ 
        inner join
            RELATIONEX_HOUSE house1_ 
                on occupant0_.RES_NUM=house1_.NO 
                and occupant0_.RES_STR=house1_.STREET_NAME 
        where
            occupant0_.id=?
     -checking child
     -checking parent
    ...
    [INFO] BUILD SUCCESS
    
  13. Add the following lines to your test method. This will test adding a second child for a common parent.

    
    
            log.debug("add more child entities");
            Occupant occupantB = new Occupant("miss beazily", occupant2.getResidence());
            em.persist(occupantB);
            em.flush();
  14. Rebuild the module and re-run the test method. Notice how only the new child needs to be persisted -- along with the foreign key columns to reference the parent.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniCompoundFK
    ...
     -add more child entities
    Hibernate: 
        insert 
        into
            RELATIONEX_OCCUPANT
            (id, name, RES_NUM, RES_STR) 
        values
            (null, ?, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  15. Add the following lines to your test method. This will query for child entities related to our parent. Note this is the only way (other than find()) that we can locate the child entities since no reference to the child entities is maintained in a many-to-one, uni-directional relationship.

    
    
            log.debug("getting new instances of children");
            em.clear();
            List<Occupant> occupants = em.createQuery(
                        "select o from Occupant o " +
                        "where o.residence.id=:houseId", 
                        Occupant.class)
                    .setParameter("houseId", occupant.getResidence().getId())
                    .getResultList();
            assertEquals("unexpected number of children", 2, occupants.size());

    Take extra note of what we did during the query and query parameter above. We did *not* pass in the individual primary key properties and do a property-by-property match. That would have been tedious and error prone if the primary key was re-factored. What we did instead was to pass in the opaque primary key object and let the provider, with its mapping knowledge of the primary key class and the parent/child entity classes -- form the details of the where clause.

  16. Rebuild the module and re-run the test method. Notice how the provider broke down the opaque primary key object we passed as a parameter and created the individual column tests in the where.

     -getting new instances of children
    Hibernate: 
        select
            occupant0_.id as id31_,
            occupant0_.name as name31_,
            occupant0_.RES_NUM as RES3_31_,
            occupant0_.RES_STR as RES4_31_ 
        from
            RELATIONEX_OCCUPANT occupant0_ 
        where
            occupant0_.RES_NUM=?       <!== provider created breakdown from PK comparison
            and occupant0_.RES_STR=?           to individual column comparisons
    Hibernate: 
        select
            house0_.NO as NO30_0_,
            house0_.STREET_NAME as STREET2_30_0_,
            house0_.name as name30_0_ 
        from
            RELATIONEX_HOUSE house0_ 
        where
            house0_.NO=? 
            and house0_.STREET_NAME=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished taking a look at the impact of a parent entity using a compound primary key when there is a relationship from a child entity. The circumstances around this situation is not all that unlike the one-to-one case, but it is still worthy covering specifically here.

In the next section we will shift the emphasis of the compound primary key from the parent to the child where the child identity is derived from a property of the parent.

In this section we are going to revisit a topic that was addressed during the one-to-one relationship coverage. We are going to take a look at a child class that uses a compound primary key with one of the properties of the primary key derived from the foreign key to the parent entity. This means that one of the columns of the child table will be both a primary key value and a foreign key value at the same time.

  1. Add the following parent entity class to your src/main tree. This parent entity class will have its primary key automatically assigned by the container and the foreign key of the children entities will have to reference that column.

    
    
    package myorg.relex.many2one;
    import javax.persistence.*;
    /**
     * This class is an example of a parent in a many-to-one, uni-directional relation where the 
     * primary key of the child is derived from the primary key of the parent. 
     */
    @Entity
    @Table(name="RELATIONEX_ITEMTYPE")
    public class ItemType {
        @Id @GeneratedValue
        private int id;
        
        @Column(length=20, nullable=false)
        private String name;
        
        protected ItemType() {}
        public ItemType(String name) {
            this.name = name;
        }
        public int getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return id +":" + name;
        }
    }
  2. Since the child entities will have a two properties -- one of them derived from the parent entity -- we need to model a compound primary key class for the child entity. Place the following primary key class in your src/main tree. It models two properties

    
    
    package myorg.relex.many2one;
    import java.io.Serializable;
    import javax.persistence.*;
    /**
     * This class provides an example primary key class for a child entity that
     * derives one of its primary key values from its parent entity in a many-to-one
     * relationship.
     */
    @SuppressWarnings("serial")
    @Embeddable
    public class ItemPK implements Serializable {
    public class ItemPK implements Serializable {
    //    @Column(name="TYPE_ID_PK")
        private int typeId; //unique value from parent ItemType.id  
    //    @Column(name="NUMBER_PK")
        private int number; //unique value assigned to instances of Item
        public int getTypeId() { return typeId; }
        public ItemPK setTypeId(int typeId) {
            this.typeId = typeId;
            return this;
        }   
        public int getNumber() { return number; }
        public ItemPK setNumber(int number) {
            this.number = number;
            return this;
        }
        
        @Override
        public int hashCode() {
            return typeId + number;
        }   
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return true; }
                ItemPK rhs = (ItemPK) obj;
                return typeId==rhs.typeId && number==rhs.number;
            } catch (Exception ex) { return false; }
        }
        @Override
        public String toString() {
            return "(typeId=" + typeId + ",number=" + number + ")";
        }
    }
  3. Place the following child entity class in your src/main tree. This entity class uses an embedded primary key and a many-to-one relation to the parent entity. As stated, one of the properties (typeId) of the primary key will need to be the same value as the foreign key to the parent entity (itemType.id). The implementation below currently models that as two separate columns and we will need to work to correct.

    
    
    package myorg.relex.many2one;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example of a child entity that derives its primary key from 
     * the parent/one side of a many-to-one relation.
     */
    @Entity
    @Table(name="RELATIONEX_ITEM")
    public class Item {
        @EmbeddedId
        private ItemPK id;
        
        @ManyToOne(optional=false)
    //  @MapsId("typeId") //refers to the ItemPK.typeId property
    //  @JoinColumn(name="TYPE_ID")
        private ItemType itemType;
        
        @Temporal(TemporalType.TIMESTAMP)
        private Date created;
        
        protected Item() {}
        public Item(ItemType itemType, int number) {        
            this.itemType = itemType;
                //typeId in PK auto-mapped to itemType FK
            this.id = new ItemPK().setNumber(number);
        }
        public ItemPK getId() { return id; }
        public ItemType getItemType() { return itemType; }
        public Date getCreated() { return created; }
        public void setCreated(Date created) {
            this.created = created;
        }
        
        @Override
        public String toString() {
            return (itemType==null?null:itemType) + "pk=" + id;
        }
    }

    Pay special notice to the fact the child entity never sets the typeId property of the primary key. This property will be ignored by the provider and the property from the foreign key used instead once we get things mapped correctly.

  4. Add the two entity classes to your peristence unit. Do *not* list the primary key class.

    
    
            <class>myorg.relex.many2one.Item</class>
            <class>myorg.relex.many2one.ItemType</class>
  5. Generate database schema for the new entity classes and their relationship. Notice how there is a typeId from the compound primary key class property and a itemType_id from the foreign key. The provider is stating that our mapping treats the two uses of the same property as two separate columns in the same table. You can tell the typeId is part of the primary key from the "primary key (number, typeId)" declaration. You can tell itemType_id is a foreign key based on the "foreign key (itemType_id)" constraint declaration.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_ITEM (
            number integer not null,
            typeId integer not null,
            created timestamp,
            itemType_id integer not null,
            primary key (number, typeId)
        );
    
        create table RELATIONEX_ITEMTYPE (
            id integer generated by default as identity,
            name varchar(20) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_ITEM 
            add constraint FK355BBDA3C6C591FD 
            foreign key (itemType_id) 
            references RELATIONEX_ITEMTYPE;
    
  6. Add the @MapsId("typeId") annotation to the @ManyToOne relation. This signals the provider to map the column for the compound primary key "typeId" column to the same column used to map the foreign key for the itemType.

    
    
        @ManyToOne(optional=false)
        @MapsId("typeId") //refers to the ItemPK.typeId property
        private ItemType itemType;
  7. Regenerate schema and notice the two columns have mapped to the foreign key column. itemType_id still exists and has a foreign key constraint. itemType_id is now also listed in the primary key declaration.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_ITEM (
            number integer not null,
            created timestamp,
            itemType_id integer,
            primary key (number, itemType_id)
        );
    
        create table RELATIONEX_ITEMTYPE (
            id integer generated by default as identity,
            name varchar(20) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_ITEM 
            add constraint FK355BBDA3C6C591FD 
            foreign key (itemType_id) 
            references RELATIONEX_ITEMTYPE;
    
  8. Make a small cosmetic change by defining the column name for the foreign key column.

    
    
        @ManyToOne(optional=false)
        @MapsId("typeId") //refers to the ItemPK.typeId property
        @JoinColumn(name="TYPE_ID")
        private ItemType itemType;

    Notice we are renaming the foreign key that the primary key is mapped to and not the primary key.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_ITEM (
            number integer not null,
            TYPE_ID integer,
            created timestamp,
            primary key (number, TYPE_ID)
        );
    
        create table RELATIONEX_ITEMTYPE (
            id integer generated by default as identity,
            name varchar(20) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_ITEM 
            add constraint FK355BBDA349D11870 
            foreign key (TYPE_ID) 
            references RELATIONEX_ITEMTYPE;
    
  9. Further test the assertion above about the primary key being mapped to the foreign key by trying to define the primary key columns for Item. Add the following for the ItemPK class.

    
    
    public class ItemPK implements Serializable {
        @Column(name="TYPE_ID_PK")
        private int typeId; //unique value from parent ItemType.id  
        @Column(name="NUMBER_PK")
        private int number; //unique value assigned to instances of Item

    Notice only the non-FK annotation column definition was picked up from the primary key class. The other property is defined by the foreign key column definition in the entity class.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_ITEM (
            NUMBER_PK integer not null,
            TYPE_ID integer,
            created timestamp,
            primary key (NUMBER_PK, TYPE_ID)
        );
    
        create table RELATIONEX_ITEMTYPE (
            id integer generated by default as identity,
            name varchar(20) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_ITEM 
            add constraint FK355BBDA349D11870 
            foreign key (TYPE_ID) 
            references RELATIONEX_ITEMTYPE;
    
  10. Add the following test method to your unit test. As in the previous sections, this code will attempt to persist the parent, assign the managed parent to the child, and then persist the child. The flush()es were added simply to control when the database output was issued and printed and not a requirement of the scenario.

    
    
        @Test
        public void testManyToOneUniMapsIdEmbedded() {
            log.info("*** testManyToOneUniMapsIdEmbedded ***");
            
            ItemType type = new ItemType("snowblower");
            log.debug("persisting parent:" + type);
            em.persist(type);
            em.flush();
            log.debug("persisted parent:" + type);
            
            Item item = new Item(type,1);
            item.setCreated(new Date());
            log.debug("persisting child:" + item);
            em.persist(item);
            em.flush();
            log.debug("persisted child:" + item);
            //check PK assigned
            ItemPK pk = new ItemPK().setTypeId(type.getId()).setNumber(1);
            assertTrue(String.format("expected PK %s not match actual %s", pk, 
                    item.getId()), pk.equals(item.getId()));
        }
  11. Build the module and run the new unit test. Notice first how the parent primary key was unassigned until after the persist. After the persist, the output changed from 0 to a non-0 value. The same thing happened for the child entity. The property in the child's primary key that is mapped to the foreign key was left unassigned by the child entity class. The provider updated the primary key values during the persist and caused the primary key property to be equal to the foreign key value.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniMapsIdEmbedded
    ...
    -persisting parent:0:snowblower
    Hibernate: 
        insert 
        into
            RELATIONEX_ITEMTYPE
            (id, name) 
        values
            (null, ?)
     -persisted parent:1:snowblower
     -persisting child:1:snowblowerpk=(typeId=0,number=1)
    Hibernate:                                ^ 
        insert                                +- unassigned
        into
            RELATIONEX_ITEM
            (created, NUMBER_PK, TYPE_ID) 
        values                               +- assigned by provider
            (?, ?, ?)                        v
     -persisted child:1:snowblowerpk=(typeId=1,number=1)
    ...
    [INFO] BUILD SUCCESS
    
  12. Place the following lines in your test method. This will cause new instances to be pulled from the database and checked against their expected values.

    
    
            log.debug("getting new instances");
            em.clear();
            Item item2 = em.find(Item.class, pk);
            log.debug("checking child");
            assertNotNull("child not found by primary key:" + pk, item2);
            assertTrue("unexpected child data", item.getCreated().equals(item2.getCreated()));
            log.debug("checking parent");
            assertEquals("unexpected parent data", type.getName(), item2.getItemType().getName());
  13. Rebuild the module and re-run the test method. Notice the provider continues to perform an EAGER fetch of the parent when using find() to locate the child. The provider is able to locate the child entity using a primary key instance initialized with the parent's ID and a unique value assigned to the child number.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniMapsIdEmbedded
    ...
     -getting new instances
    Hibernate: 
        select
            item0_.NUMBER_PK as NUMBER1_32_1_,
            item0_.TYPE_ID as TYPE2_32_1_,
            item0_.created as created32_1_,
            itemtype1_.id as id33_0_,
            itemtype1_.name as name33_0_ 
        from
            RELATIONEX_ITEM item0_ 
        inner join
            RELATIONEX_ITEMTYPE itemtype1_ 
                on item0_.TYPE_ID=itemtype1_.id 
        where
            item0_.NUMBER_PK=? 
            and item0_.TYPE_ID=?
     -checking child
     -checking parent
    ...
    [INFO] BUILD SUCCESS
    
  14. Add the following lines to the test method. This will add additional child related to the same parent entity.

    
    
            Item itemB = new Item(item2.getItemType(),2);
            log.debug("add more child entities:" + itemB);
            itemB.setCreated(new Date());
            em.persist(itemB);
            em.flush();
            log.debug("new child entities added:" + itemB);
  15. Rebuild the module and re-run the test method. Notice, as before, only the foreign key and unique value is assigned to the new child entity prior to calling persist(). The primary key property used to identify the parent entity is unassigned going into the persist and then updated by the provider once the persist completes.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2OneTest#testManyToOneUniMapsIdEmbedded
    ...
     -add more child entities:1:snowblowerpk=(typeId=0,number=2)
    Hibernate:                                       ^
        insert                                       +- unassigned
        into
            RELATIONEX_ITEM
            (created, NUMBER_PK, TYPE_ID) 
        values                                        +- assigned by provider
            (?, ?, ?)                                 v
     -new child entities added:1:snowblowerpk=(typeId=1,number=2)
    ...
    [INFO] BUILD SUCCESS
    
  16. Add the following lines to the test method. These will get new instances of all the children associated with a common parent. Notice we are using the foreign key during the where clause and no the primary key value. It does not matter which value we use since they are both mapped to the same column.

    
    
            log.debug("getting new instances of children");
            em.clear();
            List<Item> items = em.createQuery(
                        "select i from Item i " +
                        "where i.itemType.id=:typeId", 
                        Item.class)
                    .setParameter("typeId", item.getItemType().getId())
                    .getResultList();
            assertEquals("unexpected number of children", 2, items.size());
  17. Rebuild the module and re-run the test method. Notice the column used during the where clause to get the child rows is the column we mapped for the foreign key.

     -getting new instances of children
    Hibernate: 
        select
            item0_.NUMBER_PK as NUMBER1_32_,
            item0_.TYPE_ID as TYPE2_32_,
            item0_.created as created32_ 
        from
            RELATIONEX_ITEM item0_ 
        where
            item0_.TYPE_ID=?
    Hibernate: 
        select
            itemtype0_.id as id33_0_,
            itemtype0_.name as name33_0_ 
        from
            RELATIONEX_ITEMTYPE itemtype0_ 
        where
            itemtype0_.id=?
    ...
    [INFO] BUILD SUCCESS
    
  18. If you changed the query to use the primary key instead of the foreign key we end up with the same query because (again) @MapsId caused these two properties to be mapped to the same column -- the foreign key column.

    
    
            List<Item> items = em.createQuery(
                        "select i from Item i " +
                        //"where i.itemType.id=:typeId", 
                        "where i.id.typeId=:typeId", 
                        Item.class)
    Hibernate: 
        select
            item0_.NUMBER_PK as NUMBER1_32_,
            item0_.TYPE_ID as TYPE2_32_,
            item0_.created as created32_ 
        from
            RELATIONEX_ITEM item0_ 
        where
            item0_.TYPE_ID=?
    

You have completed going through an example many-to-one, uni-directional relationship where the child identity is partially determined from the identity of the parent. By default, the provider would have mapped these as two separate columns. By using @MapsId on the relationship -- the property within the primary key class identified by the @MapsId has its column mapped to the same column used to reference the parent entity.

This example used the @EmbeddedId. Another option would have been the use of an @IdClass.

In this chapter we are going to combine the aspects of the one-to-many annd many-to-one to form a bi-directional relationship. The "bi-directional" aspects are solely at the Java class level and do not change anything about the database. Foreign keys and join tables will look just as they did in the uni-directional case. Howevever, in this case, we will be able to easily navigate from parent to child and child to parent through the use of a variable reference from either direction.

As with the one-to-one, bi-directional relationships we looked at in an earlier chapter, bi-directional relationships have an owning side and and inverse side. The owning side provides the mapping information and is the side of the relationship that drives the provider actions. The inverse side simply references the owning side (via "mappedBy" attribute). The inverse side will get initialized by the provider when obtaining object trees from the database. However the provider will not update or pay attention to the current state of the inverse side when it comes to persisting the state of the relation.

JPA does have some rules we need to follow when converting from uni-directional to bi-directional relationships. JPA requires the many side of a one-to-many, bi-directional relationship to be the owning side of that relationship. There is no choice to be made along those lines. That means the one side will always be the one side.

In this section we will demonstrate the use of a simple foreign key mapping from the owning/dependent entity table to the inverse/parent entity table.

  1. Put the following class in your src/main tree. This class provides an example of the one/parent side of a one-to-many, bi-directional relationship. It is currently incomplete and we will fix shortly. This biggest issue is the lack of a "mappedBy" attribute in the @OneToMany mapping. That attribute is required to form the bi-directional relationship.

    
    
    package myorg.relex.one2manybi;
    import java.util.ArrayList;
    import java.util.List;
    import javax.persistence.*;
    /**
     * This class provides an example of the one/parent side of a one-to-many, bi-directional relationship
     * that will be realized through a foreign key from the many/child side of the relationship. Being the 
     * one side of the one-to-many relationship, this class must implement the inverse side.
     */
    @Entity
    @Table(name="RELATIONEX_BORROWER")
    public class Borrower {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(
    //          mappedBy="borrower"
    //          , cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REMOVE}
    //          , orphanRemoval=true
    //          , fetch=FetchType.EAGER
                )
        private List<Loan> loans;
        
        @Column(length=12)
        private String name;
        public int getId() { return id; }
        public List<Loan> getLoans() {
            if (loans == null) {
                loans = new ArrayList<Loan>();
            }
            return loans;
        }
        public void setLoans(List<Loan> loans) {
            this.loans = loans;
        }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  2. Put the following class in your src/main tree. This class provides an example of the many/child side of a many-to-one, bi-directional relationship. Thus, this class will define the mapping to the database and does so using a simple foreign key.

    
    
    package myorg.relex.one2manybi;
    import java.util.Date;
    import javax.persistence.*;
    /**
     * This class provides an example of the many/child side of a many-to-one, bi-directional relationship.
     * Being the many side of the many-to-one relationship, this class must implementing the owning side.
     */
    @Entity
    @Table(name="RELATIONEX_LOAN")
    public class Loan {
        @Id @GeneratedValue
        private int id;
        
        @ManyToOne(fetch=FetchType.EAGER, optional=false)
    //    @JoinColumn(name="BORROWER_ID")
        private Borrower borrower;
        
        @Temporal(TemporalType.DATE)
        @Column(nullable=false)
        private Date checkout;
        @Temporal(TemporalType.DATE)
        private Date checkin;
        
        public Loan() {}
        public Loan(Borrower borrower) {
            this.borrower=borrower;
            this.checkout=new Date();
        }
        
        public int getId() { return id; }
        public boolean isOut() { return checkin==null; }
        
        public Borrower getBorrower() { return borrower; }
        public void setBorrower(Borrower borrower) {
            this.borrower = borrower;
        }
        
        public Date getCheckout() { return checkout; }
        public void setCheckout(Date checkout) {
            this.checkout = checkout;
        }
        
        public Date getCheckin() { return checkin; }
        public void setCheckin(Date checkin) {
            this.checkin = checkin;
        }
    }
  3. Add the new entity classes to the persistence unit.

    
    
            <class>myorg.relex.one2manybi.Borrower</class>
            <class>myorg.relex.one2manybi.Loan</class>
  4. Generate schema for the module. Note the dual one-way relationships defined rather than a single bi-directional one. The foreign key from the child entity table to the parent entity table is correct. However, the link table from the parent entity table is not correct. This was added because of the lack of the the "mappedBy" attribute earlier.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
       create table RELATIONEX_BORROWER (
            id integer generated by default as identity,
            name varchar(12),
            primary key (id)
        );
    
        create table RELATIONEX_BORROWER_RELATIONEX_LOAN ( <!== WRONG!!!!
            RELATIONEX_BORROWER_id integer not null,
            loans_id integer not null,
            unique (loans_id)
        );
    ...
        create table RELATIONEX_LOAN (
            id integer generated by default as identity,
            checkin date,
            checkout date not null,
            borrower_id integer not null,                  <!== CORRECT
            primary key (id)
        );
    ...
        alter table RELATIONEX_BORROWER_RELATIONEX_LOAN    <!== WRONG!!!! 
            add constraint FKC555B9339909D56E 
            foreign key (RELATIONEX_BORROWER_id) 
            references RELATIONEX_BORROWER;
    
        alter table RELATIONEX_BORROWER_RELATIONEX_LOAN    <!== WRONG!!!! 
            add constraint FKC555B933458DDBCB 
            foreign key (loans_id) 
            references RELATIONEX_LOAN;
        alter table RELATIONEX_LOAN                        <!== CORRECT 
            add constraint FK355D0780BC290DFE 
            foreign key (borrower_id) 
            references RELATIONEX_BORROWER;
    
  5. Correct the mapping by adding "mappedBy" to the one/parent side of the relation.

    
    
    public class Borrower {
    ...
        @OneToMany(
                mappedBy="borrower"
                )
        private List<Loan> loans;

    Also make the foreign key mapping from the many/child side to the one/parent side more obvious by adding a @JoinColumn declaration.

    
    
    public class Loan {
    ...
        @ManyToOne(fetch=FetchType.EAGER, optional=false)
        @JoinColumn(name="BORROWER_ID")
        private Borrower borrower;
  6. Regenerate schema for the module. Notice how we now only have the single foreign key to the parent entity table in the child entity table.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_BORROWER (
            id integer generated by default as identity,
            name varchar(12),
            primary key (id)
        );
    ...
        create table RELATIONEX_LOAN (
            id integer generated by default as identity,
            checkin date,
            checkout date not null,
            BORROWER_ID integer not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_LOAN 
            add constraint FK355D0780BC290DFE 
            foreign key (BORROWER_ID) 
            references RELATIONEX_BORROWER;
    
  7. Add the following test method to your JUnit test case. The initial version simply persists the object tree with a parent and single child. Notice how the parent is set on the child (the owning side) and the child is set on the parent (the inverse side).

    
    
        @Test
        public void testOneToManyBiFK() {
            log.info("*** testOneToManyBiFK ***");
            
            log.debug("persisting borrower");
            Borrower borrower = new Borrower();
            borrower.setName("fred");
            em.persist(borrower);
            em.flush();
            
            log.debug("persisting loan");
            Loan loan = new Loan(borrower);
            borrower.getLoans().add(loan);
            em.persist(borrower); //cascade.PERSIST
            em.flush();

    Notice how we are attempting to persist the child -- by associating it with the parent and then calling em.persist() again on the parent. This is legal. Calling persist on an already managed entity causes nothing to happen to the already managed entity but it will execute all cascades.

  8. If you build the module and run the test method you will notice a problem. The child is never saved to the database. We will fix shortly.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -*** testOneToManyBiFK ***
     -persisting borrower
    Hibernate: 
        insert 
        into
            RELATIONEX_BORROWER
            (id, name) 
        values
            (null, ?)
     -persisting loan
    ...
    [INFO] BUILD SUCCESS
    
  9. Add the following lines to your test method to help detect the error with the persist above.

    
    
            log.debug("getting new instances from parent side");
            em.detach(borrower);
            Borrower borrower2 = em.find(Borrower.class, borrower.getId());
            log.debug("checking parent");
            assertNotNull("borrower not found", borrower2);
            log.debug("checking parent collection");
            assertEquals("no loans found", 1, borrower2.getLoans().size());
            log.debug("checking child");
            assertEquals("unexpected child id", loan.getId(), borrower2.getLoans().get(0).getId());
  10. Rebuild the module and re-run the test method. Notice in this output the provider first retrieves the parent during the find and then LAZY loads the child. The test fails because no child was found.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -getting new instances from parent side
    Hibernate: 
        select
            borrower0_.id as id36_0_,
            borrower0_.name as name36_0_ 
        from
            RELATIONEX_BORROWER borrower0_ 
        where
            borrower0_.id=?
     -checking parent
     -checking parent collection
    Hibernate: 
        select
            loans0_.BORROWER_ID as BORROWER4_36_1_,
            loans0_.id as id1_,
            loans0_.id as id37_0_,
            loans0_.BORROWER_ID as BORROWER4_37_0_,
            loans0_.checkin as checkin37_0_,
            loans0_.checkout as checkout37_0_ 
        from
            RELATIONEX_LOAN loans0_ 
        where
            loans0_.BORROWER_ID=?
    ...
    Failed tests:   testOneToManyBiFK(myorg.relex.One2ManyBiTest): no loans found expected:<1> but was:<0>
    ...
    [INFO] BUILD FAILURE
    
  11. Fix the persist issue above by adding cascade=PERSIST from the parent to the child. Add cascade.DETACH to cover the detach() call from the parent in the test method and cascade.DELETE in case we wish to delete the object tree from the parent.

    
    
    public class Borrower {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(
                mappedBy="borrower"
                , cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REMOVE}
                )
        private List<Loan> loans;
  12. Rebuild the module and re-run the test method. Notice how setting the cascade=PERSIST causes the second call of persist() on the parent entity to have the child persisted to the database.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -persisting borrower
    Hibernate: 
        insert 
        into
            RELATIONEX_BORROWER
            (id, name) 
        values
            (null, ?)
     -persisting loan
    Hibernate: 
        insert 
        into
            RELATIONEX_LOAN
            (id, BORROWER_ID, checkin, checkout) 
        values
            (null, ?, ?, ?)
    

    The parent is still LAZY loaded and attempts to load the child will not occur until the child collection is accessed. This, obviously, is efficient for when the children are not commonly accessed.

     -getting new instances from parent side
    Hibernate: 
        select
            borrower0_.id as id36_0_,
            borrower0_.name as name36_0_ 
        from
            RELATIONEX_BORROWER borrower0_ 
        where
            borrower0_.id=?
     -checking parent
    

    Once the test method accesses the child collection, the provider must query the database to obtain the children in the collection.

     -checking parent collection
    Hibernate: 
        select
            loans0_.BORROWER_ID as BORROWER4_36_1_,
            loans0_.id as id1_,
            loans0_.id as id37_0_,
            loans0_.BORROWER_ID as BORROWER4_37_0_,
            loans0_.checkin as checkin37_0_,
            loans0_.checkout as checkout37_0_ 
        from
            RELATIONEX_LOAN loans0_ 
        where
            loans0_.BORROWER_ID=?
     -checking child
    ...
    [INFO] BUILD SUCCESS
    
  13. Change the fetch mode of the parent to EAGER to see how this impacts our queries.

    
    
    public class Borrower {
    ...
        @OneToMany(
                mappedBy="borrower"
                , cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REMOVE}
                , fetch=FetchType.EAGER
                )
        private List<Loan> loans;
  14. Rebuild the module and re-run the test method. Notice how the two queries have been replaced with a single query (with a join) for both the parent and child tables. This obviously is more efficient if *all* children are always accessed as a part of accessing the parent.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -getting new instances from parent side
    Hibernate: 
        select
            borrower0_.id as id36_1_,
            borrower0_.name as name36_1_,
            loans1_.BORROWER_ID as BORROWER4_36_3_,
            loans1_.id as id3_,
            loans1_.id as id37_0_,
            loans1_.BORROWER_ID as BORROWER4_37_0_,
            loans1_.checkin as checkin37_0_,
            loans1_.checkout as checkout37_0_ 
        from
            RELATIONEX_BORROWER borrower0_ 
        left outer join
            RELATIONEX_LOAN loans1_ 
                on borrower0_.id=loans1_.BORROWER_ID 
        where
            borrower0_.id=?
     -checking parent
     -checking parent collection
     -checking child
    ...
    [INFO] BUILD SUCCESS
    
  15. Add the following lines to your test method to add an additional child to the collection. Notice how both sides of the relation are being set by the application. The provider only insists the owning/many side be set, but consistency within the application requires the inverse to be set as well. Both the inverse and owning side are initialized by the provider -- as demonstrated by the previous block of asserts.

    
    
            log.debug("adding new child");
            Loan loanB = new Loan(borrower2);
            borrower2.getLoans().add(loanB);
            em.persist(borrower2);
            em.flush();
  16. Rebuild the module and re-run the test method. Notice how a persist of the managed parent with one managed child and one un-managed child causes only the un-managed child to be persisted to the database (because we have cascade=PERSIST set on the parent)

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -adding new child
    Hibernate: 
        insert 
        into
            RELATIONEX_LOAN
            (id, BORROWER_ID, checkin, checkout) 
        values
            (null, ?, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  17. Add the following lines to your test method. They demonstrate how, because of the bi-directional relationship, we can access the object graph from the child side as well as the parent.

    
    
            log.debug("getting new instances from child side");
            em.detach(borrower2);
            Loan loan2 = em.find(Loan.class, loan.getId());
            log.debug("checking child");
            assertNotNull("child not found", loan2);
            assertNotNull("parent not found", loan2.getBorrower());
            log.debug("checking parent");
            assertEquals("unexpected number of children", 2, loan2.getBorrower().getLoans().size());
  18. Rebuild the module and re-run the test method. Notice how the first child, parent, and all its children were queried for during the first find() and prior to any accesses to the object tree. This is because of EAGER fetches defined on both sides.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -getting new instances from child side
    Hibernate: 
        select
            loan0_.id as id37_1_,
            loan0_.BORROWER_ID as BORROWER4_37_1_,
            loan0_.checkin as checkin37_1_,
            loan0_.checkout as checkout37_1_,
            borrower1_.id as id36_0_,
            borrower1_.name as name36_0_ 
        from
            RELATIONEX_LOAN loan0_ 
        inner join
            RELATIONEX_BORROWER borrower1_ 
                on loan0_.BORROWER_ID=borrower1_.id 
        where
            loan0_.id=?
    Hibernate: 
        select
            loans0_.BORROWER_ID as BORROWER4_36_1_,
            loans0_.id as id1_,
            loans0_.id as id37_0_,
            loans0_.BORROWER_ID as BORROWER4_37_0_,
            loans0_.checkin as checkin37_0_,
            loans0_.checkout as checkout37_0_ 
        from
            RELATIONEX_LOAN loans0_ 
        where
            loans0_.BORROWER_ID=?
     -checking child
     -checking parent
    ...
    [INFO] BUILD SUCCESS
    
  19. Change the fetch to LAZY on the child.

    
    
    public class Loan {
    ...
        @ManyToOne(fetch=FetchType.LAZY, optional=false)
        @JoinColumn(name="BORROWER_ID")
        private Borrower borrower;
  20. Rebuild the module and re-run the test method. Notice how only the initial child is loaded for during the find() and then the parent is loaded (with children) once accessed.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -getting new instances from child side
    Hibernate: 
        select
            loan0_.id as id37_0_,
            loan0_.BORROWER_ID as BORROWER4_37_0_,
            loan0_.checkin as checkin37_0_,
            loan0_.checkout as checkout37_0_ 
        from
            RELATIONEX_LOAN loan0_ 
        where
            loan0_.id=?
     -checking child
     -checking parent
    Hibernate: 
        select
            borrower0_.id as id36_1_,
            borrower0_.name as name36_1_,
            loans1_.BORROWER_ID as BORROWER4_36_3_,
            loans1_.id as id3_,
            loans1_.id as id37_0_,
            loans1_.BORROWER_ID as BORROWER4_37_0_,
            loans1_.checkin as checkin37_0_,
            loans1_.checkout as checkout37_0_ 
        from
            RELATIONEX_BORROWER borrower0_ 
        left outer join
            RELATIONEX_LOAN loans1_ 
                on borrower0_.id=loans1_.BORROWER_ID 
        where
            borrower0_.id=?
    ...
    [INFO] BUILD SUCCESS
    

    Feel free to experiment with a few more combinations of LAZY and EAGER to make sure you understand the implications of choosing one over the other.

  21. Add the following lines to your test method. This code orphans one of the children by removing it from the parent collection. We would like to see the orphaned child deleted by the provider, but we have to fix our mapping specification first.

    
    
            log.debug("orphaning one of the children");
            int startCount = em.createQuery("select count(l) from Loan l", Number.class).getSingleResult().intValue();
            Borrower borrower3 = loan2.getBorrower();
            borrower3.getLoans().remove(loan2);
            em.flush();
            assertEquals("orphaned child not deleted", startCount-1,
                    em.createQuery("select count(l) from Loan l", Number.class).getSingleResult().intValue());
  22. Rebuild the module and re-run the test method. Notice how nothing changed in the database and our test failed. The fact the child was removed from the inverse side of the relation meant nothing the way our relationship is currently mapped.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -orphaning one of the children
    Hibernate: 
        select
            count(loan0_.id) as col_0_0_ 
        from
            RELATIONEX_LOAN loan0_ limit ?
    Hibernate: 
        select
            count(loan0_.id) as col_0_0_ 
        from
            RELATIONEX_LOAN loan0_ limit ?
    ...
    [INFO] BUILD FAILURE
    
  23. Enable orphanRemoval on the parent collection.

    
    
    public class Borrower {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(
                mappedBy="borrower"
                , cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REMOVE}
                , orphanRemoval=true
                , fetch=FetchType.EAGER
                )
        private List<Loan> loans;
  24. Rebuild the module and re-run the test method. Notice how the orphaned child is now deleted when removed form the collection.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -orphaning one of the children
    Hibernate: 
        select
            count(loan0_.id) as col_0_0_ 
        from
            RELATIONEX_LOAN loan0_ limit ?
    Hibernate: 
        delete 
        from
            RELATIONEX_LOAN 
        where
            id=?
    Hibernate: 
        select
            count(loan0_.id) as col_0_0_ 
        from
            RELATIONEX_LOAN loan0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    
  25. Add the final lines to the test method. This will attempt to delete the entire object graph by removing just the parent. This will work because we added cascade=DELETE earlier.

    
    
            log.debug("deleting parent");
            em.remove(borrower3);
            em.flush();
            assertEquals("orphaned child not deleted", startCount-2,
                    em.createQuery("select count(l) from Loan l", Number.class).getSingleResult().intValue());
  26. Rebuild the module and re-run the test method. Notice how each child gets deleted from the database by ID and then the parent is removed.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiFK
     ...
     -deleting parent
    Hibernate: 
        delete 
        from
            RELATIONEX_LOAN 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_BORROWER 
        where
            id=?
    Hibernate: 
        select
            count(loan0_.id) as col_0_0_ 
        from
            RELATIONEX_LOAN loan0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    

You have finished going through a one-to-many/many-to-one, bi-directional relationship that is realized through a foreign key column in the child entity table. We also added fetch, cascade, and orphanRemoval features to show some build-in provider functionality that can save some code when working with large object graphs.

In this section we will demonstrate mapping a one-to-many relationship using a join table and a bi-directional relationship. From the database perspective, this will look identical to the one-to-many, uni-directional case. However, from the JPA-perspective the relationship is being owned (i.e, defined) by the child/many side. In the uni-directional case there was no property in the child/many entity class that represented the relationship. Now there is.

  1. Put the following class in your src/main tree. This entity class provides an example of the one/inverse side of a one-to-many, bi-directional relationship mapped using a join-table. Or at least it will be. The current version below has a few errors we need to correct.

    
    
    package myorg.relex.one2manybi;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import javax.persistence.*;
    /**
     * This class provides an example of the one/inverse side of a one-to-many, bi-directional 
     * relationship realized through a join-table mapped from the owning/many side.
     */
    @Entity
    @Table(name="RELATIONEX_PURCHASE")
    public class Purchase {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(
    //          mappedBy="purchase", 
    //          cascade={CascadeType.PERSIST, CascadeType.DETACH},
    //          orphanRemoval=true
            )
        private List<SaleItem> items;
        
        @Temporal(TemporalType.TIMESTAMP)
        @Column(nullable=false, updatable=false)
        private Date date;
        
        protected Purchase() {}
        public Purchase(Date date) {
            this.date = date;
        }
        public int getId() { return id; }
        public Date getDate() { return date; }
        public List<SaleItem> getItems() {
            if (items == null) {
                items = new ArrayList<SaleItem>();
            }
            return items;
        }
        public Purchase addItem(SaleItem item) {
            getItems().add(item);
            return this;
        }
    }
  2. Place the following class in your src/main tree. This class provides an example of the many/owning side of a many-to-one relationship mapped using a join table. It is currently incomplete and we will work to expose the issues and correct in the following steps.

    
    
    package myorg.relex.one2manybi;
    import java.math.BigDecimal;
    import javax.persistence.*;
    /**
     * This class provides and example of the many/owning side of a many-to-one, bi-directional 
     * relationship that is realized using a join-table.
     */
    @Entity
    @Table(name="RELATIONEX_SALEITEM")
    public class SaleItem {
        @Id @GeneratedValue
        private int id;
        
        @ManyToOne//(optional=false, fetch=FetchType.EAGER)
    //  @JoinTable(
    //      name="RELATIONEX_SALEITEM_PURCHASE", 
    //      joinColumns=@JoinColumn(name="SALEITEM_ID"),
    //      inverseJoinColumns=@JoinColumn(name="PURCHASE_ID")
    //  )
        private Purchase purchase;
        
        @Column(length=16)
        private String name;
        @Column(precision=5, scale=2)
        private BigDecimal price;
        
        protected SaleItem() {}
        public SaleItem(Purchase purchase) {
            this.purchase = purchase;
        }
        public int getId() { return id; }
        
        public Purchase getPurchase() { return purchase; }
        public void setPurchase(Purchase purchase) {
            this.purchase = purchase;
        }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        
        public double getPrice() { return price==null? 0 : price.doubleValue(); }
        public void setPrice(double price) {
            this.price = new BigDecimal(price);
        }
    }
  3. Add the new entity classes to the persistence unit.

    
    
            <class>myorg.relex.one2manybi.Purchase</class>
            <class>myorg.relex.one2manybi.SaleItem</class>
  4. Generate schema for the new entity classes and their relationship. Notice how we don't have a bi-directional relationship. We have two uni-directional relationships. The owned relationship by the one side has formed a join-table and the owned relationship from the many side has formed a foreign key relationship.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_PURCHASE (                                                                                                                                                        
            id integer generated by default as identity,
            date timestamp not null,
            primary key (id)
        );
    
        create table RELATIONEX_PURCHASE_RELATIONEX_SALEITEM ( <!== WRONG, missing @OneToMany.mappedBy
            RELATIONEX_PURCHASE_id integer not null,
            items_id integer not null,
            unique (items_id)
        );
    ...
        create table RELATIONEX_SALEITEM (
            id integer generated by default as identity,
            name varchar(16),
            price decimal(5,2),
            purchase_id integer,                               <!== WRONG, missing @JoinTable
            primary key (id)
        );
    ...
       alter table RELATIONEX_PURCHASE_RELATIONEX_SALEITEM 
            add constraint FK8157C4BCB4DABD0E 
            foreign key (RELATIONEX_PURCHASE_id) 
            references RELATIONEX_PURCHASE;
    
        alter table RELATIONEX_PURCHASE_RELATIONEX_SALEITEM 
            add constraint FK8157C4BC3F0D578 
            foreign key (items_id) 
            references RELATIONEX_SALEITEM;
    
        alter table RELATIONEX_SALEITEM 
            add constraint FKAD87326AD7F9F59E 
            foreign key (purchase_id) 
            references RELATIONEX_PURCHASE;
    
    
  5. Correct the bi-directional relationship by adding mappedBy to the @OneToMany mapping in the parent.

    
    
    public class Purchase {
        @OneToMany(
                mappedBy="purchase" 
            )
        private List<SaleItem> items;
  6. Generate schema for the new entity classes and their relationship. Notice how the join table implementing the owned relationship from the parent/one side has been removed. However, what remains is a foreign key join owned by the child/many side.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_PURCHASE (
            id integer generated by default as identity,
            date timestamp not null,
            primary key (id)
        );
    ...
        create table RELATIONEX_SALEITEM (
            id integer generated by default as identity,
            name varchar(16),
            price decimal(5,2),
            purchase_id integer,      <!=== WRONG, missing @JoinTable
            primary key (id)
        );
    ...
        alter table RELATIONEX_SALEITEM 
            add constraint FKAD87326AD7F9F59E 
            foreign key (purchase_id) 
            references RELATIONEX_PURCHASE;
    
  7. Attempt to correct the mapping (remember -- we wanted this example to use a join table), by adding a @JoinTable mapping in the child/many side. We will start by allowing the provider to generate default table names.

    
    
    public class SaleItem {
    ...
        @ManyToOne
        @JoinTable
        private Purchase purchase;
  8. Regenerate schema for the entity classes and their relationship. Notice by the error produced that the link table name must be provided when defined from the child/many side. There is no default for this case.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
    Unable to configure EntityManagerFactory: JoinTable.name() on a @ToOne association has to be explicit: myorg.relex.one2manybi.SaleItem.purchase 
  9. Add a table name for the join table.

    
    
    public class SaleItem {
    ...
        @ManyToOne//(optional=false, fetch=FetchType.EAGER)
        @JoinTable(
            name="RELATIONEX_SALEITEM_PURCHASE" 
        )
        private Purchase purchase;
  10. Regenerate schema for the entity classes and their relationship. Notice how we now have regained our link table (from when it use to be generated from the parent side), specified a name for it, and have default names for foreign keys to the parent and child tables. Notice too that since this is a many-to-one relationship, the reference to the child is a primary key for the link table -- which means the child can only be mapped once by the joint table. The same was true when the child table contained a foreign key column.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_PURCHASE (
            id integer generated by default as identity,
            date timestamp not null,
            primary key (id)
        );
    ...
        create table RELATIONEX_SALEITEM (
            id integer generated by default as identity,
            name varchar(16),
            price decimal(5,2),
            primary key (id)
        );
    
        create table RELATIONEX_SALEITEM_PURCHASE (
            purchase_id integer,       <!=== many references to same parent legacy (many-to-one)
            id integer not null,
            primary key (id)           <!=== reference to child is unique
        );
    ...
        alter table RELATIONEX_SALEITEM_PURCHASE 
            add constraint FKB4CE0B36BDB37099 
            foreign key (id) 
            references RELATIONEX_SALEITEM;
    
        alter table RELATIONEX_SALEITEM_PURCHASE 
            add constraint FKB4CE0B36D7F9F59E 
            foreign key (purchase_id) 
            references RELATIONEX_PURCHASE;
    
  11. Make a few final tweaks to the database mapping. Lets provide explicit names for the foreign key columns within the join table

    
    
    public class SaleItem {
        @Id @GeneratedValue
        private int id;
        
        @ManyToOne(optional=false, fetch=FetchType.EAGER)
        @JoinTable(
            name="RELATIONEX_SALEITEM_PURCHASE", 
            joinColumns=@JoinColumn(name="SALEITEM_ID"),
            inverseJoinColumns=@JoinColumn(name="PURCHASE_ID")
        )
        private Purchase purchase;
  12. Regenerate schema for the entity classes and their relationship. Notice this time that the foreign key column names now have explicitly assigned names and with the @ManyToOne.optional=false the definition of the column back to the parent class became non-null.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl
    ...
        create table RELATIONEX_PURCHASE (
            id integer generated by default as identity,
            date timestamp not null,
            primary key (id)
        );
    ...
        create table RELATIONEX_SALEITEM (
            id integer generated by default as identity,
            name varchar(16),
            price decimal(5,2),
            primary key (id)
        );
    
        create table RELATIONEX_SALEITEM_PURCHASE (
            PURCHASE_ID integer not null,
            SALEITEM_ID integer not null,
            primary key (SALEITEM_ID)
        );
    ...
        alter table RELATIONEX_SALEITEM_PURCHASE 
            add constraint FKB4CE0B36D7F9F59E 
            foreign key (PURCHASE_ID) 
            references RELATIONEX_PURCHASE;
    
        alter table RELATIONEX_SALEITEM_PURCHASE 
            add constraint FKB4CE0B36371BCF1E 
            foreign key (SALEITEM_ID) 
            references RELATIONEX_SALEITEM;
    
  13. Add the following test method to your existing JUnit test case. This method will create instances of the parent and child entities and relate them.

    
    
        @Test
        public void testOneToManyBiJoinTable() {
            log.info("*** testOneToManyBiJoinTable ***");
            
            log.debug("persisting parent");
            Purchase purchase = new Purchase(new Date());
            em.persist(purchase);
            em.flush();
            
            log.debug("persisting child");
            SaleItem item = new SaleItem(purchase);
            item.setPrice(10.02);
            purchase.addItem(item);
            em.persist(purchase); //cascade.PERSIST
            em.flush();
        }
  14. Build the module and run the test method. Notice how only the parent class got persisted. This is because we did not enable any cascades from the parent to the child entity.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
    -*** testOneToManyBiJoinTable ***
     -persisting parent
    Hibernate: 
        insert 
        into
            RELATIONEX_PURCHASE
            (id, date) 
        values
            (null, ?)
     -persisting child
    ...
    [INFO] BUILD SUCCESS
    
  15. Make the error more obvious by adding the following lines to the test method. Among other things, this section of code will check to see if the child entity exists in the database.

    
    
            log.debug("getting new instances");
            em.detach(purchase);
            Purchase purchase2 = em.find(Purchase.class, purchase.getId());
            assertNotNull("parent not found", purchase2);
            log.debug("checking parent");
            assertTrue("unexpected date", purchase.getDate().equals(purchase2.getDate()));
            log.debug("checking child");
            assertEquals("unexpected number of children", 1, purchase2.getItems().size());
            assertEquals("", item.getPrice(), purchase2.getItems().get(0).getPrice(),.01);
            log.debug("verify got new instances");
            assertFalse("same parent instance returned", purchase == purchase2);
            assertFalse("same child instance returned", item == purchase2.getItems().get(0));
  16. Rebuild the module and re-run the test method. Notice the initial find() simply does a LAZY load on the parent table. Once the test method accesses the child collection -- the related child entities are loaded along with the join-table and the parent table. The join table is queried to locate the parent table and the parent table is queried for because of the EAGER fetch specified in the child mapping.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
     ...
     -getting new instances
    Hibernate: 
        select
            purchase0_.id as id38_0_,
            purchase0_.date as date38_0_ 
        from
            RELATIONEX_PURCHASE purchase0_ 
        where
            purchase0_.id=?
     -checking parent
     -checking child
    Hibernate: 
        select
            items0_.PURCHASE_ID as PURCHASE1_38_2_,
            items0_.SALEITEM_ID as SALEITEM2_2_,
            saleitem1_.id as id39_0_,
            saleitem1_.name as name39_0_,
            saleitem1_.price as price39_0_,
            saleitem1_1_.PURCHASE_ID as PURCHASE1_40_0_,
            purchase2_.id as id38_1_,
            purchase2_.date as date38_1_ 
        from
            RELATIONEX_SALEITEM_PURCHASE items0_ 
        inner join
            RELATIONEX_SALEITEM saleitem1_ 
                on items0_.SALEITEM_ID=saleitem1_.id 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem1_1_ 
                on saleitem1_.id=saleitem1_1_.SALEITEM_ID 
        inner join
            RELATIONEX_PURCHASE purchase2_ 
                on saleitem1_1_.PURCHASE_ID=purchase2_.id 
        where
            items0_.PURCHASE_ID=?
    ...
    [INFO] BUILD FAILURE    <!== We expected this -- caused by no cascade=PERSIST
    
  17. Correct the cascade specification by allowing entity manager persist() commands to cascade to related children.

    
    
    public class Purchase {
        @OneToMany(
                mappedBy="purchase", 
                cascade={CascadeType.PERSIST}
            )
        private List<SaleItem> items;
  18. Rebuild the module and re-run the test method. Notice how we now persist the child and a row in the join table to form the relationship back to the parent.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -*** testOneToManyBiJoinTable ***
     -persisting parent
    Hibernate: 
        insert 
        into
            RELATIONEX_PURCHASE
            (id, date) 
        values
            (null, ?)
     -persisting child
    Hibernate: 
        insert 
        into
            RELATIONEX_SALEITEM
            (id, name, price) 
        values
            (null, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_SALEITEM_PURCHASE
            (PURCHASE_ID, SALEITEM_ID) 
        values
            (?, ?)
    

    The next block of code was able to locate the parent, relationship, and child entities. This is the same query as before except this one returned a child entity.

     -getting new instances
    Hibernate: 
        select
            purchase0_.id as id38_0_,
            purchase0_.date as date38_0_ 
        from
            RELATIONEX_PURCHASE purchase0_ 
        where
            purchase0_.id=?
     -checking parent
     -checking child
    Hibernate: 
        select
            items0_.PURCHASE_ID as PURCHASE1_38_2_,
            items0_.SALEITEM_ID as SALEITEM2_2_,
            saleitem1_.id as id39_0_,
            saleitem1_.name as name39_0_,
            saleitem1_.price as price39_0_,
            saleitem1_1_.PURCHASE_ID as PURCHASE1_40_0_,
            purchase2_.id as id38_1_,
            purchase2_.date as date38_1_ 
        from
            RELATIONEX_SALEITEM_PURCHASE items0_ 
        inner join
            RELATIONEX_SALEITEM saleitem1_ 
                on items0_.SALEITEM_ID=saleitem1_.id 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem1_1_ 
                on saleitem1_.id=saleitem1_1_.SALEITEM_ID 
        inner join
            RELATIONEX_PURCHASE purchase2_ 
                on saleitem1_1_.PURCHASE_ID=purchase2_.id 
        where
            items0_.PURCHASE_ID=?
    

    The test fails, however, because we received the same instance of the child that was related to the original parent. This is because our detach() call was not cascaded to the child.

     -verify got new instances
    ...
    Failed tests:   testOneToManyBiJoinTable(myorg.relex.One2ManyBiTest): same child instance returned
    ...
    [INFO] BUILD FAILURE
    
  19. Add cascade=DETACH to the parent side. This will cause any detach() call on the parent to also detach() the child entitities.

    public class Purchase {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(
                mappedBy="purchase", 
                cascade={CascadeType.PERSIST, CascadeType.DETACH}
            )
        private List<SaleItem> items;
    
  20. Rebuild the module and re-run the test method. Notice we now get a new instance for both the parent and child because of the call of detach on the parent and the cascade of the call to the child.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
     ...
     -verify got new instances
    ...
    [INFO] BUILD SUCCESS
    
  21. Add the following lines to your test method. This will add a new child entity to the parent.

    
    
            log.debug("adding new child");
            SaleItem itemB = new SaleItem(purchase2);
            purchase2.addItem(itemB);
            em.persist(purchase2);
            em.flush();
  22. Rebuild the module and re-run the test method. Notice this looks much like the first child that was persisted. A row in the child table is added -- followed by a row in the join table.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -adding new child
    Hibernate: 
        insert 
        into
            RELATIONEX_SALEITEM
            (id, name, price) 
        values
            (null, ?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_SALEITEM_PURCHASE
            (PURCHASE_ID, SALEITEM_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  23. Add the following lines to the test method. This will obtain a access to the object graph based on a reference from the child.

    
    
            log.debug("getting new instances from child side");
            em.detach(purchase2);
            SaleItem item2 = em.find(SaleItem.class, item.getId());
            log.debug("checking child");
            assertNotNull("child not found", item2);
            assertNotNull("parent not found", item2.getPurchase());
            log.debug("checking parent");
            assertEquals("unexpected number of children", 2, item2.getPurchase().getItems().size());
  24. Rebuild the module and re-run the test method. Notice how the find() is implementing an EAGER fetch of the relation and parent in addition the the state of the child.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -getting new instances from child side
    Hibernate: 
        select
            saleitem0_.id as id39_1_,
            saleitem0_.name as name39_1_,
            saleitem0_.price as price39_1_,
            saleitem0_1_.PURCHASE_ID as PURCHASE1_40_1_,
            purchase1_.id as id38_0_,
            purchase1_.date as date38_0_ 
        from                                 <!==== query for child
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join                      <!==== EAGER fetch of relation
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID 
        inner join                           <!==== EAGER fetch of parent
            RELATIONEX_PURCHASE purchase1_ 
                on saleitem0_1_.PURCHASE_ID=purchase1_.id 
        where
            saleitem0_.id=?
    

    However -- even though the first child, relation, and parent of that child was eagerly fetched, the remaing children for the parent must be fetched once we inspect the state of the parent.

     -checking child
     -checking parent
    Hibernate: 
        select
            items0_.PURCHASE_ID as PURCHASE1_38_2_,
            items0_.SALEITEM_ID as SALEITEM2_2_,
            saleitem1_.id as id39_0_,
            saleitem1_.name as name39_0_,
            saleitem1_.price as price39_0_,
            saleitem1_1_.PURCHASE_ID as PURCHASE1_40_0_,
            purchase2_.id as id38_1_,
            purchase2_.date as date38_1_ 
        from
            RELATIONEX_SALEITEM_PURCHASE items0_ 
        inner join
            RELATIONEX_SALEITEM saleitem1_ 
                on items0_.SALEITEM_ID=saleitem1_.id 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem1_1_ 
                on saleitem1_.id=saleitem1_1_.SALEITEM_ID 
        inner join
            RELATIONEX_PURCHASE purchase2_ 
                on saleitem1_1_.PURCHASE_ID=purchase2_.id 
        where
            items0_.PURCHASE_ID=?
    ...
    [INFO] BUILD SUCCESS
    
  25. Change the mapping from EAGER to LAZY from the child.

    
    
    public class SaleItem {
    ...
        @ManyToOne(optional=false, fetch=FetchType.LAZY)
        @JoinTable(
            name="RELATIONEX_SALEITEM_PURCHASE", 
            joinColumns=@JoinColumn(name="SALEITEM_ID"),
            inverseJoinColumns=@JoinColumn(name="PURCHASE_ID")
        )
        private Purchase purchase;
  26. Rebuild the module and re-run the test method. Notice in this case the parent is not part of the initial query caused by the find().

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -getting new instances from child side
    Hibernate: 
        select
            saleitem0_.id as id39_0_,
            saleitem0_.name as name39_0_,
            saleitem0_.price as price39_0_,
            saleitem0_1_.PURCHASE_ID as PURCHASE1_40_0_ 
        from
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID 
        where
            saleitem0_.id=?
     

    But notice how the LAZY fatch from the child seemed to change the behavior of the parent. It did an initial LAZY fetch and then followed up with a query for state for the children.

     -checking child
     -checking parent
    Hibernate: 
        select
            purchase0_.id as id38_0_,
            purchase0_.date as date38_0_ 
        from
            RELATIONEX_PURCHASE purchase0_ 
        where
            purchase0_.id=?
    Hibernate: 
        select
            items0_.PURCHASE_ID as PURCHASE1_38_1_,
            items0_.SALEITEM_ID as SALEITEM2_1_,
            saleitem1_.id as id39_0_,
            saleitem1_.name as name39_0_,
            saleitem1_.price as price39_0_,
            saleitem1_1_.PURCHASE_ID as PURCHASE1_40_0_ 
        from
            RELATIONEX_SALEITEM_PURCHASE items0_ 
        inner join
            RELATIONEX_SALEITEM saleitem1_ 
                on items0_.SALEITEM_ID=saleitem1_.id 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem1_1_ 
                on saleitem1_.id=saleitem1_1_.SALEITEM_ID 
        where
            items0_.PURCHASE_ID=?
    ...
    [INFO] BUILD SUCCESS
    
  27. Add the following lines to your test method. This will provide a test of orphan processing where we look the container to delete the child when the child is no longer referenced by the parent.

    
    
            log.debug("orphaning one of the children");
            int startCount = em.createQuery("select count(s) from SaleItem s", Number.class).getSingleResult().intValue();
            Purchase purchase3 = item2.getPurchase();
            purchase3.getItems().remove(item2);
            em.flush();
            assertEquals("orphaned child not deleted", startCount-1,
                    em.createQuery("select count(s) from SaleItem s", Number.class).getSingleResult().intValue());
  28. Rebuild the module and re-run the test method. Notice that only the count(*) selects show up in the SQL when the commands execute and the test fails because the orphaned child is not removed. There is a reason for this -- and we will fix.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -orphaning one of the children
    Hibernate: 
        select
            count(saleitem0_.id) as col_0_0_ 
        from
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID limit ?
    Hibernate: 
        select
            count(saleitem0_.id) as col_0_0_ 
        from
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID limit ?
    ...
    Failed tests:   testOneToManyBiJoinTable(myorg.relex.One2ManyBiTest): orphaned child not deleted expected:<1> but was:<2>
    ...
    [INFO] BUILD FAILURE
    

    Hold on here!?!? We admit that we didn't tell the provider to remove the orphan child, but didn't the code remove the relationship? NO! it did not. The child was removed from the parent collection, but that is the inverse side. With the way we currently have it mapped the relationship can only be removed by actions on the child and the only way to do that with a required (optional=false) parent is to manually remove the child or set orphanRemoval as we will do next.

  29. Fix the mapping by enabling orphanRemoval from the parent to the child.

    
    
    public class Purchase {
    ...
        @OneToMany(
                mappedBy="purchase", 
                cascade={CascadeType.PERSIST, CascadeType.DETACH},
                orphanRemoval=true)
        private List<SaleItem> items;
  30. Rebuild the module and re-run the test method. Notice how the child is now removed from the database when it is removed from the parent (and the transaction is commited/flushed). Notice also the row out of the relationship table is removed as well when the child is removed.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -orphaning one of the children
    Hibernate: 
        select
            count(saleitem0_.id) as col_0_0_ 
        from
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID limit ?
    Hibernate: 
        delete 
        from
            RELATIONEX_SALEITEM_PURCHASE 
        where
            SALEITEM_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_SALEITEM 
        where
            id=?
    Hibernate: 
        select
            count(saleitem0_.id) as col_0_0_ 
        from
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID limit ?
    ...
    [INFO] BUILD SUCCESS
    
  31. Add the following lines to the test method. These will remove the parent and test to see if removing the parent also removed the remaining child.

    
    
            log.debug("deleting parent");
            em.remove(purchase3);
            em.flush();
            assertEquals("orphaned child not deleted", startCount-2,
                    em.createQuery("select count(s) from SaleItem s", Number.class).getSingleResult().intValue());
  32. Rebuild the module and re-run the test method. Notice how the child and the relation were deleted even though there was not a cascade=DELETE on the parent to child relationship. That is because cascade=DELETE is not necessary with orphanDelete. They serve the same purpose when the parent is being deleted.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiJoinTable
    ...
     -deleting parent
    Hibernate: 
        delete 
        from
            RELATIONEX_SALEITEM_PURCHASE 
        where
            SALEITEM_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_SALEITEM 
        where
            id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_PURCHASE 
        where
            id=?
    Hibernate: 
        select
            count(saleitem0_.id) as col_0_0_ 
        from
            RELATIONEX_SALEITEM saleitem0_ 
        left outer join
            RELATIONEX_SALEITEM_PURCHASE saleitem0_1_ 
                on saleitem0_.id=saleitem0_1_.SALEITEM_ID limit ?
    ...
    [INFO] BUILD SUCCESS
    

You have finished looking at one-to-many/many-to-one, bi-directional relationships mapped using a join table. This was functionally no different at the Java class level than the foreign key case and very similar to the one-to-many, uni-directional join table case. However, this mapping leveraged a relationship from the child that formed the mapping to the database and could be used to easily access the parent.

In this section we will demonstrate a one-to-many, bi-directional relationship where the primary key of the owning/dependent entity is derived from the one side.

  1. Place the following class in your src/main tree. It provides an example of the one/parent/inverse side of a one-to-many, bi-directional relationship. We are going to skip making any errors with the entity and move straight to a reasonable solution. The key aspects to remember about one-to-many, bi-directional relationships are

    
    
    package myorg.relex.one2manybi;
    import java.util.Date;
    import java.util.HashSet;
    import java.util.Set;
    import javax.persistence.*;
    /**
     * This class is an example of the one/inverse side of a one-to-many, bi-directional
     * relationship mapped using a compound foreign key that is partially derived from the 
     * parent primary key.
     */
    @Entity
    @Table(name="RELATIONEX_CAR")
    public class Car {
        @Id @GeneratedValue
        private int id;
        
        @OneToMany(
                mappedBy="car",
                cascade={CascadeType.PERSIST, CascadeType.DETACH}, 
                orphanRemoval=true,
                fetch=FetchType.LAZY)
        private Set<Tire> tires;
        
        @Column(length=16)
        private String model;
        @Temporal(TemporalType.DATE)
        private Date year;
        public int getId() { return id; }
        public Set<Tire> getTires() {
            if (tires==null) {
                tires=new HashSet<Tire>();
            }
            return tires;
        }
        public String getModel() { return model; }
        public void setModel(String model) {
            this.model = model;
        }
        
        public Date getYear() { return year; }
        public void setYear(Date year) {
            this.year = year;
        }
        
        @Override
        public int hashCode() {
            return (model==null?0:model.hashCode()) + (year==null?0:year.hashCode());
        }
        @Override
        public boolean equals(Object obj) {
            try {
                if (this==obj) { return true; }
                Car rhs = (Car)obj;
                return id==0 ? super.equals(obj) : id==rhs.id;
            } catch (Exception ex) { return true; }
        }
    }
  2. Put the following Enum in place in your src/main tree. This will be used by the example to help define the primary key of the child entity.

    
    
    package myorg.relex.one2manybi;
    public enum TirePosition {
        LEFT_FRONT,
        RIGHT_FRONT,
        LEFT_REAR,
        RIGHT_REAR
    }
  3. Put the following class in your src/main tree. This provides an example of the many/child/owning side of a many-to-one, bi-directional relationship that is mapped using a foreign that is used to partially derive the child's compound primary key. The child, in this case, uses an @IdClass to model the compound primary key. That means the primary key values will be exposed in the entity class as regular @Id values. Note, however, the foreign key is mapped as a relationship and not an ID Java value. We model the relationship in the entity class. We will model the foreign key value in the @IdClass -- but the names must match.

    
    
    package myorg.relex.one2manybi;
    import javax.persistence.*;
    /**
     * This class provides an example of the many/owning side of a many-to-one, bi-directional 
     * relationship mapped using a foreign key and that foreign key is used to derive the 
     * primary key of this class.
     */
    @Entity
    @Table(name="RELATIONEX_TIRE")
    @IdClass(TirePK.class)
    public class Tire {
        @Id
        @ManyToOne
        @JoinColumn(name="CAR_ID", nullable=false)
        private Car car;
        
        @Id @Enumerated(EnumType.STRING)
        @Column(length=16)
        private TirePosition position;
        
        private int miles;
        
        protected Tire() {}
        public Tire(Car car, TirePosition position) {
            this.car = car;
            this.position = position;
        }
        public TirePosition getPosition() { return position; }
        public Car getCar() { return car; }
        
        public int getMiles() { return miles; }
        public void setMiles(int miles) {
            this.miles = miles;
        }
        
        @Override
        public int hashCode() {
            return position.hashCode();
        }
        @Override
        public boolean equals(Object obj) {
            try {
                if (this==obj) { return true; }
                Tire rhs = (Tire)obj;
                return car.equals(rhs.car) && position==rhs.position; 
            } catch (Exception ex) { return false; }
        }
    }
  4. Put the following class in place. This class represents an primary key class that will be used as an @IdClass. That means

    
    
    package myorg.relex.one2manybi;
    import java.io.Serializable;
    /**
     * This class provides an example of an IdClass used by a child entity in a 
     * many-to-one, bi-directional relationship where half of its primary key is
     * derived form the parentId;
     */
    public class TirePK implements Serializable {
        private static final long serialVersionUID = -6028270454708159105L;
        private int car;   //shared primary key value from parent and child, name matches child rel
        private TirePosition position; //child primary key value unique within parent
        
        protected TirePK() {}
        public TirePK(int carId, TirePosition position) {
            this.car=carId;
            this.position=position;
        }
        
        public int getAutoId() { return car; }
        public TirePosition getPosition() { return position; }
        
        @Override
        public int hashCode() {
            return car + (position==null?0:position.hashCode());
        }
        
        @Override
        public boolean equals(Object obj) {
            try {
                if (this==obj) { return true; }
                TirePK rhs = (TirePK)obj;
                return car==rhs.car && position==rhs.position;
            } catch (Exception ex) { return false; }
        }   
    }
  5. Add the entity classes to the persistence unit. Do not list the enum or primary key class here.

    
    
            <class>myorg.relex.one2manybi.Car</class>
            <class>myorg.relex.one2manybi.Tire</class>
  6. Generate database schema for the entity classes and their relationship. Notice the foreign key is in the child entity table and is also being used as the primary key for the child entity table.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_CAR (
            id integer generated by default as identity,
            model varchar(16),
            year date,
            primary key (id)
        );
    ...
        create table RELATIONEX_TIRE (
            CAR_ID integer not null,
            position varchar(16) not null,
            miles integer not null,
            primary key (CAR_ID, position) <!== Foreign key is also part of primary key
        );
    ...
        alter table RELATIONEX_TIRE 
            add constraint FK356095F89CA49F36 
            foreign key (CAR_ID) 
            references RELATIONEX_CAR;
    
  7. Add the following test method to your JUnit test case. This test method is similar to the previous sections. It creates an instance of the parent and child and relates the two.

    
    
        @Test
        public void testOneToManyBiDerivedClass() {
            log.info("*** testOneToManyBiDerivedClass ***");
            
            log.debug("persisting parent");
            Car car = new Car();
            car.setModel("DeLorean");
            car.setYear(new GregorianCalendar(1983, 0, 0).getTime());
            em.persist(car);
            em.flush();
            
            log.debug("persisting child");
            Tire tire = new Tire(car, TirePosition.RIGHT_FRONT);
            tire.setMiles(2000);
            car.getTires().add(tire);
            em.persist(car); //cascade.PERSIST
            em.flush();
        }
  8. Build the module and run the test method. Notice that when the child is created -- the values for the parentId (CAR_ID) and other primary key value (position) are stored with the child. The parentId (CAR_ID) is serving as the foreign key and part of the primary key.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiDerivedIdClass
    ...
     -creating entity manager
     -*** testOneToManyBiDerivedClass ***
     -persisting parent
    Hibernate: 
        insert 
        into
            RELATIONEX_CAR
            (id, model, year) 
        values
            (null, ?, ?)
     -persisting child
    Hibernate: 
        insert 
        into
            RELATIONEX_TIRE
            (miles, CAR_ID, position) 
        values
            (?, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    

    Both the parent and child were successfully inserted into the database during repeated calls to persist() and passing the parent because we enabled cascade=PERSIST in the parent relationship mapping.

  9. Add the following lines to your test method. This section will verify the parent and child exist and can be used to demonstrate the impact of a LAZY or EAGER fetch.

    
    
            log.debug("getting new instances");
            em.detach(car);
            Car car2 = em.find(Car.class, car.getId());
            assertNotNull("parent not found", car2);
            log.debug("checking parent");
            assertTrue("unexpected date", car.getYear().equals(car2.getYear()));
            log.debug("checking child");
            assertEquals("unexpected number of children", 1, car2.getTires().size());
            assertEquals("unexpected child state", tire.getMiles(), car2.getTires().iterator().next().getMiles());
            log.debug("verify got new instances");
            assertFalse("same parent instance returned", car == car2);
            assertFalse("same child instance returned", tire == car2.getTires().iterator().next());
  10. Rebuild the module and re-run the test method. Notice the parent can be located by primary key through the find() and a LAZY fetch is performed when navigating to the child. Notice when the child is accessed -- the query is issued for members of the child table that match the foreign key and not each child individually.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiDerivedIdClass
    ...
    -getting new instances
    Hibernate: 
        select
            car0_.id as id41_0_,
            car0_.model as model41_0_,
            car0_.year as year41_0_ 
        from
            RELATIONEX_CAR car0_ 
        where
            car0_.id=?
     -checking parent
     -checking child
    Hibernate: 
        select
            tires0_.CAR_ID as CAR1_41_1_,
            tires0_.CAR_ID as CAR1_1_,
            tires0_.position as position1_,
            tires0_.CAR_ID as CAR1_42_0_,
            tires0_.position as position42_0_,
            tires0_.miles as miles42_0_ 
        from
            RELATIONEX_TIRE tires0_ 
        where
            tires0_.CAR_ID=?
     -verify got new instances
    ...
    [INFO] BUILD SUCCESS
    
  11. Add the following lines to your test method to add a second child to the relationship.

    
    
            log.debug("adding new child");
            Tire tireB = new Tire(car2, TirePosition.LEFT_FRONT);
            car2.getTires().add(tireB);
            em.persist(car2);
            em.flush();
  12. Rebuild the module and re-run the test method. Notice the insert of the child and the creation of the relationship was done by a single insert into the child table (with the foreign key assigned). The child is persisted during the call to persist() on the already managed parent because of the cascade=PERSIST defined on the parent relationship mapping.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiDerivedIdClass
    ...
     -adding new child
    Hibernate: 
        insert 
        into
            RELATIONEX_TIRE
            (miles, CAR_ID, position) 
        values
            (?, ?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  13. Add the following lines to your test method to verify we can gain acess to the object tree through access from the child. This shows the power of the bi-directional relationship.

    
    
            log.debug("getting new instances from child side");
            em.detach(car2);
            Tire tire2 = em.find(Tire.class, new TirePK(car.getId(), tire.getPosition()));
            log.debug("checking child");
            assertNotNull("child not found", tire2);
            assertNotNull("parent not found", tire2.getCar());
            log.debug("checking parent");
            assertEquals("unexpected number of children", 2, tire2.getCar().getTires().size());
  14. Rebuild the module and re-run the test method. Notice that when we issue find() on the child -- both columns of the compound primary key are used in the where clause.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiDerivedIdClass
    ...
     -getting new instances from child side
    Hibernate: 
        select
            tire0_.CAR_ID as CAR1_42_1_,
            tire0_.position as position42_1_,
            tire0_.miles as miles42_1_,
            car1_.id as id41_0_,
            car1_.model as model41_0_,
            car1_.year as year41_0_ 
        from
            RELATIONEX_TIRE tire0_ 
        inner join
            RELATIONEX_CAR car1_ 
                on tire0_.CAR_ID=car1_.id 
        where
            tire0_.CAR_ID=? 
            and tire0_.position=?
     -checking child
     -checking parent
    Hibernate: 
        select
            tires0_.CAR_ID as CAR1_41_1_,
            tires0_.CAR_ID as CAR1_1_,
            tires0_.position as position1_,
            tires0_.CAR_ID as CAR1_42_0_,
            tires0_.position as position42_0_,
            tires0_.miles as miles42_0_ 
        from
            RELATIONEX_TIRE tires0_ 
        where
            tires0_.CAR_ID=?
    ...
    [INFO] BUILD SUCCESS
    
  15. Add the following lines to your test method to verify orphan removal when the child is removed from the parent collection.

    
    
            log.debug("orphaning one of the children");
            int startCount = em.createQuery("select count(t) from Tire t", Number.class).getSingleResult().intValue();
            Car car3 = tire2.getCar();
            car3.getTires().remove(tire2);
            em.flush();
            assertEquals("orphaned child not deleted", startCount-1,
                    em.createQuery("select count(t) from Tire t", Number.class).getSingleResult().intValue());
  16. Rebuild the module and re-run the test method. Notice the child is successfully deleted when it is orphaned by the removal from the parent collection. This works because we have added orphanRemoval=true to the parent relationship mapping.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiDerivedIdClass
    ...
     -orphaning one of the children
    Hibernate: 
        select
            count((tire0_.CAR_ID,
            tire0_.position)) as col_0_0_ 
        from
            RELATIONEX_TIRE tire0_ limit ?
    Hibernate: 
        delete 
        from
            RELATIONEX_TIRE 
        where
            CAR_ID=? 
            and position=?
    Hibernate: 
        select
            count((tire0_.CAR_ID,
            tire0_.position)) as col_0_0_ 
        from
            RELATIONEX_TIRE tire0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    
  17. Add the following lines to your test method to test cascade delete.

    
    
            log.debug("deleting parent");
            em.remove(car3);
            em.flush();
            assertEquals("orphaned child not deleted", startCount-2,
                    em.createQuery("select count(t) from Tire t", Number.class).getSingleResult().intValue());
  18. Rebuild the module and re-run the test method. Notice how the parent and child both get deleted by the single delete on the parent. This is because we supplied the cascade=DELETE on the parent relationship mapping.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.One2ManyBiTest#testOneToManyBiDerivedIdClass
    ...
     -deleting parent
    Hibernate: 
        delete 
        from
            RELATIONEX_TIRE 
        where
            CAR_ID=? 
            and position=?
    Hibernate: 
        delete 
        from
            RELATIONEX_CAR 
        where
            id=?
    Hibernate: 
        select
            count((tire0_.CAR_ID,
            tire0_.position)) as col_0_0_ 
        from
            RELATIONEX_TIRE tire0_ limit ?
    ...
    [INFO] BUILD SUCCESS
    

You have finished going through the derived compound primary key case with an @IdClass for a one-to-may/many-to-one, bi-directional relationship mapped using a foreign key. The primary example here was to derive the primary key from the parent for use in the child's identity. We annotated the @ManyToOne with @Id to show the foreign key mapping for the parent property was part of the child's primary key.

By the time you hit this chapter, you should have had the opportunity to see many relationship combinations and now we will cover the last official one: many-to-many. The many-to-many relationship is not a common relationship because it relates entities without any properties associated with the relationships. Commonly a relationship that starts off being a many-to-many will turn into a many-to-one and one-to-many to allow for properties to be assigned to the relationship. In UML data modeling they would refer to the "one"/entity in the middle as an Associated Class

In this section we are going to form a many-to-many, uni-directional relationship. As always with uni-directional relationships, that means we will be able to navigate the relationships from only the owning side. The only way to access the owning side from the inverse side is through a JPA query.

  1. Place the following class in your src/main tree. This class provides an example of the inverse side of a many-to-many, uni-directional relationship so there will be no reference to the relationship within this class. This class, however, has implemented a hashCode() and equals() method so that instances can be correctly identified within multiple collections.

    
    
    package myorg.relex.many2many;
    import javax.persistence.*;
    /**
     * This class provides an example of the inverse side of a many-to-many, uni-directional relationship
     */
    @Entity
    @Table(name="RELATIONEX_INDIVIDUAL")
    public class Individual {
        @Id @GeneratedValue
        private int id;
        
        @Column(length=32, nullable=false)
        private String name;
        
        protected Individual() {}
        public Individual(String name) {
            this.name = name;
        }
        public int getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        
        @Override
        public int hashCode() {
            return name==null? 0 : name.hashCode();
        }
        
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj)  return true;
                Individual rhs = (Individual) obj;
                if (name==null && rhs.name != null) { return false; }
                return name.equals(rhs.name);
            } catch (Exception ex) { return false; }
        }
    }
  2. Add the following class to your src/main tree. This class provides an example of the owning side of a many-to-many, uni-directional relationship. Therefore it defines the mapping from the entities to the database. The current implementation of the class relies on default mapping. We will make that more explicit shortly.

    
    
    package myorg.relex.many2many;
    import java.util.HashSet;
    import java.util.Set;
    import javax.persistence.*;
    /**
     * This class provides an example of the owning side of a many-to-many, uni-directional relationship.
     */
    @Entity
    @Table(name="RELATIONEX_GROUP")
    public class Group {
        @Id @GeneratedValue
        private int id;
        
        @ManyToMany
    //  @JoinTable(name="RELATIONEX_GROUP_MEMBER", 
    //          joinColumns=@JoinColumn(name="GROUP_ID"),
    //          inverseJoinColumns=@JoinColumn(name="MEMBER_ID"))
        Set<Individual> members;
        
        @Column(length=32, nullable=false)
        private String name;
        
        protected Group() {}
        public Group(String name) {
            this.name = name;
        }
        
        public int getId() { return id; }
        public Set<Individual> getMembers() {
            if (members == null) {
                members = new HashSet<Individual>();
            }
            return members;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  3. Add the new entitiy classes to your persistence unit.

    
    
            <class>myorg.relex.many2many.Group</class>
            <class>myorg.relex.many2many.Individual</class>
  4. Generate schema for the new entities and their relationship. Notice how the provider chose to use a join table mapping with foreign keys back to the individual entity class tables.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_GROUP (
            id integer generated by default as identity,
            name varchar(32) not null,
            primary key (id)
        );
    
        create table RELATIONEX_GROUP_RELATIONEX_INDIVIDUAL (
            RELATIONEX_GROUP_id integer not null,
            members_id integer not null,
            primary key (RELATIONEX_GROUP_id, members_id)
        );
    ...
        create table RELATIONEX_INDIVIDUAL (
            id integer generated by default as identity,
            name varchar(32) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_GROUP_RELATIONEX_INDIVIDUAL 
            add constraint FK4DBB1EB9B0D6112E 
            foreign key (members_id) 
            references RELATIONEX_INDIVIDUAL;
    
        alter table RELATIONEX_GROUP_RELATIONEX_INDIVIDUAL 
            add constraint FK4DBB1EB9CE0046D6 
            foreign key (RELATIONEX_GROUP_id) 
            references RELATIONEX_GROUP;
    
  5. Make the database mapping more explicit by defining the name for the join table and its individual columns.

    
    
    public class Group {
    ...
        @ManyToMany
        @JoinTable(name="RELATIONEX_GROUP_MEMBER", 
                joinColumns=@JoinColumn(name="GROUP_ID"),
                inverseJoinColumns=@JoinColumn(name="MEMBER_ID"))
        Set<Individual> members;
  6. Regenerate database schema for the two entities and their relation. Note that this time, the name of the join table and its columns follow what was specified in the mapping.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_GROUP (
            id integer generated by default as identity,
            name varchar(32) not null,
            primary key (id)
        );
    
        create table RELATIONEX_GROUP_MEMBER (
            GROUP_ID integer not null,
            MEMBER_ID integer not null,
            primary key (GROUP_ID, MEMBER_ID)
        );
    ...
        create table RELATIONEX_INDIVIDUAL (
            id integer generated by default as identity,
            name varchar(32) not null,
            primary key (id)
        );
    ...
        alter table RELATIONEX_GROUP_MEMBER 
            add constraint FK2ADA1F0A50B9540D 
            foreign key (MEMBER_ID) 
            references RELATIONEX_INDIVIDUAL;
    
        alter table RELATIONEX_GROUP_MEMBER 
            add constraint FK2ADA1F0AD00E5846 
            foreign key (GROUP_ID) 
            references RELATIONEX_GROUP;
    

    Note too -- the many-to-many mapping prevents a single entity from being related multiple times to another entity. This is enforced by the two foreign keys of the join table being used as a compound primary key for that table.

  7. Add the following test method to the existing JUnit test case. This method will create an owning and inverse instance and relate the two.

    
    
        @Test
        public void testManyToManyUni() {
            log.info("*** testManyToManyUni ***");
            
            log.debug("persisting owner");
            Group group = new Group("board");
            em.persist(group);
            em.flush();
            log.debug("persisting inverse");
            Individual individual = new Individual("manny");
            em.persist(individual);
            em.flush();
            log.debug("relating parent to inverse");
            group.getMembers().add(individual);
            em.flush();
        }
  8. Build the module and run the test method. Notice how the owning side and inverse side are persisted and then related through the join table.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
    -*** testManyToManyUni ***
     -persisting owner
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP
            (id, name) 
        values
            (null, ?)
     -persisting inverse
    Hibernate: 
        insert 
        into
            RELATIONEX_INDIVIDUAL
            (id, name) 
        values
            (null, ?)
     -relating parent to inverse
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP_MEMBER
            (GROUP_ID, MEMBER_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  9. Add the following lines to your test method. This will get new instances from the database and verify what what was done in the previous block was correctly performed.

    
    
            log.debug("getting new instances");
            em.clear();
            Group group2 = em.find(Group.class, group.getId());
            log.debug("checking owner");
            assertEquals("unexpected group.name", group.getName(), group2.getName());
            log.debug("checking inverse");
            assertEquals("unexpected size", 1, group2.getMembers().size());
            assertEquals("unexpected member.name", individual.getName(), group2.getMembers().iterator().next().getName());
  10. Rebuild the module and re-run the test method. Notice how the owning entity is first LAZY loaded during the find() and then the join table and inverse entity table is queried for once we navigate the collection.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
     -getting new instances
    Hibernate: 
        select
            group0_.id as id43_0_,
            group0_.name as name43_0_ 
        from
            RELATIONEX_GROUP group0_ 
        where
            group0_.id=?
     -checking owner
     -checking inverse
    Hibernate: 
        select
            members0_.GROUP_ID as GROUP1_43_1_,
            members0_.MEMBER_ID as MEMBER2_1_,
            individual1_.id as id44_0_,
            individual1_.name as name44_0_ 
        from
            RELATIONEX_GROUP_MEMBER members0_ 
        inner join
            RELATIONEX_INDIVIDUAL individual1_ 
                on members0_.MEMBER_ID=individual1_.id 
        where
            members0_.GROUP_ID=?
    ...
    [INFO] BUILD SUCCESS
    

    Note that the LAZY load was because of our (default fetch specification and does not have anything directly to do with the many-to-many relationship formed.

  11. Add the following lines to your test method this will add two (2) additional members to the original owning entity.

    
    
            log.debug("adding inverse members");
            Individual individualB = new Individual("moe");     
            Individual individualC = new Individual("jack");        
            group2.getMembers().add(individualB);
            group2.getMembers().add(individualC);
            em.persist(individualB);
            em.persist(individualC);
            em.flush();
  12. Rebuild the module and re-run the test method. Notice how the persist and the addition to the owning entity collection caused an insert into both the entity and join table for both entities added.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
     -adding inverse members
    Hibernate: 
        insert 
        into
            RELATIONEX_INDIVIDUAL
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_INDIVIDUAL
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP_MEMBER
            (GROUP_ID, MEMBER_ID) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP_MEMBER
            (GROUP_ID, MEMBER_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  13. Add the following lines to your test method to add a second owning entity and add a few of the existing inverse entities to the new entity. This is where the many-to-many is unique. Many-to-many allows a single entity to be related to multiple parents.

    
    
            log.debug("adding owning members");
            Group groupB = new Group("night shift");
            groupB.getMembers().add(individualB);
            groupB.getMembers().add(individualC);
            em.persist(groupB);
            em.flush();
  14. Rebuild the module and re-run the test method. Notice how there was an insert for the new owning entity and then followed only by inserts into the join table to form the relationships.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
     -adding owning members
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP
            (id, name) 
        values
            (null, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP_MEMBER
            (GROUP_ID, MEMBER_ID) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_GROUP_MEMBER
            (GROUP_ID, MEMBER_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  15. Add the following lines to the test method to verify the number of collections each inverse entity is a member of.

    
    
            log.debug("checking relations");
            assertEquals("unexpected relations for member 1", 1, em.createQuery(
                    "select count(g) from Group g where :individual member of g.members", Number.class)
                    .setParameter("individual", individual)
                    .getSingleResult().intValue());
            assertEquals("unexpected relations for member 2", 2, em.createQuery(
                    "select count(g) from Group g where :individual member of g.members", Number.class)
                    .setParameter("individual", individualB)
                    .getSingleResult().intValue());
            assertEquals("unexpected relations for member 3", 2, em.createQuery(
                    "select count(g) from Group g where :individual member of g.members", Number.class)
                    .setParameter("individual", individualC)
                    .getSingleResult().intValue());
  16. Rebuild the module and re-run the test method. Observe the selects being done to determine which entities each is associated with and the evaluation of that result passes.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
    Hibernate: 
        select
            count(group0_.id) as col_0_0_ 
        from
            RELATIONEX_GROUP group0_ 
        where
            ? in (
                select
                    individual2_.id 
                from
                    RELATIONEX_GROUP_MEMBER members1_,
                    RELATIONEX_INDIVIDUAL individual2_ 
                where
                    group0_.id=members1_.GROUP_ID 
                    and members1_.MEMBER_ID=individual2_.id
            ) limit ?
    (x3)
    ...
    [INFO] BUILD SUCCESS
    
  17. Add the following lines to the test method. This will remove the relationship between one of the inverse entities and both owning entities. At the conclusion we verify the entity relation we removed does not exist.

    
    
            log.debug("removing relations");
            assertTrue(group2.getMembers().remove(individualB));
            assertTrue(groupB.getMembers().remove(individualB));
            log.debug("verifying relation removal");
            assertEquals("unexpected relations for member 1", 0, em.createQuery(
                    "select count(g) from Group g, IN (g.members) m where m = :individual", Number.class)
                    .setParameter("individual", individualB)
                    .getSingleResult().intValue());
  18. Rebuild the module and re-run the unit test. Notice the two join table rows being deleted to officially remove the entity from the two collections. Notice also with the different query -- we changed the subquery to an INNER JOIN.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
     -removing relations
     -verifying relation removal
    Hibernate: 
        delete 
        from
            RELATIONEX_GROUP_MEMBER 
        where
            GROUP_ID=? 
            and MEMBER_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_GROUP_MEMBER 
        where
            GROUP_ID=? 
            and MEMBER_ID=?
    Hibernate: 
        select
            count(group0_.id) as col_0_0_ 
        from
            RELATIONEX_GROUP group0_ 
        inner join
            RELATIONEX_GROUP_MEMBER members1_ 
                on group0_.id=members1_.GROUP_ID 
        inner join
            RELATIONEX_INDIVIDUAL individual2_ 
                on members1_.MEMBER_ID=individual2_.id 
        where
            individual2_.id=? limit ?
    ...
    [INFO] BUILD SUCCESS
    
  19. Add the following lines to the test method to verify the removed relationship did not impact the inverse entity. Also added is a delete of the initial entity.

    
    
            log.debug("verifying inverse was not removed");
            em.flush(); em.clear();
            assertNotNull(em.find(Individual.class, individualB.getId()));
            log.debug("removing initial owner");
            em.remove(em.find(Group.class, group.getId()));
            em.flush();
  20. Rebuild the module and re-run the test method. Notice the check for the inverse still existing passes.

    $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyUni
    ...
     -verifying inverse was not removed
    Hibernate: 
        select
            individual0_.id as id44_0_,
            individual0_.name as name44_0_ 
        from
            RELATIONEX_INDIVIDUAL individual0_ 
        where
            individual0_.id=?
    

    Notice the removal of one of the owning entities causes a search of the join table, the removal of the join table rows that are associated with the owning entity we want to delete, and then the removal of that entity.

     -removing initial owner
    Hibernate: 
        select
            group0_.id as id43_0_,
            group0_.name as name43_0_ 
        from
            RELATIONEX_GROUP group0_ 
        where
            group0_.id=?
    Hibernate: 
        delete 
        from
            RELATIONEX_GROUP_MEMBER 
        where
            GROUP_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_GROUP 
        where
            id=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished going through a many-to-many, uni-directional relationship. Like all many-to-many relationships, the two sides of the relationship must be linked through a join table. In this case the foreign keys to to the entities were based on generated simple keys. In the next section we will change the relationship to be bi-directional so that we can navigate from either direction.

In the previous section we mapped a relationship only from the owning side. In this exercise we will map the many-to-many relationship from both the owning and inverse sides.

  1. Put the following class in your src/main tree. This class provides an example of both the owning and inverse side of a many-to-many relationship because the design of the relationship is to originate and end with an entity class of the same type. The recursive nature of this example has nothing specifically to do with many-to-many relationships -- but it does make for an interesting example.

    In this example, the "children" collection is the owning side and the "parent" collection is the inverse side.

    
    
    package myorg.relex.many2many;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.UUID;
    import javax.persistence.*;
    /**
     * This class provides an example of a many-to-many, bi-directional relationship that just
     * happens to be recursive. Both ends of the relationship reference an instance of this class. 
     */
    @Entity
    @Table(name="RELATIONEX_NODE")
    public class Node {
        @Id
        @Column(length=36, nullable=false)
        private String id;
        
        @ManyToMany(cascade={CascadeType.PERSIST}, fetch=FetchType.LAZY)
        @JoinTable(name="RELATIONEX_NODE_REL",
                joinColumns=@JoinColumn(name="PARENT_ID"),
                inverseJoinColumns=@JoinColumn(name="CHILD_ID"))
        private Set<Node> children;
        
        @ManyToMany(mappedBy="children", fetch=FetchType.EAGER)
        private Set<Node> parents;
        
        @Column(length=32, nullable=false)
        private String name;
        
        public Node() { id=UUID.randomUUID().toString(); }
        public Node(String name) {
            this();
            this.name = name;
        }
        public Node(Node parent, String name) {
            this();
            this.name = name;
            parent.getChildren().add(this);
            getParents().add(parent);
        }
        public String getId() { return id; }
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
        public Set<Node> getChildren() {
            if (children==null) {
                children = new HashSet<Node>();
            }
            return children;
        }
        
        public Set<Node> getParents() {
            if (parents == null) {
                parents = new HashSet<Node>();
            }
            return parents;
        }
        
        @Override
        public int hashCode() {
            return id==null?0:id.hashCode();
        }   
        @Override
        public boolean equals(Object obj) {
            try {
                if (this == obj) { return true; }
                Node rhs = (Node)obj;
                if (id==null && rhs.id != null) { return false; }
                return id.equals(rhs.id);
            } catch (Exception ex) { return false; }
        }
    }

    Note that in this class design -- a convenience constructor is supplied that accepts a parent node, adds itself to that parent's children, and then adds the parent to this entity.

  2. Add the new entity to the persistence unit.

    
    
            <class>myorg.relex.many2many.Node</class>
  3. Generate database schema for the new entity and its relationship. Notice how we only have a single entity class table and a join table that links to rows from that table to one another. In a real application, we would probably want to add an additional CHECK that parent and child cannot be equal.

    $ mvn clean process-test-classes; more target/classes/ddl/relationEx-createJPA.ddl 
    ...
        create table RELATIONEX_NODE (
            id varchar(36) not null,
            name varchar(32) not null,
            primary key (id)
        );
    
        create table RELATIONEX_NODE_REL (
            PARENT_ID varchar(36) not null,
            CHILD_ID varchar(36) not null,
            primary key (PARENT_ID, CHILD_ID)
        );
    ...
        alter table RELATIONEX_NODE_REL 
            add constraint FK7D1BC34C3F5FCCB4 
            foreign key (CHILD_ID) 
            references RELATIONEX_NODE;
    
        alter table RELATIONEX_NODE_REL 
            add constraint FK7D1BC34C57DC6666 
            foreign key (PARENT_ID) 
            references RELATIONEX_NODE;
    
  4. Add the following test method to your JUnit test case. This will create two entities and relate them through a business relationship of parent and child.

    
    
        @Test
        public void testManyToManyBi() {
            log.info("*** testManyToManyBi ***");
            
            log.debug("create instances");
            Node one = new Node("one");
            Node two = new Node(one,"two");
            em.persist(one);
            em.flush();
        }
  5. Build the module and run the test method. Notice how two rows are inserted into the entity table and then a row that relates them is added to the join table.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -*** testManyToManyBi ***
     -create instances
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE
            (name, id) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE
            (name, id) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE_REL
            (PARENT_ID, CHILD_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  6. Add the following lines to the test method. This will obtain new instances of the entities in our object tree and verify their contents. It also shows the impact of EAGER and LAZY fetching.

    
    
            log.debug("getting new instances from owning side");
            em.clear();
            Node one2 = em.find(Node.class, one.getId());
            assertNotNull("owning side not found", one2);
            log.debug("checking owning side");
            assertEquals("unexpected owning.name", one.getName(), one2.getName());
            log.debug("checking parents");
            assertEquals("unexpected parents.size", 0, one2.getParents().size());
            log.debug("checking children");
            assertEquals("unexpected children.size", 1, one2.getChildren().size());
            assertEquals("unexpected child.name", two.getName(), one2.getChildren().iterator().next().getName());
  7. Rebuild the module and re-run the test method. Notice that because of the EAGER fetch specification on parents, the join table and entity table is queried during the find() operation.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -getting new instances from owning side
    Hibernate: 
        select
            node0_.id as id45_1_,
            node0_.name as name45_1_,
            parents1_.CHILD_ID as CHILD2_45_3_,
            node2_.id as PARENT1_3_,
            node2_.id as id45_0_,
            node2_.name as name45_0_ 
        from
            RELATIONEX_NODE node0_ 
        left outer join
            RELATIONEX_NODE_REL parents1_ 
                on node0_.id=parents1_.CHILD_ID 
        left outer join
            RELATIONEX_NODE node2_ 
                on parents1_.PARENT_ID=node2_.id 
        where
            node0_.id=?
     -checking owning side
     -checking parents
    

    Because of the LAZY fetch specification on children, the join table and child entities are not searched for until specifically requested.

     -checking children
    Hibernate: 
        select
            children0_.PARENT_ID as PARENT1_45_1_,
            children0_.CHILD_ID as CHILD2_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL children0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on children0_.CHILD_ID=node1_.id 
        where
            children0_.PARENT_ID=?
    Hibernate: 
        select
            parents0_.CHILD_ID as CHILD2_45_1_,
            parents0_.PARENT_ID as PARENT1_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL parents0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on parents0_.PARENT_ID=node1_.id 
        where
            parents0_.CHILD_ID=?
    ...
    [INFO] BUILD SUCCESS
    
  8. Add the following lines to the test method to add a few additional inverse entities. The new entities are registered with the owning side within the constructor. All are persisted during the subsequent call to persist() on the owning side because of the cascade=PERSIST definition on the owning side.

    
    
            log.debug("adding more inverse instances");
            Node twoB = new Node(one2, "twoB");
            Node twoC = new Node(one2, "twoC");
            em.persist(one2);
            em.flush();
  9. Rebuild the module and re-run the unit test. Notice the two inserts for the inverse entities and two inserts in the join table relating the new entities to the owning side. We now have one owning entity and three inverse entities related.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -adding more inverse instances
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE
            (name, id) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE
            (name, id) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE_REL
            (PARENT_ID, CHILD_ID) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE_REL
            (PARENT_ID, CHILD_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  10. Add the following lines to the test method. This tests obtaining our object tree form the inverse side. This is a feature unique to bi-directional relationships.

            log.debug("getting new instances from inverse side");
            em.clear();
            Node two2 = em.find(Node.class, two.getId());
            assertNotNull("inverse node not found", two2);
            log.debug("checking inverse side");
            assertEquals("unexpected name", two.getName(), two2.getName());
            log.debug("checking parents");
            assertEquals("unexpected parents.size", 1, two2.getParents().size());
            log.debug("checking children");
            assertEquals("unexpected children.size", 0, two2.getChildren().size());
    
  11. Rebuild the module and re-run the test method. Note the entity we call find() on this time has a parent and no children. Most of the work is done during the EAGER fetch on the parent relationship.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -getting new instances from inverse side
    Hibernate: 
        select
            node0_.id as id45_1_,
            node0_.name as name45_1_,
            parents1_.CHILD_ID as CHILD2_45_3_,
            node2_.id as PARENT1_3_,
            node2_.id as id45_0_,
            node2_.name as name45_0_ 
        from
            RELATIONEX_NODE node0_ 
        left outer join
            RELATIONEX_NODE_REL parents1_ 
                on node0_.id=parents1_.CHILD_ID 
        left outer join
            RELATIONEX_NODE node2_ 
                on parents1_.PARENT_ID=node2_.id 
        where
            node0_.id=?
    Hibernate: 
        select
            parents0_.CHILD_ID as CHILD2_45_1_,
            parents0_.PARENT_ID as PARENT1_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL parents0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on parents0_.PARENT_ID=node1_.id 
        where
            parents0_.CHILD_ID=?
     -checking inverse side
     -checking parents
     -checking children
    Hibernate: 
        select
            children0_.PARENT_ID as PARENT1_45_1_,
            children0_.CHILD_ID as CHILD2_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL children0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on children0_.CHILD_ID=node1_.id 
        where
            children0_.PARENT_ID=?
    ...
    [INFO] BUILD SUCCESS
    
  12. Add the following lines to the test method to add an additional owning entity that will share a relation to one of the inverse entities. Since we didn't add a concenience method to add an existing child to a new parent, all calls here are exposed. We need to add the child to the parent collection and add the parent to the child collection.

    
    
            log.debug("adding owning entity");
            Node oneB = new Node("oneB");
            oneB.getChildren().add(two2);
            two2.getParents().add(oneB);
            em.persist(oneB);
            em.flush();
  13. Rebuild the module and re-run the test method. Notice how all we should get is an insert for the new owning entity and a row in the join table relating the new entity to one of the existing ones.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -adding owning entity
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE
            (name, id) 
        values
            (?, ?)
    Hibernate: 
        insert 
        into
            RELATIONEX_NODE_REL
            (PARENT_ID, CHILD_ID) 
        values
            (?, ?)
    ...
    [INFO] BUILD SUCCESS
    
  14. Add the following lines to the test method to verify the number of relationships that exist.

    
    
            log.debug("checking relationships");
            assertEquals("unexpected parents", 0,
                    em.createQuery("select count(p) from Node n, IN (n.parents) p where n=:node", Number.class)
                        .setParameter("node", one)
                        .getSingleResult().intValue());
            assertEquals("unexpected parents", 2,
                    em.createQuery("select count(p) from Node n, IN (n.parents) p where n=:node", Number.class)
                        .setParameter("node", two)
                        .getSingleResult().intValue());
            assertEquals("unexpected parents", 1,
                    em.createQuery("select count(p) from Node n, IN (n.parents) p where n=:node", Number.class)
                        .setParameter("node", twoB)
                        .getSingleResult().intValue());
            assertEquals("unexpected parents", 1,
                    em.createQuery("select count(p) from Node n, IN (n.parents) p where n=:node", Number.class)
                        .setParameter("node", twoC)
                        .getSingleResult().intValue());     
            assertEquals("unexpected children", 3,
                    em.createQuery("select count(c) from Node n, IN (n.children) c where n=:node", Number.class)
                        .setParameter("node", one)
                        .getSingleResult().intValue());
            assertEquals("unexpected children", 0,
                    em.createQuery("select count(c) from Node n, IN (n.children) c where n=:node", Number.class)
                        .setParameter("node", two)
                        .getSingleResult().intValue());     
            assertEquals("unexpected children", 1,
                    em.createQuery("select count(c) from Node n, IN (n.children) c where n=:node", Number.class)
                        .setParameter("node", oneB)
                        .getSingleResult().intValue());
  15. Rebuild the module and re-run the unit test. This shows the assertions on the queuries passing.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -checking relationships
    Hibernate: 
        select
            count(node2_.id) as col_0_0_ 
        from
            RELATIONEX_NODE node0_ 
        inner join
            RELATIONEX_NODE_REL parents1_ 
                on node0_.id=parents1_.CHILD_ID 
        inner join
            RELATIONEX_NODE node2_ 
                on parents1_.PARENT_ID=node2_.id 
        where
            node0_.id=? limit ?
    (x3 more)
    Hibernate: 
        select
            count(node2_.id) as col_0_0_ 
        from
            RELATIONEX_NODE node0_ 
        inner join
            RELATIONEX_NODE_REL children1_ 
                on node0_.id=children1_.PARENT_ID 
        inner join
            RELATIONEX_NODE node2_ 
                on children1_.CHILD_ID=node2_.id 
        where
            node0_.id=? limit ?
    (x2 more)
    ...
    [INFO] BUILD SUCCESS
    
  16. Add the following lines to the test method. This will remove a relationship between one of the two nodes. None the managed instance for the owning side had to be obtained from the persistence context so we could actually perform the removal.

    
    
            log.debug("getting managed owning side");
            assertNotNull(one = em.find(Node.class, one.getId()));
            log.debug("removing relationship");
            one.getChildren().remove(two);
            two.getParents().remove(one);
            em.flush();
            assertEquals("unexpected children", 2,
                    em.createQuery("select count(c) from Node n, IN (n.children) c where n=:node", Number.class)
                        .setParameter("node", one)
                        .getSingleResult().intValue());
            assertEquals("unexpected parents", 1,
                    em.createQuery("select count(p) from Node n, IN (n.parents) p where n=:node", Number.class)
                        .setParameter("node", two)
                        .getSingleResult().intValue());
  17. Rebuild the module and re-run the test method. Notice that the provider appears to be loading the related entities prior to removing the relationship.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -removing relationship
    Hibernate: 
        select
            children0_.PARENT_ID as PARENT1_45_1_,
            children0_.CHILD_ID as CHILD2_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL children0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on children0_.CHILD_ID=node1_.id 
        where
            children0_.PARENT_ID=?
    Hibernate: 
        select
            parents0_.CHILD_ID as CHILD2_45_1_,
            parents0_.PARENT_ID as PARENT1_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL parents0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on parents0_.PARENT_ID=node1_.id 
        where
            parents0_.CHILD_ID=?
    Hibernate: 
        select
            parents0_.CHILD_ID as CHILD2_45_1_,
            parents0_.PARENT_ID as PARENT1_1_,
            node1_.id as id45_0_,
            node1_.name as name45_0_ 
        from
            RELATIONEX_NODE_REL parents0_ 
        inner join
            RELATIONEX_NODE node1_ 
                on parents0_.PARENT_ID=node1_.id 
        where
            parents0_.CHILD_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE_REL 
        where
            PARENT_ID=? 
            and CHILD_ID=?
    Hibernate: 
        select
            count(node2_.id) as col_0_0_ 
    ...
    [INFO] BUILD SUCCESS 
    
  18. Add the following lines to the test method to remove one of the owning entities.

    
    
            log.debug("deleting owner");
            em.remove(oneB);
            em.flush();
            assertEquals("unexpected parents", 0,
                    em.createQuery("select count(p) from Node n, IN (n.parents) p where n=:node", Number.class)
                        .setParameter("node", two)
                        .getSingleResult().intValue());
  19. Rebuild the module and re-run the test method. Notice the provider removed owned relationships prior to removing the owning entity.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -deleting owner
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE_REL 
        where
            PARENT_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE 
        where
            id=?
    Hibernate: 
        select
            count(node2_.id) as col_0_0_ 
        from
            RELATIONEX_NODE node0_ 
        inner join
            RELATIONEX_NODE_REL parents1_ 
                on node0_.id=parents1_.CHILD_ID 
        inner join
            RELATIONEX_NODE node2_ 
                on parents1_.PARENT_ID=node2_.id 
        where
            node0_.id=? limit ?
    ...
    [INFO] BUILD SUCCESS
    
  20. Add the following lines to the test method. They will attempt to remove the inverse side of a relation while a relationstill exists.

    
    
            log.debug("deleting inverse");
            assertNotNull(twoB = em.find(Node.class, twoB.getId()));
            em.remove(twoB);
            em.flush();
            assertNull("inverse not deleted", em.find(Node.class, twoB.getId()));
  21. Rebuild the test method and re-run the test method. Notice how the delete never was issued to the database. Worse case -- I would have expected a foreign key violation from the join table but here the delete was never flushed to the database.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -deleting inverse
    ...
    Failed tests:   testManyToManyBi(myorg.relex.Many2ManyTest): inverse not deleted
    ...
    [INFO] BUILD FAILURE
    
  22. Change the test method to remove the relationship after attempting to remove the inverse side. This will help determine if the delete was waiting for the removal of the foreign key reference.

            log.debug("deleting inverse");
            assertNotNull(twoB = em.find(Node.class, twoB.getId()));
            em.remove(twoB);
            em.flush();
    //      assertNull("inverse not deleted", em.find(Node.class, twoB.getId()));
            one.getChildren().remove(twoB);
            em.flush();
            assertNull("inverse not deleted", em.find(Node.class, twoB.getId()));
    
  23. Rebuild the test method and re-run the test method. Notice the relationship is removed but nothing occurs to the inverse side because of it.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -deleting inverse
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE_REL 
        where
            PARENT_ID=? 
            and CHILD_ID=?
    ...
    Failed tests:   testManyToManyBi(myorg.relex.Many2ManyTest): inverse not deleted
    ...
    [INFO] BUILD FAILURE
    
  24. Change the test method so the inverse entity is removed after the relationship is removed. At this point in time there are no longer any incomming references to the entity.

            log.debug("deleting inverse");
            assertNotNull(twoB = em.find(Node.class, twoB.getId()));
            em.remove(twoB);
            em.flush();
    //      assertNull("inverse not deleted", em.find(Node.class, twoB.getId()));
            one.getChildren().remove(twoB);
            em.flush();
    //        assertNull("inverse not deleted", em.find(Node.class, twoB.getId()));
            em.remove(twoB);
            em.flush();
            assertNull("inverse not deleted", em.find(Node.class, twoB.getId()));
    
  25. Rebuild the test method and re-run the test method. Notice the removal of the relationship caused the removal of a specific row from the join table. The successful removal of the entity itself caused a delete from the entity table and any relationships it owned from the join table. None of this worked, however, when there was a known relationship existing in the cache. That may have had something to do with the EAGER fetch setting for parent members.

     $ mvn clean test -P\!h2db -Ph2srv -Dtest=myorg.relex.Many2ManyTest#testManyToManyBi 
     ...
     -deleting inverse
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE_REL 
        where
            PARENT_ID=? 
            and CHILD_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE_REL 
        where
            PARENT_ID=?
    Hibernate: 
        delete 
        from
            RELATIONEX_NODE 
        where
            id=?
    Hibernate: 
        select
            node0_.id as id45_1_,
            node0_.name as name45_1_,
            parents1_.CHILD_ID as CHILD2_45_3_,
            node2_.id as PARENT1_3_,
            node2_.id as id45_0_,
            node2_.name as name45_0_ 
        from
            RELATIONEX_NODE node0_ 
        left outer join
            RELATIONEX_NODE_REL parents1_ 
                on node0_.id=parents1_.CHILD_ID 
        left outer join
            RELATIONEX_NODE node2_ 
                on parents1_.PARENT_ID=node2_.id 
        where
            node0_.id=?
    ...
    [INFO] BUILD SUCCESS
    

You have finished going through an example of many-to-many, bi-directional relationship. For fun, we used a recursive relationship in this case where the same class was on both side of the relationship and that class had a copy of the owning and inverse side of the relation so that it could navigate in either direction.