개발/spring-batch

[mybatis] mapper를 못 찾음 + record 매핑 이슈

방푸린 2025. 1. 21. 12:13
반응형

환경: java17, springboot3.3.4, springbatch5

2024.08.16 - [개발/spring-batch] - [spring-batch] springboot3 mybatis 설정 그리고 mapper

 

[spring-batch] springboot3 mybatis 설정 그리고 mapper

환경: springboot3, spring batch5, mybatis그동안 jpa만 주구장창 사용했어서 올만에 Mybatis 설정이다! 1. 디비 정보 등록(application.yml)2. 빈 등록@Configuration@RequiredArgsConstructor@MapperScan( value = {"com.batch.ranking.ma

bangpurin.tistory.com

 

 

이슈: 매퍼 못 찾음

@Bean(TOP_SLIDE_READER)
@StepScope
public MyBatisCursorItemReader<NoticeVo> topSlideNoticeReader(@Qualifier(GameReplicaDataSourceConfig.SESSION_FACTORY) SqlSessionFactory logDb) {
return new MyBatisCursorItemReaderBuilder<NoticeVo>().sqlSessionFactory(logDb)
    .queryId(Constant.SUDDA_NOTICE_MAPPER + "selectTopSlideNotices")
    .build();
}

위와 같이 springbatch + mybatis 조합으로 리더를 선언했는데 아래와 같이 mapper를 못 찾는 에러 발생

Caused by: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.xx.NoticeMapper.selectTopSlideNotices
	at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:1097)
	at org.apache.ibatis.session.Configuration.getMappedStatement(Configuration.java:875)
	at org.apache.ibatis.session.Configuration.getMappedStatement(Configuration.java:868)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectCursor(DefaultSqlSession.java:123)

 

datasource config 쪽에 아래와 같이 mapper scan을 달았고, 링크 어노테이션을 선언했음

@Configuration
@MapperScan(value = {"com.xx.gamereplica"},
            annotationClass = GameReplicaDataSource.class,
            ...
@SuddaGameReplicaDataSource
public interface SuddaNoticeMapper {

매퍼 인터페이스에 어노테이션 꼭 선언해야 함

 

구조

그럼에도 같은 에러가 계속 반복되었는데..

참고로 프로젝트 구조는 아래와 같다.

매퍼 인터페이스와 매퍼 xml 가 같은 main package 안에 있다. xml을 찾는 게 번거로워서 같이 두자는 의도였다.

 

해결: gradle 옵션 추가

그렇기 때문에 추가적인 작업이 필요했다.

build.gradle에 아래 추가

tasks.processResources {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

sourceSets {
    main {
        resources {
            srcDirs "src/main/java", "src/main/resources"
            include "**/*.xml"
            include "**/*.yml"
        }
    }
}

 

  • 기본 관행: 일반적으로 MyBatis 매퍼 XML 파일은 src/main/resources 디렉터리에 위치한다. 이는 Gradle 빌드 시스템에서 리소스로 자동으로 인식하고, 빌드 결과물(build/resources/main)에 포함한다.
  • 위치 변경: src/main/java에 XML 파일을 넣는 경우, 기본적으로 src/main/java는 소스 코드(Java 파일)의 경로로 간주되며, 리소스 파일로 처리되지 않는다. 따라서 빌드 과정에서 이 파일이 리소스로 인식되지 않아, MyBatis가 매퍼 파일을 찾지 못하게 된다.
  • 따라서 sourceSets 설정을 사용하여 Gradle에 src/main/java에 있는 XML 파일도 리소스로 취급하라고 명시적으로 설정한다.
  • 작동 원리: srcDirs "src/main/java", "src/main/resources" 설정을 통해 src/main/java 내의 XML 파일들을 리소스 디렉터리처럼 처리하도록 설정. 이 설정이 없으면 src/main/java는 Java 소스 코드만 포함하는 디렉터리로 인식되며, XML 파일은 빌드 결과물에 포함되지 않는다.
  • src/main/java와 src/main/resources 모두에서 XML 파일을 포함하게 되면 중복이 발생할 수 있으므로, tasks.processResources 옵션에 duplicatesStrategy를 설정하여 중복을 방지합니다.

 

 

record + xml mapper 작성 시 주의사항

record로 받을 시 resultMap이 필수며 javaType도 다 써줘야 한다(일반적으로는 optional이지만 writable property에 대해서는 javaType이 필수고 record는 불변 객체여서 writable 한 항목이 없다). javaType을 Integer로 명시했다면 레코드에서도 Integer로 받아야 한다(int 안됨)

<resultMap id="noticeVo"
    type="com.xx.notice.NoticeVo">
    <constructor>
        <idArg column="id" javaType="Integer" name="id"/>
        <arg column="type_code" javaType="Integer" name="typeCode"/>
        <arg column="startDate" javaType="LocalDateTime" name="startDate"/>
        <arg column="endDate" javaType="LocalDateTime" name="endDate"/>
        <arg column="time_interval" javaType="Integer" name="timeInterval"/>
        <arg column="title" javaType="String" name="title"/>
        <arg column="content" javaType="String" name="content"/>
        <arg column="extra_data" javaType="String" name="extraData"/>
        <arg column="regDate" javaType="LocalDateTime" name="regDate"/>
        <arg column="registrant" javaType="String" name="registrant"/>
    </constructor>
</resultMap>
public record NoticeVo(Integer id, Integer typeCode, LocalDateTime startDate, LocalDateTime endDate, Integer timeInterval, String title,
                       String content, String extraData, LocalDateTime regDate, String registrant) {

}

항목 순서, 타입 등 모든 게 중요하니 꼼꼼하게 봐야 한다. 아니면 아래 에러 발생...

Caused by: org.apache.ibatis.builder.BuilderException: Error in result map 'com.xx.gamereplica.SuddaNoticeMapper.noticeVo'. 
Failed to find a constructor in 'com.xx.notice.NoticeVo' with arg names [id, typeCode, startDate, endDate, timeInterval, title, content, extraData, regDate, registrant]. 
Note that 'javaType' is required when there is no writable property with the same name ('name' is optional, BTW). There might be more info in debug log.

 

참고로 int로 사용하고 싶으면 아래와 같이 _int로 사용하면 된다.

https://mybatis.org/mybatis-3/sqlmap-xml.html#result-maps

<resultMap id="noticeVo"
  type="com.xx.notice.NoticeVo">
  <constructor>
   <idArg column="id" javaType="_int" name="id"/>
   <arg column="type_code" javaType="_int" name="typeCode"/>
   <arg column="startDate" javaType="LocalDateTime" name="startDate"/>
   <arg column="endDate" javaType="LocalDateTime" name="endDate"/>
   <arg column="time_interval" javaType="_int" name="timeInterval"/>
   <arg column="title" javaType="String" name="title"/>
   <arg column="content" javaType="String" name="content"/>
   <arg column="extra_data" javaType="String" name="extraData"/>
   <arg column="regDate" javaType="LocalDateTime" name="regDate"/>
   <arg column="registrant" javaType="String" name="registrant"/>
  </constructor>
</resultMap>
728x90
반응형