徒然なるままに

子育てとプログラミングが同居する不思議な空間

Spring と MyBatis の備忘録

Spring Boot と MyBatis を組み合わせて使おうと思ったら以下の選択肢がある。

github.com

もともと Java Configuration にはそんなに明るくなかったこともあって、少しはまったので備忘録として残しておく。

まず、application.properties *1 に mybatis.config で MyBatis の設定ファイルを追加するか、または mybatis.mapperLocations、mybatis.typeAliasesPackage、mybatis.typeHandlersPackage を設定ファイルに追加する。何を設定できるかは MyBatisProperties クラスを参照する。また、どう使われるかは MybatisAutoConfiguration クラスの sqlSessionFactory メソッドを参照する。

私たちの会社では Controller と Service と Mapper を組み合わせるのだけど、Application (Controller) → Service → Mapper というフローにすると、Application クラスに @MapperScan("com.example.monota.spring.dao") のように DAO *2 インターフェースの場所を指定する必要がある。そうしないと、MybatisAutoConfiguration の AutoConfiguredMapperScannerRegistrar が実行されるのだが、これが実行されると以下の例でいうと SampleService に DAO がインジェクトされるというよくわからない事象になってエラーが発生する。

@SpringBootApplication
@MapperScan("com.example.monota.spring.dao")
public class SpringSampleApplication {

    public static void main(String[] args) {
        try (ConfigurableApplicationContext context = SpringApplication.run(SpringSampleApplication.class, args)) {
            SpringSampleApplication springSampleApplication = context.getBean(SpringSampleApplication.class);
            springSampleApplication.runSampleApplication();
        }
    }

    @Autowired
    private SampleService sampleService;

    public void runSampleApplication() {
        sampleService.execute();
    }
}

参考までに @MapperScan をつけなかった場合のエラーは以下のような感じ。

Exception in thread "main" org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.monota.spring.service.SampleService.execute
    at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:196)
    at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:44)
    at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:59)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52)
    at com.sun.proxy.$Proxy50.execute(Unknown Source)
    at com.example.monota.spring.SpringSampleApplication.runSampleApplication(SpringSampleApplication.java:25)
    at com.example.monota.spring.SpringSampleApplication.main(SpringSampleApplication.java:17)

AutoConfiguredMapperScannerRegistrar は MapperScan が指定されていない場合に Spring Boot の Scanner で Mapper を探し始めるので、com.example.monota.spring に SpringApplication があり、com.example.monota.spring.service に SampleService があり、com.example.monota.spring.dao に SampleDao があるような状況だと、SampleService も SampleDao もどちらもインターフェースなので、どちらも Mapper インターフェースとして認識するという結果になっているように思える。

要は @MapperScan つけましょうねというお話でした。

*1:Environment によって細かいところは変わるけど割愛

*2:正確には Mapper