😺

[SpringBoot2.7][MyBatis]MapperインターフェースとMapperXMLの階層違いによるエラーの解消法

2023/03/04に公開

環境

  • Java -> 11
  • SpringBoot -> 2.7.8
  • org.mybatis.spring.boot:mybatis-spring-boot-starter -> 2.3.0

フォルダ構成

src
├── java
│   └── com
│       └── course
│           └── apispringbootmybatis
│               ├── ApiSpringbootMybatisApplication.java
│               ├── application
│               │   ├── controller
│               │   │   ├── EmployeeController.java
│               │   │   └── message
│               │   │       ├── EmployeeRequest.java
│               │   │       └── EmployeeResponse.java
│               │   └── exception
│               │       └── EmployeeNotFoundException.java
│               ├── config
│               │   └── ModelMapperConfig.java
│               ├── domain
│               │   ├── dto
│               │   │   ├── EmployeeDto.java
│               │   │   ├── HistoryDto.java
│               │   │   └── PersonalDto.java
│               │   ├── entity
│               │   │   ├── EmployeeEntity.java
│               │   │   ├── HistoryEntity.java
│               │   │   └── PersonalEntity.java
│               │   └── service
│               │       ├── EmployeeService.java
│               │       ├── impl
│               │       │   └── EmployeeServiceImpl.java
│               │       └── logic
│               │           ├── EmployeeLogic.java
│               │           └── impl
│               │               └── EmployeeLogicImpl.java
│               ├── enums
│               │   ├── Department.java
│               │   └── Gender.java
│               └── infrastructure
│                   └── mapper
│                       ├── EmployeeMapper.java
│                       ├── HistoryMapper.java
│                       └── PersonalMapper.java
└── resources
    ├── application.properties
    ├── mapper
    │   ├── EmployeeMapper.xml
    │   ├── HistoryMapper.xml
    │   └── PersonalMapper.xml
    └── mybatis-config.xml

コード

application.properties
# data source
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3006/company
spring.datasource.username=user
spring.datasource.password=password

# mybatis
mybatis.check-config-location=true
mybatis.config-location=classpath:/mybatis-config.xml
src/main/java/com/course/apispringbootmybatis/infrastructure/mapper/EmployeeMapper.java
/**
 * Employeeテーブル Mapperクラス
 */
@Mapper
public interface EmployeeMapper {

  /**
   * 社員IDに該当する社員情報の取得.
   *
   * @param employeeId 社員ID
   * @return 社員情報
   */
  Optional<EmployeeDto> selectById(@Param("employeeId") String employeeId);

}

src/main/resources/mapper/EmployeeMapper.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="com.course.apispringbootmybatis.infrastructure.mapper.EmployeeMapper">

  <!-- 取得結果とDtoとのマッピング定義-->
  <resultMap id="employeeMap" type="EmployeeDto">
    <id property="employeeId" column="EMP_EMPLOYEE_ID"/>
    <result property="employeeName" column="EMP_EMPLOYEE_NAME"/>
    <result property="gender" column="EMP_GENDER"/>
    <result property="department" column="EMP_DEPARTMENT_ID" />
    <association property="personal" javaType="PersonalDto">
      <id property="employeeId" column="PD_EMPLOYEE_ID"/>
      <result property="birthday" column="PD_BIRTHDAY"/>
      <result property="telephoneNumber" column="PD_TELEPHONE_NUMBER"/>
      <result property="mailAddress" column="PD_MAIL_ADDRESS"/>
    </association>
    <collection property="historyList" ofType="HistoryDto">
      <!-- 複合主キー -->
      <id property="startDate" column="HST_START_DATE"/>
      <id property="employeeId" column="HST_EMPLOYEE_ID"/>
      <id property="departmentId" column="HST_DEPARTMENT_ID"/>
      <result property="content" column="HST_CONTENT"/>
    </collection>
  </resultMap>

  <select id="selectById" resultMap="employeeMap">
    SELECT
      EMP.EMPLOYEE_ID as EMP_EMPLOYEE_ID,
      EMP.EMPLOYEE_NAME as EMP_EMPLOYEE_NAME,
      EMP.GENDER as EMP_GENDER,
      EMP.DEPARTMENT_ID as EMP_DEPARTMENT_ID,
      PD.EMPLOYEE_ID as PD_EMPLOYEE_ID,
      PD.BIRTHDAY as PD_BIRTHDAY,
      PD.TELEPHONE_NUMBER as PD_TELEPHONE_NUMBER,
      PD.MAIL_ADDRESS as PD_MAIL_ADDRESS,
      HST.START_DATE as HST_START_DATE,
      HST.EMPLOYEE_ID as HST_EMPLOYEE_ID,
      HST.DEPARTMENT_ID as HST_DEPARTMENT_ID,
      HST.CONTENT as HST_CONTENT
    FROM
      EMPLOYEE EMP
        LEFT JOIN PERSONAL PD
          ON
          EMP.EMPLOYEE_ID = PD.EMPLOYEE_ID
        LEFT JOIN HISTORY HST
          ON
          EMP.EMPLOYEE_ID = HST.EMPLOYEE_ID
    WHERE
      EMP.EMPLOYEE_ID = #{employeeId}
  </select>

</mapper>

エラー内容

Invalid bound statement (not found): com.course.apispringbootmybatis.infrastructure.mapper.EmployeeMapper.selectById

原因と対策

原因

EmployeeMapper#selectByIdを実行しようと、プログラムがselectByIdのSQLが定義されているEmployeeMapper.xmlを探しに行ったが、見つけられないためエラーとなっています。

SpringBoot+MyBatisでは、例えば下記のように、MapperインターフェースとMapperXMLのパスが同一階層の場合のみ自動でMapperXMLを読み込んでくれます。

ファイル 階層
Mapperインターフェース src/main/java/infrastructure/mapper/EmployeeMapper.java
MapperXML src/main/resources/infrastructure/mapper/EmployeeMapper.xml

現在のコードでは、MapperインターフェースとMapperXMLが同一階層にないことで、MapperXMLを自動で読み込んでくれず、期待するファイルが見つからないためエラーが発生しています。

対策

大きく2つの方法があります。

1. フォルダ階層を合わせる

MapperインターフェースとMapperXMLのパスが同一階層にすれば、自動でMapperXMLを読み込んでくれます。
ただ、今回のケースでいうと、resource配下のフォルダ階層が深くなってしまうのでこちらを採用するのは辞めます。

2. MapperXMLの格納場所をapplication.propertiesに定義する

こちらの方法であれば、MapperインターフェースとMapperXMLのパスが同一階層になくても、MapperXMLを読み込むことができます。

application.properties
mybatis.mapper-locations=classpath*:/mapper/*.xml

こちらの対策をすることで、エラーが解消しました。

Discussion