Enterprise Java Development@TOPIC@

Chapter 33. Objects Mapped to Multiple Tables

33.1. Mapping to Secondary Tables
33.2. Summary

This chapter will take you through mapping a single class to multiple tables. We will reuse the example from the embedded object mapping case because conceptually they are trying to do the same thing except

  1. Add the following Java class to your src/main tree. We are not done mapping just yet but lets see what this maps to before making changes.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_BEAR2")
    public class Bear2 {
        @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
        private int id;
        private String firstName;
        private String lastName;
        
        private int streetNumber;
        private String streetName;
        private String city;
        private String state;
        
        public int getId() { return id; }
        public Bear2 setId(int id) { this.id = id; return this; }
        
        public String getFirstName() { return firstName; }
        public Bear2 setFirstName(String firstName) {
            this.firstName = firstName; return this;
        }
        
        public String getLastName() { return lastName; }
        public Bear2 setLastName(String lastName) {
            this.lastName = lastName; return this;
        }
        
        public int getStreetNumber() { return streetNumber; }
        public Bear2 setStreetNumber(int streetNumber) { 
            this.streetNumber = streetNumber; return this; 
        }
        
        public String getStreetName() { return streetName; }
        public Bear2 setStreetName(String streetName) { 
            this.streetName = streetName; return this; 
        }
        
        public String getCity() { return city; }
        public Bear2 setCity(String city) { 
            this.city = city; return this; 
        }
        
        public String getState() { return state; }
        public Bear2 setState(String state) { 
            this.state = state; return this; 
        }
    }
  2. Add the new entity class to your persistence unit

    
    
            <class>myorg.entityex.annotated.Bear2</class>
  3. Build the module with the new entity class and observe how the class is mapped to the database. Of no surprise, it is a simple, flat mapping to a single table by default.

       create table ENTITYEX_BEAR2 (
            id integer generated by default as identity,
            city varchar(255),
            firstName varchar(255),
            lastName varchar(255),
            state varchar(255),
            streetName varchar(255),
            streetNumber integer not null,
            primary key (id)
        );
    
  4. Define a secondary table to host the name properties

    
    
    @Entity
    @Table(name="ENTITYEX_BEAR2")
    @SecondaryTables({
        @SecondaryTable(name="ENTITYEX_BEAR2_NAME")
    })
    public class Bear2 {
  5. Assign the firstName and lastName properties to the secondary table.

    
    
        @Column(table="ENTITYEX_BEAR2_NAME", name="FIRST_NAME", length=16)
        private String firstName;
        @Column(table="ENTITYEX_BEAR2_NAME", name="LAST_NAME", length=16)
        private String lastName;
  6. Rebuild the module and notice the database schema generated. The firstName and lastName are mapped to the secondary table, the two tables are joined by primary key values, and the primary key generation and propagation is taken care of by the provider.

        create table ENTITYEX_BEAR2 (
            id integer generated by default as identity,
            city varchar(255),
            state varchar(255),
            streetName varchar(255),
            streetNumber integer not null,
            primary key (id)
        );
    
        create table ENTITYEX_BEAR2_NAME (
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            id integer not null,
            primary key (id)
        );
    ...
       alter table ENTITYEX_BEAR2_NAME 
            add constraint FKED0C2F35D7F6CC81 
            foreign key (id) 
            references ENTITYEX_BEAR2;
    
  7. Add a second secondary table for the address properties

    
    
    @Entity
    @Table(name="ENTITYEX_BEAR2")
    @SecondaryTables({
        @SecondaryTable(name="ENTITYEX_BEAR2_NAME"),
        @SecondaryTable(name="ENTITYEX_BEAR2_ADDRESS")
    })
    public class Bear2 {
  8. Assign the address properties to the new table

    
    
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="STREET_NUMBER", length=16)
        private int streetNumber;
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="STREET_NAME", length=16)
        private String streetName;
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="CITY", length=16)
        private String city;
        @Column(table="ENTITYEX_BEAR2_ADDRESS", name="STATE", length=16)
        private String state;
  9. Rebuild the module and note the database schema generated. We now have a second table with a primary key join to the primary table.

        create table ENTITYEX_BEAR2 (
            id integer generated by default as identity,
            primary key (id)
        );
    
        create table ENTITYEX_BEAR2_ADDRESS (
            CITY varchar(16),
            STATE varchar(16),
            STREET_NAME varchar(16),
            STREET_NUMBER integer,
            id integer not null,
            primary key (id)
        );
    
        create table ENTITYEX_BEAR2_NAME (
            FIRST_NAME varchar(16),
            LAST_NAME varchar(16),
            id integer not null,
            primary key (id)
        );
    ...
       alter table ENTITYEX_BEAR2_ADDRESS 
            add constraint FKD1DF32EAD7F6CC81 
            foreign key (id) 
            references ENTITYEX_BEAR2;
    
        alter table ENTITYEX_BEAR2_NAME 
            add constraint FKED0C2F35D7F6CC81 
            foreign key (id) 
            references ENTITYEX_BEAR2;
    
  10. Add the following test method to the existing JUnit test case. Note this test method is similar to the embedded object test method except that all properties are directly accessible from the parent entity class.

    
    
        @Test
        public void testMultiTableMapping() {
            log.info("testMultiTableMapping");
            Bear2 bear = new Bear2()
                .setFirstName("Yogi")
                .setLastName("Bear")
                .setStreetNumber(1)
                .setStreetName("Picnic")
                .setCity("Jellystone Park")
                .setState("???");
            em.persist(bear);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(bear);
            Bear2 bear2 = em.find(Bear2.class, bear.getId());
            assertEquals("unexpected firstName", bear.getFirstName(), bear2.getFirstName());
            assertEquals("unexpected lastName", bear.getLastName(), bear2.getLastName());
            assertEquals("unexpected street number", 
                    bear.getStreetNumber(), bear2.getStreetNumber());
            assertEquals("unexpected street name", 
                    bear.getStreetName(), bear2.getStreetName());
            assertEquals("unexpected city", 
                    bear.getCity(), bear2.getCity());
            assertEquals("unexpected state", 
                    bear.getState(), bear2.getState());
        }
  11. Rebuild the module and observe the pass/fail results of the new test as well as the database interaction.

     -testMultiTableMapping
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR2
            (id) 
        values
            (null)
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR2_ADDRESS
            (CITY, STATE, STREET_NAME, STREET_NUMBER, id) 
        values
            (?, ?, ?, ?, ?)
    Hibernate: 
        insert 
        into
            ENTITYEX_BEAR2_NAME
            (FIRST_NAME, LAST_NAME, id) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            bear2x0_.id as id8_0_,
            bear2x0_1_.CITY as CITY10_0_,
            bear2x0_1_.STATE as STATE10_0_,
            bear2x0_1_.STREET_NAME as STREET3_10_0_,
            bear2x0_1_.STREET_NUMBER as STREET4_10_0_,
            bear2x0_2_.FIRST_NAME as FIRST1_9_0_,
            bear2x0_2_.LAST_NAME as LAST2_9_0_ 
        from
            ENTITYEX_BEAR2 bear2x0_ 
        left outer join
            ENTITYEX_BEAR2_ADDRESS bear2x0_1_ 
                on bear2x0_.id=bear2x0_1_.id 
        left outer join
            ENTITYEX_BEAR2_NAME bear2x0_2_ 
                on bear2x0_.id=bear2x0_2_.id 
        where
            bear2x0_.id=?
     

In this chapter we mapped multiple tables mapped thru a one-to-one primary key join into a single class. You will find this very similar to when we map a Java class inheritance hierachy to multiple tables using the JOIN strategy. However, in that case, each of the tables is mapped to a specific class within the hierarchy rather than a single class as we did here.