SpringBoot第四讲扩展和封装Spring Data JPA(一)_自定义Repository和创建自己的BaseRepository

这一讲主要介绍Spring Data JPA的封装。和设计相关的东西都是仁者见仁,智者见智的事情,如果你有更好的封装方案可以和我交流,互相学习。这一讲会讲如下一些内容
- 扩展Spring Data JPA实现自己的一些特殊方法
- 创建一个自己的BaseRepository
- 封装Specification来快速完成一些简单的查询操作
- 封装分页和排序操作。

在一些特殊时候,我们会设计到对Spring Data JPA中的方法进行重新实现,这将会面临一个问题,如果我们新创建一个实现类。如果这个实现类实现了JpaRepository接口,这样我们不得不实现该接口中的所有方法,如果不实现该接口,那意味着我们就无法使用Spring Data JPA中给我们提供的那些好用的方法。所以在扩展的时候我们需要按照如下方法进行。

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的接口

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);
}

该接口实现了JpaRepository,这样就保证拥有了Spring Data JPA中那些比较好用的方法,然后可以自定义自己需要的方法,代码中定义了一个listBySQL的方法。需要注意的是@NoRepositoryBean,这个表示该接口不会创建这个接口的实例(我们原来定义的StudentPageRepository这些,Spring Data JPA的基础组件都会自动为我们创建一个实例对象,加上这个annotation,spring data jpa的基础组件就不会再为我们创建它的实例)。之后我们编写实现类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;

    //父类没有不带参数的构造方法,这里手动构造父类
    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();
    }
}

这个实现类比较的简单,首先我们需要继承SimpleJpaRepositorySimpleJpaRepository帮助我们实现了JpaRepository中的方法。然后实现BaseRepository接口。listBySQL方法非常的简单,上面已经详细介绍过了,具体的作用就是执行一条sql返回一组投影的列表。

下一步我们需要创建一个自定义的工厂,在这个工厂中注册我们自己定义的BaseRepositoryImpl的实现。这个工厂的写法具体参照Spring Data的JpaRepositoryFactoryBeanJpaRepositoryFactory。这个类上面一堆的泛型,我们不用考虑,只要按照相同的方式来写即可。

创建JpaRepositoryFactoryBean需要调用如下方法,通过这个方法来返回一个工厂,这里返回的是JpaRepositoryFactory。

protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new JpaRepositoryFactory(entityManager);
}

在JpaRepositoryFactory中,有两个方法比较关键

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;
}

通过这两个方法来确定具体的实现类,也就是Spring Data Jpa具体实例化一个接口的时候会去创建的实现类。通过代码我们可以发现,Spring Data JPA都是调用SimpleJpaRepository来创建实例。以下是我们自己的工厂实现的代码

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);
    }

    //创建一个内部类,该类不用在外部访问
    private static class BaseRepositoryFactory<T, I extends Serializable>
            extends JpaRepositoryFactory {

        private final EntityManager em;

        public BaseRepositoryFactory(EntityManager em) {
            super(em);
            this.em = em;
        }

        //设置具体的实现类是BaseRepositoryImpl
        @Override
        protected Object getTargetRepository(RepositoryInformation information) {
            return new BaseRepositoryImpl<T, I>((Class<T>) information.getDomainType(), em);
        }

        //设置具体的实现类的class
        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return BaseRepositoryImpl.class;
        }
    }
}

接着我们需要让spring在加载的时候找到我们自定义的BaseRepository的工厂,当我们使用了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);
     }
 }

到这里我们的整个自定义工厂的流程就结束了,我们写一个接口实现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());
}

到这里,我们这部分的内容就基本结束了,下一章节我们要解决的问题是基于Specification的封装问题。

本文的源代码在这里:源代码

发布了13 篇原创文章 · 获赞 2 · 访问量 2万+
展开阅读全文

jpa自定义封装查询,继承JpaRepositoryFactoryBean出错!

06-16

直接上代码,在线等求各位大神帮忙! package com.example.rui.core.japcreatelike; 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.jpa.repository.support.SimpleJpaRepository; 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 win10 on 2017/6/16 0016. */ public class CustomRepositoryFactoryBean<T extends JpaRepository<S,ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID>{ @Override protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { return new CustomRepositoryFactory(entityManager); } private static class CustomRepositoryFactory extends JpaRepositoryFactory { public CustomRepositoryFactory(EntityManager entityManager) { super(entityManager); } @Override protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) { return new CustomRepositoryImpl<T, ID>((Class<T>) information.getDomainType(), entityManager); } @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return CustomRepositoryImpl.class; } } } ![图片说明](https://img-ask.csdn.net/upload/201706/16/1497582570_884918.png) 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览