Defining useful EJB3 entities and relationships

This topic is critical because a well-designed mapping prevents errors, saves a lot of time and has a big impact on performance.

Getting ready

In this section, we are going to present most of the Entities that we needed for the application. A couple of implementation techniques (from inheritance types to relationship cases) have been chosen here and highlighted for example purposes.

The How it works… section will explain why and how things are defined in the way they are and what were the thoughts that drove us toward the Entities' definitions we made.

How to do it...

The following steps will help you create Entities in the application:

  1. All the changes from this recipe are located in the new package edu.zipcloud.cloudstreetmarket.core.entities. First, created three simple entities as shown here:
    • The User entity:
        @Entity
        @Table(name="user")
        public class User implements Serializable{
          private static final long serialVersionUID = 1990856213905768044L;
          @Id
          @Column(nullable = false)
          private String loginName;
          private String password;
          private String profileImg;
      
        @OneToMany(mappedBy="user", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
        @OrderBy("id desc")
        private Set<Transaction> transactions = new LinkedHashSet< >();
        ...
        }
    • The Transaction entity:
        @Entity
        @Table(name="transaction")
        public class Transaction implements Serializable{
          private static final long serialVersionUID = -6433721069248439324L;
          @Id
          @GeneratedValue
          private int id;
      
          @ManyToOne(fetch = FetchType.EAGER)
          @JoinColumn(name = "user_name")
          private User user;
      
          @Enumerated(EnumType.STRING)
          private Action type;
        
          @OneToOne(fetch = FetchType.EAGER)
          @JoinColumn(name = "stock_quote_id")
          private StockQuote quote;
          private int quantity;
        ...
        }
    • And the Market entity:
        @Entity
        @Table(name="market")
        public class Market implements Serializable {
          private static final long serialVersionUID = -6433721069248439324L;
          @Id
        private String id;
        private String name;
          
        @OneToMany(mappedBy = "market", cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
        private Set<Index> indices = new LinkedHashSet<>();
        ...
        }
  2. Then, we have created some more complex entity Types such as the abstract Historic entity:
    @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "historic_type")
    @Table(name="historic")
    public abstract class Historic {
      
      private static final long serialVersionUID = -802306391915956578L;
    
      @Id
      @GeneratedValue
      private int id;
    
      private double open;
      
      private double high;
      
      private double low;
      
      private double close;
      
      private double volume;
    
      @Column(name="adj_close")
      private double adjClose;
    
      @Column(name="change_percent")
      private double changePercent;
      
      @Temporal(TemporalType.TIMESTAMP)
      @Column(name="from_date")
      private Date fromDate;
    
      @Temporal(TemporalType.TIMESTAMP)
      @Column(name="to_date")
      private Date toDate;
      
      @Enumerated(EnumType.STRING)
      @Column(name="interval")
    private QuotesInterval interval;
    ...
      }

    We have also created the two Historic subtypes, HistoricalIndex and HistoricalStock:

      @Entity
      @DiscriminatorValue("idx")
      public class HistoricalIndex extends Historic implements Serializable {
    
      private static final long serialVersionUID = -802306391915956578L;
    
      @ManyToOne(fetch = FetchType.EAGER)
      @JoinColumn(name = "index_code")
      private Index index;
    ...
    }
    @Entity
    @DiscriminatorValue("stk")
    public class HistoricalStock extends Historic implements Serializable {
    
      private static final long serialVersionUID = -802306391915956578L;
    
      @ManyToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "stock_code")
      private StockProduct stock;
    
      private double bid;
      private double ask;
      ...
        }
  3. Then, we also created the Product entity with its StockProduct subtypes:
        @Entity
        @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
        public abstract class Product {
          private static final long serialVersionUID = -    802306391915956578L;
          @Id
          private String code;
          private String name;
          ...
        }
        
        @Entity
        @Table(name="stock")
        public class StockProduct extends Product implements Serializable{
          private static final long serialVersionUID = 1620238240796817290L;
          private String currency;
          @ManyToOne(fetch = FetchType.EAGER)
          @JoinColumn(name = "market_id")
          private Market market;
          ...
        }
  4. In reality, in the financial world, an index (S&P 500 or NASDAQ) cannot be bought as such; therefore, indices haven’t been considered as products:
    @Entity
    @Table(name="index_value")
    public class Index implements Serializable{
      private static final long serialVersionUID = -2919348303931939346L;
      @Id
      private String code;
      private String name;
    
      @ManyToOne(fetch = FetchType.EAGER)
      @JoinColumn(name = "market_id", nullable=true)
      private Market market;
    
      @ManyToMany(fetch = FetchType.LAZY)
      @JoinTable(name = "stock_indices", joinColumns={@JoinColumn(name = "index_code")}, inverseJoinColumns={@JoinColumn(name ="stock_code")})
      private Set<StockProduct> stocks = new LinkedHashSet<>();
      ...
    }
  5. Finally, the Quote abstract entity with its two subtypes, StockQuote and IndexQuote, have created (indices are not products, but we can still get instant snapshots from them, and the Yahoo! financial data provider will later be called to get these instant quotes):
    @Entity
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public abstract class Quote {
      @Id
      @GeneratedValue(strategy = GenerationType.TABLE)
      protected Integer id;
      private Date date;
      private double open;
     
      @Column(name = "previous_close")
      private double previousClose;
      private double last;
      ...
    }
    
    @Entity
    @Table(name="stock_quote")
    public class StockQuote extends Quote implements Serializable{
      private static final long serialVersionUID = -8175317254623555447L;
      @ManyToOne(fetch = FetchType.EAGER)
      @JoinColumn(name = "stock_code")
      private StockProduct stock;
      private double bid;
      private double ask;
      ...
    }
    
    @Entity
    @Table(name="index_quote")
    public class IndexQuote extends Quote implements Serializable{
      private static final long serialVersionUID = -8175317254623555447L;
    
      @ManyToOne(fetch = FetchType.EAGER)
      @JoinColumn(name = "index_code")
      private Index index;
      ...
    }

How it works...

We are going to go through some basic and more advanced concepts that we have used to build our relational mapping.

Entity requirements

An entity, to be considered as such by the API requires the following conditions:

  • It has to be annotated on The type level with the @Entity annotation.
  • It needs to have a defined identifier with either a basic or a complex type. In most cases, a basic identifier is sufficient (the @Id annotation on a specific entity field).
  • It must be defined as public and not declared as final.
  • It needs to have a default constructor (implicit or not).

Mapping the schema

Both databases and Java objects have specific concepts. The metadata annotations for Entities, along with the configuration by default, describe the relational mapping.

Mapping tables

An entity class maps a table. Not specifying a @Table(name="xxx") annotation on the Type level will map the entity class to the table named with the entity name (this is the default naming).

Note

The Java's class-naming standard is CamelCased with a capital case for the first letter. This naming scheme doesn't really match the database table-naming standards. For this reason, the @Table annotation is often used.

The @Table annotation also has an optional schema attribute, which allows us to bind the table to a schema in the SQL queries (for example public.user.ID). This schema attribute will override the default schema JPA property, which can be defined on the persistence unit.

Mapping columns

As with the table names, the column name to map a field to is specified with the @Column(name="xxx") annotation. Again, this annotation is optional, and not specifying it will make the mapping fall back to the default naming scheme, which is literally the cased name of the field (in case of single words, it is often a good option).

The fields of an entity class must not be defined as public. Also keep in mind that you can almost persist all the standard Java Types (primitive Types, wrappers, Strings, Bytes or Character arrays, and enumerated) and large numeric Types, such as BigDecimals or BigIntegers, but also JDBC temporal types (java.sql.Date, java.sql.TimeStamp) and even serializable objects.

Annotating fields or getters

The fields of an entity (if not tagged as @Transient) correspond to the values that the database row will have for each column. A column mapping can also be defined from a getter (without necessarily having a corresponding field).

The @Id annotation defines the entity identifier. Also, defining this @Id annotation on a field or getter defines whether the table columns should be mapped by a field or on a by getters.

When using a getter access mode, and when a @Column annotation is not specified, the default naming scheme for the column name uses the JavaBeans property naming standard (for example, the getUser() getter would correspond to the user column).

Mapping primary keys

As we have seen already, the @Id annotation defines the entity's identifier. A persistence context will always manage no more than one instance of an entity with a single identifier.

The @Id annotation on an entity class must map the persistent identifier for a table, which is the primary key.

Identifier generation

A @GeneratedValue annotation allows ID generation from the JPA level. This value may not be populated until the object is persisted.

A @GeneratedValue annotation has a strategy attribute that is used to configure the generation method (to rely, for example, on existing database sequences).

Defining inheritance

We have defined entity inheritance for subtypes of Products, Historics, and Quotes. When two Entities are close enough to be grouped into a single concept, and if they actually can be associated with a parent entity in the application, it is worth using the JPA inheritance.

Depending upon the persistence strategy for specific data, different storage options can be considered for inheritance mapping.

The JPA allows us to configure an inheritance model from different strategies.

The single-table strategy

This strategy expects or creates one big table with a discriminator field on the schema. This table hosts the parent-entity fields; these are common to all subentities. It also hosts all the fields of subentity classes. Consequently, if an entity corresponds to one subtype or another, it will populate the specific fields and leave the others blank.

The following table represents the Historic table with its HISTORIC_TYPE discriminator:

The single-table strategy

The table-per-class strategy

This strategy uses specific tables for concrete Entities. There is no discriminator involved here, just specific tables for subtypes. These tables carry both common and specific fields.

We have, for example, implemented this strategy for the Quote entity and its concrete StockQuote and IndexQuote Entities:

The table-per-class strategy

Defining relationships

Entities have the capability to reflect database foreign keys and table to table relationships in their class attributes.

On the application side, because these relationships are built transparently by the entity managers, a huge amount of developments are bypassed.

How relationships between entities have been chosen

Before talking about relationships between entities, it is necessary to understand what we plan to do in the cloudstreet-market application.

As introduced in Chapter 1, Setup Routine for an Enterprise Spring Application, we will pull financial data from providers that open their APIs (Yahoo! actually). To do so, there are always limitations to keep in mind in terms of call frequency per IP or per authenticated user. Our application will also have its community inside of which financial data will be shared. For financial data providers, when talking about a given stock, the historical view of a stock and an instant quote of a stock are two different things. We had to deal with these two concepts to build our own data set.

In our application, Users will be able to buy and sell Products (stock, fund, option, and so on) by executing Transactions:

  • First, let's consider the User(s)/Transaction(s) relationship with the following screenshot:
    How relationships between entities have been chosen
  • A User entity can have many Transactions Entities.

    Note

    In the User class, the second part of the @OneToMany relationship annotation (the Many element) drives the Type of attribute we are creating. Specifying Many as the second part declares that the origin entity (User) can have several target Entities (Transactions). These targets will have to be wrapped in a collection type. If the origin entity cannot have several targets, then the second part of the relationship has to be One.

  • A Transaction can have only one User entity.

    Note

    Still in the User class, the first part of the @OneToMany relationship (the @One element) is the second part of the relationship annotation defined in the target entity (if defined). It is necessary to know whether the target entity can have several origins or not, in order to complete the annotation in the origin.

  • We can then deduce the two annotations: @OneToMany in User and @ManyToOne in Transactions.
  • If we are not in the case of a @ManyToMany relationship, we are talking about a unidirectional relationships. From a database's point of view, this means that one of the two tables having a join column that targets the other table. In the JPA, the table that has this join column is the relationship's owner.

    Tip

    The entity, which is the relationship's owner has to be specified by a @JoinColumn annotation on the relationship. The entity that is not the owner, has to provide for its relationship annotation a mappedBy attribute that targets the corresponding Java field name in the opposite entity.

  • This can now explain the relationship in Transaction:
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_name")
    private User user;

    The user_name column is expected (or automatically added) in the transaction table. We will talk about the fetch type later in the There’s more… section.

  • The relationship in the User entity is defined as follows:
      @OneToMany(mappedBy="user", cascade ={CascadeType.ALL}, fetch = FetchType.LAZY)
      @OrderBy("id desc")
      private Set<Transaction> transactions = new LinkedHashSet<>();

Tip

The @OrderBy annotation tells the JPA implementation to add an ORDER BY clause to its SQL query.

An Index entity has one Market entity. We have decided that a market is the geographical area (Europe, the US, Asia, and so on). A market has several concrete indices.

This looks like a @OneToMany/@ManyToOne relation again. The relationship's owner is the Index entity because we expect to have a Market column in the Index table (and not an Index column in the Market table).

It is the same story between the concrete Product (such as StockProduct) and Market entities, except that, since it doesn't look mandatory in the application to retrieve stocks directly from Market, the relationship has not been declared on the Market entity side. We have kept only the owner side.

About the concrete Quotes entity (such as StockQuote) and the concrete Products entity (such as StockProduct), one quote will have one product. If we were interested in retrieving Quote from a Product entity, one product would have many quotes. The owner of the relationship is the concrete Quote entity.

It is the same logic as the previous point for IndexQuote and Index.

Between Index and StockProduct, in reality, indices (S&P 500, NASDAQ, and so on) have constituents, and the sum of the constituents' values makes the index value. Thus, one Index entity has several potential StockProduct entities. Also one StockProduct can belong to several Indices. This looks like a bidirectional relationship. We present here the Index side:

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "stock_indices", joinColumns={@JoinColumn(name = "index_code")}, inverseJoinColumns={@JoinColumn(name ="stock_code")})
private Set<StockProduct> stocks = new LinkedHashSet<>();

This relationship is specified an extra join table (expected or generated by the JPA). It is basically a table with two join columns pointing to the @Ids fields of the respective entities.

There's more...

We are going to visit two metadata attributes that we didn’t explain yet: the FetchType attribute and the Cascade attribute.

The FetchType attribute

We have seen that the relationship annotations @OneToOne, @OneToMany, and @ManyToMany can be specified in a fetch attribute, which can either be FetchType.EAGER or FetchType.LAZY.

When a FetchType.EAGER attribute is chosen, relationships are automatically loaded by the entityManager when the entity gets managed. The overall amount of SQL queries executed by JPA is significantly increased, especially because some related entities that may not be required each time are loaded anyway. If we have two, three, or more levels of entities bound to a root entity, we should probably consider switching some fields locally to FetchType.LAZY.

A FetchType.LAZY attribute specifies the JPA implementation not to populate a field value on the entity-loading SQL query. The JPA implementation generates extra-asynchronous SQL queries to populate the LAZY fields when the program specifically asks for it (for example, when getStock() is called in the case of a HistoricalStock entity). When using Hibernate as an implementation, FetchType.LAZY is taken as the default fetch type for relationships.

It is important to think about lightening the relationship loading, especially on collections.

The Cascade attribute

Another attribute to be mentioned in relationship annotations is the Cascade attribute. This attribute can take the values CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE, and CascadeType.ALL.

This attribute specifies how the JPA implementation should process the related Entities when asked to perform an operation (such as persist, update, delete, find, and so on.) on the main Entity. It is an optional attribute which is usually defaulted to no-cascaded operations.

See also

There is a third strategy to define Entity inheritance:

  • The joined-table inheritance strategy: We haven't implemented it yet, but this strategy is a bit similar to the table-per-class strategy. It differs from it in the fact that, instead of repeating the parent-entity fields (columns) in the concrete tables, the JPA creates or expects an extra table with only the parent-entity columns and manages the joins transparently with this table.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset