实现一个通用分页查询接口
我们使用的持久层框架是:jpa + hibernate 经常使用到里面的动态查询。
最开始使用的时候觉得:窝草!还能这样玩。
写了一年多代码后.....
尼玛,不想写了。
于是乎,我最近想写个通用型的分页查询接口,只需要传递一个实体类就执行查询了,
再也不需要为了做一个查询还写repository,serivec,serviceImpl。想想都开心!
于是乎我决定先做一个demo版:
由于对jpa、hibernate源码,反射使用的都还不熟.....导致了一些问题,我这里也统统记录下来;
思路以及版本演进:
0.1:在原有方法上进行改进,将原来的从实体类上获取字段以及属性值改为通过反射获取;
问题:需要创建相关实体类的repository接口!还需要依赖接口调用,而通过反射调用方法的时候需要对象实例化,接口不能被实例化;
改进:尝试通过spring注入JpaSpecificationExecutor接口,没有成功!
0.2:修改思路,通过实例化JpaSpecificationExecutor 的实现类 SimpleJpaRepository ,直接调用findAll;
问题:SimpleJpaRepository 的构造函数中需要 EntityManager,尝试多个方法后使用了注解 @PersistenceContext 获取到了实例;
代码(只对String类型的属性做模糊查询):
这不是一个工具类,而是一个service实现类。因为注入功能需要加载到Spring容器中。
反射获取不到父类中的属性,介于我们先在项目中的实体id是继承而来,下面的代码做了特殊处理;
@PersistenceContext private EntityManager entityManager; /** * 通过反射匹配对象中的属性分页查询数据库 * * @param entity 实体类 * @param pageable 分页对象 * @return */ @Override public Object findByPage(final Object entity, Pageable pageable) { final Class<?> entityClass = entity.getClass(); SimpleJpaRepository simpleJpaRepository = new SimpleJpaRepository(entityClass, entityManager); return simpleJpaRepository.findAll(new Specification() { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { List<Predicate> predicateList = new ArrayList<>(); Field[] fields = entityClass.getDeclaredFields(); //属性名 String fieldName; //方法名 String getMethodName; //方法 Method method; //属性值 Object value; //属性类型 String type; try { //获取父类以及继承属性 Class<?> supper = entityClass.getSuperclass(); //获取继承的id属性 Field id = supper.getDeclaredField("id"); type = id.getGenericType().toString(); Method sm = supper.getDeclaredMethod("getId"); value = sm.invoke(entity); if (type.equals("class java.lang.String") && StringUtils.isNotBlank(value.toString())) { Path p = root.get("id"); predicateList.add(cb.equal(p, value.toString())); } } catch (Exception e) { e.printStackTrace(); } for (Field field : fields) { fieldName = field.getName(); type = field.getGenericType().toString(); getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); try { method = entityClass.getDeclaredMethod(getMethodName); value = method.invoke(entity); if (value == null) { continue; } if (type.equals("class java.lang.String") && StringUtils.isNotBlank(value.toString())) { Path p = root.get(fieldName); //对id做eq对比 非id的做like对比 predicateList.add(cb.like(p, "%" + value.toString() + "%")); } } catch (Exception e) { e.printStackTrace(); } } query.where(predicateList.toArray(new Predicate[predicateList.size()])); return query.getRestriction(); } }, pageable); }
0.3:实体类是由 EntityManager 来管理的,既然已经获取到了EntityManager那我们就直接拼JPQL查询吧,没必要再封装一次了!
代码如下:
/** * 根据对象属性的值通过entityManager查询 * * @param entity * @param searchParam * @return */ @Override public Object findByPageEntity(Object entity, SearchParam searchParam) { Class<?> entityClass = entity.getClass(); Field[] fields = entityClass.getDeclaredFields(); //属性名 String fieldName; //方法名 String getMethodName; //方法 Method method; //属性值 Object value; //属性类型 String type; //标记是否有where条件 boolean w = true; //构建查询JPQL语句 StringBuffer jpql = new StringBuffer("SELECT c FROM " + entityClass.getName() + " c "); StringBuffer where = new StringBuffer(); Map<String, Object> map = new HashedMap(); try { //获取父类以及继承属性 Class<?> supper = entityClass.getSuperclass(); //获取继承的id属性 Field id = supper.getDeclaredField("id"); type = id.getGenericType().toString(); Method sm = supper.getDeclaredMethod("getId"); value = sm.invoke(entity); if (value != null && type.equals("class java.lang.String") && StringUtils.isNotBlank(value.toString())) { w = false; where.append(" c.id").append(" = :id AND"); map.put("id", value); } } catch (Exception e) { e.printStackTrace(); } for (Field field : fields) { fieldName = field.getName(); type = field.getGenericType().toString(); getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); try { method = entityClass.getDeclaredMethod(getMethodName); value = method.invoke(entity); if (value == null) { continue; } //字符串类型的参数才允许模糊查询 if (type.equals("class java.lang.String") && StringUtils.isNotBlank(value.toString())) { map.put(fieldName, "%" + value.toString() + "%"); where.append(" c.").append(fieldName).append(" LIKE :").append(fieldName).append(" AND"); } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } //去掉多余的and if (!w) { jpql.append("WHERE"); where.delete(where.length() - 4, where.length()); jpql.append(where); } List<OrderFiled> orderFiles = searchParam.getOrderFiled(); if (orderFiles.size() > 0) { jpql.append(" ORDER BY"); //拼接排序语句 key为排序字段 value为排序类型 for (OrderFiled orderFiled : orderFiles) { jpql.append(" c.").append(orderFiled.getOrderFiled()).append(" ").append(orderFiled.getOrderType()).append(" ,"); } jpql.deleteCharAt(jpql.length() - 1); } Query query = entityManager.createQuery(jpql.toString()); //循环map为查询语句条件赋值 for (Map.Entry<String, Object> entry : map.entrySet()) { query.setParameter(entry.getKey(), entry.getValue()); } Integer number = searchParam.getPageNumber() - 1; Integer size = searchParam.getPageSize(); query.setFirstResult(number * size); query.setMaxResults(size); return query.getResultList(); }
0.4:自定义字段匹配条件,通过字段注解来实现
自定义注解类
/** * 用于实体类属性查询时的判断条件 * Created by nankeyimeng on 6/28/2017. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SearchLimit { /** * 条件枚举 * 分别对应模糊查询,等于,不等于,大于,大于等于,小于,小于等于 * * @author peida */ enum Where { like, eq, ne, gt, ge, lt, le } Where searchLimit() default Where.eq; }
demo出炉,这里的排序和分页是自己封装的对象
/** * 根据对象属性的值通过entityManager查询 * * @param entity * @param searchParam * @return */ @Override public Object findByPageEntity(Object entity, SearchParam searchParam) { Class<?> entityClass = entity.getClass(); Field[] fields = entityClass.getDeclaredFields(); //属性名 String fieldName; //方法名 String getMethodName; //方法 Method method; //属性值 Object value; //属性类型 String type; //标记是否有where条件 boolean w = true; //构建查询JPQL语句 StringBuffer jpql = new StringBuffer("SELECT c FROM " + entityClass.getName() + " c "); StringBuffer where = new StringBuffer(); Map<String, Object> map = new HashedMap(); try { //获取父类以及继承属性 Class<?> supper = entityClass.getSuperclass(); //获取继承的id属性 Field id = supper.getDeclaredField("id"); type = id.getGenericType().toString(); Method sm = supper.getDeclaredMethod("getId"); value = sm.invoke(entity); if (value != null && type.equals("class java.lang.String") && StringUtils.isNotBlank(value.toString())) { w = false; where.append(" c.id").append(" = :id AND"); map.put("id", value); } } catch (Exception e) { e.printStackTrace(); } for (Field field : fields) { fieldName = field.getName(); if (field.isAnnotationPresent(SearchLimit.class)) { type = field.getGenericType().toString(); getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); //获取自定义注解 SearchLimit searchLimit = field.getAnnotation(SearchLimit.class); //获取注解值 String limitAnnotation = searchLimit.searchLimit().toString(); try { method = entityClass.getDeclaredMethod(getMethodName); value = method.invoke(entity); if (value == null) { continue; } switch (limitAnnotation) { case "like": if (w) { w = false; } //字符串类型的参数才允许模糊查询 if (type.equals("class java.lang.String") && StringUtils.isNotBlank(value.toString())) { map.put(fieldName, "%" + value.toString() + "%"); where.append(" c.").append(fieldName).append(" LIKE :").append(fieldName).append(" AND"); } break; case "eq": if (w) { w = false; } map.put(fieldName, value); where.append(" c.").append(fieldName).append(" = :").append(fieldName).append(" AND"); break; case "ne": if (w) { w = false; } map.put(fieldName, value); where.append(" c.").append(fieldName).append(" != :").append(fieldName).append(" AND"); break; case "gt": if (w) { w = false; } map.put(fieldName, value); where.append(" c.").append(fieldName).append(" > :").append(fieldName).append(" AND"); break; case "ge": if (w) { w = false; } map.put(fieldName, value); where.append(" c.").append(fieldName).append(" >= :").append(fieldName).append(" AND"); break; case "lt": if (w) { w = false; } map.put(fieldName, value); where.append(" c.").append(fieldName).append(" < :").append(fieldName).append(" AND"); break; case "le": if (w) { w = false; } map.put(fieldName, value); where.append(" c.").append(fieldName).append(" <= :").append(fieldName).append(" AND"); break; default: //没有加注解的不加入查询条件 } } catch (Exception e) { e.printStackTrace(); } } } //去掉多余的and if (!w) { jpql.append("WHERE"); where.delete(where.length() - 4, where.length()); jpql.append(where); } List<OrderFiled> orderFiles = searchParam.getOrderFiled(); if (orderFiles.size() > 0) { jpql.append(" ORDER BY"); //拼接排序语句 key为排序字段 value为排序类型 for (OrderFiled orderFiled : orderFiles) { jpql.append(" c.").append(orderFiled.getOrderFiled()).append(" ").append(orderFiled.getOrderType()).append(" ,"); } jpql.deleteCharAt(jpql.length() - 1); } Query query = entityManager.createQuery(jpql.toString()); //循环map为查询语句条件赋值 for (Map.Entry<String, Object> entry : map.entrySet()) { query.setParameter(entry.getKey(), entry.getValue()); } Integer number = searchParam.getPageNumber() - 1; Integer size = searchParam.getPageSize(); query.setFirstResult(number * size); query.setMaxResults(size); return query.getResultList(); }
测试代码:
实体类:
@Entity @DynamicInsert(true) @DynamicUpdate(true) @Table(name = "UM_SYS_USER_MEMBER") public class SysUserMember extends IdEntity implements java.io.Serializable { private static final long serialVersionUID = 1531492635701976701L; private String userName;//用户名(昵称) private String userPassword; private String userSalt; private List<SysRole> sysRoles = new ArrayList<SysRole>(); private List<SysResource> sysResources = new ArrayList<SysResource>(); private List<SysOrganization> sysOrganizations = new ArrayList<SysOrganization>(); private String available; private String alias; @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+08:00") @SearchLimit(searchLimit = SearchLimit.Where.lt) private Date createTime; @SearchLimit(searchLimit = SearchLimit.Where.eq) private Integer isAdmin;
........
测试方法
@Test public void findByPageEntity() throws Exception { SysUserMember sysUserMember = new SysUserMember(); sysUserMember.setIsAdmin(1); sysUserMember.setCreateTime(new Date()); List<OrderFiled> order = new ArrayList<>(); order.add(new OrderFiled("createTime", "asc")); order.add(new OrderFiled("isAdmin", "desc")); SearchParam searchParam = new SearchParam(1, 10, order); Object object = searchService.findByPageEntity(sysUserMember, searchParam); System.out.print(object.toString()); }
测试结果符合预期,就先酱紫了。。。。已知的缺陷还是蛮多了,比如范围之类的怎么处理等等......等我有时间了再改吧。欢迎大家提意见啊.....提想法