Spring-Data-JPA自定义Repository实现自定义sql查询

Spring Data JPA是Spring Data的一个子项目,它通过提供基于JPA的Repository极大的减少了JPA作为数据访问方案的代码量。

结合Specification(规范)和自定义的Repository实现来定制一个自动模糊查询。即对于任意的实体对象进行查询,对象里面有几个值就查几个,当值为字符型时自动进行模糊查询,其余的类型使用自动等于查询,没有值就查询全部。

(1)定义Specification:

package com.wisely.specs;

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;
import static com.google.common.collect.Iterables.toArray;

public class CustomerSpecs {

    /**
     * //1 定义一个返回值为Specification的方法byAuto,这里使用的是泛型T,所以这个Specification可以适用于任意的实体类型。
     * 它接收的参数是entityManager和当前的包含值作为查询条件的实体类对象。
     *
     * @param entityManager
     * @param example
     * @param 
     * @return
     */
    public static  Specification byAuto(final EntityManager entityManager, final T example) { //1
        //2获得当前实体类对象的类类型
        final Class clazz = (Class) example.getClass();

        return new Specification() {

            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {

                //3新建Predicate列表存储构造的查询条件
                List predicates = new ArrayList<>();

                //4获得实体类的EntityType,我们可以从EntityType获得实体类的属性
                EntityType entity = entityManager.getMetamodel().entity(clazz);

                //5对实体类的所有属性做循环
                for (Attribute attr : entity.getDeclaredAttributes()) {
                    //6获得对象属性的值
                    Object attrValue = getValue(example, attr); 
                    if (attrValue != null) {
                        
                        //7 判断当前属性值为字符类型时
                        if (attr.getJavaType() == String.class) {
                            
                            //8当前字符不为空的情况下
                            if (!StringUtils.isEmpty(attrValue)) {
                                
                                //9构造当前属性like(前后%)属性值查询条件,并添加到条件列表中
                                predicates.add(cb.like(root.get(attribute(entity, attr.getName(), String.class)),
                                        pattern((String) attrValue))); 
                            }
                        } else {
                            //10其余情况下,构造属性和属性值equal查询条件,病添加到条件列表中
                            predicates.add(cb.equal(root.get(attribute(entity, attr.getName(), attrValue.getClass())),
                                    attrValue)); 
                        }
                    }

                }

                //11将条件列表转换成Predicate
                return predicates.isEmpty() ? cb.conjunction() : cb.and(toArray(predicates, Predicate.class));
            }

            /**
             * 12 通过反射获取实体类对象对应属性的属性值值
             */
            private  Object getValue(T example, Attribute attr) {
                return ReflectionUtils.getField((Field) attr.getJavaMember(), example);
            }

            /**
             * 13获得实体类的当前属性的SingularAttribute,SingularAttribute包含的是实体类的某个单独属性
             */
            private  SingularAttribute attribute(EntityType entity, String fieldName,
                                                             Class fieldClass) {
                return entity.getDeclaredSingularAttribute(fieldName, fieldClass);
            }

        };

    }

    /**
     * 14构造like的查询模式,前后加%
     */
    static private String pattern(String str) {
        return "%" + str + "%";
    }
}

(2) 定义接口

此接口中继承了JpaRepository,让我们具备了JpaRepository所提供的方法,继承了JpaSpecificationExecutor,让我们具备使用Specification的能力。

package com.wisely.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
        extends JpaRepository, JpaSpecificationExecutor {

    Page findByAuto(T example, Pageable pageable);


}

(3) 定义实现

此类继承JpaRepository的实现类SimpleJpaRepository,让我们可以适用SimpleJpaRepository的方法;此类当然还需要实现我们自定义的接口CustomRepository。

  findByAuto方法适用byAuto Specification构造的条件查询,并提供分页功能

package com.wisely.support;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.io.Serializable;

import static com.wisely.specs.CustomerSpecs.byAuto;

public class CustomRepositoryImpl
        extends SimpleJpaRepository implements CustomRepository {

    private final EntityManager entityManager;

    public CustomRepositoryImpl(Class domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public Page findByAuto(T example, Pageable pageable) {
        return findAll(byAuto(entityManager, example), pageable);
    }

}

(4)定义repositoryFactoryBean

package com.wisely.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;

/**
 * 自定义RepositoryFactoryBean,自定义JpaRepositoryFactoryBean 替代默认RepositoryFactoryBean,
 * 我们会获得一个RepositoryFactory,
 * RepositoryFactory将会注册我们自定义的Repository的实现。
 *
 * @param 
 * @param 
 * @param 
 * @author ryz2593
 */
public class CustomRepositoryFactoryBean, S, ID extends Serializable>
        extends JpaRepositoryFactoryBean {// 1自定义RepositoryFactoryBean,继承JpaRepositoryFactoryBean.

    /**
     * //2重写createRepositoryFactory方法,用当前的额CustomRepositoryFactory创建实例
     *
     * @param entityManager
     * @return
     */
    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new CustomRepositoryFactory(entityManager);
    }

    /**
     * // 3常见CustomRepositoryFactory,并继承JpaRepositoryFactory
     */
    private static class CustomRepositoryFactory extends JpaRepositoryFactory {


        public CustomRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
        }

        /**
         * // 4重写getTargetRepository方法,获得当前自定义的Repository实现
         *
         * @param information
         * @param entityManager
         * @param 
         * @param 
         * @return
         */
        @Override
        @SuppressWarnings({"unchecked"})
        protected  SimpleJpaRepository getTargetRepository(
                RepositoryInformation information, EntityManager entityManager) {
            return new CustomRepositoryImpl((Class) information.getDomainType(), entityManager);

        }

        /**
         * // 5重写getRepositoryBaseClass, 获得当前自定义的Repository实现的类型
         *
         * @param metadata
         * @return
         */
        @Override
        protected Class getRepositoryBaseClass(RepositoryMetadata metadata) {
            return CustomRepositoryImpl.class;
        }
    }
}

(5) 使用

package com.wisely.dao;

import com.wisely.domain.Person;
import com.wisely.support.CustomRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface PersonRepository extends CustomRepository {
    List findByAddress(String address);

    List fineByName(String name, Sort sort);

    Page findByName(String name, Pageable pageable);

    Person findByNameAndAddress(String name, String address);

    @Query("select p from Person p where p.name= :name and p.address= :address")
    Person withNameAndAddressQuery(@Param("name") String name, @Param("address") String address);

    Person withNameAndAddressNamedQuery(String name, String address);

}

  只需让实体类Repositoy继承我们自定义的Repository接口,即可使用我们在自定义Repository中实现的功能。

package com.wisely.web;

import com.wisely.dao.PersonRepository;
import com.wisely.domain.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DataController {
    
    @RequestMapping("/auto")
    public Page auto(Person person) {

        Page pagePeople = personRepository.findByAuto(person, new PageRequest(0, 10));

        return pagePeople;

    }


}

控制器中接收一个Person对象,当Person的name有值时,会自动对name进行like查询;当age有值时,会进行等于查询;当Person中有多个值不为空时,会自动构建多个查询条件;当Person对象为空时,默认查询出所有记录。

(6)配置

package com.wisely;

import com.wisely.dao.PersonRepository;
import com.wisely.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 Ch82Application {
    @Autowired
    PersonRepository personRepository;
	

    public static void main(String[] args) {
        SpringApplication.run(Ch82Application.class, args);

    }


}

在配置类上配置@EnableJpaRepositories,并指定repositoryFactoryBeanClass,让我们自定义的Repository实现起作用。如果我们不需要自定义Repository实现,则在Spring Data JPA里无需添加@EnableJpaRepositories注解,应为@SpringBootApplication包含的@EnableAutoConfiguration注解已经开启了对Spring Data JPA的支持。

(7)运行

访问http://localhost:8080/auto,可以查询全部

http://localhost:8080/auto?address=xx,构造like查询

 

以上内容摘自《Spring Boot实战》

你可能感兴趣的:(Spring)