0%

JPA使用指南-3

文章承自JPA使用指南-2

本文介绍映射关联关系

单向多对一;单向一对多;双向多对一;双向一对一;双向多对多

1.单向多对一

  • 创建Order实体类,Order与Customer的关系为多对一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.sust.entity;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "jpa_orders")
public class Order {
/**
* ·@ManyToOne·单向多对一,即多个Order对应一个Customer,在Order表中以Customer实体字段表现
* cascade 属性可以配置级联操作
* fetch 默认为 FetchType.EAGER
* ·@JoinColumn·指定关联中Order与CustomerId左外连接的列名(即Order表外键名)
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;

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

@Column(name = "order_name")
private String orderName;
}

运行测试后会自动创建jpa_orders表:

1
2
3
4
5
6
7
8
CREATE TABLE `jpa_orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_name` varchar(255) DEFAULT NULL,
`customer_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FKd24gr5y1e7xklyx35mqxf51g8` (`customer_id`),
CONSTRAINT `FKd24gr5y1e7xklyx35mqxf51g8` FOREIGN KEY (`customer_id`) REFERENCES `jpa_customers` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

2. 单向一对多

  • 准备
    • 注释掉Order实体中的Customer字段
    • 更改Customer,在其中添加Order集合字段
  • 运行测试后同样创建与单向多对一相同的jpa_orders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//省略掉了无关代码,完整Customer请见 指南-1
public class Customer {
/**
* ·@OneToMany·单向一对多,即一个Customer对应多个Order,在Customer中以Order集合的形式表现
* cascade 属性可以配置级联操作
* fetch 默认为 FetchType.LAZY
* ·@JoinColumn·映射外键列,注意这里生成的外键列照样在Order表中,与多对一生成的表结构相同
*/
@OneToMany
@JoinColumn(name = "customer_id")
private List<Order> orderList = new ArrayList<>();

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

3.双向多对一

  • 双向多对一 单向多对一 + 单向一对多

  • 注意相关联的 JoinColumnname 字段,也就是多端外键列名 应相等

    • Customer:
    1
    2
    3
    @OneToMany
    @JoinColumn(name = "customer_id")
    private List<Order> orderList = new ArrayList<>();
    • Order:
    1
    2
    3
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;
  • 这里注意,操作双向多对一时,我们应让端,即Order来维护关联关系,这样可以有效减少向数据库发送的为了维护关联关系所产生的sql语句

    简而言之就是持久化操作时,应先持久化Customer,再持久化Order

以上所展示的写法可以实现双向多对一,但是JPA为了维护关联关系会产生额外的sql语句(这里Customer与Order都定义了需要维护的字段)

故我们在使用双向多对一时应尽量采用以下方式:


  • Customer

    1
    2
    3
    4
    5
    6
    /**
    *这里的mappedBy所指向的是被@JoinColumn注解过的字段在java中的变量名
    *简单来说也就是将维护Customer与Order映射关系的任务都交给了Order
    */
    @OneToMany(fetch = FetchType.LAZY,mappedBy = "customer")
    private List<Order> orderList = new ArrayList<>();
  • Order:

    1
    2
    3
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;
  • 测试方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    @Test
    public void testDoubleManyToMany() {
    int i = 1;
    Customer customer = new Customer();
    customer.setAge(i);
    customer.setEmail(i + "@sust.edu.cn");
    customer.setLastName("testDoubleManyToMany_" + i);

    Order order1 = new Order();
    order1.setOrderName("Order_" + i);
    Order order2 = new Order();
    order2.setOrderName("Order_" + (i + 1));

    customer.getOrderList().add(order1);
    customer.getOrderList().add(order2);
    order1.setCustomer(customer);
    order2.setCustomer(customer);

    entityManager.persist(customer);
    entityManager.persist(order1);
    entityManager.persist(order2);

    customer.setEmail("!!!");
    customer.getOrderList().get(1).setOrderName(">>>???");

    List<Order> orderList = customer.getOrderList();
    System.out.println("--------");
    for (Order order : orderList) {
    System.out.println(order.getOrderName() + " : " +
    order.getCustomer().getEmail());
    }
    }
  • console输出中的主要部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    Hibernate: 

    create table jpa_customers (
    id integer not null auto_increment,
    age TINYINT,
    birthday date,
    create_time datetime(6),
    email varchar(255),
    last_name varchar(255) not null,
    update_time datetime(6),
    primary key (id)
    ) engine=InnoDB
    八月 24, 2019 7:32:58 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
    INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@1d035be3] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
    Hibernate:

    create table jpa_orders (
    id integer not null auto_increment,
    order_name varchar(255),
    customer_id integer,
    primary key (id)
    ) engine=InnoDB
    Hibernate:

    alter table jpa_orders
    add constraint FKd24gr5y1e7xklyx35mqxf51g8
    foreign key (customer_id)
    references jpa_customers (id)
    八月 24, 2019 7:32:58 下午 org.hibernate.tool.schema.internal.SchemaCreatorImpl applyImportSources
    INFO: HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@58faa93b'
    Hibernate:
    insert
    into
    jpa_customers
    (age, birthday, create_time, email, last_name, update_time)
    values
    (?, ?, ?, ?, ?, ?)
    Hibernate:
    insert
    into
    jpa_orders
    (customer_id, order_name)
    values
    (?, ?)
    Hibernate:
    insert
    into
    jpa_orders
    (customer_id, order_name)
    values
    (?, ?)
    --------
    Order_1 : !!!
    >>>??? : !!!
    Hibernate:
    update
    jpa_customers
    set
    age=?,
    email=?,
    update_time=?
    where
    id=?
    Hibernate:
    update
    jpa_orders
    set
    customer_id=?,
    order_name=?
    where
    id=?

4.双向一对一

  • 准备

    • 创建Department

      • manager 字段用@OneToOne来声明一对一关系,建议开启懒加载,否则查询会使用左外连接,效率比较差
      • @JoinColumn(name = "manager_id", unique = true) 则用来声明外键列名为department_id
      • 配置unique属性确保外键值唯一
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Data
      @Entity
      @Table(name = "jpa_departments")
      public class Department {
      @OneToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "manager_id", unique = true)
      private Manager manager;

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Integer id;
      @Column(nullable = false)
      private String departmentName;
      }
    • 创建Manager

      • department 字段用@OneToOne来声明一对一关系,需同样配置懒加载
      • 配置mappedBy属性,将维护工作交给Department的manager属性
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @Data
      @Entity
      @Table(name = "jpa_managers")
      public class Manager {

      @OneToOne(mappedBy = "manager",fetch = FetchType.LAZY)
      private Department department;

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Integer id;
      @Column(nullable = false)
      private String managerName;
      private Integer age;
      }
  • 插入测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Test
    public void testOneToOne() {
    Manager manager = new Manager();
    manager.setAge(19);
    manager.setManagerName("菜虚鲲");

    Department department = new Department();
    department.setDepartmentName("天庭");

    department.setManager(manager);
    manager.setDepartment(department);

    /**
    * 这里一定注意先持久化不维护关联关系的实体
    * 而由后持久化的对象负责维护关联关系
    * 否则多出的update外键列的语句会对效率有较大影响
    */
    entityManager.persist(manager);
    entityManager.persist(department);
    }

    5. 双向多对多

  • 创建Item

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Data
    @Entity
    @Table(name = "jpa_items")
    public class Item {
    @ManyToMany
    @JoinTable(name = "items_categories",
    joinColumns = {@JoinColumn(name = "items_id",
    referencedColumnName = "id")},
    inverseJoinColumns = {@JoinColumn(name = "categories_id",
    referencedColumnName = "id")}
    )
    private List<Category> categoryList;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "item_name")
    private String itemName;


    }
    • @joinColumns :映射关联表,name指定关联表表名
    • joinColumns属性:映射当前类所在表(jpa_items)在关联表(items_categories)中的外键(id)
    • inverseJoinColumns属性:映射关联的实体类所对应的表(jpa_categories)在关联表中的外键(id)
    • 注意@JoinColumn注解中,name属性只是对列的新命名,referencedColumnName才指定了主键,不过referencedColumnName可以省略,这样会默认映射到注释实体对应表的主键
    • 默认启用懒加载哦~
  • 创建Category

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Data
    @Entity
    @Table(name = "jpa_categories")
    public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "category_name")
    private String categoryName;

    @ManyToMany(mappedBy = "categoryList")
    private List<Item> itemList;
    }
    • 这里一定注意,多对多关系只配置一方(这里选择了Item)维护关系,故在另一方(Category),我们需要给@ManyToMany配置mappedBy属性(里面填存在维护关系注解的变量的名称)来托管维护
  • @JoinTable 所生成的表DDL:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CREATE TABLE `items_categories` (
    `items_id` int(11) NOT NULL,
    `categories_id` int(11) NOT NULL,
    KEY `FKovymsahbi6ua0ubka76qbvb90` (`categories_id`),
    KEY `FKn42kdeg73kpktyl367aw9ljck` (`items_id`),
    CONSTRAINT `FKn42kdeg73kpktyl367aw9ljck`
    FOREIGN KEY (`items_id`) REFERENCES `jpa_items` (`id`),
    CONSTRAINT `FKovymsahbi6ua0ubka76qbvb90`
    FOREIGN KEY (`categories_id`) REFERENCES `jpa_categories` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci