spring boot 提供的默認repository 適合大多場景,對於一些特殊場景,須要特殊的方法,除了使用@NAMEQUERY @QUERY 自定義sql語句外,還能夠自定義基礎repositoryjava
這裏記錄一些踩到的坑或者說遇到的須要注意的地方spring
一、自定義查詢條件,使用specification自定義查詢條件sql
注意的地方root 獲取屬性,criteriaBuilder自定義條件app
package com.duoke.demo.bean; 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 CustomerSpecs { /** * 自定義查詢 * @param manager * @param example * @param <T> * @return */ public static <T>Specification<T> byAuto(final EntityManager manager,final T example){ // 獲取泛型類別 final Class<T> type = (Class<T>) example.getClass(); return new Specification<T>() { @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { List<Predicate> predicates = new ArrayList<>(); // 獲取實體類的entitytype EntityType<T> entity = manager.getMetamodel().entity(type); // 對實體類的全部屬性循環 for (Attribute<T,?> attr:entity.getDeclaredAttributes()) { Object attrValue = getValue(example,attr); if(attrValue !=null){ if(attr.getJavaType() == String.class){ if(!StringUtils.isEmpty(attrValue)){ predicates.add(criteriaBuilder.like(root.get(attribute(entity,attr.getName(),String.class)),pattern((String) attrValue))); } }else{ predicates.add(criteriaBuilder.equal(root.get(attribute(entity,attr.getName(),attrValue.getClass())),attrValue)); } } } return predicates.isEmpty() ? criteriaBuilder.conjunction() : criteriaBuilder.and(toArray(predicates,Predicate.class)); } /** * 獲取field屬性 * @param example * @param attr * @param <T> * @return */ private <T>Object getValue(T example,Attribute<T,?> attr){ return ReflectionUtils.getField((Field) attr.getJavaMember(),example); } /** * 獲取實體類的當前屬性 * @param entity * @param fieldName * @param fieldClass * @param <T> * @param <E> * @return */ private <T,E> SingularAttribute<T,E> attribute(EntityType<T>entity,String fieldName,Class<E> fieldClass){ return entity.getDeclaredSingularAttribute(fieldName,fieldClass); } }; } static private String pattern(String str){ return "%"+str+"%"; } }
二、自定義領repository(1-5步弄完後,很重要的一步最容易忘記的一步是第六步,注意,不然啓動項目會報錯,諸如找不到 實體type,not suitable 缺乏構造函數等等錯誤)dom
1)、定義repository接口類,添加自定義接口ide
package com.duoke.demo.service; 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; //指明當前接口不是領域類模型(service) @NoRepositoryBean //繼承JPARES 具備基礎的方法好比findALL //繼承spec 具備使用自定義條件的基礎 public interface CustomRepo<T,ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> { Page<T> findByAuto(T example, Pageable pageable); }
2).定義實現類函數
package com.duoke.demo.service; 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.duoke.demo.bean.CustomerSpecs.*; public class CustomRepoImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements CustomRepo<T,ID>{ private EntityManager entityManager; public CustomRepoImpl(Class<T> domainClass, EntityManager entityManager) { super(domainClass, entityManager); this.entityManager = entityManager; } @Override public Page<T> findByAuto(T example, Pageable pageable) { return findAll(byAuto(entityManager,example),pageable); } }
3)定義repository 工廠beanui
package com.duoke.demo.service; 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.JpaRepositoryImplementation; 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; /** * 自定義jpa 工廠bean * @param <T> * @param <S> * @param <ID> */ public class CustomRepoFactoryBean<T extends JpaRepository<S, ID>,S,ID extends Serializable> extends JpaRepositoryFactoryBean<T,S,ID> { /** * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. * * @param repositoryInterface must not be {@literal null}. */ public CustomRepoFactoryBean(Class<? extends T> repositoryInterface) { super(repositoryInterface); } // 工廠生產repo 方法 @Override protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { return new CustomRepoFactory(entityManager); } private class CustomRepoFactory extends JpaRepositoryFactory{ private final EntityManager entityManager; /** * Creates a new {@link JpaRepositoryFactory}. * * @param entityManager must not be {@literal null} */ public CustomRepoFactory(EntityManager entityManager) { super(entityManager); this.entityManager = entityManager; } @Override // 重寫獲取實現類 protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) { return new CustomRepoImpl<T,ID>((Class<T>) information.getDomainType(),entityManager); } @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return CustomRepoImpl.class; } } }
4)改變以前的res繼承關係this
package com.duoke.demo.service; import com.duoke.demo.bean.Person; import com.sun.xml.internal.bind.v2.model.core.ID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; /** * JPA 數據訪問接口 */ public interface IPersonRepository extends CustomRepo<Person,String> { // 定義訪問接口 // List<Person> findByName(String name); // List<Person> findByAge(Integer age); // // List<Person> findByNameLike(String name); // // List<Person> findByNameAndAddress(String name,String address); // // @Query("select p from Person p where p.address = :address and p.name = :name") // List<Person> withNameAndAddress(@Param("address") String address,@Param("name") String name); // // @Modifying // @Transactional // @Query("update person set name = ?1") // int setName(String name); }
5)controller 注入repository後可直接使用google
@RequestMapping("custom") public List<Person> findByCustom(String name){ Person person = new Person(); person.setName(name); Page<Person> peoples = iPersonRepository.findByAuto(person,buildPage(1,2)); return peoples.getContent(); }
6)* 啓動類指定加載的bean工廠類
@SpringBootApplication(scanBasePackages = "com.duoke") @EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepoFactoryBean.class) public class DemoApplication { // 項目啓動main方法 public static void main(String[] args) { // 關閉banner SpringApplication app = new SpringApplication(DemoApplication.class); app.setBannerMode(Banner.Mode.OFF); app.run(); // SpringApplication.run(DemoApplication.class, args); } }
另外一種查詢條件的方式使用jdk 提供的原生反射接口獲取值
package com.duoke.demo.bean; import org.springframework.data.jpa.domain.Specification; 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.EntityType; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class PersonSpec { public static<T> Specification<T> findByEquealAddress(T example){ // 獲取實例類 Class<T> type = (Class<T>) example.getClass(); return new Specification<T>() { @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { List<Predicate> predicates = new ArrayList<>();for (Field field:type.getDeclaredFields()) { field.setAccessible(true); try { System.out.println(field.get(example)); Object value = field.get(example); if(value!=null){ if (value.getClass() == String.class){ predicates.add(criteriaBuilder.equal(root.get(field.getName()),value)); } } } catch (IllegalAccessException e) { e.printStackTrace(); } } Predicate predicate[] = new Predicate[predicates.size()]; return criteriaBuilder.and(predicates.toArray(predicate)); } }; } }