1. 程式人生 > >spring data jpa自定義baseRepository

spring data jpa自定義baseRepository

ram clas over 找到 ict app ray 註冊 基於

在一些特殊時候,我們會設計到對Spring Data JPA中的方法進行重新實現,這將會面臨一個問題,如果我們新創建一個實現類。如果這個實現類實現了JpaRepository接口,這樣我們不得不實現該接口中的所有方法,如果不實現該接口,那意味著我們就無法使用Spring Data JPA中給我們提供的那些好用的方法。所以在擴展的時候我們需要按照如下方法進行。

技術分享

這些需要註意的是,接口和實現類的名稱必須遵循spring data jpa的命名規範,如果要為接口StudentBaseRepository寫自定義的接口,首先需要創建一個接口名稱為StudentBaseRepositoryCustom

,這表示是自定義接口,實現類的名稱必須是StudentBaseRepositoryImpl,此時當StudentBaseRepository實現StudentBaseRepositoryCustom之後就可以使用我們自己實現的方法了,同理StudentBaseRepository也可以繼承JpaRepository來獲取Spring Data Jpa 給我們的方法。

StudentBaseRepositoryCustom代碼如下

public interface StudentBaseRepositoryCustom {
    //基於原生態的sql進行查詢
    List<Object[]> groupByStudentAsSql();
    //基於Hibernate的HQL進行查詢
    List<Object[]> groupByStudentAsHql();
    //基於Specification的方式進行查詢,使用的是CriteriaQuery進行查詢
    List<Object[]> groupByStudentAsSpecification();
}

以上代碼中定義了三個方法,第一個是基於原始的SQL來進行分組查詢,第二個是基於hibernate的HQL進行查詢,最後一個是用Specification中的CriteriaQuery來進行處理,首先要解決的問題是StudentBaseRepositoryCustom沒有實現Repository,該如何來執行SQL語句呢,我們可以給實現類註入另一個EntityManger,通過EntityManager來執行SQL語句。以下是StudentBaseRepositoryImpl的實現代碼

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() {
        //根據地址分組查詢,並且學生數量大於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();
    }
}

前面兩個方法的實現都非常容易理解,就是創建一個查詢語句,執行完成之後會返回一組Object[]的投影,第三個方法稍微有些復雜,這是CriteriaQuery的標準寫法。

到這裏我們解決了擴展類的問題,但仍然有些疑問,如果每個類都有自己獨立的方法,那麽是不是每一個類都得按照上面的方面來寫接口和實現類,以上做法雖然可以很好的解決自定義類的擴展問題,但是仍然稍顯麻煩,我們可以定義一個基類來覆蓋一些比較通用的方法,如通用的SQL查詢等。下面我們就來創建這個BaseRepository,整個創建的過程有些復雜,可以參照項目的源代碼

接下來我們使用自定義baseRepository

創建的第一步定義一個BaseRepository的接口

/**
* 通用repository
* 我們使用它來簡化我們的一些repository的通用CRUD
* 不需要在每一個repository中寫CRUD,只需在需要的repository上繼承就好。
* @NoRepositoryBean,這個表示該接口不會創建這個接口的實例,像UserInfoRepository等,
* 只要是在jpaConfig裏配置的基礎名裏的接口全會被實例化。
*/
@NoRepositoryBean
public interface BaseRepository<T,ID extends Serializable> extends JpaRepository<T,ID>{
//自定義sql查詢
List<T> listBySql(String sql);
//自定義多條件動態查詢
List<T> listByOwn(Map<String,Object> map1,Map<String,Object> map2);
}
之後我們編寫實現類BaseRepositoryImpl
/**
*這個實現類比較的簡單,首先我們需要繼承SimpleJpaRepository,
* SimpleJpaRepository幫助我們實現了JpaRepository中的方法。
* 然後實現BaseRepository接口。listBySQL方法非常的簡單,具體的作用就是執行一條sql返回一組投影的列表。
* Created by BFD-593 on 2017/8/16.
*/
public class BaseRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID>
implements BaseRepository<T,ID>{
private final EntityManager entityManager;

//父類沒有不帶參數的構造方法,這裏手動構造父類
public BaseRepositoryImpl(Class<T> domainClass,EntityManager entityManager){
super(domainClass, entityManager);
this.entityManager = entityManager;
}
//通過EntityManager來完成查詢,指定sql來查詢
@Override
public List<T> listBySql(String sql) {
return entityManager.createNativeQuery(sql).getResultList();
}

@Override
public List<T> listByOwn(final Map<String,Object> map1, final Map<String,Object> map2) {
Specification<T> sf = new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> list = new ArrayList<>();
String key1 = (String) map1.keySet().toArray()[0];
String key2 = (String) map2.keySet().toArray()[0];
if (StringUtils.isNotEmpty(key1)) {
list.add(criteriaBuilder.like(root.get(key1).as(String.class),"%"+map1.get(key1)+"%"));
}
if(StringUtils.isNotEmpty(key2)){
list.add(criteriaBuilder.greaterThanOrEqualTo(root.get(key2).as(Integer.class), (Integer) map2.get(key2)));
}
Predicate[] pre = new Predicate[list.size()];
criteriaQuery.where(list.toArray(pre));
return criteriaQuery.getRestriction();
}
};
return findAll(sf);
}
}
下一步我們需要創建一個自定義的工廠,在這個工廠中註冊我們自己定義的BaseRepositoryImpl的實現。
/**
* 我們需要創建一個自定義的工廠,
* 在這個工廠中註冊我們自己定義的BaseRepositoryImpl的實現。
* 這個工廠的寫法具體參照Spring Data的JpaRepositoryFactoryBean和JpaRepositoryFactory。
* 這個類上面一堆的泛型,我們不用考慮,只要按照相同的方式來寫即可。
* Created by BFD-593 on 2017/8/16.
*/
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
public BaseRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}

/**
* 此方法是JpaRepositoryFactoryBean中的,
* 目的是返回一個工廠,我們調用它來反回我們自己的工廠
* @param entityManager
* @return
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new BaseRepositoryFactory(entityManager);
}
//創建一個內部類,該類不用在外部訪問
private static class BaseRepositoryFactory<T,I extends Serializable> extends JpaRepositoryFactory{
private final EntityManager entityManager;
public BaseRepositoryFactory(EntityManager entityManager){
super(entityManager);
this.entityManager=entityManager;
}

/**
* 通過這兩個方法來確定具體的實現類,JpaRepositoryFactory中的方法
* 也就是Spring Data Jpa具體實例化一個接口的時候會去創建的實現類。
* Spring Data JPA都是調用SimpleJpaRepository來創建實例。以下是我們自己的工廠實現的代碼
* @param information
* @return
*/
//設置具體的實現類是BaseRepositoryImpl
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return new BaseRepositoryImpl<T, I>((Class<T>)information.getDomainType(), entityManager);
}
//設置具體的實現類的class
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseRepositoryImpl.class;
}
}
}

接著我們需要讓spring在加載的時候找到我們自定義的BaseRepository的工廠,當我們使用了SpringBoot之後一切都變得簡單了,只要在入口類中加入@EnableJpaRepositories即可,代碼如下


/**
* 我們使用通用repository時
* 我們需要讓spring在加載的時候找到我們自定義的BaseRepositoryFactoryBean的工廠,
* [email protected],代碼如下
*/
@EnableJpaRepositories(basePackages = {"com.example.demo.dao"},
repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)//我們自己的工廠
@SpringBootApplication
public class SpringBootJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJpaApplication.class, args);
}
}
到這裏我們的整個自定義工廠的流程就結束了,我們寫一個接口實現BaseRepository即可。
具體代碼請看https://github.com/peterowang/spring-data-jpa-demo

spring data jpa自定義baseRepository