JAVA jdbc封装 DbUtil 类3.0:DbUtilsTag -使用 #{} 占位符(废弃) 作者:马育民 • 2022-03-11 09:51 • 阅读:10100 # 废弃 本文已废弃,改为:[JAVA jdbc封装 DbUtil 类3.1:DbUtilsTag -使用 #{} 占位符](https://www.malaoshi.top/show_1IX2ymNtgfKD.html "JAVA jdbc封装 DbUtil 类3.1:DbUtilsTag -使用 #{} 占位符") # 说明 在 [JAVA jdbc封装 DbUtil 类2.0-增加事务功能](https://www.malaoshi.top/show_1IX2iXvrIKqt.html "JAVA jdbc封装 DbUtil 类2.0-增加事务功能") 中,已经实现了功能,但 **缺点是**,当 SQL 参数过多时,会有 **n 个 ?**,传入的参数会很多,容易出现错误 # 解决 ### SQL 占位符 使用 `#{}` 占位符,如下: ``` update emp set ename=#{ename},job=#{job},sal=#{sal},comm=#{comm} where empno=#{empno} ``` ``` select empno,ename,job,sal,comm,deptno from emp where #{sal} < sal and sal < #{maxSal} ``` ### 传参 传入 `Map` 类型,`key` 是 `#{}` 中的 值,如下: 上面第一个SQL参数: ``` Map params=new HashMap<>(); params.put("ename", "李雷2"); params.put("job", "程序员2"); params.put("sal", "20000"); params.put("comm", "142"); params.put("empno", "10008"); ``` 上面第二个SQL参数: ``` Map<String,Object> params=new HashMap<>(); params.put("sal", 2500); params.put("maxSal", 4000); ``` # 完整代码 ### 核心类 继承 [JAVA jdbc封装 DbUtil 类2.0-增加事务功能](https://www.malaoshi.top/show_1IX2iXvrIKqt.html "JAVA jdbc封装 DbUtil 类2.0-增加事务功能") 中的类 ``` import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * * 数据库工具类,使用 #{} 占位符 * @author mym * */ public class DbUtilsTag extends DbUtils2{ /** * sql语句使用 #{} 占位符 * @param sql * @param params * @return * @throws SQLException */ public int updateTag(String sql , Map params ) throws SQLException{ TagSQLParser p=new TagSQLParser(sql); String selectSQL=p.parse(); List<String> keywords=p.getKeywords(); PreparedStatement pstm=conn.prepareStatement(selectSQL); for(int i=0; i<keywords.size(); i++){ String keyword=keywords.get(i); Object param=params.get(keyword ); pstm.setObject(i+1, param); } int i=pstm.executeUpdate();// pstm.close();//要及时关闭pstm return i; } /** * * sql语句使用 #{} 占位符 * @param sql * @param paras * @return List,里面的元素是Map * @throws SQLException */ public List queryTag(String sql,Map<String, Object> params) throws SQLException{ TagSQLParser p=new TagSQLParser(sql); String selectSQL=p.parse(); List<String> keywords=p.getKeywords(); PreparedStatement pstm=conn.prepareStatement(selectSQL); for(int i=0; i<keywords.size(); i++){ String keyword=keywords.get(i); Object param=params.get(keyword ); pstm.setObject(i+1, param); } ResultSet rs=pstm.executeQuery(); ResultSetMetaData rsmd=rs.getMetaData(); int count=rsmd.getColumnCount();//动态返回查询的列数 //ret是return的缩写,表示这个list是要返回的 List retList=new ArrayList(); while(rs.next()){// 判断是否有查询结果,刚学的人,问:为啥这么写,没有原因,java当初就是这么设计的 Map map=new LinkedHashMap<>(); for(int i=0 ; i<count ; i++ ){ map.put(rsmd.getColumnLabel(i+1), rs.getObject(i+1)); } //将数组放入到List中 retList.add(map); } //查询后要释放资源 rs.close(); pstm.close(); return retList; } /** * sql语句使用 #{} 占位符 * @param sql * @param params * @param clazz * @return * @throws SQLException * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ public <T> List<T> queryTag(String sql,Map<String, Object> params,Class<T> clazz) throws SQLException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ TagSQLParser p=new TagSQLParser(sql); String selectSQL=p.parse(); List<String> keywords=p.getKeywords(); PreparedStatement pstm=conn.prepareStatement(selectSQL); for(int i=0; i<keywords.size(); i++){ String keyword=keywords.get(i); Object param=params.get(keyword ); pstm.setObject(i+1, param); } ResultSet rs=pstm.executeQuery(); List retList=handlResultSet(rs,clazz); //查询后要释放资源 rs.close(); pstm.close(); return retList; } /** * 处理结果集 * @param rs * @param clazz * @return * @throws SQLException * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ private <T> List<T> handlResultSet(ResultSet rs,Class<T> clazz) throws SQLException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ ResultSetMetaData rsm=rs.getMetaData(); int count=rsm.getColumnCount();//动态返回查询的列数 //ret是return的缩写,表示这个list是要返回的 List retList=new ArrayList(); while(rs.next()){// 判断是否有查询结果,刚学的人,问:为啥这么写,没有原因,java当初就是这么设计的 Object obj=clazz.newInstance(); Method[] setters=getSetterMethods(clazz); for(int i=0 ; i<count ; i++ ){ String label=rsm.getColumnLabel(i+1); Object data=rs.getObject(i+1); for(int j=0,setterLen=setters.length;j<setterLen;j++){ Method setter=setters[j]; String field=setter.getName().substring(3); if(label.equalsIgnoreCase(field)){ Object dataTemp=null; if ( data instanceof BigDecimal){ BigDecimal temp=(BigDecimal)data; dataTemp=temp.doubleValue(); }else{ dataTemp=data; } // System.out.println(setter.getName()+"---"+dataTemp); setter.invoke(obj, dataTemp); } } } retList.add(obj); } return retList; } /** * 获取setter方法 * @param clazz * @return */ Method[] getSetterMethods(Class clazz){ Method[] array=clazz.getDeclaredMethods(); List<Method> list=new ArrayList<>(); for(int i=0,len=array.length;i<len;i++){ String name=array[i].getName(); if(name.startsWith("set")){ list.add(array[i]); } } Method[] ret=new Method[list.size()]; list.toArray(ret); return ret; } } ``` ### TagSQLParser 解析器 ``` import java.util.ArrayList; import java.util.List; /** * 解析sql * @author mym * */ public class TagSQLParser { /** * 是否检测sql语句 */ private boolean isChecked=true; /** * 原SQL */ private char[] srcSQL; /** * #{} 中的关键词,放入该list */ private List<String> keywords; /** * debug模式,格式化后,可以打印sql */ private boolean debug=false; //格式化后的sql,去掉多余的空格、tab、\n private char[] formatSQL=null; private int formatSQLIndex=0;//当前位置 private int formatSQLLen=0; public TagSQLParser(String sql) { srcSQL=sql.toCharArray(); int len=srcSQL.length; if(len==0){ throw new TagSQLSyntaxException("请传入有效的SQL!"); } } /** * 给 formatBuf 填充字符 * @param c */ private void fillFormatBuf(char c){ formatSQL[formatSQLIndex]=c; formatSQLIndex++; } private void format(){ removeWhite(); } /** * 将 tab、\t、\n、\r\n 替换成 空格 * 将 #{ 后面的空格移除, } 前面的空格移除 * 当多个空格时,只保留一个空格 */ private void removeWhite(){ int srcLen=srcSQL.length; formatSQL=new char[srcLen]; char lastC=0; char c=0; int srcIndex=0; do{ c=srcSQL[srcIndex]; srcIndex++; if(c=='\r'){ //如果是windows换行符 \r,就丢弃,只认 \n continue; } if(isSpaceTabLine(lastC) ){ //上一次是空格、tab键、\n if( isSpaceTabLine(c) ){ //本次是空格、tab键、\n,就不填充字符 }else if( c == '}' ){//本次是 } ,就将上一个空格移除,并填充 } formatSQLIndex--; fillFormatBuf(c); }else{ fillFormatBuf(c); } }else if(lastC == '{'){ //上一次是 { if( isSpaceTabLine(c) ){ //本次是空格、tab键、\n,就不填充字符 }else{ fillFormatBuf(c); } }else{ //上一次不是空格、tab键、\n if( c == '\t' || c == '\n'){ //本次是 tab键、\n,就填充空格 fillFormatBuf(' '); }else{ fillFormatBuf(c); } } lastC=c; }while(srcIndex<srcLen); formatSQLLen=formatSQLIndex;//设置 formatBuf的有效长度 if(debug){ System.out.println("debug---format SQL:"+new String(formatSQL,0,formatSQLLen)); } formatSQLIndex=0; } /** * 检查语法,给出提示,规则如下: * 1. #后面必须跟着{,不能其他符号 * 2. #{ 后面可以有空格、\t符号、字母,不能有其他符号,也不能有 \n * @param sql * @return true表示正常;false表示不正常 */ private void check(){ formatSQLIndex=0; char curChar=0; do{ curChar=getFromFormatSQL(); if( curChar == '#'){ curChar=getFromFormatSQL(); if( curChar!='{'){ //#后面不是{,报错 String error=buildError(); throw new TagSQLSyntaxException(error); }else{ //#后面是{,正常 curChar=getFromFormatSQL();// { 后面的字符,由于已经格式化,所以不会是 空格 或 tab if( isLetter(curChar) ){ // { 后面第一个字符,必须是字母,否则报错,如:不能是 } do{ curChar=getFromFormatSQL(); //由于已经格式化,{ 后面不会是 空格 、tab 、\n if( isLetter(curChar) ){ // { 后面可以是 字母 }else if( curChar=='}' ){ //也可以是 } break; }else{ //其他符号报错 String error=buildError(); throw new TagSQLSyntaxException(error); } }while(formatSQLIndex<formatSQLLen); } else{ String error=buildError(); throw new TagSQLSyntaxException(error); } } } }while(formatSQLIndex<formatSQLLen); } /** * 构建错误提示消息 * @return */ private String buildError(){ int startPos=formatSQLIndex-8; int endPos=formatSQLIndex+8; if(startPos<0) startPos=0; if(endPos>formatSQLLen) endPos=formatSQLLen; String nearErrorInSQL=new String(formatSQL,startPos,endPos-startPos); String msg=" #{} 表达式错误,位置在 "+formatSQLIndex+" ,在 【"+nearErrorInSQL+"】 附近"; return msg; } /** * 从 FormatSQL 取出一个字符,并执行 formatSQLIndex++ * @return */ private char getFromFormatSQL(){ char ret=formatSQL[formatSQLIndex]; formatSQLIndex++; return ret; } /** * 判断是否字母 * @param c * @return */ private boolean isLetter(char c){ return Character.isLetter( c ); } /** * 是否 空格、tab、换行符 \n * 注意:不支持 windows换行符 \r\n * @param c * @return */ private boolean isSpaceTabLine(char c){ if( c==' ' || c=='\t' || c == '\n'){ return true; }else{ return false; } } public void setChecked(boolean isChecked) { this.isChecked = isChecked; } /** * 解析sql,将含有 #{} 表达式的sql,将其中的字符串解析出,放入到keywords中,然后将 #{} 替换成 ? * @param sql * @param keywords * @return 返回 带有 ? 的sql */ public String parse(){ format(); if(isChecked){ check(); } int sqlPos=0; char[] sql=new char[formatSQLLen]; keywords=new ArrayList<String>(); formatSQLIndex=0; char cur=0; int sqlStartPos=0; // 截取sql开始的位置 int tagStartPos=0; //#{的位置 int tagEndPos=0; // }的位置 int keywordStartPos=0; // #{} 中关键字开始的位置 int keywordEndPos=0; // #{} 中关键字结束的位置 do{ cur=getFromFormatSQL(); if( cur == '#'){ //遇到# cur=getFromFormatSQL(); if(cur=='{'){ //遇到 #{ tagStartPos=formatSQLIndex-2; // -2,是把 #{ 给减掉 int len=tagStartPos-sqlStartPos; System.arraycopy(formatSQL, sqlStartPos, sql, sqlPos, len); sqlPos=sqlPos+len; sql[sqlPos]=' '; sqlPos++; sql[sqlPos]='?'; sqlPos++; sql[sqlPos]=' '; sqlPos++; keywordStartPos=formatSQLIndex; // #{} 中关键字开始的位置 do{ cur=getFromFormatSQL(); if(cur=='}'){ keywordEndPos=formatSQLIndex-1; // #{} 中关键字结束的位置 tagEndPos=formatSQLIndex; sqlStartPos=formatSQLIndex; String keyword=new String(formatSQL,keywordStartPos,keywordEndPos-keywordStartPos); keywords.add(keyword); break; } }while(formatSQLIndex<formatSQLLen); } } }while(formatSQLIndex<formatSQLLen); int len=formatSQLLen-sqlStartPos; System.arraycopy(formatSQL, sqlStartPos, sql, sqlPos, len); sqlPos=sqlPos+len; return new String(sql,0,sqlPos); } public List<String> getKeywords() { return keywords; } public void setDebug(boolean debug) { this.debug = debug; } class TagSQLSyntaxException extends RuntimeException{ public TagSQLSyntaxException(String msg){ super(msg); } } } ``` # 测试 ``` import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; public class TestDbUtil3 { public void update() throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{ DbUtilsTag s=new DbUtilsTag(); s.getConn("com.mysql.jdbc.Driver" , "jdbc:mysql://127.0.0.1:3308/scott?useSSL=false&useUnicode=true&characterEncoding=utf-8" ,"root" ,""); Map<String,Object> params=new HashMap<>(); params.put("sal", 2500); params.put("maxSal", 4000); List<Emp> list=s.queryTag("select empno,ename,job,sal,comm,deptno from emp where #{sal} < sal and sal < #{maxSal}", params,Emp.class); // List list2=s.queryTag("select deptno,dname,loc from dept", null); s.close(); } public void query() throws ClassNotFoundException, SQLException{ DbUtilsTag s=new DbUtilsTag(); s.getConn("com.mysql.jdbc.Driver" , "jdbc:mysql://127.0.0.1:3308/scott?useSSL=false&useUnicode=true&characterEncoding=utf-8" ,"root" ,""); Map params=new HashMap<>(); params.put("ename", "李雷2"); params.put("job", "程序员2"); params.put("sal", "20000"); params.put("comm", "142"); params.put("empno", "10008"); int i=s.updateTag("update emp set ename=#{ename},job=#{job},sal=#{sal},comm=#{comm} where empno=#{empno}",params); // int i=s.update("update emp set ename='测试' where empno=10008 ",null); s.close(); } public static void main(String[] args) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub } } ``` 原文出处:/show_1IX2vQk7lbRe.html