Enterprise Java Development@TOPIC@

Chapter 36. Mapping One-to-Many Uni-directional Relationships

36.1. Setup
36.2. One-to-Many Uni-directional
36.2.1. One-to-Many Uni-directional with Join Table
36.2.2. One-to-Many Uni-directional using Foreign Key Join (from Child Table)
36.2.3. One-to-Many Uni-directional Mapping of Simple Types using ElementCollection
36.2.4. One-to-Many Uni-directional Mapping of Embeddable Type using ElementCollection
36.3. One-to-Many Provider Actions
36.3.1. One-to-Many Orphan Removal
36.4. Summary

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

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

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

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

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

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

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

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

    Note that...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Notice that...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Notice that...

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

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

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

    Notice that...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In this chapter we you worked with several types of one-to-many, uni-directional relationships. In this type of relationship -- the many/dependent/child entity knows nothing of the relationship and it must be defined from the one/parent side. You formed that mapping using a join table as well as inserting a foreign key into the child table (without the child entity knowing about it). You also created mappings to many/child/dependent non-entity classes like Strings and JPA @Embeddable classes. This allows you to create entity classes with collections of non-entity POJOs and have them mapped to database tables that can be used for searches.

We will pause our look at relationship types take a detour into collection types in the next chapter. We want to make sure we understand many of the options available and requirements associated with those options before we get too far into more relationships involving a "many" side.