Java PreparedStatement IN子句替代
如果您使用JDBC API在数据库上运行查询,则应该知道PreparedStatement比Statement是更好的选择。
但是,由于JDBC API仅对"?"使用一个文字。
参数,PreparedStatement不适用于IN子句查询。
PreparedStatement IN子句
因此,如果需要使用IN子句执行数据库查询,则需要寻找一些替代方法。
这篇文章的目的是分析不同的方法,您可以选择适合您需求的方法。
- 执行单一查询
- 使用存储过程
- 动态创建PreparedStatement查询
- 在PreparedStatement查询中使用NULL
让我们一一看一下这些方法。
但是在此之前,我们先创建一个实用程序,用于从属性文件读取数据库连接。
db.properties
#mysql DB properties DB_DRIVER_CLASS=com.mysql.jdbc.Driver DB_URL=jdbc:mysql://localhost:3306/UserDB DB_USERNAME=hyman DB_PASSWORD=hyman123 #Oracle DB Properties #DB_DRIVER_CLASS=oracle.jdbc.driver.OracleDriver #DB_URL=jdbc:oracle:thin:@localhost:1521:orcl #DB_USERNAME=hr #DB_PASSWORD=oracle
package com.theitroad.jdbc.preparedstatement.in;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBConnection {
public static Connection getConnection() {
Properties props = new Properties();
FileInputStream fis = null;
Connection con = null;
try {
fis = new FileInputStream("db.properties");
props.load(fis);
//load the Driver Class
Class.forName(props.getProperty("DB_DRIVER_CLASS"));
//create the connection now
con = DriverManager.getConnection(props.getProperty("DB_URL"),
props.getProperty("DB_USERNAME"),
props.getProperty("DB_PASSWORD"));
} catch (SQLException e) {
System.out.println("Check database is UP and configs are correct");
e.printStackTrace();
} catch (IOException e) {
System.out.println("Looks like db.property file has some issues");
e.printStackTrace();
} catch (ClassNotFoundException e) {
System.out.println("Please include JDBC API jar in classpath");
e.printStackTrace();
}finally{
try {
fis.close();
} catch (IOException e) {
System.out.println("File Close issue, lets ignore it.");
}
}
return con;
}
}
确保项目的构建路径中有JDBC jar。
现在,让我们看一下不同的方法及其分析。
执行单一查询
这是最简单的方法。
我们可以获取输入并多次执行单个PreparedStatement查询。
使用这种方法的示例程序如下所示。
JDBCPreparedStatementSingle.java
package com.theitroad.jdbc.preparedstatement.in;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCPreparedStatementSingle {
private static final String QUERY = "select empid, name from Employee where empid = ?";
public static void printData(int[] ids){
Connection con = DBConnection.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement(QUERY);
for(int empid : ids){
ps.setInt(1, empid);
rs = ps.executeQuery();
while(rs.next()){
System.out.println("Employee ID="+rs.getInt("empid")+", Name="+rs.getString("name"));
}
//close the resultset here
try{
rs.close();
} catch(SQLException e){}
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
这种方法很简单,但是非常慢,因为如果有100个参数,那么它将进行100次数据库调用。
这将导致100个ResultSet对象,这些对象将使系统超载,并且也将导致性能下降。
因此,不建议使用此方法。
使用存储过程
我们可以编写一个存储过程,并将输入数据发送到该存储过程。
然后,我们可以在存储过程中一一执行查询并获得结果。
这种方法提供了最快的性能,但是众所周知,存储过程是特定于数据库的。
因此,如果我们的应用程序处理多种类型的数据库,例如Oracle,MySQL,则将很难维护。
仅在处理单一类型的数据库并且没有计划更改数据库服务器时,才应使用此方法。
由于编写存储过程超出了本教程的范围,因此我将不演示如何使用它。
动态创建PreparedStatement查询
这种方法涉及编写逻辑以根据IN子句中元素的大小动态创建PreparedStatement查询。
一个显示如何使用它的简单示例如下代码所示。
JDBCPreparedStatementDynamic.java
package com.theitroad.jdbc.preparedstatement.in;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCPreparedStatementDynamic {
public static void printData(int[] ids){
String query = createQuery(ids.length);
System.out.println("Query="+query);
Connection con = DBConnection.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement(query);
for(int i = 1; i <=ids.length; i++){
ps.setInt(i, ids[i-1]);
}
rs = ps.executeQuery();
while(rs.next()){
System.out.println("Employee ID="+rs.getInt("empid")+", Name="+rs.getString("name"));
}
//close the resultset here
try{
rs.close();
} catch(SQLException e){}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static String createQuery(int length) {
String query = "select empid, name from Employee where empid in (";
StringBuilder queryBuilder = new StringBuilder(query);
for( int i = 0; i< length; i++){
queryBuilder.append(" ?");
if(i != length -1) queryBuilder.append(",");
}
queryBuilder.append(")");
return queryBuilder.toString();
}
}
请注意,查询是动态创建的,它将完美运行。
只有一个数据库调用,并且性能会很好。
但是,如果用户输入的大小相差很大,我们将无法获得PreparedStatement的优势来缓存和重用执行计划。
如果您不担心PreparedStatement缓存并且使用IN子句的查询不多,那么这似乎是可行的方法。
在PreparedStatement查询中使用NULL
如果您真的想利用PreparedStatement缓存功能,那么另一种方法是在PreparedStatement参数中使用NULL。
假设查询中允许的最大参数为10,那么我们可以像下面这样编写逻辑。
JDBCPreparedStatementNULL.java
package com.theitroad.jdbc.preparedstatement.in;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCPreparedStatementNULL {
private static final String QUERY = "select empid, name from Employee where empid in ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final int PA内存_SIZE = 10;
public static void printData(int[] ids){
if(ids.length > PA内存_SIZE){
System.out.println("Maximum input size supported is "+PA内存_SIZE);
//in real life, we can write logic to execute in batches, for simplicity I am returning
return;
}
Connection con = DBConnection.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement(QUERY);
int i = 1;
for(; i <=ids.length; i++){
ps.setInt(i, ids[i-1]);
}
//set null for remaining ones
for(; i<=PA内存_SIZE;i++){
ps.setNull(i, java.sql.Types.INTEGER);
}
rs = ps.executeQuery();
while(rs.next()){
System.out.println("Employee ID="+rs.getInt("empid")+", Name="+rs.getString("name"));
}
//close the resultset here
try{
rs.close();
} catch(SQLException e){}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
注意,上面的程序使用相同的PreparedStatement查询来执行IN子句语句,并将获得查询缓存和执行计划的好处。
为了简单起见,我只是返回输入参数的数量是否大于查询中的参数大小,但是我们可以轻松地扩展它以批量执行以允许任意数量的输入。
现在,让我们编写一个简单的测试程序来检查输出。
对于我的测试程序,我使用在JDBC DataSource示例中创建的Employee表。
我们的测试程序代码是;
JDBCPreparedStatementINTest.java
package com.theitroad.jdbc.preparedstatement.in;
public class JDBCPreparedStatementINTest {
private static int[] ids = {1,2,3,4,5,6,7,8,9,10};
public static void main(String[] args) {
JDBCPreparedStatementSingle.printData(ids);
System.out.println("*");
JDBCPreparedStatementDynamic.printData(ids);
System.out.println("*");
JDBCPreparedStatementNULL.printData(new int[]{1,2,3,4,5});
}
}
当我们用Employee表中的一些测试数据执行它时,我们得到下面的输出。
Employee ID=1, Name=hyman Employee ID=2, Name=David Employee ID=3, Name=Ram Employee ID=4, Name=Leela Employee ID=5, Name=Lisa Employee ID=6, Name=Saurabh Employee ID=7, Name=Mani Employee ID=8, Name=Avinash Employee ID=9, Name=Vijay * Query=select empid, name from Employee where empid in ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) Employee ID=1, Name=hyman Employee ID=2, Name=David Employee ID=3, Name=Ram Employee ID=4, Name=Leela Employee ID=5, Name=Lisa Employee ID=6, Name=Saurabh Employee ID=7, Name=Mani Employee ID=8, Name=Avinash Employee ID=9, Name=Vijay * Employee ID=1, Name=hyman Employee ID=2, Name=David Employee ID=3, Name=Ram Employee ID=4, Name=Leela Employee ID=5, Name=Lisa

