Spring JDBCの例
このチュートリアルのテーマはSpring JDBCです。データベースはほとんどのエンタープライズアプリケーションにとって必須の要素です。そのため、Java EEフレームワークにおいてJDBCとの良好な統合が非常に重要です。
Spring JDBC (スプリング JDBC)
Spring Frameworkは、JDBC APIと優れた統合を提供し、データベース操作のロジックから、接続のオープン/クローズ、ResultSet、PreparedStatementなどのボイラープレートコードを避けるために使用できるJdbcTemplateユーティリティクラスを提供します。まずは、シンプルなSpring JDBCの例を見てみましょう。その後、どのようにJdbcTemplateクラスがモジュラーなコードの作成をサポートしてくれるかを見ていきます。リソースが適切にクローズされているかどうかを心配することなく、簡単に書くことができます。Springベースのアプリケーションを開発するためのSpring Tool Suiteは非常に便利ですので、私たちはSTSを使用してSpring JDBCアプリケーションを作成します。最終的なプロジェクトの構造は以下の画像のようになります。STSメニューからシンプルなSpring Mavenプロジェクトを作成し、お好きな名前を選択するか、私のプロジェクト名「SpringJDBCExample」を使用してください。
スプリングのJDBC依存関係
まず、Mavenプロジェクトのpom.xmlファイルにSpring JDBCとデータベースドライバを追加する必要があります。私の最終的なpom.xmlファイルは以下のようになります。
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>SpringJDBCExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring JDBC Support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
ほとんどの部分はSTSによって自動的に生成されますが、Spring Frameworkのバージョンを最新の4.0.2.RELEASEに更新しました。また、必要なアーティファクトであるspring-jdbcとmysql-connector-javaを追加しました。最初のものはSpring JDBCのサポートクラスを含み、2番目のものはデータベースドライバーです。私たちはテストの目的でMySQLデータベースを使用しているため、MySQL JConnectorのjar依存関係を追加しました。他のRDBMSを使用している場合は、依存関係を対応する変更を行う必要があります。
Spring JDBCの例 – データベースのセットアップ
私たちのアプリケーションで使用するシンプルなテーブルを作成しましょう。CRUD操作の例に使用します。
CREATE TABLE `Employee` (
`id` int(11) unsigned NOT NULL,
`name` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Spring JDBCの例 – モデルクラス
JDBC操作にはDAOパターンを使用しますので、Javaのビーンを作成しましょう。このビーンは、Employeeテーブルをモデル化します。
package com.scdev.spring.jdbc.model;
public class Employee {
private int id;
private String name;
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString(){
return "{ID="+id+",Name="+name+",Role="+role+"}";
}
}
スプリングJDBCの例 – DAOインターフェースと実装
DAOパターンでは、最初に実装したいすべての操作を宣言するインターフェースを作成します。
package com.scdev.spring.jdbc.dao;
import java.util.List;
import com.scdev.spring.jdbc.model.Employee;
//CRUD operations
public interface EmployeeDAO {
//Create
public void save(Employee employee);
//Read
public Employee getById(int id);
//Update
public void update(Employee employee);
//Delete
public void deleteById(int id);
//Get All
public List<Employee> getAll();
}
package com.scdev.spring.jdbc.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import com.scdev.spring.jdbc.model.Employee;
public class EmployeeDAOImpl implements EmployeeDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(Employee employee) {
String query = "insert into Employee (id, name, role) values (?,?,?)";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, employee.getId());
ps.setString(2, employee.getName());
ps.setString(3, employee.getRole());
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee saved with id="+employee.getId());
}else System.out.println("Employee save failed with id="+employee.getId());
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public Employee getById(int id) {
String query = "select name, role from Employee where id = ?";
Employee emp = null;
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, id);
rs = ps.executeQuery();
if(rs.next()){
emp = new Employee();
emp.setId(id);
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
System.out.println("Employee Found::"+emp);
}else{
System.out.println("No Employee found with id="+id);
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return emp;
}
@Override
public void update(Employee employee) {
String query = "update Employee set name=?, role=? where id=?";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setString(1, employee.getName());
ps.setString(2, employee.getRole());
ps.setInt(3, employee.getId());
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee updated with id="+employee.getId());
}else System.out.println("No Employee found with id="+employee.getId());
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void deleteById(int id) {
String query = "delete from Employee where id=?";
Connection con = null;
PreparedStatement ps = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
ps.setInt(1, id);
int out = ps.executeUpdate();
if(out !=0){
System.out.println("Employee deleted with id="+id);
}else System.out.println("No Employee found with id="+id);
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public List<Employee> getAll() {
String query = "select id, name, role from Employee";
List<Employee> empList = new ArrayList<Employee>();
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
con = dataSource.getConnection();
ps = con.prepareStatement(query);
rs = ps.executeQuery();
while(rs.next()){
Employee emp = new Employee();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
empList.add(emp);
}
}catch(SQLException e){
e.printStackTrace();
}finally{
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return empList;
}
}
CRUD操作の実装は理解しやすいです。DataSourceについてさらに学びたい場合は、JDBC DataSourceの例を読んでください。
春のJDBCの例 – ビーンの設定
もし上記のすべてのクラスを見ると、すべてのクラスは標準のJDBC APIを使用しており、Spring JDBCフレームワークへの参照はありません。Spring JDBCフレームワークのクラスは、Spring Beanの設定ファイルを作成し、ビーンを定義する際に画面に表示されます。Spring BeanのコンテキストファイルでDataSourceを作成し、DAO実装クラスに設定します。以下に私のSpring Beanの設定ファイルの例を示します。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employeeDAO" class="com.scdev.spring.jdbc.dao.EmployeeDAOImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/TestDB" />
<property name="username" value="scdev" />
<property name="password" value="scdev123" />
</bean>
</beans>
まず、クラスDriverManagerDataSourceのDataSourceオブジェクトを作成しています。このクラスは、使用できるDataSourceの基本的な実装を提供しています。MySQLデータベースのURL、ユーザー名、パスワードをプロパティとしてDataSourceビーンに渡しています。また、dataSourceビーンをEmployeeDAOImplビーンにセットし、Spring JDBCの実装を準備します。この実装は疎結合であり、他の実装に切り替えたり、別のデータベースサーバーに移行する場合は、ビーンの設定を対応するように変更するだけで済みます。これは、Spring JDBCフレームワークが提供する主要な利点の一つです。
Spring JDBCのテストクラス
すべてが正常に動作していることを確認するために、簡単なテストクラスを書きましょう。
package com.scdev.spring.jdbc.main;
import java.util.List;
import java.util.Random;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.scdev.spring.jdbc.dao.EmployeeDAO;
import com.scdev.spring.jdbc.model.Employee;
public class SpringMain {
public static void main(String[] args) {
//Get the Spring Context
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
//Get the EmployeeDAO Bean
EmployeeDAO employeeDAO = ctx.getBean("employeeDAO", EmployeeDAO.class);
//Run some tests for JDBC CRUD operations
Employee emp = new Employee();
int rand = new Random().nextInt(1000);
emp.setId(rand);
emp.setName("Pankaj");
emp.setRole("Java Developer");
//Create
employeeDAO.save(emp);
//Read
Employee emp1 = employeeDAO.getById(rand);
System.out.println("Employee Retrieved::"+emp1);
//Update
emp.setRole("CEO");
employeeDAO.update(emp);
//Get All
List<Employee> empList = employeeDAO.getAll();
System.out.println(empList);
//Delete
employeeDAO.deleteById(rand);
//Close Spring Context
ctx.close();
System.out.println("DONE");
}
}
従業員IDのランダムな数値を生成するために、ランダムクラスを使用しています。上記のプログラムを実行すると、以下の出力が得られます。
Mar 25, 2014 12:54:18 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
Mar 25, 2014 12:54:18 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Mar 25, 2014 12:54:19 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
Employee saved with id=726
Employee Found::{ID=726,Name=Pankaj,Role=Java Developer}
Employee Retrieved::{ID=726,Name=Pankaj,Role=Java Developer}
Employee updated with id=726
[{ID=726,Name=Pankaj,Role=CEO}]
Employee deleted with id=726
Mar 25, 2014 12:54:19 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
DONE
スプリングのJdbcTemplateの例
DAOの実装クラスを見ると、Connection、PreparedStatements、ResultSetを開いたり閉じたりするための多くの雛形コードがあります。これは、リソースを正しく閉じ忘れるとリソースリークにつながる可能性があります。これらのエラーを回避するために、org.springframework.jdbc.core.JdbcTemplateクラスを使用することができます。Spring JdbcTemplateは、Spring JDBCコアパッケージの中心的なクラスで、クエリを実行し、自動的にResultSetをパースしてオブジェクトまたはオブジェクトのリストを取得するための多くのメソッドを提供しています。必要なのは、引数をオブジェクト配列として提供し、PreparedStatementSetterやRowMapperのようなコールバックインターフェースを実装することです。異なるタイプのクエリを実行するためにSpring JdbcTemplateクラスを使用するEmployeeDAOの別の実装を見てみましょう。
package com.scdev.spring.jdbc.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.scdev.spring.jdbc.model.Employee;
public class EmployeeDAOJDBCTemplateImpl implements EmployeeDAO {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void save(Employee employee) {
String query = "insert into Employee (id, name, role) values (?,?,?)";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Object[] args = new Object[] {employee.getId(), employee.getName(), employee.getRole()};
int out = jdbcTemplate.update(query, args);
if(out !=0){
System.out.println("Employee saved with id="+employee.getId());
}else System.out.println("Employee save failed with id="+employee.getId());
}
@Override
public Employee getById(int id) {
String query = "select id, name, role from Employee where id = ?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//using RowMapper anonymous class, we can create a separate RowMapper for reuse
Employee emp = jdbcTemplate.queryForObject(query, new Object[]{id}, new RowMapper<Employee>(){
@Override
public Employee mapRow(ResultSet rs, int rowNum)
throws SQLException {
Employee emp = new Employee();
emp.setId(rs.getInt("id"));
emp.setName(rs.getString("name"));
emp.setRole(rs.getString("role"));
return emp;
}});
return emp;
}
@Override
public void update(Employee employee) {
String query = "update Employee set name=?, role=? where id=?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Object[] args = new Object[] {employee.getName(), employee.getRole(), employee.getId()};
int out = jdbcTemplate.update(query, args);
if(out !=0){
System.out.println("Employee updated with id="+employee.getId());
}else System.out.println("No Employee found with id="+employee.getId());
}
@Override
public void deleteById(int id) {
String query = "delete from Employee where id=?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
int out = jdbcTemplate.update(query, id);
if(out !=0){
System.out.println("Employee deleted with id="+id);
}else System.out.println("No Employee found with id="+id);
}
@Override
public List<Employee> getAll() {
String query = "select id, name, role from Employee";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List<Employee> empList = new ArrayList<Employee>();
List<Map<String,Object>> empRows = jdbcTemplate.queryForList(query);
for(Map<String,Object> empRow : empRows){
Employee emp = new Employee();
emp.setId(Integer.parseInt(String.valueOf(empRow.get("id"))));
emp.setName(String.valueOf(empRow.get("name")));
emp.setRole(String.valueOf(empRow.get("role")));
empList.add(emp);
}
return empList;
}
}
Spring JdbcTemplateの上記のコードに注目すべき重要なポイントは次のとおりです:
- Use of Object array to pass PreparedStatement arguments, we could also use PreparedStatementSetter implementation but passing Object array seems easy to use.
- No code related to opening and closing connections, statements or result set. All that is handled internally by Spring JdbcTemplate class.
- RowMapper anonymous class implementation to map the ResultSet data to Employee bean object in queryForObject() method.
- queryForList() method returns list of Map whereas Map contains the row data mapped with key as the column name and value from the database row matching the criteria.
SpringのJdbcTemplate実装を使用するためには、以下に示す通り、Spring Beanの設定ファイル内のemployeeDAOクラスを変更するだけです。
<bean id="employeeDAO" class="com.scdev.spring.jdbc.dao.EmployeeDAOJDBCTemplateImpl">
<property name="dataSource" ref="dataSource" />
</bean>
メインクラスを実行すると、Spring JdbcTemplateの実装による出力は通常のJDBC実装で見たものと類似しています。これがSpring JDBCの例のチュートリアルの全てです。以下のリンクからサンプルプロジェクトをダウンロードし、さらに学ぶために試してみてください。
Spring JDBC プロジェクトをダウンロードしてください。