本章节将介绍使用 @OneToOne 注解实现一对一关系映射,学习 JPA 关系映射最好的方式就是通过实际例子进行介绍。接下来我们将介绍 @OneToOne 注解用法,假如我们拥有一张主表 user15,该表用来保存用户名和年龄;附表 user15ext 是主表的扩展表,用来记录用户的扩展信息,如:邮箱、家庭地址等等。两者之间是一对一的关系,即一个用户只能拥有一个扩展信息。数据模型如下图:
在实例之前,我们先看看 @OneToOne 的源码,如下:
@Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface OneToOne { Class targetEntity() default void.class; CascadeType[] cascade() default {}; FetchType fetch() default EAGER; boolean optional() default true; String mappedBy() default ""; boolean orphanRemoval() default false; }
其中:
targetEntityClass:表示默认关联的实体类型,默认为当前标注的实体类;
cascade:关联属性,这个属性定义了当前类对象操作了之后,级联对象的操作。级联可取值如下:
不定义,则对关系表不会产生任何影响
CascadeType.PERSIST 级联新建
CascadeType.REMOVE 级联删除
CascadeType.REFRESH 级联刷新
CascadeType.MERGE 级联更新
CascadeType.ALL 表示同时选择 CascadeType.PERSIST、CascadeType.REMOVE、CascadeType.REFRESH 和 CascadeType.MERGE
fetch:FetchType 类型的属性。可选择项包括:FetchType.EAGER 和 FetchType.LAZY。
FetchType.EAGER:表示关系类在主类加载的时候同时加载
FetchType.LAZY:表示关系类在被访问时才加载,默认值是 FetchType.LAZY。
mappedBy:拥有关联关系的域,如果关系是单向的就不需要,双向关系表,那么拥有关系的这一方有建立、解除和更新与另一方关系的能力,而另一方没有,只能被动管理,这个属性被定义在关系的被拥有方。双向 @OneToOne,双向 @OneToMany,双向 @ManyToMany。
orphanRemoval:是否使用孤儿删除
上面介绍了 @OneToOne 注解的各个属性含义,也介绍了 user15 和 user15ext 表的关系,下面通过实例演示 @OneToOne 的用法。
(1)定义 user15 表实体,代码如下:
import lombok.Data; import javax.persistence.*; @Data @Entity @Table public class User15 { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column private String name; @Column private Integer age; // 定义一对一关系映射 @OneToOne(cascade = CascadeType.ALL) @JoinColumn private User15Ext user15Ext; }
(2)定义 user15ext 附表的实体,代码如下:
import lombok.Data; import javax.persistence.*; @Data @Entity @Table public class User15Ext { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column private Integer id; @Column private String email; @Column private String homeUrl; }
(3)客户端代码,首先插入一条数据到 user15 和 user15ext 表中,然后使用 select 语句查询数据。代码如下:
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; import java.util.UUID; public class OpenJpaDemo15 { /** 持久化单元名称 */ private static final String NAME = "demo15"; public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory(NAME); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); // 插入数据 String uuid = UUID.randomUUID().toString(); User15Ext userExt = new User15Ext(); userExt.setEmail(uuid + "@outlook.com"); userExt.setHomeUrl("http://www.hxstrive.com"); User15 user = new User15(); user.setName("张三-" + uuid); user.setAge(28); user.setUser15Ext(userExt); em.persist(user); em.getTransaction().commit(); // 查询数据 String qlString = "select t from User15 t"; Query query = em.createQuery(qlString); List<User15> userList = (List<User15>) query.getResultList(); for (User15 item : userList) { System.out.println(JSONObject.toJSONString(item)); } em.close(); emf.close(); System.out.println("finished."); } }
(4)配置 OpenJPA,如下:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="demo15" transaction-type="RESOURCE_LOCAL"> <!-- JPA提供者 --> <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> <!-- 声明实体类 --> <class>com.huangx.openjpa.annotation.demo15.User15</class> <class>com.huangx.openjpa.annotation.demo15.User15Ext</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"/> <!-- 输出sql日志 --> <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>
执行上面实例将依次输出如下SQL语句:
-- (1)创建主表 CREATE TABLE User15 (id INTEGER NOT NULL, age INTEGER, name VARCHAR(255), user15ext_id INTEGER, PRIMARY KEY (id)) ENGINE = innodb -- (2)创建附表 CREATE TABLE User15Ext (id INTEGER NOT NULL, email VARCHAR(255), homeUrl VARCHAR(255), PRIMARY KEY (id)) ENGINE = innodb -- (3)为主表创建索引 CREATE INDEX I_USER15_USER15EXT ON User15 (user15ext_id) -- (4)获取插入数据的ID序列值 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=?, ?, ?] -- (5)插入数据 INSERT INTO User15 (id, age, name, user15ext_id) VALUES (?, ?, ?, ?) [params=?, ?, ?, ?] INSERT INTO User15Ext (id, email, homeUrl) VALUES (?, ?, ?) [params=?, ?, ?] -- (6)查询数据 SELECT t0.id, t0.age, t0.name, t1.id, t1.email, t1.homeUrl FROM User15 t0 LEFT OUTER JOIN User15Ext t1 ON t0.user15ext_id = t1.id
上面实例中,@JoinColumn 注解没有添加任何属性,全部采用默认值。如果我们需要指定部分属性的值,可以参考下面例子:
/** * 可以不添加 @JoinColumn 注解,openJPA 将采用默认值,其中: * name 为 “目标表名加_目标表主键”,这里为 user15ext_id * referencedColumnName 为目标表的主键 * table 为当前源实体的表名 */ @OneToOne(optional = true, cascade = CascadeType.ALL) @JoinColumn(name = "user15ext_id") private User15Ext user15Ext; /** * 通过 table 属性指定字段 user15ext_id 所在的表名称 */ @OneToOne(optional = true, cascade = CascadeType.ALL) @JoinColumn(name = "user15ext_id", table = "user15") private User15Ext user15Ext; /** * 通过 referencedColumnName 属性指定 user15ext_id 外键引用 User15Ext 表中的那列 */ @OneToOne(optional = true, cascade = CascadeType.ALL) @JoinColumn(name = "user15ext_id", table = "user15", referencedColumnName = "id") private User15Ext user15Ext; /** * 通过 unique 指定 user15ext_id 列的值具有唯一性 */ @OneToOne(optional = true, cascade = CascadeType.ALL) @JoinColumn(name = "user15ext_id", table = "user15", referencedColumnName = "id", unique = true) private User15Ext user15Ext; /** * 在生成的 INSERT SQL 语句中不包含该字段,如果 insertable 为 true,sql语句如下: * INSERT INTO User15 (id, age, name, user15ext_id) VALUES (?, ?, ?, ?) [params=?, ?, ?, ?] * 如果 insertable 为 true,sql语句如下: * INSERT INTO User15 (id, age, name) VALUES (?, ?, ?) [params=?, ?, ?] */ @OneToOne(optional = true, cascade = CascadeType.ALL) @JoinColumn(name = "user15ext_id", table = "user15", referencedColumnName = "id", insertable = true) private User15Ext user15Ext;