第一部分笔记回顾
在第一部分中我们编写了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
放弃了外键的维护权,转交给LinkMen
中customer
属性上面定义的注解
这样操作的话就不会多执行两个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=?
总结:
从一方查询多方的时候默认使用的是延迟加载,从多方查询一方的时候默认使用的是立即加载