본문 바로가기
spring

스프링 DB 2편 - 2

by 오우지 2023. 1. 3.

MyBatis

1. 기본설정

build.gradle에 마이바티스 implementation을 해주면서 시작하자.

//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'

application.properties에는

# MyBatis
mybatis.type-aliases-package=hello.itemservice.domain
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace

xml 작성시 리턴 타입에서 패키지 명을 뺄 수 있는 첫 번째 옵션

DB의 스테이크 케이스를 자동으로 카멜 케이스로 변경해주는 두 번째 옵션

로깅 정보에 대한 세번째 옵션이다.

 

 

마이바티스에서 쿼리를 작성하기 위해서는 @Mapper애노테이션이 붙은 인터페이스를 설정해줘야 한다. 그래야 MyBatis에서 인식하고 xml의 SQL을 실행해 결과를 돌려준다.

@Mapper
public interface ItemMapper {
    void save(Item item);

    void update(@Param("id") Long id, @Param("updateParam")ItemUpdateDto updateDto);

    Optional<Item> findById(Long id);

    List<Item> findAll(ItemSearchCond itemSearch);
}

xml을 작성해보자. 왜 ORM이 생겼는지 알것같다.

<?xml version="1.0" encoding="UTF-8"?>
http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="hello.itemservice.repository.mybatis.ItemMapper"><!--  인터페이스의 위치를 알려준다.  -->
    <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>

    <select id="findById" resultType="Item">
        select id, item_name, price, quantity
        from item
        where id = #{id}
    </select>

    <select id="findAll">
        select id, item_name, price, quantity
        from item
        <where>
            <if test="itemName != null and itemName != ''">
                and item_name like concat('%', #{itemName}, '%')
            </if>
            <if>
                and price $lt;= #{maxPrice}
            </if>
        </where>
    </select>
</mapper>

1. insert - save

- id에는 인터페이스에 설정한 메서드 이름을 지정한다.

- 파라미터는 #{ } 문법을 이용하는데 매퍼에서 넘긴 객체의 프로퍼티 이름을 지정해준다. 만약 파라미터가 하나라면 필드명을 바로 적어줘도 된다.

- #{ } 문법은 preparedStatement를 사용해서 ?를 치환해준다.

- useGeneratedKeys는 데이터베이스 키가 IDENTITY 전략일 때 사용한다. keyProperty는 키의 속성 이름을 지정한다. Insert가 끝나면 item 객체의 id 속성에 생성된 값이 입력된다.

 

2. update

- 파라미터가 Long id, ItemUpdateDto updateParam으로 2개다. 위의 인터페이스를 보면 파라미터가 2개라 @Param으로 이름을 지정해줘야 한다.

 

3. select - findById

- resultType은 반환 타입을 명시하면 되는데 여기서는 Item 객체에 매핑한다.

- 앞서 properties에서 aliases-package를 지정해줬기 때문에 패키지 명을 다 적지 않아도 되며 BeanPropertyRowMapper기능처럼 SQL 결과를 객체로 변환해준다.

 

4. select - findAll

- <if>, <where>등을 이용한 동적쿼리.. 부등호는 xml 문법과 겹치기 때문에 &lt;를 사용해줘야 한다.


2. 설정과 실행

실행 자체는 보기에 꽤나 간단하다.

@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {

    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);
    }
}

ItemMapper 인터페이스의 구현체는 없는데 위의 코드는 구현체를 주입받았다. 그 이유가 뭘까.

 

- 마이바티스 모듈이 @Mapper 어노테이션을 탐색해서 프록시 객체를 생성, 빈으로 등록해주는 과정을 대신해준다.

- 매퍼 구현체는 RuntimeException을 상속받은 DataAccessException 규격에 맞게 예외 반환도 처리해준다.

- 커넥션, 트랜잭션도 마이바티스가 연동하고 동기화해준다.


3. 동적 쿼리

동적 쿼리를 위한 기능은 다음과 같다.

- if

- choose(when, orherwise)

- trim(where, set)

- foreach

 

if

조건에 따라 추가할지 판단한다. 내부 문법은 OGNL을 사용한다.

 

choose(when, otherwise)

<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>

trim(where, set)

<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>

<where>은 문장이 없으면 where을 추가하지 않는다. 문장이 있다면 where을 추가한다. 물론 1=1 조건을 넣어줘도 같긴 하다.

trim은 where과 같은 기능을 수행하는 문이다.

<trim prefix="WHERE" prefixOverrides="AND |OR ">
 ...
</trim>

 

foreach

<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>

컬렉션을 반복 처리할 때 사용한다.

 


4. 기타 기능

- 애노테이션으로 SQL 작성

 

XML 대신 애노테이션으로 SQL을 작성할 수 있는 기능이 있다.

@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);

@Insert, @Update, @Delete, @Select 기능이 제고오디는데

XML에서는 해당 쿼리를 제거해야 하며 동적 SQL이 해결되지 않고 일괄적 관리를 위해 가급적 XML을 사용하는 것이 좋겠다.

 

 

- 문자열 대체

#{ } 문법은 ?을 넣고 파라미터를 바인딩하는 PreparedStatement를 사용하는데 떄로는 문자 그대로를 처리하고 싶을 때도 있다. 이 때는 ${ }를 사용하면 된다.

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

하지만 ${ }는 SQL 인젝션을 당할 수 있기 때문에 사용하면 안된다.

 

- 재사용 가능한 SQL 조각

<sql>을 사용하면 SQL 코드를 재사용 할 수 있다.

 

<sql id="sometable">
	${prefix}Table
</sql>
<sql id="someinclude">
	from
	<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
	select
	field1, field2, field3
	<include refid="someinclude">
	<property name="prefix" value="Some"/>
	<property name="include_target" value="sometable"/>
	</include>
</select>

 

Result Maps

결과 매핑시 컬럼명과 프로퍼티 명이 다르면 as를 사용하면 되는데 별칭을 사용하지 않고 resultMap을 사용해도 된다.

<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>

 

'spring' 카테고리의 다른 글

스프링 DB 2편 - 3  (0) 2023.01.05
스프링 DB 2편 - 1  (0) 2022.12.21
스프링 DB 1편 - 3  (0) 2022.12.12
스프링 DB 1편 - 2  (0) 2022.11.30
스프링 DB 1편 - 1  (0) 2022.11.02