利用反射跟自定義註解拼接實體對象的查詢SQL

  前言

  項目中雖然有ORM映射框架來幫咱們拼寫SQL,簡化開發過程,下降開發難度。但不免會出現須要本身拼寫SQL的狀況,這裏分享一個利用反射跟自定義註解拼接實體對象的查詢SQL的方法。java

 

  代碼

  自定義註解:
sql

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Like {

}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Between {

    /**
     * 最小值的實體屬性名
     */
    String min();

    /**
     * 最大值的實體屬性名
     */
    String max();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface In {

    /**
     * in的具體集合的屬性名
     */
    String values();
}

 

  實體對象:數據庫

@Data
@Entity
@Table(name = "RES_LOG")
public class ResLog {
    @Id
    private String logId;
    private String resourceType;
    private String resourceId;
    @Like //開啓模糊查詢
    private String resourceName;
    private String resourceCode;
    @In(values = "operationTypeList")//in查詢
    private String operationType;
    @Between(min = "operationTimeStart", max = "operationTimeEnd")//開啓區間查詢
    private Date operationTime;
    private String operatorId;
    private String operator;

    @Transient
    private Date operationTimeStart;
    @Transient
    private Date operationTimeEnd;
    @Transient
    private List<String> operationTypeList;

}

 

  拼接SQL方法:app

/**
 * 自動拼接原生SQL的「and」查詢條件,支持自定義註解:@Like @Between @In
 *
 * @param entity           實體對象
 * @param sql              待拼接SQL
 * @param ignoreProperties 忽略屬性
 */
public static void appendQueryColumns(Object entity, StringBuilder sql, String... ignoreProperties) {

    try {
        //忽略屬性
        List<String> ignoreList1 = Arrays.asList(ignoreProperties);
        //默認忽略分頁參數
        List<String> ignoreList2 = Arrays.asList("class", "pageable", "page", "rows", "sidx", "sord");
   
        //反射獲取Class的屬性(Field表示類中的成員變量)
        for (Field field : entity.getClass().getDeclaredFields()) {
            //獲取受權
            field.setAccessible(true);
            //屬性名稱
            String fieldName = field.getName();
            //屬性的值
            Object fieldValue = field.get(entity);
            //檢查Transient註解,是否忽略拼接
            if (!field.isAnnotationPresent(Transient.class)) {
                String column = new PropertyNamingStrategy.SnakeCaseStrategy().translate(fieldName).toLowerCase();
                //值是否爲空
                if (!StringUtils.isEmpty(fieldValue)) {
                    //映射關係:對象屬性(駝峯)->數據庫字段(下劃線)
                    if (!ignoreList1.contains(fieldName) && !ignoreList2.contains(fieldName)) {
                        //開啓模糊查詢
                        if (field.isAnnotationPresent(Like.class)) {
                            sql.append(" and " + column + " like '%" + escapeSql(fieldValue) + "%'");
                        }
                        //開啓等值查詢
                        else {
                            sql.append(" and " + column + " = '" + escapeSql(fieldValue) + "'");
                        }
                    }
                } else {
                    //開啓區間查詢
                    if (field.isAnnotationPresent(Between.class)) {
                        //獲取最小值
                        Field minField = entity.getClass().getDeclaredField(field.getAnnotation(Between.class).min());
                        minField.setAccessible(true);
                        Object minVal = minField.get(entity);
                        //獲取最大值
                        Field maxField = entity.getClass().getDeclaredField(field.getAnnotation(Between.class).max());
                        maxField.setAccessible(true);
                        Object maxVal = maxField.get(entity);
                        //開啓區間查詢
                        if (field.getType().getName().equals("java.util.Date")) {
                            if (!StringUtils.isEmpty(minVal)) {
                                sql.append(" and " + column + " > to_date( '" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) minVal) + "','yyyy-mm-dd hh24:mi:ss')");
                            }
                            if (!StringUtils.isEmpty(maxVal)) {
                                sql.append(" and " + column + " < to_date( '" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) maxVal) + "','yyyy-mm-dd hh24:mi:ss')");
                            }
                        }
                    }
                    
                    //開啓in查詢
                    if (field.isAnnotationPresent(In.class)) {
                        //獲取要in的值
                        Field values = entity.getClass().getDeclaredField(field.getAnnotation(In.class).values());
                        values.setAccessible(true);
                        List<String> valuesList = (List<String>) values.get(entity);
                        if (valuesList != null && valuesList.size() > 0) {
                            String inValues = "";
                            for (String value : valuesList) {
                                inValues = inValues + "'" + value + "'";
                            }
                            sql.append(" and " + column + " in (" + escapeSql(inValues) + ")");
                        }
                    }
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

  2019-10-24補充:注意!咱們這屬於動態拼寫SQL,須要進行轉義防範SQL注入!框架

    /**
     * sql轉義
     */
    public static String escapeSql(String str) {
        if (str == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char src = str.charAt(i);
            switch (src) {
                case '\'':
                    sb.append("''");// hibernate轉義多個單引號必須用兩個單引號
                    break;
                case '\"':
                case '\\':
                    sb.append('\\');
                default:
                    sb.append(src);
                    break;
            }
        }
        return sb.toString();
    }

 

  測試與效果

public static void main(String[] args) {
    ResLog resLog = new ResLog();
    resLog.setLogId("id1");//等值查詢
    resLog.setResourceName("name1");//like查詢
    resLog.setOperationTimeStart(new Date());//日期區間查詢
    resLog.setOperationTimeEnd(new Date());
    ArrayList<String> list = new ArrayList<>();
    list.add("type1");
    list.add("type2");
    resLog.setOperationTypeList(list);//in查詢
    //在外面拼寫select * from 是爲了多表聯查時的狀況
    StringBuilder sql = new StringBuilder("select * from res_log where '1' = '1'");
    appendQueryColumns(resLog,sql);
    System.out.println(sql.toString());
}

 

  拼接結果:測試

select *
  from res_log
 where '1' = '1'
   and log_id = 'id1'
   and resource_name like '%name1%'
   and operation_type in ('type1''type2')
   and operation_time >
       to_date('2018-10-08 15:00:40', 'yyyy-mm-dd hh24:mi:ss')
   and operation_time <
       to_date('2018-10-08 15:00:40', 'yyyy-mm-dd hh24:mi:ss')

 

  後記

  甚至咱們能夠直接獲取實體對象對應的表名,直接在方法裏面拼出 select * from ,這樣就不須要在外面拼接這一句優化

//獲取實體對象對應的表名
String TableName = entity.getClass().getAnnotation(Table.class).name();
System.out.println(TableName);

   爲了優化SQL,通常咱們不建議select * from,而是須要查詢那些字段就拼出那些字段,例如:select log_id fromui

  可是若是數據表有一百個字段呢?一個個手動拼接就太傻了,所以寫了一個自動拼接字段的方法,支持配置忽略拼接的字段spa

    /**
     *
     * @param entity 實體對象
     * @param ignoreProperties 動態參數  忽略拼接的字段
     * @return sql
     */
    public static StringBuilder appendFields(Object entity, String... ignoreProperties) {
        StringBuilder sql = new StringBuilder();
        List<String> ignoreList = Arrays.asList(ignoreProperties);
        try {
            sql.append("select ");

            for (Field field : entity.getClass().getDeclaredFields()) {
                //獲取受權
                field.setAccessible(true);
                String fieldName = field.getName();//屬性名稱
                Object fieldValue = field.get(entity);//屬性的值
                //非臨時字段、非忽略字段
                if (!field.isAnnotationPresent(Transient.class) && !ignoreList.contains(fieldName)) {
                    //拼接查詢字段  駝峯屬性轉下劃線
                    sql.append(new PropertyNamingStrategy.SnakeCaseStrategy().translate(fieldName).toLowerCase()).append(" ").append(",");
                }
            }
            //處理逗號(刪除最後一個字符)
            sql.deleteCharAt(sql.length() - 1);

            String tableName = entity.getClass().getAnnotation(Table.class).name();
            sql.append("from ").append(tableName).append(" where '1' = '1' ");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return sql;
    }

  接着上面的main測試hibernate

public static void main(String[] args) {
    ResLog resLog = new ResLog();
    resLog.setLogId("id1");//等值查詢
    resLog.setResourceName("name1");//like查詢
    resLog.setOperationTimeStart(new Date());//日期區間查詢
    resLog.setOperationTimeEnd(new Date());
    ArrayList<String> list = new ArrayList<>();
    list.add("type1");
    list.add("type2");
    resLog.setOperationTypeList(list);//in查詢
    //動態拼接查詢字段
    StringBuilder sql = appendFields(resLog,"remark","operator");
    appendQueryColumns(resLog,sql);
    System.out.println(sql.toString());
}

  結果

select log_id,
       resource_type,
       resource_id,
       resource_name,
       resource_code,
       operation_type,
       operation_time,
       operator_id
  from RES_LOG
 where '1' = '1'
   and log_id = 'id1'
   and resource_name like '%name1%'
   and operation_type in ('type1''type2')
   and operation_time >
       to_date('2018-12-13 10:34:33', 'yyyy-MM-dd hh24:mi:ss')
   and operation_time <
       to_date('2018-12-13 10:34:33', 'yyyy-MM-dd hh24:mi:ss')
相關文章
相關標籤/搜索