본문 바로가기
IT이야기/LANGUAGE

[JAVA/SpringBoot] DataConfig 설정 - 두 개 이상의 DB서버를 사용할 경우 @DataSource사용, JPA DatabaseConfig

by JI_NOH 2024. 1. 16.

사이드 프로젝트에서는 당연하지만

이전 회사에서도 한 프로그램에 데이터베이스를 여러개 쓰는 일은 없었다.

찾아보니 그런 일이 아예 없는 건 아니고 규모가 크면 클수록 여러개를 쓰는 일이 있기는 하다고 하더라.

혹은 Mybatis -> JPA로 전환하는 과정이나 기타 마이그레이션 작업에도 쓸 수 있겠다.

 

 

다른 프로젝트 일 좀 도와주다가 데이터베이스를 두개 설정해놓은 경우를 봤다.

 

DatabaseConfig 라는 파일을 하나 생성해주고 시작하자.

참고로 내가 사용하는 방식은 JPA + 기타 데이터베이스 형태라고 보면 되겠다.

목차

     

    JPA를 적용한 DatabaseConfig 설정

    @Configuration
    @EnableTransactionManagement(proxyTargetClass = true)
    @EnableJpaRepositories(
            entityManagerFactoryRef = "${entitymanager_별칭}",
            transactionManagerRef = "${transaction_별칭}",
            basePackages = {"${레포지토리_패키지_경로}"}
    )
    @RequiredArgsConstructor
    public class DatabaseConfig {
        private final Environment env;
    
        @Bean
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource.first")
        public DataSource firstDataSource() {
            return DataSourceBuilder.create().type(HikariDataSource.class).build();
        }
    
        @Bean
    	@Primary
    	public SqlSessionTemplate firstSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception{
    		return new SqlSessionTemplate(sqlSessionFactory);
    	}
    
        @Bean(name = "${entitymanager_별칭}")
        public LocalContainerEntityManagerFactoryBean contentsEntityManagerFactory(
                EntityManagerFactoryBuilder builder
                , @Qualifier("firstDataSource") DataSource dataSource
        ) {
    
            Map<String, String> propertiesHashMap = new HashMap<>();
            propertiesHashMap.put("hibernate.dialect", env.getProperty("spring.jpa.hibernate.dialect"));
            propertiesHashMap.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.ddl-auto"));
            propertiesHashMap.put("hibernate.show_sql", env.getProperty("spring.jpa.show-sql"));
            propertiesHashMap.put("hibernate.format_sql", env.getProperty("spring.jpa.properties.hibernate.form_sql"));
    
            return builder.dataSource(dataSource).packages("${entity 패키지 경로}")
                    .properties(propertiesHashMap).build();
        }
    
        @Bean(name = "${transaction_별칭}")
        public JpaTransactionManager transactionManager(
                @Qualifier("${entitymanager_별칭}") LocalContainerEntityManagerFactoryBean mfBean
        ) {
            JpaTransactionManager transactionManager = new JpaTransactionManager();
            transactionManager.setEntityManagerFactory(mfBean.getObject());
            return transactionManager;
        }
    
     

    우선 JPA만 사용한다고 가정하고 DatabaseConfig 클래스에 선언하는 어노테이션들이다. 그에 대한 별칭을 선언하고 시작하자.

     

    //트랜잭션 처리를 허용하는 어노테이션
    @EnableTransactionManagement(proxyTargetClass = true)
    //JPARepository들을 사용하기 위한 어노테이션으로 선언 내용은 본인이 설정하는 별칭으로 작성하면 된다.
    @EnableJpaRepositories(
            entityManagerFactoryRef = "firstJpaEntityManagerFactory",
            transactionManagerRef = "firstTransactionManager",
            basePackages = {"com.xxx.xxx.repository"}
    )
     

     

    1. DataSource

    @Bean
    //기본으로 사용하겠다.
    @Primary
    //properties에서 사용하는 config이름으로 설정하겠다.
    @ConfigurationProperties(prefix = "spring.datasource.first")
    //함수명은 편한대로 지으면 된다. 여러개니까 구분이 갈 수 있도록만 지으면 된다.
    public DataSource firstDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
     

    위처럼 ConfigurationProperties를 설정하고 나면 properties에서 아래처럼 설정을 선언할 수 있다.

    spring.datasource.first.driverClassName=org.postgresql.Driver
    spring.datasource.first.jdbc-url=jdbc:postgresql://localhost:5432/xxx
    spring.datasource.first.username=xxxxx
    spring.datasource.first.password=xxxxx
     

     

    2. LocalContainerEntityManagerFactoryBean

    우리가 앞서 properties를 설정하기 위해 DataSource 이름을 설정했고 properties에 설정을 끝마쳤다고 가정해보자. 그리고 그 설정들을 EntityManagerFactory에 설정해 줄 필요가 있겠다.

    @Bean(name = "firstJpaEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean contentsEntityManagerFactory(
            EntityManagerFactoryBuilder builder
            , @Qualifier("firstDataSource") DataSource dataSource
    ) {
        Map<String, String> propertiesHashMap = new HashMap<>();
        propertiesHashMap.put("hibernate.dialect", env.getProperty("spring.jpa.hibernate.dialect"));
        propertiesHashMap.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.ddl-auto"));
        propertiesHashMap.put("hibernate.show_sql", env.getProperty("spring.jpa.show-sql"));
        propertiesHashMap.put("hibernate.format_sql", env.getProperty("spring.jpa.properties.hibernate.form_sql"));
    
        return builder.dataSource(dataSource).packages("com.xxx..entity")
                .properties(propertiesHashMap).build();
    }
     

     

    3. TransactionManager

    //DatabaseConfig 클래스에서 선언했던 transactionManagerRef이름을 선언해주면 된다. 
    //서비스계층에서 이 빈을 사용하게 된다. 여러 데이터베이스를 사용하다 보니 어떤 transactionManager를 사용할 지 결정한다고 보면 되겠다.
    @Bean(name = "firstTransactionManager")
    public JpaTransactionManager firstTransactionManager(
            @Qualifier("firstJpaEntityManagerFactory") LocalContainerEntityManagerFactoryBean mfBean
    ) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(mfBean.getObject());
        return transactionManager;
    }
     

    서비스계층에서 사용하는 방법이다.

    @Service
    @Transactional(value ="firstTransactionManager")
    @RequiredArgsConstructor
    public class UserService {
     

     

     

     

     

    SQLMapper를 이용한 DatabaseConfig 두번째 설정

    @Configuration
    @MapperScan(basePackages = "${패키지경로}")
    @RequiredArgsConstructor
    public class DatabaseConfig {
    
        private final Environment env;
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.second")
        public DataSource secondDataSource() {
            return DataSourceBuilder.create().type(HikariDataSource.class).build();
        }
    
        @Bean
        public SqlSessionFactory secondSqlSessionFactory(@Qualifier("${DataSource별칭}") DataSource dataSource, ApplicationContext applicationContext) throws Exception{
            SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(dataSource);
    
            Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:${mapper경로}");
            sessionFactory.setMapperLocations(res);
    
            return sessionFactory.getObject();
        }
    
        @Bean
    	public SqlSessionTemplate secondSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception{
    		return new SqlSessionTemplate(sqlSessionFactory);
    	}
    
        @Bean
        public PlatformTransactionManager secondTransactionManager(@Qualifier("${DataSource별칭}") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
     

     

    1. DataSource

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.second")
    public DataSource secondDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
     

     

    두번째 데이터베이스에 대한 설정도 properties에 해주면 된다.

    spring.datasource.second.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
    spring.datasource.second.jdbc-url=jdbc:log4jdbc:mariadb://xxxxxx/xxx
    spring.datasource.second.username=xxx
    spring.datasource.second.password=xxx
     

     

    2. SqlSessionFactory

    @Bean
    public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource, ApplicationContext applicationContext) throws Exception{
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
    
        Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:static/mapper/second/**.xml");
        sessionFactory.setMapperLocations(res);
    
        return sessionFactory.getObject();
    }
    
     

    어느 경로에 있는 mapper와 매칭을 시켜서 로드 및 설정할 것인가. 정도로 보면 되겠다.

     

     

     

    3. SqlSessionTemplate

        @Bean
    	public SqlSessionTemplate secondSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception{
    		return new SqlSessionTemplate(sqlSessionFactory);
    	}
     

     

     

    4. TransactionManager

        @Bean
        public PlatformTransactionManager secondTransactionManager(@Qualifier("secondDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
     

     

    SqlMapper를 사용하는 경우는 별달리 첨언할 내용이 없다.

     

     

     

    참고 레퍼런스 : mybatis + JPA 멀티 데이터소스