目录

  • 1. 前言
  • 2. 演示
  • 3. 原理


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也是一样的,只是上面的一些拼接的字符不同,原理完全一样,只要调试次数够多,肯定能达到自己想要的效果。