Enterprise Java Development@TOPIC@

Chapter 35. Mapping One-to-One Relationships

35.1. Setup
35.2. One-to-One Uni-directional Relationships
35.2.1. One-to-One Uni-directional Using a Foreign Key
35.2.2. One-to-One Uni-directional Using a Join Table
35.2.3. One-to-One Uni-directional Using a Primary Key Join
35.2.4. One-to-One Uni-directional Using MapsId
35.2.5. One-to-One Uni-directional Using Composite Primary/Foreign Keys
35.3. Mapping One-to-One Bi-directional Relationships
35.3.1. One-to-One Bi-directional Joined By Primary Key
35.3.2. One-to-One Bi-directional 0..1 Owner Relationship
35.3.3. One-to-One Bi-directional 0..1 Inverse Relationship
35.4. One-to-One EntityManager Automated Actions
35.4.1. One-to-One Using Cascades From Owner
35.4.2. One-to-One Using Cascades From Inverse
35.4.3. One-to-One Using Orphan Removal
35.5. Summary

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.

Create a JUnit test class to host tests for the one-to-one mappings.

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.jpa.HibernatePersistenceProvider</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.

In this chapter we mapped two entity classes through a one-to-one relationship using multiple techniques and under different situations.