[Spring Boot] JPA와 MyBatis 동시에 사용하기

2019. 12. 27. 15:55Spring/Basic

300x250
반응형
  1. Mapper(MyBatis)와 JPA를 함께 사용해야하는 이유?
  2. Mapper 작성 및 sqlSessionFactory Bean 생성
    2-1) 패키지 구조
    2-2) Mapper 인터페이스 작성
    2-3) SQL Map XML 작성
    2-4) sqlSessionFactory Bean 생성

Plus tip. sqlSessionTemplate Bean을 이용한 조회


1. Mapper(MyBatis)와 JPA를 함께 사용해야하는 이유?

JPA는 분명히 장점이 큽니다.
간단한 조회 쿼리를 JPA를 이용하면 매우 직관적으로 만들 수 있습니다.

SELECT * FROM notice WHERE id = :id
Optional<Notice> findById(long id);

그러나, JPA를 이용해서 복잡한 쿼리를 나타내는 것에는 한계가 있습니다.
그다지 복잡하지 않은 아래의 쿼리를 나타내기 위해

SELECT * FROM notice
WHERE create_timestamp = :createTimestamp1 AND status = :status1
    OR  create_timestamp >= :createTimestamp2 AND STATUS = :status2

이렇게 긴 메서드를 정의해야하며,

List<Notice> findAllByCreateTimestampAndStatusOrCreateTimestampGreaterThanEqualAndStatus(Timestamp createTimestamp1, String status1, Timestamp createTimestamp2, String status2);

이 마저도 CURDATE나 DATE와 같은 SQL 함수를 이용하기 위해서는
@Query를 이용해야 합니다.

SELECT * FROM notice
WHERE create_timestamp = :createTimestamp1 AND status = :status1 AND DATE(open_timestamp) < CURDATE()
    OR create_timestamp >= :createTimestamp2 AND STATUS = :status2 AND
    DATE(open_timestamp) < CURDATE()
@Query("SELECT e FROM #{#entityName} e WHERE e.createTimestamp = :createTimestamp1 AND e.status = :status AND DATE(e.openTimestamp) < CURDATE()"
			+ " OR e.createTimestamp >= :createTimestamp2 AND e.status = :status2 AND DATE(e.openTimestamp) < CURDATE()")
List<Notice> findAllByCreateTimestampAndStatusOrCreateTimestampGreaterThanEqualAndStatus(@Param("createTimestamp1") Timestamp createTimestamp1,
    @Param("status1") String status1, @Param("createTimestamp2") Timestamp createTimestamp2, @Param("status2") String status2);

위의 커리 역시 비교적 복잡하지 않은 쿼리라 JPA로 작성이 가능했던 것이며,
통계와 같은 복잡한 쿼리를 작성하려면 SQL 을 직접 작성해야하는 경우가 불가피 할 것입니다.

보통의 경우에는 JPA를 이용하며 복잡한 쿼리를 조회할 때엔 Mapper를 이용하는 것을 권장합니다.


2. Mapper 작성 및 sqlSessionFactory Bean 생성


2-1) 패키지 구조

Spring Boot Tutorial에서 이용하고 있는 demo 프로젝트에 MyBatis 설정을 추가해봅니다.

기존에 JPA를 이용할 때에는 아래의 과정을 거쳤었다면,
Controller -> Service -> Repository(JpaRepository 상속 인터페이스)

Mapper 이용은 아래의 과정을 거칩니다.
Controller -> Service -> Mapper(인터페이스) -> xml(SQL Map XML)

demo 프로젝트에서는 현재 User, Store 엔티티가 존재하는데.
각각의 Mapper 인터페이스와 mybatis 파일을 만들어보도록 하겠습니다.

Spring Boot환경에서 프로퍼티를 이용한 JPA 설정 부분을 먼저 선행해주세요.


01

me.jiniworld.demo.mapper 패키지에 Mapper 인터페이스 를 생성하고,
resources 아래 mapper 폴더에 SQL Map xml 파일 들을 생성했습니다.


2-2) Mapper 인터페이스 작성

package me.jiniworld.demo.mapper;

import java.util.Optional;
import org.apache.ibatis.annotations.Select;
import me.jiniworld.demo.models.entities.User;

public interface UserMapper {

	public Optional<User> findById(long id);

	@Select("SELECT * FROM user where id = #{id}")
	public Optional<User> findById2(long id);

}

Mapper 인터페이스에 mybatis와 매칭될 id를 메서드명으로 선언합니다.

JPA에서 이용했던 @Query와 유사하게, ln 11과 같이 메서드 위에 애너테이션을 붙여 쿼리를 바로 작성하는 방법도 있습니다. 이럴 경우에는 SQL Map xml에 별도로 쿼리를 작성할 필요가 없습니다.


2-3) SQL Map XML(Mapper 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="me.jiniworld.demo.mapper.UserMapper">
    <select id="findById" resultType="User" parameterType="Long">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

mybatis 문서 DTD를 추가하고, ln 5 부터 mapper를 작성합니다.
mapper의 namespace는 2-2에서 작성한 Mapper 인터페이스의 풀네임을 작성하면 됩니다.(패키지 + 인터페이스명)

Mapper 인터페이스에서 선언했던 메서드명을 <select> 태그의 id로 설정하며
parameterType에 파라미터를 넣고, resultType에 리턴 객체 타입을 설정합니다.

User는 우리가 기존에 이용하고 있는 User엔티티 객체를 의미하며, MyBatis 설정에서 도메인 Alias 설정을 할 경우 도메인의 풀네임(패키지 포함)이 아닌 클래스명만 이용한 설정이 가능해집니다.

※ MyBatis 사용법에 관한 더 알고 싶다면 여기를 참고해주세요.


2-4) sqlSessionFactory Bean 생성

MyBatis 애플리케이션에서 sql session에 관련된 기능을 대신해주는 SqlSession을 만들어주는 sqlSessionFactory Bean을 생성합니다.

@Configuration
@MapperScan(basePackages={"me.jiniworld.demo.mapper"}, sqlSessionFactoryRef="sqlSessionFactory")
public class MybatisConfig {

	@Bean(name="sqlSessionFactory")
	public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource);
		sqlSessionFactoryBean.setTypeAliasesPackage("me.jiniworld.demo.models.entities");
		sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*-mapper.xml"));
		return sqlSessionFactoryBean.getObject();
	}

}

ln 2: basePackages에 Mapper 인터페이스 패키지 위치를 작성합니다.
ln 6: 기존에 이용하고 있던 dataSource를 주입받습니다. 현재 주입하는 dataSource는 application.yml 프로퍼티에 설정한 DB 정보를 통해 자동 생성된 dataSource입니다.
ln 9: setTypeAliasesPackage 설정을 통해 엔티티의 패키지 이름을 생략할 수 있도록 합니다.
ln 10: resources 아래의 mapper 폴더 내의 *-mapper.xml 파일들을 SQL Map XML파일로 설정합니다.


Plus tip. sqlSessionTemplate Bean을 이용한 조회

@Configuration
@MapperScan(basePackages={"me.jiniworld.demo.mapper"}, sqlSessionFactoryRef="sqlSessionFactory", sqlSessionTemplateRef="sqlSessionTemplate")
public class MybatisConfig {
    ...
    @Bean(name="sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
    	return new SqlSessionTemplate(sqlSessionFactory);
    }
}
@Component
public class StoreMapper {

    @Autowired
    private SqlSession sqlSessionTemplate;

    public Optional<Store> findById(long id) {
        return Optional.ofNullable(sqlSessionTemplate.selectOne("me.jiniworld.demo.mapper.StoreMapper.findById", id));
    }

}

SqlSessionTemplate을 이용하면 Map SQL XML에 정의한 namespace + id 를 이용하여 CRUD를 할 수 있습니다.

이 때, 주의해야할 점은 SqlSession을 이용하여 조회를 하기 때문에 interface가 아닌 class로 생성해야한다는 점이며,
이 Mapper 클래스파일은 @MapperScan 으로 자동스캔이 되지 않기 때문에 @Component 애너테이션을 붙여야 한다 는 점입니다.


03

MyBatisConfig 파일의 basePackages에 hover 할 경우, basePackages는 interface만 자동으로 스캔하며, class 파일은 무시한다고 쓰여있습니다.

※ 소스코드는 GitHub에서 다운받아 볼 수 있습니다.


프로퍼티 설정으로 dataSource 빈을 자동으로 생성할 수 있으나, JavaConfig를 통해 직접 dataSource 빈을 직접 생성하고 싶다면 JavaConfig로 Datasource 설정하기포스팅 참고하면 됩니다.


++ keyword

  • spring boot mybatis 설정
  • spring boot mybatis jpa 병행
  • spring boot mybatis jpa 동시 사용
300x250
반응형
  • 프로필사진
    마리2021.05.30 22:57

    좋은 글 감사합니다!! 의문점이 있어 깃허브 페이지를 방문했습니다만, 404에러가 떠서 댓글 남깁니다. mybatis와 jpa함께 사용할 경우 service도 분리해서 만드시나요?

    • 프로필사진
      Favicon of https://blog.jiniworld.me BlogIcon jiniya222021.06.01 21:27 신고

      깃허브 링크 오류는 수정했습니다.
      service는 분리하지 않고 사용해도 상관없습니다.