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