복붙노트

[SPRING] 스프링 데이터와 하이버 네이트를 사용할 때 어떻게 백그라운드 스레드를 올바르게 수행 할 수 있습니까?

SPRING

스프링 데이터와 하이버 네이트를 사용할 때 어떻게 백그라운드 스레드를 올바르게 수행 할 수 있습니까?

Spring Data와 Hibernate를 사용하는 간단한 Tomcat webapp을 만들고 있습니다. 많은 작업을 수행하는 하나의 엔드 포인트가 있으므로 작업이 완료되는 동안 웹 요청이 10 분 이상 멈추지 않도록 작업을 백그라운드 스레드로 오프로드하려고합니다. 그래서 나는 component-scan 패키지에 새로운 Service를 작성했다 :

@Service
public class BackgroundJobService {
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    public void startJob(Runnable runnable) {
         threadPoolTaskExecutor.execute(runnable);
    }
}

그런 다음 Spring에서 ThreadPoolTaskExecutor를 구성하십시오.

<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="5" />
    <property name="maxPoolSize" value="10" />
    <property name="queueCapacity" value="25" />
</bean>

이것은 모두 잘 작동합니다. 그러나이 문제는 Hibernate에서 발생한다. 내 runnable 내부 쿼리 절반 밖에 작동하지 않습니다. 내가 할 수있는:

MyObject myObject = myObjectRepository.findOne()
myObject.setSomething("something");
myObjectRepository.save(myObject);

그러나 게으른로드 된 필드가 있으면 실패합니다.

MyObject myObject = myObjectRepository.findOne()
List<Lazy> lazies = myObject.getLazies();
for(Lazy lazy : lazies) { // Exception
    ...
}

다음과 같은 오류가 발생합니다.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.stackoverflow.MyObject.lazies, could not initialize proxy - no Session

그래서 나 (Hibernate newbie)는 새로운 쓰레드가 이들 집에서 만든 쓰레드에 대한 세션을 가지고 있지 않지만 Spring Data는 자동으로 HTTP 요청 쓰레드를위한 새로운 세션을 생성하고 있다고 생각한다.

@Transactional 메소드 내부에서부터 모든 것을함으로써 약간의 문제를 해결할 수 있었지만, 웹 요청에 대해서는 정상적으로 작동하는 메소드를 사용할 수 없기 때문에 매우 좋은 해결책은 아닙니다. .

감사.

해결법

  1. ==============================

    1.Spring에서는 자신 만의 집행자가 필요 없습니다. 간단한 주석 @Async가 작업을 수행합니다. 그걸로 서비스에 heavyMethod에 주석을 달고 void 나 Future 객체를 반환하면 배경 스레드가 생깁니다. 컨트롤러 수준에서 비동기 주석을 사용하지 마십시오. 요청 풀이 요청자 풀에서 비동기 스레드를 생성하고 '요청 수락 자'가 부족할 수 있습니다.

    Spring에서는 자신 만의 집행자가 필요 없습니다. 간단한 주석 @Async가 작업을 수행합니다. 그걸로 서비스에 heavyMethod에 주석을 달고 void 나 Future 객체를 반환하면 배경 스레드가 생깁니다. 컨트롤러 수준에서 비동기 주석을 사용하지 마십시오. 요청 풀이 요청자 풀에서 비동기 스레드를 생성하고 '요청 수락 자'가 부족할 수 있습니다.

    게으른 예외 문제는 세션이없는 새 스레드로 의심되는 경우에 발생합니다. 이 문제를 피하려면 비동기 메서드가 전체 작업을 처리해야합니다. 이전에로드 된 엔티티를 매개 변수로 제공하지 마십시오. 이 서비스는 EntityManager를 사용할 수 있으며 트랜잭션이 될 수도 있습니다.

    나 자신을 위해 @Async와 @Transactional을 병합하지 않기 때문에 어느 쪽의 방법 으로든 서비스를 실행할 수있다. 방금 서비스 주위에 비동기 래퍼를 만들고 대신 필요할 경우이 패키지를 사용합니다. (이는 예를 들어 테스트를 간소화합니다)

    @Service
    public class AsyncService {
    
        @Autowired
        private Service service;
    
        @Async
        public void doAsync(int entityId) {
            service.doHeavy(entityId);
        }
    }
    
    @Service
    public class Service {
    
        @PersistenceContext
        private EntityManager em;
    
        @Transactional
        public void doHeavy(int entityId) {
            // some long running work
        }
    }
    
  2. ==============================

    2.아마도 DAO 코드에서 트랜잭션을 처리하고 Spring은 트랜잭션을 닫을 때 세션을 닫습니다.

    아마도 DAO 코드에서 트랜잭션을 처리하고 Spring은 트랜잭션을 닫을 때 세션을 닫습니다.

    모든 비즈니스 로직을 단일 트랜잭션으로 집어 넣어야합니다.

    SessionFactory를 코드에 삽입하고 SessionFactory.openSession () 메소드를 사용할 수 있습니다. 문제는 거래를 관리해야한다는 것입니다.

  3. ==============================

    3.방법 # 1 : JPA Entity Manager

    방법 # 1 : JPA Entity Manager

    백그라운드 스레드에서 : 엔티티 관리자를 삽입하거나 스프링 컨텍스트에서 가져 오거나 참조로 전달하십시오.

    @PersistenceContext
    private EntityManager entityManager;    
    

    그런 다음 공유 엔티티를 사용하지 않으려면 새 엔티티 관리자를 만듭니다.

    EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
    

    이제 트랜잭션을 시작하고 Spring DAO, 저장소, JPA 등을 사용할 수 있습니다.

    private void save(EntityManager em) {
    
        try
        {         
            em.getTransaction().begin();                
    
            <your database changes>
    
            em.getTransaction().commit();                        
        }
        catch(Throwable th) {
            em.getTransaction().rollback();
            throw th;
        }        
    }
    

    방법 # 2 : JdbcTemplate

    낮은 수준의 변경이 필요하거나 작업이 간단하면 JDBC 및 쿼리를 사용하여 수동으로 수행 할 수 있습니다.

    @Autowired
    private JdbcTemplate jdbcTemplate;
    

    그리고 어딘가에 당신의 방법 :

    jdbcTemplate.update("update task set `status`=? where id = ?", task.getStatus(), task.getId());
    

    참고 : JTA를 사용하거나 JpaTransactionManager에 의존하지 않는 한 @Transactional에서 멀리 떨어져있는 것이 좋습니다.

  4. from https://stackoverflow.com/questions/24916104/how-do-i-properly-do-a-background-thread-when-using-spring-data-and-hibernate by cc-by-sa and MIT license