Spring Data JPA 概述
什么是Spring Data JPA
Spring Data JPA 是 Spring 基于JPA 规范的基础上封装的⼀套 JPA 应⽤框架,可使开发者⽤极简的
代码即可实现对数据库的访问和操作。-
Spring Data 家族
JPA是Java Persistence api:Java持久层api规范。
Spring Data JPA 是Spring提供的一个JPA操作的框架。
Hibernate是实现了JPA规范的ORM框架。
Spring Data JPA规范和Hibernate之间的关系
Spring Data JPA应用
数据库准备:
Spring Data JPA开发步骤梳理
- 创建工程
- 导入Meven依赖
- 配置Spring的配置文件
- 编写实体类,使用JPA注解配置映射关系
- 编写Spring Data JPA的Dao层接口
- 使用Dao层接口完成Dao层开发
Spring Data JPA开发实现
- Maven依赖
<dependencies>
<!--单元测试jar-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring-data-jpa 需要引⼊的jar,start-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.1-b04</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
<!--spring-data-jpa 需要引⼊的jar,end-->
<!--spring 相关jar,start-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--spring对orm框架的⽀持包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--spring 相关jar,end-->
<!--hibernate相关jar包,start-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.0.Final</version>
</dependency>
<!--hibernate对jpa的实现jar-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
<!--hibernate相关jar包,end-->
<!--mysql 数据库驱动jar-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!--spring-test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
</dependencies>
- 配置Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd
">
<!--对Spring和SpringDataJPA进⾏配置-->
<!--1、创建数据库连接池druid-->
<!--引⼊外部资源⽂件-->
<context:property-placeholder
location="classpath:jdbc.properties"/>
<!--第三⽅jar中的bean定义在xml中-->
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--2、配置⼀个JPA中⾮常重要的对象,entityManagerFactory
entityManager类似于mybatis中的SqlSession
entityManagerFactory类似于Mybatis中的SqlSessionFactory
-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
>
<!--配置⼀些细节.......-->
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置包扫描(pojo实体类所在的包)-->
<property name="packagesToScan"
value="com.xdf.jpa.pojo"/>
<!--指定jpa的具体实现,也就是hibernate-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--jpa⽅⾔配置,不同的jpa实现对于类似于beginTransaction等细节实现
起来是不⼀样的,
所以传⼊JpaDialect具体的实现类-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
<!--配置具体provider,hibearnte框架的执⾏细节-->
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--定义hibernate框架的⼀些细节-->
<!--
配置数据表是否⾃动创建因为我们会建⽴pojo和数据表之间的映射关系
程序启动时,如果数据表还没有创建,是否要程序给创建⼀下
-->
<property name="generateDdl" value="false"/>
<!--
指定数据库的类型
hibernate本身是个dao层框架,可以⽀持多种数据库类型
的,这⾥就指定本次使⽤的什么数据库
-->
<property name="database" value="MYSQL"/>
<!--
配置数据库的⽅⾔
hiberante可以帮助我们拼装sql语句,但是不同的数据库sql
语法是不同的,所以需要我们注⼊具体的数据库⽅⾔
-->
<property name="databasePlatform"
value="org.hibernate.dialect.MySQLDialect"/>
<!--是否显示sql
操作数据库时,是否打印sql
-->
<property name="showSql" value="true"/>
</bean>
</property>
</bean>
<!--3、引⽤上⾯创建的entityManagerFactory
<jpa:repositories> 配置jpa的dao层细节
base-package:指定dao层接⼝所在包
-->
<jpa:repositories base-package="com.xdf.jpa.dao" entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
<!--4、事务管理器配置
jdbcTemplate/mybatis 使⽤的是DataSourceTransactionManager
jpa规范:JpaTransactionManager
-->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--5、声明式事务配置-->
<!--
<tx:annotation-driven/>
-->
<!--6、配置spring包扫描-->
<context:component-scan base-package="com.xdf.jpa"/>
</beans>
- 编写实体类User,使用注解配置映射关系
package com.xdf.jpa.pojo;
import javax.persistence.*;
/**
* @author xdf
* @version 1.0
* @date Create in 14:38 2021/6/22
* @description 用户实体
* @modifiedBy
*/
@Entity
@Table(name = "tb_user")
public class User {
/**
* 主键
* @Id 标记为主键
* @GeneratedValue 标记主键生成策略
*
* 常用的生成策略:
* GenerationType.IDENTITY 依赖数据库的主键自增 mysql
* GenerationType.SEQUENCE 依赖序列来产生主键 Oracle
*
* @Column 实体属性和数据库字段映射
*/
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 用户名
*
*/
@Column(name = "username")
private String username;
/**
* 密码
*/
@Column(name = "address")
private String address;
/**
* 手机号
*/
@Column(name = "phone")
private String phone;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
- 编写Dao接口
package com.xdf.jpa.dao;
import com.xdf.jpa.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
* @author xdf
* @version 1.0
* @date Create in 14:47 2021/6/22
* @description User的持久层接口
* 继承JpaRepository接口,可以不编写代码直接实现基本的curd操作,两个泛型分别代表:实体类类型,主键类型。
* 继承JpaSpecificationExecutor接口,可以不编写代码实现复杂查询,泛型为实体类类型。
* @modifiedBy
*/
public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
/**
* 自定义查询,使用JPQL语法实现查询操作
* @param id 主键id
* @param username username
* @return User实体集合
*/
@Query("from User where id=?1 and username=?2")
List<User> findByJpql(Long id,String username);
/**
* 自定义查询,使用sql语法实现自定义查询
* 需要设置nativeQuery = true
* @param username 用户名
* @param address 地址
* @return 用户实体集合
*/
@Query(value = "select * from tb_user u where u.username=?1 and u.address=?2",nativeQuery = true)
List<User> findBySql(String username,String address);
/**
* 按方法命名规则来定义查询
* 查询条件写在By后面,多个查询条件用And连接,查询方式根在属性名之后
* @param username 用户名
* @param address 地址
* @return 用户实体集合
*/
List<User> findByUsernameAndAddressLike(String username,String address);
}
- 测试Dao层功能:
import com.xdf.jpa.dao.UserDao;
import com.xdf.jpa.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.persistence.criteria.*;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* @author xdf
* @version 1.0
* @date Create in 15:00 2021/6/22
* @description UserDao测试类
* @modifiedBy
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class UserDaoTest {
/**
* IOC注入dao
*/
@Autowired
private UserDao userDao;
/**
* 基础的查询操作
*/
@Test
public void testFindById() {
Optional<User> userOptional = userDao.findById(1L);
if (userOptional.isPresent()) {
User user = userOptional.get();
System.out.println(user);
}
}
@Test
public void testFindOne() {
User user = new User();
user.setId(1L);
user.setUsername("user1");
Example<User> userExample = Example.of(user);
Optional<User> userOptional = userDao.findOne(userExample);
userOptional.ifPresent(System.out::println);
}
@Test
public void testSave() {
// 新增和更新都使用save方法,有主键是更新,无主键是插入
User user = new User();
user.setUsername("zhangsan");
user.setAddress("上海外滩");
user.setPhone("123");
User save = userDao.save(user);
System.out.println(save);
}
@Test
public void testDelete() {
userDao.deleteById(2L);
}
@Test
public void testFindAll() {
List<User> all = userDao.findAll();
System.out.println(Arrays.toString(all.toArray()));
}
@Test
public void testSort() {
Sort sort = new Sort(Sort.Direction.DESC, "id");
List<User> all = userDao.findAll(sort);
System.out.println(Arrays.toString(all.toArray()));
}
@Test
public void testPage() {
// 起始页,从0开始;页大小
Pageable pageable = PageRequest.of(0, 1);
Page<User> all = userDao.findAll(pageable);
System.out.println(all);
}
@Test
public void testJpql() {
List<User> user1 = userDao.findByJpql(1L, "user1");
System.out.println(Arrays.toString(user1.toArray()));
}
@Test
public void testSql() {
List<User> bySql = userDao.findBySql("user1", "上海虹桥");
System.out.println(Arrays.toString(bySql.toArray()));
}
@Test
public void testMethodName() {
List<User> byUsernameAndAddressLike = userDao.findByUsernameAndAddressLike("user1", "上海%");
System.out.println(Arrays.toString(byUsernameAndAddressLike.toArray()));
}
/**
* 动态条件封装
* 匿名内部类
*
* toPredicate:动态条件组装
*
*/
@Test
public void testSpecification() {
Specification<User> specification = new Specification<User>() {
@Override
public Specification<User> and(Specification<User> other) {
return null;
}
@Override
public Specification<User> or(Specification<User> other) {
return null;
}
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> name = root.get("username");
Predicate predicate = criteriaBuilder.equal(name, "user1");
return predicate;
}
};
Optional<User> userOptional = userDao.findOne(specification);
User user = userOptional.get();
System.out.println(user);
}
@Test
public void testSpecificationMultiCon() {
Specification<User> specification = new Specification<User>() {
@Override
public Specification<User> and(Specification<User> other) {
return null;
}
@Override
public Specification<User> or(Specification<User> other) {
return null;
}
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> username = root.get("username");
Path<Object> address = root.get("address");
Predicate predicate = criteriaBuilder.equal(username, "user1");
Predicate like = criteriaBuilder.like(address.as(String.class), "上海%");
return criteriaBuilder.and(predicate, like);
}
};
Optional<User> user = userDao.findOne(specification);
System.out.println(user);
}
}
Spring Data JPA执行过程源码分析
代理对象怎么产生,过程怎么样?
JPA的Dao层接口也是通过动态代理来实现的。
在Spring容器中,bean都是在refresh方法中初始化的。
在refresh
方法的finishBeanFactoryInitialization
的preInstantiateSingletons
方法中打断点观察:
发现userDao在Ioc容器中注册为一个FactoryBean:JpaRepositoryFactoryBean
FactoryBean的getObject方法可以获取真正的bean 。
这个FactoryBean是什么时候放入到容器中去的?
因为这个beanDefinition
是在getMergedLocalBeanDefinition
中获取到的,跟进去看
先从mergedBeanDefinitions
集合中去查询,没有找到。那么就会进入getMergedBeanDefinition
在getMergedBeanDefinition
中发现一个向集合mergedBeanDefinitions
中put值的代码:
这个地方应该是个关键代码。
断点打到这里,发现
这里put进入的
BeanDefinition
正好就是JpaRepositoryFactoryBean
,而传入的bd也是JpaRepositoryFactoryBean
,mbd
是跟进bd
创建出来的。那么我找找bd
是什么时候传入的。回到上面,发现是在this.getBeanDefinition(beanName)
获取到的。
在beanDefinitionMap
中取到的userDao
的bean定义。
我们再跟踪beanDefinitionMap
的put方法,看是在什么位置设置到集合中去的
我们发现在registerBeanDefinition
中对beanDefinitionMap
进行了put操作。因此在registerBeanDefinition
打断点观察
发现在这里进行
BeanDefinition
注册的时候,传入的就是一个FactoryBean
类。根据调用栈找到
RepositoryConfigurationDelegate
中的registerRepositoriesIn
构建BeanDefinition
依次跟进去返现,被直接指定为一个JpaRepositoryFactoryBean
类。
在扫描Dao并注册到BeanDefinition中的时候,固定注册JpaRepositoryFactoryBean到容器中。
getObject方法到底返回什么bean?
JpaRepositoryFactoryBean的getObject是在父类中定义的
getObject是从一个集合repository中获取的。
我们看这个集合是在哪了被赋值的?
在afterPropertiesSet
方法中,发现集合被赋值
afterPropertiesSet
是InitializingBean的钩子方法,在Spring的生命周期中被调用。
在getRepositoryInformation
方法中找到了要代理的对象是SimpleJpaRespository
创建代理对象工厂进行代理。
创建代理
代理对象类型SimpleRepository有什么特别的?
SimpleRepository
实现了JpaRepository<T, ID>
, JpaSpecificationExecutor<T>
两个接口
调用Jpa底层实现。