Spring Boot整合Spring Data JPA
Spring Boot整合Spring Data JPA
1 Spring Boot整合Spring Data JPA环境的搭建和测试
1.1 创建项目配置pom文件
Spring Boot整合Spring Data JPA时需要添加一下依赖包
<dependencies> <!-- 导入springboot的启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 导入mybatis的启动器 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <!-- 导入springboot整合spring Data JPA的启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- mysql数据库 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mysql的连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency>
该项目需要有一个父工程,该工程打包方式以pom形式打包,整合了测试所需要的的依赖包。
1.2 创建application.properties文件
注意:该文件这里主要配置数据库的连接,Jpa的一些属性
- 配置数据库的连接;
- 配置正向工程和sql语句的显示。
配置文件如下:
# 数据库的连接 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456 # 配置数据库连接池 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # Spring Data JPA的配置文件 # 开启正向工程 spring.jpa.hibernate.ddl-auto=update # 显示sql语句 spring.jpa.show-sql=true
1.3 创建实体类Users
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 10:13
* @Table(name = "t_users"):指明正向工程执行时在数据库中创建的数据表名称
* @description:TODO
*/
@Entity
@Table(name = "t_users")
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
@Column(name = "address")
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
1.4 创建dao接口
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 10:20
* 参数一 T :当前需要映射的实体
* 参数二 ID :当前映射的实体中的OID的类型
* @description:TODO
*/
public interface UsersRepository extends JpaRepository<Users,Integer> {
}
注意:这里使用JpaRepository接口,里面的方法我们都不需要编写,可以直接调用
1.5 编写启动器
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 10:12
* @description:TODO
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
1.6 编写测试方法
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 10:22
* @description:TODO
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
public class UsersTest {
@Autowired
private UsersRepository usersRepository;
@Test
public void testSave(){
Users users = new Users();
users.setAddress("天津");
users.setAge(20);
users.setName("张三");
this.usersRepository.save(users);
}
}
1.7 运行测试方法效果图如下
1.8 整合需要注意的点
- 依赖包
- application.properties文件的编写
2 Spring Data JPA的提供的核心接口
- Repository接口
- CurdRepository接口
- PagingAndSortingRepository接口
- JpaRepository接口
- JPASpecificationExecutor接口
2.1 Repository接口的使用
该接口提供了两种方式
- 提供了方法名称命名查询方式
- 提供了基于@Query注解查询与更新
2.1.1 方法名称命名查询方式
1 创建接口dao
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 11:01
* @description:演示方法名称命名查询
*/
public interface UsersRepositoryByName extends Repository<Users,Integer> {
/**
* 方法名称必须遵循驼峰式命名规则
* 方法名的构成:关键字findBy+属性名称(首字母大写)+查询条件(首字母大写)
*
* */
List<Users> findByName(String name);
/**
* 多条件查询
* */
List<Users> findByNameAndAge(String name,Integer age);
/**
* 模糊查询
*
* */
List<Users> findByNameLike(String name);
}
2 编写测试代码
/**
* 方法名称命名测试
* */
@Test
public void testFinByName(){
List<Users> list = this.usersRepositoryByName.findByName("张三");
for (Users users:list) {
System.out.println(users);
}
}
/**
* 方法名称命名测试
* */
@Test
public void testFinByNameAndAge(){
List<Users> list = this.usersRepositoryByName.findByNameAndAge("张三",20);
for (Users users:list) {
System.out.println(users);
}
}
/**
* 方法名称命名测试
* */
@Test
public void testFinByNameLike(){
List<Users> list = this.usersRepositoryByName.findByNameLike("张%");
for (Users users:list) {
System.out.println(users);
}
}
3 运行结果显示
4 从上面图中可以看到SQL语句符合设定方法的查询条件,对方法名称命名查询做出以下总结
- 方法名称必须遵循驼峰式命名规则
- 方法名的构成:关键字findBy+属性名称(首字母大写)+查询条件(首字母大写)
2.1.2 基于@Query注解查询与更新
1. 创建dao接口
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 15:53
* @description:Repository的Query注解的使用
*/
public interface UsersRepositoryQueryAnnotation extends Repository<Users,Integer> {
/**
* 使用HQL
* 这里注意几点:在低版本的spring boot中在HQL语句后面不需要加上版本号,
* 但是在Spring Boot2.x之后的版本在使用@Query注解时需要加上版本号
* */
@Query("from Users where name=?1")
List<Users> list(String name);
/**
* 使用标准的SQL语句
* */
@Query(value="select * from t_users where name=?1",nativeQuery=true)
List<Users> queryByNameUserSQL(String name);
/**
* @Query注解用于更新操作
* @Modifying:需要执行一个更新操作
* */
@Query("update Users set name =?1 where id=?1")
@Modifying
void updateUsersById(String name,Integer id);
}
2. 创建测试方法
/**
* @Query注解的测试1
* */
@Test
public void testQuery(){
List<Users> list = this.usersRepositoryQueryAnnotation.list("张三");
for (Users users:list) {
System.out.println(users);
}
}
/**
* @Query注解的测试2
* */
@Test
public void testQueryByNameSQL(){
List<Users> list = this.usersRepositoryQueryAnnotation.queryByNameUserSQL("张三");
for (Users users:list) {
System.out.println(users);
}
}
/**
* @Query注解的用于更新测试1
* */
@Test
public void testQueryUpdate(){
this.usersRepositoryQueryAnnotation.updateUsersById("张三三",1);
}
3. 运行截图如下
4. 执行更新时遇到的bug和解决方案
- 首先看一下运行测试方法时遇到的问题
分析一下这个bug:在对数据库表进行更新删除操作时,要求操作必须在一个事务当中,而在我们的dao接口和测试类中,很明显我们并没有开启事务操作,在测试方法上面加上事务注解即可
5 总结:使用@Query注解能解决多条件查询时方法名的命名长度问题,但是得注意HQL语句会随着使用的Spring Boot的版本高低有所区别
- @Test注解和@Transactional注解一起使用时,默认是自动回滚的,因此上面看到提示成功,但是数据库里的数据并没有修改
在测试方法上面加上一个注解@Rollback
/** * @Query注解的用于更新测试1 * */ @Test @Transactional @Rollback(false) public void testQueryUpdate(){ this.usersRepositoryQueryAnnotation.updateUsersById("张三三",1); }
2.2 CurdRepository接口的使用
2.2.1 CurdRepository接口的简单介绍
- CurdRepository接口继承了Repository接口
- Repository接口的所有方法他都可以使用
- 作用:实现增删改查的操作
- 该接口实现的方法如下图
-
1. 对CurdRepository的save方法源码进行解析如下图所示
- 查看该源码知道,对一个事务进行保存和更新操作时,首先会进行判断数据库中是否存在这一条数据,如果存在,则判断此次事务为update操作,不存在则是insert操作,下面进行测试 ;
编写dao接口如下
/** * @author fyzn12 * @version 1.0 * @date 2020/7/18 17:56 * @description:TODO */ public interface UsersCurdRepository extends CrudRepository<Users,Integer> { }
编写测试save方法如下
@Test public void testCurdRepositorySave(){ Users users = new Users(); users.setName("李四"); users.setAge(22); users.setAddress("天津"); this.usersCurdRepository.save(users); }
代码执行如下图所示
查看运行图可知,该save执行的insert操作。
2. 接下来测试update如下
编写测试update方法如下
@Test public void testCurdRepositoryUpdate(){ /* * 这里得注意,对于CurdRepository的更新操作,也是save操作 * */ Users users = new Users(); users.setId(2); users.setAddress("北京"); }
方法测试运行图如下图所示
查看运行图,SQL语句执行过程中,先执行了一次select操作,判断数据库中是否存在该数据,第二次执行update操作,这个也是使用CurdRepository执行更新操作需要注意的一点。
2.3 PagingAndSortingRepository接口的使用
2.3.1 PagingAndSortingRepository接口的简单介绍
- 该接口继承了CurdRepository接口
- CurdRepository接口的方法该接口也可以使用
- 该接口提供对查询所有数据进行排序的功能
- 该接口提供对所有数据进行分页功能
如图所示
2.3.2 PagingAndSortingRepository接口中排序功能的测试
1. 编写dao接口
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 18:30
* @description:TODO
*/
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<Users,Integer>{
}
2. 编写测试方法
@Test
public void testPaginAndSprtingRepositorySort(){
//定义排序规则
Order order = new Order(Direction.DESC,"id");
/**
* SpringBoot 2.2.1之后,Sort的构造方法变成私有了,不能通过构造方法实例化
*
* */
//Sort sort = new Sort(order);
//Sort对象封装了排序规则
List<Users> list = (List<Users>)this.userPagingAndSortingRepository.findAll(Sort.by(order));
for (Users users:list) {
System.out.println(users);
}
}
3. 测试方法执行如下
2.3.3 PagingAndSortingRepository接口中分页功能的演示
1. 编写dao接口接口和排序接口一样
2. 编写测试方法
@Test
public void testPaginAndSprtingRepositoryPage(){
//Pageable封装了分页的参数:当前页,每页显示的条数
//注意:当前页是从0开始(切记)
//SpringBoot 2.2.1之后不能通过new PageRequest()获取对象
Pageable pageable = PageRequest.of(0,1);
Page<Users> page = this.userPagingAndSortingRepository.findAll(pageable);
List<Users> list = page.getContent();
System.out.println("总共有:"+page.getTotalElements()+"条数据");
System.out.println("共有:"+page.getTotalPages()+"页");
System.out.println("当前是第 "+1+"页");
for (Users users:list) {
System.out.println(users);
}
}
3. 测试方法执行如下
2.4 JpaRepository接口的使用
2.4.1 JpaRepository接口的简单介绍
- 该接口继承了PagingAndSortingRepository
- 对继承的父接口的方法做了返回值的适配(也就是返回值不需要进行强制转化)
- 该接口在使用中更加适用于实际开发
2.4.2 JpaRepository接口的功能与上面PagingAndSortingRepository接口的功能相同,唯一优化的就是对父接口的返回值做了适配,这里就不在继续介绍
2.5 JPASpecificationExecutor接口的使用
2.5.1 JpaSpecificationExecutor接口的简单介绍
- 该接口主要提供了多条件查询的支持。并且可以在查询中添加分页与排序(PagingAndSortingRepository接口只能对所有数据进行排序和分页)
- 该接口是单独出现的,并没有继承上面的四个接口,是完全独立的。一般开发中是使用该接口与JpaRepository一起使用。很少单独使用该接口
- 该接口提供的方法
2.5.2 JpaSpecificationExecutor接口的单条件查询测试
1. 编写dao接口(下面测试接口相同将不再重写)
/**
* @author fyzn12
* @version 1.0
* @date 2020/7/18 19:41
* @description:TODO
*/
public interface UsersJPASpecificationExecutor extends JpaSpecificationExecutor<Users> , JpaRepository<Users,Integer> {
}
2. 编写测试方法
/**
* JpaSpecificationExecutor 单条件测试
*
* **/
@Test
public void testJpaSpecificationExecutor1(){
/**
* Specification:用于封装查询条件
*
* */
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/**
* Predicate:封装了单个查询条件,一个Predicate就是一个查询条件
* Root<Users> root:查询对象属性的封装
* CriteriaQuery<?> criteriaQuery:封装了要执行的查询中的各个部分的信息如select、form、order by
* CriteriaBuilder criteriaBuilder:查询条件的构造器,定义不同的查询条件
* */
//如现在要查询where name = "张三"
/**
* 参数一:查询的条件属性
* 参数二:条件的值
*
* */
Predicate predicate = criteriaBuilder.equal(root.get("name"), "张三");
return predicate;
}
};
List<Users> list = this.usersJPASpecificationExecutor.findAll(spec);
for (Users users:list) {
System.out.println(users);
}
}
3. 测试方法运行如下
2.5.3 JpaSpecificationExecutor接口的多条件查询测试
1. 编写dao接口
2. 编写测试方法
/**
* JpaSpecificationExecutor 多条件测试
*
* **/
@Test
public void testJpaSpecificationExecutor2(){
/**
* Specification:用于封装查询条件
*
* */
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/**
* Predicate:封装了单个查询条件,一个Predicate就是一个查询条件
* Root<Users> root:查询对象属性的封装
* CriteriaQuery<?> criteriaQuery:封装了要执行的查询中的各个部分的信息如select、form、order by
* CriteriaBuilder criteriaBuilder:查询条件的构造器,定义不同的查询条件
* */
//如现在要查询where name = "张三" and age =20
/**
* 参数一:查询的条件属性
* 参数二:条件的值
*
* */
return criteriaBuilder.and(criteriaBuilder.equal(root.get("name"),"张三"),criteriaBuilder.equal(root.get("age"),20));
}
};
List<Users> list = this.usersJPASpecificationExecutor.findAll(spec);
for (Users users:list) {
System.out.println(users);
}
}
/**
* JpaSpecificationExecutor 多条件测试
*
* **/
@Test
public void testJpaSpecificationExecutor3(){
/**
* Specification:用于封装查询条件
*
* */
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/**
* Predicate:封装了单个查询条件,一个Predicate就是一个查询条件
* Root<Users> root:查询对象属性的封装
* CriteriaQuery<?> criteriaQuery:封装了要执行的查询中的各个部分的信息如select、form、order by
* CriteriaBuilder criteriaBuilder:查询条件的构造器,定义不同的查询条件
* */
//如现在要查询where name = "张三" or age =20 or id=2
/**
* 参数一:查询的条件属性
* 参数二:条件的值
*
* */
return criteriaBuilder.or(criteriaBuilder.and(criteriaBuilder.equal(root.get("name"),"张三"),criteriaBuilder.equal(root.get("age"),20)),criteriaBuilder.equal(root.get("id"),2));
}
};
List<Users> list = this.usersJPASpecificationExecutor.findAll(spec);
for (Users users:list) {
System.out.println(users);
}
}
3. 测试方法运行如下
2.5.4 JpaSpecificationExecutor接口的使用的总结
- 但条件查询时,注意每个参数的意义,在测试方法中做了详细的介绍,这里将不再继续介绍
- 多条件查询时需要注意criteriaBuilder对象的各个方法的使用对应于SQL语句中不同条件
- 上一篇: Redis之java客户端
- 下一篇: 多线程