Polymorphic Association Mapping with Any Relationship in Hibernate

Polymorphic association, supported by Hibernate, is the solution of such mapping requirement as an instance of entity needs to be associated with one of multiple possible entities. This can be implemented with Any relationship. In this relationship, the table holding the relationship would have two columns for relationship management. One to identify the primary key value of record being associated and another to identify the table to which the associated record belongs.

As the relationship is between one table to multiple other tables, foreign key relationship would not exist in database.One such real life example is sale scenario, where a vehicle seller sells truck and bus both. The requirement is to design sales system to manage the data of vehicles being sold. As bus and truck are completely different vehicles, a developer might design the system as mentioned in following class and ER diagram.

PolymorphicAssociationERDiagram

PolymorphicAssociationClassDiagram

In this case, during each sale transaction, one vehicle will be sold. This vehicle can either be bus or truck. On the database level, SaleTransaction table would have two attributes vehicleType and vehicleId to manage the relationship. Vehicle type would represent whether corresponding vehicleId belongs to bus or truck.

As the relationship is between one table to multiple tables, foreign key constraint wouldn’t exist in the database layer.

Let us see the implementation of this scenario.

Class maintaining the relationship between SaleTransaction to Bus and Truck entity

SaleTransaction.java

package com.anytest;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;

import lombok.Data;

import org.hibernate.annotations.Any;
import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.MetaValue;

@Data
@Entity
public class SaleTransaction {

	@Id
	@GeneratedValue
	private Long Id;
	private Date saleDate;
	private Long salePrice;

	 @Any (metaColumn = @Column(name = "vehicleType"))
	 @AnyMetaDef(idType = "long", metaType = "string",
	 metaValues = {
	 @MetaValue(targetEntity = Bus.class, value = "bus"),
	 @MetaValue(targetEntity = Truck.class, value = "truck")
	 })
	@Cascade( { org.hibernate.annotations.CascadeType.ALL})
	@JoinColumn(name = "vehicleId")
	private Vehicle vehicle;

	public SaleTransaction() {
	}

	public SaleTransaction(final Date saleDate, final Vehicle vehicle,final Long salePrice) {
		this.saleDate = saleDate;
		this.vehicle = vehicle;
		this.salePrice=salePrice;
	}
}
    

In this case, the developer has defined that SaleTransaction would be managing the relationship using vehicleType and vehicleId columns in database. If the transaction is of bus sale then vehicleType column in SaleTransaction table would be ‘bus’ and vehicleId would be the value of primary key of corresponding bus. In the same way, if the transaction is of Truck sale then the vehicleType column in SaleTransaction would be ‘truck’ and vehicleId would be the value of primary key of corresponding truck.

Query to create table structure in H2 database

    create table Bus (
        id bigint generated by default as identity,
        acAvailable boolean not null,
        buyingPrice bigint,
        engineCapacity bigint,
        modelNo varchar(255),
        seatCount integer not null,
        primary key (id)
    );
    create table SaleTransaction (
        Id bigint generated by default as identity,
        saleDate timestamp,
        salePrice bigint,
        vehicleType varchar(255),
        vehicleId bigint,
        primary key (Id)
    );
    create table Truck (
        id bigint generated by default as identity,
        buyingPrice bigint,
        engineCapacity bigint,
        modelNo varchar(255),
        trunkVolumeCapacity bigint,
        primary key (id)
    );
   

Vehicle Interface

package com.anytest;

public interface Vehicle {

		Long getBuyingPrice();
		String getModelNo();
		Long getEngineCapacity();
}
    

Bus Class

package com.anytest;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.Data;

@Data
@Entity
public class Bus implements Vehicle {

	@Id
	@GeneratedValue
	Long id;
	Long buyingPrice;
	String modelNo;
	Long engineCapacity;
	int seatCount;
	boolean acAvailable;

	public Bus(){}

	public Bus(final Long buyingPrice, final String modelNo, final Long engineCapacity, final int seatCount, final boolean acAvailable)
	{
		this.buyingPrice=buyingPrice;
		this.modelNo=modelNo;
		this.engineCapacity=engineCapacity;
		this.seatCount=seatCount;
		this.acAvailable=acAvailable;
	}
}
    

Truck Class

package com.anytest;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.Data;

@Data
@Entity
public class Truck implements Vehicle {

	@Id
	@GeneratedValue
	Long id;
	Long buyingPrice;
	String modelNo;
	Long engineCapacity;
	Long trunkVolumeCapacity;

	public Truck(){}

	public Truck(final Long buyingPrice, final String modelNo, final Long engineCapacity, final Long trunkVolumeCapacity)
	{
		this.buyingPrice=buyingPrice;
		this.modelNo=modelNo;
		this.engineCapacity=engineCapacity;
		this.trunkVolumeCapacity=trunkVolumeCapacity;
	}
}
    

hibernate.cfg.xml – hibernate configuration file

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.connection.driver_class">org.h2.Driver</property>
		<property name="hibernate.connection.url">jdbc:h2:~/anyMappingDemo</property>
		<property name="hibernate.connection.username">sa</property>
		<property name="hibernate.connection.password"></property>
		<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>

		<!-- JDBC connection pool (use the built-in) -->
		<!--<property name="hibernate.hbm2dll.auto">create-drop</property> -->
		<property name="connection.pool_size">1</property>
		<property name="hibernate.show_sql">true</property>
		<property name="hibernate.format_sql">true</property>

		<mapping class="com.anytest.Bus"></mapping>
		<mapping class="com.anytest.Truck"></mapping>
		<mapping class="com.anytest.SaleTransaction"></mapping>

	</session-factory>
</hibernate-configuration>

HibernateInterface – Class to deal with Hibernte interaction

package com.anytest;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

@SuppressWarnings("deprecation")
public class HibernateInterface {

	final static Configuration CONFIGURATION = new Configuration().configure();
	final static SessionFactory SESSION_FACTORY = CONFIGURATION.buildSessionFactory();
	static
	{
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

			@Override
			public void run() {
				if(SESSION_FACTORY!=null && !SESSION_FACTORY.isClosed())
				{
					SESSION_FACTORY.close();
				}

			}
		}));
	}

	public static void saveEntities(final Object entity)
	{
		final Session session = SESSION_FACTORY.openSession();
		session.beginTransaction();
		try {
			session.saveOrUpdate(entity);
			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
			session.getTransaction().rollback();
		}
		finally{
			session.close();
		}
	}

	@SuppressWarnings("unchecked")
	public static List<Object> getRecordsOfType(final Class<?> classObject)
	{
		final Session session = SESSION_FACTORY.openSession();
		try {
			 List<Object> resultList = session.createCriteria(classObject).list();
			 resultList.toString();
			 return resultList;
		}
		finally{
			session.close();
		}
	}

	public static int exectueQuery(final String hqlQuery)
	{
		int effectedRecords=0;
		final Session session = SESSION_FACTORY.openSession();
		session.beginTransaction();
		try {
			effectedRecords=session.createQuery(hqlQuery).executeUpdate();
			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
			session.getTransaction().rollback();
		}
		finally{
			session.close();
		}
		return effectedRecords;
	}
}
    

PaymentActoinPerfomer – Class to perform Implicit Polymorphism test

package com.anytest;

import java.util.Date;
public class ActionPerformer {

	public static void main(String[] args) {

		final Bus bus = new Bus(600000L, "ABCDBusModel", 100L, 35, true);
		final Truck truck = new Truck(700000L, "ABCDTruckModel", 300L, 30000L);

		final SaleTransaction busSaleTransaction = new SaleTransaction(new Date(), bus,650000L);
		final SaleTransaction truckSaleTransaction = new SaleTransaction(new Date(), truck,780000L);

		HibernateInterface.exectueQuery("delete from com.anytest.SaleTransaction");
		HibernateInterface.exectueQuery("delete from com.anytest.Vehicle");

		HibernateInterface.saveEntities(busSaleTransaction);
		HibernateInterface.saveEntities(truckSaleTransaction);

		System.out.println("SaleTransaction records =" + HibernateInterface.getRecordsOfType(SaleTransaction.class));
	}

}
    

In this scenario, the developer has set cascade type as ALL. So while saving the transient entry of busSaleTransaction and truckSaleTransaction, hibernate at first stores the transient records of Bus into BUS table or Truck into TRUCK table and then corresponding transaction entry in SaleTransaction table.

This behaviour can be seen in the query fired hibernate. While saving bus transaction hibernate fires following query -

Hibernate:
    insert
    into
        Bus
        (id, acAvailable, buyingPrice, engineCapacity, modelNo, seatCount)
    values
        (null, ?, ?, ?, ?, ?)
Hibernate:
    insert
    into
        SaleTransaction
        (Id, saleDate, salePrice, vehicleType, vehicleId)
    values
        (null, ?, ?, ?, ?)
    

While retrieving the records from SaleTransaction table, hibernate fires the query on SaleTransaction table. Then depending on the value of vehicleType hibernate fires the query in Bus or Truck table to get the associated records. In this Sales scenario hibernate fires following queries to get SaleTransaction records -

Hibernate:
    select
        this_.Id as Id1_1_0_,
        this_.saleDate as saleDate2_1_0_,
        this_.salePrice as salePric3_1_0_,
        this_.vehicleType as vehicleT4_1_0_,
        this_.vehicleId as vehicleI5_1_0_
    from
        SaleTransaction this_
Hibernate:
    select
        bus0_.id as id1_0_0_,
        bus0_.acAvailable as acAvaila2_0_0_,
        bus0_.buyingPrice as buyingPr3_0_0_,
        bus0_.engineCapacity as engineCa4_0_0_,
        bus0_.modelNo as modelNo5_0_0_,
        bus0_.seatCount as seatCoun6_0_0_
    from
        Bus bus0_
    where
        bus0_.id=?
Hibernate:
    select
        truck0_.id as id1_2_0_,
        truck0_.buyingPrice as buyingPr2_2_0_,
        truck0_.engineCapacity as engineCa3_2_0_,
        truck0_.modelNo as modelNo4_2_0_,
        truck0_.trunkVolumeCapacity as trunkVol5_2_0_
    from
        Truck truck0_
    where
        truck0_.id=?
    

The result of retrieving all sales transactions by firing query on SaleTransaction.class is as following -

SaleTransaction records =[SaleTransaction(Id=5, saleDate=2013-10-27 18:35:09.088, vehicle=Bus(id=3, buyingPrice=600000, modelNo=ABCDBusModel, engineCapacity=100, seatCount=35, acAvailable=true), salePrice=650000),
SaleTransaction(Id=6, saleDate=2013-10-27 18:35:09.088, vehicle=Truck(id=3, buyingPrice=700000, modelNo=ABCDTruckModel, engineCapacity=300, trunkVolumeCapacity=30000), salePrice=780000)]
    

Limitation of Any Hibernate Mapping

Any relationship cannot be implemented as bidirectional association. It will be unidirectional relationship managed by entity holding the relationship data.

The second limitation of Any relationship is that one can not set foreign key relationship on database layer to manage integrity.

The working project can be accessed from here in github.

I hope this article helped you to understand polymorphic association implementation in hibernate in a better way. Do share your experience about the article and way you are planning to use polymorphic association in your application.

I will come with the different new topics in future for sure. If you have any questions, please write it in the comments section.

Comments

comments

About Salil Verma

Salil is tech lead in BNP Paribas. He has got eight years of experience and have worked with JP Morgan and Tata Consultancy Services. During his tenure he worked in Water Fall model, Rational Unified process ,Scrum and Test Driven Development.

Speak Your Mind

*