【Spring Boot】【Mybatis】アノテーションベースのマッピング実装

Spring bootでmybatisを使用する時に、アノテーションベースとXMLベースそれぞれの設定と、ネストしたオブジェクトに対してのマッピングを解説します。

前提

対象のDB・テーブル

posgreSQLを使います。

テーブルは公式のサンプルテーブルの中から

  • country
  • city
  • address

を使います。
ER図が公式にあるのでテーブルの関係は参照してください。
PostgreSQL Sample Database

オブジェクト

countryテーブルを今回メインのテーブルと見立て、
countryが1に対してcityが多となるようにオブジェクトをネストさせています。
cityが1に対してaddressが多となるようにオブジェクトをネストさせています。
また、countryの主キーは別途主キーオブジェクトを作り、ネストさせています。

Country.java

package com.example.demo.entity;

import java.sql.Timestamp;
import java.util.Set;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Country {

    private CountryId id;

    private String country;

    private Timestamp lastUpdate;

    private Set<City> city;
}

CountryId.java

package com.example.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CountryId {

    private int countryId;

}

City.java

package com.example.demo.entity;

import java.sql.Timestamp;
import java.util.Set;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class City {

    private int cityId;

    private String city;

    private Country country;

    private Timestamp lastUpdate;

    private Set<Address> address;

}

Address.java

package com.example.demo.entity;

import java.sql.Timestamp;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {

    private int addressId;

    private String address;

    private String address2;

    private String district;

    private City city;

    private String postalCode;

    private String phone;

    private Timestamp lastUpdate;

}

ディレクトリ構成

Mybatis設定

application.properties

# application.properties
spring.datasource.url=jdbc:postgresql://XXX.XXX.X.X:5432/DBName
spring.datasource.username=User
spring.datasource.password=Password
spring.datasource.driver-class-name=org.postgresql.Driver

pom.xml(dependency)

        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

アノテーションベースのマッピング実装

まず、大前提としてアノテーションベースでは結合したSQLからネストしたオブジェクトへのマッピングができません。
親となるSQLでまず親オブジェクトをマッピングし、ネストオブジェクトのマッピングはさらに別のSQLを呼び出してマッピングという流れです。

親子関係が1対1の場合(has-one)は@Oneを使用します。
親子関係が1対多の場合(has-many)は@Manyを使用します。

コード

DemoMapper.java

package com.example.demo.repository;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import com.example.demo.entity.Address;
import com.example.demo.entity.City;
import com.example.demo.entity.Country;
import com.example.demo.entity.CountryId;

@Mapper
public interface DemoMapper {
    
    @Results(id = "Country", value = {
            @Result(column = "country", property = "country"),
            @Result(column = "last_update", property = "lastUpdate"),
            
            @Result(column = "country_id", property = "id",
            one = @One(select = "selectCountryId")),
            
            @Result(column = "country_id", property = "city", 
            many = @Many(select = "selectCity"))
            //遅延読み込みを有効にする
            //many = @Many(select = "selctCity", fetchType = FetchType.LAZY))
    })
    @Select("SELECT * FROM country WHERE country_id = #{id}")
    Country selectCountry(@Param("id")Integer id);
    
    @Results(id = "CountryId", value = {
            @Result(column = "country_id", property = "countryId")
    })
    @Select("SELECT * FROM country WHERE country_id = #{id}")
    CountryId selectCountryId(Integer id);
    
    
    @Results(id = "City", value = {
            @Result(column = "city_id", property = "cityId"),
            @Result(column = "city", property = "city"),
            @Result(column = "last_update", property = "lastUpdate"),
            
            @Result(column = "city_id", property = "address", 
            many = @Many(select = "selectAddress"))
    })
    @Select("SELECT * FROM city WHERE country_id = #{id}")
    City selectCity(Integer id);
    
    
    
    @Results(id = "Address", value = {
            @Result(column = "address_id", property = "addressId"),
            @Result(column = "address", property = "address"),
            @Result(column = "address2", property = "address2"),
            @Result(column = "district", property = "district"),
            @Result(column = "postal_code", property = "postalCode"),
            @Result(column = "phone", property = "phone"),
            @Result(column = "last_update", property = "lastUpdate"),
            
    })
    @Select("SELECT * FROM address WHERE city_id = #{id}")
    Address selectAddress(Integer id);

}

説明

まず、Spring bootでMybatisを使用する場合、インタフェースに@Mapperアノテーションを付けます。

interfaceに3つのメソッドが定義されています。
それぞれに@SelectアノテーションでSQLが書かれており、メソッドが呼ばれた時にアノテーションのSQLが呼び出されます。

起点となるのはselectCountryです。
@Resultsのid属性にはマッピング対象となるオブジェクトを指定しています。(メソッドの戻り型)
value属性でフィールドとテーブルのカラムを紐づけています。

主キーカラムであるcountry_idはマッピング対象のオブジェクトでは、
CountryIdというネストされたオブジェクトの中のフィールドが該当しています。
主キーが格納されるCountryIdとCountryは1対1の関係であるため、
@OneでCountryIdへのマッピングを指定しています。

CountryIdオブジェクトへのマッピングである@Resultのcolumn属性には呼び出すSelectSQLの引数を指定しています。
property属性には親となるオブジェクトのフィールドを指定しています。(Countryのidフィールド)
@Oneのselect属性にはCountryIdを取得するメソッド名を指定しています。

CountryIdを取得するselectCountryIdの引数idには、@Oneを使用しているところのculumn属性で指定したテーブルカラムが入ります。
つまり、SQLで取得したカラムの値を次のSQLで使用しています。

Countryから見て1対多の関係であるCityに対しては@Manyを使用しています。
各属性の指定内容は@Oneと変わりません。

CityにさらにネストされたAddressに対してもやっていることはCountryの時と同じです。

今回主キー項目だけをCountryIdとしてネストオブジェクトを作成し、CountryIdに対して再度同じSQLを叩いて@Oneの使用を説明しているが、
本当はそもそも主キーは既に取得できているので

@Result(column = "country_id", property = "id.countryId")

と、そのままネストしたオブジェクトに格納できる。

結果(デバッグで変数の中を確認)

ネストされたオブジェクトへのマッピングが出来ていることが確認できます。

Xmlベースで単一SQLのネストによるマッピング実装

長くなってしまったので続きは別記事

Xmlベースで結合SQLによるマッピング実装

長くなってしまったので続きは別記事

ソース

itouoti12/mybatis_nestMapper

参考

Mapper XML ファイル(公式)
MyBatis Mapper アノテーションの使い方
MyBatis mapping @One annotation issue

mybatisカテゴリの最新記事