1. 程式人生 > >看Spring Data如何簡化數據操作

看Spring Data如何簡化數據操作

ann hba 單條件 初始化 整合 rabl context upper man

Spring Data 概述

Spring Data

用於簡化數據庫訪問,支持NoSQL 和 關系數據存儲,其主要目標是使數據庫的訪問變得方便快捷。
SpringData 項目所支持 NoSQL 存儲:
MongoDB (文檔數據庫)
Neo4j(圖形數據庫)
Redis(鍵/值存儲)
Hbase(列族數據庫)
SpringData 項目所支持的關系數據存儲技術:
JDBC
JPA

Spring Data JPA

JPA Spring Data : 致力於減少數據訪問層 (DAO) 的開發量. 開發者唯一要做的,就只是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!

開發步驟

配置 Spring 整合 JPA

在 Spring 配置文件中配置 Spring Data,讓 Spring 為聲明的接口創建代理對象,配置了 \<jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子接口的接口創建代理對象,並將代理對象註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該對象。

聲明持久層的接口,該接口繼承 Repository,Repository 是一個標記型接口,它不包含任何方法,如必要,Spring Data 可實現 Repository 其他子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法。

在接口中聲明需要的方法,Spring Data 將根據給定的策略來為其生成實現代碼。

搭建環境

導包:

antlr-2.7.7.jar
c3p0-0.9.2.1.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
dom4j-1.6.1.jar
hamcrest-core-1.3.jar
hibernate-c3p0-4.2.4.Final.jar

hibernate-commons-annotations-4.0.2.Final.jar
hibernate-core-4.2.4.Final.jar
hibernate-entitymanager-4.2.4.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
javassist-3.15.0-GA.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.1.Final.jar
junit-4.12.jar
mchange-commons-java-0.2.3.4.jar
mysql-connector-java-5.1.7-bin.jar
slf4j-api-1.6.1.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-data-commons-1.6.2.RELEASE.jar
spring-data-jpa-1.4.2.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar

applicationContext.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: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.kernel.spring.data"/>
    <!--配置數據源-->
    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--配置JPA的EntityManagerFactory-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
        </property>
        <property name="packagesToScan" value="com.kernel.spring.data"/>
        <property name="jpaProperties">
            <props>
                <!--生成的數據表的映射策略-->
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <!--生成的數據表的列的映射策略-->
                <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>
    <!--配置事務管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!--配置支持註解的事務-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!--配置Spring Data-->
    <jpa:repositories base-package="com.kernel.spring.data" entity-manager-factory-ref="entityManagerFactory"/>
</beans>

Repository 接口

Repository 接口是 Spring Data 的一個核心接口,不提供任何方法,只要遵循接口方法定義規範,就無需寫實現類,與繼承 Repository 接口等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,並為其指定domainClass 和 idClass 屬性。

Repository 的子接口

Repository: 僅僅是一個標識,表明任何繼承它的均為倉庫接口類。

CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法 。

PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法 。

JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法 。

自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的 XxxxRepository 接口就具備了通用的數據訪問控制層的能力。

JpaSpecificationExecutor: 不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法 。

Spring Data 方法定義規範

簡單條件查詢,查詢某一個實體或者集合,查詢方法以 get、find、read 開頭,涉及條件查詢時,條件的屬性用關鍵字連接,條件屬性以首字母大寫。

例如

public interface PersonRepository extends Repository<Person, Integer> {
    Person getByLastName(String lastName);

    List<Person> getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);

    List<Person> getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);

    List<Person> getByEmailInAndAgeLessThan(List list, Integer id);

    List<Person> getByAddress_IdGreaterThan(Integer id);
}

Spring Data 支持的關鍵字

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = 1?
Betwee findByStartDateBetween … where x.startDate between 1? and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1(parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1(parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> age) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

@Query 註解

這種查詢可以聲明在 Repository 方法中,擺脫像命名查詢那樣的約束,將查詢直接在相應的接口方法中聲明,結構更為清晰,這是 Spring Data 的特有實現。

索引參數:

索引值從1開始,查詢中 ?x 的個數需要與方法形參個數相等,並且順序也要一致。

@Query("SELECT p FROM Person p WHERE p.lastName = ?1 and p.age = ?2")
Person getByLastNameAndAge(String lastName, int age);

命名參數:

可以定義好參數名,賦值值采用 @Param(參數名),而不用管順序。

@Query("SELECT p FROM Person p WHERE p.lastName = :lastName and p.id = :id")
Person getByLastNameAndId(@Param("id")int id, @Param("lastName")String lastName);

本地查詢:

可以使用 @Query 執行本地查詢,只要設置 nativeQuery 為 true。

@Query(value = "SELECT COUNT (id) FROM jpa_persons", nativeQuery = true)
long getTotalCount();

@Modifying 註解和事務

@Query 與 @Modifying 這兩個註解一起使用,可以執行 UPDATE、DELETE 操作。

註意,UPDATE、DELETE 需要使用事務,因此需要定義在 Service 層。

事務:

Spring Data 提供了默認的事務處理方式,即所有的查詢均為只讀事務。

對於自定義的方法,如需改變 Spring Data 提供的事務方式,可以在方法上添加 @Transactional 註解。

進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在 Service 層實現對多個 Repository 的調用,並在相應的方法上聲明事務。

CrudRepository 接口

CrudRepository 接口提供了最基本的對實體類的添刪改查操作
T save(T entity) 保存單個實體
Iterable<T> save(Iterable<? extends T> entities) 保存集合
T findOne(ID id) 根據id查找實體
boolean exists(ID id) 根據id判斷實體是否存在
Iterable<T> findAll() 查詢所有實體,不用或慎用
long count() 查詢實體數量
void delete(ID id) 根據Id刪除實體
void delete(T entity) 刪除一個實體
void delete(Iterable<? extends T> entities) 刪除一個實體的集合
void deleteAll() 刪除所有實體,不用或慎用

PagingAndSortingRepository 接口

該接口提供了分頁與排序功能

Iterable<T> findAll(Sort sort) 排序

Page<T> findAll(Pageable pageable) 分頁查詢(含排序功能)

@Test
public void testPagingAndSortingRespository(){
    int pageNo = 3;
    int pageSize = 5;
    Sort.Order order = new Sort.Order(Sort.Direction.DESC, "id");
    Sort.Order order1 = new Sort.Order(Sort.Direction.ASC, "email");
    Sort sort = new Sort(order,order1);
    PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
    Page<Person> page = personRepository.findAll(pageable);
    System.out.println(page.getNumberOfElements());
    System.out.println(page.getContent());
}

JpaRepository 接口

該接口提供了JPA的相關功能

List<T> findAll() 查找所有實體

List<T> findAll(Sort sort) 排序、查找所有實體

List<T> save(Iterable<? extends T> entities) 保存集合

void flush() 執行緩存與數據庫同步

T saveAndFlush(T entity) 強制執行持久化

void deleteInBatch(Iterable<T> entities) 刪除一個實體集合

JpaSpecificationExecutor 接口

不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法

Specification:封裝 JPA Criteria 查詢條件。通常使用匿名內部類的方式來創建該接口的對象

@Test
public void testJpaSpecificationExecutor(){
    int pageNo = 3;
    int pageSize = 5;
    PageRequest pageable = new PageRequest(pageNo, pageSize);
    Specification<Person> specification = new Specification<Person>() {
        /**
             * @param root 代表查詢的實體類
             * @param criteriaQuery 可以從中得到root對象,即告知 JPA Criteria 查詢哪個實體類,
             *                      還可以添加查詢條件,還可以結合EntityManager對象得到最終查詢的                                    TypedQuery
             * @param criteriaBuilder 用於創建criteria工廠
             * @return Predicate類型,代表一個查詢條件
             */
        @Override
        public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
            Predicate predicate = criteriaBuilder.ge(root.get("id"),5);
            return predicate;
        }
    };
    Page<Person> page = personRepository.findAll(specification, pageable);
    System.out.println(page.getNumberOfElements());
    System.out.println(page.getContent());
}

自定義 Repository 方法

為某一個 Repository 上添加自定義方法

創建一個 XXXDAO 接口,聲明接口方法。

創建一個 XXXRepository 接口,並繼承 XXXDAO 接口。

提供 XXXRepository 的實現類 XXXRepositoryImpl,並實現 XXXDAO 定義的接口方法。

實際上在使用 XXXRepository 的自定義方法時會調用 XXXRepositoryImpl 中的實現。

為所有的 Repository 都添加自實現的方法

創建一個 XXXRepository 接口,需要繼承 創建一個 Repository 接口。

提供 XXXRepository 的實現類 XXXRepositoryImpl,且繼承 SimpleJpaRepository,並提供方法的實現。

定義 JpaRepositoryFactoryBean 的實現類,使其生成 XXXRepository 的接口實現類的對象。

修改 \<jpa:repositories/> 節點的 factory-class 屬性指向 JpaRepositoryFactoryBean 的實現類的全類名。

註意:

全局的擴展實現類不要用 Imp 作為後綴名,
或者為全局擴展接口添加 @NoRepositoryBean註解告知 SpringData 不要把其作為 Repository。

看Spring Data如何簡化數據操作