Table-per-class strategy for mapping inheritance with JPA

By | August 25, 2016

1. Table-per-class strategy

Table-per-class strategy is similar to  joined-tables strategy because each entity in domain model gets its own table. One significant difference is no relationship between the tables, which doesn’t take advantage of power of relational database at all. Following the stock market example in join-table-strategy and single-table-strategy , we are there different tables namely product, bondsshares but product_codedescriptionversion properties are duplicated in each table. So if the data like product_code column needs to be unique, we should be careful once saving data with this strategy.

2. Stock market example

2.1 Project structure

Table-Per-Class-Project-structureFigure 1: Project structure for the financial product.

2.2 JPA configuration

The @DiscriminatorColumn and @DiscriminatorValue annotations are not used in this strategy

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

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

@Entity
@Table(name="product")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class FinancialProduct{

	@Id
	@Column(name = "product_code")
	protected String productCode;
	
	@Column(name = "description")
    private String description;
	
	@Version
    protected long version;
	
	public FinancialProduct(){};
	
	public FinancialProduct(String productCode, String description, long version) {
		super();
		this.productCode = productCode;
		this.description = description;
		this.version = version;
	}
	
	public String getProductCode() {
		return productCode;
	}

	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.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.Entity;
import javax.persistence.Table;

@Entity
@Table(name="shares")
public class Shares extends FinancialProduct{
	public Shares(String productCode, String description, long version) {
		super(productCode, description, version);
	}
}

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

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

@Entity
@Table(name="bonds")
public class Bonds extends FinancialProduct{
	public Bonds(String productCode, String description, long version) {
		super(productCode, description, version);
	}
}

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="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>
            <column name="description" type="varchar(255)" />
			<column name="version" type="bigint" />
        </createTable>
        
         <createTable tableName="shares">
            <column name="product_code" type="varchar(255)">
                <constraints nullable="false" primaryKey="true"/>
            </column>
            <column name="description" type="varchar(255)" />
			<column name="version" type="bigint" />
        </createTable>
        
		<loadData tableName="product" encoding="UTF-8" file="financial_product.csv" separator=";">
		</loadData>
		
	</changeSet>
</databaseChangeLog>

src/main/resources/financial_product.csv

product_code;description;version
SH001;New York Stock Exchange;1
SH002;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.Bonds;
import mapping.inheritance.domain.FinancialProduct;
import mapping.inheritance.domain.Shares;

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

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

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

}

Output in console

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

Because of there different tables in database without relational, the executable only saves data into product table as the Figure 2. Create new services to add new data for shares and bonds table if we want.

Table-Per-Class-Product-db

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.290-291, 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.