继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
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"}
插入数据库表的数据如下图: