SpringBoot MongoDB + Mysql 설정

개발하는 구황작물·2024년 4월 5일
0

개인 프로젝트에서 MongoDB와 Mysql을 동시에 사용하게 되었다.

처음에는 회원정보만 저장되어있는 Mysql을 밀어버리고 MongoDB로 대체하려 했으나 아무래도 MongoDB 트랜잭션이 Mysql보단 지원이 미약하다는 생각이 들어 회원 정보는 Mysql, 나머지 서비스는 MongoDB에 저장하기로 결정하였다.

Mongodb + Mysql 설정

1. gradle dependency

	runtimeOnly 'com.mysql:mysql-connector-j'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

2. application.yml

spring:
  data:
    mongodb: #mongoDB
      uri: mongodb://root:password@localhost:27018,localhost:27019,localhost:27020/quiz?authSource=admin&replicaSet=rs0
      host: localhost
      port: 27018
      database: quiz
      username: root
      password: password
      authentication-database: admin
      replica-set-name: rs0
    redis:
      host: localhost
      port: 6379

  datasource: # mysql
    url: jdbc:mysql://localhost:3306/dbName
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

spring.data.mongodb.uri의 경우 mongodb transaction 적용으로 인해 위와 같이 3개의 {host}:{port}를 적게 되었다.
(자세한 내용은 여기에)

3. mysql configuration

다중 DB를 사용하는 경우 슬프게도 스프링이 자동으로 AutoConfiguration을 해주지 않아 직접 스프링에 DB를 설정해주어야한다.

@Configuration
@EnableJpaRepositories(
        basePackages = "com.quiz.domain.users.repository", //mysql을 사용하는 폴더 이름
        entityManagerFactoryRef = "mysqlEntityManagerFactory", //entityManager 이름
        transactionManagerRef = "mysqlTx") // transactionManager 이름
@EnableTransactionManagement
public class JpaConfig {

    @Value("${spring.jpa.hibernate.ddl-auto}")
    private String ddl_auto;
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.url}")
    private String url;



    @Primary
    @Bean  // (1)
    public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(mysqlDatasource()); //Datasource 설정
        em.setPackagesToScan("com.quiz.domain.*.entity"); //mysql을 사용하는 entity 패키지 설정

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        em.setJpaVendorAdapter(vendorAdapter); // JPA 구현체인 hibernate를 사용하기 위해 등록해준다.

        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", ddl_auto);
        properties.put("hibernate.show_sql","true");
        properties.put("hibernate.format_sql","true");
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Primary
    @Bean  // (2)
    public DataSource mysqlDatasource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        return dataSource;
    }

    @Primary
    @Bean(name = "mysqlTx")  // (3)
    public JpaTransactionManager mysqlTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(mysqlEntityManagerFactory().getObject());
        return transactionManager;
    }
}

(1) : LocalContainerEntityManagerFactoryBean는 스프링이 직접 제공하는 컨테이너 관리 EntityManager를 위한 EntityManagerFactory를 만들어준다.

(2) : Datasource 등록

(3) : 트랜잭션 등록

이때 모든 메서드에 @Primary 가 붙어있는데 이는 Mongodb도 Bean 으로 등록되어야 하기 때문에 순서를 정해주기 위해 추가하였다.(추가 안하면 충돌난다)

4. MongoDB Configuration

@Slf4j
@Configuration
@EnableMongoRepositories(
        basePackages = {"com.quiz.domain.*.repository.mongo"})
@EnableTransactionManagement
public class MongoDBConfig extends AbstractMongoClientConfiguration {
    @Value("${spring.data.mongodb.uri}")
    private String connectionString;

    @Value("${spring.data.mongodb.database}")
    private String databaseName;


    @Bean(name = "mongoTx")
    public MongoTransactionManager transactionManager(@Qualifier("mongoDbFactory") MongoDatabaseFactory mongoDatabaseFactory) {
        return new MongoTransactionManager(mongoDatabaseFactory);
    }

    @Bean(name = "mongoDbFactory")
    @Override
    public MongoDatabaseFactory mongoDbFactory() {
        return super.mongoDbFactory();
    }

    @Override
    public MongoClient mongoClient() {
        ConnectionString connectionString = new ConnectionString(this.connectionString);

        MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
                .applyToConnectionPoolSettings(builder -> builder
                        .maxConnectionIdleTime(10, TimeUnit.SECONDS))
                .applyConnectionString(connectionString)
                .build();

        return MongoClients.create(mongoClientSettings);
    }

    @Override
    protected String getDatabaseName() {
        return databaseName;
    }


    @Bean
    public MongoTemplate mongoTemplate() {
        return new MongoTemplate(mongoClient(), databaseName);
    }
}

참고로 mongodb 트랜잭션 적용하려면 mongodb replicaSet를 설정해주어야 한다.

(자세한 내용은 여기로)

이렇게 하나의 application에 두개의 DB를 연결할 수 있었다.


이렇게 문제가 끝날 줄 알았으나... mongoDB에서 @Transactional 사용을 했음에도 Transaction이 적용되지 않았다.

원인은 트랜잭션 매니저 빈 이름 변경으로 인해 발생한 문제였다.

@Configuration
@EnableJpaRepositories(
        basePackages = "com.quiz.domain.users.repository", //mysql을 사용하는 폴더 이름
        entityManagerFactoryRef = "mysqlEntityManagerFactory", //entityManager 이름
        transactionManagerRef = "mysqlTx") // transactionManager 이름
@EnableTransactionManagement
public class JpaConfig {

	@Primary
    @Bean(name = "mysqlTx")
    public PlatformTransactionManager mysqlTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(mysqlEntityManagerFactory().getObject());
        return transactionManager;
    }
}

mysql configuration에서 @EnableJpaRepositories의 transactionManagerRef 와 TransactionManager 빈 이름을 "mysqlTx" 로 변경시켰고

mongodb의 configuration에서는 MongoTransactionManager 이름을 "mongoTx"로 변경시키는 바람에 @Transactional 사용시 TransactionManager를 찾지 못해 발생한 일이었다.

위의 문제는 @Transactional에 value이름을 명시해주어 해결할 수 있었다.

@Slf4j
@Transactional(value = "mongoTx")
@RequiredArgsConstructor
@Service
public class ParticipantInfoFacade {

...
}
profile
어쩌다보니 개발하게 된 구황작물

0개의 댓글