やりたいこと
- mybatisのtypehandlerを使ってみる
実行環境
- 前回と同じ
- コードも前回のものからの差分のみを記載する
前提
そもそもtypehandlerって何
- Javaで定義されているクラスをJDBCクラスへのマッピング処理を行う
- DBに値を格納する場合、Javaのクラスの値をDBで扱える型に変換するみたいなイメージ(正確な表現ではないと思う)
- DBから値を取り出す場合はその逆
どういうケースで使用するか
- JDBC側で
BLOB
型として定義している値と、Java側で byte[]
型として定義している値の相互変換をするとか
- Java側でenum型で定義している値の変換
検証
やってみたいこと
- 独自で定義したenum型のためのtypehandlerを作成し、データの取得や格納で使用する
検証用データ作成
CREATE TABLE DIVISION (
DIVISION_ID CHAR(2) NOT NULL,
DIVISION_NAME VARCHAR(32) NOT NULL,
PRIMARY KEY (DIVISION_ID)
);
INSERT INTO DIVISION (DIVISION_ID, DIVISION_NAME) VALUES ('10', '人事部');
INSERT INTO DIVISION (DIVISION_ID, DIVISION_NAME) VALUES ('20', '営業部');
INSERT INTO DIVISION (DIVISION_ID, DIVISION_NAME) VALUES ('30', '開発部');
CREATE TABLE STATUS (
ID INTEGER NOT NULL,
DESCRIPTION VARCHAR(32) NOT NULL,
PRIMARY KEY (ID)
);
INSERT INTO STATUS (ID, DESCRIPTION) VALUES (0, 'アクティブ');
INSERT INTO STATUS (ID, DESCRIPTION) VALUES (9, 'ブロック');
CREATE TABLE USERS (
USER_ID VARCHAR(32) NOT NULL,
FIRST_NAME VARCHAR(32) NOT NULL,
FAMILY_NAME VARCHAR(32) NOT NULL,
DIVISION_ID CHAR(2) NOT NULL,
STATUS INTEGER NOT NULL,
PRIMARY KEY (USER_ID),
FOREIGN KEY (DIVISION_ID) REFERENCES DIVISION (DIVISION_ID),
FOREIGN KEY (STATUS) REFERENCES STATUS(ID)
);
INSERT INTO USERS (USER_ID, FIRST_NAME, FAMILY_NAME, DIVISION_ID, STATUS) VALUES ('hogefuga', 'ほげ', 'ふが', '10', 0);
INSERT INTO USERS (USER_ID, FIRST_NAME, FAMILY_NAME, DIVISION_ID, STATUS) VALUES ('foobar', 'ふー', 'ばー', '20', 9);
Userクラスの変更
public enum Status {
ACTIVE(0), BLOCKED(9);
private int id;
Status(int id) {
this.id = id;
}
}
--- a/core/src/main/java/com/github/nkiri/core/domain/model/user/User.java
+++ b/core/src/main/java/com/github/nkiri/core/domain/model/user/User.java
@@ -7,8 +7,8 @@ public class User {
private final UserId userId;
private final String firstName;
private final String familyName;
-
private Division division;
+ private Status status;
public User(final UserId userId, final String firstName, final String familyName) {
this.userId = userId;
@@ -18,7 +18,7 @@ public class User {
public String toString() {
return String.format(
- "User {userId: %s, firstName: %s, familyName: %s, division: %s}",
- userId, firstName, familyName, division);
+ "User {userId: %s, firstName: %s, familyName: %s, division: %s, status: %s}",
+ userId, firstName, familyName, division, status);
}
}
Statusを扱うtypehandlerを作成
BaseTypeHandler
インターフェースを実装する
- とりあえずIDEが自動生成してくれるメソッドで記載
import com.github.nkiri.core.domain.model.user.Status;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class StatusTypeHandler extends BaseTypeHandler<Status> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Status status, JdbcType jdbcType) throws SQLException {
}
@Override
public Status getNullableResult(ResultSet resultSet, String s) throws SQLException {
return null;
}
@Override
public Status getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public Status getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
typehandlerの登録
- mybatisがStatusを扱うときに、StatusTypeHandlerを使用するように設定する
mybatis-config.xml
xml version="1.0" encoding="UTF-8"
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeHandlers>
<typeHandler handler="com.github.nkiri.core.infrastructure.datastore.typehandler.StatusTypeHandler" javaType="com.github.nkiri.core.domain.model.user.Status"/>
</typeHandlers>
</configuration>
applicationContext.xml
diff --git a/core/src/main/resources/applicationContext.xml b/core/src/main/resources/applicationContext.xml
index dca3030..770d657 100644
--- a/core/src/main/resources/applicationContext.xml
+++ b/core/src/main/resources/applicationContext.xml
@@ -28,6 +28,7 @@
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
+ <property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<mybatis:scan base-package="com.github.nkiri.core.infrastructure.datastore"/>
StatusTypeHandlerの実装
実装するべきメソッドについて
getNullableResult
Statusクラスにメソッドを追加
diff --git a/core/src/main/java/com/github/nkiri/core/domain/model/user/Status.java b/core/src/main/java/com/github/nkiri/core/domain/model/user/Status.java
index 0a24ebb..8866d56 100644
--- a/core/src/main/java/com/github/nkiri/core/domain/model/user/Status.java
+++ b/core/src/main/java/com/github/nkiri/core/domain/model/user/Status.java
@@ -1,5 +1,7 @@
package com.github.nkiri.core.domain.model.user;
+import java.util.Arrays;
+
public enum Status {
ACTIVE(0), BLOCKED(9);
@@ -8,4 +10,13 @@ public enum Status {
Status(int id) {
this.id = id;
}
+
+ public int getId() {
+ return id;
+ }
+
+ // StatusTypeHandlerのgetNullableResultで使用する
+ public static Status getStatus(int id) {
+ return Arrays.stream(values()).filter(v -> v.getId() == id).findFirst().get();
+ }
}
getNullableResultを実装
diff --git a/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/typehandler/StatusTypeHandler.java b/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/typehandler/StatusTypeHandler.java
index 03cb667..2b35e2f 100644
--- a/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/typehandler/StatusTypeHandler.java
+++ b/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/typehandler/StatusTypeHandler.java
@@ -17,16 +17,16 @@ public class StatusTypeHandler extends BaseTypeHandler<Status> {
@Override
public Status getNullableResult(ResultSet resultSet, String s) throws SQLException {
- return null;
+ return Status.getStatus(resultSet.getInt(s));
}
@Override
public Status getNullableResult(ResultSet resultSet, int i) throws SQLException {
- return null;
+ return Status.getStatus(resultSet.getInt(i));
}
@Override
public Status getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
- return null;
+ return Status.getStatus(callableStatement.getInt(i));
}
}
実行してみた結果
- mainメソッドは前回とか前々回と同じ感じで、
hogefuga
とfoobar
のデータを取得する
- 実行結果
User {userId: hogefuga, firstName: ほげ, familyName: ふが, division: Division {10:人事部}, status: ACTIVE}
User {userId: foobar, firstName: ふー, familyName: ばー, division: Division {20:営業部}, status: BLOCKED}
Process finished with exit code 0
setNonNullParameter
準備(ステータス更新処理を作っておく)
- UserMapperにupdateメソッドを追加する
--- a/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/UserMapper.java
+++ b/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/UserMapper.java
@@ -3,8 +3,10 @@ package com.github.nkiri.core.infrastructure.datastore;
import com.github.nkiri.core.domain.model.user.User;
import com.github.nkiri.core.domain.model.user.UserId;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
User get(UserId userId);
+ int update(@Param("user") User user);
}
--- a/core/src/main/resources/com/github/nkiri/core/infrastructure/datastore/UserMapper.xml
+++ b/core/src/main/resources/com/github/nkiri/core/infrastructure/datastore/UserMapper.xml
@@ -19,6 +19,13 @@
;
</select>
+ <update id="update" parameterType="map">
+ UPDATE USERS SET STATUS = #{user.status}
+ <where>
+ USER_ID = #{user.userId.id}
+ </where>
+ </update>
+
<resultMap id="userIdResultMap" type="com.github.nkiri.core.domain.model.user.UserId">
<constructor>
- UserRepositoryにもメソッドを追加する
--- a/core/src/main/java/com/github/nkiri/core/domain/model/user/UserRepository.java
+++ b/core/src/main/java/com/github/nkiri/core/domain/model/user/UserRepository.java
@@ -6,4 +6,5 @@ import org.springframework.stereotype.Repository;
public interface UserRepository {
User get(UserId userId);
+ void update(User user);
}
- UserクラスにStatusのsetterメソッドを追加
--- a/core/src/main/java/com/github/nkiri/core/domain/model/user/User.java
+++ b/core/src/main/java/com/github/nkiri/core/domain/model/user/User.java
@@ -21,4 +21,8 @@ public class User {
"User {userId: %s, firstName: %s, familyName: %s, division: %s, status: %s}",
userId, firstName, familyName, division, status);
}
+
+ public void setStatus(final Status status) {
+ this.status = status;
+ }
}
setNonNullParameterの実装
--- a/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/typehandler/StatusTypeHandler.java
+++ b/core/src/main/java/com/github/nkiri/core/infrastructure/datastore/typehandler/StatusTypeHandler.java
@@ -12,7 +12,7 @@ import java.sql.SQLException;
public class StatusTypeHandler extends BaseTypeHandler<Status> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Status status, JdbcType jdbcType) throws SQLException {
-
+ preparedStatement.setInt(i, status.getId());
}
@Override
テスト用の呼び出し処理
@Service
public class SampleApplicationService {
private final UserRepository userRepository;
public SampleApplicationService(final UserRepository userRepository) {
this.userRepository = userRepository;
}
public void sample() {
User user1 = userRepository.get(new UserId("hogefuga"));
System.out.println(user1);
user1.setStatus(Status.BLOCKED);
userRepository.update(user1);
User user1Updated = userRepository.get(new UserId("hogefuga"));
System.out.println(user1Updated);
}
}
実行してみた結果
ACTIVE
だったものがBLOCKED
に書き変わっていることが確認できる
User {userId: hogefuga, firstName: ほげ, familyName: ふが, division: Division {10:人事部}, status: ACTIVE}
User {userId: hogefuga, firstName: ほげ, familyName: ふが, division: Division {10:人事部}, status: BLOCKED}
Process finished with exit code 0
まとめ
- BaseTypeHandlerを使ってEnum型の変換を試した
- mybatisにはEnumTypeHandlerというクラスがあるので、本来であればそっちを使うのが望ましい
- 値をセットするときはPreparedStatement、値を取得するときはResultSetに対して、カラム名またはindexを指定して対象のデータにアクセスしているっぽいことがわかった