이 글은 코드로 배우는 스프링 웹 프로젝트(남가람북스, 구멍가게 코딩단)을 읽고 공부한 내용을 바탕으로 정리한 글입니다.
1. MyBatis
MyBatis는 SQL 매핑 프레임워크입니다. 개발자들이 JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 사용합니다.
전통적이 JDBC 프로그램의 경우, 직접 Connection
을 맺고 마지막에 close()
를 해주어야 하고, PreparedStatement
를 직접 생성하고 처리해주어야 합니다. 하지만, MyBatis의 경우, 자동으로 Connection close()
를 해주고, MyBatis 내부적으로 PreparedStatement
를 처리해줍니다. 그리고 #{prop}
처럼 속성을 지정하면 내부적으로 자동으로 처리합니다.
아래는 JDBC를 이용해 작성한 코드와 MyBatis를 이용해 작성한 코드를 각각 나타낸 것입니다. (출처: https://hyoni-k.tistory.com/70)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.address.web.dao.NoticeDao;
import com.address.web.entity.Notice;
import com.address.web.entity.NoticeView;
//@Repository
public class JdbcNoticeDao implements NoticeDao {
@Override
public List<NoticeView> getList() throws ClassNotFoundException, SQLException {
int page = 1;
List<NoticeView> list = new ArrayList<>();
int index = 0;
String sql = "SELECT * FROM Notice ORDER BY regdate DESC LIMIT 10 OFFSET ?";
String url = "jdbc:mysql://dev.notead.com:0000/address?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection con = DriverManager.getConnection(url, "address", "123");
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1, (page-1)*10);
ResultSet rs = st.executeQuery();
while (rs.next()) {
NoticeView noticeView = new NoticeView();
noticeView.setId(rs.getInt("ID"));
noticeView.setTitle(rs.getString("TITLE"));
noticeView.setWriterId(rs.getString("writerId"));
noticeView.setRegdate(rs.getDate("REGDATE"));
noticeView.setHit(rs.getInt("HIT"));
noticeView.setFiles(rs.getString("FILES"));
noticeView.setPub(rs.getBoolean("PUB"));
list.add(noticeView);
}
rs.close();
st.close();
con.close();
return list;
}
@Override
public Notice get(int id) {
// TODO Auto-generated method stub
return null;
}
@Override
public int insert(Notice notice) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int update(Notice notice) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int delete(int id) {
// TODO Auto-generated method stub
return 0;
}
}
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.address.web.entity.Notice;
import com.address.web.entity.NoticeView;
@Mapper
public interface NoticeDao {
@Select("SELECT * FROM Notice WHERE ${field} LIKE '%${query}%' ORDER BY regdate DESC LIMIT 10")
List<NoticeView> getList(int page, String query, String field) throws ClassNotFoundException, SQLException;
@Select("SELECT * FROM Notice WHERE id= #{id}")
Notice get(int id);
int insert(Notice notice);
int update(Notice notice);
int delete(int id);
}
MyBatis가 자동으로 처리해주는 것이 많아 코드가 훨씬 간결해진 것을 확인할 수 있습니다.
MyBatis도 mybatis-spring 라이브러리를 사용하여 쉽게 연동할 수 있습니다. 전체적인 구조는 아래와 같습니다.
2. MyBatis 관련 라이브러리 추가
pom.xml에 mybatis 관련 라이브러리를 추가합니다.
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
3. SqlSessionFactory
MyBatis에서 가장 핵심 객체는 SqlSession
과 SQLSessionFactory
입니다. 스프링에 SqlSessionFactory
를 등록하는 작업은 SqlSessionFactoryBean
을 이용합니다. 따라서 applicationContext.xml
에 다음 코드를 추가해줍니다.
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
아래 테스트 코드를 추가로 작성해서 돌려보면 SqlSession
객체가 생성되었음을 확인할 수 있습니다.
...
import org.apache.ibatis.session.SqlSession;
...
public class DataSourceTests {
...
@Setter(onMethod_ = { @Autowired })
private SqlSessionFactory sqlSessionFactory;
...
@Test
public void testMyBatis() {
try (SqlSession session = sqlSessionFactory.openSession();
Connection con = session.getConnection();
) {
log.info(session);
log.info(con);
} catch (Exception e) {
fail(e.getMessage());
}
}
}
4. 스프링과 연동하기
SQLSessionFactory
를 이용해서 SQL 쿼리문을 직접 작성하고 직접 Connection
을 얻어서 JDBC 코딩이 가능하지만, 좀 더 편하게 작업하기 위해서는 SQL을 어떻게 처리할 것인지 별도의 설정을 분리해주고, 자동으로 처리되는 방식을 이용하는 것이 좋습니다. 이를 위해 MyBatis의 Mapper
를 작성해보겠습니다. Mapper는 SQL과 그에 대한 처리를 매핑해주는 역할입니다. mybatis-spring
라이브러리를 이용하여 XML과 인터페이스 + 어노테이션 형태로 Mapper를 작성할 수 있습니다.
1. Mapper 인터페이스
예제에서는 Mapper interface를 작성해보겠습니다. src/main/java/org/your_packages.../mapper
패키지를 만들고, TimeMapper
라는 인터페이스를 추가합니다.
TimeMapper
는 다음과 같이 작성합니다.
import org.apache.ibatis.annotations.Select;
public interface TimeMapper {
@Select("SELECT sysdate() FROM dual;")
public String getTime();
}
Mapper를 작성했다면 MyBatis가 동작할 때 Mapper를 인식할 수 있도록 applicationContext.xml
에 다음 코드를 추가해줍니다.
<mybatis-spring:scan base-package="com.hellomygreenworld.ex00.mapper" />
테스트 코드를 작성해보겠습니다.
import lombok.Setter;
import lombok.extern.log4j.Log4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.hellomygreenworld.ex00.mapper.TimeMapper;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
@Log4j
public class TimeMapperTests {
@Setter(onMethod_ = { @Autowired })
private TimeMapper timeMapper;
@Test
public void testGetTime() {
log.info(timeMapper.getClass().getName());
log.info(timeMapper.getTime());
}
}
@Test
public void testGetTime2() {
log.info(timeMapper.getTime2());
}
TimeMapper
인터페이스만 만들어주었는데, 내부적으로 클래스가 만들어지고, getTime()
메서드가 정상적으로 실행되어 시간을 출력해주고 있습니다.
2. XML mapper와 같이 쓰기
SQL이 복잡하거나 길어지는 경우에는 어노테이션보다 XML을 이용하는 방식이 더 좋습니다. mybatis-spring
은 Mapper 인터페이스와 XML을 동시에 이용할 수 있습니다.
TimeMapper 인터페이스에는 아래 코드만 추가해줍니다. @Select 어노테이션도 없고, SQL도 없습니다. SQL은 따로 XML을 만들어서 처리해주겠습니다.
public String getTime2();
TimeMapper.xml
을 아래와 같이 작성합니다. XML 파일의 위치는 Mapper 인터페이스가 있는 곳에 작성하거나 src/main/resources
에 Mapper 인터페이스와 같은 디렉토리를 생성하여 저장하면 좋습니다.
<?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.hellomygreenworld.ex00.mapper.TimeMapper">
<select id="getTime2" resultType="String">
SELECT sysdate() from dual
</select>
</mapper>
MyBatis는 Mapper 인터페이스와 XML을 인터페이스의 이름과 namespace
속성값으로 판단하는데, TimeMapper 인터페이스가 존재하고, 같은 namespace의 XML이 존재하면 이를 병합해서 처리합니다.
테스트 코드에 다음 코드를 추가하여 테스트도 해봅니다.
@Test
public void testGetTime2() {
log.info(timeMapper.getTime2());
}
5. log4jdbc-log4j2 설정
MyBatis는 내부적으로 JDBC의 Preparedstatement
를 이용해서 SQL을 처리하는데, SQL에 전달되는 파라미터가 JDBC에서와 같이 '?'로 치환되어 처리되어 값 확인이 어렵고, SQL의 내용 확인도 어렵습니다. 따라서 SQL 로그를 제대로 확인하기 위해서 log4jdbc-log4j2
라이브러리를 사용할 수 있습니다.
1. 라이브러리 추가
우선, pom.xml
에 log4jdbc-log4j2
라이브러리를 추가해줍니다.
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2</artifactId>
<version>1.16</version>
</dependency>
2. 로그 설정 파일 추가
src/main/resources
아래에 log4jdbc.log4j2.properties
파일을 추가합니다.
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
3. JDBC 연결 정보 수정
log4jdbc를 이용하기 위해 applicationContext.xml에 작성되어있는 JDBC Driver와 URL 정보를 수정합니다.
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy" />
<property name="jdbcUrl" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/SpringWeb?serverTimezone=Asia/Seoul" />
<property name="username" value="root" />
<property name="password" value="P@ssw0rd" />
</bean>
수정 후, 기존의 테스트 코드를 실행하면 sql 로그가 보기 좋게 찍힙니다!
'Java > Spring' 카테고리의 다른 글
[Spring MVC] Controller (0) | 2023.01.03 |
---|---|
[Spring MVC] 기본 구조 (0) | 2023.01.02 |
[Spring] JDBC(MySQL) 연결 (0) | 2023.01.02 |
[Spring] 의존성 주입(DI) (1) | 2022.12.30 |
[Spring MVC] IntelliJ에서 Project 생성하기 (0) | 2022.12.29 |
댓글