( 七 )、 SpringBoot 整合 JPA
( 七 )、 SpringBoot 整合 JPA
JPA官网: https://docs.spring.io/spring-data/jpa/docs/2.3.1.RELEASE/reference/html/#jpa.repositories
1、maven 依赖:
<!--spring-data-jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2、yml配置:
spring:
application:
name: jpa-study
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# mysql 8.0 以下
# url: jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8&autoReconnect=true
# driver-class-name: com.mysql.jdbc.Driver
# mysql 8.0 以上使用
url: jdbc:mysql://localhost:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
jpa:
hibernate:
# 自动更新ddl-auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
# ·create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
# ·create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
# ·update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,
# 即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
# ·validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
ddl-auto: update
# 开启驼峰_转换
naming:
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
# 日志中显示sql语
show-sql: true
3、示例:
新建实体Person类:
@Entity
@Data
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", length = 20)
private String name;
@Column(name = "age", length = 4)
private int age;
}
@Entity 注解代表它是数据库持久化类
@GeneratedValue作用:
@GeneratedValue注解存在的意义主要就是为一个实体生成一个唯一标识的主键、@GeneratedValue提供了主键的生成策略。
strategy属性:提供四种值:
AUTO 主键由程序控制, 是默认选项 ,不设置就是这个
IDENTITY 主键由数据库生成, 采用数据库自增长, Oracle不支持这种方式
SEQUENCE 通过数据库的序列产生主键, MYSQL 不支持
Table 提供特定的数据库产生主键, 该方式更有利于数据库的移植
Dao:
@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}
controller:
@RestController
public class PersonController {
@Autowired
private PersonRepository personRepository;
/**
* 新增
* @param person
*/
@PostMapping(path = "addPerson")
public void addPerson(Person person) {
personRepository.save(person);
}
/**
* 删除
* @param userId
*/
@DeleteMapping(path = "deletePerson")
public void deletePerson(Long userId) {
//删除名字是张三的记录
Person person = new Person();
person.setName("张三");
personRepository.delete(person);
//删除主键是userId的记录(李四)
personRepository.deleteById(userId);
}
/**
* 查询
* @return
*/
@GetMapping("getPerson/{userId}")
public Object getPerson(@PathVariable("userId") Long userId){
Optional<Person> byId = personRepository.findById(userId);
// Person person = byId.get();
return byId;
}
/**
* 修改 save(String var1)
* 添加和更新(修改)操作都是save(T t)方法,逻辑是根据主键判断的,如果数据库中有数据能匹配到参数中的主键,就更新匹配到的数据,否则就新添加。
* @return
*/
@PutMapping("update")
public Object updatePerson(){
Person person = new Person();
person.setId(2L);
person.setName("张三丰");
return personRepository.save(person);
}
}
4、自定义查询--根据方法名创建查询
方法名称中受支持的关键字
关键词 | 示例 | JPQL片段 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5、使用 @Query
使用@Query注解在接口方法之上自定义执行SQL。
eg:
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1 AND u.userName=?2")
User findByEmailAddress(String emailAddress, String userName);
}
like
@Query中的高级表达式
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
本地查询
该 @Query
注释允许通过设定运行的原生查询 nativeQuery
标志设置为true,如图以下示例:
nativeQuery
属性如果设置为 true 时,表示的意思是:可以执行原生sql语句,所谓原生sql,也就是说这段sql拷贝到数据库中,然后把参数值给一下就能运行了,原生的 sql 都是真实的字段,真实的表名。如果设置为 false 时,就不是原生的 sql ,而不是数据库对应的真正的表名,而是对应的实体名,并且 sql 中的字段名也不是数据库中真正的字段名,而是实体的字段名;
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
通过使用以下方法在查询方法中声明本机计数查询以进行分页 @Query
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
使用排序
排序可以通过提供PageRequest
或Sort
直接使用来完成
修改查询
@Modifying
,如以下示例所示:
删除查询
复杂的条件查询以及分页、排序
* 分页、排序、复杂条件查询
* 在涉及到复杂的查询的时候,尤其是多条件查询的时候,如果通过命名方式实现,长长的方法名将是代码显得十分的不优雅。
* 这个时候,大多数人会选择使用NativeQuery,通过编写SQL语句来实现,这种方式导致的结果就是项目代码中遍地是SQL,
* 随着时间的推移,项目已经失去了使用JPA的初衷。能否有一种方式,在保障JPQL的风格里完成这种复杂的查询呢?
* 这里介绍一种简单的方式:JpaSpecificationExecutor
Dao层继承 JpaSpecificationExecutor<T>该接口允许基于JPA标准的API规范的运行。
@Repository
public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
@Query("select p from Person AS p where p.age < ?1")
List<Person> findByAgeLessThan(Integer age, Pageable pageable);
}
Service:
1: 简单的查询条件连接:
@Override
public Page<Person> findByPageAndParams(Person personParams, Integer startPage, Integer pageSize) {
// 排序信息
///Sort.Direction是个枚举有ASC(升序)和DESC(降序),eg:按年龄age字段升序排序
Sort ageSort = Sort.by(Sort.Direction.ASC, "age");
//分页信息 注意JPA的page是从0开始算的,page传0,查询的是第1页的数据
Pageable pageable = PageRequest.of(0, 10, ageSort);
//查询条件构造
Specification<Person> specification = new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/**
* @param *root: 代表查询的实体类.
* @param query: 可以从中得到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
* 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
* @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
* @return: *Predicate 类型, 代表一个查询条件.
*/
Path<String> name = root.get("name");
Path<Integer> age = root.get("age");
Predicate like = criteriaBuilder.like(name, "%" + personParams.getName() + "%");
Predicate lt = criteriaBuilder.lt(age, personParams.getAge());
// 多个条件拼接起来
Predicate predicate = criteriaBuilder.and(like, lt);
return predicate;
}
};
Page<Person> personPageList = personRepository.findAll(specification, pageable);
return personPageList;
}
这里需要用到CriteriaBuilder提供的几个方法: 将多个条件(predicate)连接
Predicate and(Predicate... restrictions);
Predicate or(Predicate... restrictions);
CriteriaQuery实现多条件组合
public Page<Person> findByPageAndParams(Person personParams, Integer startPage, Integer pageSize) {
// 排序信息
///Sort.Direction是个枚举有ASC(升序)和DESC(降序),eg:按年龄age字段升序排序
Sort ageSort = Sort.by(Sort.Direction.ASC, "age");
//分页信息 注意JPA的page是从0开始算的,page传0,查询的是第1页的数据
Pageable pageable = PageRequest.of(0, 10, ageSort);
//查询条件构造
Specification<Person> specification = new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/**
* @param *root: 代表查询的实体类.
* @param query: 可以从中得到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
* 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
* @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
* @return: *Predicate 类型, 代表一个查询条件.
*/
Path<Integer> id = root.get("id");
Path<String> name = root.get("name");
Path<Integer> age = root.get("age");
Predicate like = criteriaBuilder.like(name, "%" + personParams.getName() + "%");
Predicate lt = criteriaBuilder.lt(age, personParams.getAge());
// 多个条件拼接起来
Predicate predicate = criteriaBuilder.and(like, lt);
//添加where条件
criteriaQuery.where(predicate);
// //指定查询项,select后面的东西
criteriaQuery.multiselect(name,age,criteriaBuilder.count(id));
//分组
criteriaQuery.groupBy(id);
//排序
criteriaQuery.orderBy(criteriaBuilder.asc(id));
//筛选
criteriaQuery.having(criteriaBuilder.greaterThan(id,0));
//获取最终的Predicate
Predicate restriction = criteriaQuery.getRestriction();
return restriction;
}
};
Page<Person> personPageList = personRepository.findAll(specification, pageable);
return personPageList;
}
CriteriaQuery与entityManager整合
@PersistenceContext private EntityManager entityManager; public List<Person> test(){ CriteriaBuilder cb = entityManager.getCriteriaBuilder(); //User指定了查询结果返回至自定义对象 CriteriaQuery<Person> query = cb.createQuery(Person.class); Root<Person> root = query.from(Person.class); Path id = root.get("id"); List<Predicate> predicates=new ArrayList<Predicate>(); Predicate predicateId = cb.equal(id,1); predicates.add(predicateId); Path<Person> email = root.get("email"); Predicate predicateEmail = cb.equal(email, "aa@qq.com"); predicates.add(predicateEmail); Predicate endPredicate = cb.and(predicates.toArray(new Predicate[predicates.size()])); //添加where条件 query.where(endPredicate); //指定查询项,select后面的东西 // query.multiselect(id,email); //分组 query.groupBy(id); //排序 query.orderBy(cb.asc(id)); //筛选 query.having(cb.greaterThan(id,0)); TypedQuery<Person> q = entityManager.createQuery(query); List<Person> result = q.getResultList(); for (Person person : result) { //打印查询结果 System.out.println(person.toString()); } return result; }
动态sql构建
public Page<Person> test2(Person personParams, Integer startPage, Integer pageSize) {
// 排序信息
///Sort.Direction是个枚举有ASC(升序)和DESC(降序),eg:按年龄age字段升序排序
Sort ageSort = Sort.by(Sort.Direction.ASC, "age");
//分页信息 注意JPA的page是从0开始算的,page传0,查询的是第1页的数据
Pageable pageable = PageRequest.of(0, 10, ageSort);
//查询条件构造
Specification<Person> specification = new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
/**
* @param *root: 代表查询的实体类.
* @param query: 可以从中得到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
* 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
* @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
* @return: *Predicate 类型, 代表一个查询条件.
*/
Path<String> name = root.get("name");
Path<Integer> age = root.get("age");
Predicate conjunction = criteriaBuilder.conjunction();
List<Expression<Boolean>> expressions = conjunction.getExpressions();
if(!StringUtils.isEmpty(name)){
expressions.add(
criteriaBuilder.and(criteriaBuilder.like(name, "%" + personParams.getName() + "%"))
);
}
if (age != null) {
expressions.add(
criteriaBuilder.and(criteriaBuilder.lt(age, personParams.getAge()))
);
}
return conjunction;
}
};
Page<Person> personPageList = personRepository.findAll(specification, pageable);
return personPageList;
}