argius note

プログラミング関連

MyBatisの@SelectProviderでListやMapの値をバインド変数に指定

MyBatisの@SelectProviderマッピングで、Listの要素をバインド変数に指定する方法がやっと分かったのでメモ。
Mapの場合も合わせて書いておきます。


環境

今回試した環境は以下の通り。おそらくですがMyBatisがver.3なら問題ないと思います。

  • MyBatis 3.4.6
    • mybatis-spring-boot-starter 1.3.2
  • Java 8 以上

準備

以下のコードを試すには、SpringBootが必要です。

ここでは、動作を簡単に確認できるように、SpringBootのCommandLineRunnerを使って実行できるようにしておきます。
MyBatis側は、app.model.Taskと関連するORMがセッティング済みとします。

  • App - CommandLineRunnerの実装サンプル
package app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import app.dao.TaskDao;

@EnableAutoConfiguration
@EnableConfigurationProperties
@ComponentScan("app") // 今回はスキャン対象がこのクラスのサブパッケージにあるので必須ではない
public final class App implements CommandLineRunner {

    @Autowired
    TaskDao dao;

    @Override
    public void run(String... args) throws Exception {
        // ここにメイン処理を書く
    }

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(App.class);
        application.setWebEnvironment(false);
        ApplicationContext context = application.run();
        SpringApplication.exit(context);
    }
}

@SelectProviderの実装サンプルと呼び出し例

Mapの場合は、参考ページに書いてあったので分かりましたが、肝心のListの場合はググっても見つけられなかったので、それっぽく書いてみたらできました。

具体的には...@Param("keywords") List<String>に対して#{keywords[0]}と書けば、そのバインド変数にkeywords.get(0)の値が割当てられます。

  • TaskDao - @SelectProviderの実装サンプル
package app.dao;

import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.jdbc.SQL;
import app.model.Task;

@Mapper
public interface TaskDao {

    @SelectProvider(type = SqlProvider.class, method = "findBy")
    @ResultMap("app.dao.TaskMapper.BaseResultMap")
    List<Task> findBy(@Param("keywords") List<String> keywords, @Param("params") Map<String, Object> params);

    static final class SqlProvider {
        public SqlProvider() {
        }
        public String findBy(@Param("keywords") List<String> keywords, @Param("params") Map<String, Object> params) {
            return new SQL() {{
                SELECT("*");
                FROM("tasks");
                // ※このLIKE句はMySQLだとエラーにはなりませんが正しく動作しませんでした
                // SQL を 'like #{...}'に、 値を %xxx% にすれば動作します
                for (int i = 0, n = keywords.size(); i < n; i++) {
                    WHERE("title || ' ' || body like '%'|| #{keywords[" + i + "]} ||'%'");
                }
                if (params.containsKey("word")) {
                    WHERE("title || ' ' || body like '%'|| #{params.word} ||'%'");
                }
            }}.toString();
        }
    }
}
  • TaskDaoの呼び出し例
// importは省略

// Appのrunメソッド

List<String> keywords = new ArrayList<>(Arrays.asList("meeting", "shopping"));
Map<String, Object> params = new HashMap<>();
params.put("word", "urgent");
List<Task> records = dao.findBy(keywords, params);
records.stream().forEach(x -> System.out.println(x.getTitle()));
11:31:27.154 [main] DEBUG app.dao.TaskDao.findBy - ==>  Preparing: SELECT * FROM tasks WHERE (title || ' ' || body like '%'|| ? ||'%' AND title || ' ' || body like '%'|| ? ||'%' AND title || ' ' || body like '%'|| ? ||'%')
11:31:27.205 [main] DEBUG app.dao.TaskDao.findBy - ==> Parameters: meeting(String), shopping(String), urgent(String)

参考URL

MyBatis で生SQLを叩きたい (Qiita)
https://qiita.com/kumazo/items/72ecdb2923b77aaa0c94

MyBatis - MyBatis 3 | SQL ビルダークラス
http://www.mybatis.org/mybatis-3/ja/statement-builders.html

(おわり)