2019. 12. 27. 15:55ㆍSpring/Basic
- Mapper(MyBatis)와 JPA를 함께 사용해야하는 이유?
- 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 설정 부분을 먼저 선행해주세요.
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 애너테이션을 붙여야 한다 는 점입니다.
MyBatisConfig 파일의 basePackages에 hover 할 경우, basePackages는 interface만 자동으로 스캔하며, class 파일은 무시한다고 쓰여있습니다.
※ 소스코드는 GitHub에서 다운받아 볼 수 있습니다.
프로퍼티 설정으로 dataSource 빈을 자동으로 생성할 수 있으나, JavaConfig를 통해 직접 dataSource 빈을 직접 생성하고 싶다면 JavaConfig로 Datasource 설정하기포스팅 참고하면 됩니다.
++ keyword
- spring boot mybatis 설정
- spring boot mybatis jpa 병행
- spring boot mybatis jpa 동시 사용
'Spring > Basic' 카테고리의 다른 글
[Spring Security] postman에서 csrf token 이용하기 (0) | 2020.03.23 |
---|---|
[JPA] @MappedSuperclass로 중복 컬럼 상속화 (0) | 2019.12.30 |
[Spring Boot] Custom Banners (2) | 2019.08.02 |
[Spring Boot] 이미지 파일 경로 외부에 설정하기 with yml (5) | 2019.07.31 |
Swagger 2 에서 Pageable 이용하기 (0) | 2019.05.29 |