Published 2024. 2. 28. 14:32

OSIV 의 역할

트랜잭션의 외부에서 조회에 대한 지연로딩을 제공한다. 

 

SpringBoot AutoConfiguration

 

1. open-in-view를 활성화하면 JpaWebConfiguration이 활성화

2. OpenEntityManagerInViewInterceptor빈이 WebRequestInterceptor에 추가된다. 

3. 웹으로부터 요청이들어오면 인터셉터의 prehandle() 메서드를호출해 요청을 받음

4. TransctionSyncronizationManager로 통해 현재 요청을 처리하는 스레드에 EntityManager를 생성한다. 그리고 EntityManager를 현제 스레드에 반영한다. => 이후 EntityManager의 라이프 사이클이 변환함

(트랜잭션과 EntityManager는 생명주기가 같음)

5. 트랜잭션이 시작되면 JpaTransactionManager의 doGetTransaction이 호출된다. 현재 스레드에 바인딩된 EntityManager를 조회하고 존재하면 트랜잭션 객체에 EntityManager를 바인딩한다.

 

JpaTransactionManager

@Override
	protected Object doGetTransaction() {
		JpaTransactionObject txObject = new JpaTransactionObject();
		txObject.setSavepointAllowed(isNestedTransactionAllowed());

		EntityManagerHolder emHolder = (EntityManagerHolder)
				TransactionSynchronizationManager.getResource(obtainEntityManagerFactory());
		if (emHolder != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found thread-bound EntityManager [" + emHolder.getEntityManager() +
						"] for JPA transaction");
			}
			txObject.setEntityManagerHolder(emHolder, false);
		}

		if (getDataSource() != null) {
			ConnectionHolder conHolder = (ConnectionHolder)
					TransactionSynchronizationManager.getResource(getDataSource());
			txObject.setConnectionHolder(conHolder);
		}

		return txObject;
	}

 

public void setEntityManagerHolder(
				@Nullable EntityManagerHolder entityManagerHolder, boolean newEntityManagerHolder) {

			this.entityManagerHolder = entityManagerHolder;
			this.newEntityManagerHolder = newEntityManagerHolder;
		}

 

여기서 newEntityManagerHolder를 보면, 바인딩된 EntityManager가 신규 생성된 것인지 여부를 확인한다. osiv가 켜져있다면 이 값은 false가 된다.

 

+ osiv가 활성화 되어있지 않다면, 동작 과정이 다르다.

	@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		JpaTransactionObject txObject = (JpaTransactionObject) transaction;

		if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
			throw new IllegalTransactionStateException(
					"Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
					"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
					"It is recommended to use a single JpaTransactionManager for all transactions " +
					"on a single DataSource, no matter whether JPA or JDBC access.");
		}

		try {
			if (!txObject.hasEntityManagerHolder() ||
					txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
				EntityManager newEm = createEntityManagerForTransaction();
				if (logger.isDebugEnabled()) {
					logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
				}
				txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
			}

doBegin 메소드에서 신규로 생성된다. (OpenEntityManagerInViewInterceptor가 바인딩 된 경우만 제외하면 대분 신규)

 

6. JpaTransactionManager는 트랜잭션이 종료되면 doCleanUpAfterConpletion을 호출한다.

이 메소드에 의해 EntityManager는 소멸된다. 하지만, 이때 isNewEnetityManagerHolderer 조건이 참일 경우에만 소멸한다.

@Override
	protected void doCleanupAfterCompletion(Object transaction) {
		JpaTransactionObject txObject = (JpaTransactionObject) transaction;
        
        /** 중략 **/
        
       
        // Remove the entity manager holder from the thread.
		if (txObject.isNewEntityManagerHolder()) {
			EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
			if (logger.isDebugEnabled()) {
				logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
			}
			EntityManagerFactoryUtils.closeEntityManager(em);
		}
		else {
			logger.debug("Not closing pre-bound JPA EntityManager after transaction");
		}
	}

이 점검 여부에 의해 EntityManager는 트랜잭션과 함꼐 소멸되지 않는다. (지연로딩이 가능해짐)

 

7. EntityManager의 소멸(트랜잭션 종료가 아닌 응답이 나갈때)

request가 종료되고 OpenEntityManagerInViewInterceptor의 afterCompletion으로  스레드에서 EntityManager를 해제하고 소멸시킴

 

결론 : Osiv는 트랜잭션이 종료되어도 EntityManager는 커넥션이 게속 열려있음을 의미한다. 그리고 다음 요청에 재사용할 수 잇음

데이터베이스 연결을 점유하고 있음, 트랜잭션과 관련없는 별개의 작업이 남아있다면 더 비효율적인 결과를 초래할 수 있음

 

 

스레드에 entitymanager가 상주 = 지연로딩 무슨 상관?

 

트랜잭션이 같으면 같은 영속성 컨텍스트

여러 스레드에서 동시에 요청이 오면 같은 엔티티 매니저를 사용해도 다른 영속성 컨텍스트를 사용하게 된다. (이때 엔티티매니저는 실제 엔티티매니저가 아닌 프록시이다. entityManager메소드를 호출하면 프록시를 통해 entityManager를 생성해 스레드 세이프티를 보장한다.) 

스레드마다 각각 다른 트랜잭션을 할당하기 때문

 

동시성 문제를 해결하는 방법

1. 메소드에 Syncronized 키워드 => 실패

Transactional 프록시 방식 때문에 프록시가 Commit종료되기 전에 다른 스레드가 해당 자원에 접근할 수 있음

 

복사했습니다!