기존에 많은 프로젝트에서 퍼시스턴스 프래임워크 사용시 IBATIS를 사용하였다. IBATIS 2.x 버전을 주로 사용했었으며 잠시 3.0버전을 사용했었다. 이후 이번 프로젝트에서 MyBatis를 적용하게 되었다.MyBatis는 IBATIS의 후속버전이며 IBATIS는 2010년 자바와 닷넷 주요개발자들을 포함한 팀전원이 아파치 소프트웨어 재단에서 구글 코드로 이전하기로 결정했다고 공표한 후 중단되었다. 2013년 11월에는 Github로 이전하였다.mybatis

MyBatis

이번 프로젝트에 적용한 스프링을 이용한 기본적인 설정과 MyBatis의 동적 SQL에 대하여 이야기 해보자 한다.

MyBatis와 스프링 연동

	<context:component-scan base-package="com.nextree.sample.entity" />

	<bean id="test_DS"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/sample" />
		<property name="username" value="test" />
		<property name="password" value="test" />
	</bean>

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="test_DS" />
		<property name="configLocation" value="/META-INF/mybatis-config.xml" />
	</bean>

	<bean id="testMapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.nextree.sample.entity" />
		<property name="annotationClass" value="com.nextree.sample.entity.shared.stereotype.Mapper" />
		<property name="sqlSessionFactory" ref="sqlSessionFactory" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="test_DS" />
	</bean>

스프링 설정

MyBatis와 스프링연동을 위해 Bean을 설정한다. 매우 적은 설정만으로 연동이 가능하다.

org.mybatis.spring.SqlSessionFactoryBean : 스프링과 MYBATIS를 연동모듈

  • dataSource : 데이터소스
  • configLocation : 설정파일경로

org.mybatis.spring.mapper.MapperScannerConfigurer : 컴포넌트 스캔을 통하여 Mapper을 찾기 위한 설정.

  • basePackage : Mapper을 찾는 베이스 패키지
  • annotationClass : Mapper을 지정하는 어노테이션 클래스
  • sqlSessionFactory : 앞에 지정한 SqlSessionFactoryBean의 아이디

이 두가지의 빈을 설정하면 스프링과 MyBatis를 연동된다. MyBatis의 설정 파일에서는 기본설정들과 typeAlias, typeHandler 등을 설정한다.

IBATA와 MyBatis의 차이점을 보자면 가장 큰 부분은 퀴리를 호출하는 방식이다. 기존 IBATIS의 경우 xml의 sqlId를 문자열로 정의하여 호출하였지만 Mybatis의 경우 Mapper 인터페이스를 만들어 호출하는 방식을 사용한다. 이번 프로젝트에서는 이와 같은 방식을 사용했다. 호출방식은 다른 방식도 존재하나 이번에는 실제 적용한 방식을 공유하겠다.

UserMapper.java파일은 앞서말한 Sql을 호출하는 인터페이스이며 UserMapper.xml은 실제 쿼리가 존재하는 XML이다. 실제Mapper의 경우 구현로직에서만 호출하기 되어 있어 이와 같은 패키지를 구성하였다.

UserMapper.java의 경우 매우 간단하게 구성된다.

@Mapper
public interface UserMapper {
	List<User> selectUser(@Param("user") User user,@Param("ids") List<Long> ids);

	void insertUser(User user);

	void updateUser(User user);

	void deleteUser(Long id);
}

Mapper

Mapper의 경우 일반적인 인터페이스로 작성한다. 단지 앞서 설정한 어노테이션만 붙여주면 스프링의 컴포넌트 스캐너를 통하여 sql을 호출하는 Mapper이 된다.

인터페이스의 메소드명을 통하여 XMl의 쿼리와 매핑되어 호출되며 자바코드에서는 그냥 인터페이스를 선언후 호출하기만 하면된다.

@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class UserEntityLogic implements UserEntity {

	@Autowired
	private UserMapper userMapper;

	@Override
	public List<User> readUserList(List<Long> ids) {
		return userMapper.selectUser(null,ids);
	}

Mapper 호출

예제를 보면 Mapper인터페이스를 선언하고 @Autowired를 선언하여 간단하게 사용이 가능하다. 소스에 보이는 어노테이션들과 앞의 스프링 설정에 <context:component-scan base-package="com.nextree.sample.entity" />부분을 통하여 base-package를 기준으로 각 어노테이션의 맞는 객체들을 생성하고 주입해준다. 이런 과정을 통하여 실제 쿼리 호출 및 처리를 하게 된다. 아래 XML은 실제로 쿼리가 코딩되는 Mapper Xml이다.

<mapper namespace="com.nextree.sample.entity.logic.mapper.UserMapper">

	<resultMap type="User" id="user-resut">
		<result property="id" 	column="ID" javaType="java.lang.Long"/>
		<result property="name" column="NAME"/>
		<result property="age" 	column="AGE"/>			
		<result property="note" column="NOTE"/>			
	</resultMap>

	<select id="selectUser" resultMap="user-resut" parameterType="java.util.Map">
		SELECT * FROM biz_user
		<trim prefix="WHERE" prefixOverrides="AND |OR ">
			<if test='user != null and user.id != null and !user.id.equals("")'>
				id = #{id}
			</if>
			<if test='user != null and user.name != null and !user.name.equals("")'>
				AND name like #{name}
			</if>
			<if test='user != null and user.age != null and !user.age.equals("")'>
				AND age = #{age}
			</if>
			<if test='ids != null'>
			AND id in
				<foreach collection="ids" item="id" open="(" close=")" separator=",">
					#{id}
				</foreach>
			</if>
		</trim>
	</select>

Mapper XML

Mapper XML의 namesapce부분을 해당 인터페이스를 입력하여 매핑한다. 또한 select id와 Mapper의 메소드명을 통하여 관련 쿼리를 호출한다. resultMap의 경우 기존 IBATIS와 거의 같은 방식으로 사용하면 된다. 동적 SQL의 경우 기존과 비고하여 매우 직관적으로 변경 되었다. 매우 복잡한 동적 SQL의 경우 기존IBATIS에서는 많은 테그를 사용해야 처리가 가능했지만 MyBatis에서는 그동안의 사용 경험을 참조하여 직관적이며 간단하게 수정되었다. 각 동적SQL에 대하여 살펴보겠다.

동적 SQL

다음은 IF테그에 대한 예제이다. IF는 조건문으로 매우 많이 사용된다.

<if test='user != null and user.id != null and !user.id.equals("")'>
    id = #{id}
</if>
<if test='user != null and user.name != null and !user.name.equals("")'>
    AND name like #{name}
</if>
<if test='user != null and user.age != null and !user.age.equals("")'>
    AND age = #{age}
</if>

동적SQL IF

기존 IBATIS의 경우 조건문은 isEqual, isEqual, isNotEmpty, isEmpty을 사용하여 처리하였다. 물론 대부분의 경우 처리가 가능했으나 여러가지 조건을 처리하기위해서는 여러 테크를 중복해서 사용하여 알아보기 힘들어 지는 경우가 발생했다. 예를들어 한컬럼에 대한 여러조건을 걸기 위해서는 isEqual를 여러번 사용해야 했으나 MyBatis에서는 한 테그안에서 해결이 가능하다. 또한 이런식의 처리는 매우 직관적으로 동적SQL을 예측할 수 있다.

또한 switch문에 해당하는 choose,when, otherwise 테그도 추가되었다.

반복문의 경우 foreach가 추가되었다.

<if test='ids != null'>
AND id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
</foreach>
</if>

동적SQL foreach

  • collection : 반복할 값
  • item : 반복시 사용할 이름
  • open : 시작시 만들 SQL
  • close : 종료시 만들 SQL
  • separator : 반복  중간에 만들 SQL

각 속성을 채워서 사용하면 반복문을 사용이 가능하다.

새로이 추가된 trim 테그가 있다. 이테그는 IBATIS동적 쿼리에서 약간 부족하게 느껴졌던 부분을 채워준다. 동적 쿼리를 만들때 앞이나 뒤에 특정 SQL을 붙이거나 제거해야 할 경우가 있다. 예를 들자면 where이나 UPDATE문에서 중간 항목이 조건에 따라 업데이트 항복에서 빼거나 추가해야할때 기존IBATS에서는 처리하기가 곤란한 경우가 있었다. 하지만 이 테그가 추가되어서 이런 부분을 직관적으로 처리가 가능하다.

<trim prefix="WHERE" prefixOverrides="AND |OR ">
	<if test='user != null and user.id != null and !user.id.equals("")'>
		id = #{id}
	</if>
	<if test='user != null and user.name != null and !user.name.equals("")'>
		AND name like #{name}
	</if>
	<if test='user != null and user.age != null and !user.age.equals("")'>
		AND age = #{age}
	</if>
	<if test='ids != null'>
	AND id in
		<foreach collection="ids" item="id" open="(" close=")" separator=",">
			#{id}
		</foreach>
	</if>
</trim>

동적 SQL trim-select

tirm은 앞서 설명한것 처럼 동적 SQL의 결과에 따라 처리가 된다. 이 예제의 경우 trim에서 동적 SQL이 생성되었을 경우 WHERE을 붙여주며 시작부분의 AND나 OR이 올경우 제거해준다. trim은 다음과 속성을 가지고 있다.

  • prefix : 테그안에 동적SQL생성되면 앞에 붙여준다
  • prefixOverrides : 테그안의 동적SQL이 해당 하는 값으로 시작되면 제거한다.
  • suffix : 테그안에 동적SQL 생성되면 뒤에 붙어준다.
  • suffixOverrides : 테그안의 동적 SQL이 해당 값으로 끝나면 제거한다.

이것을 활용하여 업데이트문에도 사용이 가능하다.

<update id="updateUser" parameterType="User">
	UPDATE biz_user
       <trim prefix="SET" suffixOverrides="," >
       <if test='name != null and !name.equals("")'>
           name = #{name},
       </if>
       <if test='age != null and !age.equals("")'>
           age = #{age},
       </if>
      <if test='note != null and !note.equals("")'>
           note = #{note}
       </if>
   	</trim>
       WHERE id = #{id}
</update>

동적 SQL trim-update

적용 후기

프레임워크가 버전이 올라갈수록 새로운 버전에 적응만 한다면 불필요한 소스를 줄여주며 개발자끼리 서로의 소스를 이해하기 쉽게 도와주는 것 같다. IBATIS의 비해 MyBatis는 그런 부분들을 만족시켜주는 버전업이 된 것 같다. 아마 지금 MyBatis를 적용하여 사용하는 프로젝트도 많이 존재하리라 생각된다. 만일 기존 익숙함 때문에 IBATIS를 사용중이라면 MyBatis를 적용하여 사용하는것을 매우 강력하게 추천한다.

namoosori
안녕하세요. 나무소리 입니다. 나무소리는 넥스트리(주)의 교육 브랜드 입니다.넥스트리가 지난 20년 동안 쌓아온 개발 및 교육 경험들을 나무소리를 통해 많은 분들과 공유 하려고 합니다.앞으로 저희 나무소리를 통해 보다 나은 교육을 경험 하실 수 있도록 구성원 모두 최선을 다하겠습니다.