[Spring boot] 다중 DB(Multiple DB) 연결 세팅


프로젝트를 시작하며 업무에서 2개의 DB를 하나의 서버 프로젝트에 커넥션을 잡아줘야 하는 일을 맡게됬습니다.

항상 토이프로젝트를 하든 팀 프로젝트를 하든 하나의 DB를 가지고 세팅을 했기 때문에 구글링도 여러번 해보고 헷갈렸는데요. 혹시나 까먹고 두번 다시 이걸로 고생하고싶지 않아서 정리해두려고합니다. 

 

우선 순서를 정리해보면

 

1. 메인 DB와 서브 DB 정하기

 

2. build-gradle에 connector 추가

-- log4jdbc는 추가 설정파일(.properties) 추가

 

3. 각 DB의 config 클래스 생성 후 DB 정보 입력

 

3-1. 메인 DB와 서브 DB에 따른 설정 추가

 

4. mapper에 각 DB SQL문 작성

 

5. DAO에 메인,서브DB Bean정상 호출 확인

 

6. 웹 실행을 통한 최후 테스트

 

 

이런식으로 갑니다.

 

우선 메인 DB와 서브 DB를 정하는 이유는 잠시 뒤 보실 설정부분과 호출부분에서 Primary로 설정하는 DB는 설정이 간단하고 호출도 1개의 DB만 사용할 때 그대로 사용하면 되기때문에 자주 사용하시는 DB를 메인 DB로 정하시고 가시는게 설정시 매우 편합니다.

 

이제 시작해보겠습니다.

 

 

1. 메인/서브 DB 정하기

우선 저는 프로젝트에서 MySQL과 Tibero를 사용하기로 했는데, 자주 사용되는 DB가 Tibero이기 때문에 메인 DB는 Tibero, 서브 DB는 MySQL로 사용하겠습니다.


2. 라이브러리에 connector 추가 및 log4jdbc 설정 파일 추가


  - 우선 build.gradle의 dependencies에 필요한 DB커넥터들을 추가 후 빌드합니다.

log4jdbc쓰시는 분들은 같이 ㄱㄱ~
외부 라이브러리에서 확인하기!

  - log4jdbc.log4j2.properties 파일 생성

- src/main/resources 아래에 log4jdbc.log4j2.properties라는 파일을 생성 후 아래와 같이 입력합니다.

   

- log4jdbc는 기존 DB 커넥터를 감싸 로그 기능을 추가해주는 개념이기때문에.. 베이스로 어떤 DB 커넥터를 사용할지          등을 설정해주는 파일이 필요합니다. 저는 두 개의  DB에 모두 로그를 남겨야 하기때문에, 커넥터를 두개 다 넣었습니        다.  만약 둘중 하나만 설정하고 싶으시다면, 설정하고 싶은 DB의 원래 커넥터를 입력하시면 되겠습니다.


3. Config 클래스 생성 후 설정


  - xml등을 사용하여 설정하는 방법도 있지만 스프링은 어노테이션을 사용하면 쉽게 Bean설정을 할 수 있기 때문에

    Config 클래스를 통해 설정해보겠습니다.

( 사실은 이렇게 해보고도 싶고 xml 가독성이 너무 떨어진다고 개인적으로 생각되어 사용했습니다.)

 

파일은 src/main/java/com.xxx.xx의 MVC구조 파일들이 모여있는 곳에 넣어야합니다. 기본적으로 Spring이 실행될 때 src/main/java안에 있는 파일들을 컴파일해 스프링 컨테이너에 Bean을 등록하기때문에, 별도 설정없이 해당 디렉터리를 벗어나면 Bean 자체를 인식하지 못하는 경우가 생깁니다.

 

우선 아까 정한 메인 DB 부터 보겠습니다

package com.example.samyuk.config;

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.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
public class TiberoDataSourceConfig {
    @Bean
    @Primary // setting Multyple DataBase Connection by sangU
    public DataSource tiberoDataSource() {

        return DataSourceBuilder.create()
                .url("jdbc:log4jdbc:tibero:thin:@IP:PORT:DBNAME") // URL을 명시적으로 지정
                .driverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy") // 드라이버 클래스명을 명시적으로 지정
                .username("DB유저 id")
                .password("DB비밀번호")
                .build();
    }

    @Bean
    @Primary
    public SqlSessionFactory firstSqlSessionFactory(DataSource firstDataSource, ApplicationContext applicationContext) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(firstDataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:mappers/*.xml"));//xml파일의 위치, src/main/resources아래에 위치
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * DAO 클래스에서 사용한다.
     **/
    @Bean
    @Primary
    public SqlSessionTemplate firstSqlSessionTemplate(SqlSessionFactory firstSqlSessionFactory) throws Exception {
        final SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(firstSqlSessionFactory);
        return sqlSessionTemplate;
    }
}

코드 자체는 DB 1개를 연동 할때와 비슷하지만 자세히 보면 @Primary라는 어노테이션이 있습니다. 저도 이번 일을 겪으면서  처음 마주한 어노테이션인데요. 스프링 컨테이너에 Bean들을 등록할 때, 동일한 Bean이 두 개 등록 되있으면 어떤 Bean을 가져야할지 모릅니다. 때문에 에러가 발생되는데요. Primary 어노테이션을 붙여놓으면 다른곳에서 Bean 선언시 아무설정이 없다면 기본적으로 Primary어노테이션이 붙은 Bean으로 자동 주입됩니다.

 

때문에 메인 DB와 서브 DB를 정해 놓으라고 저는 말씀드리고 싶었는데요, 만약 자주 사용하는 DB를 Primary로 놓지 않으면 선언시마다 설정을 해주어야합니다. 이러면 효율 자체도 좋지 않고 한 두줄 추가되는 것도 좋지 않을 수 있다고 생각합니다.

 

이제 서브 DB의 config클래스를 살펴보겠습니다.

package com.example.samyuk.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
public class MariaDataSourceConfig {
    @Bean(name = "mariaDataSource")
    public DataSource mariaDataSource(){
        return DataSourceBuilder.create()
                .url("jdbc:log4jdbc:mysql://127.0.0.1:3306/test_db")
                .driverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy")
                .username("root")
                .password("12345")
                .build();
    }

    @Bean(name = "mariaSqlSessionFactory")
    public SqlSessionFactory mariaSqlSessionFactory(@Qualifier("mariaDataSource") DataSource dataSource) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mappers/*.xml"));//xml파일의 위치, src/main/resources아래에 위치
        return sqlSessionFactoryBean.getObject();
    }



    /**
     * DAO 클래스에서 사용한다.
     **/
    @Bean(name = "mariaSqlSessionTemplate")
    public SqlSessionTemplate mariaSqlSessionTemplate(@Qualifier("mariaSqlSessionFactory") SqlSessionFactory mariaSqlSessionFactory) throws Exception {
        final SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(mariaSqlSessionFactory);
        return sqlSessionTemplate;
    }
}

크게 다를건 없지만  SessionFactory와 SqlSessionTemplate객체의 인자에 @Qualifier 어노테이션이 존재하는데요, @Qualifier("Bean명")을 하면 괄호 안의 문자에 맞는 이름을 가진 Bean을 가져옵니다. 해당 밑 두개도 서브 DB config클래스에 들어가기때문에, 어떤 DB의 커넥션 DataSource를 넣을지 정해주어야 SqlSessionTemplate선언시 원하는 DB에 붙습니다.

(저는 여기서 한참 해멨습니다...)

 

* 또한 Bean 어노테이션에 name 속성을 추가하면 후에 선언시 해당 속성명으로 Bean을 부를 수 있습니다! 지정하지 않으면 메소드명으로 세팅됩니다.( 따라서 본문의 코드에는 넣으나 안넣으나 마찬가지입니다.)


4. Mapper에 각 DB SQL문 작성


우선 Mapper까지 구분할 필요는 없으나, SQL문법이 조금 다른 언어의 경우 헷갈릴 수 있으니 실제로 사용하실땐 따로 사용하시는 것도 괜찮은거 같습니다. 하지만 해당 글에서는 테스트전용으로 만들기 때문에 같은 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="mappers.ExampleMapper">
    
    <select id="try36" resultType="com.example.samyuk.vo.CategorySetListVO">
        select 
            *
        from
            CONSENT_CATEGORY_SET_LIST
    </select> <-- 이거 tibero꺼
   
   <select id="testMultiple" resultType="com.example.samyuk.vo.MultipleConnTestVO">
        select
            *
        from
            test_db.user u
    </select> <-- 이거 MySQL

</mapper>

이건 Mybatis를 사용하시는 분들에게 매우 익숙한 부분이라 추가 설명은 하지 않겠습니다.


5. DAO호출 후 정상 작동 확인


DAO에 호출 후 정상적으로 import되는지 확인할 단계입니다. 우선 선언부 코드입니다.

 

package com.example.samyuk.DAO;

import com.example.samyuk.vo.*;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class TestDAO {

    @Autowired
    private SqlSession sqlSession; // mainDB @Primary로 인해 직접 지정해줄 필요없음

    @Autowired
    @Qualifier(value = "mariaSqlSessionTemplate") // 같은 bean이 2개 이상 존재할 시 어떤 bean을 사용할 건지 지정해야함.
    private SqlSession sqlSession2;         
    // subDB
    public List<MultipleConnTestVO> getTestConn(){
        return sqlSession2.selectList("mappers.ExampleMapper.testMultiple");
    }
    
    public List<CategorySetListVO> get36Test(){
        return sqlSession.selectList("mappers.ExampleMapper.try36");
    }
    
    }

 

보시면 메인DB(Tibero)를 바라보는 SqlSession객체는 자동주입 어노테이션만 존재합니다. 이럴경우 @Primary 어노테이션이 붙은 Bean이 주입됩니다.

 

서브DB(MySQL)을 바라보는 sqlSession2 객체는 @Qualifier 어노테이션을 통해 mariaSqlSessionTemplate이라는 이름의 Bean을 주입합니다. 그렇게 두 개를 작성하고 각 DB에 맞는 SQL Mapper문을 통해 실행하면 됩니다.

 

다들 아시겠지만 IntelliJ나 Eclipse같은 경우 @Qualifier의 value값을 ctrl + 좌클릭시 제대로 연동되면 저희가 설정한 Config클래스로 이동됩니다.

 

그리고 나머지 구조를 통해 DB의 데이터가 잘 출력되는지 확인해보면 됩니다. view를 따로 만들기는 좀 번거로워서 ResponseBody로 전송값만 확인했습니다.

 

다 정상 출력됬네여

 

이상 다중 DB (Multiple DataBase) 연동방법에 대해 알아봤습니다. 혹시 자세한 개념에서 틀린부분이나 지적사항 있으시면 댓글로 말씀해주시면 감사하겠습니다.

 

 

 

 

+ Recent posts