使用StringBuilder动态拼接SQL语句

1. 前言

我们在使用sql查询数据库的时候,如果每一个sql都要自己写,这样就很麻烦。这里不考虑联合查询、子查询、分页等复杂方法。实际上在没有使用框架之前,我们如果使用普通的增删改查来操作数据库,直接传一个对象,使用sql拼接会更好。这样就减少了sql语句在代码中出现的次数,代码更加简洁。
比如,当我们想要操作select语句的时候,我们需要什么条件就在entity里面设置对应的属性为什么条件,我们需要where Id=1, 那我们就可以设置User类里面的id为1。然后使用工具类拼接成sql语句。


2. 演示

为了方便演示,这里我就使用JDBCTemplate进行操作sql。在拼接的过程中,会基于反射来操作。

/**
		实体类:MyUser,封装对象
		这里使用注解和把get、set、toString、构造器写出来效果是	一样的。
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MyUser {
    private int id;
    private String name;
    private Integer age;
    private String email;

}
/**
		拼接sql的方法
*/
  public static <T>  String seek(T entity) throws Exception{
        List<String> list = new LinkedList<>();
        //创建拼接对象
        StringBuilder sbd = new StringBuilder();
        sbd.append("select ");
        //获取当前传入的对象的类
        Class<?> aClass = entity.getClass();
        //获取当前类名
        String className = MyStringUtil.getObj(String.valueOf(aClass));
        //再将类名转化为DB下的表名
        String classNameTODBName = MyStringUtil.classNameReverseToDBName(className);
        //获取当前传入的类的属性值
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            //获取当前类的特点属性的名字
            String fieldName = declaredFields[i].getName();
            //对fieldName进行转化为数据库的_命名规范
            String dbName = MyStringUtil.fieldNameReverseToDBName(fieldName);
            //调用get方法获取对象的fieldName的值
            String field = MyStringUtil.getMethodByName(entity.getClass(), entity, fieldName);
            //对getFieldByMethod进行判断,如果是int属性,未赋值就是0,char[]或者text就是null或者""
            if (!(field.equals("null") || field.equals(" ") || field.equals("0"))) {
                //存入list中作为查询条件
                list.add(dbName + "= '" + field + "'");
            }
            if (i != declaredFields.length - 1) {
                sbd.append(dbName + " " + fieldName + ", ");
            } else {
                sbd.append(dbName + " " + fieldName + " ");
            }
        }
        sbd.append(" from `" + classNameTODBName + "` ");
        if(list.size() == 0){
            //如果没有条件。就直接返回全部
            return sbd.toString();
        }
        sbd.append("where ");
        //把list中的数据提取出来进行拼接
        for (int i = 0; i < list.size(); i++) {
            if (i != list.size() - 1) {
                sbd.append(list.get(i) + " and ");
            } else {
                sbd.append(list.get(i));
            }
        }
        return sbd.toString();
    }
/**
		这是一些工具类
*/
public class MyStringUtil {
    //存储要获取的信息
    public static List<Object> getMSG = new LinkedList<>();
    //拼接字符串
    public static StringBuilder sql = new StringBuilder();

    //数据库字段和entity实体类的转化

    /**
     * @param db 传入的数据
     * @return 返回转化后的数据
     */
    public static String dbNameReverseToFieldName(String db) {
        if (db == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        //分割
        String[] s = db.split("_");
        for (int i = 0; i < s.length; i++) {
            if (i != 0) {
                s[i] = s[i].toLowerCase();
            }
            if (i == 0) {
                //拼接
                sb.append(s[0]);
            } else {
                //拼接
                sb.append(s[i].substring(0, 1).toUpperCase()).append(s[i].substring(1));
            }
        }
        return sb.toString();
    }

    /**
     * @param db 实体类对应的值
     * @return 返回转化后的数据库名称
     */
    public static String fieldNameReverseToDBName(String db) {
        if (db == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        int dbLength = db.length();
        for (int i = 0; i < dbLength; i++) {
            //获取字符
            char c = db.charAt(i);
            //判断
            if (c >= 'A' && c <= 'Z') {
                //遇到大写,就把全部的变小写
                sb.append("_").append((char) (c + 32));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * @param db 数据库的表的名字
     * @return
     * @Descripton 这个方法用来将数据库表名转化为规范的类名
     */
    public static String dbNameReverseToClassName(String db) {
        if (db == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        //分割
        String[] s = db.split("_");
        for (int i = 0; i < s.length; i++) {
            sb.append(s[i].substring(0, 1).toUpperCase()).append(s[i].substring(1));
        }
        return sb.toString();
    }

    /**
     * @param db
     * @return
     * @Description 将实体类类名转化为数据库的表名
     */
    public static String classNameReverseToDBName(String db) {
        if (db == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < db.length(); i++) {
            char c = db.charAt(i);
            if (c >= 'A' && c <= 'Z') {
                //转化为小写
                c = (char) (c + 32);
                //如果大写字母不是在首个,加_
                if (i != 0) {
                    sb.append('_');
                }
                sb.append(c);
                continue;
            }
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * @Description 获取反射类和属性
     * @param field
     * @return
     */
    public static String getObj(String field){
        if(field == null){
            return null;
        }
        String[] split = field.split("\\.");
        return split[split.length - 1];
    }

    /**
     * @Description 通过属性名字调用属性方法获取对应的属性值
     * @param name 传入的反射类获取的属性
     * @return 返回获取的属性值
     */
    public static String getMethodByName(Class clazz, Object object, String name) throws Exception {
        //得到get方法
        String fieldName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
        Method getMethod = clazz.getMethod(fieldName);
        //执行get方法,把属性全部转化为String
        String getFieldByMethod = String.valueOf( getMethod.invoke(object));
        return getFieldByMethod;
    }

    /**
     * 把传入的参数转化为set的String类型返回
     * @param name 传入的参数
     * @return 返回该参数的set方法的方法名
     */
    public static String toSet(String name){
        String setName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
        return setName.trim();
    }

    public static String getBriefMethodByName(String name){
        String[] split = name.split(".");
        if(split.length == 0){
            return null;
        }
        return split[split.length - 1];
    }

    /**
     * 解决传入数据库/丢失问题
     * @param path 原来的路径
     * @return
     */
    public static String imgsPath(String path){
        StringBuilder bd = new StringBuilder();
        for(int i = 0; i < path.length(); i++){
            char c = path.charAt(i);
            if(c == '\\'){
                //c是\字符
                bd.append('/');
            }else{
                bd.append(c);
            }
        }
        return bd.toString();
    }
}



测试+结果:

     @Test
    public void test1() throws Exception{
        //测试1:只有一个新的对象,没有设置参数,相当于没有where条件,会返回全部数据
        MyUser myUser = new MyUser();
        String sql = SQLUtils.seek(myUser);
        List<MyUser> query = jdbcTemplate.query(sql, new Object[]{}, new BeanPropertyRowMapper<MyUser>(MyUser.class));
        for (MyUser user : query) {
            System.out.println(user);
            //MyUser(id=1, name=Jone, age=18, email=test1@baomidou.com)
            //MyUser(id=2, name=Jack, age=20, email=test2@baomidou.com)
            //MyUser(id=3, name=Tom, age=28, email=test3@baomidou.com)
            //MyUser(id=4, name=Sandy, age=21, email=test4@baomidou.com)
            //MyUser(id=5, name=Billie, age=24, email=test5@baomidou.com)
        }

        //测试2 带条件的sql语句
        MyUser myUser2 = new MyUser();
        myUser2.setId(1);
        String sql1 = SQLUtils.seek(myUser2);
        List<MyUser> result = jdbcTemplate.query(sql1, new Object[]{}, new BeanPropertyRowMapper<MyUser>(MyUser.class));
        System.out.println(result);
        //[MyUser(id=1, name=Jone, age=18, email=test1@baomidou.com)]
    }

可以看到的是,我们在对象中set什么数据,就会用什么数据作为查询条件。这就达到了我们想要的效果,以后使用sql的时候只需要传入一个实体类,并且在实体类中添加查询的条件就可以了。比如想要查询id为1的,就把实体类中的id设置为1。

3. 原理

反射+拼接

 public static <T>  String seek(T entity) throws Exception{
        List<String> list = new LinkedList<>();
        //创建拼接对象
        StringBuilder sbd = new StringBuilder();
        //首先以select开头,接下来拼接
        sbd.append("select ");
        //获取当前传入的对象的类
        Class<?> aClass = entity.getClass();
        //获取当前类名,因为表名和类名是对应的
        String className = MyStringUtil.getObj(String.valueOf(aClass));
        //再将类名转化为数据库下的表名,驼峰命名法
        String classNameTODBName = MyStringUtil.classNameReverseToDBName(className);
        //获取当前传入的类的属性
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            //获取当前类的属性的名字
            String fieldName = declaredFields[i].getName();
            //把fieldName进行转化为数据库的名字,驼峰命名
            String dbName = MyStringUtil.fieldNameReverseToDBName(fieldName);
            //调用get方法获取对象的fieldName的值
            String field = MyStringUtil.getMethodByName(entity.getClass(), entity, fieldName);
            //对getFieldByMethod进行判断,如果是int属性,未赋值就是0,char[]或者text就是null或者"",这里根据自己的业务需求,我这里是把数字0和""和null作为为输入
            if (!(field.equals("null") || field.equals(" ") || field.equals("0"))) {
                //存入list中作为查询条件,到后面进行拼接
                list.add(dbName + "= '" + field + "'");
            }
            //添加要查询的对象,也就是from前面的那一部分
            if (i != declaredFields.length - 1) {
                sbd.append(dbName + " " + fieldName + ", ");
            } else {
            	//如果到了最后一个参数就不加,
                sbd.append(dbName + " " + fieldName + " ");
            }
        }
        //遍历完之后拼接from 表名
        sbd.append(" from `" + classNameTODBName + "` ");		
        //判断实体类有没有赋值,如果没有就是不要看条件直接查询出全部的
        if(list.size() == 0){
            return sbd.toString();
        }
        //添加 where 
        sbd.append("where ");
        //把list中的数据提取出来进行拼接
        for (int i = 0; i < list.size(); i++) {
            if (i != list.size() - 1) {
                sbd.append(list.get(i) + " and ");
            } else {
            	//如果是最后一个条件,就不用加and了
                sbd.append(list.get(i));
            }
        }
        return sbd.toString();
    }

其实原理很简单,用了反射把传入的实体类的对应的属性和属性值拿出来进行拼接。在拼接的时候要注意是不是最后一个条件。上面只展示了select,对于insert、delete、update也是一样的,只是上面的一些拼接的字符不同,原理完全一样,只要调试次数够多,肯定能达到自己想要的效果。

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Java中,动态拼接SQL语句使用圆括号非常简单。我将为您提供一个示例以展示具体如何实现。 假设我们有一个用户表,需要根据不同的条件动态生成SQL查询语句。我们希望根据用户的姓名、年龄和性别进行查询,其中姓名和年龄为必填项,而性别为可选项。 首先,我们需要定义一个StringBuilder对象,用于拼接SQL语句。接着,根据用户的输入,我们可以使用条件语句来动态拼接SQL语句。 以下是示例代码: ```java StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM user WHERE "); String name = "张三"; int age = 20; String gender = "男"; sqlBuilder.append("name = '").append(name).append("' AND "); sqlBuilder.append("age = ").append(age).append(" AND "); if (gender != null && !gender.isEmpty()) { sqlBuilder.append("gender = '").append(gender).append("' AND "); } // 去除最后一个AND String sql = sqlBuilder.toString().substring(0, sqlBuilder.length() - 5); System.out.println(sql); ``` 以上代码中,我们首先定义了一个StringBuilder对象`sqlBuilder`,并使用初始SQL查询语句"SELECT * FROM user WHERE "进行初始化。然后,我们根据传入的参数动态拼接SQL语句,将条件逐个追加到`sqlBuilder`中。最后,我们使用`substring`方法去除最后一个多余的"AND",然后将拼接好的SQL语句保存在`sql`变量中,并打印输出。 根据上述示例,您可以根据具体需求动态拼接SQL语句,包括使用圆括号进行条件组合。 ### 回答2: Java中可以使用字符串拼接动态生成SQL语句,并且可以通过添加圆括号来确保SQL语句的正确性和逻辑性。 在拼接SQL语句时,需要将字符串常量和变量进行组合。可以使用加号 (+) 运算符将它们连接起来。例如,假设有一个变量name用于存储要查询的姓名,可以通过以下方式拼接SQL语句并添加圆括号: String query = "SELECT * FROM users WHERE (name = '" + name + "')"; 在上述代码中,将name变量与SQL语句的其他部分进行连接,同时在变量所在位置添加了单引号,确保生成的SQL语句的正确性。此外,还在name = 'xxx' 这一条件的外部添加了圆括号,以便在多个条件的情况下保持逻辑正确。 需要注意的是,使用字符串拼接的方式来构建SQL语句虽然方便,但也存在一定的安全隐患,容易被SQL注入攻击。为了避免这种情况,建议使用预编译语句或ORM框架,如MyBatis等,它们可以在执行SQL语句之前对参数进行安全处理,提高应用程序的安全性。以下是一个使用预编译语句的示例: String query = "SELECT * FROM users WHERE (name = ?)"; PreparedStatement statement = connection.prepareStatement(query); statement.setString(1, name); 上述代码中,使用了占位符 '?' 来代替具体的参数值,然后通过setString方法设置参数的值。这样可以起到参数安全处理的作用,有效防止了SQL注入攻击。 ### 回答3: 在Java中,动态拼接SQL语句时,有时候需要在语句中使用圆括号来改变运算符的优先级或者改变条件的逻辑关系。 一个常见的方法是使用String类的拼接功能来构建SQL语句。例如,假设我们有一个字符串变量query用于存储拼接后的SQL语句。我们可以使用加号(+)操作符来拼接字符串,然后使用括号将不同的条件组合在一起。 例如,假设我们要拼接一个SELECT语句,其中包含两个条件。第一个条件是age大于等于18,第二个条件是gender为“男性”。我们可以这样拼接: String query = "SELECT * FROM users WHERE (age >= 18) AND (gender = '男性')"; 在这个例子中,我们使用了括号来确保age >= 18的条件在gender = '男性'之前被评估。这样可以确保正确的条件逻辑,并确保SQL语句的准确性。 当然,这只是一个简单的例子。实际应用中,我们可能需要动态拼接SQL语句,根据不同的条件和参数生成不同的查询语句。在这种情况下,我们可以借助StringBuilder类来更高效地拼接字符串。使用StringBuilder类,我们可以动态地添加和删除字符串片段,从而根据需要构建SQL语句。 总之,在Java动态拼接SQL语句时,使用圆括号可以帮助我们更好地控制条件逻辑,并确保SQL语句的正确性和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值