JoinColumn vs mappedBy

Stackoverflow有一道题JoinColumn vs mappedBy很有意思:

@Entity
public class Company {
    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
...
}
@Entity
public class Company {
    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}

上面两种级联有什么区别?

原题下面有两个高分答案。可惜对于我这种学渣,看完还是一头雾水。

看不懂别人写的,那就自己调调看。

准备环境

磨刀不误砍柴工,先准备调试环境。

我用的是Spring Boot 2.0 + Spring Data JPA 2.0, Hibernate版本是5.2.14。

首先在Spring Boot的application.yml里开启Hibernate的调试选项。

spring:
  jpa:
    properties:
      hibernate:
        show_sql: true  # 打印SQL语句
        format_sql: true  # 格式化SQL语句
        use_sql_comments: true  # 增加注释信息,就知道语句对应的Entity类型了
        generate_statistics: true  # 统计信息,给出了每一步的耗时信息

还要在log配置开启org.hibernate.typedebug级别的日志信息。我用的是log4j2,就需要在log4j2.yml文件中添加:

Configuration:
  Loggers
    Logger:
      - name: org.hibernate.type
        additivity: false
        level: trace  # 这个最关键
        AppenderRef:
          - ref: CONSOLE
          - ref: ROLLING_FILE

第一次测试

原题里使用的是“公司-部门”模型,我这里本地已经有现成的“部门-雇员”模型,就直接复用了。道理是一样的。

@Entity
@Table(name="department_demo")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "departmentId")
    private Set<Employee> employeeSet = new HashSet<>();

    // setters & getters
}
@Entity
@Table(name="employee_demo")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private Integer departmentId;

    // setters & getters
}

在这里,Department使用主键id一对多的关联于Employee的departmentId属性。

然后就是保存一个对象看看。

@Service
public class DepartmentService {

    @Autowired
    DepartmentRepository departmentRepository;

    @PostConstruct
    public void insertNewRecord() {

        Department department = new Department();
        department.setName("Leader");
        departmentRepository.save(department);

        Employee emily = new Employee();
        emily.setName("David");

        Employee alice = new Employee();
        alice.setName("Wang Dali");

        department.addEmployee(emily);
        department.addEmployee(alice);

        departmentRepository.save(department);

    }
}

启动之后发现log里是这样打印的。

2018-04-22 09:11:22,155:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
    0 nanoseconds spent acquiring 0 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    0 nanoseconds spent preparing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
    /* insert com.example.demo.model.PO.Department
        */ insert
        into
            department_demo
            (name)
        values
            (?)
2018-04-22 09:11:22,314:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [VARCHAR] - [Leader]
Hibernate:
    select
        currval(‘department_demo_id_seq‘)
2018-04-22 09:11:22,438:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
    733681 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    6711770 nanoseconds spent preparing 2 JDBC statements;
    75097150 nanoseconds spent executing 2 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    14646304 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 1 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
    /* load com.example.demo.model.PO.Department */ select
        department0_.id as id1_1_1_,
        department0_.name as name2_1_1_,
        employeese1_.department_id as departme2_2_3_,
        employeese1_.id as id1_2_3_,
        employeese1_.id as id1_2_0_,
        employeese1_.department_id as departme2_2_0_,
        employeese1_.name as name3_2_0_
    from
        department_demo department0_
    left outer join
        employee_demo employeese1_
            on department0_.id=employeese1_.department_id
    where
        department0_.id=?
2018-04-22 09:11:22,460:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [33]
2018-04-22 09:11:22,499:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([id1_2_0_] : [INTEGER]) - [null]
2018-04-22 09:11:22,502:TRACE restartedMain (BasicExtractor.java:61) - extracted value ([name2_1_1_] : [VARCHAR]) - [Leader]
2018-04-22 09:11:22,503:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([departme2_2_3_] : [INTEGER]) - [null]
Hibernate:
    /* insert com.example.demo.model.PO.Employee
        */ insert
        into
            employee_demo
            (department_id, name)
        values
            (?, ?)
2018-04-22 09:11:22,516:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [33]
2018-04-22 09:11:22,516:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [David]
Hibernate:
    select
        currval(‘employee_demo_id_seq‘)
Hibernate:
    /* insert com.example.demo.model.PO.Employee
        */ insert
        into
            employee_demo
            (department_id, name)
        values
            (?, ?)
2018-04-22 09:11:22,526:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [33]
2018-04-22 09:11:22,527:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [Wang Dali]
Hibernate:
    select
        currval(‘employee_demo_id_seq‘)

第二次测试

第二次将Department的级联注解做了修改。

@Entity
@Table(name="department_demo")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    @JoinColumn(name="departmentId", referencedColumnName = "id")
    private Set<Employee> employeeSet = new HashSet<>();

打印的log是这样子。

2018-04-22 09:16:29,366:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
    0 nanoseconds spent acquiring 0 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    0 nanoseconds spent preparing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
    /* insert com.example.demo.model.PO.Department
        */ insert
        into
            department_demo
            (name)
        values
            (?)
2018-04-22 09:16:29,478:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [VARCHAR] - [Leader]
Hibernate:
    select
        currval(‘department_demo_id_seq‘)
2018-04-22 09:16:29,513:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
    743154 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    4202462 nanoseconds spent preparing 2 JDBC statements;
    9628594 nanoseconds spent executing 2 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    11708469 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 1 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
    /* load com.example.demo.model.PO.Department */ select
        department0_.id as id1_1_1_,
        department0_.name as name2_1_1_,
        employeese1_.department_id as departme2_2_3_,
        employeese1_.id as id1_2_3_,
        employeese1_.id as id1_2_0_,
        employeese1_.department_id as departme2_2_0_,
        employeese1_.name as name3_2_0_
    from
        department_demo department0_
    left outer join
        employee_demo employeese1_
            on department0_.id=employeese1_.department_id
    where
        department0_.id=?
2018-04-22 09:16:29,529:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,538:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([id1_2_0_] : [INTEGER]) - [null]
2018-04-22 09:16:29,541:TRACE restartedMain (BasicExtractor.java:61) - extracted value ([name2_1_1_] : [VARCHAR]) - [Leader]
2018-04-22 09:16:29,542:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([departme2_2_3_] : [INTEGER]) - [null]
Hibernate:
    /* insert com.example.demo.model.PO.Employee
        */ insert
        into
            employee_demo
            (department_id, name)
        values
            (?, ?)
2018-04-22 09:16:29,552:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,552:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [Wang Dali]
Hibernate:
    select
        currval(‘employee_demo_id_seq‘)
Hibernate:
    /* insert com.example.demo.model.PO.Employee
        */ insert
        into
            employee_demo
            (department_id, name)
        values
            (?, ?)
2018-04-22 09:16:29,555:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,556:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [David]
Hibernate:
    select
        currval(‘employee_demo_id_seq‘)
Hibernate:
    /* create one-to-many row com.example.demo.model.PO.Department.employeeSet */ update
        employee_demo
    set
        department_id=?
    where
        id=?
2018-04-22 09:16:29,576:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,577:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [INTEGER] - [30]
Hibernate:
    /* create one-to-many row com.example.demo.model.PO.Department.employeeSet */ update
        employee_demo
    set
        department_id=?
    where
        id=?
2018-04-22 09:16:29,595:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,596:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [INTEGER] - [29]
2018-04-22 09:16:29,600:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
    225339 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    731494 nanoseconds spent preparing 7 JDBC statements;
    24985288 nanoseconds spent executing 7 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    35806114 nanoseconds spent executing 1 flushes (flushing a total of 3 entities and 1 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

简单分析

对比log,我们可以看出,两次持久化新的employee对象时,都会:

  1. 执行一次left join查询语句,查询department关联的所有employee对象
  2. 每一个新的employee对象,都会执行一次插入操作

不同之处在于,第二次使用JoinColumn注解的时候,log显示:每一个新增employee对象都执行了一次update语句,更新了外键

原因就在于,mappedBy将外键的赋值操作委托给了Employee对象。而JoinColumn则选择由Department对象自己来约束外键的关联。

两个注解只有少许区别,但是最终的执行结果差异却很大。多出来的写操作,在生产环境下很容易对数据库构成很大的压力。在代码中完成对Employee对象departmentId属性的赋值,显然是一个更为合适的方案。

实际上,上面两个方案是JPA API文档OneToMany注解的示例2、示例3.

示例1的双向注解,也可以避免多余的写操作。

参考资料

  1. [JavaEE - JPA] 性能优化: 如何定位性能问题
  2. The best way to map a @OneToMany relationship with JPA and Hibernate

原文地址:https://www.cnblogs.com/rim99/p/8905876.html

时间: 2025-01-23 13:20:32

JoinColumn vs mappedBy的相关文章

Hibernate中,mappedBy和注解@JoinColumn的对比

mappedBy 我们知道,mappedBy用于指定具有双向关系的两个实体中.哪个实体是被关联处理的.它有如下四个特点: 1.只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,ManyToOne不存在该属性: [email protected](mapped="由One的一方指向Many的一方,并且,这个属性应该等于Many的一方中含有One类的属性的对象名,否则会出错") 3.关系的拥有方(即Many的一方)负责关系的维护,在拥有方建立外键.所

hibernate基于注解的维护权反转:@OneToMany(mappedBy=)

背景说明:首先是SSH环境下,对象基于注解的方式映射到数据库: 昨天遇到一个比较纠结的问题,@OneToMany(mappedBy="xxx"), mappedBy属性有什么用,然后是写在哪一边? 还有一个问题是:@JoinColumn(name="xxxxx"),JoinColumn有什么用? 先贴出最初的代码:一些基本的注解,在一对多的关系上没有使用JoinColumn和mappedBy属性 部门类:主要是第33.34行 1 package com.lizhou

(原创)hibernate 一对多建表实例详解 附上各个注释的含义

这个是hibernate的一对多建表实例:一的一端是部门(Department),对的一端是员工(Employee),下面贴上成员源代码:其中@mappedBy是加在@OneToMany一端,并且它的name属性=多的那一端(N端)属性是一的那一端的属性名,mappedBy是定义在Department类中,即Department类不负责维护级联关系.即维护者是Employee类 Department类: package com.javabean; import java.io.Serializa

双向一对一

@OneToOne注解用来一对一映射: 需要在被维护关系的一端加上属性 mappedBy 来表示放弃维护关联关系: 例如:可以建立两个类 Company.Boss: 假设一个公司只有一个老板,而一个老板只有一个公司:只是这么假设: Boss类: 维护关联关系的一端需要用@JoinColumn注解的name 属性来指定外键名: 注意:由于是一对一映射,需要加上unique=true属性: @JoinColumn(name="b_id",unique=true) @OneToOne pri

Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn【报错】

自己的项目没有测通  可能是自己项目原因--因为自己项目中级联关系的类涉及太多 自己的项目[这样的配置报错] @OneToMany(fetch=FetchType.LAZY,cascade = { CascadeType.REMOVE }, mappedBy="scProjectTecApply") @JoinColumn(name="busid") public List<ScTeam> getTeams() { return teams; } [修改

。。。Hibernate中mappedBy属性。。。

今天在学习Hibernate中,感觉这个mappedBy这个注解属性有点小难度.不过理解之后,还是阔以的! 首先,mappedBy这个注解只能够用在@OntToOne,@OneToMany,@manyToMany中,不能够用在@manyToOne中: 第二,这个注解看网上的意思可以简单地理解为:这个注解用在主表的一方,就是被引用的一方: 第三,这个注解是与@JoinColumn这个注解是互斥的,因为@JoinColumn这个注解使用在拥有外键的表的一方,就是从表的一方. 第四,这个注解的属性值是

hibernate的注解属性mappedBy详解

mappedBy: 1>只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,ManyToOne不存在该属性: 2>mappedBy标签一定是定义在被拥有方的,他指向拥有方: 3>mappedBy的含义,应该理解为,拥有方能够自动维护跟被拥有方的关系,当然,如果从被拥有方,通过手工强行来维护拥有方的关系也是可以做到的: 4>mappedBy跟joinColumn/JoinTable总是处于互斥的一方,可以理解为正是由于拥有方的关联被拥有方的字段存

Hibernate之mappedBy【必读】

[http://www.cnblogs.com/redcoatjk/p/4236445.html] 一.mappedBy 单向关系不需要设置该属性,双向关系必须设置,避免双方都建立外键字段 数据库中1对多的关系,关联关系总是被多方维护的即外键建在多方,我们在单方对象的@OneToMany(mappedby=" ") 把关系的维护交给多方对象的属性去维护关系 对于mappedBy复习下: a) 只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,Ma

JPA OneToOne,OneToMany和ManyToMany的mappedBy属性

在四种关联关系OneToOne,OneToMany,ManyToOne和ManyToMany中,只有OneToOne.OneToMany和ManyToMany这三中关联关系有mappedBy属性. 下面是mappedBy属性在java doc里边的解释: the field that owns the relationship. Required unless the relationship is unidirectional. 翻译成中文是: 拥有关联关系的域,如果关系是单向的就不需要. 那