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 предоставляет класс QueryRunner в качестве основной точки входа для большинства доступных функций.
Этот класс работает, получая соединение с базой данных, инструкцию 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 { BeanListHandlerbeanListHandler = 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 { ScalarHandlerscalarHandler = new ScalarHandler<>(); QueryRunner runner = new QueryRunner(); String query = "SELECT COUNT(*) FROM employee"; long count = runner.query(connection, query, scalarHandler); assertEquals(count, 5); }
Чтобы узнать все реализации Resultsethandler , вы можете обратиться к документации ResultSetHandler .
4.1. Пользовательские обработчики
Мы также можем создать пользовательский обработчик для передачи методам QueryRunner , когда нам нужно больше контроля над тем, как результаты будут преобразованы в объекты.
Это может быть сделано либо путем реализации интерфейса ResultSetHandler , либо путем расширения одной из существующих реализаций, предоставляемых библиотекой.
Давайте посмотрим, как выглядит второй подход. Во-первых, давайте добавим еще одно поле в наш класс Employee :
public class Employee { private Listemails; // ... }
Теперь давайте создадим класс, который расширяет тип 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(); Listemployees = 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 (сопоставление не зависит от регистра). Однако это не всегда так – например, когда в именах столбцов используются подчеркивания для разделения составных слов.
В этих ситуациях мы можем воспользоваться интерфейсом Row Processor и его реализациями, чтобы сопоставить имена столбцов с соответствующими полями в наших классах.
Давайте посмотрим, как это выглядит. Во-первых, давайте создадим еще одну таблицу и вставим в нее несколько записей:
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; } // ... }
Обратите внимание, что мы используем Bean-процессор для фактического сопоставления столбцов с полями и только для тех, которые необходимо обработать.
Наконец, давайте проверим, все ли в порядке:
@Test public void givenResultHandler_whenExecutingQuery_thenAllPropertiesSetted() throws SQLException { EmployeeHandler employeeHandler = new EmployeeHandler(connection); QueryRunner runner = new QueryRunner(); String query = "SELECT * FROM employee_legacy"; Listemployees = 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 { ScalarHandlerscalarHandler = 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. Заключение
В этом уроке мы изучили наиболее заметные функции библиотеки DBUtils Apache Commons.
Мы запрашивали данные и преобразовывали их в различные типы объектов, вставляли записи, получая сгенерированные первичные ключи, а также обновляли и удаляли данные на основе заданных критериев. Мы также воспользовались классом AsyncQueryRunner для асинхронного выполнения операции запроса.
И, как всегда, полный исходный код этой статьи можно найти на Github .