인프런 커뮤니티 질문&답변

hehihoho3님의 프로필 이미지
hehihoho3

작성한 질문수

[스프링 배치 입문] 예제로 배우는 핵심 Spring Batch

다중DB 접속 설정 에러

해결된 질문

작성

·

649

·

수정됨

0

안녕하세요!
강의 잘 봤습니다!
강의 보고 다중DB를 설정하는과정에서 에러가나서 질문드려요
여기서 하면 안되는거같은데 ... 도움부탁드릴려고 글 남깁니다!

targetDb에서 select는 정상적으로 들어오는데 localDB save 할때 할당 문제인거 같은데 ...



에로코드

org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@4ff98dfc] for key [HikariDataSource (HikariPool-1)] bound to thread [scheduling-1]
	at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:467) ~[spring-orm-5.3.6.jar:5.3.6]
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400) ~[spring-tx-5.3.6.jar:5.3.6]
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) ~[spring-tx-5.3.6.jar:5.3.6]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:595) ~[spring-tx-5.3.6.jar:5.3.6]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:382) ~[spring-tx-5.3.6.jar:5.3.6]

------

	at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) [spring-context-5.3.6.jar:5.3.6]
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [spring-context-5.3.6.jar:5.3.6]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_181]
	at java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:308) [na:1.8.0_181]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java) [na:1.8.0_181]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_181]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_181]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@4ff98dfc] for key [HikariDataSource (HikariPool-1)] bound to thread [scheduling-1]
	at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:193) ~[spring-tx-5.3.6.jar:5.3.6]
	at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:442) ~[spring-orm-5.3.6.jar:5.3.6]
	... 93 common frames omitted

2024-02-26 12:16:27.150  INFO 23616 --- [   scheduling-1] o.s.batch.core.step.AbstractStep         : Step: [step] executed in 737ms
2024-02-26 12:16:27.160  INFO 23616 --- [   scheduling-1] c.m.mj_agent.listener.JobLoggerListener  : taragetJob Job is Done. (Status: FAILED)
2024-02-26 12:16:27.160  INFO 23616 --- [   scheduling-1] c.m.mj_agent.listener.JobLoggerListener  : Job is Failed
2024-02-26 12:16:27.168  INFO 23616 --- [   scheduling-1] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=taragetJob]] completed with the following parameters: [{time=1708917386263}] and the following status: [FAILED] in 797ms

 

localDbConfig

package com.munjaon.mj_agent.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;

/**
 *
 */
@Configuration
@PropertySource({ "classpath:application.yml" })
@EnableJpaRepositories(
//        basePackages = {"com.repia.collectweb.common", "com.repia.collectweb.schedule"},
//		basePackages = {"com.munjaon.mj_agent.batch.local.repo"},
		basePackages = {"com.munjaon.mj_agent.batch.local"},
		entityManagerFactoryRef = "primaryEntityManager",
		transactionManagerRef = "primaryTransactionManager"
		)
public class LocalDbConfig {
	@Autowired
	private Environment env;

	@Bean
	@Primary
	@ConfigurationProperties(prefix = "spring.local-datasource.datasource.hikari")
	public DataSource primaryDataSource() {
		return DataSourceBuilder.create().build();
	}

	@Bean
	@Primary
	public LocalContainerEntityManagerFactoryBean primaryEntityManager() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();

		em.setDataSource(primaryDataSource());
		em.setPackagesToScan(new String[] { "com.munjaon.mj_agent.batch.local.repo.entity" });


		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		em.setJpaVendorAdapter(vendorAdapter);
		HashMap<String, Object> properties = new HashMap<>();
		properties.put("hibernate.show_sql", env.getProperty("spring.jpa.show_sql"));
		properties.put("hibernate.format_sql", env.getProperty("spring.jpa.format_sql"));
		properties.put("hibernate.use_sql_comments", env.getProperty("spring.jpa.use_sql_comments"));

		properties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.hbm2ddl.auto"));
		properties.put("hibernate.dialect", env.getProperty("spring.jpa.database-platform"));
//		properties.put("hibernate.dialect", env.getProperty("spring.jpa.database"));

		em.setJpaPropertyMap(properties);

		return em;
	}

	@Bean
	@Primary
	public PlatformTransactionManager primaryTransactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(primaryEntityManager().getObject());
		return transactionManager;
	}



}


TargetDbConfig

package com.munjaon.mj_agent.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Objects;

/**
 *
 */
@Configuration
@PropertySource({ "classpath:application.yml" })
@EnableTransactionManagement
@EnableJpaRepositories(
		basePackages = {"com.munjaon.mj_agent.batch.target.repo"}
		, entityManagerFactoryRef = "targetDbEntityManager"
		, transactionManagerRef = "targetDbTransactionManager"
		)
public class TargetDbConfig {
	@Autowired
	private Environment env;

	@Bean
//	@Primary
	public LocalContainerEntityManagerFactoryBean targetDbEntityManager() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();

		em.setDataSource(targetDbDataSource());
		em.setPackagesToScan(new String[] { "com.munjaon.mj_agent.batch.target.repo.entity" });

		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		em.setJpaVendorAdapter(vendorAdapter);
		HashMap<String, Object> properties = new HashMap<>();
		properties.put("hibernate.show_sql", env.getProperty("spring.jpa.show_sql"));
		properties.put("hibernate.format_sql", env.getProperty("spring.jpa.format_sql"));
		properties.put("hibernate.use_sql_comments", env.getProperty("spring.jpa.use_sql_comments"));

//		properties.put("hibernate.hbm2ddl.auto", false);
//		properties.put("hibernate.dialect", false);
		properties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.hibernate.hbm2ddl.auto"));
		properties.put("hibernate.dialect", env.getProperty("spring.jpa.database-platform"));
		em.setJpaPropertyMap(properties);

		return em;
	}


	@Bean
//	@Primary
	@ConfigurationProperties(prefix = "spring.target-datasource.datasource.hikari")
	public DataSource targetDbDataSource() {
		return DataSourceBuilder.create().build();
	}

//	@Primary
//	@Bean
	@Bean(name = "targetDbTransactionManager")
	public PlatformTransactionManager targetDbTransactionManager(EntityManagerFactoryBuilder builder) {
		return new JpaTransactionManager(Objects.requireNonNull(targetDbEntityManager().getObject()));
	}
}

 

감사합니다!

 

 

 

답변 1

0

안녕하세요, 인프런 AI 인턴입니다.
hehihoho3님, 다중 데이터베이스를 설정하시는 과정에서 발생한 에러에 대해 질문 주셨군요.

에러 메시지를 보면, Transaction 관리에서 문제가 발생했음을 알 수 있습니다. CannotCreateTransactionExceptionjava.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@4ff98dfc] for key [HikariDataSource (HikariPool-1)] bound to thread [scheduling-1]이라는 부분에서 힌트를 얻을 수 있습니다.

다중 데이터베이스 구성 시 @Primary 어노테이션을 사용하여 주 데이터 소스를 지정하는 것을 잘 하셨습니다. 다만, 트랜잭션 관리자에 대해서도 분리가 필요합니다. 여러 데이터베이스에 대한 각각의 EntityManagerFactoryTransactionManager 설정이 필요하죠. 여기서 중요한 점은 각각의 설정에서 고유한 이름을 가지고 있어야 한다는 것입니다.

또한, LocalDbConfigTargetDbConfig 둘 다 @EnableTransactionManagement@EnableJpaRepositories를 명시하였는데, JPA 리포지토리와 트랜잭션 매니저의 연결이 제대로 이루어지고 있는지 확인이 필요합니다.

제공해주신 정보를 바탕으로 Spring Boot 관련 문서에서 관련 설정을 할 수 있는 예제 코드를 찾았습니다. 다중 EntityManagerFactory 설정을 위한 예제 코드를 참고해 보시면 문제 해결에 도움이 될 것 같습니다.

Java 설정 예제:

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(
    basePackageClasses = Order.class, 
    entityManagerFactoryRef = "firstEntityManagerFactory", 
    transactionManagerRef = "firstTransactionManager"
)
public class OrderConfiguration {
    // ...
}

Kotlin 설정 예제:

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(
    basePackageClasses = [Order::class],
    entityManagerFactoryRef = "firstEntityManagerFactory",
    transactionManagerRef = "firstTransactionManager"
)
class OrderConfiguration {
    // ...
}

상기 설정에서 basePackageClasses, entityManagerFactoryRef, transactionManagerRef 등을 자신의 설정에 맞게 변경해 주어야 합니다.

코드에서 Order.classOrderConfiguration은 각각 예제에서 사용된 도메인 클래스와 설정 클래스입니다. 실제 연결하려는 도메인 클래스와 설정 클래스 이름으로 바꿔주셔야 합니다.

각각의 EntityManagerFactory에 대한 설정을 EntityManagerFactoryBuilder를 이용해 줄 수 있으며, JpaVendorAdapter를 올바르게 설정하고, 필요한 JPA 속성을 properties에 채워넣어야 합니다.

이 정보와 위의 코드 예제를 토대로 본인의 설정을 점검하고, 문제가 되는 부분을 수정하시면 될 것 같습니다. 설정을 분리하고 각 설정에 대해 적절한 EntityManagerFactoryTransactionManager를 할당하는 방식으로 접근해 보세요.

자세한 설정 방법은 다음 문서에서 확인하실 수 있습니다:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-access.use-multiple-entity-managers
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.java-config

적절한 설정을 통해 문제를 해결하시길 바랍니다. 추가 질문이 있으시면 도움을 드릴 준비가 되어 있습니다. 감사합니다!

MJ코딩님의 프로필 이미지
MJ코딩
지식공유자

안녕하세요 😊

강의를 시청해주셔서 감사합니다 :) 지금처럼 하나씩 하나씩 세팅도 해보고, 개발을 하시면 더욱 큰 실력을 쌓을 수 있으리라 생각이 되겠습니다.

코드를 보았을 때 primaryDataSource() 와 targetDbDataSource() 호출 되는 부분이 있습니다. 빈으로 등록을 해놓으셔도 함수를 직접 호출하지 않고, 스프링으로 부터 의존성 주입을 받아서 사용을 해주셔야 될 것으로 판단이 되겠습니다.

감사합니다.

hehihoho3님의 프로필 이미지
hehihoho3

작성한 질문수

질문하기