SpringDataJPA·编码部分

CY 2019年02月28日 1,052次浏览

第一部分笔记回顾

在第一部分中我们编写了Customer实体类:

package com.example.application.model;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "cst_customer")
@Data
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;

    @Column(name = "cust_address")
    private String custAddress;

    @Column(name = "cust_industry")
    private String custIndustry;

    @Column(name = "cust_level")
    private String custLevel;

    @Column(name = "cust_name")
    private String custName;

    @Column(name = "cust_phone")
    private String custPhone;

    @Column(name = "cust_source")
    private String custSource;
}

还写了一个Repository接口:

package com.example.application.repository;

import com.example.application.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}

在这一部分中我们就来真正的进行代码开发,以及解决一些代码开发中遇到的问题

增删改查

上面的Repository接口已经实现了两个接口,意味着这个Repository接口中就拥有了两个接口中定义的方法,这些方法都是可以使用的。

查询操作

package com.example.application;

import com.example.application.model.Customer;
import com.example.application.repository.CustomerRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.Optional;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataJpaDemoApplicationTests {

    @Resource
    private CustomerRepository repository;

    @Test
    public void findOne() {
        Optional<Customer> one = repository.findById(1L);
        System.out.println(one.orElseThrow(() -> new NullPointerException("没有查询到任何结果")));
    }
}

执行结果SQL:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?

不仅仅可以使用findById方法,还可以使用getOne方法

这两个方法有很大的区别

findById是立即加载,getOne是延迟加载

findById不需要开启事务,getOne需要开启事务@Transactional

findById无查询结果返回null,getOne无查询结果抛异常

添加操作

@Test
public void insert() {
    Customer customer = new Customer();
    customer.setCustName("CY");
    customer.setCustAddress("Beijing");
    customer.setCustLevel("1LV");
    repository.save(customer);
}

执行结果:

Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)

更新操作

@Test
public void update() {
    Customer customer = new Customer();
    customer.setCustId(2L); // 注意更新操作需要指定要更新的ID
    customer.setCustName("CY");
    customer.setCustAddress("Shanghai");
    // 更新操作的时候去掉了一个字段,数据表中就去掉了一个字段,不会保留原来字段的值
    repository.save(customer);
}

执行结果:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?
Hibernate: update cst_customer set cust_address=?, cust_industry=?, cust_level=?, cust_name=?, cust_phone=?, cust_source=? where cust_id=?

注意上面更新的时候少了一个set方法customer.setCustLevel("1LV");更新后数据表中CustLevel字段的值就消失掉了,解决的方法有很多,如果我是SpringDataJPA的设计者,我也会这样设计,因为万一用户在前台把某个值去掉了,后台没有接收到这个值,那是在数据库中保留这个值呢,还是删除这个值呢,很显然,是删除掉这个值。

添加和更新操作使用的都是save方法,save方法会判断ID是否为null,如果ID不为空就更新,为空就保存

删除操作

@Test
public void delete() {
    repository.deleteById(1L);
}

执行结果:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?
Hibernate: delete from cst_customer where cust_id=?

查询所有

@Test
public void findAll() {
    List<Customer> all = repository.findAll();
    for (Customer customer : all) {
        System.out.println(customer);
    }
}

执行结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_

其他操作

统计查询

@Test
public void count() {
    long count = repository.count();
    System.out.println(count);
}

执行结果:

Hibernate: select count(*) as col_0_0_ from cst_customer customer0_

是否存在

@Test
public void isExists() {
    boolean exists = repository.existsById(1L);
    System.out.println(exists);
}

执行结果:

Hibernate: select count(*) as col_0_0_ from cst_customer customer0_ where customer0_.cust_id=?

JPQL查询

public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

    @Query(value = "from Customer where custAddress = ?1")
    List<Customer> findByAddress(String address);
}

测试方法:

@Test
public void jpqlFind() {
    List<Customer> beijing = repository.findByAddress("Shanghai");
    for (Customer customer : beijing) {
        System.out.println(customer);
    }
}

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ where customer0_.cust_address=?

JPQL语句中的?1的解释参考《SpringDataJPA概述·第一部分》的笔记

JPQL更新

Repository接口中定义下面的方法

@Query(value = "update Customer set custAddress = :address where custId = :id")
@Modifying
void updateAddress(Long id, String address);

@Modifying表示该方法进行的是修改操作,必须添加该注解,否则报错

测试类:

@Test
@Transactional
@Rollback(value = false)
public void jpqlUpdate() {
    repository.updateAddress(2L, "Beijing");
}

@Transactional:修改操作一定要开启事务

@Rollback(value = false)SpringDataJPA默认是事务回滚,设置value为false,使其不自动回滚,因为自动回滚会让数据更新后又回滚到之前的数据,所以导致实际上是没有更新

测试结果

Hibernate: update cst_customer set cust_address=? where cust_id=?

SQL查询全部

Repository接口中:

@Query(value = "select * from cst_customer", nativeQuery = true)
List<Customer> findAllSQL();

测试类:

@Test
public void sqlFindAll() {
    List<Customer> allSQL = repository.findAllSQL();
    for (Customer customer : allSQL) {
        System.out.println(customer);
    }
}

运行结果:

Hibernate: select * from cst_customer

SQL条件查询

Repository接口中:

@Query(value = "select * from cst_customer where cust_address = ?", nativeQuery = true)
List<Customer> findByAddressSQL(String address);

测试代码

@Test
public void sqlFindParam() {
    List<Customer> beijing = repository.findByAddressSQL("Beijing");
    for (Customer customer : beijing) {
        System.out.println(customer);
    }
}

测试结果:

Hibernate: select * from cst_customer where cust_address = ?

使用SQL语句查询的时候,占位符相比于JPQL查询自由

方法命名规则查询

方法名称规范

普通条件查询

Repository接口中:

List<Customer> findAllByCustAddress(String address);

测试类:

@Test
public void methodNameQuery() {
    List<Customer> beijing = repository.findAllByCustAddress("Beijing");
    for (Customer customer : beijing) {
        System.out.println(customer);
    }
}

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ where customer0_.cust_address=?

模糊查询

Repository接口中:

List<Customer> findAllByCustAddressLike(String address);

测试类:

@Test
public void methodNameQueryLike() {
    List<Customer> beijing = repository.findAllByCustAddressLike("%eijin%");
    for (Customer customer : beijing) {
        System.out.println(customer);
    }
}

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ where customer0_.cust_address like ? escape ?

多条件查询

Repository接口中:

List<Customer> findAllByCustAddressLikeAndAndCustName(String address, String name);

测试类:

@Test
public void methodNameQueryLikeAnd() {
    List<Customer> beijing = repository.findAllByCustAddressLikeAndAndCustName("%eijin%", "CY");
    for (Customer customer : beijing) {
        System.out.println(customer);
    }
}

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ where (customer0_.cust_address like ? escape ?) and customer0_.cust_name=?

JpaSpecificationExecutor接口的使用

多条件查询

@Test
public void testJpaSpecificationExecutor() {

    Optional<Customer> one = repository.findOne((Specification<Customer>) (root, query, criteriaBuilder) -> {
        Path<Object> custAddress = root.get("custAddress");
        Path<Object> custName = root.get("custName");
        Predicate beijing = criteriaBuilder.equal(custAddress, "Beijing");
        Predicate cy = criteriaBuilder.equal(custName, "CY");
        return criteriaBuilder.and(beijing, cy);
    });
    System.out.println(one);
}

运行结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ where customer0_.cust_address=? and customer0_.cust_name=?

模糊查询

@Test
public void testJpaSpecificationExecutorLike() {

    List<Customer> list = repository.findAll((Specification<Customer>) (root, query, criteriaBuilder) -> {
        Path<Object> custAddress = root.get("custAddress");
        return criteriaBuilder.like(custAddress.as(String.class), "%eijin%");
    });
    for (Customer customer : list) {
        System.out.println(customer);
    }
}

运行结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ where customer0_.cust_address like ?

排序查询

@Test
public void testJpaSpecificationExecutorSort() {
    // 排序查询
    Sort orders = new Sort(Sort.Direction.DESC, "custId");
    List<Customer> list = repository.findAll(
        (Specification<Customer>) (root, query, criteriaBuilder) -> null,
        orders
    );
    for (Customer customer : list) {
        System.out.println(customer);
    }
}

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ order by customer0_.cust_id desc

分页查询

@Test
public void testJpaSpecificationExecutorPageable() {
    // 分页查询
    Pageable pageable = PageRequest.of(0, 10);
    Page<Customer> page = repository.findAll(
        (Specification<Customer>) (root, query, criteriaBuilder) -> null,
        pageable
    );
    long totalElements = page.getTotalElements(); // 总元素数
    int totalPages = page.getTotalPages(); // 总页数
    List<Customer> content = page.getContent(); // 查询内容
}

测试结果:

Hibernate: select customer0_.cust_id as cust_id1_0_, customer0_.cust_address as cust_add2_0_, customer0_.cust_industry as cust_ind3_0_, customer0_.cust_level as cust_lev4_0_, customer0_.cust_name as cust_nam5_0_, customer0_.cust_phone as cust_pho6_0_, customer0_.cust_source as cust_sou7_0_ from cst_customer customer0_ limit ?

SpringDataJPA多表操作

一对多

一的一方是主表,多的一方是从表

外键:需要在从表上新建一列作为外键,他的取值来源于主表的主键

创建联系人实体类

package com.example.application.model;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "cst_linkman")
@Getter
@Setter
public class LinkMan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId;

    @Column(name = "lkm_name")
    private String lkmName;

    @Column(name = "lkm_gender")
    private String lkmGender;

    @Column(name = "lkm_phone")
    private String lkmPhone;

    @Column(name = "lkm_mobile")
    private String lkmMobile;

    @Column(name = "lkm_email")
    private String lkmEmail;

    @Column(name = "lkm_position")
    private String lkmPosition;

    @Column(name = "lkm_memo")
    private String lkmMemo;
}

这里使用Lombok,但是在进行多表操作的时候出现了一个问题,原因是@Data自动生成了hashCode方法,所以这里不使用@Data注解,使用两个注解@Setter、@Getter来替代,当然,Customer实体类也要变,这个问题是一个坑,之前好像在看Halo博客的源码的时候看到了@EqualsAndHashCode(callSuper = true)然后不理解这个东西是什么意思,百度看了看好像就说的是这个问题,现在也找不到原来的文章了。

设置关联关系

Customer实体类中添加:

// 对方实体类的类型
@OneToMany(targetEntity = LinkMan.class)
// name 外键的名称,referencedColumnName参照的主键的列名
// 对客户而言也具备了维护外键的功能
@JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
private Set<LinkMan> linkMans = new HashSet<>();

LinkMan实体类中添加:

@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
private Customer customer;

创建一个测试类:

package com.example.application;

import com.example.application.model.Customer;
import com.example.application.model.LinkMan;
import com.example.application.repository.CustomerRepository;
import com.example.application.repository.LinkManRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import javax.transaction.Transactional;
import java.util.Arrays;

@RunWith(SpringRunner.class)
@SpringBootTest
public class OneToManyTest {

    @Resource
    private CustomerRepository customerRepository;

    @Resource
    private LinkManRepository linkManRepository;

    @Test
    @Transactional
    @Rollback(false)
    public void testSave() {
        Customer customer = new Customer();
        customer.setCustName("张三");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("张三的联系人1");

        LinkMan linkMan2 = new LinkMan();
        linkMan2.setLkmName("张三的联系人2");

        customer.getLinkMans().addAll(Arrays.asList(linkMan, linkMan2));

        customerRepository.save(customer);
        linkManRepository.saveAll(Arrays.asList(linkMan, linkMan2));
    }
}

运行结果:

建表语句:

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q

Hibernate: drop table if exists cst_customer

Hibernate: drop table if exists cst_linkman

Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id)) engine=InnoDB

Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id)) engine=InnoDB

Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)

添加语句:

Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)

Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)

Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)

Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

上面是在Customer(一的一方)添加了两个LinkMan(多的一方)

也可以在LinkMan添加Customer

@Test
@Transactional
@Rollback(false)
public void testSave() {
    Customer customer = new Customer();
    customer.setCustName("张三");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("张三的联系人1");

    LinkMan linkMan2 = new LinkMan();
    linkMan2.setLkmName("张三的联系人2");

    linkMan.setCustomer(customer);
    linkMan2.setCustomer(customer);

    customerRepository.save(customer);
    linkManRepository.saveAll(Arrays.asList(linkMan, linkMan2));
}

甚至可以互相关联

@Test
@Transactional
@Rollback(false)
public void testSave() {
    Customer customer = new Customer();
    customer.setCustName("张三");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("张三的联系人1");

    LinkMan linkMan2 = new LinkMan();
    linkMan2.setLkmName("张三的联系人2");

    customer.getLinkMans().addAll(Arrays.asList(linkMan, linkMan2));
    linkMan.setCustomer(customer);
    linkMan2.setCustomer(customer);

    customerRepository.save(customer);
    linkManRepository.saveAll(Arrays.asList(linkMan, linkMan2));
}

观察这三种代码的执行情况可以知道,除了Many的一端设置了One的一端,剩下的都要多执行两个update,这两个update方法用来设置外键

Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

我们可以不让多执行这两个update,可以将One的一方放弃外键的维护权:

@OneToMany(mappedBy = "customer")
private Set<LinkMan> linkMans = new HashSet<>();

mappedBy的意思是Customer放弃了外键的维护权,转交给LinkMencustomer属性上面定义的注解

这样操作的话就不会多执行两个update了,但是与此同时就无法使用customer.getLinkMans().addAll(Arrays.asList(linkMan, linkMan2));设置外键了

级联操作

之前添加的时候同时要写两个save方法,分别保存两个对象,级联操作的作用就是可以保存一个对象的同时保存另外一个对象。

例如:

添加LinkMan的同时添加Customer

@ManyToOne(targetEntity = Customer.class, cascade = CascadeType.PERSIST)
@JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
private Customer customer;

CascadeType.ALL级联操作所有

CascadeType.PERSIST级联操作保存

CascadeType.MERGE级联操作更新

CascadeType.REMOVE级联操作删除

CascadeType.REFRESH级联操作刷新(重新获取数据)

CascadeType.DETACH级联操作脱管/游离,如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。

测试级联添加操作

@Test
@Transactional
@Rollback(false)
public void testSave() {
    Customer customer = new Customer();
    customer.setCustName("张三");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("张三的联系人1");

    LinkMan linkMan2 = new LinkMan();
    linkMan2.setLkmName("张三的联系人2");

    linkMan.setCustomer(customer);
    linkMan2.setCustomer(customer);

    linkManRepository.saveAll(Arrays.asList(linkMan, linkMan2));
}

上面的代码中只添加了联系人,与此同时客户的数据也被添加了

如果要添加Customer的同时添加LinkMan,因为Customer没有外键的维护权,所以不能只使用customer.getLinkMans().addAll(Arrays.asList(linkMan, linkMan2));,因为这样做是不会有外键关联的。关联关系的设置应该是下面的样子:

customer.getLinkMans().addAll(Arrays.asList(linkMan, linkMan2));
linkMan.setCustomer(customer);
linkMan2.setCustomer(customer);

测试级联删除操作

@OneToMany(mappedBy = "customer", cascade = CascadeType.REMOVE)
private Set<LinkMan> linkMans = new HashSet<>();

测试代码:

@Test
@Transactional
@Rollback(false)
public void testDelete() {
    customerRepository.deleteById(1L);
}

删除了一个Customer,它底下对应的联系人就被删除了

多对多

中间表:中间表中最少应该由两个字段组成,这两个字段作为外键指向两张表的主键,又组成了联合主键

新建两个实体类:

package com.example.application.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Table(name = "sys_role")
@Getter
@Setter
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;

    @Column(name = "role_name")
    private String roleName;
}
package com.example.application.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Table(name = "sys_user")
@Getter
@Setter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

    private Integer age;
}

用户和角色的关系就是多对多的关系。

配置关联关系:

// 多对多,目标实体类为Role
@ManyToMany(targetEntity = Role.class)
@JoinTable(
    // 中间表的表名
    name = "sys_user_role",
    // 该实体类在中间表中的列名,和引用的主键名
    joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")},
    // 对方实体类在中间表中的列名,和引用的主键名
    inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();
@ManyToMany(targetEntity = User.class)
@JoinTable(
    name = "sys_user_role",
    joinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")},
    inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}
)
private Set<User> users = new HashSet<>();

多对多添加

@Test
@Transactional
@Rollback(false)
public void testSave() {
    User user = new User();
    user.setUserName("张三");

    Role role = new Role();
    role.setRoleName("产品经理");

    user.getRoles().add(role);

    userRepository.save(user);
    roleRepository.save(role);
}

运行结果

建表语句

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q

Hibernate: alter table sys_user_role drop foreign key FK1ef5794xnbirtsnudta6p32on

Hibernate: alter table sys_user_role drop foreign key FKsbjvgfdwwy5rfbiag1bwh9x2b

Hibernate: drop table if exists cst_customer

Hibernate: drop table if exists cst_linkman

Hibernate: drop table if exists sys_role

Hibernate: drop table if exists sys_user

Hibernate: drop table if exists sys_user_role

Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id)) engine=InnoDB

Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id)) engine=InnoDB

Hibernate: create table sys_role (role_id bigint not null auto_increment, role_name varchar(255), primary key (role_id)) engine=InnoDB

Hibernate: create table sys_user (user_id bigint not null auto_increment, age integer, user_name varchar(255), primary key (user_id)) engine=InnoDB

Hibernate: create table sys_user_role (sys_user_id bigint not null, sys_role_id bigint not null, primary key (sys_role_id, sys_user_id)) engine=InnoDB

Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)

Hibernate: alter table sys_user_role add constraint FK1ef5794xnbirtsnudta6p32on foreign key (sys_role_id) references sys_role (role_id)

Hibernate: alter table sys_user_role add constraint FKsbjvgfdwwy5rfbiag1bwh9x2b foreign key (sys_user_id) references sys_user (user_id)

添加语句

Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)

上面的代码是添加成功的,如果是下面的写法,就会添加失败:

@Test
@Transactional
@Rollback(false)
public void testSave() {
    User user = new User();
    user.setUserName("张三");

    Role role = new Role();
    role.setRoleName("产品经理");

    user.getRoles().add(role);
    role.getUsers().add(user);

    userRepository.save(user);
    roleRepository.save(role);
}

报错内容如下:

Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1-1' for key 'PRIMARY'
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	...

可以看到数据库的主键1-1重复了,这是因为两方都要维护中间表的主键,所以出现了问题

这个时候只需要一方放弃维护即可,被动的一方放弃维护权,我的个人理解:

比如说用户和角色,用户的身份肯定要比角色大,所以用户是最重要的表,所以让用户具有维护权,角色就放弃维护权

@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();

这个时候就执行成功了

多对多级联操作

加入级联操作

// 多对多,目标实体类为Role
@ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL)
@JoinTable(
    // 中间表的表名
    name = "sys_user_role",
    // 该实体类在中间表中的列名,和引用的主键名
    joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")},
    // 对方实体类在中间表中的列名,和引用的主键名
    inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();

添加用户的同时添加角色

@Test
@Transactional
@Rollback(false)
public void testSaveCascade() {
    User user = new User();
    user.setUserName("张三");

    Role role = new Role();
    role.setRoleName("产品经理");

    user.getRoles().add(role);

    userRepository.save(user);
}

删除用户的同时删除角色(慎用)

@Test
@Transactional
@Rollback(false)
public void testDeleteCascade() {
    userRepository.deleteById(1L);
}

对象导航查询

对象导航查询的意思是:通过查询一个对象,查询出对象后调用其get方法可以查出对象里面的关联对象

@Test
@Transactional
public void testObjectNavQuery() {
    Optional<Customer> byId = customerRepository.findById(1L);
    byId.ifPresent(customer -> {
        Set<LinkMan> linkMans = customer.getLinkMans();
        for (LinkMan linkMan : linkMans) {
            System.out.println(linkMan);
        }
    });
}

执行的SQL

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?

Hibernate: select linkmans0_.lkm_cust_id as lkm_cust9_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_cust_id as lkm_cust9_1_1_, linkmans0_.lkm_email as lkm_emai2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_memo as lkm_memo4_1_1_, linkmans0_.lkm_mobile as lkm_mobi5_1_1_, linkmans0_.lkm_name as lkm_name6_1_1_, linkmans0_.lkm_phone as lkm_phon7_1_1_, linkmans0_.lkm_position as lkm_posi8_1_1_ from cst_linkman linkmans0_ where linkmans0_.lkm_cust_id=?

上面的查询在使用到linkMan的时候才进行了一次linkMan的查询,如果不想使用延迟加载的形式需要在Customer中配置立即加载

@OneToMany(mappedBy = "customer", cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>();
@Test
@Transactional
public void testObjectNavQuery() {
    Optional<Customer> byId = customerRepository.findById(1L);
    System.out.println(byId.orElseThrow(() -> new NullPointerException("无查询结果")));
}

运行的SQL:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_, linkmans1_.lkm_cust_id as lkm_cust9_1_1_, linkmans1_.lkm_id as lkm_id1_1_1_, linkmans1_.lkm_id as lkm_id1_1_2_, linkmans1_.lkm_cust_id as lkm_cust9_1_2_, linkmans1_.lkm_email as lkm_emai2_1_2_, linkmans1_.lkm_gender as lkm_gend3_1_2_, linkmans1_.lkm_memo as lkm_memo4_1_2_, linkmans1_.lkm_mobile as lkm_mobi5_1_2_, linkmans1_.lkm_name as lkm_name6_1_2_, linkmans1_.lkm_phone as lkm_phon7_1_2_, linkmans1_.lkm_position as lkm_posi8_1_2_ from cst_customer customer0_ left outer join cst_linkman linkmans1_ on customer0_.cust_id=linkmans1_.lkm_cust_id where customer0_.cust_id=?

从多的一方查询一的一方

@Test
@Transactional
public void testObjectNavQueryManyToOne() {
    Optional<LinkMan> byId = linkManRepository.findById(1L);
    byId.ifPresent(linkMan -> {
        Customer customer = linkMan.getCustomer();
        System.out.println(customer);
    });
}

执行的SQL:

Hibernate: select linkman0_.lkm_id as lkm_id1_1_0_, linkman0_.lkm_cust_id as lkm_cust9_1_0_, linkman0_.lkm_email as lkm_emai2_1_0_, linkman0_.lkm_gender as lkm_gend3_1_0_, linkman0_.lkm_memo as lkm_memo4_1_0_, linkman0_.lkm_mobile as lkm_mobi5_1_0_, linkman0_.lkm_name as lkm_name6_1_0_, linkman0_.lkm_phone as lkm_phon7_1_0_, linkman0_.lkm_position as lkm_posi8_1_0_, customer1_.cust_id as cust_id1_0_1_, customer1_.cust_address as cust_add2_0_1_, customer1_.cust_industry as cust_ind3_0_1_, customer1_.cust_level as cust_lev4_0_1_, customer1_.cust_name as cust_nam5_0_1_, customer1_.cust_phone as cust_pho6_0_1_, customer1_.cust_source as cust_sou7_0_1_ from cst_linkman linkman0_ left outer join cst_customer customer1_ on linkman0_.lkm_cust_id=customer1_.cust_id where linkman0_.lkm_id=?

从多的一方查询一的一方的时候直接使用的就是立即加载,如果想让其变为延迟加载,需要修改代码:

@ManyToOne(targetEntity = Customer.class, cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
private Customer customer;

查看执行的SQL:

Hibernate: select linkman0_.lkm_id as lkm_id1_1_0_, linkman0_.lkm_cust_id as lkm_cust9_1_0_, linkman0_.lkm_email as lkm_emai2_1_0_, linkman0_.lkm_gender as lkm_gend3_1_0_, linkman0_.lkm_memo as lkm_memo4_1_0_, linkman0_.lkm_mobile as lkm_mobi5_1_0_, linkman0_.lkm_name as lkm_name6_1_0_, linkman0_.lkm_phone as lkm_phon7_1_0_, linkman0_.lkm_position as lkm_posi8_1_0_ from cst_linkman linkman0_ where linkman0_.lkm_id=?

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?

总结:

从一方查询多方的时候默认使用的是延迟加载,从多方查询一方的时候默认使用的是立即加载