前言

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

/**
* 自动拼接原生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 '' = ''
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 from

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

    /**
*
* @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测试

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 '' = ''
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')

利用反射跟自定义注解拼接实体对象的查询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. Python数据结构之单链表

    Python数据结构之单链表 单链表有后继结点,无前继结点. 以下实现: 创建单链表 打印单链表 获取单链表的长度 判断单链表是否为空 在单链表后插入数据 获取单链表指定位置的数据 获取单链表指定元素 ...

  2. python装饰器同时支持有参数和无参数的练习题

    ''' 预备知识: …… @decorator def f(*args,**kwargs): pass # 此处@decorator  等价于 f = decorator(f) @decorator2 ...

  3. bzoj4445(半平面交)

    列出式子对一下然后上半平面交 #include<iostream> #include<cstring> #include<cmath> #include<cs ...

  4. python基础自学 第四天

    break和continue break:某一条件满足,退出循环,不在执行后续重复代码 continue:某一条件满足时,不执行后续重复的代码 注意:在循环中,如果使用continue这个关键字,使用 ...

  5. Reader和Writer

  6. Django 信号、中间件、i18n 专题

    信号 Django中提供了“信号调度”,用于在框架执行操作时解耦.通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者. 1. Django内置信号 Model signals pr ...

  7. JSON Web Token(JWT)使用步骤说明

    在JSON Web Token(JWT)原理和用法介绍中,我们了解了JSON Web Token的原理和用法的基本介绍.本文我们着重讲一下其使用的步骤: 一.JWT基本使用 Gradle下依赖 : c ...

  8. 微博第三方登录使用social_django实现显示登陆的用户名

    首先修改social_soce源码,将用户信息添加进cookie 将其修改为:  response =  backend.strategy.redirect(url)    payload = jwt ...

  9. 线程池工厂Executors编程的艺术

    Executors是一个线程池的工厂类,提供各种有用的线程池的创建,使用得当,将会使我们并发编程变得简单!今天就来聊聊这个工厂类的艺术吧! Executors只是Executor框架的主要成员组件之一 ...

  10. spring中的mybatis的sqlSession是如何做到线程隔离的?

    项目中常常使用mybatis配合spring进行数据库操作,但是我们知道,数据的操作是要求做到线程安全的,而且按照原来的jdbc的使用方式,每次操作完成之后都要将连接关闭,但是实际使用中我们并没有这么 ...