继承关系映射

继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

Java 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。

这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。

例如:可以先定义一个商品基础类(拥有id和name),而又由商品派生出图书(添加 ISBN)和衣服(添加 color)。类图如下:

在 JPA 中,怎样实现上图中三者之间的继承关系?JPA 可以使用三种方式来完成关系的维护:

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

每个实体类一张表,如下图:

这种方式存在的问题:多态查询效率低下,代码如下:

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语句。

@Inheritance(strategy = InheritanceType.JOINED)

每个子类一张表,如下图:

这种方式存在的问题:新增查询需要操作多张表、且效率低下。执行多态查询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

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

将商品全部放在一张表中,如下图:

这种方式存在的问题:

  • 无法对具体的列做非空约束,如: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&amp;
                      serverTimezone=UTC&amp;useUnicode=true&amp;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"}

插入数据库表的数据如下图:

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号