這一講主要介紹Spring Data JPA的封裝。和設(shè)計(jì)相關(guān)的東西都是仁者見仁裤唠,智者見智的事情昔园,如果你有更好的封裝方案可以和我交流炉菲,互相學(xué)習(xí)。這一講會講如下一些內(nèi)容
- 擴(kuò)展Spring Data JPA實(shí)現(xiàn)自己的一些特殊方法
- 創(chuàng)建一個(gè)自己的BaseRepository
- 封裝Specification來快速完成一些簡單的查詢操作
- 封裝分頁和排序操作补君。
在一些特殊時(shí)候引几,我們會設(shè)計(jì)到對Spring Data JPA中的方法進(jìn)行重新實(shí)現(xiàn),這將會面臨一個(gè)問題赚哗,如果我們新創(chuàng)建一個(gè)實(shí)現(xiàn)類她紫。如果這個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)了JpaRepository接口,這樣我們不得不實(shí)現(xiàn)該接口中的所有方法屿储,如果不實(shí)現(xiàn)該接口贿讹,那意味著我們就無法使用Spring Data JPA中給我們提供的那些好用的方法。所以在擴(kuò)展的時(shí)候我們需要按照如下方法進(jìn)行够掠。
這些需要注意的是民褂,接口和實(shí)現(xiàn)類的名稱必須遵循spring data jpa的命名規(guī)范,如果要為接口StudentBaseRepository
寫自定義的接口疯潭,首先需要創(chuàng)建一個(gè)接口名稱為StudentBaseRepositoryCustom
赊堪,這表示是自定義接口,實(shí)現(xiàn)類的名稱必須是StudentBaseRepositoryImpl
竖哩,此時(shí)當(dāng)StudentBaseRepository
實(shí)現(xiàn)StudentBaseRepositoryCustom
之后就可以使用我們自己實(shí)現(xiàn)的方法了哭廉,同理StudentBaseRepository
也可以繼承JpaRepository
來獲取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來進(jìn)行分組查詢辽幌,第二個(gè)是基于Hibernate的HQL進(jìn)行查詢,最后一個(gè)是用Specification中的CriteriaQuery來進(jìn)行處理椿访,首先要解決的問題是StudentBaseRepositoryCustom沒有實(shí)現(xiàn)Repository乌企,該如何來執(zhí)行SQL語句呢,我們可以給實(shí)現(xiàn)類注入另一個(gè)EntityManger成玫,通過EntityManager來執(zhí)行SQL語句加酵。以下是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è)查詢語句猪腕,執(zhí)行完成之后會返回一組Object[]的投影,第三個(gè)方法稍微有些復(fù)雜荣病,這是CriteriaQuery的標(biāo)準(zhǔn)寫法码撰。
到這里我們解決了擴(kuò)展類的問題,但仍然有些疑問个盆,如果每個(gè)類都有自己獨(dú)立的方法脖岛,那么是不是每一個(gè)類都得按照上面的方面來寫接口和實(shí)現(xiàn)類,以上做法雖然可以很好的解決自定義類的擴(kuò)展問題颊亮,但是仍然稍顯麻煩柴梆,我們可以定義一個(gè)基類來覆蓋一些比較通用的方法,如通用的SQL查詢等终惑。下面我們就來創(chuàng)建這個(gè)BaseRepository绍在,整個(gè)創(chuàng)建的過程有些復(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è)表示該接口不會創(chuàng)建這個(gè)接口的實(shí)例(我們原來定義的StudentPageRepository這些适揉,Spring Data JPA的基礎(chǔ)組件都會自動為我們創(chuàng)建一個(gè)實(shí)例對象,加上這個(gè)annotation煤惩,spring data jpa的基礎(chǔ)組件就不會再為我們創(chuàng)建它的實(shí)例)嫉嘀。之后我們編寫實(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;
//父類沒有不帶參數(shù)的構(gòu)造方法,這里手動構(gòu)造父類
public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
//通過EntityManager來完成查詢
@Override
public List<Object[]> listBySQL(String sql) {
return entityManager.createNativeQuery(sql).getResultList();
}
}
這個(gè)實(shí)現(xiàn)類比較的簡單魄揉,首先我們需要繼承SimpleJpaRepository
剪侮,SimpleJpaRepository
幫助我們實(shí)現(xiàn)了JpaRepository中的方法。然后實(shí)現(xiàn)BaseRepository
接口洛退。listBySQL
方法非常的簡單瓣俯,上面已經(jīng)詳細(xì)介紹過了红淡,具體的作用就是執(zhí)行一條sql返回一組投影的列表。
下一步我們需要創(chuàng)建一個(gè)自定義的工廠降铸,在這個(gè)工廠中注冊我們自己定義的BaseRepositoryImpl的實(shí)現(xiàn)。這個(gè)工廠的寫法具體參照Spring Data的JpaRepositoryFactoryBean
和JpaRepositoryFactory
摇零。這個(gè)類上面一堆的泛型推掸,我們不用考慮,只要按照相同的方式來寫即可驻仅。
創(chuàng)建JpaRepositoryFactoryBean需要調(diào)用如下方法谅畅,通過這個(gè)方法來返回一個(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;
}
通過這兩個(gè)方法來確定具體的實(shí)現(xiàn)類,也就是Spring Data Jpa具體實(shí)例化一個(gè)接口的時(shí)候會去創(chuàng)建的實(shí)現(xiàn)類粘优。通過代碼我們可以發(fā)現(xiàn)仇味,Spring Data JPA都是調(diào)用SimpleJpaRepository來創(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)部類雹顺,該類不用在外部訪問
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之后一切都變得簡單了,只要在入口類中加入@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é)束了贩挣,我們寫一個(gè)接口實(shí)現(xiàn)BaseRepository
即可
/**
* Created by konghao on 2016/12/7.
*/
public interface StudentExtendsRepository extends BaseRepository<Student,Integer> {
/**
* 原來JPARepository的方法依然可以使用*/
List<Student> findByNameAndAddress(String name, String address);
}
測試類中的代碼如下
@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é)我們要解決的問題是基于Specification的封裝問題王财。
本文的源代碼在這里:源代碼