마이바티스는 객체와 SQL사이의 Mapping을 도와주고, SQL쿼리문을 xml파일로 분리하여 관리한다. DB에 접근할 때, 자바코드에서 xml태그의 id를 호출하기만 하면 되기 때문에 개발이 용이하다. SQL 수정시 굳이 프로젝트를 다시 컴파일하지 않아도 되기 때문에 매우!! 편리하다.
마이바티스는 SqlSessionFactory를 사용해 SqlSession을 생성한다. 이로서 세션을 한번 생성했을 때 매핑구문을 실행하거나 커밋, 롤백을 수행할 수 있다.
그런데 마이바티스 스프링 연동모듈을 사용하면 SqlSessionFactory를 직접 사용할 필요 없이 쓰레드에 안전한 SqlSession 개체를 스프링 빈에 주입해 사용할 수 있다.
현재 진행중인 프로젝트를 통해 Spring에서 마이바티스를 사용하는 방법을 정리하자.
참고로 이와 관련된 전체 코드는 Github의 Hola 레파지토리에 저장되어있으니 구경하세용😊
현재 프로젝트 환경은 다음과 같다
<!--Mybatis library-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!--Mybatis Spring library-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
: 스프링의 DataSource 및 트랜잭션 관리 기능을 Mybatis와 연동할 때, 이에 필요한 기능을 제공한다. 핵심 클래스는 SqlSessionFactoryBean과 SqlSessionTemplate이다.
mybatis-spring 모듈이 제공하는 SqlSessionFactoryBean을 이용해서 mybatis의 SqlSessionFactory를 생성한다.
<!--Mybatis 연결 설정-->
<!-- SqlSessionFactory 설정 : dataSource를 참조, mybatis-config.xml 경로 설정-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:/mybatis-config.xml"/>
<!--mapper 경로-->
<property name="mapperLocations" value="classpath:com.hyegyeong.hola.mappers/**/*Mapper.xml" />
</bean>
참고로 나는 dataSource 설정을 applicationContext.xml에 아래와 같이 입력해놨다.
<!--DataSource 설정-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>
<property name="url" value="jdbc:log4jdbc:mysql://x.x.x.x:x(DB연결 IP를 입력)/X_DB(가져올 데이터베이스입력)?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC"/>
<property name="username" value="xxx(DB사용자)"/>
<property name="password" value="xxx(DB사용자패스워드)"/>
</bean>
SqlSessionTemplate 클래스는 MyBatis의 SqlSession 기능과 스프링의 DB 지원 기능을 연동해준다. SqlSessionTemplate이 내부적으로 사용하는 SqlSession 프록시 객체가 스프링을 연동해주기 때문에 SqlSessionTemplate을 사용해 DAO를 구현하면 된다.
<!-- Mybatis에서 DAO를 이용하는 경우-->
<!-- DB 연결 + 종료작업처리 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
Mybatis 설정이 올바르게 되었는지 테스트 코드로 검증한다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:web/WEB-INF/applicationContext.xml"})
public class MybatisTest {
@Inject
private SqlSessionFactory sqlSessionFactory;
@Test
public void testFactory() {
System.out.println(sqlSessionFactory);
}
@Test
public void testSession() throws Exception {
try(SqlSession session = sqlSessionFactory.openSession()) {
System.out.println(session);
} catch (Exception e) {
e.printStackTrace();
}
}
}
CRUD에 사용될 데이터를 담을 DTO 객체이다.
Lombok의 Data 어노테이션으로 Getter, Setter를 한번에 설정한다.
@Data
public class DiaryDTO {
private int diaryId; //게시물번호
private String title; //제목
private String content; //본문
private int memberId; //작성자(회원번호)
private String moodCode; //MoodCode
private int heart; //하트수
private String opnFlag; //공유여부
private Date regDate; //작성날짜
private Date updDate; //수정날짜
private String delFlag; //삭제여부
}
@Component
public class MyDiaryDAO {
@Inject
private SqlSession sqlSession;
private static final String NAMESPACE = "com.hyegyeong.hola.mappers.diaryMapper";
/**
* 새로운 다이어리 추가
* @param diaryDTO 추가하려는 내용을 담고있는 다이어리 객체
* @return insert에 의해 영향을 받은 레코드 수
*/
public int insertDiary(DiaryDTO diaryDTO) {
return sqlSession.insert(NAMESPACE + ".insertDiary", diaryDTO);
}
/**
* 특정 회원이 작성한 모든 다이어리 객체를 가져온다
* @param memberId 회원 아이
* @return 특정 회원이 작성한 모든 다이어리 객체
*/
public List<DiaryDTO> selectDiaryList(int memberId) {
List<DiaryDTO> diaryList;
diaryList = sqlSession.selectList(NAMESPACE + ".selectDiaryList", memberId);
return diaryList;
}
/**
* 특정 다이어리 하나를 읽어온다
* @param diaryId 읽으려는 다이어리의 아이디
* @return 읽어온 다이어리 객체
*/
public DiaryDTO selectDiary(int diaryId) throws BusinessException {
DiaryDTO diaryDTO = sqlSession.selectOne(NAMESPACE + ".selectDiary", diaryId);
return diaryDTO;
}
/**
* 다이어리 내용을 수정한다
* @param diaryDTO 수정하려는 다이어리의 아이디
* @return update영향을 받은 레코드 수
*/
public int updateDiary(DiaryDTO diaryDTO) {
return sqlSession.update(NAMESPACE + ".updateDiary", diaryDTO);
}
/**
* 다이어리를 삭제한다
* @param diaryId 삭제하려는 다이어리 이이디
* @return delete영향을 받은 레코드 수
*/
public int deleteDiary(int diaryId) {
return sqlSession.delete(NAMESPACE + ".deleteDiary", diaryId);
}
}
<?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="com.hyegyeong.hola.mappers.diaryMapper">
<resultMap id="diaryResult" type="com.hyegyeong.hola.dto.DiaryDTO">
<id column="DIARY_ID" property="diaryId"/>
<result column="TITLE" property="title"/>
<result column="CONTENT" property="content"/>
<result column="MEMBER_ID" property="memberId"/>
<result column="MOOD_CODE" property="moodCode"/>
<result column="HEART" property="heart"/>
<result column="OPN_FLAG" property="opnFlag"/>
<result column="REG_DATE" property="regDate"/>
<result column="UPD_DATE" property="updDate"/>
<result column="DEL_FLAG" property="delFlag"/>
</resultMap>
<insert id="insertDiary" useGeneratedKeys="true" keyProperty="diaryId">
INSERT INTO DIARYS_MST (
TITLE
,CONTENT
,MEMBER_ID
,MOOD_CODE
,OPN_FLAG
)
VALUES (
#{title}
,#{content}
,#{memberId}
,#{moodCode}
,#{opnFlag}
)
</insert>
<select id="selectDiaryList" resultMap="diaryResult">
SELECT *
FROM DIARYS_MST
WHERE MEMBER_ID = #{memberId}
AND DEL_FLAG = 'N'
</select>
<select id="selectDiary" resultMap="diaryResult">
SELECT *
FROM DIARYS_MST
WHERE DIARY_ID = #{diaryId}
AND DEL_FLAG = 'N'
</select>
<update id="updateDiary">
UPDATE DIARYS_MST
SET TITLE = #{title}
,CONTENT = #{content}
,MOOD_CODE = #{moodCode}
,OPN_FLAG = #{opnFlag}
WHERE DIARY_ID = #{diaryId}
AND DEL_FLAG = 'N'
</update>
<delete id="deleteDiary">
UPDATE DIARYS_MST
SET DEL_FLAG = 'Y'
WHERE DIARY_ID = #{diaryId}
</delete>
</mapper>
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:web/WEB-INF/applicationContext.xml"})
public class MyDiaryDaoTest {
@Inject
private MyDiaryDAO myDiaryDAO;
/**
* 회원이 새로운 여행 다이어리를 작성해 저장한다
* (원래 파라미터로 들어온 다이어리 객체에서 회원 번호를 알아내서 저장하나, 회원 번호를 임의로 지정해주었음)
* @throws Exception
*/
@Test
public void testInsertDiary() throws Exception {
DiaryDTO diaryDTO = new DiaryDTO();
diaryDTO.setTitle("testTitle3");
diaryDTO.setContent("test Content3");
diaryDTO.setMemberId(2);
diaryDTO.setMoodCode("NO");
diaryDTO.setOpnFlag("Y");
int resultNums = myDiaryDAO.insertDiary(diaryDTO);
log.info("success insert Diary : " + resultNums);
}
/**
* 특정 회원이 작성한 다이어리를 모두 읽어온다
* @throws BusinessException
*/
@Test
public void testSelectDiaryList() throws Exception {
myDiaryDAO.selectDiaryList(1);
}
/**
* 작성한 다이어리들 중 하나의 다이어리를 읽어온다.
* @throws Exception
*/
@Test
public void testSelectDiary() throws Exception {
myDiaryDAO.selectDiary(1); //읽으려는 다이어리의 ID
}
/**
* diaryId에 해당하는 다이어리를 수정한다
* @throws Exception
*/
@Test
public void testUpdateDiary() throws Exception {
DiaryDTO diaryDTO = new DiaryDTO();
diaryDTO.setDiaryId(1);
diaryDTO.setTitle("updated Title3");
diaryDTO.setContent("updated Content1");
diaryDTO.setMoodCode("NA");
diaryDTO.setOpnFlag("Y");
int resultNums = myDiaryDAO.updateDiary(diaryDTO);
log.info("success update Diary : " + resultNums);
}
@Test
public void testDeleteDiary() throws Exception {
int resultNums = myDiaryDAO.deleteDiary(1); //1번 다이어리를 삭제(삭제여부를 Y로 업데)
log.info("success delete Diary : " + resultNums);
}
}