说明
在 JAVA jdbc封装 DbUtil 类2.0-增加事务功能 中,已经实现了功能,但 缺点是,当 SQL 参数过多时,会有 n 个 ?,传入的参数会很多,容易出现错误
解决
SQL 占位符
使用 #{} 占位符,如下:
查询语句占位符:
select empno,ename,job,sal,comm,deptno from emp where #{sal} < sal and sal < #{maxSal}
更新语句占位符:
update emp set ename=#{ename},job=#{job},sal=#{sal},comm=#{comm} where empno=#{empno}
传参
传入 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-增加事务功能 中的类
package website;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
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 params
* @return List,里面的元素是Map
* @throws SQLException
*/
public List<Record> 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当初就是这么设计的
Record record=new Record();
for(int i=0 ; i<count ; i++ ){
record.put(rsmd.getColumnLabel(i+1), rs.getObject(i+1));
}
//将数组放入到List中
retList.add(record);
}
//查询后要释放资源
rs.close();
pstm.close();
return retList;
}
}
sql解析类
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
* @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中,然后将 #{} 替换成 ?
* @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;
}
}
异常类
public 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{
Connection conn = ConnectionFactory.connect("com.mysql.jdbc.Driver"
, "jdbc:mysql://127.0.0.1:3308/scott?useSSL=false&useUnicode=true&characterEncoding=utf-8"
,"root"
,"")
DbUtilsTag s=new DbUtilsTag();
s.setConnection(conn);
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{
Connection conn = ConnectionFactory.connect("com.mysql.jdbc.Driver"
, "jdbc:mysql://127.0.0.1:3308/scott?useSSL=false&useUnicode=true&characterEncoding=utf-8"
,"root"
,"")
DbUtilsTag s=new DbUtilsTag();
s.setConnection(conn);
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
}
}