Javaweb——Spring Boot 系列(11)自定义 Repository访问数据库

  • JPA 除了可以直接调用相关接口来访问和操作数据库,还支持通过继承接口自定义 Repository 实现访问数据库,自定义实现主要工作为自定义编辑一个 Specification,一个继承 JpaRepository 的 Interface 以及相应的实现,定义一个 repositoryFactoryBean。

1、Specification 自定义

  • 在项目目录下新建一个 specs 文件夹,新建一个 CustomSpecs 类,编辑如下代码:
    package com.pyc.springjpa.specs;
    
    import static com.google.common.collect.Iterables.toArray;
    
    import org.springframework.data.jpa.domain.Specification;
    import org.springframework.util.ReflectionUtils;
    import org.springframework.util.StringUtils;
    
    import javax.persistence.EntityManager;
    import javax.persistence.criteria.CriteriaBuilder;
    import javax.persistence.criteria.CriteriaQuery;
    import javax.persistence.criteria.Predicate;
    import javax.persistence.criteria.Root;
    import javax.persistence.metamodel.Attribute;
    import javax.persistence.metamodel.EntityType;
    import javax.persistence.metamodel.SingularAttribute;
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.List;
    
    public class CustomSpecs {
        // Define a method that returns a Specification and named byAuto, use generic as return type of this method
        // so that this method can affect any entity class,the parameter of this method is entityManager
        // and a query condition with current parameter value.
        // 定义一个返回值为 Specification 名称为byAuto的方法,使用泛型 T,表明这个Specification 可用于
        // 任何实体类;参数为 entityManager 和包含当前值的查询条件
        public static <T> Specification<T> byAuto(final EntityManager entityManager,
                                                  final T example) {
            //获得当前实体类对象的类型
            final Class<T> type = (Class<T>) example.getClass();
            return new Specification<T>() {
                @Override
                public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery,
                                             CriteriaBuilder criteriaBuilder) {
                    // create a Predicate list to stores the constructed query conditions
                    List<Predicate> predicates = new ArrayList<>(); //新建 Predicate 列表存储构造的查询条件
                    // get the type of entity so,and then can get the attributes of entity by using entity type
                    //获得实体类的 EntityType,从而获得实体类的属性
                    EntityType<T> entity = entityManager.getMetamodel().entity(type);
                    // Doing loop on all attributes of entity
                    // 对实体类的所有属性做循环
                    for (Attribute<T, ?> attr : entity.getDeclaredAttributes()) {
                        // get the value of an attribute of entity
                        // 获得实体类对象某一个属性的值
                        Object attrValue = getValue(example, attr);
                        if (attrValue != null) {
                            // if the value type of current attribute is character type
                            //当前属性值为字符类型的时候
                            if (attr.getJavaType() == String.class) {
                                // if current character value not null
                                // 当前字符不为空
                                if (!StringUtils.isEmpty(attrValue)) {
                                    // construct a query condition of current attribute by using 'like'  expression
                                    // and add to condition list
                                    // 构造当前属性 like(前后%)属性值查询条件,并添加到条件列表
                                    predicates.add(criteriaBuilder.like(root.get(attribute(entity,
                                            attr.getName(), String.class)), pattern((String) attrValue)));
                                }
                            } else {
                            // other happening,construct a query condition of current attribute by using 'equal' expressing
                                // 其他条件,构造属性和属性值 equal 查询条件并添加到条件列表
                                predicates.add(criteriaBuilder.equal(root.get(attribute(entity,
                                        attr.getName(), attrValue.getClass())), attrValue));
                            }
                        }
                    }
                    // Transform the query condition list to Predicate
                    //将条件列表转换成 Predicate
                    return predicates.isEmpty() ? criteriaBuilder
                            .conjunction() : criteriaBuilder.and(toArray(predicates, Predicate.class));
                }
    
                private <T> Object getValue(T example, Attribute<T, ?> attr) {
                    // get the attribute value of the corresponding attribute of entity object by reflect function
                    // 通过反射获得实体类对象对应属性的属性值
                    return ReflectionUtils.getField((Field) attr.getJavaMember(), example);
                }
                // Get the SingularAttribute of the current attribute of the entity class,
                //the SingularAttribute contains a single attribute of the entity class
                    // 获得实体类的当前属性的 SingularAttribute,SingularAttribute 包含实体类某个单独属性
                private <E, T> SingularAttribute<T, E> attribute(EntityType<T> entity,
                                                                 String fieldName,
                                                                 Class<E> fieldClass) {
                    return entity.getDeclaredSingularAttribute(fieldName, fieldClass);
                }
            };
        }
        // 构造 like 查询模式
        static private String pattern(String str) {
            return "%" + str + "%";
        }
    }
    

2、自定义接口

  • 新建一个 support 文件夹,用于存放虚接口类,代码如下:
    package com.pyc.springjpa.support;
    
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    import org.springframework.data.repository.NoRepositoryBean;
    
    import java.io.Serializable;
    
    @NoRepositoryBean
    public interface CustomRepository<T, ID extends Serializable>extends JpaRepository<T, ID> ,JpaSpecificationExecutor<T>{
    
        Page<T> findByAuto(T example,Pageable pageable);
    
    
    }
    
  • 继承 J怕Repository,从而继承我们需要的方法,而继承 JpaSpecificationExecutor 则可以是该接口的实现类有使用 Specification 的能力。

3、接口实现类

  • 新建一个类 CustomRepositoryImpl 用于实现刚刚新建的接口 CustomRepository,代码如下:
    package com.pyc.springjpa.support;
    
    import java.io.Serializable;
    
    import javax.persistence.EntityManager;
    
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
    
    import static com.pyc.springjpa.specs.CustomSpecs.*;
    
    public class CustomRepositoryImpl <T, ID extends Serializable>
            extends SimpleJpaRepository<T, ID>  implements CustomRepository<T,ID> {
        private final EntityManager entityManager;
    
        public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
            super(domainClass, entityManager);
            this.entityManager = entityManager;
        }
    
        public Page<T>findByAuto(T example, Pageable pageable){
            return findAll(byAuto(entityManager,example),pageable);
        }
    }
    
  • 继承自定义接口的同时,也继承了 SimpleJpaRepository 中的方法。

4、定义 RepositoryFactoryBean

  • 代码如下:
    package com.pyc.springjpa.support;
    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;
    
    public class CustomRepositoryFactoryBean<T extends JpaRepository<S,ID>,S,ID extends Serializable>
    extends JpaRepositoryFactoryBean<T, S, ID> {
        // 重写 createRepositoryFactory
        // rewrite method createRepositoryFactory
        @Override
        protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
            return new CustomRepositoryFactory(entityManager);
        }
        // create inner class CustomRepository and extend JpaRepositoryFactory
        private static class CustomRepositoryFactory extends JpaRepositoryFactory {
    
    
            public CustomRepositoryFactory(EntityManager entityManager) {
                super(entityManager);
            }
            // rewrite method getTargetRepository and using  CustomRepository
            @Override
            @SuppressWarnings({"unchecked"})
            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;
            }
        }
    }
    
  • 以上三个类文件位于同个目录下。

5、修改 PersonRepository

  • 修改上一篇博文所使用的 PersonRepository,将其继承的接口从 JpaRepository 改为自定义的 CustomRepository 接口,代码如下:
    package com.pyc.springjpa.dao;
    
    import com.pyc.springjpa.domain.Person;
    import com.pyc.springjpa.support.CustomRepository;
    import org.springframework.data.repository.query.Param;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    
    import java.util.List;
    
    public interface PersonRepository extends CustomRepository<Person,Long> {
        // 根据地址查询,返回值为列表
        List<Person> findByAddress(String address);
        // 根据姓名和地址查询,返回值为单个对象
        Person findByNameAndAddress(String name, String address);
        // 使用 @Query 查询,参数按照名称绑定
        @Query("select p from Person p where p.name= :name and p.address= :address")
        Person withNameAndAddressQuery(@Param("name")String name,@Param("address")String address);
        //使用 @NamedQuery 查询,在实体类中已注解
        Person withNameAndAddressNamedQuery(String name, String address);
    
    }
    

6、添加 URL

  • 在上一篇中的 DataController 中增加一个 @RequestMapping 注解的方法,代码如下:
    @RequestMapping("/auto")
    public Page<Person> auto(Person person){
        Page<Person> pagePeople;
        pagePeople = personRepository.findByAuto(person,new PageRequest(0,10));
        return pagePeople;
    }
    

7、配置入口类

  • 在入口类上增加一个 @EnableRepositories 注解,以及 @Autowired PersonRepository,代码如下:
    package com.pyc.springjpa;
    
    import com.pyc.springjpa.dao.PersonRepository;
    import com.pyc.springjpa.support.CustomRepositoryFactoryBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    @SpringBootApplication
    @EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
    public class SpringjpaApplication {
        @Autowired
        PersonRepository personRepository;
    
        public static void main(String[] args) {
            SpringApplication.run(SpringjpaApplication.class, args);
        }
    }
    

8、运行测试

  • 运行项目,打开浏览器,首先访问 localhost:8080/auto,无构造查询条件,查询所有的数据记录,浏览器获得后台返回的数据,如下:
    在这里插入图片描述
  • 再测试 localhost:8080/auto?address=海,构造 address 的 like 查询,即如下查询语句:
    select * from PERSON where address like '%海%';
    
  • 查询结果如下:
    在这里插入图片描述
  • 使用 URL=localhost:8080/auto?address=海&name=y&age=22,该 url 等价于如下的 SQL 语句,
    select * from PERSON where address like '%海%' and name like '%y%' and age=22
    
  • 返回结果如下:
    在这里插入图片描述
  • 在测试的时候,一开始忘记给入口类增加新注解 @EnableJpaRepository 导致无法执行,一度以为自定义接口写错了,改了好几遍才发现是入口类忘记配置了,这说明配置的重要性,该用的注解一个都不能少了。

上一篇

发布了148 篇原创文章 · 获赞 15 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42896653/article/details/104237878