当前位置:网站首页>Named parameter implementation of JDBC PreparedStatement
Named parameter implementation of JDBC PreparedStatement
2022-08-02 01:37:00 【allway2】
Named Parameters for PreparedStatement | InfoWorld
PreparedStatement 语法问题
PreparedStatement The problem stems from its argument syntax.Parameters are anonymous,and accessed by index,如下所示:
PreparedStatement p = con.prepareStatement("select * from people where
(first_name = ? or last_name = ?) and address = ?");
p.setString(1, name);
p.setString(2, name);
p.setString(3, address);
For small queries with one or two parameters,这不是问题.但是,For larger queries,It becomes very difficult to keep track of indexes.Developers must read carefully SQL statement and evaluate the question mark to determine where the parameter is inserted.If removing or inserting parameters,then all subsequent parameter indices must be renumbered.很明显,If the parameter is near the beginning of the statement and there are multiple parameters,Or if the query is restructured so that the parameters appear in a different order,这可能会出现问题.
Another inconvenience is setting multiple parameters that may logically be the same.This can happen in egselect * from people where first_name=? or last_name=?
. (This particular query can be rewritten asselect * from people where ? in (first_name, last_name)
,But some queries are not so easy to convert.)
解决方法
A workaround to avoid renumbering the index is to use a counter to keep track of the index:
PreparedStatement p = con.prepareStatement("select * from people where
(first_name = ? or last_name = ?) and address = ?");
int i = 1;
p.setString(i++, name);
p.setString(i++, name);
p.setString(i++, address);
This is especially useful for stability when inserting and deleting parameters.但是,The code is still no longer readable,Programmers must still take care to ensure that the parameters are listed in the same order they are used in the query.
Named parameter statement
This brings us to my class(Because there is no better name)NamedParameterStatement
.语法与 PreparedStatement 相同,It's just that the parameter is represented as a colon followed by an identifier,而不是问号.
String query = "select * from people where (first_name = :name or last_name
= :name) and address = :address");
NamedParameterStatement p = new NamedParameterStatement(con, query);
p.setString("name", name);
p.setString("address", address);
在幕后,The class is created by replacing parameter markers with question marks PreparedStatement 来工作.Keeps a mapping between parameter names and their indices.This map is referenced when injecting parameters.These two classes are compatible with each other,So programmers can add them as they see fit PreparedStatement for some queries,并将 NamedParameterStatement for other queries.
表现
compared to the time it takes to execute the query,The time spent translating the query and finding the parameter index is actually minimal.Consider translation time if necessary,Then the class can be modified to have attach(Connection)
和detach()
方法.NamedParameterStatement 可以预先创建、attached to connection、use and separation.但是,缓存(和同步,如有必要)These objects may take longer than it takes to create new objects.
A test of the above query on my computer against a small table gives NamedParameterStatement 的 352 微秒、AttachableNamedParameterStatement(无同步)的 325 Microseconds and raw PreparedStatement 的 332 微秒的时间.Translating a query takes approx 6 微秒.不可思议的是,AttachableNamedParameterStatement Always better than original PreparedStatement 稍有优势.due to garbage collection、Just-in-time compilation, etc,对 Java Benchmarking code is notoriously difficult,These results should therefore be taken with a grain of salt.
一般来说,性能不是问题,Especially when the actual query is doing anything important.
结论
NamedParameterStatement 作为 PreparedStatement A drop-in replacement is available,无需配置.The simpler interface provided by the new classes can improve programmer productivity.也许更重要的是,维护更容易,Because the code is more readable.
代码
//STEP 1. Import required packages
import java.sql.*;
public class JDBCNamedParameterStatement {
// JDBC driver name and database URL
static final String DB_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/emp?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
// Database credentials
static final String DB_USERNAME = "root";
static final String DB_PASSWORD = "root";
public static void main(String[] args) {
Connection conn = null;
try {
//STEP 2: Register JDBC driver
Class.forName(DB_DRIVER_CLASS_NAME);
//STEP 3: Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
//STEP 4: Execute a query
System.out.println("Creating statement...");
String query = "SELECT id, first, last, age FROM Employees where id=:id";
NamedParameterStatement p = new NamedParameterStatement(conn, query);
p.setInt("id", 100);
ResultSet rs = p.executeQuery();
//STEP 5: Extract data from result set
while (rs.next()) {
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
//Display values
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
//STEP 6: Clean-up environment
rs.close();
p.close();
conn.close();
} catch (SQLException se) {
//Handle errors for JDBC
se.printStackTrace();
} catch (Exception e) {
//Handle errors for Class.forName
e.printStackTrace();
} finally {
//finally block used to close resources
try {
if (conn != null) {
conn.close();
}
} catch (SQLException se) {
se.printStackTrace();
}//end finally try
}//end try
System.out.println("Goodbye!");
}//end main
}//end JDBCExample
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This class wraps around a {@link PreparedStatement} and allows the programmer
* to set parameters by name instead of by index. This eliminates any confusion
* as to which parameter index represents what. This also means that rearranging
* the SQL statement or adding a parameter doesn't involve renumbering your
* indices. Code such as this:
*
*
* Connection con=getConnection(); String query="select * from my_table where
* name=? or address=?"; PreparedStatement p=con.prepareStatement(query);
* p.setString(1, "bob"); p.setString(2, "123 terrace ct"); ResultSet
* rs=p.executeQuery();
*
* can be replaced with:
*
* Connection con=getConnection(); String query="select * from my_table where
* name=:name or address=:address"; NamedParameterStatement p=new
* NamedParameterStatement(con, query); p.setString("name", "bob");
* p.setString("address", "123 terrace ct"); ResultSet rs=p.executeQuery();
*
* Sourced from JavaWorld Article @
* http://www.javaworld.com/javaworld/jw-04-2007/jw-04-jdbc.html
*
* @author adam_crume
*/
public class NamedParameterStatement {
/**
* The statement this object is wrapping.
*/
private final PreparedStatement statement;
/**
* Maps parameter names to arrays of ints which are the parameter indices.
*/
private Map<String, int[]> indexMap;
/**
* Creates a NamedParameterStatement. Wraps a call to c.{@link Connection#prepareStatement(java.lang.String)
* prepareStatement}.
*
* @param connection the database connection
* @param query the parameterized query
* @throws SQLException if the statement could not be created
*/
public NamedParameterStatement(Connection connection, String query) throws SQLException {
String parsedQuery = parse(query);
statement = connection.prepareStatement(parsedQuery);
}
/**
* Parses a query with named parameters. The parameter-index mappings are
* put into the map, and the parsed query is returned. DO NOT CALL FROM
* CLIENT CODE. This method is non-private so JUnit code can test it.
*
* @param query query to parse
* @param paramMap map to hold parameter-index mappings
* @return the parsed query
*/
final String parse(String query) {
// I was originally using regular expressions, but they didn't work well for ignoring
// parameter-like strings inside quotes.
int length = query.length();
StringBuffer parsedQuery = new StringBuffer(length);
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
int index = 1;
HashMap<String, List<Integer>> indexes = new HashMap<String, List<Integer>>(10);
for (int i = 0; i < length; i++) {
char c = query.charAt(i);
if (inSingleQuote) {
if (c == '\'') {
inSingleQuote = false;
}
} else if (inDoubleQuote) {
if (c == '"') {
inDoubleQuote = false;
}
} else {
if (c == '\'') {
inSingleQuote = true;
} else if (c == '"') {
inDoubleQuote = true;
} else if (c == ':' && i + 1 < length && Character.isJavaIdentifierStart(query.charAt(i + 1))) {
int j = i + 2;
while (j < length && Character.isJavaIdentifierPart(query.charAt(j))) {
j++;
}
String name = query.substring(i + 1, j);
c = '?'; // replace the parameter with a question mark
i += name.length(); // skip past the end if the parameter
List<Integer> indexList = indexes.get(name);
if (indexList == null) {
indexList = new LinkedList<Integer>();
indexes.put(name, indexList);
}
indexList.add(Integer.valueOf(index));
index++;
}
}
parsedQuery.append(c);
}
indexMap = new HashMap<String, int[]>(indexes.size());
// replace the lists of Integer objects with arrays of ints
for (Map.Entry<String, List<Integer>> entry : indexes.entrySet()) {
List<Integer> list = entry.getValue();
int[] intIndexes = new int[list.size()];
int i = 0;
for (Integer x : list) {
intIndexes[i++] = x.intValue();
}
indexMap.put(entry.getKey(), intIndexes);
}
return parsedQuery.toString();
}
/**
* Returns the indexes for a parameter.
*
* @param name parameter name
* @return parameter indexes
* @throws IllegalArgumentException if the parameter does not exist
*/
private int[] getIndexes(String name) {
int[] indexes = indexMap.get(name);
if (indexes == null) {
throw new IllegalArgumentException("Parameter not found: " + name);
}
return indexes;
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setObject(int, java.lang.Object)
*/
public void setObject(String name, Object value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setObject(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setString(int, java.lang.String)
*/
public void setString(String name, String value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setString(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setInt(String name, int value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setInt(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setLong(String name, long value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setLong(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setTimestamp(int, java.sql.Timestamp)
*/
public void setTimestamp(String name, Timestamp value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setTimestamp(indexes[i], value);
}
}
/**
* Returns the underlying statement.
*
* @return the statement
*/
public PreparedStatement getStatement() {
return statement;
}
/**
* Executes the statement.
*
* @return true if the first result is a {@link ResultSet}
* @throws SQLException if an error occurred
* @see PreparedStatement#execute()
*/
public boolean execute() throws SQLException {
return statement.execute();
}
/**
* Executes the statement, which must be a query.
*
* @return the query results
* @throws SQLException if an error occurred
* @see PreparedStatement#executeQuery()
*/
public ResultSet executeQuery() throws SQLException {
return statement.executeQuery();
}
/**
* Executes the statement, which must be an SQL INSERT, UPDATE or DELETE
* statement; or an SQL statement that returns nothing, such as a DDL
* statement.
*
* @return number of rows affected
* @throws SQLException if an error occurred
* @see PreparedStatement#executeUpdate()
*/
public int executeUpdate() throws SQLException {
return statement.executeUpdate();
}
/**
* Closes the statement.
*
* @throws SQLException if an error occurred
* @see Statement#close()
*/
public void close() throws SQLException {
statement.close();
}
/**
* Adds the current set of parameters as a batch entry.
*
* @throws SQLException if something went wrong
*/
public void addBatch() throws SQLException {
statement.addBatch();
}
/**
* Executes all of the batched statements.
*
* See {@link Statement#executeBatch()} for details.
*
* @return update counts for each statement
* @throws SQLException if something went wrong
*/
public int[] executeBatch() throws SQLException {
return statement.executeBatch();
}
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class NamedParameterStatement implements AutoCloseable {
public static final int[] EMPTY = {};
/**
* The statement this object is wrapping.
*/
public final PreparedStatement statement;
/**
* Maps parameter names to arrays of ints which are the parameter indices.
*/
private final Map indexMap;
/**
* Creates a NamedParameterStatement. Wraps a call to c.{@link Connection#prepareStatement(java.lang.String)
* prepareStatement}.
*
* @param connection the database connection
* @param query the parameterized query
* @throws SQLException if the statement could not be created
*/
public NamedParameterStatement(Connection connection, String query) throws SQLException {
indexMap = new HashMap();
String parsedQuery = parse(query, indexMap);
statement = connection.prepareStatement(parsedQuery);
}
/**
* Parses a query with named parameters. The parameter-index mappings are
* put into the map, and the parsed query is returned. DO NOT CALL FROM
* CLIENT CODE. This method is non-private so JUnit code can test it.
*
* @param query query to parse
* @param paramMap map to hold parameter-index mappings
* @return the parsed query
*/
static String parse(String query, Map paramMap) {
// I was originally using regular expressions, but they didn't work well for ignoring
// parameter-like strings inside quotes.
int length = query.length();
final StringBuilder parsedQuery = new StringBuilder(length);
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
int index = 1;
for (int i = 0; i < length; i++) {
char c = query.charAt(i);
if (inSingleQuote) {
if (c == '\'') {
inSingleQuote = false;
}
} else if (inDoubleQuote) {
if (c == '"') {
inDoubleQuote = false;
}
} else {
if (c == '\'') {
inSingleQuote = true;
} else if (c == '"') {
inDoubleQuote = true;
} else if (c == ':' && i + 1 < length
&& Character.isJavaIdentifierStart(query.charAt(i + 1))) {
int j = i + 2;
while (j < length && Character.isJavaIdentifierPart(query.charAt(j))) {
j++;
}
String name = query.substring(i + 1, j);
c = '?'; // replace the parameter with a question mark
i += name.length(); // skip past the end if the parameter
List indexList = (List) paramMap.get(name);
if (indexList == null) {
indexList = new LinkedList();
paramMap.put(name, indexList);
}
indexList.add(new Integer(index));
index++;
}
}
parsedQuery.append(c);
}
// replace the lists of Integer objects with arrays of ints
for (Iterator itr = paramMap.entrySet().iterator(); itr.hasNext();) {
Map.Entry entry = (Map.Entry) itr.next();
List list = (List) entry.getValue();
int[] indexes = new int[list.size()];
int i = 0;
for (Object aList : list) {
Integer x = (Integer) aList;
indexes[i++] = x.intValue();
}
list.clear();
entry.setValue(indexes);
}
return parsedQuery.toString();
}
/**
* Returns the indexes for a parameter.
*
* @param name parameter name
* @return parameter indexes
* @throws IllegalArgumentException if the parameter does not exist
*/
private int[] getIndexes(String name) {
int[] indexes = (int[]) indexMap.get(name);
if (indexes == null) {
// throw new IllegalArgumentException("Parameter not found: " + name);
return EMPTY;
}
return indexes;
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setObject(int, java.lang.Object)
*/
public void setObject(String name, Object value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setObject(indexes[i], value);
}
}
public void setProperties(Map<String, Object> params) throws SQLException {
for (Map.Entry<String, Object> entry : params.entrySet()) {
int[] indexes = getIndexes(entry.getKey());
final Object value = entry.getValue();
Timestamp valueTS = null;
if (value != null && value.getClass() == Date.class) {
valueTS = new Timestamp(((Date) value).getTime());
}
for (int i : indexes) {
if (valueTS != null) {
statement.setTimestamp(i, valueTS);
} else {
statement.setObject(i, value);
}
}
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setString(int, java.lang.String)
*/
public void setString(String name, String value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setString(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setInt(String name, int value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setInt(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setInt(int, int)
*/
public void setLong(String name, long value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setLong(indexes[i], value);
}
}
public void setDouble(String name, double value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setDouble(indexes[i], value);
}
}
/**
* Sets a parameter.
*
* @param name parameter name
* @param value parameter value
* @throws SQLException if an error occurred
* @throws IllegalArgumentException if the parameter does not exist
* @see PreparedStatement#setTimestamp(int, java.sql.Timestamp)
*/
public void setTimestamp(String name, Timestamp value) throws SQLException {
int[] indexes = getIndexes(name);
for (int i = 0; i < indexes.length; i++) {
statement.setTimestamp(indexes[i], value);
}
}
/**
* Returns the underlying statement.
*
* @return the statement
*/
public PreparedStatement getStatement() {
return statement;
}
/**
* Executes the statement.
*
* @return true if the first result is a {@link ResultSet}
* @throws SQLException if an error occurred
* @see PreparedStatement#execute()
*/
public boolean execute() throws SQLException {
return statement.execute();
}
/**
* Executes the statement, which must be a query.
*
* @return the query results
* @throws SQLException if an error occurred
* @see PreparedStatement#executeQuery()
*/
public ResultSet executeQuery() throws SQLException {
return statement.executeQuery();
}
/**
* Executes the statement, which must be an SQL INSERT, UPDATE or DELETE
* statement; or an SQL statement that returns nothing, such as a DDL
* statement.
*
* @return number of rows affected
* @throws SQLException if an error occurred
* @see PreparedStatement#executeUpdate()
*/
public int executeUpdate() throws SQLException {
return statement.executeUpdate();
}
/**
* Closes the statement.
*
* @throws SQLException if an error occurred
* @see Statement#close()
*/
public void close() throws SQLException {
statement.close();
indexMap.clear();
}
/**
* Adds the current set of parameters as a batch entry.
*
* @throws SQLException if something went wrong
*/
public void addBatch() throws SQLException {
statement.addBatch();
}
/**
* Executes all of the batched statements.
* <p>
* See {@link Statement#executeBatch()} for details.
*
* @return update counts for each statement
* @throws SQLException if something went wrong
*/
public int[] executeBatch() throws SQLException {
return statement.executeBatch();
}
public void setFetchSize(int rows) throws SQLException {
statement.setFetchSize(rows);
statement.setFetchDirection(ResultSet.FETCH_FORWARD);
}
}
边栏推荐
- ALCCIKERS Shane 20191114
- 记录一次数组转集合出现错误的坑点,尽量使用包装类型数组进行转换
- Oracle data to mysql FlinkSQL CDC to achieve synchronization
- R语言使用cph函数和rcs函数构建限制性立方样条cox回归模型、使用anova函数进行方差分析通过p值确认指定连续变量和风险值HR之间是否存在非线性关系
- 秒懂大模型 | 3步搞定AI写摘要
- 传统企业数字化转型需要经过几个阶段?
- dbeaver连接MySQL数据库及错误Connection refusedconnect处理
- Kubernetes之本地存储
- Redis和MySQL数据一致性问题,有没有好的解决方案?
- 使用百度EasyDL实现厂区工人抽烟行为识别
猜你喜欢
【服务器数据恢复】服务器Raid5阵列mdisk磁盘离线的数据恢复案例
Test Cases: Four-Step Test Design Approach
大话西游无法登陆解决
哈希表
力扣 1374. 生成每种字符都是奇数个的字符串
typescript37-class的构造函数实例方法继承(extends)
html+css+php+mysql实现注册+登录+修改密码(附完整代码)
Two ways to pass feign exceptions: fallbackfactory and global processing Get server-side custom exceptions
Kubernetes — 核心资源对象 — 存储
一本适合职场新人的好书
随机推荐
datagrip 报错 “The specified database userpassword combination is rejected...”的解决方法
C语言:打印整数二进制的奇数位和偶数位
Debian侵犯Rust商标,妥协改名还是会得到豁免?
Shell入门终章
Docker安装canal、mysql进行简单测试与实现redis和mysql缓存一致性
H5页面打开微信小程序
canal实现mysql数据同步
ofstream,ifstream,fstream读写文件
from origin ‘null‘ has been blocked by CORS policy Cross origin requests are only supported for
hash table
HSDC和独立生成树相关
canal realizes mysql data synchronization
html+css+php+mysql实现注册+登录+修改密码(附完整代码)
大话西游创建角色失败解决
【软件工程之美 - 专栏笔记】34 | 账号密码泄露成灾,应该怎样预防?
input禁止输入
flyway的快速入门教程
ImportError cannot import name ‘Mapping‘ from ‘collections‘
dayjs时间处理库的基本使用
typescript36-class的构造函数实例方法