Apache Commons DbUtils 가이드

1. 개요

Apache Commons DbUtils는 JDBC 작업을 훨씬 쉽게 해주는 작은 라이브러리입니다.

이 기사에서는 그 특징과 기능을 보여주는 예제를 구현할 것입니다.

2. 설정

2.1. Maven 종속성

먼저 commons-dbutilsh2 종속성을 pom.xml 에 추가해야합니다 .

 commons-dbutils commons-dbutils 1.6   com.h2database h2 1.4.196 

Maven Central에서 최신 버전의 commons-dbutils 및 h2를 찾을 수 있습니다.

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. POJO

마지막으로 두 개의 간단한 클래스가 필요합니다.

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); }

모든 ResultSerHandler 구현 을 배우려면 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; } }

우리가 기대하고 주목 연결 우리가 이메일을 얻을 수있는 쿼리를 실행할 수 있도록 생성자에서 개체를.

마지막으로 모든 것이 예상대로 작동하는지 확인하기 위해 코드를 테스트 해 보겠습니다.

@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 를 사용하여 열을 필드에 실제 매핑하고 주소를 지정해야하는 항목에 대해서만 수행합니다.

Finally, let's test everything is ok:

@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. Inserting Records

The QueryRunner class provides two approaches to creating records in a database.

The first one is to use the update() method and pass the SQL statement and an optional list of replacement parameters. The method returns the number of inserted records:

@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); }

The second one is to use the insert() method that, in addition to the SQL statement and replacement parameters, needs a ResultSetHandler to transform the resulting auto-generated keys. The return value will be what the handler returns:

@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. Updating and Deleting

The update() method of the QueryRunner class can also be used to modify and erase records from our database.

Its usage is trivial. Here's an example of how to update an employee's salary:

@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); }

And here's another to delete an employee with the given id:

@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. Asynchronous Operations

DbUtils provides the AsyncQueryRunner class to execute operations asynchronously. The methods on this class have a correspondence with those of QueryRunner class, except that they return a Future instance.

Here's an example to obtain all employees in the database, waiting up to 10 seconds to get the results:

@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. Conclusion

In this tutorial, we explored the most notable features of the Apache Commons DbUtils library.

데이터를 쿼리하고 다른 개체 유형으로 변환하고, 생성 된 기본 키를 가져 오는 레코드를 삽입하고, 주어진 기준에 따라 데이터를 업데이트 및 삭제했습니다. 또한 AsyncQueryRunner 클래스를 활용 하여 쿼리 작업을 비동기 적으로 실행했습니다.

항상 그렇듯이이 기사의 전체 소스 코드는 Github에서 찾을 수 있습니다.