SpringDataJpa中的复杂查询和动态查询,多表查询。

在前面的章节已经讲述了SpringDataJpa的CRUD操作以及其底层代理实现的分析,下面介绍SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)

文章字数较多,请各位按需阅读。

不清楚JPA的小伙伴可以参考这篇文章:JPA简介

不清楚SpringDataJPA环境搭建的小伙伴可以参考这篇文章:SpringDataJPA入门案例

想了解SpringDataJPA代理类实现过程可以参考这篇文章:SpringDadaJPA底层实现原理

如需转载,请注明出处。

1.复杂查询

i.方法名称规则查询

方法名查询:只需要按照SpringDataJpa提供的方法名称规则去定义方法,在dao接口中定义方法即可。

其中对于方法的名称有一套约定。

KeyWord Sample JPQL
And findByLastnameAndFirstname where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname where x.lastname = ?1 or x.firstname = ?2
Between findByAgeBetween where x.Age between ?1 and ?2
LessThan findByAgeLessThan where x.age < ?1
GreaterThan findByAgeGreaterThan where x.age > ?1
Like findByFirstnameLike where x.firstname like ?1
NotLike findByFirstnameNotLike where x.firstname not like ?1
TRUE findByActiveTrue() where x.active = true
FALSE findByActiveFalse() where x.active = false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 方法名的约定:
* findBy:查询
* 对象中的属性名(首字母大写):查询条件
* *默认情况:使用 =的方式查询
* 特殊的查询方式,比如模糊查询
* findByCustName-----根据客户名称查询 findBy表示要查询 CustName属性名
* springDataJpa在运行阶段
* 会根据方法名称进行解析 findBy from XXX(实体类)
* 属性名称 where custName
* 1. findBy+属性名称(根据属性名称进行完成匹配任务)
* 2. findBy+属性名称+查询方式(Like|isnull)
* 3. 多条件查询
* findBy+属性名称+查询方式+多条件连接符(and|or)+属性名+查询方式
*/
public List<Customer> findByCustName(String name);
//查询id为3且name中含有大学的用户
public Customer findByCustId(Long id);
public Customer findByCustIdAndCustNameLike(Long id,String name);
}

ii.JPQL查询

使用 Spring Data JPA 提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来
说,我们还需要灵活的构造查询条件,这时就可以使用@Query 注解,结合 JPQL 的语句方式完成
查询 。

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个 JPQL 查询语句即可

注意:

通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询 。

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
复制代码public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 1.根据客户名称查询客户
* jpql:from Customer where custName=?
*/
@Query(value="from Customer where custName =?")
public List<Customer> findCustomerJpql(String name);
/**
* 2.根据客户名称和客户id查询
* 对多个占位符参数
* 默认情况下,占位符的位置需要和方法参数中的位置保持一致
* 也可以指定占位符参数的位置(注意:中间不要有空格)
* ? 索引的方式,指定此占位符的取值来源 eg ?2表示此占位符对应第二个参数
*/
@Query(value="from Customer where custName=?2 and custId=?1")
public Customer findByNameAndId(Long id,String name);
/**
* 3.根据id更新客户的name
* sql:update cst_customer set cust_name=? where cust_id=?
* jpql:update Customer set custName=? where custId=?
*
* @query:代表的是进行查询
* 需要声明此方法是执行更新操作
* 使用 @Modifying
*/
@Query(value = "update Customer set custName=? where custId=?")
@Modifying
public void updateCustomerName(String name,Long id);
}

注意:在执行springDataJpa中使用jpql完成更新,删除操作时,需要手动添加事务的支持 必须的;因为默认会执行结束后,回滚事务。

1
2
3
4
5
6
复制代码 @Test
@Transactional//添加事务的支持
@Rollback(value = false)
public void updateCustomerName(){
customerDao.updateCustomerName("学生公寓",4L);
}

iii.SQL查询

Spring Data JPA 同样也支持 sql 语句的查询,如下:

1
2
3
4
5
6
7
8
9
10
复制代码/**
* 查询所有用户:使用sql查询
* Sql:select * from cst_customer
* nativeQuery = true配置查询方式,true表示Sql查询,false表示Jpql查询
* 注意:返回值是一个Object[]类型的list
*/
// @Query(value = "select * from cst_customer",nativeQuery = true)
// public List<Object []>findSql();
@Query(value = "select * from cst_customer where cust_name like ?",nativeQuery = true)
public List<Object []>findSql(String name);

2.动态查询

springdatajpa的接口规范:

  • JpaRepository<操作的实体类型,实体类型中的 主键 属性的类型>

封装了基本的CRUD的操作,分页等;

  • JpaSpecificationExecutor<操作的实体类类型>

封装了复杂查询。

上述查询方法使用到的是接口JpaRepository中的方法,下面分析JpaSpecificationExecutor中的方法。

i.为什么需要动态查询

可能有些许疑惑,为什么还需要动态查询呢?有时候我们在查询某个实体的时候哦,给定的查询条件不是固定的,这个时候就需要动态构建相应的查询语句,可以理解为上述的查询条件是定义在dao接口中的,而动态查询条件定义在实现类中。

ii.JpaSpecificationExecutor中定义的方法

1
2
3
4
5
6
7
8
9
10
11
复制代码public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> var1);

List<T> findAll(Specification<T> var1);

Page<T> findAll(Specification<T> var1, Pageable var2);

List<T> findAll(Specification<T> var1, Sort var2);

long count(Specification<T> var1);
}

在上述方法中,我们可以看到接口Specification。可以简单理解为,Specification构造的就是查询条件。我们看看Specification中定义的方法。

1
2
3
4
5
6
7
8
复制代码/*
* root :T表示查询对象的类型,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

与上述查询方法不同,复杂查询定义在dao接口中,而动态查询定义在实现类中。

1)单条件查询

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
复制代码@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
@Test
public void conditionTest(){
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询对象类型,需要那个对象就写哪个泛型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借书方法参数中的两个形参
* root:用于获取查询的对象属性
* CriteriaBuilder:构造查询条件,内部封装了很多的查询条件(例如:模糊匹配,精准匹配)
* 需求:根据客户名称查询,查询客户名称为大学
* 查询条件
* 1.查询方法 (精准匹配,是否为空...)
* CriteriaBuilder对象
* 2.比较的属性名称(与哪个字段去以什么方式去比较)
* root对象
*/

Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//1.获取比较的属性(不是字段名)
Path<Object> custName = root.get("custName");
//2.构造查询条件
/**
* 第一个参数:需要比较的属性(Path)
* 第二个参数:当前比较的取值
*/
Predicate predicate = cb.equal(custName, "三峡大学");//进行精准匹配 (比较的属性,比较的属性的取值)
return predicate;
}
};
//根据返回的对象个数选择findOne或者findAll
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
}

2)多条件查询

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
复制代码@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 多条件查询:根据用户名和所属行业进行查询
* root:获取属性
* 用户名
* 所属行业
* cb:构造查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3,将以上两个查询联系起来
*/
@Test
public void findByNmaeAndIndustray(){
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//1.获取属性
Path<Object> custName = root.get("custName");
Path<Object> industry = root.get("custIndustry");
//2.构造查询
Predicate p1 = cb.equal(custName, "6测试数据-coderxz");
Predicate p2 = cb.equal(industry, "6测试数据-java工程师");
//3。将多个查询条件组合到一起(and/or)
Predicate predicate = cb.and(p1, p2);
return predicate;
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
}

3)模糊查询

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
复制代码@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 案例:根据客户名称进行模糊配置,返回客户列表
*
* equal:直接的path对象(属性),然后直接进行比较即可
*
* 对于gt,lt,le,like:得到path对象,根据path对象指定比较参数的类型(字符串or数字...),再进行比较
* 指定参数类型 path.as(类型的字节码对象)
*/
@Test
public void findVagueCustomer(){
Specification<Customer>spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
return predicate;
}
};
List<Customer> customers = customerDao.findAll(spec);
for(Customer c:customers){
System.out.println(c);
}
}
}

4)分页查询

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
复制代码@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 分页查询
* findAll(Pageable) 没有条件的分页查询
* findAll(Specification,Pageable)
* Specification查询条件
* Pageable分页参数 查询的页码,每页查询的条件
* 返回:Pahe(StringDataJpa)为我们封装好的pageBean对象,数据列表,
*/
@Test
public void pageCustomer(){
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return null;
}
};
/**
* Pageable 接口
* PageRequest是其实现类
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
* 注意:在新版本的jpa中,此方法已过时,新方法是PageRequest.of(page,size)
*/
Pageable pageable = new PageRequest(0,1);
//分页查询 page是SpringDataJpa为我们封装的一个JavaBean
Page<Customer> page = customerDao.findAll(spec, pageable);
//获得总页数(这些数据需要分几页)
System.out.println("查询总页数:"+page.getTotalPages());
//获得总记录数(数据库的总记录数)
System.out.println("查询总记录数:"+page.getTotalElements());
//得到数据集合列表
System.out.println("数据集合列表:"+page.getContent());
}
}

5)对查询结果进行排序

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
复制代码@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 对查询结果进行排序
*/
@Test
public void findSortCustomer(){
Specification<Customer>spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
return predicate;
}
};
/**
*创建排序对象,需要调用构造方法实例化对象
* 第一个参数:排序的顺序(正序,倒序)
* sort.Direction.DESC:倒序
* sort.Direction.ASC:升序
* 第二个参数:排序的属性名称
*/
Sort sort = new Sort(Sort.Direction.DESC, "custId");
List<Customer> customers = customerDao.findAll(spec,sort);
for(Customer c:customers){
System.out.println(c);
}
}
}

3.多表查询

上述复杂查询和动态查询都是基于单表查询,只需要指定实体类与数据库表中一对一的映射。而多表查询需要修改实体类之间的映射关系。

在数据库中表与表之间,存在三种关系:多对多、一对多、一对一。

多表查询01

那么与之对应的实体映射也应该有三种关系。那么在JPA中表的关系如何分析呢?

1.建立表与表之间的关系

  • 第一步:首先确定两张表之间的关系。
    如果关系确定错了,后面做的所有操作就都不可能正确。
  • 第二步:在数据库中实现两张表的关系
  • 第三步:在实体类中描述出两个实体的关系
  • 第四步:配置出实体类和数据库表的关系映射(重点)

4.JPA中的一对多

案例分析:

采用两个实体对象:公司与员工

在不考虑兼职的情况下,每名员工对应一家公司,每家公司有多名员工。

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对
多的关系,需要使用数据库的外键约束。

**什么是外键?**指的是从表中有一列,取值参照主表中的主键,这一列就是外键。

springdatajpa进阶01

数据库表:

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
复制代码CREATE TABLE `cst_customer` (
`cust_id` bigint(20) NOT NULL AUTO_INCREMENT,
`cust_address` varchar(255) DEFAULT NULL,
`cust_industry` varchar(255) DEFAULT NULL,
`cust_level` varchar(255) DEFAULT NULL,
`cust_name` varchar(255) DEFAULT NULL,
`cust_phone` varchar(255) DEFAULT NULL,
`cust_source` varchar(255) DEFAULT NULL,
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;

CREATE TABLE `cst_linkman` (
`lkm_id` bigint(20) NOT NULL AUTO_INCREMENT,
`lkm_email` varchar(255) DEFAULT NULL,
`lkm_gender` varchar(255) DEFAULT NULL,
`lkm_memo` varchar(255) DEFAULT NULL,
`lkm_mobile` varchar(255) DEFAULT NULL,
`lkm_name` varchar(255) DEFAULT NULL,
`lkm_phone` varchar(255) DEFAULT NULL,
`lkm_position` varchar(255) DEFAULT NULL,
`lkm_cust_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`lkm_id`),
KEY `FKh9yp1nql5227xxcopuxqx2e7q` (`lkm_cust_id`),
CONSTRAINT `FKh9yp1nql5227xxcopuxqx2e7q` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

1.建立实体与表之间的映射关系

注意:使用的注解都是JPA规范的,导包需要导入javac.persistence下的包

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
复制代码package ctgu.pojo;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
*我们需要配置:
* 1.实体类与表的映射关系(此pojo与数据库中的那一张表关系映射)
* @ Entity
* @ Table(name="cst_customer")name表示数据库中表的名称
* 2.实体类中属性与表中字段的映射关系
* @ Id声明主键的设置
* @ GeneratedValue配置主键是生成策略(自动增长)
* strategy=
* GenerationType.IDENTITY:自增 Mysql(底层数据库支持的自增长方式对id自增)
* GenerationType.SEQUENCE:序列 Oracle(底层数据库必须支持序列)
* GenerationType.TABLE:jpa提供的一种机制,通过一张数据库表的形式帮助我们完成自增
* GenerationType.AUTO:有程序自动的帮助我们选择主键生成策略
* @ Column(name = "cust_id")数据库中表中字段的名字
*/
@Entity
@Table(name = "cst_customer")
public class Customer {
/**
* @ Id声明主键的设置
* @ GeneratedValue配置主键是生成策略(自动增长)
* GenerationType.IDENTITY
* @ Column(name = "cust_id")数据库中表中字段的名字
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
/**
* 配置客户与联系人之间的关系(一个客户对应多个联系人)
* 使用注解的形式配置多表关系
* 1 声明关系
* @ OnetoMany:配置一对多关系
* targetEntity:对方对象的字节码对象
* 2.配置外键(中间表)
* @ JoinColumn
* name:外键的在从表的字段名称(不是属性,是数据库的字段名称)
* referencedColumnName:参照的主表的字段名称
*/
@OneToMany(targetEntity = LinkMan.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Set<LinkMan> linkMans=new HashSet<>();
/*
get/set/toString()方法略......
*/
}
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
复制代码package ctgu.pojo;
import javax.persistence.*;
@Entity
@Table(name="cst_linkman")
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;
/**
* 配置联系人到客户的多对一关系
* 外键字段是设置在从表中的,且该字段并未作为对象的属性去配置,而实作为外键去配置
*
* 使用注解的形式配置多对一关系
* 1.配置表关系
* @ManyToOne : 配置多对一关系
* targetEntity:对方的实体类字节码
* 2.配置外键(中间表)
*
* * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
*
*/
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
/*
get/set/toString略...
*/
}

注意:在上述实体中,均对外键进行了维护。

2.映射的注解说明

i.@OneToMany

作用:建立一对多的关系映射
属性:

  • targetEntityClass:指定多的多方的类的字节码(常用)
  • mappedBy:指定从表实体类中引用主表对象的名称。(常用)
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • orphanRemoval:是否使用孤儿删除

ii.@ManyToOne

作用:建立多对一的关系
属性:

  • targetEntityClass:指定一的一方实体类字节码(常用)
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • optional:关联是否可选。如果设置为 false,则必须始终存在非空关系。

iii.@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。
属性:

  • name:指定外键字段的名称(常用)
  • referencedColumnName:指定引用主表的主键字段名称(常用)
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

3.一对多测试

i.保存公司和联系人

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
复制代码package ctgu.OntoMany;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 保存一个客户,保存一个联系人
* 现象:从表(联系人)的外键为空
* 原因:
* 主表中没有配置关系
*/
@Test
@Transactional
@Rollback(value = false)
public void addTest(){
Customer customer = new Customer();
LinkMan linkMan = new LinkMan();
customer.setCustName("TBD云集中心");
customer.setCustLevel("VIP客户");
customer.setCustSource("网络");
customer.setCustIndustry("商业办公");
customer.setCustAddress("昌平区北七家镇");
customer.setCustPhone("010-84389340");

linkMan.setLkmName("小明");
linkMan.setLkmGender("male");
linkMan.setLkmMobile("13811111111");
linkMan.setLkmPhone("010-34785348");
linkMan.setLkmEmail("123456@qq.com");
linkMan.setLkmPosition("老师");
linkMan.setLkmMemo("还行吧");
/**
* 配置了客户到联系人的关系
* 从客户的角度上,发送了两条insert语句,发送一条更新语句更新数据库(更新从表中的外键值)
* 由于我们配置了客户到联系人的关系,客户可以对外键进行维护
*/

linkMan.setCustomer(customer);
//此添加可以不写会
customer.getLinkMans().add(linkMan);
customerDao.save(customer);
linkManDao.save(linkMan);
}
}

运行结果:

1
2
3
复制代码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: update cst_linkman set lkm_cust_id=? where lkm_id=?

分析:

执行了两条insert语句以及一条update语句,有一条update的语句是多余的。产生这种现象的原因是:我们在两个实体类中均对外键进行了维护,相当于维护了两次,解决的办法是放弃一方的维权。

修改:将主表中的关系映射修改为:

1
2
复制代码 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();

ii.级联添加

级联操作:操作一个对象同时操作它的关联对象

使用方法:只需要在操作主体的注解上配置casade

1
2
3
4
5
6
7
8
9
10
11
复制代码 /**
* 放弃外键维护权:我的一对多映射参照对方的属性就可以了
* mappedBy:对方维护关系的属性名称
* cascade = CascadeType.ALL 进行级联操作,all表示级联所有(insert,delete,update)
* .merge 更新
* .persist保存
* .remove 删除
* fetch 配置延迟加载
*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>()

一般是对配置在主表中,但是:注意:慎用CascadeType.ALL

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
复制代码 package ctgu.OntoMany;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 级联添加:
* 保存一个客户的同时,保存客户的所有联系人
* 需要在操作主题的实体类上,配置casache属性
*/
@Test
@Transactional
@Rollback(value = false)
public void cascadeAdd(){
Customer customer = new Customer();
LinkMan linkMan = new LinkMan();
customer.setCustName("测试公司1");
linkMan.setLkmName("测试员工张三1");
//注意此处添加
linkMan.setCustomer(customer);
customer.getLinkMans().add(linkMan);

customerDao.save(customer);
}
}

测试结果:

1
2
复制代码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 (?, ?, ?, ?, ?, ?, ?, ?)

iii.级联删除

删除公司的同时,删除对应公司的所有员工。

JPA中删除是先执行查询再执行删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码 /**
* 级联删除:删除1号客户的同时,删除1号客户的所有联系人
* 1.需要区分操作主体(你对那个对象进行操作)
* 2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
* 3.cascade(配置级联)
*/
@Test
@Transactional
@Rollback(value = false)
public void cascadeDelete(){

// Customer customer = customerDao.findOne(1L);
customerDao.delete(40L);
}

测试结果:

1
2
3
4
复制代码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=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?

注意:一般使用级联删除是比较危险的,在一对多的情况下。如果没有使用级联操作,应该如何删除数据?

只删除从表数据:可以任意删除。

删除主表数据:

  • 有从表数据
    1. 在默认情况下,会将外键字段置为null,然后再执行删除。此时如果从表的结构上,外键字段存在非空约束将会报错。
    2. 使用级联删除。
    3. 应该先根据外键值,删除从表中的数据,再删除主表中的数据。
  • 没有从表数据:随便删

iv.一对多删除(非级联删除)

创建方法:根据customer删除员工。(使用复杂查询中的自定义方法)

1
2
3
4
5
6
7
8
9
10
11
复制代码package ctgu.dao;

import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface LinkManDao extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor<LinkMan> {
//根据外键值进行删除
public void deleteByCustomer(Customer customer);
}

此时的主表的关键映射为设置级联操作:

1
2
复制代码    @OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();

测试:

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
复制代码 package ctgu.OntoMany;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class OntoManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
@Test
@Transactional
@Rollback(value = false)
public void cascadeDelete(){
Customer customer = customerDao.findOne(47L);
linkManDao.deleteByCustomer(customer);
customerDao.delete(47L);
}
}

测试结果:

1
2
3
4
复制代码Hibernate: select linkman0_.lkm_id as lkm_id1_1_, linkman0_.lkm_cust_id as lkm_cust9_1_, linkman0_.lkm_email as lkm_emai2_1_, linkman0_.lkm_gender as lkm_gend3_1_, linkman0_.lkm_memo as lkm_memo4_1_, linkman0_.lkm_mobile as lkm_mobi5_1_, linkman0_.lkm_name as lkm_name6_1_, linkman0_.lkm_phone as lkm_phon7_1_, linkman0_.lkm_position as lkm_posi8_1_ from cst_linkman linkman0_ left outer join cst_customer customer1_ on linkman0_.lkm_cust_id=customer1_.cust_id where customer1_.cust_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?

5.JPA中的多对多

案例:用户和角色。

用户:指社会上的某个人。

角色:指人们可能有多种身份信息

比如说:小明有多种身份,即使java工程师,还是后端攻城狮,也是CEO;而Java工程师除了小明,还有张三、李四等等。

所以我们说,用户和角色之间的关系是多对多。

springdatajpa进阶02

1.建立实体类与表直接的关系映射

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
复制代码package ctgu.pojo;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "sys_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="age")
private Integer age;
/**
* 配置用户到角色的 多对多 关系
* 配置多对多的映射关系
* 1.声明表关系的配置
* @ManyToMany()
* targetEntity = Role.class声明对方的实体类字节码
* 2.配置中间表(两个外键)
* @JoinTable
* name :中间表的名称
* joinColumns,当前对象在中间表的位置
* @JoinColumn
* name:外键在中间表的字段名称
* referencedColumnName:参照的主表的主键名称
* inverseJoinColumns,对方对象在中间表的位置
*/
// @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表的位置
joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
//inverseJoinColumns,对方对象在中间表的位置
inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();

public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
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
复制代码package ctgu.pojo;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "sys_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long roleId;
@Column(name = "role_name")
private String roleName;
@ManyToMany(targetEntity = User.class)
@JoinTable(name = "sys_user_role",
//joinColumns,当前对象在中间表的位置
joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
//inverseJoinColumns,对方对象在中间表的位置
inverseJoinColumns ={@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}
)
//@ManyToMany(mappedBy="roles")应该有一方放弃维护
private Set<User> users = new HashSet<>();
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}

2,映射注解说明

i.@ManyToMany

作用:用于映射多对多关系
属性:

  • cascade:配置级联操作。
  • fetch:配置是否采用延迟加载。
  • targetEntity:配置目标的实体类。映射多对多的时候不用写。
  • mappedBy:指定从表实体类中引用主表对象的名称。(常用)

ii.@JoinTable

作用:针对中间表的配置
属性:

  • nam:配置中间表的名称
  • joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
  • inverseJoinColumn:中间表的外键字段关联对方表的主键字段

iii.@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。
属性:

  • name:指定外键字段的名称
  • referencedColumnName:指定引用主表的主键字段名称
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

3.多对多测试

i.保存用户和角色

数据库表:(其实可以直接由springdataJPA自动生成)

1
2
3
4
5
6
7
8
9
10
11
12
复制代码CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;

dao接口:

1
2
3
4
5
6
7
8
复制代码package ctgu.dao;

import ctgu.pojo.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface RoleDao extends JpaRepository<Role,Long>, JpaSpecificationExecutor<Role> {
}
1
2
3
4
5
6
7
8
复制代码package ctgu.dao;

import ctgu.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}

测试案例:

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
复制代码package ctgu;
import ctgu.dao.RoleDao;
import ctgu.dao.UserDao;
import ctgu.pojo.Role;
import ctgu.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ManyToMany {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;

/**
* 保存一个用户,保存一个角色
* 多对多放弃维护权:
* 被动的一方放弃,谁被选择谁放弃
*/
@Test
@Transactional
@Rollback(false)
public void addUserAndRole(){
User user = new User();
Role role1 = new Role();
Role role2 = new Role();
Role role3 = new Role();
user.setUserName("李大明");
role1.setRoleName("后端攻城狮");
role2.setRoleName("java程序员");
role3.setRoleName("CEO");
//用户和角色都可以对中间表进行维护,添加两次就重复了
//配置角色到用户的关系,可以对中间表中的数据进行维护
role1.getUsers().add(user);
role2.getUsers().add(user);
role3.getUsers().add(user);
//配置用户到角色的关系,
user.getRoles().add(role1);
user.getRoles().add(role2);
user.getRoles().add(role3);
userDao.save(user);
roleDao.save(role1);
roleDao.save(role2);
roleDao.save(role3);
}
}

测试结果:

1
复制代码org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

原因:

在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,
中间表的 2 个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一
方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:

1
2
3
复制代码//放弃对中间表的维护权,解决保存中主键冲突的问题
@ManyToMany(mappedBy="roles")
private Set<SysUser> users = new HashSet<SysUser>(0);

正确结果:

1
2
3
4
5
6
7
8
复制代码Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
Hibernate: insert into sys_user_role (sys_role_id, sys_user_id) values (?, ?)

系统会自动创建表sys_user_role并添加数据。

ii.级联保存

保存用户的同时,保存其关联角色。

只需要在操作对象的注解上配置cascade

1
2
复制代码@ManyToMany(mappedBy = "roles",cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>();
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
复制代码package ctgu;

import ctgu.dao.RoleDao;
import ctgu.dao.UserDao;
import ctgu.pojo.Role;
import ctgu.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ManyToMany {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
/**
* 级联操作:保存一个用户的同时,保存用户的关联角色
* 只需要在操作对象的注解上配置cascade
*/
@Test
@Transactional
@Rollback(false)
public void addCasecade() {
User user = new User();
Role role = new Role();
user.setUserName("张三");
role.setRoleName("java程序员");
//用户和角色都可以对中间表进行维护,添加两次就重复了
//配置角色到用户的关系,可以对中间表中的数据进行维护
role.getUsers().add(user);
//配置用户到角色的关系,
user.getRoles().add(role);
roleDao.save(role);
}
}

测试结果:

1
2
3
复制代码Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user (age, user_name) values (?, ?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)

iii.级联删除

1
2
3
4
5
6
7
8
9
复制代码   /**
* 级联操作:删除id为1的用户,同时删除他的关联对象
*/
@Test
@Transactional
@Rollback(false)
public void deleteCasecade() {
roleDao.delete(23L);
}

测试结果:

1
2
3
4
5
复制代码Hibernate: select role0_.role_id as role_id1_0_0_, role0_.role_name as role_nam2_0_0_ from sys_role role0_ where role0_.role_id=?
Hibernate: select users0_.sys_role_id as sys_role2_2_0_, users0_.sys_user_id as sys_user1_2_0_, user1_.user_id as user_id1_1_1_, user1_.age as age2_1_1_, user1_.user_name as user_nam3_1_1_ from sys_user_role users0_ inner join sys_user user1_ on users0_.sys_user_id=user1_.user_id where users0_.sys_role_id=?
Hibernate: delete from sys_user_role where sys_user_id=?
Hibernate: delete from sys_user where user_id=?
Hibernate: delete from sys_role where role_id=?

注意:

  • 调用的对象是role,所有需要在role对象中配置级联cascade = CascadeType.ALL;
  • 慎用!可能会清空相关联的数据;

6.SpringDataJPA中的多表查询

以下例子采用一对多的案例实现。

i.对象导航查询

对象导航查询的方式就是根据已加载的对象,导航到他的关联对象。利用实体与实体之间的关系来检索对象。例如:通过ID查询出一个Customer,可以调用Customer对象中的getLinkMans()方法来获取该客户的所有联系人。

对象导航查询使用的要求是:两个对象之间必须存在关联联系。

案例:查询公司,获取公司下所有的员工

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
复制代码package ctgu.QueryTest;

import ctgu.dao.CustomerDao;
import ctgu.dao.LinkManDao;
import ctgu.pojo.Customer;
import ctgu.pojo.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ObjectQuery {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
/**
* 测试导航查询(查询一个对象的时候,通过此查询他的关联对象)
* 对于对象导航查询,默认使用的是延迟加载的形式来查询的,(需要才去查询)
* 调用get方法并不会立即发送查询,而实在关联对象使用的时候才会查询
* 修改配置,将延迟加载改为立即加载
* fetch 需要配置多表映射关系发注解上
*
*/
@Test
@Transactional//解决在java代码中的no Session问题
public void QueryTest01(){
Customer customer = customerDao.findOne(26L);
Set<LinkMan> linkMans = customer.getLinkMans();
for(LinkMan man:linkMans){
System.out.println(man);
}
}
}

问题:我们在查询Customer时,一定要把LinkMan查出来吗?

分析:如果我们不查的话,在需要的时候需要重新写代码,调用方法查询;但是每次都查出来又会浪费服务器的内存。

解决:查询主表对象时,采用延迟加载的思想,通过配置的方式,当我们需要使用的时候才查询。

延迟加载

由于上述调用的对象为Customer,故而在Customer对象中需要配置延迟加载。Customer对象

1
2
复制代码@OneToMany(mappedBy = "customer",fetch = FetchType.LAZY)
private Set<LinkMan> linkMans=new HashSet<>();

测试结果:

1
2
3
4
复制代码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{lkmId=31, lkmName='李四', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
LinkMan{lkmId=30, lkmName='张三', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}

分析:我们发现其执行了两条select语句。

问题:在我们查LinkMan时,是否需要把Customer查出来?

分析:由于一个用户只属于一家公司,及每个LinkMan都有唯一的Customer与之对应。如果我们不查,在使用的时候需要额外代码查询。且查询出的是单个对象,对内存消耗较小。

解决:在从表中采用立即加载的思想,只要查询从表实体,就把主表对象同时查出来。

立即加载

1
2
复制代码    @OneToMany(mappedBy = "customer",fetch = FetchType.EAGER)
private Set<LinkMan> linkMans=new HashSet<>();

测试结果:

1
2
3
复制代码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=?
LinkMan{lkmId=30, lkmName='张三', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}
LinkMan{lkmId=31, lkmName='李四', lkmGenger='null', lkmPhone='null', lkmMobile='null', lkmEmail='null', lkmPosition='null', lkmMemo='null'}

分析结果:我们发现其只执行了一条select语句。

对比可以发现,立即加载是一次性将查询对象以及关联对象查出来,而延迟加载是先查询目标对象,如果未调用Set<LinkMan> linkMans = customer.getLinkMans();方法,则将不会执行关联对象的查询。

ii.使用 Specification 查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码/**
* Specification的多表查询
*/
@Test
public void testFind() {
Specification<LinkMan> spec = new Specification<LinkMan>() {
public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//Join代表链接查询,通过root对象获取
//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
return cb.like(join.get("custName").as(String.class),"传智播客1");
}
};
List<LinkMan> list = linkManDao.findAll(spec);
for (LinkMan linkMan : list) {
System.out.println(linkMan);
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%