この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
よく訓練されたアップル信者、都元です。では今回は、前回の予告どおり、Spring環境からのDBアクセスについて見て行こうと思います。
DB環境整備(MySQL)
というわけで、今回はDBを使いますので、ローカルにMySQLをインストールしておいてください。筆者の検証環境におけるMySQLのバージョンは5.6系(Server version: 5.6.13-log Source distributionという奴)ですが、まぁまだ基本的なことしかしませんので、最新でなくても良いと思います。また、localhostからはパスワード無しでrootユーザで接続できるような環境を前提としています。適宜そのように調整するか、パスワードが必要な環境を前提とするのであれば、適宜読み替えをおこなってください。
さて、ではMySQLに接続して、とりあえずDBとスキーマを作り、適当なデータを投入しておきましょう。
CREATE DATABASE berserker CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
USE berserker;
CREATE TABLE users (
username VARCHAR(32) PRIMARY KEY,
password CHAR(60) NOT NULL
);
CREATE TABLE events (
event_id BIGINT AUTO_INCREMENT PRIMARY KEY,
event_name VARCHAR(128) NOT NULL,
event_owner VARCHAR(32),
FOREIGN KEY (event_owner) REFERENCES users (username)
);
CREATE TABLE participations (
username VARCHAR(32),
event_id BIGINT,
PRIMARY KEY (username, event_id),
FOREIGN KEY (username) REFERENCES users (username),
FOREIGN KEY (event_id) REFERENCES events (event_id)
);
CREATE TABLE pictures (
picture_id BIGINT AUTO_INCREMENT PRIMARY KEY,
location VARCHAR(255) NOT NULL,
event_id BIGINT NOT NULL,
FOREIGN KEY (event_id) REFERENCES events (event_id)
);
INSERT INTO users
(username, password)
VALUES
('miyamoto', '$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy'),
('yokota', '$2a$10$nkvNPCb3Y1z/GSearD7s7OBdS9BoLBss3D4enbFQIgNJDvr0Xincm');
ちなみに、DBに生パスワードを保存するという設計は、やってはいけません。上記はエンコード(BCrypt)によって加工したパスワードを記述しています。
依存ライブラリ定義
前回、せっかくGradleを導入しましたので、そのファイルに修正を入れます。まずライブラリのバージョンは、このようにgradle.properties内で変数として参照できるようように定義しておくと、バージョンアップや他の依存ライブラリ追加の際に楽ができます。
springVersion = 4.1.5.RELEASE
mysqlVersion = 5.1.36
Springの追加ライブラリとしては2つです。DBのトランザクション制御を担うspring-tx、JDBCをラップする各種クラスを提供するspring-jdbcです。これの他に、MySQLを使いますので、MySQLのドライバを追加しておきましょう。
dependencies {
// spring
compile "org.springframework:spring-context-support:$springVersion"
compile "org.springframework:spring-tx:$springVersion" // 追加1
compile "org.springframework:spring-jdbc:$springVersion" // 追加2
// other
// ...
compile "mysql:mysql-connector-java:$mysqlVersion" // 追加3
}
Springの設定とJava側の準備
データベース設定: DataSourceConfiguration
SpringでDBを扱う場合、大抵はjavax.sql.DataSourceの実装を使うのが基本になります。今回は最もシンプルな実装として、DriverManagerDataSourceというのを使います。
DataSourceConfigurationというクラスを作成し、以下のような内容を記述します。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class DataSourceConfiguration {
@Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/berserker");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("");
return driverManagerDataSource;
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
DriverManagerDataSourceに対してドライバやURL等、接続に必要な情報を与えます。続いて、トランザクションの制御を行うtransactionManagerを定義 *1し、アノテーションによる「宣言的トランザクション制御」を有効にします。最後に、jdbcTemplateというbeanを定義していますが、クライアントのプログラムからはこのbeanを利用します。
今回のプログラム側の基本骨格
以上を作成の上、メインのクラスを書いていきましょう。今回はDBへのアクセスを確認するだけの目的ですので、単純なmainメソッドを起点とするコンソールプログラムです。以前にもご紹介した手法ですが、DataAccessSampleをbeanとして扱い、このインスタンスにDIが可能な状況を作り出しています。
@Component
@ComponentScan
public class DataAccessSample {
public static void main(String[] args) {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(DataAccessSample.class)) {
DataAccessSample das = context.getBean(DataAccessSample.class);
das.execute();
}
}
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional
public void execute() {
// do some work
}
}
ポイントは2つ。1つ目はDataDourceConfigurationで定義したjdbcTemplateをDIしていること。もう1つはexecuteメソッドに@Transactionalというアノテーションを付けていることです。
@Transactioanlアノテーションは、トランザクション制御を宣言的に行う手法です。詳しくは次回以降にお話しますが、executeの実行直前にトランザクションを開始(begin)させ、実行直後にコミットします。executeが正常に終了せず、例外によって終了した場合はロールバックします。つまり、executeメソッド内のDB処理がアトミックになるわけです。
ということを、「手続きの記述」 *2ではなく、メソッドにアノテーションを付与するという「宣言の記述」で表現しようとする試み *3です。
あとは、usersテーブルに対応するJava beansを作っておきましょう。
@ToString
public class User {
@Getter @Setter
private String username;
@Getter @Setter
private String password;
}
いよいよDBアクセス
あとはexecuteの中からjdbcTemplateのメソッドを呼び出すだけです。まずは簡単なところから。usersテーブルの全件数を取ってみましょう。第1引数がクエリで、第2引数は戻り値の型となります。結果的に2が返るはずです。
Long allUsersCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Long.class);
System.out.println("allUsersCount = " + allUsersCount);
つづいて、miyamotoユーザのパスワードを取得。第2引数には、クエリ内に埋め込んだプレースホルダ?に割り当てるパラメータを配列で渡せます。結果は$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy。
String password = jdbcTemplate.queryForObject("SELECT password FROM users WHERE username = ?", new Object[] {
"miyamoto"
}, String.class);
System.out.println("password = " + password);
ここまでは1カラムから構成される単純な値(いわゆるスカラー値)を取得するだけでした。複数のカラムがある場合は、RowMapperというのを使います。
User user = jdbcTemplate.queryForObject("SELECT * FROM users WHERE username = ?", new Object[] {
"miyamoto"
}, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
System.out.println("user = " + user);
これで、ResultSetから取得できる各カラムの値をUserクラスのフィールドにマッピングできました。
サンプルプロジェクト berserker v4.0
2015-07-13追記:このコードを、GitHubに上げておきました。ご興味のある方は、下記のように実行してみてください。初回実行時には色々表示も異なり、実行開始まで時間が掛かります。execute1タスクが、Mainクラスの実行、execute2タスクがSpringMainクラスの実行です。ログ出力は少々異なりますが、どちらもfooという出力がされていますね。
$ git clone https://github.com/classmethod-sandbox/berserker.git
$ cd berserker
$ git checkout 4.0
$ ./gradlew execute
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:execute
2016/04/08 18:43:46.773 [main] INFO o.s.c.a.AnnotationConfigApplicationContext:578 - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@511baa65: startup date [Fri Apr 08 18:43:46 JST 2016]; root of context hierarchy
2016/04/08 18:43:47.253 [main] INFO o.s.j.d.DriverManagerDataSource:133 - Loaded JDBC driver: com.mysql.jdbc.Driver
2016/04/08 18:43:47.697 [main] INFO j.c.e.berserker.DataAccessSample:56 - allUsersCount = 2
2016/04/08 18:43:47.719 [main] INFO j.c.e.berserker.DataAccessSample:61 - password = $2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy
2016/04/08 18:43:47.723 [main] INFO j.c.e.berserker.DataAccessSample:75 - user = User(username=miyamoto, password=$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy)
2016/04/08 18:43:47.728 [main] INFO o.s.c.a.AnnotationConfigApplicationContext:960 - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@511baa65: startup date [Fri Apr 08 18:43:46 JST 2016]; root of context hierarchy
BUILD SUCCESSFUL
Total time: 2.674 secs
まとめ
と、いうのがSpringが標準で用意しているデータアクセスの基本です。もう少し使いやすい工夫がされたMappingSqlQuery等の仕組みもありますが、もう少しコードの記述がしやすくなるだけで、基本的な考え方は変わりません。ここまで見て、少々原始的(低級)な印象が強いのではないでしょうか。生のJDBCを操作するよりだいぶましですが、Javaコードの中にSQLを記述すること自体、あまりモダンな感じがしません。
しかし、これ以上の高級なAPIを利用したい場合は、Spring単独ではなく、外部のライブラリと連携する必要があります。代表的な選択肢としては、Hibernate、JDO、JPA、iBatis等があります。Springはいずれの選択肢についても、インテグレーションのサポートを提供しています。
さらに、Spring DataというSpringのサブプロジェクトを利用するという選択肢もあります。筆者は個人的にこちらの方が好みなので、次回は Spring Data についてのお話をしようと思います。
という訳で、次回は「第5回 Spring環境におけるDBアクセス(2) 〜 Spring Data篇」です。実は、データアクセスの仕組みはこっちが本命。正直JdbcTemplateとか、私はあんま使わないですよwww(ごめんなさいごめんなさいwww *4