组件关系

Component(组件)是一个被包含的对象,它作为值类型被持久化,而非一个被引用的实体。

Component(组件)这一术语指的是面向对象的合成概念(而并不是系统构架层次上的组件的概念)。

在 OpenJPA 中可借助 @Embeddable 注解定义一个 Component 组件,完成对复杂数据表的细分。在 OpenJPA 语义中,将某个实例对象中的一个逻辑组成称为 Component(组件)。Component(组件)与实体对象的根本差别就在于 Componet(组件)没有标识符(identity),它作为一个逻辑组成完全从属于实体对象。

下面以公司和公司地址、注册地址为例。

如果我们不使用 OpenJPA 的组件,最快想到的是将公司地址和注册地址和公司信息放在一起。代码如下:

@Entity
@Table
public class Company {
    @Id
    @GeneratedValue
    private Long id;

    @Column
    private String name;

    // 公司地址信息
    @Column
    private String province;

    @Column
    private String city;

    @Column
    private String street;

    // 注册地址信息
    @Column
    private String regProvince;

    @Column
    private String regCity;

    @Column
    private String regStreet;
}

上面 Company 实体会创建如下数据表:

CREATE TABLE `company` (
  `id` bigint(20) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `city` varchar(255) DEFAULT NULL,
  `province` varchar(255) DEFAULT NULL,
  `street` varchar(255) DEFAULT NULL,
  `reg_city` varchar(255) DEFAULT NULL,
  `reg_province` varchar(255) DEFAULT NULL,
  `reg_street` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

看上去也没有任何问题,依然可以成功新增、修改和查询公司信息。如果公司还需要添加一个地址信息,那么该怎么办呢?再次添加三个属性,有点麻烦!下面介绍使用 OpenJPA 的组件映射关系。

大概思路

将公司信息中的地址信息单独提取到一个类中,然后在公司类中地址信息作为成员变量引用。代码如下:

(1)地址信息对象设计

@Data
@Embeddable
public class Address {
    private String province;
    private String city;
    private String street;
}

上面代码中,@Embeddable 注解表示 Address 可以嵌入到其他实体中。

(2)公司信息对象设计

@Data
@Entity
@Table
public class Company {
    @Id
    @GeneratedValue
    private Long id;

    @Column
    private String name;

    // 公司地址
    private Address address;

    // 注册地址
    @AttributeOverrides({
        @AttributeOverride(name = "city", column = @Column(name= "reg_city")),
        @AttributeOverride(name = "province", column=@Column(name="reg_province")),
        @AttributeOverride(name = "street", column = @Column(name="reg_street"))
    })
    private Address regAddress;

}

上面代码中,引入了两个 Address 地址。这会导致 OpenJPA 将会在 Company 表中创建对应的列,但是 Company 中有两个 Address 对象,所以将会存在相同的列。

此时,需要使用 @AttributeOverrides 注解对其中一个 Address 地址对象的属性的列做映射(即起别名),解决列名冲突的问题。

(3)配置信息

<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_component2" transaction-type="RESOURCE_LOCAL">
        <!-- JPA提供者 -->
        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <!-- 声明实体类 -->
        <class>com.hxstrive.openjpa.annotation.demo_component2.Company</class>
        <class>com.hxstrive.openjpa.annotation.demo_component2.Address</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>

(4)客户端代码

import com.alibaba.fastjson.JSONObject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.util.List;

public class Demo {
    /** 持久化单元名称 */
    private static final String NAME = "demo_component2";

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(NAME);
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();

        // 插入数据
        Company company = new Company();
        company.setName("张三");
        // 公司地址信息
        Address address = new Address();
        address.setProvince("四川省");
        address.setCity("成都市");
        address.setStreet("武侯区");
        company.setAddress(address);
        // 注册地址信息
        Address regAddress = new Address();
        regAddress.setProvince("四川省");
        regAddress.setCity("成都市");
        regAddress.setStreet("高新区");
        company.setRegAddress(regAddress);
        em.persist(company);

        // 查询数据
        String qlString = "select t from Company t";
        Query query = em.createQuery(qlString);
        List<Company> companyList = (List<Company>) query.getResultList();
        for (Company item : companyList) {
            System.out.println(JSONObject.toJSONString(item));
        }

        em.getTransaction().commit();
        em.close();
        emf.close();
        System.out.println("finished.");
    }

}

运行客户端输出SQL如下:

-- 创建数据表
CREATE TABLE Company (id BIGINT NOT NULL, name VARCHAR(255), city VARCHAR(255), province VARCHAR(255), street VARCHAR(255), reg_city VARCHAR(255), reg_province VARCHAR(255), reg_street 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=?, ?, ?]
-- 插入公司信息
INSERT INTO Company (id, name, city, province, street, reg_city, reg_province, reg_street) VALUES (?, ?, ?, ?, ?, ?, ?, ?) [params=?, ?, ?, ?, ?, ?, ?, ?]
-- 查询公司信息
SELECT t0.id, t0.city, t0.province, t0.street, t0.name, t0.reg_city, t0.reg_province, t0.reg_street FROM Company t0
-- 公司信息
{"address":{"city":"成都市","province":"四川省","street":"武侯区"},"id":1,"name":"张三","regAddress":{"city":"成都市","province":"四川省","street":"高新区"}}

数据表信息如下图:

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