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& 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>
(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":"高新区"}}
数据表信息如下图: