Entity (JPA Annotated POJO)

How to annotate and transform your POJO to a JPA Entity Object

  1. How to create a JPA Entity from a data-base table definition
  2. How to create a data-base table from a JPA Entity
  3. Do I annotate the field or property accessors (Field vs. Property Access)
  4. How to implement and handle data-base ID Generation (Primary Key IDs)
  5. ID Generation Database Table Design/Requirements
  6. How to handle relationships between entities/tables (Primary/Foriegn Keys)
  7. (TODO) How to handle Indexed Tables
  8. How to map tables to collections (Collection Mapping)
  9. How to use an entity with a specific entity manager
  10. Can I share my generated entities with others as a service

Architecture Overview

How/Where an Entity Fits Overall

Entity

  • JPA recognizes two types of persistent classes: entity classes and embeddable classes. Each persistent instance of an entity class - each entity - represents a unique datastore record. You can use the EntityManager to find an entity by its persistent identity, or use a Query to find entities matching certain criteria.
  • Serializable; usable as detached objects in other tiers
    • No need for data transfer objects
  • Can extend other entity and non-entity classes

Best Practices & How To's

What package does JPA API reside on

  • Only use available API under this package
    • *
      import javax.persistence.*;
      *
      

Entity Generation from data-base or vice-versa

Entity Generation:use Netbeans or Eclipse EE built in JPA tools

Implemntation of Entity's equals(Object obj) and hashCode()

equals(Object obj) and hashCode()

  • The main purpose of the IdClass is to be used as the structure passed to the EntityManager find() and getReference() API. Some JPA products also use theIdClass as a cache key to track an object's identity. Because of this, it is required (depending on JPA product) to implement an equals() and hashCode()method on the IdClass. Ensure that the equals() method checks each part of the primary key, and correctly uses equals for objects and == for primitives. Ensure that the hashCode() method will return the same value for two equal objects.
  • Read the following article about the importance of equals and hashCode and best practices: Equals and HashCode

Field access vs Property access in JPA

Field access vs Property access in JPA:

  • JPA allows for two types of access to the data of a persistent class. 
    • Field access which means that it maps the instance variables (fields) to columns in the database and
    • Property access which means that is uses the getters to determine the property names that will be mapped to the db. What access type it will be used is decided by where you put the @Id annotation (on the id field or the getId() method).
  • Which to use:
    • Field Access (recommended):
        • Field access makes your life much easier when it comes to auto setter/getter generation by your IDE, i.e. often programmers change field names and hence recommending field access as one of the reasons
        • because we frequently define getter methods that are not property accessors e.g. a getter that does not just return the value of a field but does a calculation and returns the result
        • 2nd reason is that with property access you have to define getter methods for every field just for use by JPA even if your code will never call them

Entity Identity

  • Every entity has a persistence identity
    • Maps to primary key in database
  • Can correspond to simple type
  • @Id—single field/property in entity class
  • @GeneratedValue—value can be generated automatically using various strategies (SEQUENCE, TABLE, IDENTITY, AUTO)
  • Can correspond to user-defined class
    • @EmbeddedId—single field/property in entity class
    • @IdClass—corresponds to multiple Id fields in entity class
  • Must be defined on root of entity hierarchy or mapped superclass
  • We recommend letting JPA to take care of ID generation for your entities via "TABLE" strategy
  • Please refer to the "TableGenerator" section for a better explanation
  • i.e.
    • *
      @GeneratedValue(strategy = GenerationType.TABLE, generator = "YOUR_GENERATOR_NAME")
      *
      

@Embeddable and @Embeddedexplanation

@Embeddable and @Embedded

  • an Embeddable object is different from an entity in that there is no corresponding data-base table or ID in that object
  • an Embeddable object can not be used for quering or persistence purposes
  • and Embeddable object can be embedded into an entity using @Embedded annotation
  • for a full explaination please see Java Persistence/Embeddables - Wikibooks

@EmbeddedId

@EmbeddedId: used to define a composite ID for an entity, use @Id otherwise

Primary Keys

Primary Keys: Entity Field(s) Annotated via @Id or @EmbeddedId

Entity Relationships

Entity Relationships: JPA offers the following relationships between entities

Join Strategies

Join Strategies: once the entity relationships are determined

  • You can use annotations such as @JoinColumn, @JoinTable, @PrimaryKeyJoinColumn, ... to define join columns further if needed
  • See: Java Persistence/Relationships

Inheritance Strategies

Inheritance Strategies:

  • Luckily, JPA gives you a choice of inheritance strategies, making the best of a bad situation
      • Single Table @Inheritance(strategy=InheritanceType.JOINED)
      • Joined @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
      • Table Per Class @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
      • for a list of advantages vs. disadvantages See: openJPA Docs and Java Persistence/Inheritance

Entity

InheritanceType.JOINED:

  • JPA Specification does not REQUIRE the use of a @DiscriminatorColumn
  • most JPA Vendors require the use of it such as EclipseLink and openJPA and some do not such as Hibernate
  • when using Hibernate as a JPA vendor use of @DiscriminatorColumn is ignored but please define such a column in the data-base and also in your entity for portability and querying purposes.
    • *
      ...
      @Entity
      @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
      @DiscriminatorColumn(name="TRANSACTION_TYPE_CODE",discriminatorType=DiscriminatorType.STRING)
      @Table(schema = "COST_SHARE", name = "CS_TRANSACTION")
      public class CsTransaction implements Serializable {
      ...
      *
      

 @Enumerated fields (Ordinal vs. String)

  • EnumType.ORDINAL: Ordinal (default)
    • In Java enumerated types are defined implicitly by the order of declaration. You can modify the order at runtime.
    • The ordinal values (Integer) can be saved in a data-base table to be mapped by JPA 
  • EnumType.STRING: String (recommended)
    • In Java enumerated types are defined implicitly by the order of declaration. You can modify the order at runtime.
    • The ordinal values (Integer) can be saved in a data-base table to be mapped by JPA 
  • Why use STRING over ORDINAL
    • Using an ordinal value might (and most likely will) cause problems in the future, for example 
      • if the order of the Enum changes by mistake
      • if the business logic changes, it is much harder to change than if we were using the EnumType.STRING
        • say if in the example below, if transaction type 'L' was to be removed or if we had to add a new type and we were using ORDINAL (which would correspond to number 2), We could go through the database and adjust all the entities to have their correct type, but if the transaction type is used elsewhere, then we would need to make sure that they were all fixed as well. This is not a good maintenance situation to be in.
        • A better solution would be to store the name of the value as a string instead of storing the ordinal. This would isolate us from any changes in declaration and allow us to add new types without having to worry about the existing data. We can do this by adding an annotation on the attribute and specifying a value of STRING.

  • Example of EnumType.STRING
    • Enum Definition
      • *
        public enum EnumCsTransactionType {
             P,L,M
        }
        *
        
    • Enum Usage
      • *
        @Enumerated(EnumType.STRING)
        EnumCsTransactionType transactionTypeCode;
        *
        
    • Now the Corresponding Database Table Column should be defined as String rather than int

MetaModel Generation

  • See Hibernate JPA 2 Metamodel Generator
  • JPA 2 defines a new typesafe Criteria API which allows criteria queries to be constructed in a strongly-typed manner, using metamodel objects to provide type safety. For developers it is important that the task of the metamodel generation can be automated. Hibernate Metamodel Generator is an annotation processor based on the  Pluggable Annotation Processing API with the task of creating JPA 2 static metamodel classes.
    • You can setup eclipse or netbeans with an annotation processor so all the MetaModels for your entities are automatically generated
    • The following example shows how the metamodel class for the class Order looks like:
      • *@Entity
        public class Order {
        @Id 
        @GeneratedValue
        Integer id; 
        @ManyToOne 
        Customer customer; 
        @OneToMany 
        Set<Item> items;
        BigDecimal totalCost; 
        // standard setter/getter methods
        }
        *
        
      • Generated Class

        *
        @StaticMetamodel(Order.class)
        public class Order_ {
        public static volatile SingularAttribute<Order, Integer> id;
        public static volatile SingularAttribute<Order, Customer> customer;
        public static volatile SetAttribute<Order, Item> items;
        public static volatile SingularAttribute<Order, BigDecimal> totalCost;
        }
        
      • Now you can use Order_.id in your code (CriteriaAPI) rather than referring to the column name directly i.e. "id"

        • *
          ...
          Root<Order> orderRoot = subQueryIndx.from(Order.class);
          ...
          //NOT Using MetaModel
          queryOrderPredicatesList.add(criteriaBuilder.equal(orderRoot.<Integer>get("id"), 1 ));
          //Using MetaModel
          queryOrderPredicatesList.add(criteriaBuilder.equal(Order_.id, 1 ));
          ...
          *
          

ID Generation (@TableGenerator: use this rather than Sequences or DB Identity Columns) 

Table ID Generation read, please read Hibernate/JPA Identity Generators 

  • JPA provides 3 options:
    • 1)Identity: generated and tracked by the database

      • Cons:
        • Non-portable: Database dependent
        • Slow, batch inserts are not possible
        • No ID pre-allocation is possible
        • Hard to define identity column on pre-existing table columns (usually an alter will not work)
        • Pros:
          • none

      2)Sequence: done by the database

      • Cons:
        • Non-portable: Database dependent
        • Requires creation of Sequences
        • Pros:
          • Good Performance like Table Generators (see 3)
          • ID allocation is possible

      3)(recommended) Table: done by the JPA Vendor (Hibernate in our case)

      • Cons:
        • Requires creation of ONE table to hold the sequence names
        • Pros:
          • Portable: Database Independent
          • Great Performance
          • ID allocation is possible
          • Works on existing tables
          • Clusters - good

@TableGenerator (recommended ID generation)

  • @TableGenerator Usage Example:
    • Annotation Usage/Options
      • *
         
        @Column(name = "TRANSACTION_ID", nullable = false)
        @Id
        @TableGenerator(name = "CS_TRANSACTION_GEN", 
        schema="COST_SHARE"
        ,table = "CS_ID_GEN" 
        ,pkColumnName = "SEQUENCE_NAME" 
        ,valueColumnName = "SEQUENCE_NEXT_HI_VALUE" 
        ,pkColumnValue = "CS_TRANSACTION_GEN" 
        ,initialValue = 0
        ,allocationSize = 1)
        @GeneratedValue(strategy = GenerationType.TABLE, generator = "CS_TRANSACTION_GEN") 
        Integer transactionId;
        *
        
    • Data-base table
      • content example (see multiple generators in one table
    • note that every vendor has default column names
      • for portability reasons please specify pkColumnName and valueColumnName in your annoration
    • allocationSize: 
      • adjust allocationSize as appropriate
      • for example if you will be inserting lots of rows at once increase this number accordingly
      • This increases performance as JPA pre-allocates ids without going to the data-base for every insert
  • Note
    • You do NOT have to create multiple tables for various generators
    • Good practices are to create a table generator per application or schema depending on your applications requirments

@Transient fields

@Transient fields: non-persistent state

  • If you need to include a column in your Entity that is not part of the data-base, annotate it with @Transient keyword
  • The annotated entity within the Entity will be ignored by JPA when persisting data to/from data-base
  • *
    
    @Transient
    
    Contact contact = new Contact();
    
    
    *
    

Governance

  • As always Code Reviews should take place on your code/design
  • When possible follow the content of this article
  • Let JPA handle your db table ID generation (use @TableGenerator)
  • implement entities equal and hashCode methods
  • Define appropriate relationships between your entities
    • define primary and foreign keys on the data-base tables
  • Only use JPA API 
    •  import javax.persistence.*;
      
  • Do not use JPA Vendors propertiy propriety API 
    • for example anything under the following package is considered propertiy 
      • import org.hibernate.*;
        

Unit Testing

  • Please refer to the unit-testing section

Advanced

Consult books and JPA documentation for advanced topics such as

  • @PostLoad
  • @PostPersist
  • and more ..........

References