继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
Java 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
例如:可以先定义一个商品基础类(拥有id和name),而又由商品派生出图书(添加 ISBN)和衣服(添加 color)。类图如下:

在 JPA 中,怎样实现上图中三者之间的继承关系?JPA 可以使用三种方式来完成关系的维护:
每个实体类一张表,如下图:

这种方式存在的问题:多态查询效率低下,代码如下:
TypedQuery<Goods> typedQuery = em.createQuery("select t from Goods t", Goods.class);
for(Goods goods : typedQuery.getResultList()) {
System.out.println(JSONObject.toJSONString(goods));
}查询时执行的SQL如下:
SELECT t0.id, t0.name FROM Goods t0 SELECT t0.id, t0.name, t0.isbn FROM Book t0 SELECT t0.id, t0.name, t0.color FROM Clothe t0
执行多态查询时,发起了3条SQL语句。
每个子类一张表,如下图:

这种方式存在的问题:新增查询需要操作多张表、且效率低下。执行多态查询SQL语句如下:
SELECT t0.id, t1.id, t2.id, t0.name, t1.isbn, t2.color FROM Goods t0 LEFT OUTER JOIN Book t1 ON t0.id = t1.id LEFT OUTER JOIN Clothe t2 ON t0.id = t2.id
将商品全部放在一张表中,如下图:

这种方式存在的问题:
无法对具体的列做非空约束,如:color、isbn 类不能做非空约束,如果为 color 添加了非空约束,则插入图书和商品信息失败;
需要添加额外的列来区分子类型,如:type 列;
执行多态查询SQL语法如下:
SELECT t0.id, t0.DTYPE, t0.name, t0.isbn, t0.color FROM Goods t0
实际开发中,方式一(一个实体一张表)使用较多,因为在查询性能方面效率更高。
下面将使用每个实体一个表格的方式来演示继承关系的使用,其他实现继承映射方式自行演示。
(1)定义商品基础类实体,代码如下:
@Data
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Goods {
@Id
@GeneratedValue
private int id;
@Column
private String name;
}(2)定义图书实体类,代码如下:
@Data
@Entity
public class Book extends Goods {
@Column
private String isbn;
}(3)定义衣服实体类,代码如下:
@Data
@Entity
public class Clothe extends Goods {
@Column
private String color;
}(4)修改 persistence.xml 配置文件,配置如下:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="demo_extend" transaction-type="RESOURCE_LOCAL"> <!-- JPA提供者 --> <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> <!-- 声明实体类 --> <class>com.hxstrive.openjpa.annotation.demo_extend.Goods</class> <class>com.hxstrive.openjpa.annotation.demo_extend.Book</class> <class>com.hxstrive.openjpa.annotation.demo_extend.Clothe</class> <!-- 配置JPA数据库属性 --> <properties> <property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/openjpa_learn?useSSL=false& serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"/> <property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/> <property name="openjpa.ConnectionUserName" value="root"/> <property name="openjpa.ConnectionPassword" value="aaaaaa"/> <property name="openjpa.Log" value="SQL=TRACE"/> <!-- 自动生成表 --> <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/> <!-- 不使用加载时强化和编译时强化,使用运行时Unenhanced(不能发挥OpenJPA的最大效能,所以也不推荐) --> <property name="openjpa.ClassLoadEnhancement" value="false"/> <property name="openjpa.DynamicEnhancementAgent" value="false"/> <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> </properties> </persistence-unit> </persistence>
(5)客户端代码:
import com.alibaba.fastjson.JSONObject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
public class Demo {
/** 持久化单元名称 */
private static final String NAME = "demo_extend";
private static void insertData(EntityManager em) {
try {
em.getTransaction().begin();
// 商品
Goods goods = new Goods();
goods.setName("商品1");
em.persist(goods);
// 图书
Book book = new Book();
book.setName("图书1");
book.setIsbn("ISBN001");
em.persist(book);
// 衣服
Clothe clothe = new Clothe();
clothe.setName("衣服1");
clothe.setColor("红色");
em.persist(clothe);
em.getTransaction().commit();
} finally {
em.close();
}
}
private static void selectData(EntityManager em) {
try {
em.getTransaction().begin();
TypedQuery<Goods> typedQuery = em.createQuery("select t from Goods t", Goods.class);
for(Goods goods : typedQuery.getResultList()) {
System.out.println(JSONObject.toJSONString(goods));
}
em.getTransaction().commit();
} finally {
em.close();
}
}
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(NAME);
// 插入数据
insertData(emf.createEntityManager());
// 查询数据
selectData(emf.createEntityManager());
emf.close();
System.out.println("finished.");
}
}执行客户端代码输出SQL如下:
-- 创建表格
CREATE TABLE Book (id INTEGER NOT NULL, name VARCHAR(255), isbn VARCHAR(255), PRIMARY KEY (id)) ENGINE = innodb
CREATE TABLE Clothe (id INTEGER NOT NULL, name VARCHAR(255), color VARCHAR(255), PRIMARY KEY (id)) ENGINE = innodb
CREATE TABLE Goods (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY (id)) ENGINE = innodb
CREATE TABLE OPENJPA_SEQUENCE_TABLE (ID TINYINT NOT NULL, SEQUENCE_VALUE BIGINT, PRIMARY KEY (ID)) ENGINE = innodb
-- 生成ID
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
INSERT INTO OPENJPA_SEQUENCE_TABLE (ID, SEQUENCE_VALUE) VALUES (?, ?) [params=?, ?]
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?]
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?]
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?]
-- 插入数据
INSERT INTO Goods (id, name) VALUES (?, ?) [params=?, ?]
INSERT INTO Book (id, name, isbn) VALUES (?, ?, ?) [params=?, ?, ?]
INSERT INTO Clothe (id, name, color) VALUES (?, ?, ?) [params=?, ?, ?]
-- 查询数据
SELECT t0.id, t0.name FROM Goods t0
SELECT t0.id, t0.name, t0.isbn FROM Book t0
SELECT t0.id, t0.name, t0.color FROM Clothe t0
-- 输出日志
{"id":1,"name":"商品1"}
{"id":51,"isbn":"ISBN001","name":"图书1"}
{"color":"红色","id":101,"name":"衣服1"}插入数据库表的数据如下图:
