Hibernate Advance

Dev_RV
13 min readMar 24, 2024

--

Hibernate advance topics -

Composite Primary Key -

Hibernate provides two ways for creating composite primary key:

1. using @IdClass

The primary key of the entity class ismarked with @Id annotation

Preferred for the small application having a simple data structure

2. using @EmbeddedId

Entity class contains the reference of a class that has the fields of the composite primary key

Preferred when the data structure is complex, especially when there is a join column with multiple tables.

Example:-

While persisting an object of CustomerLoan, the object of CustomerLoanPK must be created and set it to the object of CustomerLoan. When the save() method is used to persist, it returns the CustomerLoanPK object.

While retrieving the object using get() or load() methods, you need to pass the object of CustomerLoanPK as an identifier value.

Association Mapping

@ManytoOne association

For many to one association mapping implementation below are the steps:

1. From the given entities find out the source and the target entity

The foreign key column (ADDRESSID) of CUSTOMER table references the primary key of the ADDRESS table, so Customer entity can be the source and address as the target. To map the foreign key column, source entity can be associated with the target entity i.e. the Customer has a reference of Address.

2. Give appropriate annotations in the Customer entity to map with Address entity

The annotations used in Customer (source) class for many to one mapping with Address (target) are:

@ManyToOne(cascade=CascadeType.ALL)

This annotation is applied to the reference attribute of Address in Customer entity to indicate that the relationship has many to one cardinality. The cascade attribute of the annotation is mandatory. This attribute transfers operation (such as insert, update, delete) done on the Customer object to its Address object.

@JoinColumn(name=”addressId”)

The annotation has a single name attribute, which specifies the name of the foreign key column in the Customer table.

Many to many relationships are represented by creating a third table called lookup table (CUSTOMERACCOUNT) which contains a composite primary key i.e. combination of primary keys from CUSTOMER and ACCOUNT tables. The CUSTOMER, ACCOUNT and CUSTOMERACCOUNT tables are as shown.

1. Identify the Source entity and the Target entity among the given Entities

In the many to many mapping, you can use either of the entities as source entity and the remaining as target entity. In the given scenario we are using Customer as source and Account as the target.

2. Give appropriate annotations in the Customer entity to map with Account entity

The annotations used in Customer class for Many to Many mapping with Account are:

@ManyToMany(cascade = CascadeType.ALL)

This annotation is applied to the property in Customer, which is a collection of references of the Account entity. It indicates that the relationship has many to many cardinalities. The cascade attribute of the annotation is mandatory. This attribute transfers operation (such as insert, update, delete) done on the Customer object to its Account object.

As many to many mapping do not define a separate entity for the lookup table, this annotation is used to map with the lookup table.

The name attribute specifies the lookup table name (CUSTOMERACCOUNT) which contains the primary keys of both the source and target table.

The joinColumns attribute indicates the joining column names of the source table.

The inverseJoinColumns attribute indicates the joining column names of the target table.

The @JoinColumn annotation which is used for both joinColumns and inverseJoinColumns has two attributes, the name attribute specifies the lookup table column name and referencedColumnName attribute specifies the source/target table column name.

Inheritance Mapping

To solve the inheritance issue Hibernate supports three different methods of mapping these entity classes with the database tables.

Table per class hierarchy: This mapping strategy helps to have only one table in the database which maps the entire hierarchy of the classes. In the example, there are classes like Account, SavingsAccount, CurrentAccount with inheritance relationship between them, but all these entities are mapped with only one table ACCOUNT in the database.

Table per sub class: This mapping strategy helps to have normalized tables in the database for every class present in the hierarchy. If there are classes like Account, SavingsAccount, CurrentAccount with inheritance relationship between them, there will be their tables one for each classes Account, SavingsAccount, CurrentAccount as shown below with only the properties of their own as columns. Child class tables will have the foreign key column to map with parent properties.

Table per concrete class: This mapping strategy helps to have a separate table in the database for each class present in the hierarchy. If there are classes like Account, SavingsAccount, CurrentAccount with inheritance relationship between them, there will be their tables one for each classes Account, SavingsAccount, CurrentAccount as shown below with all the properties (inherited and their own) as columns.

Table Per class

Step 1: Identify the discriminator column

To determine the type of account that each row represents, the ACCOUNT table uses accountType column.

The three entity classes will be mapping to a single ACCOUNT table. So, the framework needs a way to differentiate the row representing an object of one class (an object of SavingsAccount) from the row representing an object of another class (an object of CurrentAccount). The persistence framework uses the accountType column for the same and it refers this column as discriminator column.

Step 2: Annotate the entity classes to map with the single table

The annotations used in entity classes are:

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name=”discriminatorColumnName”)

@DiscriminatorColumnValue(“discriminatorColumnValue”)

The @Inheritance and @DiscriminatorColumn annotations should be applied on the base entity class whereas @DiscriminatorColumnValue to all the classes.

@Inheritance:

This is the annotation which signifies the strategy type used for inheritance relationship mapping using the strategy attribute. For table per class hierarchy, the type should be InheritanceType.SINGLE_TABLE.

@DiscriminatorColumn:

This annotation is used to map the extra column (ACCOUNTTYPE) that is present in the table for which there is no corresponding property written in any of the entity classes in the hierarchy where the name attribute is the name of the discriminator column.

@DiscriminatorValue:

The string value in this annotation indicates the discriminator value that the instance of the class will have when it is getting persisted into the database. In the absence of this annotation, the class name itself will act as the discriminator value during persist.

Note: @DiscriminatorValue annotation can be applied in the base class entity as well. So that while persisting a base class object it can have the given value for the discriminator column.

Table per Sub class

Observe the strategy type in the @Inheritance annotation. For table per subclass, the type should be InheritanceType.JOINED. Observe the @Table annotation in the subclasses. No other annotations are required for a table per subclass strategy.

All the classes are mapped with their tables in the database.(DAO.class code below)

Table Per Concrete class

Observe the strategy type in the @Inheritance annotation. For table per concrete class, the strategy should be InheritanceType.TABLE_PER_CLASS. No other annotations are required for a table per subclass strategy.

Each class in the hierarchy is mapped to its table in the underlying database.

Criteria API

Criteria API lets one build up a criteria query object. It programmatically applies filtration rules and logical conditions. In Criteria API, queries are written using objects, dot operator and methods. Hence Criteria API is more towards object oriented way for querying.

Criteria object is created using createCriteria() method of Session instance. For applying the conditions on records, selecting required columns and ordering selected rows we use Restrictions, Projection and Order class respectively.

Let us see how can we do this by implementing the requirement.

SELECT * FROM Customer WHERE address LIKE “?%”;

For adding WHERE clause in criteria, Restrictions class and their methods can be used. A Criterion object is returned by the Restrictions methods and this needs to be added to the Criteria with the help of add() method.

SELECT * FROM Customer WHERE address LIKE “?%” AND phoneNumber IS NOT NULL;

and()/or() methods of Restrictions class can be used in order to add more than one conditions using AND/OR. These methods accept Criterion objects as parameters and return a Criterion object that needs to be added to the Criteria as suggested below:

SELECT customerName FROM Customer WHERE address LIKE “?%” AND phoneNumber IS NOT NULL;

To represent the column list in the SELECT clause, Projections class can be used. The Projections property() method is used to suggest the property name that we are looking to fetch. This then returns the Projection object that needs to be set to Criteria with the help of the setProjection() method.

SELECT customerName, address FROM Customer WHERE address LIKE “?%” AND phoneNumber IS NOT NULL;

To select multiple columns, an instance of ProjectionsList needs to be created as shown below:

SELECT customerName, address FROM Customer WHERE address LIKE “?%” AND phoneNumber IS NOT NULL ORDER BY customerName;

For ordering the rows Order class can be used. Use asc() or desc() methods of Order as per the requirement which returns an Order object. The Order object should be added to Criteria using the addOrder() method.

Caching

For better Performance, Cache is a small block of memory that stores the data temporarily. This data is either directly retrieved from the database or some transient data computed from database records. The process of storing the data in the cache memory is called caching.

Caching in Hibernate

In Hibernate, cache sits between the application and database. It stores a local copy of data retrieved or computed. So, when an application wants to access the same data again, it directly gets the data from the cache. Thus, the traffic between the application and database is reduced.

In Hibernate, a Session instance itself acts as a default cache. This type of cache is known as first-level cache or session cache. This is default in nature and cannot be turned off.

In session cache, when the application makes a request for the same persistent object more than once, it makes sure that the application is returned the same instance that was cached during the initial request.

Second level cache is not available by default. It is optional and is pluggable.

To plug in the second level cache into the Hibernate application, a cache provider needs to be selected.

Cache providers are those who implement the Hibernate cache specification provided in the interface org.hibernate.cache.CacheProvider.

List of cache providers:

EHCache or Easy Hibernate Cache

OSCache

SwarmCache

JBossCache

Concurrency strategy is an intermediary mechanism which determines how the data should be stored in cache, and should be retrieved from cache.

Different cache concurrency strategies:

TRANSACTIONAL

READ_WRITE

NONSTRICT_READ_WRITE

READ_ONLY

Different cache concurrency strategies and supported cache are:

A vital point here is, the first level cache will always be accessed prior to tracing an object during second level caching.

Envers

Envers (ENtity VERSioning) libraries are used for auditing purposes. Information that can be maintained in Audit tables for auditing are:

entities changed

modified values of properties which are mentioned for auditing

the primary key of the row which has modified

time of change

type of operation like add or update or delete occurred on a specified row of that entity

each transaction is treated as a “revision”, these revisions are global with a unique revision number assigned

Class Level Audting

Step 1: Add hibernate-envers.jar to the classpath. If you are using old versions of Hibernate(less than 4) we need to specify listeners in the Hibernate configuration file. But in the latest versions of Hibernate just add a hibernate-envers jar file in the classpath, it will automatically register required listeners.

Step 2: Annotate the persistent class that needs to be audited with @Audited

Step 3: Create the required tables

  • Create a global table named ‘REVINFO’
  • The global table which is used to store the revision information of all the transactions irrespective of entity class
  • The table should contain two columns REV and REVTSTMP both of type Number to store revision number(REV) and the transaction time in milliseconds(REVTSTMP)
  • Create a table for each audited entity named as <entity_name>_AUD like ‘LOGIN_AUD’
  • This table is used to store local information of all transactions for the respective entity class
  • This table should have all the columns of the entity class table including two extra columns ‘REV’ and ‘REVTYPE’ of type number to store revision number and revision type
  • Revision type can have the following values.

When you insert/update/delete a row of audited entity class table i.e LOGIN, a row will be inserted into both REVINFO and LOGIN_AUD. Let us consider the following code for updating the password of George.48

A row in LOGIN table will be updated

A row with a unique revision number and the timestamp of execution in milliseconds will be inserted to REVINFO table

A row with the audited properties (new updated values), primary key value, revision number (same number which is added to REVINFO) and the revision type (update 1) will be added to the LOGIN_AUD table.

Property level Auditing

Instead of auditing whole class, only a few parameters of the class can be audited by specifying @Audited at attribute level as shown below:

Whenever there a password attribute is modified the audit information is added to audit tables. Here LOGIN_AUD table will have the audited property columns (password), primary key column (loginId), REV and REVTYPE column.

Both the entity table (LOGIN) and the audit table (LOGIN_AUD) will be having the updated value of the password.

Note: The properties which are not audited will not be monitored i.e. if any update happens in a non-audited property then no information will be added in the audit tables. The columns of non-audited properties will also be not present in the <entity_name>_AUD table.

Events & Events Listeners

Event in the persistence layer(like loading, saving, deleting entities) event architecture can be used. Events support is available from Hibernate 3 onwards. Session generates an appropriate event according to the method that was invoked on the session object and it then passes it on to the event listeners configured of that type.

The listeners should be singletons in nature i.e listeners are shared among requests, hence it is not advised to save any state as instance variables. Some Hibernate Events and corresponding Event Listeners are:

All these event objects are correlated to the session interface methods. Here session generates an event based on the persistent method that invoked it. This information is passed on to the respective event listeners configured for it. Events and their corresponding listeners are available in org.hibernate.event package.

Interceptors in hibernates

Step 1: Create a custom interceptor by implementing Interceptor interface

Step 2: After creating the class which implements the interceptor ,override one of the below callback methods based on the requirement.

Step 3: The implemented interceptor class sendMoneyListener instance can be attached either to Session or SessionFactory

--

--

Dev_RV

Knows about DSA , Java , Springboot , Microservices