일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch8
- @Aspect
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch3
- 자바의 정석 기초편 ch7
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch13
- 스프링 입문(무료)
- 자바의 정석 기초편 ch12
- jpa - 객체지향 쿼리 언어
- 2024 정보처리기사 시나공 필기
- 코드로 시작하는 자바 첫걸음
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch6
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch4
- 스프링 db1 - 스프링과 문제 해결
- 스프링 mvc2 - 타임리프
- jpa 활용2 - api 개발 고급
- 스프링 mvc1 - 서블릿
- 자바의 정석 기초편 ch1
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch5
- 게시글 목록 api
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch14
- 타임리프 - 기본기능
- Today
- Total
나구리의 개발공부기록
데이터 접근기술 - MyBatis 소개 및 설정, MyBatis 적용(기본/설정과 진행/분석), MyBatis 기능 정리(동적 쿼리/기타 기능) 본문
데이터 접근기술 - MyBatis 소개 및 설정, MyBatis 적용(기본/설정과 진행/분석), MyBatis 기능 정리(동적 쿼리/기타 기능)
소소한나구리 2024. 9. 20. 15:26 출처 : 인프런 - 스프링 DB 2편 데이터 접근 핵심 원리 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2
1. MyBatis 소개
- JdbcTemplate보다 더 많은 기능을 제공하는 SQL Mapper이며 기본적으로 JdbcTemplate의 대부분의 기능을 제공함
- MyBatis는 SQL을 XML에 편리하게 작성할 수 있고 또 동적 쿼리를 매우 편리하게 작성할 수 있다는 장점이 존재함
1) JdbcTemplate Vs MyBatis 비교
(1) JdbcTemplate - SQL 여러줄
- 자바 코드로 직접 쿼리문을 작성하게 되면 " "사이에 입력되는 띄어쓰기에 매우 유의해서 작성해야함
- "update item" + "set item ..." 처럼 작성하게 되면 문법 오류가 발생됨
String sql = "update item " +
"set item_name=:itemName, price=:price, quantity=:quantity " +
"where id=:id";
(2) MyBatis - SQL 여러줄
- MyBatis는 XML에 작성하기 때문에 라인이 길어져도 문자 더하기에 대한 불편함이 없이 일반 SQL을 작성하듯이 편리하게 작성할 수 있음
<update id="update">
update item
set item_name=#{itemName},
price=#{price},
quantity=#{quantity}
where id = #{id}
</update>
(3) JdbcTemplate - 동적쿼리
- 자바 코드로 직접 여러 상황에 대한 고려를 하면서 동적 쿼리를 작성해야 하다보니 코드가 길어지고 복잡해짐
String sql = "select id, item_name, price, quantity from item"; //동적 쿼리
if (StringUtils.hasText(itemName) || maxPrice != null) {
sql += " where";
}
boolean andFlag = false;
if (StringUtils.hasText(itemName)) {
sql += " item_name like concat('%',:itemName,'%')";
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
sql += " and";
}
sql += " price <= :maxPrice";
}
log.info("sql={}", sql);
return template.query(sql, param, itemRowMapper());
(4) MyBatis - 동적쿼리
- <where>, <if> 와같은 태그의 기능을 활용해서 동적쿼리를 JdbcTemplate 보다는 편리하게 작성할 수 있음
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%',#{itemName},'%')
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
(5) 설정의 장단점
- JdbcTemplate은 스프링에 내장된 기능이고 별도의 설정 없이 사용할 수 있는 반면, MyBatis는 약간의 설정이 필요함
- 스프링부트와 MyBatis와 연동하는 모듈을 쓰면 편리하게 설정할 수 있음
(6) 무엇을 사용하면 좋을까?
- 프로젝트에서 동적 쿼리와 복잡한 것이 많으면 MyBatis를 사용하고 단순한 쿼리가 많으면 JdbcTemplate을 선택해서 사용하면 됨
- 둘을 함께 사용해도 되지만 MyBatis를 선택했으면 이미 충분할 것
- JPA나 스프링데이터 JPA, Querydsl 등의 ORM 기술스택을 기본으로 사용하고 있다면 동적쿼리에 대한 문제들이 어느정도 해결이 되지만 가끔 네이티브 SQL 쿼리를 써야할 때가 있는데, 그럴때는 JdbcTemplate으로 대부분 문제가 해결이 되었다고 함
** 참고
- 해당강의에서 MyBatis의 기능을 자세하게 다루지는 않고 MyBatis의 사용 이유, 주로 사용하는 기능 위주로만 다룸
- 기초를 다루기 때문에 MyBatis로 개발은 가능하며 추가로 필요한 내용을 공식 사이트에서 찾아서 사용할 수 있음
- MyBatis는 기능도 단순하고 공식사이트가 한글로 잘 번역되어 있어서 원하는 기능을 편리하게 찾아볼 수 있음
- 공식 사이트
2. MyBatis 설정
1)build.gradle 설정
- 스프링 부트 버전에 맞게 아래의 의존관계를 추가
- 스프링 부트가 버전을 관리해주는 공식 라이브러리들은 의존관계 추가시에 버전 정보를 입력하지 않아도 최적의 버전을 자동으로 찾아주지만(롬복, h2, jdbc 등) MyBatis는 공식 라이브러리가 아니기 때문에 직접 버전을 입력해주어야 함
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
//MyBatis 스프링 부트 3.0 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
- 의존관계 추가 후 gradle을 리프레쉬 한 후 라이브러리의 항목을 살펴보면 MyBatis 관련 라이브러리들이 추가 되어있음
2) application.properties 설정
- main, test의 모든 application.properties에 MyBatis 설정을 똑같이 지정해야함
- 앞서 배웠듯 둘중 하나만하면 적용한곳에서만 동작됨
#MyBatis
mybatis.type-aliases-package=hello.itemservice.domain
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace
(1) mybatis.type-aliases-package
- 마이바티스에서 타입 정보를 사용할 때 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있으며 지정한 패키지와 그 하위 패키지가 자동으로 인식됨
- 여러 위치를 지정하려면 , or ; 로 구분하면됨
(2) mybatis.configuration.map-underscore-to-camel-case=true
- JdbcTemplate의 BeanPropertyRowMapper에서처럼 스네이크 표기법을 카멜표기법으로 자동 변경해주는 기능을 활성화함과
- 관계형 데이터베이스에서는 스네이크 표기법을, 자바 에서는 카멜 표기법을 사용하기에 이런 기능들이 생겨났으며 관례의 불일치에 대한 내용은 JdbcTemplate 강의 참고
(3) logging.level.hello.itemservice.repository.mybatis=trace
- MyBatis에서 실행되는 쿼리 로그를 확인할 수 있음
** 참고
- 간혹 패키지명에 iBatis로 되어있는 것이 있는데 MyBatis의 과거버전 이름인데 아직 패키지명에 남아 있는 것이 있음
3. MyBatis 적용1 - 기본
- XML에 SQL을 작성한다는 점을 제외하고는 JDBC 반복을 줄여준다는 점에서 기존 JdbcTemplate과 거의 유사함
1) ItemMapper
- MyBatis 매핑 XML을 호출해주는 매퍼 인터페이스이며 해당 인터페이스에는 @Mapper 애노테이션을 붙여주어야 MyBatis에서 인식할 수 있음
- 해당 인터페이스의 메서드를 호출하면 XML의 id에 동일하게 작성된 SQL을 실행하고 결과를 돌려줌
- ItemMapper 인터페이스의 구현체는 자동으로 만들어지며 이에 대한 부분은 뒤에 별도로 설명
package hello.itemservice.repository.mybatis;
@Mapper
public interface ItemMapper {
void save(Item item);
// 파라미터가 2개가 넘어가는 경우 @Param을 넣어줘야함
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
Optional<Item> findById(Long id);
List<Item> findAll(ItemSearchCond itemSearch);
}
2) ItemMapper.xml
- 자바 코드가 아니기 때문에 src/main/resources 하위에 만들고 패키지 위치와 동일하게 디렉토리를 만들어서 XML파일을 생성
- XML파일명도 인터페이스와 동일하게 설정하고 디렉토리도 인터페이스 경로의 패키지명과 동일하게 작성해 주어야 함
- namespace: ItemMapper인터페이스와 그 패키지 경로를 지정하면 됨
<?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="hello.itemservice.repository.mybatis.ItemMapper">
<!-- id가 자동 생성되도록 설정-->
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item (item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
</insert>
<update id="update">
update item
set item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity}
where id = #{id}
</update>
<!-- 원래는 resultType에 패키지명을 입력해 줘야하지만 설정을 해두었기 때문에 간단히 입력할 수 있게 됨-->
<select id="findById" resultType="Item">
select id, item_name, price, quantity
from item
where id = #{id}
</select>
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
-- 동적쿼리를 위한 where, if 태그
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%', #{itemName}, '%')
</if>
<!-- xml파일이므로 < 기호가 충돌을 일으켜서 아래처럼 사용해야함-->
<if test="maxPrice != null">
and price $lt;= #{maxPrice}
</if>
</where>
</select>
</mapper>
** 참고
- 기본은 XML 파일의 위치를 Mapper 인터페이스와 경로를 맞춰줘야 하지만 원하는 위치에 모아두고싶을 때가 있음
- 그럴때는 application.properties에 mybatis.mapper-locations=classpath:mapper/**/*.xml 이렇게 입력해두면 resources/mapper를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식하며 이렇게 하면 XML 파일이름도 자유롭게 설정할 수 있음
- 마찬가지로 test에 있는 application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식할 수 있음
(1) insert - save
- INSERT SQL은 <insert> 태그를 사용하고 id에는 매퍼 인터페이스에 설정한 메서드 이름을 지정
- 파라미터는 #{} 문법을 사용해서 매퍼에서 넘긴 객체의 프로퍼티 이름을 적어서 넘기고, PreparedStatement를 사용해서 동작함 (JDBC의 ?를 치환, 파라미터이름을 기반으로 동작하기 때문에 NamedParameterJdbcTemplate처럼 동작한다고 보면 됨)
- 데이터베이스가 키를 생성해주는 IDENTITY 전략일 때 userGeneratedKeys를 사용해서 keyProperty에 생성되는 키의 속성의 이름을 지정해주면 Insert가 끝나고 난 후 item객체의 id속성에 생성된 값이 입력됨
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item (item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
</insert>
(2) update-update
- UPDATE SQL은 <update>를 사용하면 됨
- 여기서는 파라미터가 Long Id, ItemUpdateDto updateParam으로 2개이므로 @Param을 지정해줘야함
- 파라미터가 1개만 있으면 @Param을 생략해도 되지만 파라미터가 2개 이상이면 @Param으로 이름을 지정해서 파라미터를 구분해야함
- updateParam.itemName 처럼 파라미터로 넘어온 객체의 값을 꺼낼수 있음
<update id="update">
update item
set item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity}
where id = #{id}
</update>
(3) select - findById
- SELECT SQL 은 <select>를 사용하고 resultType은 반환 타입을 명시하면 됨(여기서는 Item 객체에 매핑)
- application.properties에 패키지 속성을 지정했기 때문에 모든 패키지 명을 다 적지 않고 Item만 입력이 가능함
- JdbcTemplate의 BeanPropertyRowMapper처럼 SELECT SQL의 결과를 편리하게 객체로 바꿔서 바로 변환해줌
- mybatis.configuration.map-underscore-to-camel-case=true 속성을 지정해서 스네이크 표기법을 카멜 표기법으로 자동 변환하여 처리함(item_name -> itemName)
- 자바 코드에서 반환 객체가 하나이면 Item, Optional<Item>과 처럼 사용하면 되고, 반환 객체가 하나 이상이면 컬렉션(주로 List)을 사용하면 됨
<select id="findById" resultType="Item">
select id, item_name, price, quantity
from item
where id = #{id}
</select>
(4) select - findAll
- <where>, <if> 같은 동적 쿼리 문법을 통해 편리한 동적쿼리를 지원함
- <if> : 해당 조건이 만족하면 구문을 추가함
- <where>: 적절하게 where문장을 만들어줌, <if>가 모두 실패하면 SQL where를 만들지 않고 하나라도 성공하면 처음 나타나는 and를 where로 변환해줌
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<!-- 동적쿼리를 위한 where, if 태그 -->
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%', #{itemName}, '%')
</if>
<!-- xml파일이므로 < 기호가 충돌을 일으켜서 아래처럼 사용해야함-->
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
(5) XML 특수문자
- XML 에서는 TAG가 시작하거나 종료할때 <, >의 특수문자를 사용하기 때문에 데이터 영역에 <. > 과 같은 특수문자를 사용할 수 없음
- 그래서 <=을 사용해서 <=을 표현함
- > : >
& : &
< : < - XML에서 지원하는 CDATA 구문 문법을 사용해도 해결 가능함 (해당 구문안에서는 특수문자를 사용할 수 있지만 XML TAG가 단순 문자로 인식되기때문에 <if>, <where> 등을 적용할 수 없음)
- 간단한 경우에는 특수문자 Entity로 작성하고 복잡한 경우는 CDATA을 사용하면 됨
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%',#{itemName},'%')
</if>
<if test="maxPrice != null">
<![CDATA[
and price <= #{maxPrice}
]]>
</if>
</where>
4. MyBatis 적용2 - 설정과 실행
1) 설정 추가
(1) MyBatisItemRepository 생성
- ItemRepository를 구현하고 각 메서드는 단순히 ItemMapper에 기능을 위임함
package hello.itemservice.repository.mybatis;
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {
// ItemMapper를 주입
private final ItemMapper itemMapper;
@Override
public Item save(Item item) {
itemMapper.save(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
itemMapper.update(itemId, updateParam);
}
@Override
public Optional<Item> findById(Long id) {
return itemMapper.findById(id);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
return itemMapper.findAll(cond);
}
}
(2) MyBatisConfig.java 생성
- ItemMapper를 주입받고 필요한 의존관계를 생성
package hello.itemservice.config;
@Configuration
@RequiredArgsConstructor
public class MyBatisConfig {
private final ItemMapper itemMapper; // ItemMapper 주입
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new MyBatisItemRepository(itemMapper);
}
}
(3) ItemServiceApplication - @Import 변경
- @Import를 MyBatisConfig로 변경
@Import(MyBatisConfig.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
2) 실행
- 테스트 실행 : 모두 정상작동하고 MyBatis로 동작하는 쿼리문 로그가 보임
- 애플리케이션 실행 : H2 데이터베이스를 미리 띄우고 실행해보면 모든 기능이 정상 작동하며 마찬가지로 MyBatis로 동작하는 쿼리문로그가 보임
5. MyBatis 적용3 - 분석
1) ItemMapper 매퍼 인터페이스의 구현체가 없이 동작하는 원리
- MyBatis 스프링 연동 모듈에서 자동으로 처리해 줌
- 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper가 붙어있는 인터페이스를 조사함
- 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 ItemMapper 인터페이스의 구현체를 생성
- 생성된 구현체를 스프링 빈으로 등록
2) 동적 프록시 기술 확인해보기
- MyBatisItemRepository 클래스에 @Slf4j 를 추가하고 아무 메서드에 log를 추가해서 itemMapper의 클래스를 확인
- log를 추가한 메서드를 테스트 실행 해보면 itemMapper class=class jdk.proxy3.$Proxy71 로그를 확인할 수 있음 -> JDK 동적 프록시가 적용됨
- 동적 프록시 기술은 스프링 핵심원리 - 고급편에서 자세히 설명
log.info("itemMapper class={}", itemMapper.getClass());
3) 매퍼 구현체
- 마이바티스 스프링 연동 모듈이 만들어 주는 ItemMapper의 구현체 덕분에 인터페이스만으로 편리하게 XML의 데이터를 찾아서 호출할 수 있음
- 원래 마이바티스를 사용하려면 더 번잡한 코드를 거쳐야하지만 이런부분을 인터페이스 하나로 깔끔하고 편리하게 사용 할 수 있음
- 매퍼 구현체는 예외 변환까지 처리해주는데 MyBatis에서 발생한 예외를 스프링 예외추상화인 DataAccessException에 맞게 변환해서 반환해줌(JdbcTemplate의 예외 변환 기능을 여기서도 제공한다고 보면 됨)
4) 정리
- 매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해서 사용할 수 있고, 스프링 예외 추상화도 함께 적용됨
- 마이바티스 스프링 연동 모듈은 데이터베이스 커넥션, 트랜잭션과 관련된 기능 연동 및 동기화 등의 여러 부분을 자동으로 설정해줌
-
MybatisAutoConfiguration 클래스를 참고하면 마이바티스 스프링 연동 모듈이 자동으로 등록된 부분을 확인할 수 있으나, 스프링 부트를 잘 알고있는 사람만 한번 살펴고, 그외에는 그냥 이런 부분이 있구나 하고 넘어가도 됨
6. MyBatis 기능 정리1 - 동적 쿼리
1) 동적 SQL
- MyBatis가 제공하는 최고의 기능이자 사용하는 이유
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
2) 메뉴얼에서 제공하는 예제로 동적 SQL 살펴보기
(1) if
- 해당 조건에 따라 값을 추가할지 말지 판단함
- 내부 문법은 OGNL 표현식을 사용함 -> OGNL 관련글
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
(2) choose, when, otherwise
- 자바의 switch문과 유사하게 사용됨 otherwise는 위 when 조건들이 모두 안맞으면 실행됨
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
(3-1) trim, where ,set
- 아래의 예제는 2가지의 문제점이 있는데, 모든 문장을 만족하지 않았을때, title의 조건만 만족했을때 SQL 구문 오류가 발생함
- WHERE 문을 언제 넣어야할지, AND를 언제 빼줘야할지 SQL문이 동적으로 달라지는 문제가 있음
<!-- 문제점 발생 -->
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
(3-2) <where>를 사용해서 문제를 해결
- <where>는 문장이 없으면 where를 추가하지 않고, 문장이 있으면 where를 추가함
- 만약에 and가 먼저 시작된다면 and를 지움
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
(3-3) trim
- 아래처럼 trim 기능을 사용해도 <where>와 동일한 기능을 수행함
<trim prefix="WHERE" prefixOverrides="AND | OR ">
...
</trim>
(3-4) foreach
- 컬렉션을 반복 처리할때 사용하며 where in (1,2,3,4,5,6)과 같은 문장을 쉽게 완성할 수 있음
- 파라미터로 List를 전달하면됨
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
6. MyBatis 기능 정리2 - 기타 기능
1) 애노테이션으로 SQl 작성
- XML 대신에 ItemMapper 매퍼에 애노테이션으로 SQL을 작성할 수 있게 해줌
- @Insert, @Update, @Delete, @Select 기능이 제공되며 XML에 SQL을 중복 작성하면 오류가 발생함
- 동적 SQL이 해결되지 않으므로 간단한 경우에만 사용하는데, 동적 SQL과 XML에 SQL을 작성한다는 것이 MyBatis의 장점인 것을 감안하면 사용할일이 거의 없음
- 애노테이션으로 SQL 작성 메뉴얼
@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
2) 문자열 대체 (String Substitution)
- #{} 문법은 ?를 넣고 파라미터 바인딩을 하는 PreparedStatement를 사용하는데 때로는 파라미터 바인딩이 아닌 문자 그대로 처리하고 싶은 경우도 있을텐데, 그럴때는 ${}를 사용하면됨
- ${}를 사용하면 Statement와 유사하게 동작하므로 SQL 인젝션 공격을 당할 수 있어 가급적 사용하면 안되며, 사용하더라도 매우 주의 깊게 사용해야함(DBA와 소통이 매우 필요함)
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
3) 재사용 가능한 SQL 조각
- <sql>과 <include>를 사용하면 SQL 코드를 재사용 하거나, 파라미터로 값을 전달하듯이 프로퍼티 값을 전달할 수 있음
<!-- 재사용할 문장의 id를 userColumns로 지정 -->
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<!-- <include refid="userColumns">로 지정하여 재사용할 sql을 불러와서 씀-->
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
- 프로퍼티 값을 전달하는 예제, 해당 값은 내부에서 사용할 수 있음
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<!-- someinclude인 sql 조각을 사용 -->
<include refid="someinclude">
<!-- ${prefix}의 변수를 Some으로 변경해서 SomeTable이 됨 -->
<property name="prefix" value="Some"/>
<!-- ${include_target}의 변수를 sometable로 변경되고
<include refid="sometable"/>로 대체가 됨
위에서 ${prefix}가 Some으로 변경된 SomeTable이 표시 -->
<property name="include_target" value="sometable"/>
</include>
</select>
<!-- 결과적으로 아래의 SQL이 완성됨 -->
select
field1, field2, field3
from
SomeTable
4) Result Maps
- 결과를 매핑할 때 테이블의 컬럼명은 user_id, 객체의 프로퍼티 명이 id일 때 별칭(as)을 사용하면 문제를 해결할 수 있으나, 별칭을 사용하지 않고 resultMap을 선언하면 문제를 해결할 수 있음
- <resultMap id="userResultMap"> 으로 정보들을 매핑해두고 실제 쿼리를 실행하는 태그에서 resultMap="userResultMap으로 불러오면 매핑된 컬럼명으로 사용됨
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
** 참고 - 복잡한 결과 매핑
- MyBatis도 매우 복잡한 결과에 객체 연관관계를 고려한 데이터 조회가 가능하며 이때는 <association>, <collection>등을 사용함
- 그러나 이부분은 성능과 실효성 측면에서 많은 고민이 필요한데, JPA는 객체와 관계형 데이터베이스를 ORM 개념으로 매핑하기 때문에 이런 부분이 자연스럽지만 MyBatis에서는 들어가는 리소스도 많고 성능을 최적화하기(N+1문제 발생, 복잡한 매핑 로직)가 어려우므로 해당 기능을 사용할 때는 신중하게 사용해야 함
- 결과 매핑 메뉴얼