言成言成啊 | Kit Chen's Blog

Spring Data JPA

发布于2021-04-18 17:23:31,更新于2023-02-08 20:48:18,标签:jpa spring  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

源码

关于SpringDataJPA中@Column里面字段报红的处理方法

一、概述

1.1 概念

Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码是实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展,使用Spring Data JPA可以极大地提高开发效率。

Spring Data JPA让我们解脱了dao层的操作,基本上所有的CRUD都可以依赖它来实现,在实际的工作中,使用Spring Data JPA+ORM(如Hibernate)完成操作,这样在切换不同的ORM时,提供了极大的方便,同时也使数据库层操作更加简单、方便解耦。

1.2 关系

JPA是一套规范,内部是由接口和抽象类组成的。

Hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也称Hibernate为JPA的一种实现方式,我们使用JPA的api进行编程,就意味着,要站在更高的角度上看问题,即面向接口编程。

Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门的用来进行数据持久化的解决方案

二、入门操作

2.1 搭建环境

步骤

  1. 创建工程导入坐标
  2. 配置spring的配置文件,添加log4j日志配置文件
  3. 编写实体类Customer,使用jpa注解配置映射关系
  4. 编写dao层接口,不需要实现类

创建工程导入坐标

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.meethigher</groupId>
<artifactId>SpringDataJPA</artifactId>
<version>1.0</version>

<properties>
<spring.version>5.3.4</spring.version>
<mysql.version>8.0.12</mysql.version>
<hibernate.version>5.4.30.Final</hibernate.version>
<log4j.version>1.2.17</log4j.version>
<c3p0.version>0.9.1.2</c3p0.version>
<slf4j.version>1.7.25</slf4j.version>
</properties>
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!--以下是spring环境-->
<!--解析切入点表达式-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!--spring aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring ioc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring对orm框架的支持包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!--以上是spring环境-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>

<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--java日志框架-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.4.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

<!--使用spring data jpa必须引入的-->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
</project>

配置spring的配置文件

spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!--spring和spring data jpa的配置-->
<!--1. 创建EntityManagerFactory对象交给spring容器管理-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="ds"/>
<!--配置扫描的包(实体类所在的包)-->
<property name="packagesToScan" value="top.meethigher.demo01.domain"/>
<!--jpa的实现方式,如Hibernate-->
<!--这种写法配置的bean类作用域只对当前作用域有效-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--jpa的供应商适配器-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--自动创建数据库表-->
<property name="generateDdl" value="false" />
<!--指定数据库类型,ctrl左键可知此处是个枚举类型,类型要符合枚举的取值-->
<property name="database" value="MYSQL" />
<!--数据库方言,也就是支持的特有语法,比如mysql分页limit,而oracle是rownum-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
<!--是否显示SQL-->
<property name="showSql" value="true" />
</bean>
</property>
<!--jpa的方言:高级的特性,比如Hibernate的一级缓存、二级缓存-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
</bean>
<!--2. 创建数据库连接池-->
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdatajpa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!--3. 整合SpringDataJPA basePackage是存放接口的包,springjpa会对这个包进行动态代理增强-->
<jpa:repositories base-package="top.meethigher.demo01.dao"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>
<!--4. 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--5. 配置spring的声明式事务,一般是service层的,学习的demo只有dao,所有就不配置了-->
<!--<tx:advice id="txAdvice" transaction-manager="transactionManager">-->
<!-- <tx:attributes>-->
<!-- <tx:method name="save*" propagation="REQUIRED"/>-->
<!-- <tx:method name="insert*" propagation="REQUIRED"/>-->
<!-- <tx:method name="update*" propagation="REQUIRED"/>-->
<!-- <tx:method name="delete*" propagation="REQUIRED"/>-->
<!-- <tx:method name="get*" read-only="true"/>-->
<!-- <tx:method name="find*" read-only="true"/>-->
<!-- <tx:method name="*" propagation="REQUIRED"/>-->
<!-- </tx:attributes>-->
<!--</tx:advice>-->
<!--<aop:config>-->
<!-- <aop:pointcut id="pointcut" expression="execution(* cn.itcast.service.*.*(..))" />-->
<!-- <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />-->
<!--</aop:config>-->

<!--6. 可能会用到ioc的注解,所以需要扫描-->
<context:component-scan base-package="top.meethigher.demo01"/>
</beans>

添加Log4j配置

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
#############
# 输出到控制台
#############

# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
# WARN:日志级别 CONSOLE:输出位置自己定义的一个名字 logfile:输出位置自己定义的一个名字
log4j.rootLogger=ERROR,CONSOLE,logfile
# 配置CONSOLE输出到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 配置CONSOLE设置为自定义布局模式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 配置CONSOLE日志的输出格式 [frame] 2019-08-22 22:52:12,000 %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n

################
# 输出到日志文件中
################

# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
# 保存编码格式
log4j.appender.logfile.Encoding=UTF-8
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.logfile.File=logs/root.log
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.logfile.MaxFileSize=10MB
# 设置滚定文件的最大值3 指可以产生root.log.1、root.log.2、root.log.3和root.log四个日志文件
log4j.appender.logfile.MaxBackupIndex=3
# 配置logfile为自定义布局模式
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

##########################
# 对不同的类输出不同的日志文件
##########################

# club.bagedate包下的日志单独输出
log4j.logger.club.bagedate=DEBUG,bagedate
# 设置为false该日志信息就不会加入到rootLogger中了
log4j.additivity.club.bagedate=false
# 下面就和上面配置一样了
log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
log4j.appender.bagedate.Encoding=UTF-8
log4j.appender.bagedate.File=logs/bagedate.log
log4j.appender.bagedate.MaxFileSize=10MB
log4j.appender.bagedate.MaxBackupIndex=3
log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n

编写实体类配置映射关系

客户实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@Entity
@Table(name="customer")
public class Customer {
@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;


public Long getCustId() {
return custId;
}

public void setCustId(Long custId) {
this.custId = custId;
}

public String getCustName() {
return custName;
}

public void setCustName(String custName) {
this.custName = custName;
}

public String getCustSource() {
return custSource;
}

public void setCustSource(String custSource) {
this.custSource = custSource;
}

public String getCustIndustry() {
return custIndustry;
}

public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}

public String getCustLevel() {
return custLevel;
}

public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}

public String getCustAddress() {
return custAddress;
}

public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}

public String getCustPhone() {
return custPhone;
}

public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}

@Override
public String toString() {
return "Customer{" +
"custId=" + custId +
", custName='" + custName + '\'' +
", custSource='" + custSource + '\'' +
", custIndustry='" + custIndustry + '\'' +
", custLevel='" + custLevel + '\'' +
", custAddress='" + custAddress + '\'' +
", custPhone='" + custPhone + '\'' +
'}';
}
}

编写dao层接口

dao层接口规范

  1. 需要继承两个接口
    • JpaRepository<操作的实体类类型,实体类中主键属性的类型>
      • 封装了基本CRUD操作
        • findById:通过id查询,获取到的对象需要通过.get()再获取
        • save:保存或者更新。如果没有id,或者有id但是数据库里没有,都是执行不带id的插入操作。如果数据库里有了id就会进行更新
        • deleteById:通过id删除
        • findAll:查找所有
    • JpaSpecificationExecutor<操作的实体类类型>
      • 封装了复杂查询,如分页
  2. 提供相应的泛型

注意是接口继承了两个接口

1
2
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}

2.2 CRUD基本操作

测试类

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
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring.xml")//指定spring容器的配置信息
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;

/**
* 根据id查询
*/
@Test
public void testFindById() {
Optional<Customer> byId = customerDao.findById(3L);
if(byId.isPresent()){
System.out.println(byId.get());
}
}
/**
* save()保存或者更新
* 如果没有id主键属性,就是保存
* 如果存在id主键属性,并且在数据库查到了,就是更新,否则就是不携带主键进行保存
*/
@Test
public void testSave() {
Customer customer = new Customer();
customer.setCustId(1L);
customer.setCustName("雅妃");
/**
* 这种直接save存在的问题是,会将没有属性的字段置为null
* 解决办法是,先查询获取对象,再更新
*/
customerDao.save(customer);
}

/**
* 根据id删除
*/
@Test
public void testDeleteById() {
customerDao.deleteById(1L);
}
/**
* 查询所有
*/
@Test
public void testFindAll() {
List<Customer> list = customerDao.findAll();
// list.stream().forEach(x->{
// System.out.println(x);
// });
//使用lambda实现上面
list.forEach(System.out::println);
}
}

三、运行过程和原理剖析

3.1 分析

CustomerDao只是一个接口。

真正发挥作用的应该是接口的实现类,在执行过程中自动动态生成接口的实现类对象。

如何实现的?动态代理!

可以通过debug的模式来看

3.2 执行流程

当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对customerDao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?

答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository

通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?

我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发

总结

  1. 通过JdkDynamicAopProxy的invoke创建一个动态代理对象
  2. SimpleJpaRepository当中封装了JPA的操作(借助JPA的Api完成数据库的CRUD)
  3. 通过Hibernate完成数据库操作(封装了JDBC)

四、复杂查询

4.1 Count、Exist、GetOne

注意:因为GetOne底层实现了getReference,是懒加载。所以需要加注解@Transactional保证正常运行

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
/**
* 测试统计查询
*/
@Test
public void testCount(){
long count = customerDao.count();
System.out.println(count);
}
/**
* 判断id为4的客户是否存在
*
*/
@Test
public void testExist(){
boolean b = customerDao.existsById(4L);
System.out.println(b);
}
/**
* 测试getOne
* getOne是懒加载
*/
@Test
@Transactional//保证getOne正常运行
public void testGetOne(){
Customer one = customerDao.getOne(4L);
System.out.println(one);
}

4.2 JPQL查询

JPQL:JPA Query Language,jpa查询语言

特点:语法或者关键字和SQL语句类似,但是查询的是类和类中的属性

完成JPQL查询,步骤分两步

  1. 在接口上添加对应方法
  2. 在方法上使用注解的形式配置JPQL语句
  3. @Query:代表当前的操作是个查询操作

对于多个占位符的

  • 默认情况,占位符位置需要和方法参数的位置保持一致
  • 指定占位符参数的位置
  • 占位符从?1开始
1
2
@Query(value = "from Customer where custName=?1 and custId=?2")
Customer findCustomerNameAndId(String name,Long id);//name的索引为1,对应

更新操作

  • @Modifying:代表当前的操作是更新操作
  • @Transactional:添加事务的支持,默认会执行结束之后,回滚事务
  • @Rollback:设置是否自动回滚,true或false

CustomerDao

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
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 根据客户名称查询客户
* jpql:from Customer where custName=?1
* 配置jpql语句,使用@Query注解
*/
@Query(value = "from Customer where custName =?1")
Customer findJpql(String custName);

/**
* 根据客户名称和客户id查询客户
* jpql:from Customer where custName=?1 and custId=?2
*/
@Query(value = "from Customer where custName=?2 and custId=?1")
Customer findCustomerNameAndId(Long id,String name);

/**
* 使用jpql完成更新的操作
* 根据id,更新名称
* 添加更新操作,@Modifying
*/
@Modifying
@Query("update Customer set custName=?2 where custId=?1")
void updateNameById(Long id,String name);

}

测试类

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
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring.xml")//指定spring容器的配置信息
public class JpqlTest {
@Autowired
private CustomerDao customerDao;
@Test
public void testFindJpql() {
Customer customer = customerDao.findJpql("美杜莎");
System.out.println(customer);
}
@Test
public void testFindCustomerNameAndId(){
Customer customer = customerDao.findCustomerNameAndId(4L,"美杜莎");
System.out.println(customer);
}

/**
* 测试JPQL的更新操作
* SpringDataJPA中使用jpql完成更新/删除操作
* 1. 需要手动添加事务的支持
* 2. 默认会执行结束之后,回滚事务
*/
@Test
@Transactional
@Rollback(value=false)
public void testUpdateNameById(){
customerDao.updateNameById(4L, "小舞");
Customer customer = customerDao.findById(4L).get();
System.out.println(customer);
}
}

4.3 SQL语句的查询

步骤跟JPQL差不多

完成这种SQL语句的查询,步骤分两步

  1. 在接口上添加对应方法
  2. 在方法上使用注解的形式配置JPQL语句
  3. @Query:代表当前的操作是个查询操作
    • value:可以是JPQL语句,也可以是SQL语句
    • nativeQuery
      • true:使用本地查询,sql查询
      • false:默认值,使用jpql查询
    • SQL的占位符赋值方式跟JPQL是一样的,从?1开始

CustomerDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 使用SQL的形式查询,查询全部客户
*/
@Query(value="select * from customer",nativeQuery = true)
List<Object[]> findSql();

/**
* 使用SQL的形式查询
* 使用用户名模糊查询所有信息
*/
@Query(value="select * from customer where cust_name like ?1",nativeQuery = true)
List<Object[]> findSqlByName(String name);
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring.xml")//指定spring容器的配置信息
public class JpqlTest {
@Autowired
private CustomerDao customerDao;
@Test
public void testFindSql() {
List<Object[]> sql = customerDao.findSql();
for (Object[] obj :
sql) {
System.out.println(Arrays.toString(obj));
}
}
@Test
public void testFindSqlByName() {
List<Object[]> sqlByName = customerDao.findSqlByName("%美%");
for (Object[] obj :
sqlByName) {
System.out.println(Arrays.toString(obj));
}
}
}

4.4 方法名称规则查询

方法名称规则查询是对jpql查询,更加深入的一层封装,我们只需要按照SpringDataJPA提供的方法名称规则定义方法,不需要再去配置JPQL语句完成查询。

方法名的约定

findBy:查询

  • findBy+属性名称(首字母要大写):精准查询
  • 如findByCustName,是根据客户名称查询。在SpringDataJPA的运行阶段,会根据方法名称进行解析成from 实体类 where custName=?1
  • findBy+属性名称+查询方式(Like、IsNull、Null、NotNull、IsNotNull)
  • findBy+属性名称+查询方式+多条件连接符(And、Or)+属性名称+查询方式

CustomerDao

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
package top.meethigher.demo01.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import top.meethigher.demo01.domain.Customer;

import java.util.List;

/**
* CustomerDao
* 符合SpringDataJpa的dao层接口规范
* @author kit chen
* @github https://github.com/meethigher
* @blog https://meethigher.top
* @time 2021/4/12
*/
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 使用方法命名规则查询
*/
Customer findByCustName(String name);

/**
* 按照客户名称模糊匹配
* @param name
* @return
*/
List<Customer> findByCustNameLike(String name);

/**
* findBy+属性名称+查询方式+多条件连接符(And、Or)+属性名称+查询方式
*/
List<Customer> findByCustNameLikeAndCustAddress(String name,String address);
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring.xml")//指定spring容器的配置信息
public class JpqlTest {
@Autowired
private CustomerDao customerDao;
@Test
public void testFindByCustName() {
Customer customer = customerDao.findByCustName("小舞");
System.out.println(customer);
}

@Test
public void testFindByCustNameLike(){
List<Customer> 小 = customerDao.findByCustNameLike("%小%");
System.out.println(小 );
}

@Test
public void testFindByCustNameLikeAndCustAddress(){
List<Customer> 斗破苍穹 = customerDao.findByCustNameLikeAndCustAddress("%小%", "斗破苍穹");
System.out.println(斗破苍穹);
}
}

4.5 Specification动态查询

基本使用

方法列表

  • 查询单个对象:OptionalfindOne(@Nullable Specificationvar1)
  • 查询列表:ListfindAll(@Nullable Specificationvar1);
  • 通过分页查询全部:PagefindAll(@Nullable Specificationvar1, Pageable var2);
    • Pageable:分页参数
      • PageRequest是Pageable的实现类,PageRequest.of(int,int)
        • 构造函数第一个参数:当前的页数(从0开始)
        • 构造函数第二个参数:每页的查询数量
    • Page:分页pageBean
  • 通过排序的方式查询全部:ListfindAll(@Nullable Specificationvar1, Sort var2);
  • 统计查询:long count(@Nullable Specificationvar1);

Specification是一个接口,里面有一个方法比较重要,也就是下面这个。

1
2
3
4
5
6
7
8
9
10
11
/**
* root:查询的根对象,查询的任何属性都可以从根对象中获取
* CriteriaQuery:顶层查询对象,自定义查询方式(一般不用)
* CriteriaBuilder:查询的构造器,封装了很多查询的条件
*/
new Specification<要查询的实体的对象>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
return null;
}
};

自定义查询条件的步骤

  1. 实现Specification接口,提供的泛型是查询对象的类型
  2. 实现toPredicate方法,构造查询条件
  3. 需要借助方法参数中的两个参数
    • Root:获取需要查询的对象属性
    • CriteriaQuery:顶层查询对象,自定义查询方式(一般不用)
    • CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
      • and:以and的形式拼接多个查询条件
      • or:以or的形式拼接多个查询条件
      • gt:大于,greaterThan
      • lt:小于,lessThan
      • ge:大于等于,greaterThanOrEqualTo
      • le:小于等于,lessThanOrEqualTo
      • 如果以上在传参时,无法比较,可将参数.as(比较的类型的字节码)即可。比如cb.like(root.get("name").as(String.class),"%1%")

CustomerDao

1
2
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;

/**
* 根据条件查询单个对象
*/
@Test
public void testFindOne() {
//匿名内部类
Specification<Customer> specification = new Specification<Customer>() {
/**
* 根据id查询
* 需要明确
* 1.查询方式:cb
* 2.比较的属性名称:root
* @param root
* @param cq
* @param cb
* @return
*/
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
//1. 获取比较的属性
Path<Object> custId = root.get("custId");
//2. 构造查询条件,
// 第一个参数,比较的对象;第二个参数,取值
Predicate predicate = cb.equal(custId, 4L);
return predicate;
}
};
Optional<Customer> one = customerDao.findOne(specification);
if (one.isPresent()) {
System.out.println(one.get());
}
}

/**
* 根据客户名和所属行业查询
* SELECT * FROM customer WHERE cust_name =? AND cust_industry=?
*/
@Test
public void testFindByCustNameAndCustIndustry() {
List<Customer> all = customerDao.findAll(new Specification<Customer>() {
/**
* 1.root获取客户名、所属行业
* 2.将以上两个查询联系起来
* @param root
* @param cq
* @param cb
* @return
*/
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Predicate equal = cb.equal(root.get("custName"), "小舞");
Predicate equal1 = cb.equal(root.get("custIndustry"), "蛇人族");
Predicate and = cb.and(equal, equal1);
return and;
}
});
System.out.println(all);
}

/**
* 根据(客户名或者客户名称)和地址进行查询
* SELECT * FROM customer WHERE cust_address =? AND (cust_id = 4 OR cust_name =?)
*/
@Test
public void testFindByAddressAndNameOrId() {
List<Customer> all = customerDao.findAll(new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Predicate custId = cb.equal(root.get("custId"), 4L);
Predicate custName = cb.equal(root.get("custName"), "小舞");
Predicate custAddress = cb.equal(root.get("custAddress"), "斗破苍穹");
Predicate or = cb.or(custId, custName);
return cb.and(custAddress, or);
}
});
System.out.println(all);
}

/**
* 根据名称模糊查询,查询列表
*/
@Test
public void testFindLike() {
List<Customer> all = customerDao.findAll(new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Path<Object> objectPath = root.get("custName");
Predicate custName = cb.like(objectPath.as(String.class), "%小%");
return custName;
}
});
System.out.println(all);
}

/**
* 测试排序
*/
@Test
public void testSort() {
/**
* 创建排序对象
* 第一个参数:排序的顺序,倒序DESC、正序ASC
* 第二个参数:排序的属性名称
*/
// Sort orders = Sort.by("custId").descending();
Sort orders = Sort.by(Sort.Direction.DESC, "custId");
List<Customer> all = customerDao.findAll(orders);
all.forEach(System.out::println);
}

/**
* 分页查询
*/
@Test
public void testPage() {
Specification<Customer> specification = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
return null;
}
};
/**
* 第一个参数:当前页,从0开始
* 第二个参数:查询的个数
*/
Sort custId = Sort.by("custId").descending();
Pageable page = PageRequest.of(0, 2, custId);
Page<Customer> all = customerDao.findAll(specification, page);
System.out.println(all.getTotalElements());//总条数
System.out.println(all.getTotalPages());//总页数
System.out.println(all.getContent());//内容
}
}

扩展查询

简单记录一个最平常不过的写法。

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
private Specification<FenceInfo> queryByPageSpecification(QueryFencePageRequest request) {
String keywords = request.getKeywords();
String status = request.getStatus();
return new Specification<FenceInfo>() {
@Override
public Predicate toPredicate(Root<FenceInfo> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
List<Predicate> list=new LinkedList<>();
if(!ObjectUtils.isEmpty(status)) {
Predicate statusEqual = cb.equal(root.get("status"), status);
list.add(statusEqual);
}
if(!ObjectUtils.isEmpty(keywords)) {
Predicate nameLike = cb.like(root.get("name"), "%" + keywords + "%");
Predicate codeEqual = cb.equal(root.get("code"), keywords);
Predicate or = cb.or(nameLike, codeEqual);
list.add(or);
}
//排序示例(先根据类型排序,后根据创建时间排序)
cq.orderBy(cb.asc(root.get("type")),cb.asc(root.get("createTime")));
//最终将查询条件拼好然后return
Predicate[] predicates = new Predicate[list.size()];
return cb.and(list.toArray(predicates));
}
};
}

五、多表操作

5.1 多表关系

表关系

  1. 一对一
  2. 一对多
    • 一的一方:主表
    • 多的一方:从表
    • 外键:需要在从表上选择一列作为外键,他的取值来源于主表的主键
  3. 多对多
    • 中间表:中间表中最少由两个字段组成,这两个字段作为外键指向两张表的主键,又组成了联合主键

实体类中的关系

  1. 包含关系:可以通过实体类中的包含关系描述表关系
  2. 继承关系

分析步骤

  1. 明确表关系
  2. 确定表关系,也就是描述出来,通过外键描述一对多,通过中间表描述多对多
  3. 编写实体类,并且在实体类中,描述表关系(包含关系)
  4. 配置映射关系

5.2 一对多或多对一

一对多案例,比如单位和员工。

一个单位有多个员工,一个员工从属于一个单位

单位:包含一个员工的集合

员工:包含一个单位的对象

使用JPA注解配置一对多映射关系

使用注解配置一对多

  • 声明关系

    • @OneToMany,一对多关系
      • targetEntity:对方目标对象的字节码
      • mappedBy:关联对象的配置关系的属性的名称
      • cascade:如CascadeType.ALL
    • @ManyToOne,多对一关系
      • 跟@OneToMany差不多
  • 配置外键

    • @JoinColumn,配置外键
      • name:外键名称
      • referencedColumnName:主表的主键字段名
    • 在一的一方添加了外键的配置,所以他具备了维护外键的作用
    • 在多的一方添加了外键的配置,所以他具备维护外键作用
    • 如果双方都添加了外键,那么,这就是个双向的可以互查的。否则就只能单向查询
  • 删除从表数据:随便删除

  • 删除主表数据:

    1. 有从表引用
      • 默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的结构上,外键字段有非空约束,默认情况会报错了。
      • 如果配置了放弃维护关联关系的权利,则不能删除。与外键字段是否允许NULL无关,因为在删除时,它不会去更新从表的外键字段
      • 如果还想删除,使用级联删除引用
    2. 无从表引用
      • 随便删除
  • 级联操作:操作一个对象的同时,也操作他的关联对象。需要区分操作主体,想通过谁来级联操作,就在谁的关系上添加级联属性(需要添加到多表映射关系的主街上),cascade(配置级联)

    1. 级联添加:保存单位的时候,也保存了他的单位下的个人信息
    2. 级联删除:删除单位的时候,也会将单位下所有个人删除

注意因为mysql5.5之前,是MyISAM引擎,不支持事务,后来MySQ5.5后引入了InnoDB。我一开始MySQL的方言引入的是MySQLDialect,创建表有问题。后来又引入了MySQL8Dialect,注意要与自己数据库相对应,否则会有问题。

配置文件

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="ds"/>
<property name="packagesToScan" value="top.meethigher.demo03.domain"/>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true"/>
<property name="database" value="MYSQL"/>
<property name="databasePlatform" value="org.hibernate.dialect.MySQL8Dialect"/>
<property name="showSql" value="true"/>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
<!--注入jpa的配置信息,加载jpa基本配置信息和jpa实现方式(hibernate)的配置信息-->
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/springdatajpa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<jpa:repositories base-package="top.meethigher.demo03.dao"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<context:component-scan base-package="top.meethigher.demo03"/>
</beans>

实体类Unit

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
@Entity
@Table(name="unit")
public class Unit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "unit_id")
private Integer unitId;
@Column(name = "unit_name")
private String unitName;
@Column(name="unit_address")
private String unitAddress;

//配置单位和员工的关系,一对多
// @OneToMany(targetEntity = Person.class)
// @JoinColumn(name = "p_u_id",referencedColumnName = "unit_id")
@OneToMany(mappedBy = "unit")//放弃对外键的维护权
private Set<Person> persons=new HashSet<>();


public Integer getUnitId() {
return unitId;
}

public void setUnitId(Integer unitId) {
this.unitId = unitId;
}

public String getUnitName() {
return unitName;
}

public void setUnitName(String unitName) {
this.unitName = unitName;
}

public String getUnitAddress() {
return unitAddress;
}

public void setUnitAddress(String unitAddress) {
this.unitAddress = unitAddress;
}

public Set<Person> getPersons() {
return persons;
}

public void setPersons(Set<Person> persons) {
this.persons = persons;
}
}

实体类Person

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
@Entity
@Table(name = "person")
public class Person {
@Id
@Column(name="p_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer pId;
@Column(name="person_name")
private String personName;
@Column(name="person_address")
private String personAddress;

//配置个人和单位的多对一关系
@ManyToOne(targetEntity = Unit.class,cascade = CascadeType.ALL)
@JoinColumn(name = "p_u_id",referencedColumnName = "unit_Id")
private Unit unit;

public Integer getpId() {
return pId;
}

public void setpId(Integer pId) {
this.pId = pId;
}


public String getPersonName() {
return personName;
}

public void setPersonName(String personName) {
this.personName = personName;
}

public String getPersonAddress() {
return personAddress;
}

public void setPersonAddress(String personAddress) {
this.personAddress = personAddress;
}

public Unit getUnit() {
return unit;
}

public void setUnit(Unit unit) {
this.unit = unit;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class OneToManyTest {
@Autowired
private PersonDao personDao;
@Autowired
private UnitDao unitDao;


@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd1() {
Unit unit = new Unit();
unit.setUnitName("小米科技");
unit.setUnitAddress("北京");
Person person = new Person();
person.setPersonName("雷军");
person.setPersonAddress("北京");

/**
* 保存一个unit和person到数据库中,但是两个并没有关系,外键为空
* 原因:我们的实体类当中,没有配置关系,需要添加进去
*/
unit.getPersons().add(person);

unitDao.save(unit);
personDao.save(person);


}

@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd2() {
Unit unit = new Unit();
unit.setUnitName("腾讯科技");
unit.setUnitAddress("北京");
Person person = new Person();
person.setPersonName("马化腾");
person.setPersonAddress("深圳");


person.setUnit(unit);

unitDao.save(unit);
personDao.save(person);


}

@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd3() {
Unit unit = new Unit();
unit.setUnitName("腾讯科技");
unit.setUnitAddress("北京");
Person person = new Person();
person.setPersonName("马化腾");
person.setPersonAddress("深圳");

person.setUnit(unit);
unit.getPersons().add(person);
unitDao.save(unit);
personDao.save(person);
}

//级联添加
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeAdd() {
Unit unit = new Unit();
unit.setUnitName("腾讯科技");
unit.setUnitAddress("北京");
Person person = new Person();
person.setPersonName("马化腾");
person.setPersonAddress("深圳");

person.setUnit(unit);
unit.getPersons().add(person);
// unitDao.save(unit);//不配置cascade,只会保存自己
personDao.save(person);//不设置cascade会报错
}

/**
* 级联删除
*/
@Test
@Transactional
@Rollback(false)
public void testCascadeRemove() {
//查询
Optional<Person> byId = personDao.findById(2);
//删除
byId.ifPresent(person -> personDao.delete(byId.get()));
}
}

5.3 多对多

多对多案例,比如用户和角色。

一个用户可以有多个角色。

一个角色可以有多个用户。

多对多配置

  • 声明关系

    • @ManyToMany
      • mappedBy:对方类中引用该对象的属性名称。表示放弃维护权,一般是在比较被动的实体中放弃。
  • 配置中间表(包含两个外键)

    • @JoinTable

      • name:中间表名称

      • joinColumns:配置当前对象在中间表中的外键

        • @JoinColumn
          • name:外键名称
      • referencedColumnName:主表的主键字段名

  • inverseJoinColumns:配置对方对象在中间表中的外键

    • @JoinColumn
      • name:外键名称
      • referencedColumnName:主表的主键字段名

实体类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
@Entity
@Table(name ="t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private Integer userId;
@Column(name="user_name")
private String userName;
@Column(name="user_age")
private Integer userAge;

@ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@JoinTable(name="user_role",
joinColumns = {@JoinColumn(name ="user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name="role_id",referencedColumnName = "role_id")})
private Set<Role> roles=new HashSet<>();

public Integer getUserId() {
return userId;
}

public void setUserId(Integer userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public Integer getUserAge() {
return userAge;
}

public void setUserAge(Integer userAge) {
this.userAge = userAge;
}

public Set<Role> getRoles() {
return roles;
}

public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}

实体类Role

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
@Entity
@Table(name = "t_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="role_id")
private Integer roleId;
@Column(name="role_name")
private String roleName;

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

public Integer getRoleId() {
return roleId;
}

public void setRoleId(Integer 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;
}
}

测试类

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
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class ManyToManyTest {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;

/**
* 保存一个用户保存一个角色
*/
@Test
@Transactional
@Rollback(false)
public void testAdd() {
User user = new User();
Role role = new Role();
user.setUserName("雅妃");
role.setRoleName("监察长老");

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

roleDao.save(role);
userDao.save(user);
}

/**
* 级联操作
* 保护一个用户的同时,保存所有角色的关联
*/
@Test
@Transactional
@Rollback(false)
public void testCascadeAdd() {
User user = new User();
Role role = new Role();
user.setUserName("雅妃");
role.setRoleName("监察长老");

user.getRoles().add(role);

userDao.save(user);
}

/**
* 级联操作
* 保护一个用户的同时,保存所有角色的关联
*/
@Test
@Transactional
@Rollback(false)
public void testCascadeRemove() {
Optional<User> byId = userDao.findById(1);
byId.ifPresent(user -> userDao.delete(user));
}
}

六、对象导航查询

6.1 一查多

对象导航查询:查询一个对象的同时,通过此对象查询他的关联对象。

直接进行查询,如findById(底层是find,立即加载)、getOne(底层是getReference,延迟加载)。这种的是可选的。

但如果是对象导航查询查多的一方时,也就是级联查询查多的一方时,其实默认都是懒加载的

如果我们需要单位查个人时立即加载,那我们可以通过修改单位中个人属性的加载机制,如下。

如果出现懒加载no session,可以通过添加@Transaction注解来解决。

no session是因为执行完一次查询后,session已经关闭了,而懒加载需要再次获取session。通过事务,可以让他们共用一个查询。

单位查人

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
@Entity
@Table(name="unit")
public class Unit {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "unit_id")
private Integer unitId;
@Column(name = "unit_name")
private String unitName;
@Column(name="unit_address")
private String unitAddress;

//配置单位和员工的关系,一对多
// @OneToMany(targetEntity = Person.class)
// @JoinColumn(name = "p_u_id",referencedColumnName = "unit_id")
@OneToMany(mappedBy = "unit",fetch = FetchType.LAZY)//放弃对外键的维护权
private Set<Person> persons=new HashSet<>();


public Integer getUnitId() {
return unitId;
}

public void setUnitId(Integer unitId) {
this.unitId = unitId;
}

public String getUnitName() {
return unitName;
}

public void setUnitName(String unitName) {
this.unitName = unitName;
}

public String getUnitAddress() {
return unitAddress;
}

public void setUnitAddress(String unitAddress) {
this.unitAddress = unitAddress;
}

public Set<Person> getPersons() {
return persons;
}

public void setPersons(Set<Person> persons) {
this.persons = persons;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class Test05 {

@Autowired
private PersonDao personDao;
@Autowired
private UnitDao unitDao;

@Test
@Transactional//解决java代码中的noSession
public void testQueryOneToMany() {
Unit unit = unitDao.getOne(3);//懒加载
// Unit unit = unitDao.findById(3).get();//立即加载
//对象导航查询
Set<Person> persons = unit.getPersons();//对象导航查询,也就是级联查询,需要配置注解
System.out.println("测试懒加载");
System.out.println(unit);
System.out.println(persons.size());

}
}

6.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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@Entity
@Table(name = "person")
public class Person {
@Id
@Column(name="p_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer pId;
@Column(name="person_name")
private String personName;
@Column(name="person_address")
private String personAddress;

//配置个人和单位的多对一关系
@ManyToOne(targetEntity = Unit.class,cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name = "unit_id",referencedColumnName = "unit_Id")
private Unit unit;

public Integer getpId() {
return pId;
}

public void setpId(Integer pId) {
this.pId = pId;
}


public String getPersonName() {
return personName;
}

public void setPersonName(String personName) {
this.personName = personName;
}

public String getPersonAddress() {
return personAddress;
}

public void setPersonAddress(String personAddress) {
this.personAddress = personAddress;
}

public Unit getUnit() {
return unit;
}

public void setUnit(Unit unit) {
this.unit = unit;
}

@Override
public String toString() {
return "Person{" +
"pId=" + pId +
", personName='" + personName + '\'' +
", personAddress='" + personAddress + '\'' +
'}';
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class Test05 {

@Autowired
private PersonDao personDao;
@Autowired
private UnitDao unitDao;

@Test
@Transactional//解决java代码中的noSession
public void testQueryManyToOne() {
Person one = personDao.getOne(3);
Unit unit = one.getUnit();
System.out.println("测试懒加载");
System.out.println(unit);
}
}

七、实际案例

7.1 筛选查询

以上这种的算是太入门级别的查询了,工作中用到的查询,瞧一瞧!先上原型

这要涉及到开始时间、结束时间、申请状态、项目编号,以及输入框中接口名称和接口名字的模糊查询。

SQL语句应该先判断各项是否为空,为空就是全查,如果不为空,就要用条件来查了。

如果全部为空的情况下,不考虑外连接、内连接的情况下,应该是这样。

1
select * from table where startTime<? and endTime>? and projectCode=? and applyState=? and (code=? or name=?);

而实际中,applyState又是一个靠自己计算出来的值,如果申请人是当前登录用户,那么就是已申请;如果申请人不等于当前用户或者为null那么就不是。这样又多出来一个or。最后的实现时这样的。

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
public Specification<InterfaceInfo> queryApplyByPageSpecification(InterfaceApplyQueryRequest request) {
return (Specification<InterfaceInfo>) (root, cq, cb) -> {
ArrayList<Predicate> predicatesAnd = new ArrayList<>();
predicatesAnd.add(cb.equal(root.get("interfaceState"), DataStates.USING.code));
Join<InterfaceInfo, Project> join = root.join("projectComm", JoinType.INNER);
if (!ObjectUtils.isEmpty(request.getApplyState())) {
if (request.getApplyState() == InterfaceApplyType.NOT_APPLIED.code) {
Join<InterfaceInfo, InterfaceApplyInfo> leftJoin = root.join("interfaceApplyInfos", JoinType.LEFT);
Predicate applyUserIdNotEqual = cb.notEqual(leftJoin.get("applyUserId"), UserUtils.getLogInfo().getId());
Predicate applyUserIdIsNull = cb.isNull(leftJoin.get("applyUserId"));
Predicate notEqualOrIsNull = cb.or(applyUserIdNotEqual, applyUserIdIsNull);
predicatesAnd.add(notEqualOrIsNull);
//实现notIn子查询,未申请里面应该排除掉(申请者是当前用户的并且审批状态为非拒绝的),太坑了!
Subquery<String> subQuery = cq.subquery(String.class);
Root<InterfaceInfo> subRoot = subQuery.from(InterfaceInfo.class);
Join<InterfaceInfo, InterfaceApplyInfo> subLeftJoin = subRoot.join("interfaceApplyInfos", JoinType.INNER);
subQuery.select(subRoot.get("interfaceCode")).where(cb
.equal(subLeftJoin.get("applyUserId"), UserUtils.getLogInfo().getId()),
cb.notEqual(subLeftJoin.get("interfaceApplyStatus"), DataStates.APPROVAL_NO_PASS.code));
predicatesAnd.add(cb.not(cb.in(root.get("interfaceCode")).value(subQuery)));
}
if (request.getApplyState() == InterfaceApplyType.APPLIED.code) {
Join<InterfaceInfo, InterfaceApplyInfo> leftJoin = root.join("interfaceApplyInfos", JoinType.LEFT);
predicatesAnd.add(cb.equal(leftJoin.get("applyUserId"), UserUtils.getLogInfo().getId()));
predicatesAnd.add(cb.equal(leftJoin.get("interfaceApplyStatus"), DataStates.USING.code));
}
if (request.getApplyState() == InterfaceApplyType.APPLYING.code) {
Join<InterfaceInfo, InterfaceApplyInfo> leftJoin = root.join("interfaceApplyInfos", JoinType.LEFT);
predicatesAnd.add(cb.equal(leftJoin.get("applyUserId"), UserUtils.getLogInfo().getId()));
predicatesAnd.add(cb.notEqual(leftJoin.get("interfaceApplyStatus"), DataStates.USING.code));
predicatesAnd.add(cb.notEqual(leftJoin.get("interfaceApplyStatus"), DataStates.APPROVAL_NO_PASS.code));
}
}
if (!ObjectUtils.isEmpty(request.getKeywords())) {
Predicate or = cb.or(cb.like(root.get("interfaceName"), '%' + request.getKeywords() + '%'), cb.like(root.get("interfaceCode"), '%' + request.getKeywords() + '%'));
predicatesAnd.add(or);
}
if (!ObjectUtils.isEmpty(request.getProjectCode())) {
Predicate projectName = cb.like(join.get("projectCode"), '%' + request.getProjectCode() + '%');
predicatesAnd.add(projectName);
}
if (!ObjectUtils.isEmpty(request.getStartTime())) {
Predicate createTime = cb.greaterThanOrEqualTo(root.get("createTime"), request.getStartTime());
predicatesAnd.add(createTime);
}
if (!ObjectUtils.isEmpty(request.getEndTime())) {
Predicate createTime = cb.lessThanOrEqualTo(root.get("createTime"), request.getEndTime());
predicatesAnd.add(createTime);
}
Predicate[] arrayAnd = new Predicate[predicatesAnd.size()];
Predicate and = cb.and(predicatesAnd.toArray(arrayAnd));
//cq.groupBy(root.get("interfaceCode"));//使用distinct
cq.distinct(true);
return cq.where(and).getRestriction();
};
}

其实这个代码还可以优化,里面有很多重复代码

7.2 查询成新的Pojo

新的pojo

1
2
3
4
5
6
public Class InterfaceLogCount{
private String code;
private Long count;

getter and setter...
}

查询接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface InterfaceLogRepository extends JpaRepository<InterfaceLog, String>, JpaSpecificationExecutor<InterfaceLog> {
/**
* 通过编号和类型查询接口
*
* @param code
* @param type
* @return
*/
List<InterfaceLog> findAllByInterfaceCodeAndInterfaceType(String code, Integer type, Pageable page);

/**
* 按人员调用日志的次数由高到低,取前100条接口编号
*
* @return
*/
@Query("select new com.shenlan.dataserver.interfacelog.interfaces.rest.dto.InterfaceLogCount(il.interfaceCode,count(il.interfaceCode)) " +
"from InterfaceLog as il where il.interfaceCallType=2 group by il.interfaceCode order by count(il.interfaceCode) desc")
List<InterfaceLogCount> findCountGroupByCode(Pageable page);
}

八、联合主键

直接放上源码

发布:2021-04-18 17:23:31
修改:2023-02-08 20:48:18
链接:https://meethigher.top/blog/2021/spring-data-jpa/
标签:jpa spring 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏