SPRING

스프링 DB 2편 - 데이터 접근 활용 기술 (4)

illho 2023. 6. 21. 21:06

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2

 

스프링 DB 2편 - 데이터 접근 활용 기술 - 인프런 | 강의

백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - 강의 소개 | 인

www.inflearn.com

 

데이터 접근 기술 - MyBatis

 

MyBatis는 JdbcTemplate 보다 더 많은 기능을 제공하는 SQL Mapper 이다.

JdbcTemplate과 비교해서 MyBatis의 장점은 SQL을 XML에서 편리하게 작성할 수 있고 또 동적 쿼리를 매우 편리하게 작성할 수 있다.

 

JdbcTemaplate은 스프링에 내장된 기능이고, 별도의 설정없이 사용할 수 있다는 장점이 있다.

반면에 MyBatis는 약간의 설정이 필요하다.

 

MyBatis 설정

 

mybatis-spring-boot-starter 라이브러리를 사용하면 MyBatis를 스프링과 통합하고, 설정도 아주 간단히 할 수 있다.

build.gradle에 의존관계를 추가한다.

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

 

다음과 같은 라이브러리가 추가 된다.

  • mybatis-spring-boot-starter : MyBatis를 스프링 부트에서 편리하게 사용할 수 있게 시작하는 라이브러리
  • mybatis-spring-boot-autoconfigure : MyBatis와 스프링 부트 설정 라이브러리
  • mybatis-spring : MyBatis와 스프링을 연동하는 라이브러리
  • mybatis : MyBatis 라이브러리

설정

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

 

  • mybatis.type-aliases-package : 마이바티스에서 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있다. 여러 위치를 지정하려면 , ; 로 구분하면 된다.
  • mybatis.configuration.map-underscore-to-camel-case : 언더바를 카멜로 자동 변경해주는 기능을 활성화 한다.
  • logging.level.hello.itemservice.repository.mybatis=trace : MyBatis에서 실행되는 쿼리 로그를 확인할 수 있다.

 

MyBatis 적용 

 

@Mapper
public interface ItemMapper {

    void save(Item item);

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

    List<Item> findAll(ItemSearchCond itemSearch);

    Optional<Item> findById(Long id);
}

마이바티스 매핑 XML을 호출해주는 매퍼 인터페이스이다.

인터페이스에는 @Mapper를 애노테이션을 붙여주어야한다. 그래야 MyBatis에서 인식할 수 있다.

XML 매핑 파일은 자바 코드가 아니기 때문에 src/main/resources 하위에 만들되, 패키지 위치는 맞추어 주어야 한다.

 

src/main/resources/hello/itemservice/repository/mybatis/ItemMapper.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="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" 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 &lt;= #{maxPrice}
            </if>
        </where>
    </select>
</mapper>

 

namespace : 앞서 만든 매퍼 인터페이스를 지정하면 된다.

 XML파일을 원하는 위치에 두고싶으면 application.properties 에 다음과 같이 설정하면 된다.

mybatis.mapper-locations=classpath:mapper/**/*.xml

이렇게 하면 resources/mapper 를 포함한 하위 폴더에 있는 XML을 XML매핑 파일로 인식한다.

 

 

insert 문

void save(Item item);

<insert id="save" useGeneratedKeys="true" keyProperty="id">
 insert into item (item_name, price, quantity)
 values (#{itemName}, #{price}, #{quantity})
</insert>

useGeneratedKeys는 데이터베이스가 키를 생성해주는 IDENTITY 전략일 때 사용한다.

keyProperty는 생성되는 키의 속성 이름을 지정한다.

 

update 문

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

<update id="update">
 update item
 set item_name=#{updateParam.itemName},
 price=#{updateParam.price},
 quantity=#{updateParam.quantity}
 where id = #{id}
</update>

파라미턱 2개 이상이면 @Param 으로 이름을 지정해서 파라미터를 구분해야 한다.

 

 select 문

Optional<Item> findById(Long id);

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

resultType은 반환 타입을 명시한다. mybatis.type-aliasespackage=hello.itemservice.domain 을 지정한 덕분에 패키지명을 간단하게 쓸 수 있다.

 

XML 특수문자

 

XML 에서는 데이터 영역에 < , > 같은 특수 문자를 사용할 수 없기 때문에 CDATA 구문 문법을 사용해야 한다.

<if test="maxPrice != null">
 <![CDATA[
 and price <= #{maxPrice}
 ]]>
 </if>

 

매퍼 인터페이스의 구현체

위의 내용을 보면 ItemMapper 매퍼 인터페이스의 구현체가 없는데 동작한다.

그 이유는 MyBatis 스프링 연동 모듈에서 자동으로 처리해주기 때문이다.

 

1. 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper 가 붙어있는 인터페이스를 조사한다.

2. 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 ItemMapper 인터페이스의 구현체를 만든다.

3. 생성된 구현체를 스프링 빈으로 등록한다.

 

MyBatis - 동적 쿼리

 

if

<if test="title != null">
 AND title like #{title}
 </if>

 

 

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>

 

where

<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 를 추가한다. 만약 and가 먼저 시작된다면 and를 지운다.

 

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>

컬렉션을 반복 처리할 때 사용한다. where in (1,2,3,4,5,6)와 같은 문장을 쉽게 완성할 수 있다.

 

기타 기능

애노테이션으로 SQL 작성

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

@Insert , @Update , @Delete , @Select 기능이 제공된다.

이 경우에는 XML 파일에 Select id=findById 는 제거해야한다.

동적 SQL이 해결되지 않으므로 간단한 경우에만 사용한다.

 

문자열 대체

#{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement를 사용한다.

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

 

 

재사용 가능한 SQL조각

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
 	select
 		<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>

<include>를 통해서 <sql> 조각을 찾아서 사용할 수 있다.

 

Result Maps

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

별칭을 사용하지 않고도 resultMap을 선언해서 사용할 수 있다.