MyBatis 란?
SQL 매핑 프레임워크로 JDBC의 대안으로 사용한다. 스프링프레임 워크에서는 mybatis-spring이라는 라이브러리를 통해 연동작업을 처리할 수 있다.
MyBatis 관련 라이브러리 추가
pom.xml 추가
jre라이브러리에 jdbc관련 모듈이 있지만, 선택적으로 있어 추가한다.
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- Mapper 사용하기 위해 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.11</version>
</dependency>
SQLSeesionFactory( root-context.xml, RootConfig 추가)
mybatis의 핵심 객체는 SQLSesstion과 SQLSessionFactory이다. SQLSessionFactory에서 SQLSesstion을 만들고, SQLSesstion을 통해 Connection을 생성하거나 SQL을 전달하고, 리턴을 받는 구조로 작성한다.
root-context.xml
- 네임스페이스 추가
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
RootConfig
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource());//db의 url, username,password등의 정보를 담은 메서드
//sqlSessionFactory은 SqlSessionFactoryBean클래스 타입이라 형변환
return (SqlSessionFactory)sqlSessionFactory.getObject();
}
테스트하기
- testMyBatis.java
@Test
public void testMyBatis() {
// java의 예외차리 문법 중 try-with-resources문법
// AutoCloseable 인터페이스(close메서드를 정의한)를 구현한 리소스(SqlSession,Connection..)를
// 자동으로 닫는데 사용됨(리소스 누수를 방지함)
// 주로 파일, 네트워크 연결, 데이터베이스 연결과 같은 리소스를 다룰 때 활용함
// sqlsessiton에 autoclose가 구현되어 있고,
// try 구문이 끝나면 close()메서드가 호출되어 자동으로 리소스가 닫힌다.
try (SqlSession session = sqlSessionFactory.openSession();
// RootConfig에 정의한 데이터 연결 정보를 session에 담음
Connection con = session.getConnection();
// Connection클래스는 데이터베이스와 연결 및 반환하는 역할
) {
log.info(session);
log.info(con);
} catch (Exception e) {
fail(e.getMessage());
}
}
sqlsesstion에서 autoclose가 있는지 확인해보았다.
세션팩토리 빈에서 만든 트랜젝션용 데이타소스를 세션팩토리에서 db연결을 위한 sql세션을 생성하는 것 같다.
org.apache.ibatis.session.SqlSessionFactory;
\/**
* Creates an {@link SqlSession} out of a connection or a DataSource
* (연결 또는 데이터 소스에서 {@link SqlSession}을 생성합니다)
*
* @author Clinton Begin
*/
public interface SqlSessionFactory {
// 기본 설정으로 SqlSession을 생성하는 메서드
SqlSession openSession();
// 트랜잭션 자동 커밋 여부를 지정하여 SqlSession을 생성하는 메서드
SqlSession openSession(boolean autoCommit);
// 기존 JDBC 연결을 사용하여 SqlSession을 생성하는 메서드
SqlSession openSession(Connection connection);
// 지정된 트랜잭션 격리 수준을 사용하여 SqlSession을 생성하는 메서드
SqlSession openSession(TransactionIsolationLevel level);
// 지정된 실행자 유형으로 SqlSession을 생성하는 메서드
SqlSession openSession(ExecutorType execType);
// 실행자 유형과 트랜잭션 자동 커밋 여부를 지정하여 SqlSession을 생성하는 메서드
SqlSession openSession(ExecutorType execType, boolean autoCommit);
// 실행자 유형과 트랜잭션 격리 수준을 지정하여 SqlSession을 생성하는 메서드
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
// 실행자 유형과 기존 JDBC 연결을 사용하여 SqlSession을 생성하는 메서드
SqlSession openSession(ExecutorType execType, Connection connection);
// 현재 SqlSessionFactory의 설정(Configuration)을 가져오는 메서드
Configuration getConfiguration();
}
세션팩토리빈은 트랜젝션용데이타소스를 생성하는 것 같다. 그리고 세션팩토리와 타입을 맞춘다.
org.mybatis.spring.SqlSessionFactoryBean;.setDataSource
/**
* Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} should
* match the one used by the {@code SqlSessionFactory}: for example, you could specify the same JNDI DataSource for
* both.
* (이 인스턴스가 관리할 트랜잭션용 JDBC {@code DataSource}를 설정합니다. 이 {@code DataSource}는 {@code SqlSessionFactory}에서 사용하는 것과 일치해야 합니다. 예를 들어, 동일한 JNDI DataSource를 두 곳에 지정할 수 있습니다.)
*
* A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code accessing
* this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}.
* (이 {@code DataSource}에 대한 트랜잭션 처리 가능한 JDBC {@code Connection}은 이 {@code DataSource}에 직접 액세스하는 응용 프로그램 코드에게 제공됩니다. 이를 통해 {@code DataSourceUtils} 또는 {@code DataSourceTransactionManager}를 통해 접근할 수 있습니다.)
*
* The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not a
* {@code TransactionAwareDataSourceProxy}. Only data access code may work with
* {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the underlying target
* {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} passed in, it will be
* unwrapped to extract its target {@code DataSource}.
* (여기에서 지정한 {@code DataSource}는 트랜잭션을 관리하기 위한 대상 {@code DataSource}여야 합니다. {@code TransactionAwareDataSourceProxy}가 아니어야 합니다. {@code TransactionAwareDataSourceProxy}는 데이터 액세스 코드만 사용할 수 있으며, 트랜잭션 관리자는 기본 대상 {@code DataSource}에서 작동해야 합니다. 그럼에도 불구하고 {@code TransactionAwareDataSourceProxy}가 전달된 경우, 해당 프록시는 언랩핑(unwrapping)되어 대상 {@code DataSource}를 추출합니다.)
*
* @param dataSource
* a JDBC {@code DataSource}
*/
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
// TransactionAwareDataSourceProxy를 사용하는 경우 해당 프록시의 기본 대상 DataSource에서 트랜잭션을 수행해야 합니다.
// 그렇지 않으면 데이터 액세스 코드가 적절하게 노출된 트랜잭션(대상 DataSource의 트랜잭션)을 보지 못할 것입니다.
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
} else {
// TransactionAwareDataSourceProxy가 아닌 경우, 지정된 dataSource를 그대로 사용합니다.
this.dataSource = dataSource;
}
}
- SQLSessionFactoryBean에서는 데이터 연결을 위한 datasouce를 트랜젝션용 datasouce로 만들어 주는 것 같다.
- test코드 결과
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
// 스프링 빈(Bean) 설정 파일을 읽는 작업을 수행하는 XmlBeanDefinitionReader 클래스가
// "root-context.xml" 파일을 읽고 XML 빈 정의 및 로드
INFO : org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@618425b5: startup date [Sun Oct 29 14:08:16 KST 2023]; root of context hierarchy
// GenericApplicationContext 클래스가 스프링 애플리케이션 컨텍스트를 초기화
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
// JSR-330 'javax.inject.Inject' 어노테이션을 지원하고, @autowiring에 지원되고 있음
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
// Hikari 풀1이 동작
INFO : com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.jdbc.JDBC4Connection@26425897
// Hikari 풀에 새로운 데이터베이스(JDBC4Connection@26425897) 연결이 추가됨
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
// Hikari 풀1이 시작이 완료함. 커넥션 풀이 초기화 되고, 사용가능 한 상태로 설정됨
INFO : org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@618425b5: startup date [Sun Oct 29 17:22:33 KST 2023]; root of context hierarchy
// Spring의 애플리케이션 컨텍스트(GenericApplicationContext)가 종료됨
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
// Hikari 풀1을 종료함
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
// Hikari 풀1이 종료가 완료됨
---------------------------------------------------------
log.info("sqlSessionFactory.openSession() :" + sqlSessionFactory.openSession());
log.info("session은: " + session);
log.info("session.getConnection() :" + session.getConnection());
log.info("con은: " + con);
위의 로그를 출력하면,
sqlSessionFactory.openSession() :org.apache.ibatis.session.defaults.DefaultSqlSession@59aa20b3
// DefaultSqlSession@59aa20b3의 주소를 가진 객체를 반환함(MyBatis에서 생성한 새로운 SqlSession 객체)
session은: org.apache.ibatis.session.defaults.DefaultSqlSession@363f6148
// 변수 session는 DefaultSqlSession@363f6148의 주소 값을 가지며 SqlSession객체를 참조함
session.getConnection() :HikariProxyConnection@1260487756 wrapping com.mysql.jdbc.JDBC4Connection@26425897
// session.getConnection()에서 반환된 객체가 SqlSession에서 데이터베이스 연결 객체를 반환하고 HikariProxyConnection가 래핑함
// JDBC 드라이버와 관련있는 JDBC4Connection클래스가 커넥션풀의 커넥션 객체에 매핑됨.
con은: HikariProxyConnection@1260487756 wrapping com.mysql.jdbc.JDBC4Connection@26425897
// session.getConnection()과 같은 주소 값을 참조함.
- 스프링환경설정 파일을 읽고, 커넥션 풀에 데이터베이스를 추가하여 초기화 하여 해당 클래스를 실행할 준비를 끝내고,
- MyBatis에서 생성한 객체 SqlSession 데이터베이스연결객체(JDBC드라이버)를 HikariProxyConnection가 래핑한다.
- 이후 스프링 컨텍스트가 종료되고, 커넥션풀도 종료가 된다.
`close;` 를 사용하 여 닫기
커넥션풀이 종료되는 시점이 커넥션 풀의 사용이 끝났을때인지 확인하고 싶었다. 이렇게 확인하는게 맞을 진 모르겠지만... sysout을 추가하여 콘솔을 확인하니 마찬가지로 컨텍스트가 종료되고, hikari 커넥션 풀이 종료되는 걸 확인했다...
커넥션풀이 반환도 알아서 처리하는 것으로 알고 있는데.
close(); 메서드로 직접 닫으면 더 빠르게 처리가 되는것을 확인하고 싶었다.
위의 로직을 test하면 테스트 실행 시간이 0.046s
close(); 를 추가하면 0.031s
스프링과 연동처리( mabatis-spring을 이용 Mapper)
mabatis-spring의 Mapper를 사용하면 SQL을 어떻게 처리할 것인지 별도의 설정을 분리 및 자동 처리를 할 수 있다.
즉, 개발자가 SQL과 인터페이스만 작성하면 mabatis-spring가 SQL처리가 되는 클래스를 자동으로 생성한다.
spring 에서는 Mapper를
1) XML과 인터페이스+어노테이션 형태로 작성
2) Mapper 인터페이스+XML 형태로 작성 : SQL이 복잡해 지는 경우 사용
RootConfig.java
- 스프링 설정 파일. DB연동을 위한 데이터
- DB연동을 위한 DataSource 빈 설정을 java설정으로 할때( class드라이버 Log4JDBC로 변경, url 변경)
Pom.xml
- maven프로젝트 설정파일. mybatis-spring 라이브러리
root-context.xml
- 스프링 애플리케이션의 루트 컨텍스트 설정 파일. mybatis에서 사용될 DB를 연동하기 위한 설정값
- DB연동을 위한 DataSource 빈 설정을 XML에서 할때( class드라이버 Log4JDBC로 변경, url 변경)
TimeMapper.java/ TimeMapper.xml
- MyBatis 매퍼 인터페이스(@MapperScan의 대상).
- 1) 어노테이션을 이용해 mybatis에서 사용될 SQL 구문을 담거나
- 2)TimeMapper.java에서 정의 mybatis에서 사용될 SQL 구문을 담고 있는 xml파일
Mapper 설정
- mabatis-spring 네임스페이스 추가
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
- root-config
Mapprer 테스트하기(spring인터페이스를 이용해 객체 생성)
- TimeMapper.java
인터페이스 생성
public interface TimeMapper {
// 1) Mapper
// SQL은 어노테이션으로 퍼리
// @Select() MyBatis 어노테이션(SQL)
@Select("SELECT now() from dual") // 데이터베이스에서 현재 시간 및 날짜를 가져옴
public String getTime();
// 2) XML+Mapper
// SQL은 .xml 파일에서 처리할 예정
public String getTime2();
}
* SQL함수는 DB벤더사 마다 지원하는 명칭이 다르다
현재시간을 가져올때 mysql은 `now()`를 사용하고, oracle은 `sysdate`를 사용한다.
1) Mapper 테스트하기(SQL을 어노테이션을 이용하여 처리)
- TimeMapperTests.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RootConfig.class })
@Log4j
public class TimeMapperTests {
@Setter(onMethod_ = @Autowired)
private TimeMapper timeMapper;
@Test
public void testGetTime() {
log.info(timeMapper.getClass().getName()); //실제 동작하는 클래스의 이름 확인
log.info(timeMapper.getTime());
}
}
- 결과
2) XML + Mapper 테스트하기(SQL을 XML 파일에서 처리)
- ( Mabatis XML작성방법 )
- xml 파일 생성(resources 하위 폴더 > xml파일생성)
- TimeMapper.xml
SQL을 처리하기 위해 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="org.zerock.mapper.TimeMapper">
<!-- id: 메서드이름 resultType: 메서드의 리런타입 -->
<select id="getTime2" resultType="string">
SELECT sysdate FROM dual
</select>
</mapper>
- TimeMappers.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RootConfig.class })
@Log4j
public class TimeMapperTests {
@Setter(onMethod_ = @Autowired)
private TimeMapper timeMapper;
@Test
public void testGetTime2() {
log.info("getTime2()");
log.info(timeMapper.getTime2());
}
}
- 결과
JDBC작업을 로깅, 모니터링 하는 ` logjdbc-log4j2 `라이브러리
Mapprer 테스트하기(spring인터페이스를 이용해 객체 생성)
MyBatis는 내부적으로 JDBC PreparedSatement를 이용해 SQL을 처리한다. SQL에 전달 되는 파라미터는 JDBC와 같이 '?'으로 치환되어 내용을 정확히 알기 어렵다.
log4jdbc-log4j2 라이브러리는 JDBC(Java Database Connectivity) 작업을 로깅하고 모니터링 하기 위한 라이브러리
애플리케이션에서 실행되는 SQL 쿼리와 그에 대한 매개변수를 로깅하고 기록한다. SQL쿼리에 대한 상세 정보를 확인 할 수 있다.
Pom.xml
- maven프로젝트 설정파일. log4jdbc 라이브러리 추가
JDBC 연결정보인 DataSource 빈설정(RootConfig.java, root-context.xml)
- 스프링 설정 파일. DB연동을 위한 데이터
- class드라이버 Log4JDBC로 변경, url 변경
log4jdbc.properties
- 어떤 클래스를 사용할지 명시적으로 표시
- class드라이버, url 변경 (RootConfig.java 또는 xml)
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
<property name="jdbcUrl" value="jdbc:log4jdbc:mysql://localhost:3306/BOOK?useSSL=false"></property>
- pom.xml
<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4.1 -->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>1.16</version>
</dependency>
- log4jdbc 설정파일(스파이로깅 설정)
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
- 결과
JDBC 연결 객체가 생성되고 반환되는 로그를 볼 수 있다.
* 클래스 및 패키지의 로그 수준은 로그설정파일을 수정한다.
- log4j.xml
<logger name="jdbc.audit">
<level value="warn"></level>
</logger>
// jdbc.audit패키지의 로그를 warn 이상 레벨 부터 출력
이렇게 추가하면, audit패키지의 로그는 ` warn `이상만 콘솔에 확인된다.