[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) 연동방법에 대해 알아봤습니다. 혹시 자세한 개념에서 틀린부분이나 지적사항 있으시면 댓글로 말씀해주시면 감사하겠습니다.

 

 

 

 

CRUD와 기본 명령어

오늘은 CRUD와 기본적으로 많이 사용되는 SELECT,DELETE,UPDATE,CREATE,INSERT명령어에 대해 알아보려고 합니다.

 

DB툴은 많은 것이 있지만, 저는 ORACLE을 사용하는 SQLDeveloper를 사용하여 예제를 보여드리겠습니다. 문법은 대체로 비슷하지만 다른 SQL과 약간의 차이가 있을 수 있습니다.

 

CRUD란?

CRUD란

C - CREATE : DB를 생성

R - READ : DB를 읽기

U - UPDATE : DB를 최신화(갱신)

D - DELETE : 필요없는 DATA를 삭제

라는 컴퓨터의 데이터 처리 기본기능에 대한 줄임말 입니다.

 

전화번호부를 예로 들면

C - 홍길동의 연락처 생성

R - 홍길동의 전화번호 찾기

U - 홍길동의 바뀐 전화번호 등록

D - 홍길동의 연락처 삭제

이 네 가지로 이해하시면 되겠습니다.

 

CRUD는 기본명령어에서 C는 INSERT, R은 SELECT, U는 UPDATE, D는 DELETE의 명령어로 각 기능을 수행하는데요, 거기에 테이블과 후에 다룰 인덱스, 뷰등을 만들때도 사용하는 CREATE를 통해 예제를 만들어 보겠습니다.

 

 

CREATE 명령어의 문법은 

CREATE TABLE 테이블명 (

                                               필드명1 속성,

                                               필드명2 속성, ..... );

위의 방식으로 만듭니다. 

create명령어로 연락처 phone_book의 테이블을 만들었으며 필드명 name에 varchar2속성의 10자리 필드라고 생각하시면 이해하기 편하실 거라고 생각됩니다.

 

우선 해당 테이블로 SELECT문을 사용하여 데이터를 READ해보겠습니다.

SELECT문은 다음과 같이 사용합니다

SELECT   읽어낼 컬럼명 FROM 테이블명 ;

하지만 테이블의 모든 데이터를 읽을때는 컬럼명의 자리에 *를 넣습니다. 예제로 다시한번 보겠습니다.

phone_book의 모든 데이터를 읽어와라

실행 결과는 다음과 같습니다.

                                         

읽어온 테이블의 데이터

당연히 테이블만 생성하고 아무것도 하지 않았기 때문에 데이터가 비어있는 상태입니다.

그럼 이 테이블에 데이터를 추가(INSERT)해보겠습니다.

I

 

NSERT문의 문법은 아래와 같습니다

INSERT INTO 테이블명(속성1,속성2,속성3....) VALUES(값1,값2,값3...);

이때 속성명은 생략이 가능하나, 속성명을 생략하면 위 사진의 순서대로 값을 입력해주어야 합니다.

두 가지 예제로 값을 넣어보겠습니다.

컬럼 지정 후 지정한 순서대로 값 넣어주기

                               

컬럼을 지정하기 않고 테이블의 모든 컬럼에 값 삽입하기
컬럼 두개의 값만 삽입하기
결과 사진

윗 사진과 같은 결과가 나옵니다. 보통 데이터를 모든 컬럼 값에 입력해야 할때는 2번째와 같은 방식을 많이 사용하고,

1,3번째 방법은 특정 컬럼에만 값을 넣을때 사용합니다.

 

이젠 UPDATE문을 사용해보겠습니다. 기본 구조는

UPDATE 테이블명 set 변경할필드 = '변경할 값' where 조건;

이며 짱구의 전화번호를 010-1111-2222로 변경하겠습니다.

짱구의 전화번호 변경
테이블에서 변경된 짱구의 전화번호

해당 구문으로 데이터를 수정할 수 있으며, WHERE은 일종의 조건문으로 나중에 CASE와 WHEN문을 다룰때 같이 다뤄보겠습니다.

 

마지막 DELETE입니다. 위처럼 WHERE을 이용하여 특정 데이터를 삭제할 수 있습니다. 기본 구조는 

DELETE 테이블명  WHERE 조건;입니다.

성별이 'M'인 데이터를 삭제하겠습니다.

성별이 남자인 데이터 삭제
남자의 데이터가 삭제된 테이블

이로서 CRUD의 의미와 자주 사용되는 4가지 명령어를 알아보았는데요. 모자란 부분이 많겠지만 복기하며 작성한 글이라 좋게 봐주시면 좋을거 같습니다.

+ Recent posts