實現一個通用分頁查詢接口


我們使用的持久層框架是: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());
}

測試結果符合預期,就先醬紫了。。。。已知的缺陷還是蠻多了,比如范圍之類的怎么處理等等......等我有時間了再改吧。歡迎大家提意見啊.....提想法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM