這一講主要介紹Spring Data JPA的封裝。和設(shè)計(jì)相關(guān)的東西都是仁者見(jiàn)仁,智者見(jiàn)智的事情,如果你有更好的封裝方案可以和我交流,互相學(xué)習(xí)。這一講會(huì)講如下一些內(nèi)容
- 擴(kuò)展Spring Data JPA實(shí)現(xiàn)自己的一些特殊方法
- 創(chuàng)建一個(gè)自己的BaseRepository
- 封裝Specification來(lái)快速完成一些簡(jiǎn)單的查詢操作
- 封裝分頁(yè)和排序操作。
在一些特殊時(shí)候,我們會(huì)設(shè)計(jì)到對(duì)Spring Data JPA中的方法進(jìn)行重新實(shí)現(xiàn),這將會(huì)面臨一個(gè)問(wèn)題,如果我們新創(chuàng)建一個(gè)實(shí)現(xiàn)類。如果這個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)了JpaRepository接口,這樣我們不得不實(shí)現(xiàn)該接口中的所有方法,如果不實(shí)現(xiàn)該接口,那意味著我們就無(wú)法使用Spring Data JPA中給我們提供的那些好用的方法。所以在擴(kuò)展的時(shí)候我們需要按照如下方法進(jìn)行。
這些需要注意的是,接口和實(shí)現(xiàn)類的名稱必須遵循spring data jpa的命名規(guī)范,如果要為接口StudentBaseRepository
寫(xiě)自定義的接口,首先需要?jiǎng)?chuàng)建一個(gè)接口名稱為StudentBaseRepositoryCustom
,這表示是自定義接口,實(shí)現(xiàn)類的名稱必須是StudentBaseRepositoryImpl
,此時(shí)當(dāng)StudentBaseRepository
實(shí)現(xiàn)StudentBaseRepositoryCustom
之后就可以使用我們自己實(shí)現(xiàn)的方法了,同理StudentBaseRepository
也可以繼承JpaRepository
來(lái)獲取Spring Data Jpa 給我們的方法。
StudentBaseRepositoryCustom代碼如下
public interface StudentBaseRepositoryCustom {
//基于原生態(tài)的sql進(jìn)行查詢
List<Object[]> groupByStudentAsSql();
//基于Hibernate的HQL進(jìn)行查詢
List<Object[]> groupByStudentAsHql();
//基于Specification的方式進(jìn)行查詢,使用的是CriteriaQuery進(jìn)行查詢
List<Object[]> groupByStudentAsSpecification();
}
以上代碼中定義了三個(gè)方法,第一個(gè)是基于原始的SQL來(lái)進(jìn)行分組查詢,第二個(gè)是基于Hibernate的HQL進(jìn)行查詢,最后一個(gè)是用Specification中的CriteriaQuery來(lái)進(jìn)行處理,首先要解決的問(wèn)題是StudentBaseRepositoryCustom沒(méi)有實(shí)現(xiàn)Repository,該如何來(lái)執(zhí)行SQL語(yǔ)句呢,我們可以給實(shí)現(xiàn)類注入另一個(gè)EntityManger,通過(guò)EntityManager來(lái)執(zhí)行SQL語(yǔ)句。以下是StudentBaseRepositoryImpl的實(shí)現(xiàn)代碼
public class StudentBaseRepositoryImpl implements StudentBaseRepositoryCustom {
@Autowired
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Object[]> groupByStudentAsSql() {
List<Object[]> list = entityManager
.createNativeQuery("select address,count(*) from t_student group by address")
.getResultList();
return list;
}
@Override
public List<Object[]> groupByStudentAsHql() {
List<Object[]> list = entityManager
.createQuery("select address,count(*) from Student group by address")
.getResultList();
return list;
}
@Override
public List<Object[]> groupByStudentAsSpecification() {
//根據(jù)地址分組查詢,并且學(xué)生數(shù)量大于3的所有地址
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Student> root = query.from(Student.class);
query.multiselect(root.get("address"),builder.count(root.get("id")))
.groupBy(root.get("address")).having(builder.gt(builder.count(root.get("id")),3));
return entityManager.createQuery(query).getResultList();
}
}
前面兩個(gè)方法的實(shí)現(xiàn)都非常容易理解,就是創(chuàng)建一個(gè)查詢語(yǔ)句,執(zhí)行完成之后會(huì)返回一組Object[]的投影,第三個(gè)方法稍微有些復(fù)雜,這是CriteriaQuery的標(biāo)準(zhǔn)寫(xiě)法。
到這里我們解決了擴(kuò)展類的問(wèn)題,但仍然有些疑問(wèn),如果每個(gè)類都有自己獨(dú)立的方法,那么是不是每一個(gè)類都得按照上面的方面來(lái)寫(xiě)接口和實(shí)現(xiàn)類,以上做法雖然可以很好的解決自定義類的擴(kuò)展問(wèn)題,但是仍然稍顯麻煩,我們可以定義一個(gè)基類來(lái)覆蓋一些比較通用的方法,如通用的SQL查詢等。下面我們就來(lái)創(chuàng)建這個(gè)BaseRepository,整個(gè)創(chuàng)建的過(guò)程有些復(fù)雜,可以參照項(xiàng)目的源代碼(源代碼在文章的最后有鏈接)。
創(chuàng)建的第一步定義一個(gè)BaseRepository
的接口
package org.konghao.repo.base;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.io.Serializable;
import java.util.List;
/**
* Created by konghao on 2016/12/7.
*/
@NoRepositoryBean
public interface BaseRepository<T,ID extends Serializable> extends JpaRepository<T,ID> {
List<Object[]> listBySQL(String sql);
}
該接口實(shí)現(xiàn)了JpaRepository,這樣就保證擁有了Spring Data JPA中那些比較好用的方法,然后可以自定義自己需要的方法,代碼中定義了一個(gè)listBySQL的方法。需要注意的是@NoRepositoryBean
,這個(gè)表示該接口不會(huì)創(chuàng)建這個(gè)接口的實(shí)例(我們?cè)瓉?lái)定義的StudentPageRepository這些,Spring Data JPA的基礎(chǔ)組件都會(huì)自動(dòng)為我們創(chuàng)建一個(gè)實(shí)例對(duì)象,加上這個(gè)annotation,spring data jpa的基礎(chǔ)組件就不會(huì)再為我們創(chuàng)建它的實(shí)例)。之后我們編寫(xiě)實(shí)現(xiàn)類BaseRepositoryImpl
package org.konghao.repo.base;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.List;
/**
* Created by konghao on 2016/12/7.
*/
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T,ID>
implements BaseRepository<T,ID> {
private final EntityManager entityManager;
//父類沒(méi)有不帶參數(shù)的構(gòu)造方法,這里手動(dòng)構(gòu)造父類
public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
//通過(guò)EntityManager來(lái)完成查詢
@Override
public List<Object[]> listBySQL(String sql) {
return entityManager.createNativeQuery(sql).getResultList();
}
}
這個(gè)實(shí)現(xiàn)類比較的簡(jiǎn)單,首先我們需要繼承SimpleJpaRepository
,SimpleJpaRepository
幫助我們實(shí)現(xiàn)了JpaRepository中的方法。然后實(shí)現(xiàn)BaseRepository
接口。listBySQL
方法非常的簡(jiǎn)單,上面已經(jīng)詳細(xì)介紹過(guò)了,具體的作用就是執(zhí)行一條sql返回一組投影的列表。
下一步我們需要?jiǎng)?chuàng)建一個(gè)自定義的工廠,在這個(gè)工廠中注冊(cè)我們自己定義的BaseRepositoryImpl的實(shí)現(xiàn)。這個(gè)工廠的寫(xiě)法具體參照Spring Data的JpaRepositoryFactoryBean
和JpaRepositoryFactory
。這個(gè)類上面一堆的泛型,我們不用考慮,只要按照相同的方式來(lái)寫(xiě)即可。
創(chuàng)建JpaRepositoryFactoryBean需要調(diào)用如下方法,通過(guò)這個(gè)方法來(lái)返回一個(gè)工廠,這里返回的是JpaRepositoryFactory。
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new JpaRepositoryFactory(entityManager);
}
在JpaRepositoryFactory中,有兩個(gè)方法比較關(guān)鍵
protected Object getTargetRepository(RepositoryInformation information) {
SimpleJpaRepository repository = this.getTargetRepository(information, this.entityManager);
repository.setRepositoryMethodMetadata(this.crudMethodMetadataPostProcessor.getCrudMethodMetadata());
return repository;
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return this.isQueryDslExecutor(metadata.getRepositoryInterface())?QueryDslJpaRepository.class:SimpleJpaRepository.class;
}
通過(guò)這兩個(gè)方法來(lái)確定具體的實(shí)現(xiàn)類,也就是Spring Data Jpa具體實(shí)例化一個(gè)接口的時(shí)候會(huì)去創(chuàng)建的實(shí)現(xiàn)類。通過(guò)代碼我們可以發(fā)現(xiàn),Spring Data JPA都是調(diào)用SimpleJpaRepository來(lái)創(chuàng)建實(shí)例。以下是我們自己的工廠實(shí)現(xiàn)的代碼
package org.konghao.repo.base;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.persistence.EntityManager;
import java.io.Serializable;
/**
* Created by konghao on 2016/12/7.
*/
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
return new BaseRepositoryFactory(em);
}
//創(chuàng)建一個(gè)內(nèi)部類,該類不用在外部訪問(wèn)
private static class BaseRepositoryFactory<T, I extends Serializable>
extends JpaRepositoryFactory {
private final EntityManager em;
public BaseRepositoryFactory(EntityManager em) {
super(em);
this.em = em;
}
//設(shè)置具體的實(shí)現(xiàn)類是BaseRepositoryImpl
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return new BaseRepositoryImpl<T, I>((Class<T>) information.getDomainType(), em);
}
//設(shè)置具體的實(shí)現(xiàn)類的class
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseRepositoryImpl.class;
}
}
}
接著我們需要讓spring在加載的時(shí)候找到我們自定義的BaseRepository的工廠,當(dāng)我們使用了SpringBoot之后一切都變得簡(jiǎn)單了,只要在入口類中加入@EnableJpaRepositories
即可,代碼如下
/**
* Created by konghao on 2016/11/24.
*/
@EnableJpaRepositories(basePackages = {"org.konghao"},
repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class//指定自己的工廠類
)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
到這里我們的整個(gè)自定義工廠的流程就結(jié)束了,我們寫(xiě)一個(gè)接口實(shí)現(xiàn)BaseRepository
即可
/**
* Created by konghao on 2016/12/7.
*/
public interface StudentExtendsRepository extends BaseRepository<Student,Integer> {
/**
* 原來(lái)JPARepository的方法依然可以使用*/
List<Student> findByNameAndAddress(String name, String address);
}
測(cè)試類中的代碼如下
@Test
public void testBaseRepository() {
//直接使用BaseRepository中的方法
List<Object[]> list = studentExtendsRepository.listBySQL("select address,count(*) from t_student group by address");
Assert.assertEquals(2,list.size());
Assert.assertEquals("km",list.get(0)[0]);
//原JpaRepository的方法依然可以使用
List<Student> list2 = studentExtendsRepository.findByNameAndAddress("bar","zt");
Assert.assertEquals(1,list2.size());
}
到這里,我們這部分的內(nèi)容就基本結(jié)束了,下一章節(jié)我們要解決的問(wèn)題是基于Specification的封裝問(wèn)題。
本文的源代碼在這里:源代碼