SpringBoot整合Spring Data JPA
1 Spring Data JPA
1.1 简介
1.1.1 JPA
JPA(Java Persistence API)即java持久化API,它的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。
JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,是JDK为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行
总的来说,JPA包括以下三方面的技术:
ORM映射元数据:支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系API:操作实体对象来执行CRUD操作查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合
1.1.2 Spring Data JPA
Spring Data JPA的基本介绍:
Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库。此模块处理对基于JPA的数据访问层的增强支持。它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。底层是使用hibernate实现
在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦。必须编写太多样板代码来执行简单查询以及执行分页和审计。Spring Data JPA旨在减少实际需要的工作量来显著改善数据访问层的实现
1.2 配置文件
pom依赖
SpringBoot整合Spring Data JPA:
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
数据库配置:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/chapter05?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#表示Jpa对应的数据库是mysql
spring.jpa.show-sql=true
#项目启动时根据实体类更新数据库中的表
spring.jpa.hibernate.ddl-auto=update
其中:spring.jpa.hibernate.ddl-auto,可选参数:
create:每次运行程序时,都会重新创建表,故而数据会丢失create-drop:每次运行程序时会先创建表结构,然后程序结束时清空表update:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)validate:运行程序会校验数据于数据库的字段类型是否相同,字段不同会报错none:禁用DDL处理
1.3 操作使用JPA
1.3.1 实体类相关
创建实体类:
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@Table(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "book_name")
private String name;
@Column(name = "book_author")
private String author;
private Float price;
@Transient
private String description;
}
注解解释:
@Entity:注解表示该类是一个实体类,在项目启动时会根据该类自动生成一张表,表的名称即@Entity注解中name的值,如果不配置name,默认表名为类名
指定实体名称(表名):- 没有指定
name属性且没有使用@Table,命名为类名生成 - 指定
name属性且没有使用@Table,命名为name属性value值 - 指定
name属性且使用了@Table指定name,命名以@Table的name的value
- 没有指定
@Id:所有的实体类都要有的主键,@Id注解表示该属性是一个主键@GeneratedValue:注解表示主键自动生成,strategy则表示主键的生成策略
JPA自带的几种主键生成策略:TABLE:使用一个特定的数据库表格来保存主键SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要于generator一起使用,generator指定生成主键的生成器IDENTITY:主键由数据库自动生成(主要支持自动增长的数据库,如mysql)AUTO:主键由程序控制,也是GenerationType的默认值,mysql不支持,会报错:test.hibernate_sequence不存在
@Column:默认情况下,生成的表中字段的名称是实体类中属性的名称,通过@Column注解可以定制生成的字段的属性,name表示该属性对应的数据表中字段的名称,nullable表示该字段非空@Transient:注解表示在生成数据库的表时,该属性被忽略,即不生成对应的字段
1.3.2 Dao层
1.3.2.1 基本示例
创建Dao接口,继承JpaRepository,代码如下:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public interface BookDao extends JpaRepository<User,Integer> {
//查询以某个字符开始的所有书
List<Book> getBooksByAuthorStartingWith(String author);
//查询单价大于某个值的所有书
List<Book> getBooksByPriceGreaterThan(Float price);
@Query(value = "select * from t_book where id=(select max(id) from t_book)",nativeQuery = true)
Book getMaxIdBook();
@Query("select b from t_book b where b.id>:id and b.author=:author")
List<Book> getBookByIdAndAuthor(@Param("author") String author,@Param("id") Integer id);
@Query("select b from t_book b where b.id<?2 and b.name like %?1%")
List<Book> getBookByIdAndName(String name,Integer id);
}
解释:
- 自定义
Dao继承自JpaRepositiory<T,ID>,需要两个参数T指当前需要映射得实体,ID指当前映射得实体中的主键类型。JpaRepositiory中提供了一些基本的数据操作方法,有基本的增删改查、分页查询、排序查询等 - 在
Spring Data JPA中,只要方法的定义符合既定规范,Spring Data就能分析出开发者意图,从而避免开发者定义SQL
1.3.2.2 @Query注解
@Query注解使用起来很简单,默认的属性是value,就是当前写的SQL语句,有时会用到nativeQuery属性,这个属性是用来标记当前的SQL是本地SQL,还是符合JPA语法规范的SQL。
这里需要解释一下本地SQL和JPA语法规范的SQL区别。
本地SQL:根据实际使用的数据库类型写的SQL,这种SQL中使用到的一些语法格式不能被JPA解析以及可能不兼容其他数据库,这种SQL称为本地SQL,此时需要将nativeQuery属性设置为true,否则会报错。JPA语法规范的SQL:往往这种SQL本身是不适用于任何数据库的,需要JPA将这种SQL转换成真正当前数据库所需要的SQL语法格式。
注意:JPA很好的一个特性就是用JPA语法规范写的SQL,会根据当前系统使用的数据库类型改变生成的SQL语法,兼容数据库类型的切换,如之前使用的是MySQL,现在换成Oracle,由于不同类型的数据库,SQL语法会有区别,如果使用的是mybatis,就需要手动去改SQL兼容Oracle,而JPA就不用,无缝对接。
说明:很大的时候使用JPA感觉都是为了兼容后期可能会有数据库切换的问题,所以在使用JPA的时候,不要去使用本地SQL,这就违背了使用JPA的初衷,让nativeQuery属性保持默认值即可
1.3.2.3 SQL传参
根据这个例子再引出一些常用的东西,代码如下:
//示例1
@Query("select t from Device t where t.deviceSn=:deviceSn and t.deleteFlag=1")
Device findExistDevice(@Param("deviceSn") String deviceSn);
//示例2
@Query("select t from Device t where t.deviceSn=:deviceSn and t.deviceType =:deviceType and t.deleteFlag=1")
Device findExistDevice(@Param("deviceSn") String deviceSn,@Param("deviceType")Integer deviceType);
//示例3
@Query("select t from Device t where t.deviceSn=?1 and t.deviceType = ?2 and t.deleteFlag=1")
Device findDevice(String deviceSn,Integer deviceType);
在SQL上使用占位符的两种方式:
- 使用
:后加变量的名称,需要使用@Param注解来指定变量名 - 使用
?后加方法参数的位置。就需要注意参数的位置。
SQL语句中直接用实体类代表表名,因为在实体类中使用了@Table注解,将该实体类和表进行了关联
1.3.2.4 @Modifying注解
相信在正常的项目开发中都会涉及到修改数据信息的操作,如逻辑删除、封号、解封、修改用户名、头像等等。在使用JPA的时候,如果@Query涉及到update就必须同时加上@Modifying注解,注明当前方法是修改操作。
如下代码:
@Modifying
@Query("update Device t set t.userName =:userName where t.id =:userId")
User updateUserName(@Param("userId") Long userId,@Param("userName") String userName);
当在测试类中直接调用时,需要需要在测试类中添加注解@Transactional,但是@Transactional和@Test一起连用会导致事务自动回滚,这时候需要指定事务不回滚@Rollback(false)
@Test
@Transactional
@Rollback(false)
public void updateById() {
dao11.updateById("张三",1);
}
1.3.3 Service层
创建BookService,代码如下:
import com.example.demo.dao.BookDao;
import com.example.demo.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
@Autowired
BookDao bookDao;
//save方法由JpaRepository接口提供
public void addBook(Book book){
bookDao.save(book);
}
//分页查询
public Page<Book> getBookByPage(Pageable pageable){
return bookDao.findAll(pageable);
}
public List<Book> getBooksByAuthorStartingWith(String author){
return bookDao.getBooksByAuthorStartingWith(author);
}
public List<Book> getBooksByPriceGreaterThan(Float price){
return bookDao.getBooksByPriceGreaterThan(price);
}
public Book getMaxIdBook(){
return bookDao.getMaxIdBook();
}
public List<Book> getBookByIdAndName(String name,Integer id){
return bookDao.getBookByIdAndName(name,id);
}
public List<Book> getBookByIdAndAuthor(String author,Integer id){
return bookDao.getBookByIdAndAuthor(author,id);
}
}
1.3.4 Controller层
创建BookContrller,实现对数据的测试,代码如下:
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class BookController {
@Autowired
BookService bookService;
@GetMapping(value = "/findAll")
public void findAll(){
PageRequest pageRequest = PageRequest.of(2,3);
Page<Book> page = bookService.getBookByPage(pageRequest);
System.out.println("总页数:"+page.getTotalPages());
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("查询结果:"+page.getContent());
//从0开始记,所以加上1
System.out.println("当前页数:"+(page.getNumber()+1));
System.out.println("当前记录数:"+page.getNumberOfElements());
System.out.println("每页记录数:"+page.getSize());
}
@GetMapping(value = "search")
public void search(){
List<Book> bs1 = bookService.getBookByIdAndAuthor("鲁迅",7);
List<Book> bs2 = bookService.getBooksByAuthorStartingWith("吴");
List<Book> bs3 = bookService.getBookByIdAndName("西",8);
List<Book> bs4 = bookService.getBooksByPriceGreaterThan(30F);
Book b = bookService.getMaxIdBook();
System.out.println("bs1:"+bs1);
System.out.println("bs2:"+bs2);
System.out.println("bs3:"+bs3);
System.out.println("bs4:"+bs4);
System.out.println("b:"+b);
}
@GetMapping(value = "/save")
public void save(){
Book book = new Book();
book.setAuthor("鲁迅");
book.setName("呐喊");
book.setPrice(23F);
bookService.addBook(book);
}
}
代码解释:
在findAll接口中,首先通过调用PageRequest中的of方法构造PageRequest对象。of方法接收两个参数:第一个参数是页数,从0开始计数,第二个参数是每页显示的条数
1.4 Spring Data JPA提供的核心接口
1.4.1 Repository接口
Repository<T, ID>:其中T是要查询的表,ID是主键类型
1.4.1.1 方法名称命名方式
方法名称命名查询方式,hibername会根据方法名生成对应的sql语句
注意:方法名称必须要遵循驼峰命名规则
import cn.jzh.entity.User;
import org.springframework.data.repository.Repository;
import java.util.List;
/**
* Repository接口方法名称命名查询
*/
public interface UserRepository extends Repository<User,Integer> {
List<User> findByName (String name);
List<User> findByNameOrAge(String name, Integer age);
List<User> findByNameLike(String name);
}
1.4.1.2 注解方式
@Query注解查询方式,用这样的方式就不局限于方法名(hibernate根据方法名生成sql)
@Query("from User u where u.name = ?1")
List<User> queryByUseHQL (String name);
@Modifying
@Query("update User set name =?1 where id =?2")
void updateById(String name, Integer id);
测试
@Test
public void queryByUse() {
List<User> list = dao11.queryByUseHQL("哈哈");
System.out.println(JSON.toJSON(list));
}
@Test
@Transactional
@Rollback(false)
public void updateById() {
dao11.updateById("张三",1);
}
当在测试类中直接调用时,需要需要在测试类中添加注解@Transactional,但是@Transactional和@Test一起连用会导致事务自动回滚,这时候需要指定事务不回滚@Rollback(false)
1.4.2 CrudRepository接口
CrudRepository<T, ID>:其中T是要查询的表,ID是主键类型
CrudRepository接口,主要是完成一些增删改查的操作,封装了好多接口可以直接使用,当然依旧可以使用注解方式
注意:CrudRepository接口继承了Repository接口
public interface UserCrudRepository extends CrudRepository<User,Integer> {
}
测试
@Test
public void testCrudAdd(){
User u = new User();
u.setName("张无忌");
u.setAge(20);
u.setAddress("冰火岛");
u.setTime(new Date());
User save = userCrudRepository.save(u);
System.out.println(save);
}
1.4.3 PagingAndSortingRepository接口
PagingAndSortingRepository<T, ID>:其中T是要查询的表,ID是主键类型
PagingAndSortingRepository主要是提供了分页与排序的操作
注意:PagingAndSortingRepository接口继承了CrudRepository接口
import org.springframework.data.repository.PagingAndSortingRepository;
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {
}
测试使用
//排序操作
@Test
public void pageAndSort(){
Sort.Order order = new Sort.Order(Sort.Direction.DESC,"id");
Sort sort = Sort.by(order);
List<User> all = (List<User>)dao.findAll(sort);
System.out.println(JSON.toJSON(all));
}
//分页操作
@Test
public void page(){
PageRequest pageParam = PageRequest.of(0, 1);
Page<User> page = dao.findAll(pageParam);
System.out.println("总页数:"+page.getTotalPages());
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("查询结果:"+page.getContent());
//从0开始记,所以加上1
System.out.println("当前页数:"+(page.getNumber()+1));
System.out.println("当前记录数:"+page.getNumberOfElements());
System.out.println("每页记录数:"+page.getSize());
}
1.4.4 JpaRepository接口
该接口继承了PagingAndSortingRepository接口,是开发中最常用的接口
对继承的父接口中的方法的返回值进行适配
具体使用方法参考上面例子
1.4.5 JpaSpecificationExecutor接口
JpaSpecificationExecutor<T>,其中T是要查询的表
JpaSpecificationExecutor接口主要是提供了多条件查询的支持,并且可以在查询中添加分页与排序。
注意:JpaSpecificationExecutor是单独存在,完全独立的,一般是和上面接口联合使用
public interface UserJPASpecificationExecutor
extends JpaRepository<User, Integer>,JpaSpecificationExecutor<User> {}
测试使用
@Autowired
private UserJPASpecificationExecutor executor;
@Test
public void jpsExecutorPage1(){
Specification<User> spec = new Specification<User>(){
/**
* 封装了查询条件
* root:查询对象的属性封装
* query:封装了要执行的查询中的各个部分信息:select from order by 等,询哪些字段,排序是什么(主要是把多个查询的条件连系起来)
* criteriaBuilder:查询条件的构造器,定义不同的查询条件
* 字段之间是什么关系,如何生成一个查询条件,每一个查询条件都是什么方式
* 主要判断关系(和这个字段是相等,大于,小于like等)
*/
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
/**
* 参数一:查询属性
* 参数二:条件的值
*/
List<Predicate> list = new ArrayList<>();
list.add(cb.equal(root.get("name"), "张三"));
Predicate[] arr = new Predicate[list.size()];
Predicate[] predicates = list.toArray(arr);
return cb.and(predicates);
或者这样操作
return cb.and(cb.equal(root.get("name"), "张三"),cb.equal(root.get("name"), "张三"));
}
};
List<User> list = executor.findAll(spec);
System.out.println(JSON.toJSON(list));
}

浙公网安备 33010602011771号