SpringData概述
- Spring Data : Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關係數據存儲。其主要目標是使數據庫的訪問變得方便快捷。
- SpringData 項目所支持 NoSQL 存儲:
- MongoDB (文檔數據庫)
- Neo4j(圖形數據庫)
- Redis(鍵/值存儲)
- Hbase(列族數據庫)
- SpringData 項目所支持的關係數據存儲技術:
- JDBC
- JPA
JPA Spring Data : 致力於減少數據訪問層 (DAO) 的開發量. ==開發者唯一要做的,就只是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!==
框架怎麼可能代替開發者實現業務邏輯呢?
Spring Data JPA 做的便是==規範方法==的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。
比如:當有一個 UserDao.findUserById() 這樣一個方法聲明,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 對象
SpringData JPA進行持久化開發的4個步驟
- Spring整合JPA
Spring配置SpringData
- 讓Spring爲聲明的接口創建代理對象
聲明持久層的接口(繼承Repository)
- Repository 是一個標記型接口,它不包含任何方法,如必要,Spring Data 可實現 Repository 其他子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法。
在接口中聲明方法
- Spring Data 將根據給定的策略(具體策略稍後講解)來爲其生成實現代碼。
框架搭建
導入jar包
配置配置文件
Spring整合JPA(完整版)
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
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-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置自動掃描的包 -->
<context:component-scan base-package="com.xiaoming.springdata"></context:component-scan>
<!-- 1. 配置數據源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<!-- 配置其他屬性 -->
</bean>
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--配置數據源-->
<property name="dataSource" ref="dataSource"></property>
<!-- 配置 JPA 提供商的適配器. 可以通過內部 bean 的方式來配置 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置實體類所在的包 -->
<property name="packagesToScan" value="com.xiaoming.springdata"></property>
<!-- 配置 JPA 的基本屬性. 例如 JPA 實現產品的屬性 -->
<property name="jpaProperties">
<props>
<!-- 二級緩存相關 -->
<!--
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
-->
<!-- 生成的數據表的列的映射策略 -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!-- hibernate 基本屬性 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 3.配置JPA使用的事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 4. 配置支持註解的事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--上面的是Spring整合JPA-->
<!-- 5. 配置 SpringData -->
<!-- 加入 jpa 的命名空間 -->
<!--
base-package: 掃描 Repository Bean 所在的 package
爲掃描的包中繼承Repository或其子接口的接口創建代理對象,並將代理對象註冊爲SpringBean
-->
<jpa:repositories base-package="com.xiaoming.springdata" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
</beans>
在Spring配置文件中配置SpringData
<!-- 5. 配置 SpringData -->
<!-- 加入 jpa 的命名空間 -->
<!--
base-package: 掃描 Repository Bean 所在的 package
爲掃描的包中繼承Repository或其子接口的接口創建代理對象,並將代理對象註冊爲SpringBean
-->
<jpa:repositories base-package="com.xiaoming.springdata" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
SpringData的約束
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
Person類
@Table(name = "person")
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private Integer age;
.......
}
PersonRepository類
public interface PersonRepository extends Repository<Person,Long> {
//根據用戶名獲取person對象
Person getByUsername(String username);
}
代碼測試(每完成一步測試一下)
package com.xiaoming.springdata.test;
import com.xiaoming.springdata.domain.Person;
import com.xiaoming.springdata.repsotory.PersonRepsotory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
/**
* 測試SpringData項目是否搭建成功
*/
public class InitTest {
private ApplicationContext applicationContext = null;
{
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void testHelloWorldSpringData() throws Exception{
PersonRepsotory personRepsotory = applicationContext.getBean(PersonRepsotory.class);
System.out.println(personRepsotory.getClass().getName());
Person person = personRepsotory.getByUsername("張三");
System.out.println(person);
}
@Test
public void testJpa(){
}
/**
* 測試數據庫連接池是否整合成功
*/
@Test
public void testDataSource() throws Exception{
DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println(dataSource.getConnection());
}
}
Repository接口詳解
Repository接口的概述
- Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者需要在自己定義的接口中聲明需要的方法
public interface Repository<T, ID extends Serializable> {}
Spring Data可以讓我們只定義接口,只要遵循 Spring Data的規範,就無需寫實現類。
與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併爲其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的
//@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonRepository extends Repository<Person,Long> {
......
}
Repository的子接口
基礎的 Repository 提供了最基本的數據訪問功能,其幾個子接口則擴展了一些功能。它們的繼承關係如下:
- Repository: 僅僅是一個標識,表明任何繼承它的均爲倉庫接口類
- CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法
- PagingAndSortingRepository: 繼承 - CrudRepository,實現了一組分頁排序相關的方法
- JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法
- 自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的 XxxxRepository 接口就具備了通用的數據訪問控制層的能力。
- JpaSpecificationExecutor: 不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法
SpringData方法定義規範
簡單條件查詢(查詢某一個實體類或者集合)
- 簡單條件查詢: 查詢某一個實體類或者集合
- ==按照 Spring Data 的規範,查詢方法以 find | read | get 開頭,涉及條件查詢時,條件的屬性用條件關鍵字連接,要注意的是:條件屬性以首字母大寫==。
- 例如:定義一個 Entity 實體類
class User{
private String firstName;
private String lastName;
......
}
使用And條件連接時,應這樣寫:==findByLastNameAndFirstName(String lastName,String firstName)==;
==條件的屬性名稱與個數要與參數的位置與個數一一對應==
SpringData支持的關鍵字
==直接在接口中定義查詢方法,如果是符合規範的,可以不用寫實現==
測試代碼
PersonRepository
public interface PersonRepository extends Repository<Person,Long> {
//根據用戶名獲取person對象
Person getByUsername(String username);
//WHERE username LIKE ?% AND id < ?
//這個方法like後面沒有 自動添加 % 傳的參數中需要手動添加
List<Person> getByUsernameLikeAndAgeGreaterThan(String username,Integer age);
//是否包含該字符串(jpql語句不會寫emmm)
List<Person> getByUsernameContaining(String username);
//WHERE username IN (?, ?, ?) OR age < ?
List<Person> getByUsernameInOrAgeLessThanEqual(List<String> name,Integer age);
}
RepositoryTest(測試一部分)
public class SpringDataRepositoryTest {
private ApplicationContext applicationContext = null;
private PersonRepository personRepsotory = null;
{
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
personRepsotory= applicationContext.getBean(PersonRepository.class);
}
@Test
public void testKeyWords(){
//Like關鍵字後面只添加 ? 沒有添加 % 手動添加
List<Person> personList = personRepsotory.getByUsernameLikeAndAgeGreaterThan("%a%", 45);
System.out.println(personList);
}
@Test
public void testKeyWords2(){
List<Person> personList = personRepsotory.getByUsernameContaining("a");
System.out.println(personList.size());
System.out.println(personList);
}
@Test
public void testKeyWords3(){
List<Person> personList = personRepsotory.getByUsernameInOrAgeLessThanEqual(Arrays.asList("aa","bb","cc"),47);
System.out.println(personList.size());
System.out.println(personList);
}
}
簡單條件查詢的弊端
- 需要滿足規範方法名太長
- 靈活性較差
簡單條件查詢進行級聯查詢時出現的問題
- 簡單條件查詢是是支持級聯查詢的,默認使用左外連接
- 但是如果要查詢的級聯的屬性的名稱跟本類中的屬性名稱重複,那麼優先使用本類的屬性
- 如果想要使用級聯屬性那麼屬性之間應該用_進行連接
List<Person> getByAddress_IdGreaterThan(Integer id);
使用Query註解
這種查詢可以聲明在 Repository 方法中,==擺脫像命名查詢那樣的約束,將查詢直接在相應的接口方法中聲明==,結構更爲清晰,這是 Spring data 的特有實現
//查詢 id 值最大的那個 Person
//使用 @Query 註解可以自定義 JPQL 語句以實現更靈活的查詢
@Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
Person getMaxIdPerson();
動態參數賦值問題
索引參數
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
//SpringData 允許在佔位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationLikeParam(String lastName, String email);
命名參數(推薦)
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
測試代碼
- PersonRepository
//查詢 id 值最大的那個 Person
//使用 @Query 註解可以自定義 JPQL 語句以實現更靈活的查詢
@Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
Person getMaxIdPerson();
//爲 @Query 註解傳遞參數的方式1: 使用佔位符.
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
//爲 @Query 註解傳遞參數的方式1: 命名參數的方式.
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
//SpringData 允許在佔位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationLikeParam(String lastName, String email);
//SpringData 允許在佔位符上添加 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")
List<Person> testQueryAnnotationLikeParam2(@Param("email") String email, @Param("lastName") String lastName);
//設置 nativeQuery=true 即可以使用原生的 SQL 查詢
@Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)
long getTotalCount();
- SpringDataRepositoryTest
@Test
public void testQueryAnnotationParams1(){
List<Person> persons = personRepsotory.testQueryAnnotationParams1("AA", "[email protected]");
System.out.println(persons);
}
@Test
public void testQueryAnnotationParams2(){
List<Person> persons = personRepsotory.testQueryAnnotationParams2("[email protected]", "AA");
System.out.println(persons);
}
@Test
public void testQueryAnnotationLikeParam(){
// List<Person> persons = personRepsotory.testQueryAnnotationLikeParam("%A%", "%bb%");
// System.out.println(persons.size());
// List<Person> persons = personRepsotory.testQueryAnnotationLikeParam("A", "bb");
// System.out.println(persons.size());
List<Person> persons = personRepsotory.testQueryAnnotationLikeParam2("bb", "A");
System.out.println(persons.size());
}
@Test
public void testNativeQuery(){
long count = personRepsotory.getTotalCount();
System.out.println(count);
}
@Query 與 @Modifying 執行更新操作
因爲Repository接口是頂層的接口是一個空的接口,作爲標記,我們前面講到的全是查詢的方法,因爲hql不支持insert所以我們只能用hql進行更新操作
想要執行更新操作我們有兩種方法,第一種是繼承Repository的子接口,裏面聲明瞭增刪改的方法,還有一種就是在Repository接口的基礎上使用@Query跟@Modifying註解來執行操作
//可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
//在 @Query 註解中編寫 JPQL 語句, 但必須使用 @Modifying 進行修飾. 以通知 SpringData, 這是一個 UPDATE 或 DELETE 操作
//UPDATE 或 DELETE 操作需要使用事務, 此時需要定義 Service 層. 在 Service 層的方法上添加事務操作.
//默認情況下, SpringData 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作!
@Modifying
@Query("UPDATE Person p SET p.email = :email WHERE id = :id")
void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);
事物
Spring Data 提供了默認的事務處理方式,即所有的查詢均聲明爲只讀事務。
對於自定義的方法,如需改變 Spring Data 提供的事務默認方式,可以在方法上註解 @Transactional 聲明
進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在 Service 層實現對多個 Repository 的調用,並在相應的方法上聲明事務。
CrudRepository接口
CrudRepository 接口提供了最基本的對實體類的添刪改查操作
- T save(T entity);//保存單個實體
- Iterable save(Iterable
@Test
public void testCrudRepository(){
List<Person> persons = new ArrayList<>();
Person p1 = new Person();
p1.setUsername("王五");
p1.setPassword("123");
p1.setAge(18);
Person p2 = new Person();
p1.setUsername("趙六");
p1.setPassword("123");
p1.setAge(18);
persons.add(p1 );
persons.add(p2 );
// personService.savePersons(persons);
personRepsotory.save(persons);
PagingAndSortingRepository接口
該接口提供了分頁與排序功能
- Iterable findAll(Sort sort); //排序
- Page findAll(Pageable pageable); //分頁查詢(含排序功能)
- PagingAndSortingRepository接口中的方法
@Test
public void testPagingAndSortingRespository(){
//pageNo 從 0 開始.
int pageNo = 6 - 1;
int pageSize = 5;
//Pageable 接口通常使用的其 PageRequest 實現類. 其中封裝了需要分頁的信息
//排序相關的. Sort 封裝了排序的信息
//Order 是具體針對於某一個屬性進行升序還是降序.
Order order1 = new Order(Direction.DESC, "id");
Order order2 = new Order(Direction.ASC, "email");
Sort sort = new Sort(order1, order2);
PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
Page<Person> page = personRepsotory.findAll(pageable);
System.out.println("總記錄數: " + page.getTotalElements());
System.out.println("當前第幾頁: " + (page.getNumber() + 1));
System.out.println("總頁數: " + page.getTotalPages());
System.out.println("當前頁面的 List: " + page.getContent());
System.out.println("當前頁面的記錄數: " + page.getNumberOfElements());
}
- Repository中簡單查詢
//根據用戶名獲取person對象
List<Person> getByUsernameContaining(String username, Pageable pageable);
@Test
public void testPagingAndSortingRepository(){
//SpringData實現分頁要傳入Pageable接口類型的參數-->實現類PageRequest
//PageRequest 需要參數 pageNumber,pageSize Sort(排序的看需求)
int pageNumber = 0; //當前頁 第3頁 從0開始
int pageSize = 9; //每頁記錄數5
//不排序的
// PageRequest pageRequest = new PageRequest(pageNumber,pageSize);
//排序
Sort.Order order = new Sort.Order(Sort.Direction.DESC,"age");
Sort sort = new Sort(order);
PageRequest pageRequest = new PageRequest(pageNumber,pageSize,sort);
List<Person> persons = personRepsotory.getByUsernameContaining("a", pageRequest);
System.out.println(persons.size());
System.out.println(persons);
}
JpaRepository接口
該接口提供了JPA的相關功能
- List findAll(); //查找所有實體
- List findAll(Sort sort); //排序、查找所有實體
- List save(Iterable
@Test
public void testJpaRepository(){
Person person = new Person();
person.setBirth(new Date());
person.setEmail("[email protected]");
person.setLastName("xyz");
person.setId(28);
Person person2 = personRepsotory.saveAndFlush(person);
System.out.println(person == person2);
}
JpaSpecificationExecutor接口
不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法
Specification:封裝 JPA Criteria 查詢條件。通常使用匿名內部類的方式來創建該接口的對象
前面講到的Repository體系中分頁查詢,添加條件不方便,這個外來的接口極大地方便了我們帶條件的分頁查詢
我們可以寫一個通用的帶條件的分頁查詢
/**
* 目標: 實現帶查詢條件的分頁. age <45 並按照年齡排序 的條件
* <p>
* 調用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
* Specification: 封裝了 JPA Criteria 查詢的查詢條件
* Pageable: 封裝了請求分頁的信息: 例如 pageNo, pageSize, Sort
*/
@Test
public void testJpaSpecificationExecutor() {
//findAll(Specification<T> spec, Pageable pageable);
//1.先解決分頁排序問題
Sort.Order order = new Sort.Order(Sort.Direction.DESC, "age");
Sort sort = new Sort(order);
PageRequest pageRequest = new PageRequest(0, 20, sort);
//2.Specification<T>
//通常使用 Specification 的匿名內部類創建對象
Specification<Person> specification = new Specification<Person>() {
/**
*
* @param root(掌握):代表查詢的實體類(這裏指的是Person)
* @param criteriaQuery(瞭解):可以從中能得到實體類;可以添加查詢條件;
* 還可以結合 EntityManager 對象得到最終查詢的 TypedQuery 對象.
* @param criteriaBuilder(掌握):創建Criteria相關對象的工廠,可以從中獲得目標對象Predicate
* @return Predicate(掌握): 代表一個查詢條件_
*/
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//先通過root導航,導航到相關屬性(條件)
Path<Integer> age = root.get("age");
//將導航到的屬性添加到條件中
Predicate predicate = criteriaBuilder.lt(age, 45);
return predicate;
}
};
Page<Person> page = personRepsotory.findAll(specification, pageRequest);
System.out.println("總記錄數: " + page.getTotalElements());
System.out.println("當前第幾頁: " + (page.getNumber() + 1));
System.out.println("總頁數: " + page.getTotalPages());
System.out.println("當前頁面的 List: " + page.getContent());
System.out.println("當前頁面的記錄數: " + page.getNumberOfElements());
}
root導航圖
自定義 Repository 方法
我們通過繼承PagingAndSortingRepository接口和繼承JpaSpecificationExecutor能夠實現大部分的增刪改查以及分頁查詢(帶條件)
當不滿足我們的時候我們還可以通過@Modifying跟@Query註解來手寫JPQL語句(insert不行)
當還不滿足我們的時候我們可以自定義Repository方法
- ==爲某一個 Repository 上添加自定義方法(重點)==
- 爲所有的 Repository 都添加自實現的方法
步驟(某個Repository):
- 定義一個接口: 聲明要添加的, 並自實現的方法
- 提供該接口的實現類: 類名需在要聲明的 Repository 後添加 Impl, 並實現方法
- 聲明 Repository 接口, 並繼承 1) 聲明的接口
- 使用.
- 注意: 默認情況下, Spring Data 會在 base-package 中查找 “接口名Impl” 作爲實現類. 也可以通過 repository-impl-postfix 聲明後綴.
爲某一個 Repository 上添加自定義方法
自己寫一個接口PersonDao
public interface PersonDao {
void test();
}
自己寫PersonDao的實現PersonRepsotoryImpl(固定的)
public class PersonRepsotoryImpl implements PersonDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public void test() {
Person person = entityManager.find(Person.class, 11);
System.out.println("-->" + person);
}
}
PersonReposity繼承我們寫的PersonDao
public interface PersonRepsotory extends
JpaRepository<Person, Integer>,
JpaSpecificationExecutor<Person>, PersonDao{
......
}
測試
//調用的是我們自己實現的test方法
@Test
public void testCustomRepositoryMethod(){
personRepsotory.test();
}