[JPA] 다중 데이터베이스 연결, Could Not Initialize Proxy - No Session

sju3358·2024년 7월 26일
1

삽질 일기

목록 보기
3/6

보통 이런 오류가 떠서 구글에 검색하면, 트렌젝션에 관한 얘기가 나온다.


@Entity
public class Entity{
	@Id
    private Long id;
    
    @ManyToOne
    public AnotherEntity anotherEntity;
}


//Class A
public class A{

	@Autowire
    private B b;
    
	public void method(){
    
    	Entity entity = b.method();
        entity.
    }
}


//Class B
public class B{

	@Transactional
    public Entity method(){
    }
}

만약 위와 같은 구조가 있다고 할때,
1) Fetch Join을 하지 않는다면
2) Fetch Type이 Lazy라면

Entity가 조회될때 다음과 같이 영속된다.
1) Entity가 영속됨.
2) AnotherEntity는 Proxy 객체로 주입됨.

이때 Entity가 AnotherEntity의 값을 조회할때,
JPA는 다시한번 SELECT 쿼리를 날리게 되고,
이때 AnotherEntity가 영속하게 된다.

하지만 위 같은 사례는 A의 메소드에 Transactional 어노테이션이 붙어있지
않기때문에, 이미 B.method()에서 Transaction이 끝나버려, Entity의 영속이 해제된다.
이상태에서, AnotherEntity를 조회하게되면,
그대로 JPA에서 임의로 주입한 Proxy객체를 조회하게 되어
Could Not Initialize Proxy - No Session 오류가 뜨게 된다

이때 해결방법은 여러가지 있겠다.
A에 Transactional 어노테이션을 붙이던가
Eager로 조회하던가
Fetch Join으로 애초에 데이터를 같이 조회하면 되겠다.

혹은 Transactional 관계가 복잡한경우는
전파전략을 잘 세우거나 OSIV를 설정하여, 무조건 트랜젝션을 유지시킬 수도 있다.

여기까지가 일반적인 해결방법

나의 상황은 조금 달랐다.
우선 현재 DB가 두개의 RDB를 동시에 연결시켜 놓았다.
이게 스노우볼을 굴릴줄은 몰랐지..

그래서 각 DB의 Configuration클래스를 따로 작성해놔야하는데,

먼저 첫번째 Configure

@Configuration
@EnableJpaRepositories(
    basePackages = "io.a.a.global",
    entityManagerFactoryRef = "entityManager",
    transactionManagerRef = "transactionManager"
)
public class DatabaseConfig {

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

  @Bean
  @Primary
  public LocalContainerEntityManagerFactoryBean entityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(DataSource());
    em.setPackagesToScan("io.a.a.global");
    ...
//이하 생략

두번째 Configure

@Configuration
@EnableJpaRepositories(
    basePackages = "io.a.a.external",
    entityManagerFactoryRef = "secondEntityManager",
    transactionManagerRef = "secondTransactionManager"
)
public class DataBaseSecondConfig {

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

  @Bean
  public LocalContainerEntityManagerFactoryBean secondEntityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(secondDataSource());
    em.setPackagesToScan("io.a.a.external");
    ...

// 이하 생략

보면
io.a.a.global 패키지의 repository는 첫번째 Config를
io.a.a.external 패키지의 repository는 두번째 Config를
사용하며 Bean등록을 하게 되어있다.
이때 transcationManagerRef도 지정하여 bean등록이될 트랜잭션매니저의 인스턴스의 이름을 지정 할 수 있다.(이름이 같은게 두개있으면 안되니까 당연)

이때 무심코 그냥 아래와같이 선언했는데...똑같은 오류가 발생한다.

@Transactional
public class A{

public void method(){
	//io.a.a.external의 repository 호출!!
    //여기서 받아온 Entity의 다른 연관관계의 Entity값 조회시
    //Could Not Initialize..... 크아아아악
}

}

오잉? 난 관련된 모든클래스에 Transactional을 붙여놨는데?
여러 삽질(persistContext 찍어보기, TransactionName 찍어보기 등등)의 결과...
이상하게 Respository호출 전후로, 새로운 트랜잭션이 새로 생기고 닫힌다는 사실을 알아냈고
그 원인은 바로바로....

첫번째 DBConfigure가 @Primary라는 사실을 기억하자.
그럼 @Transactional의 트랜잭션 매니저는 어떤 트랜잭션 매니저가 주입이 될까?
그렇다...
첫번째 DBConfigure에서 정의한 트랜잭션 매니저가 주입이 된다.

하지만? io.a.a.external의 repository를 호출할때는
두번째 DBconfigure에서 정의한 트랜잭션 매니저가 주입이 된다ㅠㅠ

결론

io.a.a.extenral의 Repository를 사용하는 서비스가 있다면
@Transactional(transactionManager = "secondTransactionManager")
위와같이 어떤 트랜잭션 매니저를 쓸것인지 자세히 명시해야한다.

마치 Qualifier와 비슷..

profile
주니어 백엔드 개발자의 삽질 일기장

1개의 댓글

comment-user-thumbnail
2024년 7월 26일

최고에요

답글 달기