Enterprise Java Development@TOPIC@

Chapter 31. Mapping Compound Primary Keys

31.1. Using Embedded Compound Primary Keys
31.2. Using Compound Primary Keys as IdClass
31.3. Summary

This chapter will take you through mapping a set of two or more natural fields as a compound primary key. Nothing is being generated with natural/compound keys. We are combining multiple fields to represent the object's identity.

A compound primary key is required to...

Embedded compound primary keys are primary key classes that are integrated into the using class in its aggregate form. The using class makes only a reference to the primary key class and not to any of the primary key class properties.

  1. Create an instance of the following JPA primary key class in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import java.io.Serializable;
    import javax.persistence.*;
    @Embeddable
    public class CowPK implements Serializable { //required to be Serializable
        private static final long serialVersionUID = 1L;
        private String herd;
        private String name;
        
        public CowPK(){} //required default ctor
        public CowPK(String herd, String name) {
            this.herd = herd;
            this.name = name;
        };
        
        public String getHerd() { return herd; }
        public String getName() { return name; }
        
        @Override
        public int hashCode() { //required hashCode method
            return herd.hashCode() + name.hashCode();
        } 
        
        @Override
        public boolean equals(Object obj) { //required equals method
            try {
                return herd.equals(((CowPK)obj).herd) && 
                        name.equals(((CowPK)obj).name);
            } catch (Exception ex) {
                return false;
            }
        }
    }

    Note the class is...

    Note the class also...

  2. Add the following entity class to the src/main tree. This class will declare an @EmbdeddedId and no @Id property. It will also use FIELD level access to properties.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENITYEX_COW")
    public class Cow {
        @EmbeddedId
        private CowPK pk;
        private int weight;
        public Cow() {}
        public Cow(CowPK cowPK) {
            this.pk = cowPK;
        }
        
        public CowPK getPk() { return pk; }
        public void setPk(CowPK pk) {
            this.pk = pk;
        }
        
        public int getWeight() { return weight; }
        public void setWeight(int weight) {
            this.weight = weight;
        }
    }

    Note the entity class makes wholesale use of the embedded primary key class and is not required to have any interaction with the properties of the embedded class.

  3. Add the new entity class to your persistence unit.

    
    
            <class>myorg.entityex.annotated.Cow</class>
  4. Build the module with the new entity and primary key class. Note the schema that is produced. The herd and name from the primary key class have been integrated into the using class' table.

        create table Cow (
            herd varchar(255) not null,
            name varchar(255) not null,
            weight integer not null,
            primary key (herd, name)
        );
    
  5. Notice in the above database schema that the ID columns are set to their default values. We can set the properties from either the primary key class or within the using entity class. Add the following to your two classes to help define the database columns used by the primary key class.

    
    
        @Column(name="HERD", length=16)
        private String herd;
    
    
        @EmbeddedId
        @AttributeOverrides({
            @AttributeOverride(name="name", column=@Column(name="NAME", length=16))
        })
        private CowPK pk;

    Note the technique used for the "herd" is to annotate the property directly within the primary key class. This is useful when the primary key class is dedicated for use by a single entity class or the annotations are common across entity classes. The technique used for "name" is to annotate the primary key property of the entity class using an @AttributeOverrride. Note there can only be a single @AnnotationOverride per property -- so we show the use of the @AttrinbuteOverrides({}) annotation to hold one or more @AttributeOverride elements.

  6. Rebuild the module and note the generated database schema produced. The herd and name columns have been constrained to 16 characters. The names of the columns are also showing up as CAPITALIZED since that is how we typed them in the @Column.name mapping.

        create table Cow (
            HERD varchar(16) not null,
            NAME varchar(16) not null,
            weight integer not null,
            primary key (HERD, NAME)
        );
    
  7. Add the following test method to your JUnit test case

    
    
        @Test
        public void testEmbeddedId() {
            log.info("testEmbedded");
            Cow cow = new Cow(new CowPK("Ponderosa", "Bessie"));
            cow.setWeight(900);
            em.persist(cow);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(cow);
            Cow cow2 = em.find(Cow.class, new CowPK("Ponderosa", "Bessie"));
            assertNotNull("cow not found", cow2);
            assertEquals("unexpected herd", cow.getPk().getHerd(), cow2.getPk().getHerd());
            assertEquals("unexpected name", cow.getPk().getName(), cow2.getPk().getName());
            assertEquals("unexpected weight", cow.getWeight(), cow2.getWeight());       
        }

    Note how an instance of the compound primary key class is passed to the find() method to locate the entity class by primary key.

  8. Rebuild and test your module with the new test method in place. You should notice the following information within the database when complete.

    $ mvn clean test -Ph2srv -P\!h2db
    ...
     -testEmbedded
    Hibernate: 
        insert 
        into
            ENITYEX_COW
            (weight, HERD, NAME) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            cow0_.HERD as HERD5_0_,
            cow0_.NAME as NAME5_0_,
            cow0_.weight as weight5_0_ 
        from
            ENITYEX_COW cow0_ 
        where
            cow0_.HERD=? 
            and cow0_.NAME=?
    ...
    [INFO] BUILD SUCCESS
    
    
    SELECT * FROM ENITYEX_COW;
    HERD    NAME    WEIGHT  
    Ponderosa   Bessie  900
    

In this section you have modeled an embedded class as a compound primary key class that has encapsulated all properties within the primary key class.

In this section we will address the case were the entity class wishes to module one or more of the primary key properties as direct properties of the entity class.

  1. Put the following entity class in place in your src/main tree.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENITYEX_COW2")
    @IdClass(CowPK.class)
    @AttributeOverrides({
        @AttributeOverride(name="name", column=@Column(name="NAME", length=16)) 
    })
    public class Cow2 {
        @Id
        private String herd;
        @Id
        private String name;
        private int weight;
        public Cow2() {}
        public Cow2(String herd, String name) {
            this.herd = herd;
            this.name = name;
        }
        
        public String getHerd() { return herd; }
        public String getName() { return name; }
        public int getWeight() { return weight; }
        public void setWeight(int weight) {
            this.weight = weight;
        }
    }

    Note this class...

    This class also happens to...

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

    
    
            <class>myorg.entityex.annotated.Cow2</class>
  3. Build the module and not the generated database schema is identical to the embedded primary key mapping we created in the previous section).

        create table ENITYEX_COW2 (
            HERD varchar(16) not null,
            NAME varchar(16) not null,
            weight integer not null,
            primary key (HERD, NAME)
        );
    
  4. Add the following test method for the new entity class to your existing Junit test case.

        @Test
        public void testIdClass() {
            log.info("testIdClass");
            Cow2 cow = new Cow2("Ponderosa", "Bessie");
            cow.setWeight(900);
            em.persist(cow);
            
            //flush to DB and get a new instance
            em.flush(); em.detach(cow);
            Cow2 cow2 = em.find(Cow2.class, new CowPK("Ponderosa", "Bessie"));
            assertNotNull("cow not found", cow2);
            assertEquals("unexpected herd", cow.getHerd(), cow2.getHerd());
            assertEquals("unexpected name", cow.getName(), cow2.getName());
            assertEquals("unexpected weight", cow.getWeight(), cow2.getWeight());       
        }
    

    Note how the use of the primary key is the same when interfacing with the entitymanager. The only real difference is that the entity deals directly with the properties of the primary key class and not the primary key class itself.

  5. Rebuild the module with the new test method. You should get the following in the database when complete.

    $ mvn clean test -Ph2srv -P\!h2db
    ...
     -testIdClass
    Hibernate: 
        insert 
        into
            ENITYEX_COW2
            (weight, HERD, NAME) 
        values
            (?, ?, ?)
    Hibernate: 
        select
            cow2x0_.HERD as HERD6_0_,
            cow2x0_.NAME as NAME6_0_,
            cow2x0_.weight as weight6_0_ 
        from
            ENITYEX_COW2 cow2x0_ 
        where
            cow2x0_.HERD=? 
            and cow2x0_.NAME=?
    ...
    [INFO] BUILD SUCCESS
    
    
    SELECT * FROM ENITYEX_COW2;
    HERD    NAME    WEIGHT  
    Ponderosa   Bessie  900
    

In this section you successfully mapped a compound primary key class as an IdClass for an entity where multiple @Id properties were directly exposed by the entity. The IdClass declared properties that were identical to the number and name of @Id properties within the entity class.

In this chapter we worked with primary keys made up of multiple values. There were two styles of usage for the primary key class; embedded and idclass. For the embedded case, the entity worked with the primary key class directly and never the primary key class properties. In the idclass case, the entity class declared properties that matched the idclass (or vice-versa) but never interacted with the primary key class. We showed how both mappings to the database were identical and how to define custom mappings for the targeted database schema.