Рубрики
Без рубрики

Руководство по Apache Commons DBUtils

Научитесь работать с JDBC через DBUtils Apache Commons.

Автор оригинала: baeldung.

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> list
      = runner.query(connection, "SELECT * FROM employee", beanListHandler);

    assertEquals(list.size(), 5);
    assertEquals(list.get(0).get("firstname"), "John");
    assertEquals(list.get(4).get("firstname"), "Christian");
}

Далее, вот пример использования 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);
}

Чтобы узнать все реализации Resultsethandler , вы можете обратиться к документации ResultSetHandler .

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 (сопоставление не зависит от регистра). Однако это не всегда так – например, когда в именах столбцов используются подчеркивания для разделения составных слов.

В этих ситуациях мы можем воспользоваться интерфейсом 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";
    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. Заключение

В этом уроке мы изучили наиболее заметные функции библиотеки DBUtils Apache Commons.

Мы запрашивали данные и преобразовывали их в различные типы объектов, вставляли записи, получая сгенерированные первичные ключи, а также обновляли и удаляли данные на основе заданных критериев. Мы также воспользовались классом AsyncQueryRunner для асинхронного выполнения операции запроса.

И, как всегда, полный исходный код этой статьи можно найти на Github .