我們使用的持久層框架是: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()); }
測試結果符合預期,就先醬紫了。。。。已知的缺陷還是蠻多了,比如范圍之類的怎么處理等等......等我有時間了再改吧。歡迎大家提意見啊.....提想法