Joined-tables strategy for mapping inheritance with JPA

By | August 13, 2016

1. Joined-tables strategy

Follow up the Single-table strategy for mapping inheritance with JPA, this strategy stores all financial products in a single table. In contrast, the joined table strategy creates separate tables for each entity in the domain model. This means that the financial product is the parent table and it holds common data for all product types. The Bonds, Shares,…table are children of financial product.

The joined-tables strategy is probably the best mapping choice from a design perspective. From a performance perspective, it’s worse than the single-table strategy because it requires the joining of multiple tables for polymorphic queries.[1]

2. Stock Market Example

2.1 Project Structure

Joined-tables strategy-structureFigure 1: Project structure for the financial product.

2.2 JPA configuration

Used the same annotation as the single-table: @DiscriminatorColumn, @DiscriminatorValue. and the @Inheritance annotation’s strategy element is specified as JOINED. One different thing is @PrimaryKeyJoinColumn annotation for Shares, Bonds children.

src/main/java/mapping/inheritance/domain/FinancialProduct.java

package mapping.inheritance.domain;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.dozer.Mapping;

@Entity
@Table(name="product")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name="type", 
discriminatorType=DiscriminatorType.INTEGER)
public abstract class FinancialProduct{

	@Transient
    @Mapping("type")
    public FinancialProductType getFinancialProductType() {
        return FinancialProductType.get(this.getClass());
    }
	@Id
	@Column(name = "product_code")
	protected String productCode;
	
	@Column(name = "description")
    private String description;
	
	@Version
    protected long version;
	
	public void setProductCode(String productCode) {
		this.productCode = productCode;
	}

	public void setDescription(String description) {
		this.description = description;
	}
	
	public void setVersion(long version) {
		this.version = version;
	}

	@Override
	public String toString() {
		return this.productCode + " " + this.getFinancialProductType() +
				" " + this.description + " " + this.version;
	}
	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		FinancialProduct financialProduct = (FinancialProduct) o;

		if (version != financialProduct.version)
			return false;
		if (!productCode.equals(financialProduct.productCode))
			return false;

		return true;
	}
	
	@Override
    public int hashCode() {
        int result = productCode.hashCode();
        result = 31 * result + (int) (version ^ (version >>> 32));
        return result;
    }
}

src/main/java/mapping/inheritance/domain/Shares.java

package mapping.inheritance.domain;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="shares")
@DiscriminatorValue("1")
@PrimaryKeyJoinColumn(referencedColumnName="product_code")
public class Shares extends FinancialProduct{
}

src/main/java/mapping/inheritance/domain/Bonds.java

package mapping.inheritance.domain;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="bonds")
@DiscriminatorValue("2")
@PrimaryKeyJoinColumn(referencedColumnName="product_code")
public class Bonds extends FinancialProduct{
}

2.3 Resource configuration

src/main/resources/db-changelog.xml

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.3.xsd">

	<changeSet id="1" author="Khiem.Truong">
		<createTable tableName="product">
			<column name="product_code" type="varchar(255)">
				<constraints nullable="false" primaryKey="true" />
			</column>
			<column name="type" type="integer">
				<constraints nullable="false" />
			</column>
			<column name="description" type="varchar(255)" />
			<column name="version" type="bigint" />
		</createTable>

		 <createTable tableName="bonds">
            <column name="product_code" type="varchar(255)">
                <constraints nullable="false" primaryKey="true"/>
            </column>
        </createTable>
         <createTable tableName="shares">
            <column name="product_code" type="varchar(255)">
                <constraints nullable="false" primaryKey="true"/>
            </column>
        </createTable>
        
		<loadData tableName="product" encoding="UTF-8" file="financial_product.csv" separator=";">
		</loadData>
		
		<addForeignKeyConstraint baseColumnNames="product_code" baseTableName="shares"
                         constraintName="fk_shares_product_code" deferrable="true" 
                         referencedColumnNames="product_code" referencedTableName="product" />
			
		<addForeignKeyConstraint baseColumnNames="product_code" baseTableName="bonds"
                         constraintName="fk_bonds_product_code" deferrable="true" 
                         referencedColumnNames="product_code" referencedTableName="product" />
	</changeSet>
</databaseChangeLog>

src/main/resources/financial_product.csv

product_code;type;description;version
SH001;1;New York Stock Exchange;1
SH002;1;London Stock Exchange Group;1

2.4 Make the application executable

src/main/java/mapping/inheritance/service/Application.java

package mapping.inheritance.service;

import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import mapping.inheritance.domain.FinancialProduct;
import mapping.inheritance.domain.FinancialProductType;
import mapping.inheritance.mapper.FinancialProductDTO;

@SpringBootApplication
@ComponentScan(basePackages = { "mapping.inheritance.domain", 
                               "mapping.inheritance.service",
		               "mapping.inheritance.mapper" })
@EnableJpaRepositories(value = { "mapping.inheritance.repository" })
@EntityScan(basePackageClasses = FinancialProduct.class)
public class Application {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(Application.class, args);

		FinancialProductService shareService = (FinancialProductService)
                                       ctx.getBean("financialProductService");
		
		FinancialProductDTO sharesDTO = new FinancialProductDTO("SH003", 
				FinancialProductType.BONDS, "NASDAQ Stock Exchange", "1");
		shareService.add(sharesDTO);
		
		sharesDTO = new FinancialProductDTO("SH004", 
				FinancialProductType.SHARES, "Japan Exchange Group - Tokyo", "1");
		shareService.add(sharesDTO);
		
		List<FinancialProduct> sharesList = shareService.findAll();
		sharesList.forEach(s -> {
			System.out.println(s);
		});
	}

}

Output in console

SH001 SHARES New York Stock Exchange 1
SH002 SHARES London Stock Exchange Group 1
SH003 BONDS NASDAQ Stock Exchange 1
SH004 SHARES Japan Exchange Group - Tokyo 1

Actually, there are bonds and shares tables in database.

joined-table-statergy-in-database

Figure 2: Tables in database.

Download Zip here (Build with Maven)

References:

  1. D. Pan, R. Rahman, R. Cuprak, M. Remijan, “Mapping inheritance”, In EJB3 in Action,pp.289-290, Manning, Second Edition, 2014

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.