項目中雖然有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')