【Spring】MyBatis

MyBatisを使用できるようにする

使用した環境

spring 5.3.0
DB postgreSql
ビルドツール Maven
1.(Mavenの場合)ライブラリをクラスパスに追加

pom.xml に下記のdependency を追加
(自動で使えるようにしてくれる)

<dependencies>
    <!-- JDBC -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>

    <!-- MyBatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>3.5.0</version>
    </dependency>

    <!-- MyBatis-Spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.1.0</version>
    </dependency>

    <!-- postgreSql -->
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.3.1</version>
    </dependency>

    <!-- ※今回は使用しないのでメモ書き-->
    <!-- mySQL -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
</dependencies>

!バージョンの組み合わせが間違っているとエラーになる
https://mybatis.org/spring/ja/index.html

2.設定ファイルに情報を追加

root-context.xml(applicationContext.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- SQLセッションファクトリー -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="/WEB-INF/spring/mybatis-config.xml" />
    </bean>

    <!-- postgreSql接続設定 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql://localhost:5432/(DB名)" />
        <property name="username" value="(ユーザー名)" />
        <property name="password" value="(パスワード)" />
    </bean>

    <!-- ※今回使用しないためメモ書き -->
    <!-- MySql接続設定 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:5432/(DB名)" />
        <property name="username" value="(ユーザー名)" />
        <property name="password" value="(パスワード)" />
    </bean>
</beans>
3.MyBatisの設定ファイルを作成する

紐づけは上記のSQLセッションファクトリー内で行っている

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>
    <settings>
        <!-- テーブルの項目名のアンダースコアをキャメルケースに置き換える設定(例.user_id→userId) -->
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
</configuration>
4.Beanファイルを作成する(適宜)

テーブルと同じ内容のBeanを作成しておく

public class MemberBean {
    private Integer userId;
    private String name;
    private String age;

    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
}
5.Mapperファイル(javaxml)を作成する

javaファイル(main>java内に作成)

public interface OfferRepository {

    List<MemberBean> selectMember();

    Integer deleteMember();
}

xmlファイル(main>repository内に作成)
※必ずjavaファイル側と階層とファイル名が同じになるようにしておく
例.jp>co>demo>offer>repository>OfferRepository

<?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="jp.co.demo.offer.repository.OfferRepository">

  <!-- ↓java側と同じ名前にする -->
  <select id="selectMember" resultType="jp.co.demo.offer.model.MemberBean">
    select * from member
  </select>

  <delete id="deleteMember">
    delete from member
  </delete>
</mapper>
6.mapperを登録する

【方法1】mybatis:scanを追加
servlet-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

  <!-- 指定した配下のフォルダがmapperの検索範囲となる -->
  <mybatis:scan base-package="jp.co.demo" />

</beans>

【方法2】MapperScannerConfigurerを登録
root-context.xml

<beans>

    <!-- 指定した配下のフォルダがmapperの検索範囲となる -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="jp.co.demo" />
    </bean>

</beans>

使用方法

基本的にcontrollerでなくserviceで使う

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;  // mapper

    public List<MemberBean> getMember() {
        List<MemberBean> member = offerRepository.selectMember();
        return member;
    }
}

サンプル

Select

Mapper(java)

public interface OfferRepository {
    // パターン1.パラメータなし+Beanに格納
    List<MemberBean> selectMember();

    // パターン2.パラメータあり+resultMapを使用
    MemberBean selectMemberByKey(Integer userId);

    // パターン3.パラメータがBean+単一項目を取得(※複数取れたらエラーになる)
    Integer getUserId(MemberBean memberBean);

    // パターン4.パラメータがMAP+MAPで取得
    List<Map<String, Object>> getMember(Map sqlParam);
}

Mapper(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="jp.co.demo.offer.repository.OfferRepository">

  <!-- パターン1.パラメータなし+Beanに格納 -->
  <select id="selectMember" resultType="jp.co.demo.offer.model.MemberBean">
    select * from member
  </select>

  <!-- パターン2.パラメータあり+resultMapを使用 -->
  <!-- テーブルの項目とBeanの紐づけをする -->
  <resultMap id="memberMap" type="jp.co.demo.offer.model.MemberBean">
    <result column="USER_ID" property="userId" />
    <result column="NAME" property="name" />
  </resultMap>
  <select id="selectMemberByKey" resultMap="memberMap">
    select
      user_id
      ,name
    from
      member
    where
      user_id= #{userId}
  </select>

  <!-- パターン3.パラメータがBean+単一項目を取得(※複数取れたらエラーになる) -->
  <select id="getUserId" resultType="Integer">
    select
      user_id
    from
      member
    where
      name = #{name}
      and age = #{age}
  </select>

  <!-- パターン4.パラメータがMAP+MAPで取得 -->
  <select id="getMember" resultType="map">
    select
      *
    from
      member
    where
      name = #{name}
      and age = #{age}
  </select>
</mapper>

Service(呼び出し元)

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;

    public void selectTest() {

        // パターン1.パラメータなし+Beanに格納
        List<MemberBean> member = offerRepository.selectMember();

        // パターン2.パラメータあり+resultMapを使用
        MemberBean member2 = offerRepository.selectMemberByKey(1);

        // パターン3.パラメータがBean+単一項目を取得(※複数取れたらエラーになる)
        MemberBean bean = new MemberBean();
        bean.setName("高橋");
        bean.setAge("22");
        Integer userId = offerRepository.getUserId(bean);

        // パターン4.パラメータがMap+Mapで取得
        Map<String, Object> sqlParam = new HashMap<>();
        sqlParam.put("name","高橋");
        sqlParam.put("age","22");
        List<Map<String, Object>> resultMap = offerRepository.getMember(sqlParam);

        return;
    }
}
Insert

Mapper(java)

public interface OfferRepository {
    // パターン1.パラメータがBean
    // 戻り値がint、Integer、long等であれば更新件数を返す
    Integer insertMember1(MemberBean memberBean);

    // パターン2.パラメータがBean+登録した値を取得(自動採番される値を取得するのに有効)
    // 戻り値がboolean、Booleanであれば更新結果を返す(true:更新件数1以上、false:更新件数0)
    Boolean insertMember2(MemberBean memberBean);

    // パターン3.パラメータがMap+登録した値を取得(自動採番される値を取得するのに有効)
    // 戻り値がvoidであれば反映結果を返さない
    void insertMember3(Map<String, Object> sqlParam);

    // パターン4.一括登録
    Integer insertMember4(List<MemberBean> paramList);
}

Mapper(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="jp.co.demo.offer.repository.OfferRepository">

  <!-- パターン1.パラメータがBean -->
  <insert id="insertMember1">
    insert into member (
        user_Id
      , name
      , age
    ) values (
        #{userId}
      , #{name}
      , #{age}
    )
  </insert>

  <!-- パターン2.パラメータがBean+登録した値を取得(自動採番される値を取得するのに有効) -->
  <!-- ※取得する項目がテーブルの先頭以外以外の場合、keyColumnはで項目の指定が必要 -->
  <!-- ※keyColumnとkeyPropertyはカンマ区切りで複数指定可能 -->
  <insert id="insertMember2" useGeneratedKeys="true" keyColumn="member_no" keyProperty="memberNo">
    insert into member (
        user_Id
      , name
    ) values (
        #{userId}
      , #{name}
    )
  </insert>

  <!-- パターン3.パラメータがMap+登録した値を取得(自動採番される値を取得するのに有効) -->
  <insert id="insertMember3" useGeneratedKeys="true" keyColumn="member_no" keyProperty="memberNo">
    insert into member (
        user_Id
    ) values (
        #{userId}
    )
  </insert>

  <!-- パターン4.一括登録 -->
  <insert id="insertMember4">
    insert into member (
        user_Id
      , name
      , age
    ) values
    <foreach collection="list" item="item" separator=",">
    (
        #{item.userId}
      , #{item.name}
      , #{item.age}
    )
    </foreach>
  </insert>
</mapper>

Service(呼び出し元)

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;

    public void insert() {

        // パターン1.パラメータがBean
        MemberBean bean = new MemberBean();
        bean.setUserId(5);
        bean.setName("田中");
        bean.setAge("41");
        // 戻り値がIntegerの場合、登録件数を返す
        Integer count = offerRepository.insertMember1(bean);

        // パターン2.パラメータがBean+登録した値を取得(自動採番される値を取得するのに有効)
        MemberBean bean2 = new MemberBean();
        bean.setUserId(6);
        bean.setName("中村");
        // 戻り値がBooleanの場合、登録結果を返す
        Boolean result = offerRepository.insertMember2(bean);
        // keyPropertyを指定している場合、beanに登録した値(自動採番)が格納される
        Integer memberNo = bean.getMemberNo();

        // パターン3.パラメータがMap+登録した値を取得(自動採番される値を取得するのに有効)
        Map<String, Object> sqlParam = new HashMap<>();
        sqlParam.put("userId",7);
        // 戻り値がvoidの場合、反映結果を返さない
        offerRepository.insertMember3(sqlParam);
        // keyPropertyを指定している場合、Mapに登録した値(自動採番)が格納される
        Integer memberNo2 = (Integer)sqlParam.get("memberNo");

        // パターン4.一括登録
        List<MemberBean> paramlist = new ArrayList<>();
        MemberBean bean3 = new MemberBean();
        bean3.setUserId(8);
        bean3.setName("島田");
        bean3.setAge("34");
        paramlist.add(bean3);
        bean3 = new MemberBean();
        bean3.setUserId(9);
        bean3.setName("高橋");
        bean3.setAge("13");
        paramlist.add(bean3);
        
        Integer count4 = offerRepository.insertMember4(paramlist);

        return;
    }
}
Update

Mapper(java)

public interface OfferRepository {
    // パターン1.パラメータがBean
    // 戻り値がint、Integer、long等であれば更新件数を返す
    Integer updateMember1(MemberBean memberBean);

    // パターン2.パラメータがMap
    // 戻り値がboolean、Booleanであれば更新結果を返す(true:更新件数1以上、false:更新件数0)
    Boolean updateMember2(Map<String, Object> sqlParam);

}

Mapper(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="jp.co.demo.offer.repository.OfferRepository">

  <!-- パターン1.パラメータがBean-->
  <update id="updateMember1">
    update
        member
    set
        name = #{name}
      , age = #{age}
    where
        user_id = #{userId}
  </update>

  <!-- パターン2.パラメータがMap -->
  <update id="updateMember2">
    update
        member
    set
        name = #{name}
      , update_datetime = current_timestamp
    where
        user_id = #{userId}
  </update>
</mapper>

Service(呼び出し元)

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;

    public void update() {

        // パターン1.パラメータがBean
        MemberBean bean = new MemberBean();
        bean.setUserId(5);
        bean.setName("たなか");
        bean.setAge("11");
        // 戻り値がIntegerの場合、登録件数を返す
        Integer count = offerRepository.updateMember1(bean);

        // パターン2.パラメータがMap
        Map<String, Object> sqlParam = new HashMap<>();
        sqlParam.put("userId",6);
        sqlParam.put("name","にしむら");
        // 戻り値がBooleanの場合、登録結果を返す
        Boolean result = offerRepository.updateMember2(sqlParam);

        return;
    }
}
Delete

Mapper(java)

public interface OfferRepository {
    // パターン1.パラメータがBean
    // 戻り値がint、Integer、long等であれば更新件数を返す
    Integer deleteMember1(MemberBean memberBean);

    // パターン2.パラメータがMap
    // 戻り値がboolean、Booleanであれば更新結果を返す(true:更新件数1以上、false:更新件数0)
    Boolean deleteMember2(Map<String, Object> sqlParam);
}

Mapper(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="jp.co.demo.offer.repository.OfferRepository">

  <!-- パターン1.パラメータがBean-->
  <delete id="deleteMember1">
    delete from
        member
    where
        user_id = #{userId}
        and name = #{name}
  </delete>

  <!-- パターン2.パラメータがMap -->
  <delete id="deleteMember2">
    delete from
        member
    where
        user_id = #{userId}
  </delete>
</mapper>

Service(呼び出し元)

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;

    public void delete() {

        // パターン1.パラメータがBean
        MemberBean bean = new MemberBean();
        bean.setUserId(5);
        bean.setName("たなか");
        // 戻り値がIntegerの場合、登録件数を返す
        Integer count = offerRepository.deleteMember1(bean);

        // パターン2.パラメータがMap
        Map<String, Object> sqlParam = new HashMap<>();
        sqlParam.put("userId",6);
        // 戻り値がBooleanの場合、登録結果を返す
        Boolean result = offerRepository.deleteMember2(sqlParam);

        return;
    }
}

動的SQL

if文
  <select id="selectMember1" resultType="jp.co.demo.offer.model.MemberBean">
    select
        *
    from
        member
    where
        user_id = #{userId}
    <!-- ifの条件に合致する場合のみif内の処理を挿入する-->
    <if test="name != null">
        and name like concat('%', #{name}, '%')
    </if>
  </select>

  <select id="selectMember2" resultType="jp.co.demo.offer.model.MemberBean">
    select
        *
    from
        member
    <!-- whereをつけることでいずれかの結果を返すときだけwhereを挿入-->
    <!-- また、andまたはorで始まっていた場合これを削除する-->
    <where>
      <if test="userId != null">
        user_id = #{userId}
      </if>
      <if test="name != null">
        and name = #{name}
      </if>
    </where>
  </select>
choose
  <select id="selectMember1" resultType="jp.co.demo.offer.model.MemberBean">
    select
        *
    from
        member
    <where>
    <!-- chooseをつけるとwhenの中から合致するものを1つ実行-->
    <!-- すべて合致しない場合があるためwhereをつけておく-->
      <choose>
        <when test="userId != null">
            user_id = #{userId}
        </when>
        <when test="name != null">
            and name = #{name}
        </when>
      </choose>
    </where>
  </select>
foreach
  <select id="selectMember1" resultType="jp.co.demo.offer.model.MemberBean">
    select
        *
    from
        member
    where
        user_id in
        <!-- listの中身がなくなるまで繰り返す-->
        <foreach item="item" collection="list" open="(" separator="," close=")">
            #{item}
        </foreach>
  </select>

呼出元

public interface OfferRepository {
    List<MemberBean> selectMember1(List<Integer> listId);
}

比較(<、>)を使用する場合

XMLファイルのため、<、>はエスケープ処理が必要

<!-- 「<」は「&lt;」-->
<!-- 「>」は「&gt;」-->
  <select id="selectMember1" resultType="jp.co.demo.offer.model.MemberBean">
    select
        *
    from
        member3
    where
        user_id &gt; 7 --user_id > 7と同じ
  </select>

SQL内のコメントアウト

RDBMSコメントアウトに依存する

  <select id="selectMember1" resultType="jp.co.demo.offer.model.MemberBean">
    select
        name --sql内のコメント(postgreSqlの場合--を使用)
    from
        member3
    where
        user_id = 5
  </select>

SQLの共通化(include)

  <select id="selectMember1" resultType="jp.co.demo.offer.model.MemberBean">
    select
        *
    from
        member
    where
    <!-- includeタグで共通化したsqlを呼び出す(select、fromでも使用可能)-->
    <include refid="whereMember" />
  </select>

  <!-- sqlタグ内に共通化するsqlを記載する-->
  <sql id="whereMember">
      user_id = 7
  </sql>

トランザクション

【共通の準備】
root-context.xml(applicationContext.xml)に下記を追記する。

<!-- トランザクションマネージャー -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
        <property name="rollbackOnCommitFailure" value="true" />
    </bean>
方法1.アノテーション(@Transactional)を使用する

【準備】コンテキストファイルに下記を追記する
servlet-context.xml(spring-mvc.xml)
※<annotation-driven />が記載されているファイルに<tx:annotation-driven />を追加。<annotation-driven />は消さないこと。

<beans:beans xmlns="

  xmlns:tx="http://www.springframework.org/schema/tx"

  xsi:schemaLocation="

    http://www.springframework.org/schema/tx
    https://www.springframework.org/schema/tx/spring-tx.xsd

    ">

  <tx:annotation-driven />

【使用方法】

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;

    @Transactional(rollbackFor = Exception.class)
    public void sample() {
        try {
            offerRepository.insertMember();
            offerRepository.updateMember();
            offerRepository.insertMember();
          } catch (Exception e) {

        }
        return;
    }
}

point

・サービスクラスで使用する

・メソッドまたはクラスに@Transactionalアノテーションを付与する(クラスにつけると全てのメソッドが対象)

・メソッドはpublicにする必要あり

・メソッドの開始時にトランザクションが開始され、終了時にコミットされる

・例外処理の必要あり。例外処理時にロールバックされる

・rollbackForを省略するとRuntimeException(例外処理が必須でない)時のみロールバックされる

・DB操作でのエラーでのみロールバックされる(DB操作は上手くいって、他処理でエラーになってもコミットされる)

方法2.Bean定義ファイルを使用する

【準備】AOPを使用できるようにする
(Mavenの場合)pom.xmlに下記を記載してライブラリを取り込む

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>${org.springframework-version}</version>
    </dependency>

【準備】コンテキストファイルに下記を追記する
servlet-context.xml(spring-mvc.xml)
・メソッド名がtxExecuteやtxInsertから始まるものを対象にトランザクションを開始する
・対象のクラスの範囲はjp.co.demo.○でクラス名が「~Service」となっているものが対象

<beans:beans xmlns="

  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:aop="http://www.springframework.org/schema/aop"

  xsi:schemaLocation="

    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd

    ">
    <!-- 宣言的トランザクションの織り込み -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager" >
        <tx:attributes>
           <tx:method name="txExecute*" propagation="REQUIRED" rollback-for="java.lang.Exceptoin" />
           <tx:method name="txInsert*" propagation="REQUIRED" rollback-for="java.lang.Exceptoin" />
           <tx:method name="txUpdate*" propagation="REQUIRED" rollback-for="java.lang.Exceptoin" />
           <tx:method name="txDelete*" propagation="REQUIRED" rollback-for="java.lang.Exceptoin" />
           <tx:method name="txSelect*" read-only="true" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:advisor pointcut="execution(* jp.co.demo..*Service.*(..))" advice-ref="txAdvice" />
    </aop:config>

【使用方法】
・@Transactionalは不要
・クラス名、メソッド名をコンテキストファイルで定義した名前のルールに合わせる

@Service
public class OfferService {

    @Autowired
    OfferRepository offerRepository;

    public void txExecuteSample() {
        try {
            offerRepository.insertMember();
            offerRepository.updateMember();
            offerRepository.insertMember();
        } catch (Exception e) {

        }

        return;
    }
}