前回の記事ではもMyBatisの使い方を紹介したけど、今回はもう一歩踏み込んで、より複雑な設定や高度な機能を見ていこう。MyBatisは設定ファイル(mybatis-config.xml)とマッパーファイル(XMLまたはアノテーション)を使ってDB操作を定義するんだけど、実はこの設定ファイル、かなり奥が深いんだ。
エイリアスの活用でコードをスッキリさせる
SQLマッピングファイルでJavaの完全修飾クラス名(com.example.Userとか)を毎回書くのは面倒だよね。そんな時に便利なのがエイリアスだ。mybatis-config.xmlでタイプエイリアスを設定しておけば、短い名前でクラスを参照できるようになる。
XML
<configuration>
<typeAliases>
<typeAlias type="com.example.User" alias="User" />
<package name="com.example.model"/>
</typeAliases>
</configuration>
これで、マッパーファイルではcom.example.Userの代わりにUserと書けるようになる。パッケージ指定だと、クラス名がそのままエイリアスになるんだ。
環境ごとの設定を切り替える
開発環境、テスト環境、本番環境でDB接続情報が違う、なんてことはよくある話だよね。MyBatisでは、environmentsタグを使って複数の環境設定を定義し、実行時に切り替えることができる。
XML
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:devdb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</dataSource>
</environment>
<environment id="production">
<transactionManager type="MANAGED"/>
<dataSource type="JNDI">
<property name="initial_context" value="java:comp/env"/>
<property name="data_source" value="jdbc/myDs"/>
</dataSource>
</environment>
</environments>
</configuration>
default属性でデフォルトの環境を指定できるし、JavaコードからSqlSessionFactoryBuilderでビルドする際に環境IDを指定すれば、簡単に環境を切り替えられるよ。
マッパーファイルの活用術
基本的なSELECTやINSERTはもうバッチリかな?ここからは、もうちょっと複雑なクエリや便利な機能を見ていこう。
結果マッピングの柔軟な設定: <resultMap>
前回の記事ではシンプルなPOJOへのマッピングを紹介したけど、DBのカラム名とJavaオブジェクトのプロパティ名が一致しない場合や、複数のテーブルを結合した結果を1つのオブジェクトにマッピングしたい場合があるよね。そんな時に威力を発揮するのが**<resultMap>**だ。
例えば、usersテーブルにuser_idというカラムがあり、JavaのUserクラスにはidというプロパティがあるとしよう。
XML
<mapper namespace="com.example.UserMapper">
<resultMap id="userResultMap" type="com.example.User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
</select>
</mapper>
idタグは主キー、resultタグはそれ以外のカラムとプロパティのマッピングを指定する。これで、DBのカラム名とJavaのプロパティ名が異なっていても、MyBatisが正しくマッピングしてくれるんだ。
さらに、複雑なオブジェクトの関連も<resultMap>で表現できる。例えば、ユーザーが複数の注文を持つ場合を考えてみよう。
Java
// User.java
public class User {
private int id;
private String name;
private List<Order> orders; // ユーザーは複数の注文を持つ
// getter, setter
}
// Order.java
public class Order {
private int orderId;
private String productName;
// getter, setter
}
XML
<mapper namespace="com.example.UserMapper">
<resultMap id="userOrdersResultMap" type="com.example.User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="orders" ofType="com.example.Order"
select="selectOrdersByUserId" column="user_id"/>
</resultMap>
<select id="selectUserWithOrders" resultMap="userOrdersResultMap">
SELECT user_id, user_name FROM users WHERE user_id = #{id}
</select>
<select id="selectOrdersByUserId" resultType="com.example.Order">
SELECT order_id, product_name FROM orders WHERE user_id = #{userId}
</select>
</mapper>
ここでは、<collection>タグを使って、UserオブジェクトのordersプロパティにOrderオブジェクトのリストをマッピングしている。select属性で別のSELECT文を指定し、その結果をコレクションとして格納するんだ。これがNested Selectと呼ばれる機能だ。
SQLの再利用: <sql>タグ
複数のSQL文で同じようなWHERE句やSELECT句を使い回したい、なんて思ったことはないかな?そんな時は**<sql>タグ**を使うと便利だよ。
XML
<mapper namespace="com.example.UserMapper">
<sql id="userColumns">
user_id, user_name, user_email
</sql>
<select id="selectAllUsers" resultType="com.example.User">
SELECT
<include refid="userColumns"/>
FROM users
</select>
<select id="selectActiveUsers" resultType="com.example.User">
SELECT
<include refid="userColumns"/>
FROM users
WHERE status = 'active'
</select>
</mapper>
<sql>タグで定義したSQLスニペットは、<include refid="...">で参照できる。これでDRY(Don’t Repeat Yourself)原則に則ったコードが書けるようになるね。
例で理解を深める
これまでの説明だけだとイメージしにくいかもしれないから、具体的な例をいくつか見てみよう。
例1: エイリアスと環境設定
mybatis-config.xml
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.example.model"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</dataSource>
</environment>
<environment id="production">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql://localhost:5432/prod_db"/>
<property name="username" value="prod_user"/>
<property name="password" value="prod_pass"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>
User.java (com.example.modelパッケージ)
Java
package com.example.model;
public class User {
private int id;
private String name;
private String email;
// コンストラクタ、getter, setter
public User() {}
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
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 getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
UserMapper.xml (com.example.mapperパッケージ)
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<insert id="insertUser" parameterType="User"> INSERT INTO users (id, name, email) VALUES (#{id}, #{name}, #{email})
</insert>
<select id="selectUserById" parameterType="int" resultType="User"> SELECT id, name, email FROM users WHERE id = #{id}
</select>
</mapper>
Main.java
Java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.example.model.User;
import com.example.mapper.UserMapper;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.Statement;
public class Main {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
// 開発環境用のSqlSessionFactoryをビルド
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
// DB初期化 (H2インメモリDB)
try (SqlSession session = sqlSessionFactory.openSession();
Connection conn = session.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255))");
session.commit();
} catch (Exception e) {
e.printStackTrace();
}
// ユーザーの挿入と取得
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user1 = new User(1, "Alice", "alice@example.com");
userMapper.insertUser(user1);
session.commit();
User fetchedUser = userMapper.selectUserById(1);
System.out.println("取得したユーザー: " + fetchedUser);
}
}
}
実行結果
取得したユーザー:
User{id=1, name='Alice', email='alice@example.com'}
例2: ResultMapとNested Select
Order.java (com.example.modelパッケージ)
Java
package com.example.model;
public class Order {
private int orderId;
private String productName;
// コンストラクタ、getter, setter
public Order() {}
public Order(int orderId, String productName) {
this.orderId = orderId;
this.productName = productName;
}
public int getOrderId() { return orderId; }
public void setOrderId(int orderId) { this.orderId = orderId; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
@Override
public String toString() {
return "Order{" +
"orderId=" + orderId +
", productName='" + productName + '\'' +
'}';
}
}
User.java (com.example.modelパッケージ) ※ordersリストを追加
Java
package com.example.model;
import java.util.List;
public class User {
private int id;
private String name;
private String email;
private List<Order> orders; // 新たに追加
// コンストラクタ、getter, setter
// ... (上記例と同じ) ...
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", orders=" + orders + // ordersも表示
'}';
}
}
UserMapper.xml
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="userOrdersResultMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
<collection property="orders" ofType="Order"
select="selectOrdersByUserId" column="user_id"/>
</resultMap>
<select id="selectUserWithOrders" resultMap="userOrdersResultMap">
SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
</select>
<select id="selectOrdersByUserId" resultType="Order">
SELECT order_id, product_name FROM orders WHERE user_id = #{userId}
</select>
<insert id="insertUser" parameterType="User">
INSERT INTO users (user_id, user_name, user_email) VALUES (#{id}, #{name}, #{email})
</insert>
<insert id="insertOrder" parameterType="Order">
INSERT INTO orders (order_id, product_name, user_id) VALUES (#{orderId}, #{productName}, #{userId})
</insert>
</mapper>
Main.java (DB初期化部分と呼び出し部分を変更)
Java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.example.model.User;
import com.example.model.Order; // Orderをインポート
import com.example.mapper.UserMapper;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays; // Arraysをインポート
public class Main {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
// DB初期化 (usersとordersテーブルを作成)
try (SqlSession session = sqlSessionFactory.openSession();
Connection conn = session.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("DROP TABLE IF EXISTS orders"); // 既存テーブル削除
stmt.execute("DROP TABLE IF EXISTS users"); // 既存テーブル削除
stmt.execute("CREATE TABLE users (user_id INT PRIMARY KEY, user_name VARCHAR(255), user_email VARCHAR(255))");
stmt.execute("CREATE TABLE orders (order_id INT PRIMARY KEY, product_name VARCHAR(255), user_id INT)");
session.commit();
} catch (Exception e) {
e.printStackTrace();
}
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
// ユーザーと注文を挿入
User userA = new User(1, "Alice", "alice@example.com");
userMapper.insertUser(userA);
userMapper.insertOrder(new Order(101, "Laptop", 1)); // userIdを追加
userMapper.insertOrder(new Order(102, "Mouse", 1));
session.commit();
User userB = new User(2, "Bob", "bob@example.com");
userMapper.insertUser(userB);
userMapper.insertOrder(new Order(201, "Keyboard", 2));
session.commit();
// ユーザーと関連する注文を取得
User fetchedUserWithOrders = userMapper.selectUserWithOrders(1);
System.out.println("取得したユーザーと注文: " + fetchedUserWithOrders);
}
}
}
実行結果
取得したユーザーと注文:
User
{id=1
,name='Alice'
,email='alice@example.com'
,orders=[Order
{orderId=101
,productName='Laptop'
},Order
{orderId=102
,productName='Mouse'
}]
}
例3: SQLの再利用 (<sql>)
例1のUserMapper.xmlでuserColumnsを定義し、それをselectAllUsersとselectActiveUsersで再利用する。
UserMapper.xml
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<sql id="userColumns">
id, name, email
</sql>
<insert id="insertUser" parameterType="User">
INSERT INTO users (<include refid="userColumns"/>) VALUES (#{id}, #{name}, #{email})
</insert>
<select id="selectAllUsers" resultType="User">
SELECT
<include refid="userColumns"/>
FROM users
</select>
<select id="selectActiveUsers" resultType="User">
SELECT
<include refid="userColumns"/>
FROM users
WHERE email IS NOT NULL
</select>
</mapper>
Main.java (selectAllUsersとselectActiveUsersの呼び出しを追加)
Java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.example.model.User;
import com.example.mapper.UserMapper;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
// DB初期化
try (SqlSession session = sqlSessionFactory.openSession();
Connection conn = session.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("DROP TABLE IF EXISTS users");
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255))");
session.commit();
} catch (Exception e) {
e.printStackTrace();
}
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.insertUser(new User(1, "Alice", "alice@example.com"));
userMapper.insertUser(new User(2, "Bob", null)); // emailがnullのユーザー
userMapper.insertUser(new User(3, "Charlie", "charlie@example.com"));
session.commit();
List<User> allUsers = userMapper.selectAllUsers();
System.out.println("全てのユーザー: " + allUsers);
List<User> activeUsers = userMapper.selectActiveUsers();
System.out.println("アクティブなユーザー: " + activeUsers);
}
}
}
実行結果
全てのユーザー:
[User
{ id=1
,name='Alice'
,email='alice@example.com'
},User
{ id=2
,name='Bob'
,email='null'
}, User
{ id=3
,name='Charlie'
,email='charlie@example.com'
}]
アクティブなユーザー:
[User
{id=1
,name='Alice'
,email='alice@example.com'
}, User
{id=3
,name='Charlie'
,email='charlie@example.com'
}]
まとめ
今回はMyBatisの応用的な使い方として、エイリアス、環境ごとの設定、<resultMap>による柔軟な結果マッピング、Nested Select、そして**<sql>タグによるSQLの再利用**について学んだ。これらの機能を使いこなせば、より複雑なアプリケーションにもMyBatisを適用できるようになるはずだ。
MyBatisは非常に柔軟で強力なO/Rマッパーなので、ぜひ色々な機能を試してみてほしい。もし「前回の記事も見てみたい!」と思った人は、ここからチェックしてみてね!
- MyBatisの基本を学ぶなら MyBatis 初級: JavaとDBをもっと仲良しに! | ToolDocs


コメント