新人エンジニアのつぶやき

日々の学びをアウトプットするためのブログです

【MyBatis】ネストしたリストに値をマッピングする方法 ~ アノテーションは大事 ~

この記事について

  • MyBatisでネストしたリスト(階層構造)をマッピングする際は注意
  • MyBatisでネストしたリストをマッピングする場合、結果を格納するクラスには@NoArgsConstructorを付与しないといけない

この問題でとても時間を要したので、備忘録を込めてまとめます。

MyBatisとは

前提

  • 以下のようなentityクラスに対応する情報をMyBatisを用いてDBから取得したい
  • クラスは全て仮の呼び名です。特に意味はありません。
  • 旅行の計画のクラスとその旅行にホテルが紐づいているイメージです

entityクラス

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class TravelPlan implements Serializable {
    private static final long serialVersionUID = 1L;

    private String ulid;
    private int status;
    private String name;
    private int area;
    private List<TravelPlanHotelCompanyBelonging> farmerList;
    private BigInteger createdId;
    private LocalDateTime createdAt;
}
@Data
public class TravelPlanHotelCompanyBelonging implements Serializable {
    private static final long serialVersionUID = 1L;

    private String travelPlanId;
    private BigInteger HotelCompanyId;
    private int planProposalStatus;
    private BigInteger createdId;
    private LocalDateTime createdAt;
    private BigInteger updatedId;
    private LocalDateTime updatedAt;
}

Repositoryクラス

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TravelPlanRepository {
  TravelPlan selectByTravelPlanId(String travelPlanId);
}
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="TravelPlanRepository">
    <resultMap id="TravelPlanMap" type="TravelPlan" autoMapping="true">
        <id property="ulid" column="ulid" />
        <result property="status" column="status" />
        <result property="name" column="name" />
        <result property="area" column="area"/>
        <result property="createdId" column="created_id"/>
        <result property="createdAt" column="created_at"/>
        <collection property="hotelCompanyList" resultMap="HotelCompanyMap" />
    </resultMap>
    <resultMap id="HotelCompanyMap" type="TravelPlanHotelCompanyBelonging">
        <id property="travelPlanId" column="hotel_travel_plan_id" />
        <id property="hotelCompanyId" column="company_id" />
        <result property="planProposalStatus" column="farmer_plan_proposal_status" />
    </resultMap>
    <sql id="selectTravelPlan">
        SELECT
            tp.ulid
            , tp.status
            , tp.name
            , tp.created_id
            , tp.created_at
            , hotel.travel_plan_id AS hotel_travel_plan_id
            , hotel.company_id
            , hotel.plan_proposal_status AS travel_plan_proposal_status
        FROM
            travel_plans as tp
            LEFT JOIN travel_plan_hotel_company_belonging as hotel
                ON hotel.travel_plan_id = tp.ulid
    </sql>
    <select id="selectByTravelPlanId" resultMap="TravelPlanMap">
        <include refid="selectTravelPlan"/>
        WHERE
            tp.ulid = #{TravelPlanId}
    </select>
</mapper>

問題

  • こんなエラー文が出た
    Caused by: org.apache.ibatis.executor.result.ResultMapException: 
    Error attempting to get column 'hotel_travel_plan_id' from result set. 
    Cause: java.lang.NumberFormatException: For input string: "01HBYRVACPMDR2NMVYF69C8W6E"
  • データセットからhotel_travel_plan_idカラムを取得し、クラスにセットしようとしているところでエラーが出ていました
  • セットする値であるhotel_travel_plan_idの型が違うのかと思い、確認するが、きちんとStringで定義しており、エラーメッセージにも For input stringと表示されていました

解決方法

結果

  • 結果を格納する親クラスに@NoArgsConstructorを付与し、デフォルトコンストラクタを定義する!!!
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class TravelPlan implements Serializable {
    private static final long serialVersionUID = 1L;

    private String ulid;
    private int status;
    private String name;
    private int area;
    private List<TravelPlanHotelCompanyBelonging> farmerList;
    private BigInteger createdId;
    private LocalDateTime createdAt;
}
  • 値をセットするための箱を用意してあげる必要があり、そこにMyBatis側でマッピングしていると想定しています
  • 少し実験してみました

値をセットする@Setterやセッターを使えるようになる@Dataなど、値をセットするようなアノテーションを使うことでマッピングできるのかが気になったので、実験してみました。以下がその結果です。

# @Data @NoArgsConstructor @Getter @Setter マッピングできるか
1 - -
2 - - - ×
3 - -
4 -
5 - - ×

上記の結果より、@NoArgsConstructorがないとマッピングはできなさそうです。

最後に

  • Spring Bootのアノテーションは未だに使いこなせていない気がしています。使いこなすと便利なので、まだまだ勉強を続けていこうと思います。