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

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

반응형
  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 동시 사용
728x90
반응형