[스프링 DB 2편] #4. 데이터 접근 기술 - MyBatis #573
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
MyBatis 소개
MyBatis는 JdbcTemplate보다 더 많은 기능을 제공하는 SQL Mapper 이고, 기본적으로 JdbcTemplate이 제공하는 대부분의 기능을 제공한다.
JdbcTemplate과 비교해서 MyBatis의 가장 장점은 SQL을 XML에 편리하게 작성할 수 있고, 동적 쿼리를 편리하게 작성할 수 있다는 점이다.
비교
JdbcTemplate - SQL 여러줄
MyBatis - SQL 여러줄
JdbcTemplate - 동적 쿼리
MyBatis - 동적 쿼리
JdbcTemplate은 자바 코드로 직접 동적 쿼리를 작성해야 한다.
반면에 MyBatis는 동적 쿼리를 매우 편리하게 작성 할 수 있는 다양한 기능들을 제공해준다.
설정의 장단점
JdbcTemplate은 스프링에 내장된 기능이고, 별도의 설정없이 사용할 수 있다는 장점이 있다. 반면에 MyBatis는 약간의 설정이 필요하다.
정리
프로젝트에서 동적 쿼리와 복잡한 쿼리가 많다면 MyBatis를 사용하고, 단순한 쿼리들이 많으면 JdbcTemplate을 선택해서 사용하면 된다.
물론 둘을 함께 사용해도 된다. 하지만 MyBatis를 선택했다면 그것으로 충분할 것이다.
MyBatis 설정
mybatis-spring-boot-starter라이브러리를 사용하면 MyBatis를 스프링과 통합하고, 설정도 아주 간단히 할 수 있다.mybatis-spring-boot-starter라이브러리를 사용해서 간단히 설정하는 방법을 알아보자.build.gradle에 다음 의존 관계를 추가한다.build.gradle - 의존관계 전체
dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.2.2' runtimeOnly 'com.h2database:h2' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' //테스트에서 lombok 사용 testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' }다음과 같은 라이브러리가 추가된다.
mybatis-spring-boot-starter: MyBatis를 스프링 부트에서 편리하게 사용할 수 있게 시작하는 라이브러리mybatis-spring-boot-autoconfigure: MyBatis와 스프링 부트 설정 라이브러리mybatis-spring: MyBatis와 스프링을 연동하는 라이브러리mybatis: MyBatis 라이브러리main - application.propertiestest - application.propertiesmybatis.type-aliases-package,,;로 구분하면 된다.mybatis.configuration.map-underscore-to-camel-caseBeanPropertyRowMapper에서 처럼 언더바를 카멜로 자동 변경해주는 기능을 활성화 한다.바로 다음에 설명하는 관례의 불일치 내용을 참고하자.
logging.level.hello.itemservice.repository.mybatis=traceMyBatis에서 실행되는 쿼리 로그를 확인할 수 있다.관례의 불일치
자바 객체에는 주로 카멜(
camelCase) 표기법을 사용한다.itemName처럼 중간에 낙타 봉이 올라와 있는 표기법이다.반면에 관계형 데이터베이스에서는 주로 언더스코어를 사용하는
snake_case표기법을 사용한다.item_name처럼 중간에 언더스코어를 사용하는 표기법이다.이렇게 관례로 많이 사용하다 보니
map-underscore-to-camel-case기능을 활성화 하면 언더스코어 표기법을 카멜로 자동 변환해준다.따라서 DB에서
select item_name으로 조회해도 객체의itemName(setItemName()) 속성에 값이 정상 입력된다.정리하면 해당 옵션을 켜면
snake_case는 자동으로 해결되니 그냥 두면 되고컬럼 이름과 객체 이름이 완전히 다른 경우에는 조회 SQL에서 별칭을 사용하면 된다.
별칭을 통한 해결방안
select item_name as nameMyBatis 적용1 - 기본
ItemMapper
@Mapper애노테이션을 붙여주어야 한다. 그래야 MyBatis에서 인식할 수 있다.xml의 해당 SQL을 실행하고 결과를 돌려준다.ItemMapper인터페이스의 구현체에 대한 부분은 뒤에 별도로 설명한다.namespace: 앞서 만든 매퍼 인터페이스를 지정하면 된다.insert - save
<insert>를 사용하면 된다.id에는 매퍼 인터페이스에 설정한 메서드 이름을 지정하면 된다. 여기서는 메서드 이름이save()이므로save로 지정하면 된다.#{}문법을 사용하면 된다. 그리고 매퍼에서 넘긴 객체의 프로퍼티 이름을 적어주면 된다.#{}문법을 사용하면PreparedStatement를 사용한다. JDBC의?를 치환한다 생각하면 된다.useGeneratedKeys는 데이터베이스가 키를 생성해 주는IDENTITY전략일 때 사용한다.keyProperty는 생성되는 키의 속성 이름을 지정한다. Insert가 끝나면item객체의id속성에 생성된 값이 입력된다.update - update
<update>를 사용하면 된다.Long id,ItemUpdateDto updateParam으로 2개이다.@Param을 지정하지 않아도 되지만, 파라미터가 2개 이상이면@Param으로 이름을 지정해서 파라미터를 구분해야 한다.select - findById
<select>를 사용하면 된다.resultType은 반환 타입을 명시하면 된다. 여기서는 결과를Item객체에 매핑한다.application.properties에mybatis.type-aliases-package=hello.itemservice.domain속성을 지정한 덕분에모든 패키지 명을 다 적지는 않아도 된다. 그렇지 않으면 모든 패키지 명을 다 적어야 한다.
BeanPropertyRowMapper처럼 SELECT SQL의 결과를 편리하게 객체로 바로 변환해준다.mybatis.configuration.map-underscore-to-camel-case=true속성을 지정한 덕분에 언더스코어를 카멜 표기법으로 자동으로 처리해준다.(
item_nameitemName)Item,Optional<Item>과 같이 사용하면 되고, 반환 객체가 하나 이상이면 컬렉션을 사용하면 된다.주로
List를 사용한다. 다음을 참고하자.select - findAll
<where>,<if>같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다.<if>는 해당 조건이 만족하면 구문을 추가한다.<where>은 적절하게where문장을 만들어준다.<if>가 모두 실패하게 되면 SQLwhere를 만들지 않는다.<if>가 하나라도 성공하면 처음 나타나는and를where로 변환해준다.XML 특수문자
그런데 가격을 비교하는 조건을 보자
and price <= #{maxPrice}여기에 보면
<=를 사용하지 않고<=를 사용한 것을 확인할 수 있다. 그 이유는 XML에서는 데이터 영역에<,>같은 특수 문자를 사용할 수 없기 때문이다. 이유는 간단한데, XML에서 TAG가 시작하거나 종료할 때
<,>와 같은 특수문자를 사용하기 때문이다.다른 해결 방안으로는 XML에서 지원하는
CDATA구문 문법을 사용하는 것이다. 이 구문 안에서는 특수문자를 사용할수 있다. 대신 이 구문 안에서는 XML TAG가 단순 문자로 인식되기 때문에
<if>,<where>등이 적용되지 않는다.XML CDATA 사용
특수문자와 CDATA 각각 상황에 따른 장단점이 있으므로 원하는 방법을 그때그때 선택하면 된다.
MyBatis 적용2 - 설정과 실행
MyBatisItemRepository
ItemRepository를 구현해서MyBatisItemRepository를 만들자.MyBatisItemRepository는 단순히ItemMapper에 기능을 위임한다.MyBatisConfig
MyBatisConfig는ItemMapper를 주입받고, 필요한 의존관계를 만든다.ItemServiceApplication - 변경
MyBatis 적용3 - 분석
ItemMapper매퍼 인터페이스의 구현체가 없는데 어떻게 동작한 것일까?ItemMapper 인터페이스
@Mapper가 붙어있는 인터페이스를 조사한다.ItemMapper인터페이스의 구현체를 만든다.MyBatisItemRepository - 로그 추가
실행 결과
itemMapper class=class com.sun.proxy.$Proxy66출력해보면 JDK 동적 프록시가 적용된 것을 확인할 수 있다.
매퍼 구현체
ItemMapper의 구현체 덕분에 인터페이스 만으로 편리하게 XML의 데이터를 찾아서 호출할 수 있다.DataAccessException에 맞게 변환해서 반환해준다.JdbcTemplate이 제공하는 예외 변환 기능을 여기서도 제공한다고 이해하면 된다.
정리
데이터베이스 커넥션, 트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고 동기화해준다.
MyBatis 기능 정리1 - 동적 쿼리
동적 SQL
마이바티스를 사용하는 이유는 바로 동적 SQL 기능 때문이다.
동적 쿼리를 위해 제공되는 기능은 다음과 같다.
ifchoose (when, otherwise)trim (where, set)foreachif
choose, when, otherwise
trim, where, set
이 예제의 문제점은 문장을 모두 만족하지 않을 때 발생한다.
title만 만족할 때도 문제가 발생한다.결국
WHERE문을 언제 넣어야 할지 상황에 따라서 동적으로 달라지는 문제가 있다.<where>를 사용하면 이런 문제를 해결할 수 있다.** 사용**
<where>는 문장이 없으면where를 추가하지 않는다. 문장이 있으면where를 추가한다. 만약and가 먼저 시작된다면and를 지운다.참고로 다음과 같이
trim이라는 기능으로 사용해도 된다. 이렇게 정의하면<where>와 같은 기능을 수행한다.foreach
where in (1,2,3,4,5,6)와 같은 문장을 쉽게 완성할 수 있다.List를 전달하면 된다.MyBatis 기능 정리2 - 기타 기능
애노테이션으로 SQL 작성
다음과 같이 XML 대신에 애노테이션에 SQL을 작성할 수 있다.
@Insert,@Update,@Delete,@Select기능이 제공된다.<select id="findById"> ~ </select>는 제거해야 한다.문자열 대체(String Substitution)
#{}문법은 ?를 넣고 파라미터를 바인딩하는PreparedStatement를 사용한다.때로는 파라미터 바인딩이 아니라 문자 그대로를 처리하고 싶은 경우도 있다. 이때는
${}를 사용하면 된다.다음 예제를 보자
ORDER BY ${columnName}주의
${}를 사용하면 SQL 인젝션 공격을 당할 수 있다. 따라서 가급적 사용하면 안된다. 사용하더라도 매우 주의깊게 사용해야 한다.재사용 가능한 SQL 조각
<sql>을 사용하면 SQL 코드를 재사용 할 수 있다.<include>를 통해서<sql>조각을 찾아서 사용할 수 있다.Result Maps
결과를 매핑할 때 테이블은
user_id이지만 객체는id이다.이 경우 컬럼명과 객체의 프로퍼티 명이 다르다. 그러면 다음과 같이 별칭(
as)을 사용하면 된다.별칭을 사용하지 않고도 문제를 해결할 수 있는데, 다음과 같이
resultMap을 선언해서 사용하면 된다.복잡한 결과매핑
MyBatis도 매우 복잡한 결과에 객체 연관관계를 고려해서 데이터를 조회하는 것이 가능하다.
이때는
<association>,<collection>등을 사용한다. 이 부분은 성능과 실효성에서 측면에서 많은 고민이 필요하다.JPA는 객체와 관계형 데이터베이스를 ORM 개념으로 매핑하기 때문에 이런 부분이 자연스럽지만,
MyBatis에서는 들어가는 공수도 많고 성능을 최적화하기도 어렵다. 따라서 해당기능을 사용할 때는 신중하게 사용해야 한다.
All reactions