Руководство по Apache Commons DbUtils
1. обзор
Apache Commons DbUtils - небольшая библиотека, которая значительно упрощает работу с JDBC.
В этой статье мы реализуем примеры, чтобы продемонстрировать его функции и возможности.
2. Настроить
2.1. Maven Зависимости
Во-первых, нам нужно добавить зависимостиcommons-dbutils иh2 к нашемуpom.xml:
commons-dbutils
commons-dbutils
1.6
com.h2database
h2
1.4.196
Вы можете найти последнюю версиюcommons-dbutils иh2 на Maven Central.
2.2. Тестовая база данных
Имея наши зависимости, давайте создадим скрипт для создания таблиц и записей, которые мы будем использовать:
CREATE TABLE employee(
id int NOT NULL PRIMARY KEY auto_increment,
firstname varchar(255),
lastname varchar(255),
salary double,
hireddate date,
);
CREATE TABLE email(
id int NOT NULL PRIMARY KEY auto_increment,
employeeid int,
address varchar(255)
);
INSERT INTO employee (firstname,lastname,salary,hireddate)
VALUES ('John', 'Doe', 10000.10, to_date('01-01-2001','dd-mm-yyyy'));
// ...
INSERT INTO email (employeeid,address)
VALUES (1, '[email protected]');
// ...
Все примеры тестовых случаев в этой статье будут использовать только что созданное соединение с базой данных H2 в памяти:
public class DbUtilsUnitTest {
private Connection connection;
@Before
public void setupDB() throws Exception {
Class.forName("org.h2.Driver");
String db
= "jdbc:h2:mem:;INIT=runscript from 'classpath:/employees.sql'";
connection = DriverManager.getConnection(db);
}
@After
public void closeBD() {
DbUtils.closeQuietly(connection);
}
// ...
}
2.3. POJOs
Наконец, нам понадобятся два простых класса:
public class Employee {
private Integer id;
private String firstName;
private String lastName;
private Double salary;
private Date hiredDate;
// standard constructors, getters, and setters
}
public class Email {
private Integer id;
private Integer employeeId;
private String address;
// standard constructors, getters, and setters
}
3. Вступление
Библиотека DbUtils предоставляетthe QueryRunner class as the main entry point для большинства доступных функций.
Этот класс работает, получая соединение с базой данных, оператор SQL для выполнения и необязательный список параметров для предоставления значений для заполнителей запроса.
Как мы увидим позже, некоторые методы также получают реализациюResultSetHandler, которая отвечает за преобразование экземпляровResultSet в объекты, ожидаемые нашим приложением.
Конечно, библиотека уже предоставляет несколько реализаций, которые обрабатывают наиболее распространенные преобразования, такие как списки, карты и JavaBeans.
4. Запрос данных
Теперь, когда мы знаем основы, мы готовы запросить нашу базу данных.
Давайте начнем с быстрого примера получения всех записей в базе данных в виде списка карт с использованиемMapListHandler:
@Test
public void givenResultHandler_whenExecutingQuery_thenExpectedList()
throws SQLException {
MapListHandler beanListHandler = new MapListHandler();
QueryRunner runner = new QueryRunner();
List
Далее, вот пример использованияBeanListHandler для преобразования результатов в экземплярыEmployee:
@Test
public void givenResultHandler_whenExecutingQuery_thenEmployeeList()
throws SQLException {
BeanListHandler beanListHandler
= new BeanListHandler<>(Employee.class);
QueryRunner runner = new QueryRunner();
List employeeList
= runner.query(connection, "SELECT * FROM employee", beanListHandler);
assertEquals(employeeList.size(), 5);
assertEquals(employeeList.get(0).getFirstName(), "John");
assertEquals(employeeList.get(4).getFirstName(), "Christian");
}
Для запросов, которые возвращают одно значение, мы можем использоватьScalarHandler:
@Test
public void givenResultHandler_whenExecutingQuery_thenExpectedScalar()
throws SQLException {
ScalarHandler scalarHandler = new ScalarHandler<>();
QueryRunner runner = new QueryRunner();
String query = "SELECT COUNT(*) FROM employee";
long count
= runner.query(connection, query, scalarHandler);
assertEquals(count, 5);
}
Чтобы изучить все реализацииResultSerHandler, вы можете обратиться кResultSetHandler documentation.
4.1. Пользовательские обработчики
Мы также можем создать собственный обработчик для передачи в методыQueryRunner, когда нам нужно больше контроля над тем, как результаты будут преобразованы в объекты.
Это можно сделать либо путем реализации интерфейсаResultSetHandler, либо путем расширения одной из существующих реализаций, предоставляемых библиотекой.
Посмотрим, как выглядит второй подход. Во-первых, давайте добавим еще одно поле в наш классEmployee:
public class Employee {
private List emails;
// ...
}
Теперь давайте создадим класс, который расширяет типBeanListHandler и устанавливает список адресов электронной почты для каждого сотрудника:
public class EmployeeHandler extends BeanListHandler {
private Connection connection;
public EmployeeHandler(Connection con) {
super(Employee.class);
this.connection = con;
}
@Override
public List handle(ResultSet rs) throws SQLException {
List employees = super.handle(rs);
QueryRunner runner = new QueryRunner();
BeanListHandler handler = new BeanListHandler<>(Email.class);
String query = "SELECT * FROM email WHERE employeeid = ?";
for (Employee employee : employees) {
List emails
= runner.query(connection, query, handler, employee.getId());
employee.setEmails(emails);
}
return employees;
}
}
Обратите внимание, что мы ожидаем объектConnection в конструкторе, чтобы мы могли выполнять запросы для получения электронных писем.
Наконец, давайте проверим наш код, чтобы убедиться, что все работает должным образом:
@Test
public void
givenResultHandler_whenExecutingQuery_thenEmailsSetted()
throws SQLException {
EmployeeHandler employeeHandler = new EmployeeHandler(connection);
QueryRunner runner = new QueryRunner();
List employees
= runner.query(connection, "SELECT * FROM employee", employeeHandler);
assertEquals(employees.get(0).getEmails().size(), 2);
assertEquals(employees.get(2).getEmails().size(), 3);
}
4.2. Пользовательские процессоры строк
В наших примерах имена столбцов таблицыemployee соответствуют именам полей нашего классаEmployee (совпадение не зависит от регистра). Однако это не всегда так - например, когда в именах столбцов используются символы подчеркивания для разделения составных слов.
В этих ситуациях мы можем воспользоваться интерфейсомRowProcessor и его реализациями, чтобы сопоставить имена столбцов с соответствующими полями в наших классах.
Посмотрим, как это выглядит. Сначала создадим еще одну таблицу и вставим в нее несколько записей:
CREATE TABLE employee_legacy (
id int NOT NULL PRIMARY KEY auto_increment,
first_name varchar(255),
last_name varchar(255),
salary double,
hired_date date,
);
INSERT INTO employee_legacy (first_name,last_name,salary,hired_date)
VALUES ('John', 'Doe', 10000.10, to_date('01-01-2001','dd-mm-yyyy'));
// ...
Теперь давайте изменим наш классEmployeeHandler:
public class EmployeeHandler extends BeanListHandler {
// ...
public EmployeeHandler(Connection con) {
super(Employee.class,
new BasicRowProcessor(new BeanProcessor(getColumnsToFieldsMap())));
// ...
}
public static Map getColumnsToFieldsMap() {
Map columnsToFieldsMap = new HashMap<>();
columnsToFieldsMap.put("FIRST_NAME", "firstName");
columnsToFieldsMap.put("LAST_NAME", "lastName");
columnsToFieldsMap.put("HIRED_DATE", "hiredDate");
return columnsToFieldsMap;
}
// ...
}
Обратите внимание, что мы используемBeanProcessor для фактического сопоставления столбцов с полями и только для тех, которые необходимо адресовать.
Наконец, давайте проверим, что все в порядке:
@Test
public void
givenResultHandler_whenExecutingQuery_thenAllPropertiesSetted()
throws SQLException {
EmployeeHandler employeeHandler = new EmployeeHandler(connection);
QueryRunner runner = new QueryRunner();
String query = "SELECT * FROM employee_legacy";
List employees
= runner.query(connection, query, employeeHandler);
assertEquals((int) employees.get(0).getId(), 1);
assertEquals(employees.get(0).getFirstName(), "John");
}
5. Вставка записей
КлассQueryRunner предоставляет два подхода к созданию записей в базе данных.
Первый - использовать методupdate() и передать инструкцию SQL и необязательный список параметров замены. Метод возвращает количество вставленных записей:
@Test
public void whenInserting_thenInserted() throws SQLException {
QueryRunner runner = new QueryRunner();
String insertSQL
= "INSERT INTO employee (firstname,lastname,salary, hireddate) "
+ "VALUES (?, ?, ?, ?)";
int numRowsInserted
= runner.update(
connection, insertSQL, "Leia", "Kane", 60000.60, new Date());
assertEquals(numRowsInserted, 1);
}
Второй - использовать методinsert(), который, помимо оператора SQL и параметров замены, требуетResultSetHandler для преобразования автоматически сгенерированных ключей. Возвращаемое значение будет тем, что возвращает обработчик:
@Test
public void
givenHandler_whenInserting_thenExpectedId() throws SQLException {
ScalarHandler scalarHandler = new ScalarHandler<>();
QueryRunner runner = new QueryRunner();
String insertSQL
= "INSERT INTO employee (firstname,lastname,salary, hireddate) "
+ "VALUES (?, ?, ?, ?)";
int newId
= runner.insert(
connection, insertSQL, scalarHandler,
"Jenny", "Medici", 60000.60, new Date());
assertEquals(newId, 6);
}
6. Обновление и удаление
Методupdate() классаQueryRunner также можно использовать для изменения и удаления записей из нашей базы данных.
Его использование тривиально. Вот пример того, как обновить зарплату сотрудника:
@Test
public void givenSalary_whenUpdating_thenUpdated()
throws SQLException {
double salary = 35000;
QueryRunner runner = new QueryRunner();
String updateSQL
= "UPDATE employee SET salary = salary * 1.1 WHERE salary <= ?";
int numRowsUpdated = runner.update(connection, updateSQL, salary);
assertEquals(numRowsUpdated, 3);
}
А вот еще один вариант удаления сотрудника с данным идентификатором:
@Test
public void whenDeletingRecord_thenDeleted() throws SQLException {
QueryRunner runner = new QueryRunner();
String deleteSQL = "DELETE FROM employee WHERE id = ?";
int numRowsDeleted = runner.update(connection, deleteSQL, 3);
assertEquals(numRowsDeleted, 1);
}
7. Асинхронные операции
DbUtils предоставляет классAsyncQueryRunner для асинхронного выполнения операций. Методы этого класса соответствуют методам классаQueryRunner, за исключением того, что они возвращают экземплярFuture.
Вот пример получения всех сотрудников в базе данных, ожидающих до 10 секунд, чтобы получить результаты:
@Test
public void
givenAsyncRunner_whenExecutingQuery_thenExpectedList() throws Exception {
AsyncQueryRunner runner
= new AsyncQueryRunner(Executors.newCachedThreadPool());
EmployeeHandler employeeHandler = new EmployeeHandler(connection);
String query = "SELECT * FROM employee";
Future> future
= runner.query(connection, query, employeeHandler);
List employeeList = future.get(10, TimeUnit.SECONDS);
assertEquals(employeeList.size(), 5);
}
8. Заключение
В этом руководстве мы рассмотрели наиболее примечательные функции библиотеки Apache Commons DbUtils.
Мы запрашивали данные и преобразовывали их в объекты различных типов, вставляли записи, получая сгенерированные первичные ключи, и обновляли и удаляли данные на основе заданных критериев. Мы также воспользовались преимуществом классаAsyncQueryRunner для асинхронного выполнения операции запроса.
И, как всегда, полный исходный код этой статьи можно найти вover on Github.