@OneToOne 注解

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