Spring Data JPA 概述
什么是Spring Data JPA
Spring Data JPA 是 Spring 基于JPA 規(guī)范的基礎(chǔ)上封裝的?套 JPA 應(yīng)?框架,可使開發(fā)者?極簡的
代碼即可實現(xiàn)對數(shù)據(jù)庫的訪問和操作妥箕。-
Spring Data 家族
Spring Data 家族 JPA是Java Persistence api:Java持久層api規(guī)范递胧。
Spring Data JPA 是Spring提供的一個JPA操作的框架。
Hibernate是實現(xiàn)了JPA規(guī)范的ORM框架。
Spring Data JPA規(guī)范和Hibernate之間的關(guān)系
Spring Data JPA應(yīng)用
數(shù)據(jù)庫準備:
Spring Data JPA開發(fā)步驟梳理
- 創(chuàng)建工程
- 導(dǎo)入Meven依賴
- 配置Spring的配置文件
- 編寫實體類斧拍,使用JPA注解配置映射關(guān)系
- 編寫Spring Data JPA的Dao層接口
- 使用Dao層接口完成Dao層開發(fā)
Spring Data JPA開發(fā)實現(xiàn)
- 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 相關(guān)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 相關(guān)jar,end-->
<!--hibernate相關(guān)jar包,start-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.0.Final</version>
</dependency>
<!--hibernate對jpa的實現(xiàn)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相關(guān)jar包,end-->
<!--mysql 數(shù)據(jù)庫驅(qū)動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、創(chuàng)建數(shù)據(jù)庫連接池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"
>
<!--配置?些細節(jié).......-->
<!--配置數(shù)據(jù)源-->
<property name="dataSource" ref="dataSource"/>
<!--配置包掃描(pojo實體類所在的包)-->
<property name="packagesToScan"
value="com.xdf.jpa.pojo"/>
<!--指定jpa的具體實現(xiàn)片挂,也就是hibernate-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--jpa??配置,不同的jpa實現(xiàn)對于類似于beginTransaction等細節(jié)實現(xiàn)
起來是不?樣的,
所以傳?JpaDialect具體的實現(xiàn)類-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
<!--配置具體provider贞盯,hibearnte框架的執(zhí)?細節(jié)-->
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--定義hibernate框架的?些細節(jié)-->
<!--
配置數(shù)據(jù)表是否?動創(chuàng)建因為我們會建?pojo和數(shù)據(jù)表之間的映射關(guān)系
程序啟動時音念,如果數(shù)據(jù)表還沒有創(chuàng)建,是否要程序給創(chuàng)建?下
-->
<property name="generateDdl" value="false"/>
<!--
指定數(shù)據(jù)庫的類型
hibernate本身是個dao層框架邻悬,可以?持多種數(shù)據(jù)庫類型
的症昏,這?就指定本次使?的什么數(shù)據(jù)庫
-->
<property name="database" value="MYSQL"/>
<!--
配置數(shù)據(jù)庫的??
hiberante可以幫助我們拼裝sql語句,但是不同的數(shù)據(jù)庫sql
語法是不同的父丰,所以需要我們注?具體的數(shù)據(jù)庫??
-->
<property name="databasePlatform"
value="org.hibernate.dialect.MySQLDialect"/>
<!--是否顯示sql
操作數(shù)據(jù)庫時肝谭,是否打印sql
-->
<property name="showSql" value="true"/>
</bean>
</property>
</bean>
<!--3、引?上?創(chuàng)建的entityManagerFactory
<jpa:repositories> 配置jpa的dao層細節(jié)
base-package:指定dao層接?所在包
-->
<jpa:repositories base-package="com.xdf.jpa.dao" entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
<!--4蛾扇、事務(wù)管理器配置
jdbcTemplate/mybatis 使?的是DataSourceTransactionManager
jpa規(guī)范:JpaTransactionManager
-->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--5攘烛、聲明式事務(wù)配置-->
<!--
<tx:annotation-driven/>
-->
<!--6、配置spring包掃描-->
<context:component-scan base-package="com.xdf.jpa"/>
</beans>
- 編寫實體類User镀首,使用注解配置映射關(guān)系
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 依賴數(shù)據(jù)庫的主鍵自增 mysql
* GenerationType.SEQUENCE 依賴序列來產(chǎn)生主鍵 Oracle
*
* @Column 實體屬性和數(shù)據(jù)庫字段映射
*/
@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接口坟漱,可以不編寫代碼直接實現(xiàn)基本的curd操作,兩個泛型分別代表:實體類類型更哄,主鍵類型芋齿。
* 繼承JpaSpecificationExecutor接口,可以不編寫代碼實現(xiàn)復(fù)雜查詢成翩,泛型為實體類類型觅捆。
* @modifiedBy
*/
public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
/**
* 自定義查詢,使用JPQL語法實現(xiàn)查詢操作
* @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語法實現(xiàn)自定義查詢
* 需要設(shè)置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);
/**
* 按方法命名規(guī)則來定義查詢
* 查詢條件寫在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;
/**
* 基礎(chǔ)的查詢操作
*/
@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()));
}
/**
* 動態(tài)條件封裝
* 匿名內(nèi)部類
*
* toPredicate:動態(tài)條件組裝
*
*/
@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執(zhí)行過程源碼分析
代理對象怎么產(chǎn)生释移,過程怎么樣?
JPA的Dao層接口也是通過動態(tài)代理來實現(xiàn)的鱼喉。
在Spring容器中秀鞭,bean都是在refresh方法中初始化的趋观。
在refresh
方法的finishBeanFactoryInitialization
的preInstantiateSingletons
方法中打斷點觀察:
發(fā)現(xiàn)userDao在Ioc容器中注冊為一個FactoryBean:JpaRepositoryFactoryBean
FactoryBean的getObject方法可以獲取真正的bean 。
這個FactoryBean是什么時候放入到容器中去的锋边?
因為這個beanDefinition
是在getMergedLocalBeanDefinition
中獲取到的皱坛,跟進去看
先從mergedBeanDefinitions
集合中去查詢,沒有找到豆巨。那么就會進入getMergedBeanDefinition
在getMergedBeanDefinition
中發(fā)現(xiàn)一個向集合mergedBeanDefinitions
中put值的代碼:
這個地方應(yīng)該是個關(guān)鍵代碼剩辟。
斷點打到這里,發(fā)現(xiàn)
這里put進入的
BeanDefinition
正好就是JpaRepositoryFactoryBean
,而傳入的bd也是JpaRepositoryFactoryBean
往扔,mbd
是跟進bd
創(chuàng)建出來的贩猎。那么我找找bd
是什么時候傳入的。回到上面萍膛,發(fā)現(xiàn)是在this.getBeanDefinition(beanName)
獲取到的吭服。
在beanDefinitionMap
中取到的userDao
的bean定義。
我們再跟蹤beanDefinitionMap
的put方法蝗罗,看是在什么位置設(shè)置到集合中去的
我們發(fā)現(xiàn)在registerBeanDefinition
中對beanDefinitionMap
進行了put操作艇棕。因此在registerBeanDefinition
打斷點觀察
發(fā)現(xiàn)在這里進行
BeanDefinition
注冊的時候,傳入的就是一個FactoryBean
類串塑。根據(jù)調(diào)用棧找到
RepositoryConfigurationDelegate
中的registerRepositoriesIn
構(gòu)建BeanDefinition
依次跟進去返現(xiàn)沼琉,被直接指定為一個JpaRepositoryFactoryBean
類。
在掃描Dao并注冊到BeanDefinition中的時候桩匪,固定注冊JpaRepositoryFactoryBean到容器中打瘪。
getObject方法到底返回什么bean?
JpaRepositoryFactoryBean的getObject是在父類中定義的
getObject是從一個集合repository中獲取的傻昙。
我們看這個集合是在哪了被賦值的闺骚?
在afterPropertiesSet
方法中,發(fā)現(xiàn)集合被賦值
afterPropertiesSet
是InitializingBean的鉤子方法妆档,在Spring的生命周期中被調(diào)用葛碧。
在getRepositoryInformation
方法中找到了要代理的對象是SimpleJpaRespository
創(chuàng)建代理對象工廠進行代理。
創(chuàng)建代理
代理對象類型SimpleRepository有什么特別的过吻?
SimpleRepository
實現(xiàn)了JpaRepository<T, ID>
, JpaSpecificationExecutor<T>
兩個接口
調(diào)用Jpa底層實現(xiàn)。