JAVA jdbc封装 DbUtil 类3.0:DbUtilsTag -使用 #{} 占位符(废弃)

废弃

本文已废弃,改为:JAVA jdbc封装 DbUtil 类3.1:DbUtilsTag -使用 #{} 占位符

说明

在 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-增加事务功能 中的类

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