먼저 의존성 추가
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
mybatis-spring 모듈을 사용하면, 스프링의 ApplicationContext에 마이바티스 빈을 설정할 수 있다.
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:/mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:/sql-map/*.xml"/>
<!-- statement 선언의 오류를 좀 더 빠르게 파악하기 위해서 true로 설정 -->
<property name="failFast" value="true"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
<constructor-arg index="1" value="BATCH" />
</bean>
스프링에서 SqlSessionFactory빈을 만들고 SqlSessionTemplate빈을 만들어서 생성자로 넣어준다. SqlSessionTemplate빈은 thread safe하게 SqlSession 객체를 제공하기 때문에, 여러 개의 스프링 빈에서 동일한 SqlSessionTemplate 객체를 공유할 수 있다. 개념적으로는 SqlSessionTemplate이 스프링 DAO 모듈의 JdbcTemplate과 동일하다.
mapper interface가 있는 패키지를 체크하고 자동으로 mapper interface를 mapper bean으로 등록하기 위해 MapperScannerConfigurer를 사용할 수 있다.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test.mapper" />
<property name="processPropertyPlaceHolders" value="true" />
</bean>
java config code
@Configuration
@MapperScan("com.test.mapper")
public class TestConfig {
@Autowired
ApplicationContext applicationContext;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:/sql-map/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate;
}
}
MapperScanner를 사용하면 mapper namespace로 맵핑시켜서 사용하면 된다.
@Repository(value = "TestMapper")
public interface TestMapper {
}
<mapper namespace="com.test.TestMapper">
만약 MapperScanner를 사용안한다면 sqlSessionTemplate을 직접 DI받아서 사용하면 된다.
@Repository
public class TestDaoImpl implements TestDao {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
}
스프링을 사용한 트랜잭션 관리
<tx:annotation-driven></tx:annotation-driven>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
트랜잭션매니저에서 사용한 dataSource는 sqlSessionFactory 빈이 사용하는 것과 동일한 dataSource여야 한다.
ResultMap 확장이 가능하다.
<resultMap type="Student" id="StudentResult">
<result property="name" column="name"/>
<result property="email" column="email"/>
...
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult" extends="StudentResult">
<result property="address.addrId" column="addr_id"/>
<result property="address.street" column="street"/>
...
</resultMap>
두개의 트랜잭션매니저를 묶는 chainedTransactionManager 가 필요하다. cf) spring data commons 의존성이 없다면 추가
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.1.17.RELEASE</version>
</dependency>
그리고 설정에 추가한다.
<!-- Chained Transaction manager for a multi tx -->
<bean id="chainedTransactionManager" class="org.springframework.data.transaction.ChainedTransactionManager">
<constructor-arg>
<list>
<ref bean="transactionManager" />
<ref bean="secondaryTransactionManager" />
</list>
</constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="secondaryTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="secondaryDataSource"/>
</bean>
그리고 secondaryDataSource 빈을 등록한다.
<bean id="secondaryDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<!-- 프로퍼티 설정 -->
</bean>
그리고 동적 db 판별을 위한 설정으로 databaseIdProvider 빈을 등록한다. 서로 호환이 안되는 쿼리가 있을 경우 사용한다.
<bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
<property name="properties">
<props>
<prop key="MSSQL">MSSQL</prop>
<prop key="MySQL">MYSQL</prop>
</props>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 동적DB 판별을 위한 설정 -->
<property name="databaseIdProvider" ref="databaseIdProvider"/>
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="secondarySqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 동적DB 판별을 위한 설정 -->
<property name="databaseIdProvider" ref="databaseIdProvider"/>
<property name="dataSource" ref="secondaryDataSource" />
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
</bean>
<bean id="secondarySqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="secondarySqlSessionFactory"></constructor-arg>
</bean>
설정이 끝나면 쿼리에서 다음과 같이 분기해서 사용하면 된다.
<select>
<if test="_databaseId == 'MSSQL'">
<!-- MSSQL 전용쿼리 -->
</if>
<if test="_databaseId == 'MYSQL'">
<!-- MYSQL 전용쿼리 -->
</if>
</select>
자바코드에서 사용할 때는 Dao 를 두개 만들어야한다. ex) TaskDao, TaskSecondaryDao 그래서 서비스레이어에서 각 dao를 호출해서 dual-write를 수행하고, TaskSecondaryDao 에서는 secondarySqlSessionTemplate 를 주입받아 사용한다.
@Autowired
@Qualifier("secondarySqlSessionTemplate")
private SqlSessionTemplate secondarySqlSessionTemplate;
참고로 호환이 안되는 쿼리 분기는 master, secondary 관계없이 작업해야한다. mssql -> mysql 로 바꿨을 때 master, secondary 가 서로 바뀌는데 이때 영향을 받지 않는다.
마이바티는 <if>
, <choose>
, <where>
, <foreach>
, <trim>
과 같은 엘리먼트를 사용해서 동적인 SQL 쿼리를 만들도록 지원한다.
if 엘리먼트는 test 조건이 true가 될 때만 해당되는 SQL이 쿼리에 추가된다.
<resultMap type="Course" id="CourseResult">
<id property="courseId" column="course_id"/>
<result property="name" column="name"/>
<result property="description" column="description"/>
<result property="start_date" column="startDate"/>
<result property="end_date" column="endDate"/>
</resultMap>
<select id="searchCourses" parameterType="hashmap" resultMap="CourseResult">
<![CDATA[
select * from courses where tutor_id=#{tutorId}
<if test="courseName != null">
and name like #{courseName}
</if>
<if test="startDate != null">
and start_date >= #{startDate}
</if>
<if test="endDate != null">
and end_date <= #{endDate}
</if>
]]>
</select>
마이바티스는 <choose>
의 test 조건을 확인해서 가장 먼저 true가 되는 조건을 사용한다. 어느 조건도 true가 되지 못하면, <otherwise>
절이 사용된다.
select * from courses
<choose>
<when test="searchBy == 'Tutor'">
where tutor_id = #{tutorId}
</when>
<when test="searchBy == 'CourseName'">
where name like #{courseName}
</when>
<otherwise>
where tutor start_date > = now()
</otherwise>
</choose>
가끔은 모든 검색 조건 중 하나도 선택하지 않을 수 있다 마이바티스는 이러한 SQL문을 만들기 위해 <where>
엘리먼트를 제공한다.
내부조건을 나타내는 엘리먼트에 의해 리턴되는 내용이 있을 때에만 where를 추가한다(모든 검색 조건이 필수가 아니라 선택할 수 있을 때 사용된다).
select * from courses
<where>
<if test="tutorId != null">
tutor_id=#{tutorId}
</if>
<if test="courseName != null">
and name like #{courseName}
</if>
<if test="startDate != null">
and start_date >= #{startDate}
</if>
<if test="endDate != null">
and end_date <= #{endDate}
</if>
</where>
<trim>
엘리먼트는 <where>
엘리먼트와 유사하지만 접두사/접미사를 추가하거나 제거하는 기능을 추가로 제공한다.
<if>
조건이 true이면 where절을 추가하고 where 뒤에 접두사 AND나 OR가 있으면 제거한다.
select * from courses
<trim prefix="where" prefixOverrides="AND | OR">
<if test="tutorId != null">
tutor_id=#{tutorId}
</if>
<if test="courseName != null">
and name like #{courseName}
</if>
</trim>
배열이나 리스트를 통해 반복적인 처리를 하고 AND나 OR 조건을 붙이거나 IN 절을 처리하는 공통적인 요구사항을 담당한다.
tutor_id의 아이디가 1,3,6인 교사가 가르치는 모든 교육과정을 찾는다고 해보자. tutor_id의 아이디 목록을 매핑 구문에 전달하고 <foreach>
엘리먼트를
사용해서 리스트를 반복 처리해서 동적 쿼리를 만들 수 있다.
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
select * from courses
<if test="tutorIds != null">
<where>
<foreach item="tutorId" collections="tutorIds">
OR tutor_id=#{tutorId}
</foreach>
</where>
</if>
</select>
IN절을 만드는법을 알아보자.
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
select * from courses
<if test="tutorIds != null">
<where>
tutor_id IN
<foreach item="tutorId" collections="tutorIds" open="(" seperator="," close=")">
#{tutorId}
</foreach>
</where>
</if>
</select>
<where>
엘리먼트와 유사하고 내부 조건이 리턴하는 내용이 있을 경우 SET을 추가할 것이다.
<update id="updateStudent" parameterType="Student">
update students
<set>
<if test="name != null">name=#{name},</if>
<if test="email != null">email=#{email},</if>
<if test="phone != null">phone=#{phone},</if>
</set>
where stud_id=#{id}
</update>
여기서 <set>
엘리먼트는 <if>
조건이 텍스트를 리턴한다면 set 키워드를 추가하고 마지막에 콤마(,)를 제거한다.