一、Spring Data Jpa 简介
1.JPA
JPA
(Java Persistence API
)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338,这些接口所在包为javax.persistence
,详细内容可参考:https://github.com/javaee/jpa-spec)
JPA的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。总的来说,JPA包括以下3方面的技术:
- ORM映射元数据: 支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系
- API: 操作实体对象来执行CRUD操作
- 查询语言: 通过面向对象而非面向数据库的查询语言(
JPQL
)查询数据,避免程序的SQL语句紧密耦合
JPA架构
2.Spring Data Jpa
https://spring.io/projects/spring-data-jpa#overview
Spring Data Jpa官方解释
Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库。 此模块处理对基于JPA的数据访问层的增强支持。 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。
在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦。 必须编写太多样板代码来执行简单查询以及执行分页和审计。 Spring Data JPA旨在通过减少实际需要的工作量来显著改善数据访问层的实现。 作为开发人员,您编写repository接口,包括自定义查找器方法,Spring将自动提供实现。
Spring Data生态
3.Jpa、Hibernate、Spring Data Jpa三者之间的关系
JPA是ORM规范,Hibernate、TopLink等是JPA规范的具体实现,这样的好处是开发者可以面向JPA规范进行持久层的开发,而底层的实现则是可以切换的。Spring Data Jpa则是在JPA之上添加另一层抽象(Repository层的实现),极大地简化持久层开发及ORM框架切换的成本。
Jpa、Hibernate、Spring Data Jpa三者之间的关系
二、入门案例
项目结构
build.gradle
dependencies {
compile group: 'junit', name: 'junit', version: '4.12'
compile group: 'org.springframework', name: 'spring-test', version: '5.1.8.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.3.Final'
compile group: 'org.hibernate', name: 'hibernate-c3p0', version: '5.4.3.Final'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.1.9.RELEASE'
compileOnly 'org.projectlombok:lombok:1.18.4'
annotationProcessor 'org.projectlombok:lombok:1.18.4'
}
user.sql
INSERT INTO `user` VALUES (1, 'Andrew', 22, '湖北');
INSERT INTO `user` VALUES (2, 'Jack', 22, '湖北');
INSERT INTO `user` VALUES (3, 'Andy', 18, '湖南');
INSERT INTO `user` VALUES (4, 'Tom', 18, '河北');
- 配置spring相关
- 数据源信息
- jpa的实现方式
- 配置要用到的实体类
- 配置jpa实现方的配置信息
- 配置事务管理器
- 声明式事务
spring_data_jpa.xml
<?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:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--spring-->
<!--配置spring的注解扫描-->
<context:component-scan base-package="com.daniel"/>
<!--spring data jpa-->
<!--整合spring data jpa-->
<jpa:repositories base-package="com.daniel.dao" entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
<!--创建实体管理器工厂,交给spring管理-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置要扫描的包,实体所在包-->
<property name="packagesToScan" value="com.daniel.entity"/>
<!--配置jpa的实现方法2-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--jpa的实现方法的配置-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--数据库类型-->
<property name="database" value="MYSQL"/>
<!--控制台显示sql语句-->
<property name="showSql" value="true"/>
<!--是否自动创建数据库表-->
<property name="generateDdl" value="true"/>
<!--数据库方言-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
</bean>
</property>
<!--自动生成数据库表-->
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!--数据源配置项-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa?characterEncoding=utf8"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
</beans>
User.java
package com.daniel.entity;
import lombok.Data;
import javax.persistence.*;
/**
* @Author Daniel
* @Description 实体类
**/
@Data
@Entity
@Table(name = "user")
public class User {
@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;
}
@Query
- value:数据库操作语句
- nativeQuery:是否是原生查询,默认false,即默认使用jpql查询
@Modifying:声明当前是一个更新操作,需要修改数据库数据。
- 只能用于void或int/Integer的返回类型
- 因为需要修改数据库数据,未防止修改失败造成未知后果,需要搭配事务管理来是使用
@Transactional:添加事务管理支持
- 一般需要设置rollbackFor或者noRollbackFor,来表示什么情况下进行事务回滚
@Rollback:是否可以回滚,默认true
方法命名规则查询
spring data jpa制定了一些约定,如果按照这些约定来定义方法名,则会自动解析出sql语句。
findBy + 属性名 + 查询方式 + (And|Or) + 属性名 + 查询方式...
查询方式 | 方法命名 | sql where字句 |
And | findByNameAndPwd | where name= ? and pwd =? |
Or | findByNameOrSex | where name= ? or sex=? |
Is,Equals | findById,findByIdEquals | where id= ? |
Between | findByIdBetween | where id between ? and ? |
LessThan | findByIdLessThan | where id < ? |
LessThanEquals | findByIdLessThanEquals | where id <= ? |
GreaterThan | findByIdGreaterThan | where id > ? |
GreaterThanEquals | findByIdGreaterThanEquals | where id > = ? |
After | findByIdAfter | where id > ? |
Before | findByIdBefore | where id < ? |
IsNull | findByNameIsNull | where name is null |
isNotNull,NotNull | findByNameNotNull | where name is not null |
Like | findByNameLike | where name like ? |
NotLike | findByNameNotLike | where name not like ? |
StartingWith | findByNameStartingWith | where name like '?%' |
EndingWith | findByNameEndingWith | where name like '%?' |
Containing | findByNameContaining | where name like '%?%' |
OrderBy | findByIdOrderByXDesc | where id=? order by x desc |
Not | findByNameNot | where name <> ? |
In | findByIdIn(Collection<?> c) | where id in (?) |
NotIn | findByIdNotIn(Collection<?> c) | where id not in (?) |
True | findByAaaTue | where aaa = true |
False | findByAaaFalse | where aaa = false |
IgnoreCase | findByNameIgnoreCase | where UPPER(name)=UPPER(?) |
UserDao.java
package com.daniel.dao;
import com.daniel.entity.User;
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.data.repository.query.Param;
import java.util.List;
/**
* @Author Daniel
* @Description Dao层
* 继承两个接口
* 1.JpaRepository:封装了增删改查分页排序等基本操作
* 2.JpaSpecificationExecutor:封装了标准查询
**/
public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
/* // 原生sql语句查询
@Query(value = "query * from user where name = :name and age = :age", nativeQuery = true)
public User findUserByName(@Param("name") String userName, @Param("age") int age);*/
// jpql查询
@Query(value = "from User where name = :name and age = :age")
public User findUserByName(@Param("name") String userName, @Param("age") int age);
// jpql更新
@Query(value = "update User set name = :name where id = :id")
@Modifying
public Integer updateNameById(@Param("id") int id, @Param("name") String userName);
// 方法命名规则查询
public User findByName(String name);
public User findByNameLike(String name);
public User findByNameLikeAndAge(String name, int age);
public List<User> findByIdBetween(int idMin, int idMax);
}
1.增删改查
CURDTest.java
package com.daniel.test;
import com.daniel.dao.UserDao;
import com.daniel.entity.User;
import org.junit.runner.RunWith;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
/**
* @Author Daniel
* @Description 数据库操作
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
public class CURDTest {
@Autowired
private UserDao userDao;
@Test
public void save() {
User user = new User();
user.setName("Daniel");
user.setAge(22);
user.setAddress("湖北");
User userResult = userDao.save(user);
System.out.println(userResult);
}
@Test
public void delete() {
userDao.deleteById(5);
}
@Test
/*
如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,
加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚
*/
@Transactional(rollbackFor = Exception.class)
// @Rollback(value = false)//发生异常不回滚
public void update() {
User user = userDao.findUserByName("Jack", 22);
System.out.println(user);
userDao.updateNameById(user.getId(), "Jack_2");
// 抛出异常
int exception = 1 / 0;
userDao.updateNameById(user.getId(), "Jack_3");
}
@Test
@Transactional
public void query() {
// 统计
long count = userDao.count();
System.out.println("count:" + count);
// exists系列方法,判断数据库中是否存在
boolean flag = userDao.existsById(5);
System.out.println("exists系列方法:" + flag);
/*
1.findById得到的是一个Optional(jdk1.8的新特性之一),之后用.get()就可以获取相应的对象
2.getOne得到的是对应得实体类对象,如果需要在单元测试中使用此方法,要加上事务支持注解,
即@Transactional,才可以正常使用,否则可能会抛出
org.hibernate.LazyInitializationException: could not initialize prox
*/
// find系列方法,立即加载,在一开始进行操作的时候就从数据库查询数据
Optional<User> user1 = userDao.findById(3);
System.out.println("find系列方法:" + user1.get());
// getOne,延迟加载,返回的是一个动态代理对象,调用时才从数据库查询数据
User user2 = userDao.getOne(3);
System.out.println("getOne:" + user2);
}
// 使用方法命名规则查询
@Test
public void definedQuery() {
User user1 = userDao.findByName("Andrew");
System.out.println(user1);
User user2 = userDao.findByNameLike("t%");
System.out.println(user2);
User user3 = userDao.findByNameLikeAndAge("j%", 22);
System.out.println(user3);
List<User> users = userDao.findByIdBetween(1, 3);
users.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
}
}
2.标准查询
这里是JpaSpecificationExecuto的源码,为了便于阅读,我删除了注释,有的方法已经在上面的代码中演示过。可以看到,这5个方法有个共同点,接收一个Specification参数。
package org.springframework.data.jpa.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
public interface JpaSpecificationExecutor<T> {
//查询一个
Optional<T> findOne(@Nullable Specification<T> spec);
//查询全部
List<T> findAll(@Nullable Specification<T> spec);
//查询全部 提供分页功能
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
//查询全部,提供排序功能
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
//统计
long count(@Nullable Specification<T> spec);
}
Specification是对JPA规范中Root、CriteriaQuery、CriteriaBuilder的一层封装,用于构建过滤条件。实例化Specification需要实现它的toPerdicate方法
这里介绍一个例子,查询表中年龄大于18的湖北人
SpecificationTest.java
package com.daniel.test;
import com.daniel.dao.UserDao;
import com.daniel.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
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.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.function.Consumer;
/**
* @Author Daniel
* @Description 标准查询
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
public class SpecificationTest {
@Autowired
private UserDao userDao;
@Test
public void get() {
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
// 构造两个过滤条件
Predicate namePredicate = criteriaBuilder.like(root.get("address"), "湖北%");
// >=18
Predicate agePredicate = criteriaBuilder.ge(root.get("age"), 18);
// 组合过滤条件
Predicate predicate = criteriaBuilder.and(namePredicate, agePredicate);
return predicate;
}
};
// 根据id排序
Sort sort = new Sort(Sort.Direction.DESC, "id");
// 分页(pageIndex,pageSize,sort)
Pageable pageable = PageRequest.of(0, 10, sort);
Page<User> users = userDao.findAll(specification, pageable);
users.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
}
}