Автор оригинала: Pankaj Kumar.
Если вы используете API JDBC для выполнения запросов к базе данных, вы должны знать, что PreparedStatement-лучший выбор, чем оператор . Однако, поскольку API JDBC допускает только один литерал для одного параметра”?”, PreparedStatement не работает для запросов предложений IN.
ПОДГОТОВЛЕННОЕ заявление В пункте
Поэтому, если нам нужно выполнить запрос к базе данных в предложении, нам нужно найти какой-то альтернативный подход. Цель этого поста-проанализировать различные подходы, и вы можете выбрать тот, который соответствует вашим требованиям.
- Выполнение Отдельных Запросов
- Использование Хранимой Процедуры
- Динамическое создание запроса PreparedStatement
- Использование NULL в подготовленном запросе оператора
Давайте рассмотрим эти подходы один за другим. Но перед этим давайте создадим служебную программу для чтения конфигураций соединений с базой данных из файла свойств.
Давайте рассмотрим эти подходы один за другим. Но перед этим давайте создадим служебную программу для чтения конфигураций соединений с базой данных из файла свойств.
#mysql DB properties DB_DRIVER_CLASS=com.mysql.jdbc.Driver DB_URL=jdbc:mysql://localhost:3306/UserDB DB_USERNAME=pankaj DB_PASSWORD=pankaj123 #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.journaldev.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-банки в пути сборки проекта.
Теперь давайте рассмотрим различные подходы и их анализ.
Выполнение Отдельных Запросов
Это самый простой подход. Мы можем получить входные данные и выполнить один подготовленный запрос оператора несколько раз. Пример программы с таким подходом будет выглядеть следующим образом.
Это самый простой подход. Мы можем получить входные данные и выполнить один подготовленный запрос оператора несколько раз. Пример программы с таким подходом будет выглядеть следующим образом.
package com.journaldev.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 объектов набора результатов, которые перегрузят систему, а также снизят производительность. Поэтому такой подход не рекомендуется.
Использование Хранимой Процедуры
Мы можем написать хранимую процедуру и отправить входные данные в хранимую процедуру. Затем мы можем выполнять запросы один за другим в хранимой процедуре и получать результаты. Этот подход обеспечивает максимальную производительность, но, как мы все знаем, хранимые процедуры зависят от конкретной базы данных. Поэтому, если наше приложение имеет дело с несколькими типами баз данных, такими как Oracle, MySQL, то его будет сложно поддерживать. Мы должны использовать этот подход только в том случае, если мы работаем с одним типом базы данных и не планируем менять сервер базы данных. Поскольку написание хранимой процедуры выходит за рамки этого руководства, я не буду демонстрировать, как ее использовать.
Динамическое создание запроса PreparedStatement
Этот подход предполагает написание логики для динамического создания запроса PreparedStatement на основе размера элементов в предложении IN. Простой пример, показывающий, как его использовать, будет выглядеть следующим образом.
Этот подход предполагает написание логики для динамического создания запроса PreparedStatement на основе размера элементов в предложении IN. Простой пример, показывающий, как его использовать, будет выглядеть следующим образом.
package com.journaldev.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 и в предложении не так много запросов, то, похоже, это правильный путь.
Использование NULL в подготовленном запросе оператора
Если вы действительно хотите использовать функцию кэширования подготовленных операторов, то другой подход заключается в использовании NULL в параметрах PreparedStatement. Предположим, что максимально допустимые параметры в запросе равны 10, тогда мы можем написать нашу логику, как показано ниже.
Если вы действительно хотите использовать функцию кэширования подготовленных операторов, то другой подход заключается в использовании NULL в параметрах PreparedStatement. Предположим, что максимально допустимые параметры в запросе равны 10, тогда мы можем написать нашу логику, как показано ниже.
package com.journaldev.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 PARAM_SIZE = 10; public static void printData(int[] ids){ if(ids.length > PARAM_SIZE){ System.out.println("Maximum input size supported is "+PARAM_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<=PARAM_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 для выполнения В операторе предложения и получит преимущества кэширования запросов и плана выполнения. Для простоты я просто возвращаю, если количество входных параметров больше, чем размер параметров в запросе, но мы можем легко расширить его для пакетного выполнения, чтобы разрешить любое количество входных данных.
Теперь давайте напишем простую тестовую программу для проверки результатов. Для моей тестовой программы я использую таблицу сотрудников, созданную в примере источника данных JDBC.
Наш код тестовой программы является;
Наш код тестовой программы является;
package com.journaldev.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 ID=1, Name=Pankaj 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=Pankaj 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=Pankaj Employee ID=2, Name=David Employee ID=3, Name=Ram Employee ID=4, Name=Leela Employee ID=5, Name=Lisa
Это все для различных опций, для которых мы должны использовать PreparedStatement в предложении IN в запросах. Вы можете использовать любой из них в зависимости от требований вашего проекта.