Spring JDBCの紹介 No. 1
Spring FrameworkのSpring JDBCを使っていたのですが、
もうSpring JDBCでいいやって気になってきた。
— かっぱ大王 (@kuwalab)
May 5, 2014
という気分になってきたので、紹介させてもらいます。
Spring Frameworkを使っていてちょっとDBアクセスしたいという時には非常に便利に使えると思います。
動くサンプルはhttps://github.com/kuwalab/SpringSampleのspring_db40に作っています。
導入
導入は例のごとくMavenです。Springが依存関係が結構深いのでMaven等を使わないと面倒です。pom.xmlは以下のようにします。今回はデータベースにH2を使うので合わせて導入します。
Spring JDBCを使うためには、spring-contextの他にspring-jdbcが必要になります。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.spring_db40</groupId> <artifactId>spring_db40</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>mvcjsontest Maven Webapp</name> <url>http://maven.apache.org</url> <build> <finalName>spring_db40</finalName> <pluginManagement> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <!-- Spring Framework --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- AspectJ --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectJ.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${aspectJ.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.11</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.177</version> </dependency> </dependencies> <properties> <spring.version>4.0.3.RELEASE</spring.version> <aspectJ.version>1.7.1</aspectJ.version> <junit.version>4.11</junit.version> </properties> </project>
Spring JDBCの設定
データソースは適当に用意してください。今回は、組み込みのH2を用意します。Springの設定ファイルに以下のように記述します。
<jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:/ddl/db01/schema.sql" /> </jdbc:embedded-database>
jdbc:scriptのlocation属性で起動時に読み込むscriptを指定できます。毎回新規に作成されるようなので、何度実行しても起動時は同じ状態になっています。
scriptは単純にSQLを書けばいいです。今回はbookテーブルを作成しテスト用のデータを入れておきます。
CREATE TABLE book(
book_id INTEGER PRIMARY KEY,
book_name VARCHAR(20),
price INTEGER
);
INSERT INTO book VALUES(1,'よくわかるSpring',2000);
INSERT INTO book VALUES(2,'すぐわかるSpring',2980);
Spring JDBCはJdbcTemplateクラスを介してアクセスします。そのためのBeanも設定しておきます。
<bean class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource" /> </bean>
最低限必要な設定は以上です。
基本的なCRUD
最低限のCRUDを面倒な方法で実装してみます。一番ロジカルでわかりやすいですが、非常に面倒な方法です。まずDaoクラスの全体像を見てみます。
package com.example.spring.db01;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<Book> selectBookList() {
return jdbcTemplate.query("SELECT * FROM book", new RowMapper<Book>() {
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
Book book = new Book();
book.setBookId(rs.getInt(1));
book.setBookName(rs.getString(2));
book.setPrice(rs.getInt(3));
return book;
}
});
}
public int insert(Book book) {
return jdbcTemplate.update(
"INSERT INTO BOOK(book_id,book_name,price) VALUES(?,?,?)",
book.getBookId(), book.getBookName(), book.getPrice());
}
public int update(Book book) {
return jdbcTemplate.update(
"UPDATE book SET book_name=?,price=? WHERE book_id=?",
book.getBookName(), book.getPrice(), book.getBookId());
}
public int delete(Book book) {
return jdbcTemplate.update("DELETE FROM book WHERE book_id=?",
book.getBookId());
}
}
Bookクラスは単純なJavaBeansです。
public class Book {
private Integer bookId;
private String bookName;
private Integer price;
@Override
public String toString() {
return "Book [bookId=" + bookId + ", bookName=" + bookName + ", price="
+ price + "]";
}
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
SELECT
最初にSELECT文を見ていきます。
public List<Book> selectBookList() {
return jdbcTemplate.query("SELECT * FROM book", new RowMapper<Book>() {
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
Book book = new Book();
book.setBookId(rs.getInt(1));
book.setBookName(rs.getString(2));
book.setPrice(rs.getInt(3));
return book;
}
});
}
queryメソッドはいろいろな引数でオーバーロードされています。今回は次のものを使います。
<T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException
SQLにはパラメータを渡さない例になっています。引数のsqlを実行し、その結果をRowMapperで処理して型TのListを返します。
RowMapperはインターフェイスのため、そこで定義されているmapRowメソッドを実装する必要があります。
mapRowメソッドの定義は次のとおりです。
T mapRow(ResultSet rs, int rowNum) throws SQLException
戻り値は行ごとに取得した結果から作成するオブジェクトになります。SQLを発行した結果の行単位にmapRowメソッドが呼び出されるため、ResultSetとrowNumから必要なデータを作成します。
例では、生のJDBCの如く処理をしています。
更新
更新処理はupdateメソッドで実装します。
public int insert(Book book) {
return jdbcTemplate.update(
"INSERT INTO BOOK(book_id,book_name,price) VALUES(?,?,?)",
book.getBookId(), book.getBookName(), book.getPrice());
}
public int update(Book book) {
return jdbcTemplate.update(
"UPDATE book SET book_name=?,price=? WHERE book_id=?",
book.getBookName(), book.getPrice(), book.getBookId());
}
public int delete(Book book) {
return jdbcTemplate.update("DELETE FROM book WHERE book_id=?",
book.getBookId());
}
1番目の引数にSQL文、2番目の引数は可変長引数になっていて、?の割り当てる値を?の順番通りに指定してきます。
Spring JDBCの紹介 No. 2
Spring JDBCの2回目です。
今回は更新系の処理のパラメータ処理を通して、Spring JDBCの便利なところを確認していきます。
NamedParameterJdbcTemplate
今回はNamedParameterJdbcTemplateを使います。そのため、最初にBean定義をしておきます。
<bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource" /> </bean>
NamedParameterJdbcTemplateを使うとその名の通り、パラメーターに名前をつけて処理することができます。
パラメーターに名前をつける
INSERT処理を通して、パラメーターに名前をつける例を見ていきます。
public int insert(Book book) {
return npJdbcTemplate.update(
"INSERT INTO BOOK(book_id,book_name,price) "
+ "VALUES(:book_id,:book_name,:price)",
new MapSqlParameterSource()
.addValue("book_id", book.getBookId())
.addValue("book_name", book.getBookName())
.addValue("price", book.getPrice()));
}
INSERT文のパラメーターに?の代わりに「:」から始まるパラメーター名を指定します。パラメーター名は任意の名前を指定できます。
パラメーターに値を渡すには、MapSqlParameterSourceオブジェクトを利用します。名前の通り、パラメーター名に対して渡す値を設定します。
addValueする順番は関係ありません。パラメーターの名前でmapされるので、パラメーターの名前があっていれば問題ありません。
Javaオブジェクトでパラメーターを渡す
次に、UPDATE文を通してJavaオブジェクトからパラメータを渡す例を見ていきます。
public int update(Book book) {
BeanPropertySqlParameterSource beanProps = new BeanPropertySqlParameterSource(
book);
return npJdbcTemplate.update("UPDATE book "
+ "SET book_name=:bookName,price=:price WHERE book_id=:bookId",
beanProps);
}
INSERTと同様にSQLのパラメータには名前をつけます。ただし、今回はパラメータとして渡すJavaBeansオブジェクトのプロパティ名とします。
パラメータはBeanPropertySqlParameterSourceオブジェクトを渡します。コンストラクタでパラメータを含んだJavaBeansオブジェクトを指定することで適切に読み取ってくれます。
Spring JDBCの3回目です。
今回はSELECT時のNamedParameterJdbcTemplateのサンプルです。
今回のソースです。
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate npJdbcTemplate;
public List<Book> searchBookList1(String bookName) {
return jdbcTemplate.query("SELECT * FROM book WHERE book_name LIKE ?",
new BeanPropertyRowMapper<Book>(Book.class), bookName + "%");
}
public List<Book> searchBookList2(String bookName) {
return npJdbcTemplate.query(
"SELECT * FROM book WHERE book_name LIKE :book_name",
new MapSqlParameterSource().addValue("book_name", bookName),
new BeanPropertyRowMapper<Book>(Book.class));
}
public List<Book> searchBookList3(Book book) {
return npJdbcTemplate.query(
"SELECT * FROM book WHERE book_name LIKE :bookName",
new BeanPropertySqlParameterSource(book),
new BeanPropertyRowMapper<Book>(Book.class));
}
?にパラメータを渡す
?にパラメータを渡す例です。3番目の引数は可変長になっているため、?の数に合わせて?の順番通りにパラメータを渡していきます。
public List<Book> searchBookList1(String bookName) {
return jdbcTemplate.query("SELECT * FROM book WHERE book_name LIKE ?",
new BeanPropertyRowMapper<Book>(Book.class), bookName + "%");
}
2番目の引数にBeanPropertyRowMapperを指定しています。これは引数のclassのオブジェクトを作成し、SELECT文で取得した列名をJavaオブジェクトのプロパティに割り当てます。このとき「_」で区切られた列名の場合にはそのままのプロパティがあればそのまま、なければキャメルケースに変換して割り当てます。今回の場合book_nameやbook_idというプロパティが無いため、自動的にbookNameとbookIdに値が割り当てられています。
パラメーター名
前回と同様、?ではなくパラメーター名を指定して値を設定することができます。
public List<Book> searchBookList2(String bookName) {
return npJdbcTemplate.query(
"SELECT * FROM book WHERE book_name LIKE :book_name",
new MapSqlParameterSource().addValue("book_name", bookName),
new BeanPropertyRowMapper<Book>(Book.class));
}
JavaBeansでパラメーターを渡す
同様にJavaBeansでもパラメーターが渡せます。
public List<Book> searchBookList3(Book book) {
return npJdbcTemplate.query(
"SELECT * FROM book WHERE book_name LIKE :bookName",
new BeanPropertySqlParameterSource(book),
new BeanPropertyRowMapper<Book>(Book.class));
}