1概要
Apache Commons DbUtilsは、JDBCの操作を非常に簡単にする小さなライブラリです。
この記事では、その機能と機能を紹介するために例を実装します。
2セットアップ
2.1. Mavenの依存関係
まず、 commons-dbutils と h2 の依存関係を pom.xml に追加する必要があります。
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
commons-の最新バージョンを見つけることができます。 Maven Centralでは、[dbutils およびhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22com.h2database%22%20AND%20a%3A%22h2%22[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
最後に、2つの単純なクラスが必要です。
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<Map<String, Object>> 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<Employee> beanListHandler
= new BeanListHandler<>(Employee.class);
QueryRunner runner = new QueryRunner();
List<Employee> 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<Long> scalarHandler = new ScalarHandler<>();
QueryRunner runner = new QueryRunner();
String query = "SELECT COUNT(** ) FROM employee";
long count
= runner.query(connection, query, scalarHandler);
assertEquals(count, 5);
}
すべての ResultSerHandler 実装を学ぶために、https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/ResultSetHandler.html[ ResultSetHandler documentation]を参照できます。
4.1. カスタムハンドラ
結果をオブジェクトに変換する方法をさらに制御する必要がある場合は、 QueryRunner のメソッドに渡すカスタムハンドラを作成することもできます。
これは、 ResultSetHandler インターフェースを実装するか、ライブラリによって提供される既存の実装の1つを拡張することによって実行できます。
2番目のアプローチがどのように見えるかを見てみましょう。まず、 Employee クラスに別のフィールドを追加しましょう。
public class Employee {
private List<Email> emails;
//...
}
それでは、 BeanListHandler タイプを拡張し、各従業員のEメールリストを設定するクラスを作成しましょう。
public class EmployeeHandler extends BeanListHandler<Employee> {
private Connection connection;
public EmployeeHandler(Connection con) {
super(Employee.class);
this.connection = con;
}
@Override
public List<Employee> handle(ResultSet rs) throws SQLException {
List<Employee> employees = super.handle(rs);
QueryRunner runner = new QueryRunner();
BeanListHandler<Email> handler = new BeanListHandler<>(Email.class);
String query = "SELECT ** FROM email WHERE employeeid = ?";
for (Employee employee : employees) {
List<Email> 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<Employee> 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<Employee> {
//...
public EmployeeHandler(Connection con) {
super(Employee.class,
new BasicRowProcessor(new BeanProcessor(getColumnsToFieldsMap())));
//...
}
public static Map<String, String> getColumnsToFieldsMap() {
Map<String, String> 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<Employee> employees
= runner.query(connection, query, employeeHandler);
assertEquals((int) employees.get(0).getId(), 1);
assertEquals(employees.get(0).getFirstName(), "John");
}
5レコードを挿入する
QueryRunner クラスは、データベースにレコードを作成するための2つの方法を提供します。
最初の方法は、 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);
}
2番目の方法は、 insert() メソッドを使用することです。このメソッドでは、SQLステートメントと置換パラメーターに加えて、生成された自動生成キーを変換するために ResultSetHandler が必要です。戻り値はハンドラが返すものになります。
@Test
public void
givenHandler__whenInserting__thenExpectedId() throws SQLException {
ScalarHandler<Integer> 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. 更新と削除
QueryRunner クラスの update() メソッドを使用して、データベースのレコードを変更および消去することもできます。
その使い方は簡単です。これは、従業員の給与を更新する方法の例です。
@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);
}
また、指定された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. 非同期操作
DbUtilsは、操作を非同期的に実行するための AsyncQueryRunner クラスを提供します。このクラスのメソッドは、 Future インスタンスを返す点を除いて、 QueryRunner クラスのメソッドと対応しています。
これは、データベース内の全従業員を取得し、結果を得るために最大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<List<Employee>> future
= runner.query(connection, query, employeeHandler);
List<Employee> employeeList = future.get(10, TimeUnit.SECONDS);
assertEquals(employeeList.size(), 5);
}
8結論
このチュートリアルでは、Apache Commons DbUtilsライブラリの最も注目すべき機能について説明しました。
データを照会してさまざまなオブジェクトタイプに変換し、生成された主キーを取得するレコードを挿入し、特定の基準に基づいてデータを更新および削除しました。また、 AsyncQueryRunner クラスを利用してクエリ操作を非同期的に実行しました。
そしていつものように、この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/libraries-apache-commons[over Github]にあります。