Spring Data JPA
JPA即Java Persistence API,是一个基于O/R映射的标准规范,该规范只负责定义规则的标准(注解或接口),而不需要提供具体实现,具体的实现交由软件提供商来实现,目前主要的JPA提供商为Hibernate,EclipseLink和OperJPA。
Spring Data JPA是Spring Data的一个子项目,通过提供基于JPA的Repository来简化代码量。
其提供了一个org.springframework.data.jpa.repository.JpaRepository,我们的Repository只要继承该JpaRepository,即可享受到JPA带来的好处。
Spring Boot通过spring-boot-starter-data-jpa来提供对JPA的支持,Spring Boot默认的JPA实现者是Hibernate。
Spring Boot项目中使用JPA
创建项目时选择JPA依赖,或者手工将spring-boot-starter-data-jpa添加到pom中。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
此时项目会自动开启如下两个自动配置类:
- JpaRepositoriesAutoConfiguration
- HibernateJpaAutoConfiguration
application.properties中增加jpa相关配置
#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd
#spring_jpa
#启动时会根据实体类生成数据表,或者更新表结构,不清空数据,开发阶段使用;validate:表结构稳定后使用,可用于正式环境;
spring.jpa.hibernate.ddl-auto=update
#控制台打印sql
spring.jpa.show-sql=true
#让控制器输出的json格式更美观
spring.jackson.serialization.indent-output=true
在项目中使用JPA时,只需要创建一个继承于JpaRepository的Repository接口,即可拥有JpaRepository及其父类中提供的全部数据访问方法。如果提供的方法不满足业务需要,可以按如下规则扩展数据方法。
package org.springframework.data.jpa.repository;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAll(Iterable<ID> var1);
<S extends T> List<S> save(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
自定义Repository:PersonRepository,并扩展数据访问方法,具体扩展方法参看示例代码
package com.example.dao;
import com.example.model.Person;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonRepository extends JpaRepository<Person, Integer> {
//1.以下方法基于属性名称和查询关键字,所以方法名称必须遵循命名规则,并且参数类型要与实体的参数类型一致。
// 只用于查询方法,以下给出常用的示例
//等于
List<Person> findByPName(String PName);
//And --- 等价于 SQL 中的 and 关键字;
List<Person> findByPNameAndPAge(String PName, Integer PAge);
// Or --- 等价于 SQL 中的 or 关键字;
List<Person> findByPNameOrPAge(String PName, Integer PAge);
//Between --- 等价于 SQL 中的 between 关键字;
List<Person> findByPAgeBetween(Integer min, Integer max);
//LessThan --- 等价于 SQL 中的 "<"; 日期类型也可以使用Before关键字
List<Person> findByPAgeLessThan(Integer max);
//LessThanEqual --- 等价于 SQL 中的 "<=";
List<Person> findByPAgeLessThanEqual(Integer max);
//GreaterThan --- 等价于 SQL 中的">";日期类型也可以使用After关键字
List<Person> findByPAgeGreaterThan(Integer min);
//GreaterThanEqual --- 等价于 SQL 中的">=";
List<Person> findByPAgeGreaterThanEqual(Integer min);
//IsNull --- 等价于 SQL 中的 "is null";
List<Person> findByPNameIsNull();
//IsNotNull --- 等价于 SQL 中的 "is not null";
List<Person> findByPNameIsNotNull();
//NotNull --- 与 IsNotNull 等价;
List<Person> findByPNameNotNull();
//Like --- 等价于 SQL 中的 "like";
List<Person> findByPNameLike(String PName);
//NotLike --- 等价于 SQL 中的 "not like";
List<Person> findByPNameNotLike(String PName);
//OrderBy --- 等价于 SQL 中的 "order by";
List<Person> findByPNameNotNullOrderByPAgeAsc();
//Not --- 等价于 SQL 中的 "! =";
List<Person> findByPNameNot(String PName);
//In --- 等价于 SQL 中的 "in";
List<Person> findByPNameIn(String PName);
//NotIn --- 等价于 SQL 中的 "not in";
List<Person> findByPNameNotIn(String PName);
//Top --- 查询符合条件的前两条记录,等价与First关键字
List<Person> findTop2ByPName(String PName);
//2.以下方法基于@Query注解,方法名称可以随意,可用于查询和更新方法,更新方法要设置@Modifying注解
//使用命名参数
@Query("select p from Person p where p.pName = :name and p.pAge = :age")
List<Person> withNameAndAgeQuery(@Param("name") String name, @Param("age") Integer age);
//使用参数索引
@Query("select p from Person p where p.pName = ?1 and p.pAge = ?2")
List<Person> withNameAndAgeQuery2(String name, Integer age);
//删除操作,使用hql,如果要使用sql,需要增加nativeQuery = true
@Query(value = "delete from Person where pId=?1")
@Modifying
int deletePersonById(Integer id);
//修改操作
@Query(value = "update Person set pName=?1 where pId=?2 ")
@Modifying
int updatePersonName(String name, Integer id);
//插入操作,使用sql操作
@Query(value = "insert into person(p_name,p_age) value(?1,?2)",nativeQuery = true)
@Modifying
int insertPersonByParam(String name, Integer age);
//3.以下方法实现分页查询功能,只需要在方法中增加Pageable pageable参数即可,返回结果为Page集合
Page<Person> findByPNameNot(String name, Pageable pageable);
//使用命名参数
@Query("select p from Person p where p.pName = :name ")
Page<Person> withNameQueryPage(@Param("name") String name, Pageable pageable);
}
POJO实体对象:Person
package com.example.model;
import javax.persistence.*;
import static javax.persistence.GenerationType.IDENTITY;
@Entity //lombook注解,简化开发
@Table(name = "person")
public class Person implements java.io.Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "p_id", unique = true, nullable = false)
private Integer pId;
@Column(name = "p_name", length = 45)
private String pName;
@Column(name = "p_age")
private Integer pAge;
//setter and getter
@Override
public String toString() {
return "Person{" +
"pId=" + pId +
", pName='" + pName + '\'' +
", pAge=" + pAge +
'}';
}
}
测试演示
package com.example;
import com.example.dao.PersonRepository;
import com.example.model.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.Iterator;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class JpaSingleDatasourceApplicationTests {
@Autowired
private PersonRepository personRepository;
@Test
public void findByPName() {
String name = "王五";
List<Person> list = personRepository.findByPName(name);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
@Test
public void findByPNameAndPAge() {
String name = "王五";
int age = 18;
List<Person> list = personRepository.findByPNameAndPAge(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
@Test
public void findByPNameOrPAge() {
String name = "王五";
int age = 25;
List<Person> list = personRepository.findByPNameOrPAge(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
@Test
public void findTop2ByPName() {
String name = "王五";
List<Person> list = personRepository.findTop2ByPName(name);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
@Test
public void withNameAndAgeQuery() {
String name = "王五";
int age = 18;
List<Person> list = personRepository.withNameAndAgeQuery(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
@Test
public void withNameAndAgeQuery2() {
String name = "王五";
int age = 18;
List<Person> list = personRepository.withNameAndAgeQuery2(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}
@Test
public void deletePersonById(){
int id = 1;
int result = personRepository.deletePersonById(id);
System.out.println("result = " + result);
}
@Test
public void updatePersonName(){
int id = 1;
String name = "哈哈";
int result = personRepository.updatePersonName(name,id);
System.out.println("result = " + result);
}
@Test
public void insertPersonByParam(){
int age = 10;
String name = "哈哈";
int result = personRepository.insertPersonByParam(name,age);
System.out.println("result = " + result);
}
@Test
public void findByPNameNot(){
String name = "哈哈";
//排序
Sort sort = new Sort(Sort.Direction.DESC, "pId");
//查询第一页,按一页三行分页
Pageable pageable = new PageRequest(0, 3, sort);
Page<Person> pages = personRepository.findByPNameNot(name,pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
Iterator<Person> it=pages.iterator();
while(it.hasNext()){
System.out.println("value:"+((Person)it.next()));
}
}
@Test
public void withNameQueryPage(){
String name = "王五";
//排序
Sort sort = new Sort(Sort.Direction.DESC, "pId");
//查询第二页,按一页三行分页
Pageable pageable = new PageRequest(1, 3, sort);
Page<Person> pages = personRepository.withNameQueryPage(name,pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
Iterator<Person> it=pages.iterator();
while(it.hasNext()){
System.out.println("value:"+((Person)it.next()));
}
}
}
测试数据库
CREATE SCHEMA `springboot1` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot1`.`person` (
`p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`p_name` VARCHAR(45) NULL COMMENT '姓名',
`p_age` INT NULL COMMENT '年龄',
PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人员信息表';
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '张三', '20');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('3', '王五', '18');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('4', '王五', '18');
JpaSpecificationExecutor查询接口 (动态条件查询)
- 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
- Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象
Spring Data Jpa 支持 Criteria 查询方式,使用这种方式需要继承 JpaSpecificationExecutor 接口,该接口提供了如下一些方法
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);
调用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> var1, Pageable pageable);
Specification: 封装了 JPA Criteria 查询的查询条件
Pageable: 封装了请求分页的信息: 例如 pageNo, pageSize, Sort
通过实现 Specification 中的 toPredicate 方法来定义动态查询,通过 CriteriaBuilder 来创建查询条件
Page<ComicDailyDetail> allInfo = dailyDetailRepo.findAll(new Specification<ComicDailyDetail>() {
@Override
public Predicate toPredicate(Root<ComicDailyDetail> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
/**
* @param *root: 代表查询的实体类.
* @param query: 可以从中可到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
* 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
* @param *cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
* @return: *Predicate 类型, 代表一个查询条件.
*/
List<Predicate> list = new ArrayList();
Expression<String> dayExp = root.get("day");
list.add(cb.equal(dayExp, finalDate));
Expression<String> platformIdExp = root.get("platformId");
list.add(cb.equal(platformIdExp, platformId));
// add到一起表示且关系,or就是或关系
Predicate[] p = new Predicate[list.size()];
query.where(list.toArray(p));
query.orderBy(cb.desc(root.get(orderTitle)));
return null;
}
}, pageRequest);
List<Student> stus = studentSpecificationRepository.findAll(new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//root.get("address")表示获取address这个字段名称,like表示执行like查询,%zt%表示值
Predicate p1 = criteriaBuilder.like(root.get("address"), "%zt%");
Predicate p2 = criteriaBuilder.greaterThan(root.get("id"),3);
//将两个查询条件联合起来之后返回Predicate对象
return criteriaBuilder.and(p1,p2);
}
});
可以定义多个 Specification,然后通过 Specifications 对象将其连接起来
//第一个Specification定义了两个or的组合
Specification<Student> s1 = new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate p1 = criteriaBuilder.equal(root.get("id"),"2");
Predicate p2 = criteriaBuilder.equal(root.get("id"),"3");
return criteriaBuilder.or(p1,p2);
}
};
//第二个Specification定义了两个or的组合
Specification<Student> s2 = new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate p1 = criteriaBuilder.like(root.get("address"),"zt%");
Predicate p2 = criteriaBuilder.like(root.get("name"),"foo%");
return criteriaBuilder.or(p1,p2);
}
};
//通过Specifications将两个Specification连接起来,第一个条件加where,第二个是and
List<Student> stus = studentSpecificationRepository.findAll(Specifications.where(s1).and(s2));
这个代码对应的SQL语句是
select * from t_student where (id=2 or id=3) and (address like 'zt%' and name like 'foo%')
CriteriaBuilder 中各个方法的对应
equle : filed = value gt / greaterThan : filed > value lt / lessThan : filed < value ge / greaterThanOrEqualTo : filed >= value le / lessThanOrEqualTo: filed <= value notEqule : filed != value like : filed like value notLike : filed not like value