Enterprise Java Development@TOPIC@

Chapter 26. Mapping Class Properties

26.1. Map Entity to Specific Table
26.2. Using JPA Property Access
26.3. Summary

In this chapter we will create custom class/database mappings for some class properties

  1. Copy your Animal.java class to Cat.java

    package myorg.entityex.mapped;
    
    
    import java.util.Date;
    public class Cat {
        private int id;
        private String name;
        private Date dob;
        private double weight;
        
        public Cat() {} //must have default ctor
        public Cat(String name, Date dob, double weight) {
            this.name = name;
            this.dob = dob;
            this.weight = weight;
        }
        
        public int getId() { return id; }
    ...
  2. Copy your Animal2.java class to Cat2.java

    package myorg.entityex.annotated;
    
    
    import java.util.Date;
    @javax.persistence.Entity
    public class Cat2 {
        private int id;
        private String name;
        private Date dob;
        private double weight;
        
        public Cat2() {} //must have default ctor
        public Cat2(String name, Date dob, double weight) {
            this.name = name;
            this.dob = dob;
            this.weight = weight;
        }
        
        @javax.persistence.Id
        public int getId() { return id; }
    ...
  3. Name the new Cat entity class in the Animal-orm.xml

    
    # src/main/resources/orm/Animal-orm.xml

        <entity class="myorg.entityex.mapped.Animal">
    ...
        <entity class="myorg.entityex.mapped.Cat">
            <attributes>
                <id name="id"/>
            </attributes>
        </entity>
  4. Name the new Cat2 entity class in the persistence.xml

    
    # src/test/resources/META-INF/persistence.xml

            <mapping-file>orm/Animal-orm.xml</mapping-file>
            <class>myorg.entityex.Auto</class>
            <class>myorg.entityex.annotated.Animal2</class>
            <class>myorg.entityex.annotated.Cat2</class>
  5. Rebuild your module form the command line and observe the create schema generated for Cat and Cat2. Notice that the JPA provider used the class name as the default entity name and will be attempting to map the entity to a database table by the same name as the entity.

    $ more target/classes/ddl/*
    ...
       create table Cat (
            id integer not null,
            dob timestamp,
            name varchar(255),
            weight double not null,
            primary key (id)
        );
    
        create table Cat2 (
            id integer not null,
            dob timestamp,
            name varchar(255),
            weight double not null,
            primary key (id)
        );
  6. Add a table element to the orm.xml definition to map Cat to the ENTITYEX_CAT table.

    
    
        <entity class="myorg.entityex.mapped.Cat">
            <table name="ENTITYEX_CAT"/>
            <attributes>
  7. Add a @javax.persistence.Table annotation to the Cat2 class to map instances to the ENTITYEX_CAT table.

    @javax.persistence.Entity
    
    @javax.persistence.Table(name="ENTITYEX_CAT")
    public class Cat2 {
        private int id;
  8. Rebuild your module form the command line and observe the create schema generated for Cat and Cat2. Notice now that we have mapped two entity classes to the same table using a custom table name.

    $ more target/classes/ddl/*
    ...
       create table ENTITYEX_CAT (
            id integer not null,
            dob timestamp,
            name varchar(255),
            weight double not null,
            primary key (id)
        );
  9. Map the id property for both the Cat and Cat2 to the CAT_ID column. Also have the persistence provider automatically generate a value for the primary key during the persist(). The exercise will go into generated primary key types in more detaiu

        @javax.persistence.Id
    
        @javax.persistence.Column(name="CAT_ID")
        @javax.persistence.GeneratedValue
        private int id;
    
        <entity class="myorg.entityex.mapped.Cat">
            <table name="ENTITYEX_CAT"/>
            <attributes>
                <id name="id">
                    <column name="CAT_ID"/>
                    <generated-value/>
                </id>
            </attributes>
        </entity>
  10. Make the name column mandatory (nullable=false) and define the length of the string to be 20 characters. Note that these property assignments are only useful as documentation and generating schema. Many of the column properties are not used at runtime by the provider.

        @javax.persistence.Column(nullable=false, length=20)
    
        private String name;
    
                <basic name="name">
                    <column nullable="false" length="20"/>
                </basic>
  11. Have the weight column stored with a precision of 3 digits, with 1 digit (scale) to the right of the decimal place. You will need to change the datatype of the mapped property to BigDecimal to fully leverage this capability.

    # src/main/java/myorg/entityex/annotated/Cat2.java
    
    
        @javax.persistence.Column(precision=3, scale=1)  //10.2lbs
        private BigDecimal weight;
    ...
        public Cat2(String name, Date dob, BigDecimal weight) {
    ...
        public BigDecimal getWeight() { return weight; }
        public void setWeight(BigDecimal weight) {
    # src/main/java/myorg/entityex/mapped/Cat.java
    
    
        private BigDecimal weight;
    ...
        public Cat(String name, Date dob, BigDecimal weight) {
    ...
        public BigDecimal getWeight() { return weight; }
        public void setWeight(BigDecimal weight) {
    
    # src/main/resources/orm/Animal-orm.xml
                <basic name="weight">
                    <column precision="3" scale="1"/>
                </basic>
  12. Rebuild the module from the command line and observe the database schema generated generated for the ENTITEX_CAT table.

    # target/classes/ddl/entityEx-createJPA.ddl
    
        create table ENTITYEX_CAT (
            CAT_ID integer generated by default as identity,
            dob date,
            name varchar(20) not null,
            weight decimal(3,1),
            primary key (CAT_ID)
        );

    Notice how

In the above example, you used FIELD access to the property values. This is the preferred method if your business object attributes provide an accurate representation as to what should be stored in the database. FIELD access was chosen by the provider by the fact that our annotated class placed the @Id annotation on a Java attribute and not a Java getter().

# implies FIELD access


    @javax.persistence.Id
    @javax.persistence.Column(name="CAT_ID")
    @javax.persistence.GeneratedValue
    private int id;
...    
    public int getId() { return id; }

If moved the @Id property definitions to the getter(), then the access would have been switched to PROPERTY. That was how JPA 1.0 annotated classed worked and it was always one way or another.

# implies PROPERTY access


    private int id;
...    
    @javax.persistence.Id
    @javax.persistence.Column(name="CAT_ID")
    @javax.persistence.GeneratedValue
    public int getId() { return id; }

Since it was always one way or the other with JPA 1.0, the specification in the orm.xml file was placed on the root element of the entity


    <entity class="myorg.entityex.mapped.Cat"
        access="FIELD">

Starting with JPA 2.0, we can also make the specification more explicit (like the XML technique) with the addition of the @Access annotation

@javax.persistence.Access(javax.persistence.AccessType.FIELD)

public class Cat2 {

Although switching between FIELD and PROPERTY access was always a capability in JPA 1.0 -- JPA 2.0 added the ability to chose on a per-property basis. This is done by applying the @Access annotation to the getter() you want to have property access. In this section, we will continue to expose all our properties to the provider through FIELD access, but define a PROPERTY access for the "weight" property.

  1. Update the annotated Cat2 entity to store weight as a double and expose it to the provider as a BigDecimal.

        private double weight;
    
    ...
        @javax.persistence.Column(precision=3, scale=1)  //10.2lbs
        @javax.persistence.Access(javax.persistence.AccessType.PROPERTY)
        public BigDecimal getWeight() {
            return new BigDecimal(weight); 
        }
        public void setWeight(BigDecimal weight) {
            this.weight = weight==null ? 0 : weight.doubleValue();
        }
  2. Add a logger and some log statements to help identify the calls to the getter and setter methods

    # src/main/java/myorg/entityex/annotated/Cat2.java
    
    
        private static final Log logger = LogFactory.getLog(Cat2.class);
    ...
        public BigDecimal getWeight() {
            logger.debug("annotated.getWeight()");
            return new BigDecimal(weight); 
        }
        public void setWeight(BigDecimal weight) {
            logger.debug("annotated.setWeight()");
            this.weight = weight==null ? 0 : weight.doubleValue();
        }
  3. Add the following test method to your AnimalTest. By persisting the entity, we will force the provider to get properties from the entity. By clearing the persistence unit of the entity prior to executing the find, we will force the provider to instantiate a new entity instance and set the properties within the entity.

    # src/test/java/myorg/entityex/AnimalTest.java
    
    
        @Test
        public void testCreateCatAnnotated() {
            logger.info("testCreateCatAnnotated");
            myorg.entityex.annotated.Cat2 cat = new myorg.entityex.annotated.Cat2("fluffy", null, 99.9);
            em.persist(cat);                                                 //get provider to call getters
            em.flush(); em.detach(cat);
            cat = em.find(myorg.entityex.annotated.Cat2.class, cat.getId()); //get provider to call setters
        }
  4. Run your new test method and observe the calls to getWeight and setWeight printed.

    $ mvn clean test -Dtest=myorg.entityex.AnimalTest#testCreateCatAnnotated
    
    ...
    -testCreateCatAnnotated
     -annotated.getWeight() //<----------------
    Hibernate: 
        insert 
        into
            ENTITYEX_CAT
            (CAT_ID, dob, name, weight) 
        values
            (null, ?, ?, ?)
     -annotated.getWeight() //<----------------
     -annotated.getWeight() //<----------------
    Hibernate: 
        select
            cat2x0_.CAT_ID as CAT1_2_0_,
            cat2x0_.dob as dob2_0_,
            cat2x0_.name as name2_0_,
            cat2x0_.weight as weight2_0_ 
        from
            ENTITYEX_CAT cat2x0_ 
        where
            cat2x0_.CAT_ID=?
     -annotated.setWeight() //<----------------
  5. Make the same code changes for weight and debugging in the mapped entity class. These changes expose weight to the provider as a different type than it is stored locally. The debug will help use track the calls to the getter and setter.

    # src/main/java/myorg/entityex/mapped/Cat.java
    
    
    public class Cat {
        private static final Log logger = LogFactory.getLog(Cat.class);
    ...
        private double weight;
    ...
        public Cat(String name, Date dob, double weight) {
    ...
        public BigDecimal getWeight() {
            logger.debug("mapped.getWeight()");
            return new BigDecimal(weight); 
        }
        public void setWeight(BigDecimal weight) {
            logger.debug("mapped.setWeight()");
            this.weight = weight==null ? 0 : weight.doubleValue();
        }
  6. Add the following test method to your JUnit class.

        @Test
    
        public void testCreateCatMapped() {
            logger.info("testCreateCatMapped");
            myorg.entityex.mapped.Cat cat = new myorg.entityex.mapped.Cat("fluffy", null, 99.9);
            em.persist(cat);                                                 //get provider to call getters
            em.flush(); em.detach(cat);
            cat = em.find(myorg.entityex.mapped.Cat.class, cat.getId()); //get provider to call setters
        }
  7. Add the access="PROPERTY" to the weight definition within the orm.xml

    
                <basic name="weight" access="PROPERTY">
                    <column precision="3" scale="1"/>
                </basic>
  8. Build and run the test for the mapped version of the class. Look for the debug statements coming from the getter and setter

     -testCreateCatMapped
     -mapped.getWeight() //<----------------
    Hibernate: 
        insert 
        into
            ENTITYEX_CAT
            (CAT_ID, dob, name, weight) 
        values
            (null, ?, ?, ?)
     -mapped.getWeight() //<----------------
     -mapped.getWeight() //<----------------
    Hibernate: 
        select
            cat0_.CAT_ID as CAT1_2_0_,
            cat0_.dob as dob2_0_,
            cat0_.name as name2_0_,
            cat0_.weight as weight2_0_ 
        from
            ENTITYEX_CAT cat0_ 
        where
            cat0_.CAT_ID=?
     -mapped.setWeight() //<----------------

In this chapter you performed some core class/database table mappings that allowed you to

You also had a chance to change the provider access from FIELD to PROPERTY either for the entire entity or on a per-property basis.