Hibernate/JPA Table Per Class Inheritance Example
In this tutorial, we show how to map entity hierarchies onto database tables. There are several ways of mapping inheritance to the database. Here, we’ll look into Hibernate/JPA table per class inheritance.
In a Table per class inheritance strategy, each concrete subclass has its own table containing both the subclass and the base class properties.
Maven Dependencies
We use Apache Maven to manage the projects dependencies.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.memorynotfound.db.hibernate.configuration</groupId> <artifactId>entity-manager</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>war</packaging> <name>HIBERNATE - ${project.artifactId}</name> <url>https://memorynotfound.com</url> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.4</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.3.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>5.2.3.Final</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
Creating Models + Hibernate/JPA Mappings
We have the following polymorphic classes.
We cannot use the SequenceType.SEQUENCE generation strategy. Instead we can create a custom TABLE generation strategy for automatically creating the id’s. We do this by using the @TableGenerator annotation and pass it in the @GeneratedValue annotation.
package com.memorynotfound.hibernate; import javax.persistence.*; @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Shape { @Id @TableGenerator( name = "SHAPE_GEN", table = "ID_Generator", pkColumnName = "name", valueColumnName = "sequence", allocationSize = 1) @GeneratedValue(strategy = GenerationType.TABLE, generator = "SHAPE_GEN") private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
package com.memorynotfound.hibernate; import javax.persistence.Entity; @Entity public class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } @Override public String toString() { return "Circle{" + "radius=" + radius + '}'; } }
package com.memorynotfound.hibernate; import javax.persistence.Entity; @Entity public class Rectangle extends Shape { private double width; private double length; public Rectangle(double width, double length) { this.width = width; this.length = length; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getLength() { return length; } public void setLength(double length) { this.length = length; } @Override public String toString() { return "Rectangle{" + "width=" + width + ", length=" + length + '}'; } }
If you prefer XML over Annotations, you can use the equivalent JPA XML mapping. This file is located in the src/main/resources/META-INF folder and is named orm.xml.
<?xml version="1.0" encoding="UTF-8" ?> <entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_0.xsd" version="2.1"> <!-- abstract shape --> <entity class="com.memorynotfound.hibernate.Shape"> <inheritance strategy="TABLE_PER_CLASS"/> <table-generator name="SHAPE_GEN" table="ID_Generator" pk-column-name="name" pk-column-value="sequecne" allocation-size="1"/> <attributes> <id name="id"> <generated-value strategy="TABLE" generator="SHAPE_GEN"/> </id> </attributes> </entity> <!-- circle concrete subclass --> <entity class="com.memorynotfound.hibernate.Circle"> <attributes> <basic name="radius"/> </attributes> </entity> <!-- rectangle concrete subclass --> <entity class="com.memorynotfound.hibernate.Rectangle"> <attributes> <basic name="length"/> <basic name="width"/> </attributes> </entity> </entity-mappings>
Hibernate/JPA Configuration
We configure the JPA Persistence Unit using the persistence.xml file, which is located in the src/main/resources/META-INF directory.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="mnf-pu" transaction-type="RESOURCE_LOCAL"> <!-- enable if you prefer xml over annotations --> <!--<mapping-file>META-INF/orm.xml</mapping-file>--> <properties> <!-- Configuring JDBC properties --> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/memorynotfound?serverTimezone=Europe/Brussels"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value=""/> <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <!-- Hibernate properties --> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.show_sql" value="true"/> </properties> </persistence-unit> </persistence>
Create, Run and Test application
package com.memorynotfound.hibernate; import javax.persistence.*; public class App { public static void main (String...args) throws InterruptedException { EntityManagerFactory emf = Persistence.createEntityManagerFactory("mnf-pu"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); Circle circle = new Circle(13.05); em.persist(circle); em.getTransaction().commit(); em.getTransaction().begin(); Rectangle rectangle = new Rectangle(5.02, 10.45); em.persist(rectangle); em.getTransaction().commit(); Listaccounts = em .createQuery("select a from Shape a") .getResultList(); emf.close(); } }
Since we used the TABLE_PER_CLASS inheritance strategy, a table is created for each concrete subclass.
create table Circle ( id integer not null, radius double precision not null, primary key (id) ) create table Rectangle ( id integer not null, length double precision not null, width double precision not null, primary key (id) ) create table ID_Generator ( name varchar(255) not null, sequence bigint, primary key (name) )
Here is the SQL INSERT statement.
select tbl.sequence from ID_Generator tbl where tbl.name=? for update insert into ID_Generator (name, sequence) values (?,?) update ID_Generator set sequence=? where sequence=? and name=? insert into Circle (radius, id) values (?, ?) select tbl.sequence from ID_Generator tbl where tbl.name=? for update update ID_Generator set sequence=? where sequence=? and name=? insert into Rectangle (length, width, id) values (?, ?, ?)
The SQL SELECT statement of a TABLE_PER_CLASS inheritance requires multiple UNION queries, so be aware of the performance implications of a large class hiearchy.
select shape0_.id as id1_2_, shape0_.radius as radius1_0_, shape0_.length as length1_1_, shape0_.width as width2_1_, shape0_.clazz_ as clazz_ from ( select id, radius, null as length, null as width, 1 as clazz_ from Circle union select id, null as radius, length, width, 2 as clazz_ from Rectangle ) shape0_
Downloads
From:一号门
COMMENTS