前言

  项目中虽然有ORM映射框架来帮我们拼写SQL,简化开发过程,降低开发难度。但难免会出现需要自己拼写SQL的情况,这里分享一个利用反射跟自定义注解拼接实体对象的查询SQL的方法。

  代码

  自定义注解:

  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Like {
  4.  
  5. }
  6.  
  7. @Target(ElementType.FIELD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface Between {
  10.  
  11. /**
  12. * 最小值的实体属性名
  13. */
  14. String min();
  15.  
  16. /**
  17. * 最大值的实体属性名
  18. */
  19. String max();
  20. }
  21.  
  22. @Target(ElementType.FIELD)
  23. @Retention(RetentionPolicy.RUNTIME)
  24. public @interface In {
  25.  
  26. /**
  27. * in的具体集合的属性名
  28. */
  29. String values();
  30. }

  实体对象:

  1. @Data
  2. @Entity
  3. @Table(name = "RES_LOG")
  4. public class ResLog {
  5. @Id
  6. private String logId;
  7. private String resourceType;
  8. private String resourceId;
  9. @Like //开启模糊查询
  10. private String resourceName;
  11. private String resourceCode;
  12. @In(values = "operationTypeList")//in查询
  13. private String operationType;
  14. @Between(min = "operationTimeStart", max = "operationTimeEnd")//开启区间查询
  15. private Date operationTime;
  16. private String operatorId;
  17. private String operator;
  18.  
  19. @Transient
  20. private Date operationTimeStart;
  21. @Transient
  22. private Date operationTimeEnd;
  23. @Transient
  24. private List<String> operationTypeList;
  25.  
  26. }

  拼接SQL方法:

  1. /**
  2. * 自动拼接原生SQL的“and”查询条件,支持自定义注解:@Like @Between @In
  3. *
  4. * @param entity 实体对象
  5. * @param sql 待拼接SQL
  6. * @param ignoreProperties 忽略属性
  7. */
  8. public static void appendQueryColumns(Object entity, StringBuilder sql, String... ignoreProperties) {
  9.  
  10. try {
  11. //忽略属性
  12. List<String> ignoreList1 = Arrays.asList(ignoreProperties);
  13. //默认忽略分页参数
  14. List<String> ignoreList2 = Arrays.asList("class", "pageable", "page", "rows", "sidx", "sord");
  15.  
  16. //反射获取Class的属性(Field表示类中的成员变量)
  17. for (Field field : entity.getClass().getDeclaredFields()) {
  18. //获取授权
  19. field.setAccessible(true);
  20. //属性名称
  21. String fieldName = field.getName();
  22. //属性的值
  23. Object fieldValue = field.get(entity);
  24. //检查Transient注解,是否忽略拼接
  25. if (!field.isAnnotationPresent(Transient.class)) {
  26. String column = new PropertyNamingStrategy.SnakeCaseStrategy().translate(fieldName).toLowerCase();
  27. //值是否为空
  28. if (!StringUtils.isEmpty(fieldValue)) {
  29. //映射关系:对象属性(驼峰)->数据库字段(下划线)
  30. if (!ignoreList1.contains(fieldName) && !ignoreList2.contains(fieldName)) {
  31. //开启模糊查询
  32. if (field.isAnnotationPresent(Like.class)) {
  33. sql.append(" and " + column + " like '%" + escapeSql(fieldValue) + "%'");
  34. }
  35. //开启等值查询
  36. else {
  37. sql.append(" and " + column + " = '" + escapeSql(fieldValue) + "'");
  38. }
  39. }
  40. } else {
  41. //开启区间查询
  42. if (field.isAnnotationPresent(Between.class)) {
  43. //获取最小值
  44. Field minField = entity.getClass().getDeclaredField(field.getAnnotation(Between.class).min());
  45. minField.setAccessible(true);
  46. Object minVal = minField.get(entity);
  47. //获取最大值
  48. Field maxField = entity.getClass().getDeclaredField(field.getAnnotation(Between.class).max());
  49. maxField.setAccessible(true);
  50. Object maxVal = maxField.get(entity);
  51. //开启区间查询
  52. if (field.getType().getName().equals("java.util.Date")) {
  53. if (!StringUtils.isEmpty(minVal)) {
  54. sql.append(" and " + column + " > to_date( '" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) minVal) + "','yyyy-mm-dd hh24:mi:ss')");
  55. }
  56. if (!StringUtils.isEmpty(maxVal)) {
  57. sql.append(" and " + column + " < to_date( '" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) maxVal) + "','yyyy-mm-dd hh24:mi:ss')");
  58. }
  59. }
  60. }
  61.  
  62. //开启in查询
  63. if (field.isAnnotationPresent(In.class)) {
  64. //获取要in的值
  65. Field values = entity.getClass().getDeclaredField(field.getAnnotation(In.class).values());
  66. values.setAccessible(true);
  67. List<String> valuesList = (List<String>) values.get(entity);
  68. if (valuesList != null && valuesList.size() > 0) {
  69. String inValues = "";
  70. for (String value : valuesList) {
  71. inValues = inValues + "'" + value + "'";
  72. }
  73. sql.append(" and " + column + " in (" + escapeSql(inValues) + ")");
  74. }
  75. }
  76. }
  77. }
  78. }
  79. } catch (Exception e) {
  80. e.printStackTrace();
  81. }
  82. }

  2019-10-24补充:注意!我们这属于动态拼写SQL,需要进行转义防范SQL注入!

  1. /**
  2. * sql转义
  3. */
  4. public static String escapeSql(String str) {
  5. if (str == null) {
  6. return null;
  7. }
  8. StringBuilder sb = new StringBuilder();
  9. for (int i = 0; i < str.length(); i++) {
  10. char src = str.charAt(i);
  11. switch (src) {
  12. case '\'':
  13. sb.append("''");// hibernate转义多个单引号必须用两个单引号
  14. break;
  15. case '\"':
  16. case '\\':
  17. sb.append('\\');
  18. default:
  19. sb.append(src);
  20. break;
  21. }
  22. }
  23. return sb.toString();
  24. }

  测试与效果

  1. public static void main(String[] args) {
  2. ResLog resLog = new ResLog();
  3. resLog.setLogId("id1");//等值查询
  4. resLog.setResourceName("name1");//like查询
  5. resLog.setOperationTimeStart(new Date());//日期区间查询
  6. resLog.setOperationTimeEnd(new Date());
  7. ArrayList<String> list = new ArrayList<>();
  8. list.add("type1");
  9. list.add("type2");
  10. resLog.setOperationTypeList(list);//in查询
  11. //在外面拼写select * from 是为了多表联查时的情况
  12. StringBuilder sql = new StringBuilder("select * from res_log where '1' = '1'");
  13. appendQueryColumns(resLog,sql);
  14. System.out.println(sql.toString());
  15. }

  拼接结果:

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

  后记

  甚至我们可以直接获取实体对象对应的表名,直接在方法里面拼出 select * from ,这样就不需要在外面拼接这一句

  1. //获取实体对象对应的表名
  2. String TableName = entity.getClass().getAnnotation(Table.class).name();
  3. System.out.println(TableName);

  为了优化SQL,一般我们不建议select * from,而是需要查询那些字段就拼出那些字段,例如:select log_id from

  但是如果数据表有一百个字段呢?一个个手动拼接就太傻了,因此写了一个自动拼接字段的方法,支持配置忽略拼接的字段

  1. /**
  2. *
  3. * @param entity 实体对象
  4. * @param ignoreProperties 动态参数 忽略拼接的字段
  5. * @return sql
  6. */
  7. public static StringBuilder appendFields(Object entity, String... ignoreProperties) {
  8. StringBuilder sql = new StringBuilder();
  9. List<String> ignoreList = Arrays.asList(ignoreProperties);
  10. try {
  11. sql.append("select ");
  12.  
  13. for (Field field : entity.getClass().getDeclaredFields()) {
  14. //获取授权
  15. field.setAccessible(true);
  16. String fieldName = field.getName();//属性名称
  17. Object fieldValue = field.get(entity);//属性的值
  18. //非临时字段、非忽略字段
  19. if (!field.isAnnotationPresent(Transient.class) && !ignoreList.contains(fieldName)) {
  20. //拼接查询字段 驼峰属性转下划线
  21. sql.append(new PropertyNamingStrategy.SnakeCaseStrategy().translate(fieldName).toLowerCase()).append(" ").append(",");
  22. }
  23. }
  24. //处理逗号(删除最后一个字符)
  25. sql.deleteCharAt(sql.length() - 1);
  26.  
  27. String tableName = entity.getClass().getAnnotation(Table.class).name();
  28. sql.append("from ").append(tableName).append(" where '1' = '1' ");
  29. } catch (IllegalAccessException e) {
  30. e.printStackTrace();
  31. }
  32. return sql;
  33. }

  接着上面的main测试

  1. public static void main(String[] args) {
  2. ResLog resLog = new ResLog();
  3. resLog.setLogId("id1");//等值查询
  4. resLog.setResourceName("name1");//like查询
  5. resLog.setOperationTimeStart(new Date());//日期区间查询
  6. resLog.setOperationTimeEnd(new Date());
  7. ArrayList<String> list = new ArrayList<>();
  8. list.add("type1");
  9. list.add("type2");
  10. resLog.setOperationTypeList(list);//in查询
  11. //动态拼接查询字段
  12. StringBuilder sql = appendFields(resLog,"remark","operator");
  13. appendQueryColumns(resLog,sql);
  14. System.out.println(sql.toString());
  15. }

  结果

  1. select log_id,
  2. resource_type,
  3. resource_id,
  4. resource_name,
  5. resource_code,
  6. operation_type,
  7. operation_time,
  8. operator_id
  9. from RES_LOG
  10. where '' = ''
  11. and log_id = 'id1'
  12. and resource_name like '%name1%'
  13. and operation_type in ('type1''type2')
  14. and operation_time >
  15. to_date('2018-12-13 10:34:33', 'yyyy-MM-dd hh24:mi:ss')
  16. and operation_time <
  17. to_date('2018-12-13 10:34:33', 'yyyy-MM-dd hh24:mi:ss')

利用反射跟自定义注解拼接实体对象的查询SQL的更多相关文章

  1. java 利用反射完成自定义注解

    元注解: 元注解的作用就是负责注解其他注解.Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明.Java5.0定义的元注解: 1.@ ...

  2. java反射与注解结合使用(根据传入对象输出查询sql)

    我们在项目开发中有很多地方使用到了注解,关于注解的定义与创建小伙伴可以参考我的文章<java注解>.有任何问题的小伙伴们可以在评论区指出哦,欢迎各位大佬指出问题. 今天我要说的是使用注解与 ...

  3. Android面试基础(一)IOC(DI)框架(ViewUtils)讲解_反射和自定义注解类

    1. Android中的IOC(DI)框架 1.1 ViewUtils简介(xUtils中的四大部分之一) IOC: Inverse of Controller 控制反转. DI: Dependenc ...

  4. java 中利用反射机制获取和设置实体类的属性值

    摘要: 在java编程中,我们经常不知道传入自己方法中的实体类中到底有哪些方法,或者,我们需要根据用户传入的不同的属性来给对象设置不同的属性值,那么,java自带的反射机制可以很方便的达到这种目的,同 ...

  5. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  6. (转)利用Spring AOP自定义注解解决日志和签名校验

    一.需解决的问题 部分API有签名参数(signature),Passport首先对签名进行校验,校验通过才会执行实现方法. 第一种实现方式(Origin):在需要签名校验的接口里写校验的代码,例如: ...

  7. Java反射与自定义注解

    反射,在Java常用框架中屡见不鲜.它存在于java.lang.reflact包中,就我的认识,它可以拿到类的字段和方法,及构造方法,还可以生成对象实例等.对深入的机制我暂时还不了解,本篇文章着重在使 ...

  8. Java 自定义注解实现ORM对象关系映射

    一,ORM概念 ORM即Object Relation Mapping,Object就是对象,Relation就是关系数据库,Mapping映射,就是说Java中的对象和关系数据库中的表存在一种对应关 ...

  9. C# winform利用反射和自定义特性加载功能模块(插件式开发)

    由于在实际的工作中, 碰见这样的一个问题: 一个软件, 销售给A客户 他需要所有功能, 但是销售给B客户, 他只需要其中的一部分, 1.如果我们在实际的开发过程中, 没有把一些功能模块区分开来的话, ...

随机推荐

  1. SQL 经典应用

    SQL Server日常维护常用的一些脚本整理. 1.sql server开启clr权限: exec sp_configure 'clr enabled', 1 GO RECONFIGURE GO A ...

  2. 【转载】row cache lock

    转自:http://blog.itpub.net/26736162/viewspace-2139754/   定位的办法: --查询row cache lock等待 select event,p1   ...

  3. Jenkins关闭和重启实现方式.

    1.关闭Jenkins 只需要在访问jenkins服务器的网址url地址后加上exit.例如我jenkins的地址http://localhost:8080/,那么我只需要在浏览器地址栏上敲下http ...

  4. 一键访问Google和YouTube等国外知名网站

    1.首先打开快速安全通道网站,点击注册!网站地址 https://www.faststunnel.xyz/GWx6uy0M 2.注册好之后下载插件 3.将插件下载完后拖到浏览器安装 4.登录插件,即可 ...

  5. Django富文本需要添加配置

    TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'width': 600, 'height': 400, }

  6. 微信小程序web-view实例

    微信小程序web-view实例 index.js //index.js //获取应用实例 const app = getApp() Page({ /** * 页面的初始数据 */ data: { }, ...

  7. 第三节:带你详解Java的操作符,控制流程以及数组

    前言 大家好,给大家带来带你详解Java的操作符,控制流程以及数组的概述,希望你们喜欢 操作符 算数操作符 一般的 +,-,*,/,还有两个自增 自减 ,以及一个取模 % 操作符. 这里的操作算法,一 ...

  8. Springboot 前后端数据传输 常见误区

    一 content-Type代表的是,传输数据的编码方式 当ajax,JS向后台发起请求的时候,常常会设置content-type,告知服务器前台传输的数据是什么编码方式 1 application/ ...

  9. NIO/BIO

    NIO/BIO    BIO网络通信        概述            网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地 ...

  10. thymeleaf-在font标签中的使用

    <font color="red" th:text="开始了">font外</font>页面显示红色字体 开始了 (同时存在,则前者覆盖 ...