ABOUT ME

Today
Yesterday
Total
  • mybatis & hikari 설정
    Web-Spring 2023. 12. 5. 03:06

    본 포스팅은 Spring boot 3.2.0, Java 17, MySQL을 기준으로 설명한다.

     

     

    프로젝트 구조

    여기서 사용되는 Product 와 같은 package 내용은 무시하여도 된다.

     

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>3.2.0</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.khc</groupId>
    	<artifactId>demo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>demo</name>
    	<description>Demo project for Spring Boot</description>
    	<properties>
    		<java.version>17</java.version>
    	</properties>
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-devtools</artifactId>
    			<scope>runtime</scope>
    			<optional>true</optional>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
            
    		<dependency>
    			<groupId>com.mysql</groupId>
    			<artifactId>mysql-connector-j</artifactId>
    			<version>8.0.33</version>
    		</dependency>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>

     

     

    mybatis 와 hikari 를 설정하기 앞서 두 녀석에 대한 간단한 설명

     

    Mybatis

    https://mybatis.org/mybatis-3/ko/index.html

     

    MyBatis – 마이바티스 3 | 소개

    마이바티스는 무엇인가? 마이바티스는 개발자가 지정한 SQL, 저장프로시저 그리고 몇가지 고급 매핑을 지원하는 퍼시스턴스 프레임워크이다. 마이바티스는 JDBC로 처리하는 상당부분의 코드와

    mybatis.org

    위의 글 대로 마이바티스는 자바 퍼시스턴스 프레임워크 이다.

    JDBC에서 PrepareStatement 등을 사용하여 처리하는 상당 부분의 코드와 결과 매핑을 대신 해주며

    이를 위해 XML과 애너테이션을 적극 활용한다.

    따라서 복잡한 JDBC 코드가 깔끔해지고 SQL 변경이 있을때 마다 자바 코드를 수정하거나 컴파일 하지 않아도 된다.

     

    Mybatis 를 통한 DB Access 

    처음에 Spring-boot의 Controller의한 요청 처리를 위해 알맞은 Service 가 호출되고

    Service 단에서 ~~Mapper 인터페이스를 호출하게 된다.

    이 때 ~~Mapper인터페이스에서 실제 쿼리문이 담긴 XML로 작성한 Mapper 파일에 매핑된 쿼리문이 호출되고

    이 쿼리문을 바탕으로 실제 DB 처리를 수행 할 JDBC와

    Mybatis Configure 파일의 DB 연결 정보가 담긴 DataSource를 통해 I/O를 처리한다.

    이를 도식화 하면 다음과 같다.

    MyBatis 3의 주요 컴포넌트

    MyBatis 3 의 주요 컴포넌트에는 

    MyBatis 설정파일, SqlSessionFactoryBuilder, SqlSessionFactory, SqlSession, ~~.xml 이 있다.

    MyBatis 설정 파일에는 주로 DB 접속정보(DataSource로 관리하는 DB접속정보), DTO들 관리하는 type-aliase, Mapper파일 위치 등이 담겨있다.

    SqlSessionFactoryBuilder는 이러한 설정파일을 바탕으로 처음 실행시 한번 호출되어 SqlSessionFactory 객체를 만든다.

    SqlSessionFactory는 Mapper(DAO)단에서 요청이 들어올 때 마다 SqlSession객체를 만드는 역할을 수행하며

    이렇게 만들어진 SqlSession은 각종 정보들을 바탕으로 SQL문 실행 및 Transaction관리를 수행한다.

    이 때  SqlSession은 Thread-safe 하지 않으므로 thread 마다 필요에 따라 생성된다. (SqlSession은 인터페이스이다.)

    ~~.xml 은 각종 SQL문과 해당 SQL을 실행할 Mapper 인터페이스와의 매핑 정보등이 담겨 있다.

     

    MyBatis-Spring 의 주요 컴포넌트

    MyBatis-Spring은 내부적으로 SqlSessionFactoryBean이 있으므로 그걸 사용하여 SqlSessionFactory를 만든다.

    이러한 SqlSessionFactoryBean을 사용하여 Bean을 등록할 때

    내부에 Mybaits-config 파일 정보, TypeAliase(DTO) 정보, Mapper.xml 정보를 함께 사용한다.

    또한 SqlSession은 인터페이스면서 Thread-safe하지 않지만,

    이를 구현한 SqlSessionTemplate을 사용함으로서 똑같은 일을 수행하면서 Thread-safe하게 된다.

     

     

    본 포스팅에서는 Spring-boot에서 설정할 것이므로 이 이상 구현한 코드는 Spring boot 기준으로 설명한다.

     

    DataBase Connection Pool & HikariCP

    https://hudi.blog/dbcp-and-hikaricp/

     

    데이터베이스 커넥션 풀 (Connection Pool)과 HikariCP

    데이터베이스 커넥션 데이터베이스 커넥션 풀에 대해 알아보기 이전에 데이터베이스 커넥션이 무엇인지부터 알아봐야한다. 우리가 개발하는 웹 애플리케이션과 데이터베이스는 서로 다른 시

    hudi.blog

    https://github.com/brettwooldridge/HikariCP

     

    GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.

    光 HikariCP・A solid, high-performance, JDBC connection pool at last. - GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.

    github.com

     

    위 두 글을 정리하자면 다음 과 같다.

    일반적으로 JDBC를 사용하여 DB에 연결을 하는 생명주기는 아래와 같다.

     

    1. Driver load 후 DriverManager를 통해 Connection 객체 얻기

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    public class DBUtil {
    	
    	private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; // 드라이버 위치
    	private static final String URL = "jdbc:mysql://localhost:3306/[DB]?serverTimezone=UTC"; // MySQL 기준 주소
    	private static final String DB_ID = "DB 접속 ID";
    	private static final String DB_PWD = "DB 접속 PW";
    	
    	private static DBUtil instance = new DBUtil();
    
    	private DBUtil() {
    		try {
    			Class.forName(DRIVER); // 동적 클래스 로딩
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static DBUtil getInstance() {
    		return instance;
    	}
    	
    
    	public Connection getConnection() throws SQLException {
    		return DriverManager.getConnection(URL, DB_ID, DB_PWD); 
            // 연결 정보를 통해 DB와 연결된 Connection 객체 하나 만들기
    	}
    	
    }

     

    2. JDBC를 이용하여 SQL문 수행 혹은 MyBatis를 이용한 SQL문 수행

     

    3. DB와의 연결 닫기

     

    보통 위의 단계를 거치는데, 여기서 DB를 연결하고 해제하는 과정의 비용이 매우 크다.

    즉, 사용자의 요청이 들어왔을 때 DB를 연결하고 해제하는 것은 상당한 굉장히 비효율적이게 된다.

    이러한 비효율을 해결하기 위해 미리 DB연결을 유지할 Connection객체를 여러개 만들어 두는 것을 

    DataBase Connection Pool  이라고 한다. 

     

    이러한 커넥션 풀 안에는 사전에 연결정보를 유지하고 있는 Connection 객체들이 모여있게 되고

    이를 관리하는 컨넥션 풀 컨테이너로 부터 커넥션 객체 하나를 받고 사용후 반납하면 

    DB연결/ 해제에 필요한 비용을 줄일 수 있게 된다.

     

    여기서 SpringBoot 의 기본으로 내장되어 있는 JDBC 데이터 베이스 커넥션 풀링 프레임워크가 

    바로 HikariCP 다.

     

    Hikari의 Connection Pool

    hikariCP에서는 컨넥션 풀에서 다음과 같은 커넥션 풀 사이즈를 제안한다.

    https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing#the-formula

     

    About Pool Sizing

    光 HikariCP・A solid, high-performance, JDBC connection pool at last. - brettwooldridge/HikariCP

    github.com

    위의 말을 읽어보면

    Connections = ((CPU_CORE_COUNT * 2) + effective_spindle_count) 라고 되어있다.

    이말은 즉 CPU 코어 수 * 2 에다가 DB서버가 관리할 수 있는 동시 I/O 요청 수를 더한 값만큼 컨넥션 객체를 가진다는 것을 의미한다.

     

    추가적으로 Hikari DeadLock 상태에 대한 정보는 아래의 기술블로그를 참고하자

    https://techblog.woowahan.com/2664/

     

    HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그

    {{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할

    techblog.woowahan.com

     

    본격적으로 Mybatis & HikariCP를 설정 해보도록 하자.

    우선 POM.xml에 다음과 같은 dependency를 추가하자.

    	<!-- mybatis-spring-boot-starter -->
        <dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>3.0.3</version>
    	</dependency>
        
        <!-- mysql 사용을 위한 connector -->
        <dependency>
    		<groupId>com.mysql</groupId>
    		<artifactId>mysql-connector-j</artifactId>
    		<version>8.0.33</version>
    	</dependency>

     

    그리고 application.properties 파일에 각종 hikariCP설정 값 및 database setting값을 넣어준다.

    # spring.datasource. 에 기본 내장 되어있다.
    spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.hikari.jdbc-url=jdbc:mysql://localhost:3306/[db명]?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8
    spring.datasource.hikari.username=db 접속 ID
    spring.datasource.hikari.password=db 접속 PW
    spring.datasource.hikari.pool-name=Hikari Connection Pool
    
    # hikariCP property setting
    spring.datasource.hikari.maximum-pool-size=50
    spring.datasource.hikari.minimum-idle=50
    spring.datasource.hikari.connection-timeout=5000
    spring.datasource.hikari.connection-init-sql=SELECT 1
    spring.datasource.hikari.idle-timeout=600000
    spring.datasource.hikari.max-lifetime=1800000
    spring.datasource.hikari.auto-commit=true

     

    위의 HikariCP property setting의 각 주제들의 의미는 다음과 같다.

    maximum-pool-size :

    Connection Pool이 제공할 수 있는 최대 커넥션 수를 의미,

    이는 사용중인 Connection + Idle 커넥션을 의미한다.

     

    minimum-idle : 

    아무런 일을 하지 않아도 적어도 이 값 만큼의 커넥션을 유지하는 설정

     

    connection-timeout : (최소 250ms, default : 30000ms)

    pool 에서 커넥션을 얻기까지 기다리는 최대시간, 이 시간을 넘어서면 SQLException을 던짐

     

    connection-init-sql :  

    새로운 커넥션이 생성되고, Pool에 추가되기 전에 실행될 SQL query를 설정

     

    connection-idle-timeout : (최소 10000ms, default : 600000ms )

    pool에 일을 안하는 커넥션을 유지하는 시간

     

    이 옵션은 maximum-pool-size 보다 minimum-idle이 작게 설정 되어 있을 때만 설정하면 된다.

    즉, 전체 maximum-pool-size에서 일을 안하는 커넥션들이 connection-idle-timeout을 지나게 되면 

    최소 minimum-idle 만큼의 커넥션을 유지하게 된다.

     

    connection-max-lifetime : ( default : 1800000ms )

    커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간

    사용중인 커넥션은 max-lifetime에 상관없이 제거되지 않음

    풀 전체에 적용되는것이 아니라 각각의 커넥션 별로 적용이 되는데, 이는 한번에 여러 커넥션들이 죽지 않기 위해서

     

    만약 0으로 설정하면 infinite lifetime이 적용됨(단, idle-timeout이 설정되어 있지 않아야 함)

     

    auto-commit : autocommit 설정(default : true)

     

    그리고 Java파일을 통한 SpringBoot의 @Configuration 어노테이션을 통해 DataBase 설정 클래스를 만든다.

    package com.khc.shop.config;
    
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    
    import javax.sql.DataSource;
    
    @Configuration
    @PropertySource("classpath:/application.properties")
    @MapperScan(basePackages = {"메퍼 인터페이스가 모인 곳"})
    public class DataBaseConfiguration{
    
        final ApplicationContext applicationContext; 
        // mapper.xml 위치를 Resource객체 로 들고오기 위해
    
        public DataBaseConfiguration(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }
    	
        
        @Bean
        // ApplicationContext이 Bean을 관리하는 역할을 수행하고
        // SpringBootApplication이 실행될 때 @Configuration 어노테이션이 붙은 java파일을 설정정보로 등록한다.
        // 이 때 @Bean으로 등록된 메서드들을 기반으로 빈 목록을 생성한다.
        @ConfigurationProperties(prefix = "spring.datasource.hikari")
        public HikariConfig hikariConfig() {
        	// application.properties 파일에서 spring.datasource.hikari로 시작하는 설정 값들을 가져와 Hikari 설정 객체를 반환한다.
            return new HikariConfig();
        }
    
        @Bean
        public DataSource dataSource() {
        	// HikariDataSource를 사용하기 위해 Hikari 설정 객체를 생성자로 넣은 HikariDataSource 객체 반환
            return new HikariDataSource(hikariConfig());
        }
    
        @Bean
        public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        	// SqlSessionFactory는 Mybatis-spring 때 처럼 SqlSessionFactoryBean을 통해 생성되는데
            // 각종 typeAliase(DTO), mapperLocation(xml위치), DB접속정보(datasource)를 property를 통해 추가한다.
            // 즉 setter를 통해 추가된다.
            
            SqlSessionFactoryBean session = new SqlSessionFactoryBean();
            session.setDataSource(dataSource);
            session.setMapperLocations(applicationContext.getResources("classpath:mapper/**/*.xml"));
            session.setTypeAliasesPackage("com.khc.shop.*.model");
            return session.getObject();
        }
    
        @Bean
        public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        	// SqlSessionTemplate은 SqlSession인터페이스 타입의 객체이며
            // 결국 SqlSessionFactory의 각종 설정 정보를 통해 만들어지므로 생성자로 넣게 된다.
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

     

    본격적인 사용은 우선 @MapperScan 에 등록해둔 경로에 해당하는 mapper 폴더에 인터페이스를 만든다.

     

    여기 현재 프로젝트의 경우 ProductMapper가 존재한다.

     

    그리고 이 파일이 Mapper 인터페이스라는것을 인지하게 하는 @Mapper 어노테이션을 추가한다.

    package com.khc.shop.product.model.mapper;
    
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface ProductMapper {
    
    }

     

    이렇게 정의된 Mapper와 실제 SQL문이 담겨있는 xml파일과 연결해야 한다.

     

    xml 파일들이 정의된 위치는 아까 classpath:mapper/**/*.xml 로 설정하였으므로

     

    resources/mapper 에 Product.xml을 만든다.

     

    이 때 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="">
        
    </mapper>

     

    intellj 의 경우에는 아래와 같이 추가한다면

     

    아래의 우클릭 new 를 통해 Mybatis xml 템플릿을 원할때마다 만들 수 있다.

     

    해당 xml의 중요한 부분은 우선 namespace이다.

    이곳에는 실제 어떤 Mapper 인터페이스와 매핑되는 xml 파일인지 풀 패키지 명으로 적어야 한다.

     

    현재 상황에서는 아래와 같아진다.

    <mapper namespace="com.khc.shop.product.model.mapper.ProductMapper">
    
    <mapper>

     

    이 내부에는 MyBatis 3 의 https://mybatis.org/mybatis-3/ko/sqlmap-xml.html 공식 문서를 통해 

    원하는 Sql문을 작성 해 나가면 된다.

    'Web-Spring' 카테고리의 다른 글

    JUnit  (2) 2023.12.12
Designed by Tistory.