MyBatis入門
Posted on May 04, 2019 at 15:30 (JST)
MyBatis3+Java8+Mavenで試したことのメモ。
今回作成したコードはこちら
動作環境
OS: macOS Mojave ver. 10.14.2
Java: 1.8.0_181
この記事に書いてあること
- MyBatis migrations maven plugin を使ってDBバージョン管理
- MyBatis generator plugin を使ってDBアクセス関連コードの自動生成
- 自動生成したコードの使用方法
MyBatis migrations maven plugin を使ってDBバージョン管理
1. migrations-maven-plugin
を使用するための設定をpom.xml
にくわえる
[pom.xml(抜粋)]
<plugin>
<groupId>org.mybatis.maven</groupId>
<artifactId>migrations-maven-plugin</artifactId>
<version>1.1.2</version>
<configuration>
<repository>migration</repository>
<output>dist/migration-name.sql</output>
</configuration>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
</dependency>
</dependencies>
</plugin>
2. バージョン管理に必要なファイルを自動生成する
下記コマンドを実行する。
$ ./mvnw migration:init
以下のファイルが生成される。
3. DB接続情報を記載する
[migration/environments/development.properties(抜粋)]
## JDBC connection properties.
driver=org.postgresql.Driver
url=jdbc:postgresql://localhost:5432/test_db
username=root
password=password
4. マイグレーション用のDDLを配備する
DDLを migration/scripts
ディレクトリ直下に配備する。
ファイル名はのプリフィックス(日時)は 20xxxxxxxxxxxx_first_migration.sql
より未来にすること。
例) migration/scripts/20190427130000_2nd_migration.sql
5. status確認
migration:status
でマイグレーションの適用状態がわかる。
$ ./mvnw migration:status
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example:java8-maven-exercise >------------------
[INFO] Building java8-maven-exercise 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- migrations-maven-plugin:1.1.2:status (default-cli) @ java8-maven-exercise ---
[INFO] Executing MyBatis Migration Schema StatusCommand
[INFO] ID Applied At Description
[INFO] ================================================================================
[INFO] 20190503172642 ...pending... create changelog
[INFO] 20190503172643 ...pending... first migration
[INFO] 20190427130000 ...pending... 2nd migration
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.643 s
[INFO] Finished at: 2019-05-04T02:33:26+09:00
[INFO] ------------------------------------------------------------------------
6. migration実行
migration:up
を実行するとDBが最新の状態になる。
$ ./mvnw migration:up
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.example:java8-maven-exercise >------------------
[INFO] Building java8-maven-exercise 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- migrations-maven-plugin:1.1.2:up (default-cli) @ java8-maven-exercise ---
[INFO] Executing MyBatis Migration Schema UpCommand
[INFO] ========== Applying: 20190503172642_create_changelog.sql =======================
[INFO]
[INFO] ========== Applying: 20190503172643_first_migration.sql ========================
[INFO]
[INFO] ========== Applying: 20190427130000_2nd_migration.sql ========================
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.788 s
[INFO] Finished at: 2019-05-04T02:34:20+09:00
[INFO] ------------------------------------------------------------------------
dropをマイグレーション用Scriptに記述しておけば、migration:down
でデグレーションできる。
MyBatis generator plugin を使ってDBアクセス関連コードの自動生成
1. mybatis-generator-maven-plugin
を使用するための設定をpom.xml
にくわえる
[pom.xml(抜粋)]
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
2. コード生成に必要なファイルを配備する
[resources/generatorConfig.xml]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration >
<classPathEntry location="/Users/someone/.m2/repository/org/postgresql/postgresql/42.2.5/postgresql-42.2.5.jar"/>
<context id="context1" >
<jdbcConnection driverClass="org.postgresql.Driver" connectionURL="jdbc:postgresql://localhost:5432/test_db" userId="root" password="password" />
<javaModelGenerator targetPackage="com.example.exercise.generated" targetProject="src/main/java/" />
<sqlMapGenerator targetPackage="com.example.exercise.generated" targetProject="src/main/resources/" />
<javaClientGenerator targetPackage="com.example.exercise.generated" targetProject="src/main/java/" type="XMLMAPPER" />
<table schema="public" tableName="%" />
</context>
</generatorConfiguration>
targetPackage
は生成したコードの出力先を指定する。
それぞれ異なるパッケージにした場合、実行時エラーとなったので全て同じパッケージにしている。
3. 自動生成コマンドを実行する
下記コマンドを実行すると指定したディレクトリにXxx.java
XxxExample.java
XxxMapper.java
XxxMapper.xml
が生成される。
$ ./mvnw mybatis-generator:generate
Xxx.java
テーブルに対応するEntityクラス。
カラムに対応するフィールドと、それに対するGetter/Setterが定義されている。
(コード例省略。)
XxxExample.java
Where句の条件を組み立てるためのクラス。
ビルダーパターンで組み立てて使用する。
[CustomersExample.java(抜粋)]
public class CustomersExample {
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table public.customers
*
* @mbg.generated Sat May 04 03:04:05 JST 2019
*/
public Criteria createCriteria() {
Criteria criteria = createCriteriaInternal();
if (oredCriteria.size() == 0) {
oredCriteria.add(criteria);
}
return criteria;
}
/**
* This class was generated by MyBatis Generator.
* This class corresponds to the database table public.customers
*
* @mbg.generated Sat May 04 03:04:05 JST 2019
*/
protected abstract static class GeneratedCriteria {
protected List<Criterion> criteria;
public Criteria andFamilyNameIsNull() {
addCriterion("family_name is null");
return (Criteria) this;
}
public Criteria andFamilyNameEqualTo(String value) {
addCriterion("family_name =", value, "familyName");
return (Criteria) this;
}
public Criteria andFamilyNameNotEqualTo(String value) {
addCriterion("family_name <>", value, "familyName");
return (Criteria) this;
}
public Criteria andEmailIsNull() {
addCriterion("email is null");
return (Criteria) this;
}
public Criteria andEmailEqualTo(String value) {
addCriterion("email =", value, "email");
return (Criteria) this;
}
public Criteria andEmailLike(String value) {
addCriterion("email like", value, "email");
return (Criteria) this;
}
<他省略。すべてのフィールドに対し様々な条件が指定できる>
}
XxxMapper.java
DBアクセスにて使用頻度の高いメソッドを定義したインターフェース。
#insert
や#updateByPrimaryKey
など、ここで定義されているものは自分でロジックを実装することなく利用できる。
実装は後述するXxxMapper.xmlに記載されている。
[CustomersMapper.java(抜粋)]
package com.example.exercise.generated;
import com.example.exercise.generated.Customers;
import com.example.exercise.generated.CustomersExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface CustomersMapper {
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table public.customers
*
* @mbg.generated Sat May 04 03:04:05 JST 2019
*/
List<Customers> selectByExample(CustomersExample example);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table public.customers
*
* @mbg.generated Sat May 04 03:04:05 JST 2019
*/
Customers selectByPrimaryKey(Long id);
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table public.customers
*
* @mbg.generated Sat May 04 03:04:05 JST 2019
*/
int updateByPrimaryKey(Customers record);
}
XxxMapper.xml
DBアクセスにて使用頻度の高いメソッドを定義したinterface。
#insert
や#updateByPrimaryKey
など、ここで定義されているものは自分でロジックを実装することなく利用できる。
[CustomersMapper.xml(抜粋)]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.exercise.generated.CustomersMapper">
<resultMap id="BaseResultMap" type="com.example.exercise.generated.Customers">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="given_name" jdbcType="VARCHAR" property="givenName" />
<result column="family_name" jdbcType="VARCHAR" property="familyName" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
</resultMap>
<sql id="Example_Where_Clause">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
id, given_name, family_name, email, created_at
</sql>
<select id="selectByExample" parameterType="com.example.exercise.generated.CustomersExample" resultMap="BaseResultMap">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from public.customers
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
select
<include refid="Base_Column_List" />
from public.customers
where id = #{id,jdbcType=BIGINT}
</select>
<update id="updateByPrimaryKey" parameterType="com.example.exercise.generated.Customers">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Sat May 04 03:04:05 JST 2019.
-->
update public.customers
set given_name = #{givenName,jdbcType=VARCHAR},
family_name = #{familyName,jdbcType=VARCHAR},
email = #{email,jdbcType=VARCHAR},
created_at = #{createdAt,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
自動生成したコードの使用方法
src/main/resources
ディレクトリ下に接続情報などを記載したconfigを配備する。
[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>
<environments default="develop">
<environment id="develop">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/test_db" />
<property name="username" value="root" />
<property name="password" value="password" />
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.example.exercise.generated" />
</mappers>
</configuration>
利用方法はこんな感じ。
[GeneratedCodeTest.java]
package com.example.exercise.generated;
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 org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.io.IOException;
import java.io.Reader;
import java.sql.Date;
import java.util.List;
import static org.junit.Assert.*;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class GeneratedCodeTest {
private static final long PRODUCT_ID = 777;
@BeforeClass
public static void init() {
try (Reader r = getConfigAsReader()) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
try (SqlSession session = factory.openSession()) {
ProductsMapper mapper = session.getMapper(ProductsMapper.class);
mapper.deleteByPrimaryKey(PRODUCT_ID);
session.commit();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void _01_insertできる() {
try (Reader r = getConfigAsReader()) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
try (SqlSession session = factory.openSession()) {
Products product = new Products();
product.setId(PRODUCT_ID);
product.setName("textbook");
product.setSalesStartDate(Date.valueOf("2019-05-01"));
ProductsMapper mapper = session.getMapper(ProductsMapper.class);
int countInserted = mapper.insert(product);
assertEquals(1, countInserted);
session.commit();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void _02_指定した項目のみupdateできる() {
try (Reader r = getConfigAsReader()) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
try (SqlSession session = factory.openSession()) {
Products product = new Products();
product.setId(PRODUCT_ID);
product.setSalesEndDate(Date.valueOf("2019-09-10"));
ProductsMapper mapper = session.getMapper(ProductsMapper.class);
int countUpdated = mapper.updateByPrimaryKeySelective(product);
assertEquals(1, countUpdated);
session.commit();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void _03_Exampleを使用してselectできる() {
try (Reader r = getConfigAsReader()) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(r);
try (SqlSession session = factory.openSession()) {
ProductsExample ex = new ProductsExample();
ex.createCriteria()
.andNameLike("text%")
.andSalesEndDateIsNotNull();
ProductsMapper mapper = session.getMapper(ProductsMapper.class);
List<Products> products = mapper.selectByExample(ex);
assertEquals(0, products.get(0).getName().indexOf("text"));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static Reader getConfigAsReader() throws IOException {
return Resources.getResourceAsReader(Thread.currentThread().getContextClassLoader(),
"mybatis-config.xml");
}
}
以上。