2014-07-23 14 views
8

Tôi đang xây dựng một ứng dụng web Tomcat đơn giản đang sử dụng Spring Data và Hibernate. Có một điểm kết thúc thực hiện rất nhiều công việc, vì vậy tôi muốn tải công việc xuống một chuỗi nền để yêu cầu web không treo trong hơn 10 phút trong khi công việc đang được thực hiện. Vì vậy, tôi đã viết một dịch vụ mới trong một gói phần-scan'd:Làm cách nào để thực hiện đúng chuỗi nền khi sử dụng Dữ liệu Spring và Hibernate?

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

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

Sau đó có ThreadPoolTaskExecutor cấu hình trong mùa xuân:

<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> 

Đây là tất cả làm việc tuyệt vời. Tuy nhiên, vấn đề đến từ Hibernate. Bên trong runnable của tôi, truy vấn chỉ có một nửa công việc. Tôi có thể làm:

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

Nhưng nếu tôi có các trường lười nạp, nó không thành công:

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

tôi nhận được lỗi sau:

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

Vì vậy, có vẻ như với tôi (Hibernate newbie) rằng luồng mới không có phiên làm việc trên các luồng được tạo trong nhà này, nhưng Spring Data sẽ tự động tạo các phiên mới cho các chuỗi yêu cầu HTTP.

  • Có cách nào để bắt đầu phiên mới theo cách thủ công từ trong phiên không?
  • Hoặc một cách để cho hồ bơi chủ đề làm điều đó cho tôi?
  • Thực hành tiêu chuẩn để thực hiện loại công việc này là gì?

Tôi đã có thể làm việc xung quanh nó một chút bằng cách làm mọi thứ từ bên trong một phương pháp @Transactional, nhưng tôi nhanh chóng tìm hiểu đó không phải là một giải pháp rất tốt, vì điều đó không cho phép tôi sử dụng các phương pháp hoạt động tốt cho các yêu cầu web.

Cảm ơn.

Trả lời

0

Điều gì xảy ra là, có thể, bạn có giao dịch trên đoạn mã DAO của bạn và Spring đang đóng phiên giao dịch gần.

Bạn nên ép tất cả logic nghiệp vụ thành một giao dịch duy nhất.

Bạn có thể tiêm SessionFactory vào mã của mình và sử dụng phương pháp SessionFactory.openSession().
Vấn đề là bạn sẽ phải quản lý các giao dịch của mình.

+0

Một phần của vấn đề là, tôi muốn có thể cập nhật trạng thái công việc trong khi nó đang chạy. Vì vậy, nếu tôi bọc tất cả trong một giao dịch, trạng thái không cập nhật cho đến khi cam kết ngoài cùng cam kết. Trừ khi tôi đang thiếu một cái gì đó ở đây ... đó là hoàn toàn có thể :) – Joel

+0

Bạn có thể hiển thị lớp thực hiện Runnable của bạn và có thể ObjectRepository của bạn? Tôi cũng rất tò mò về cách thức và vị trí bạn dự định xuất bản các cập nhật trạng thái. Không rõ ràng với các ứng dụng web dựa trên http. – alobodzk

+0

Cụ thể để cập nhật trạng thái tiến trình: sử dụng giao dịch lồng nhau. Đảm bảo rằng nhà cung cấp JPA của bạn hỗ trợ họ. – Virmundi

19

Với mùa xuân bạn không cần người thực thi của riêng bạn. Một chú thích đơn giản @Async sẽ thực hiện công việc cho bạn. Chỉ cần chú thích heavyMethod trong dịch vụ của bạn với nó và trả lại khoảng trống hoặc đối tượng Future và bạn sẽ nhận được một chuỗi nền. Tôi sẽ tránh sử dụng chú thích async ở cấp độ bộ điều khiển, vì điều này sẽ tạo ra một chuỗi không đồng bộ trong trình thực thi pool yêu cầu và bạn có thể chạy ra khỏi 'yêu cầu chấp nhận'.

Sự cố với ngoại lệ lười biếng của bạn đến khi bạn nghi ngờ từ chuỗi mới không có phiên. Để tránh vấn đề này, phương thức async của bạn nên xử lý công việc hoàn chỉnh. Không cung cấp các thực thể được tải trước đó làm tham số. Dịch vụ có thể sử dụng một EntityManager và cũng có thể được giao dịch.

Tôi cho bản thân mình không hợp nhất @Async@Transactional để tôi có thể chạy dịch vụ theo một trong hai cách. Tôi chỉ cần tạo trình bao bọc không đồng bộ xung quanh dịch vụ và sử dụng trình bao này thay thế nếu cần. (Điều này giúp đơn giản hoá thử nghiệm ví dụ)

@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 
    } 
} 
+0

Kỹ thuật này đã hoạt động hoàn hảo cho ứng dụng Spring Roo 1.3.x cũ của tôi. Tôi đã phải nâng cao ứng dụng cũ và nó hoạt động như một sự quyến rũ. Cảm ơn bạn!! – sunitkatkar

-1

Phương pháp # 1: JPA Entity quản lý

Trong sợi nền: Tiêm quản lý thực thể hoặc làm cho nó từ bối cảnh mùa xuân hoặc vượt qua nó như tài liệu tham khảo:

@PersistenceContext 
private EntityManager entityManager;  

Sau đó, tạo người quản lý đối tượng mới, để tránh sử dụng một người quản lý thực thể:

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

Bây giờ bạn có thể bắt đầu giao dịch và sử dụng Spring DAO, Repository, JPA, vv

private void save(EntityManager em) { 

    try 
    {   
     em.getTransaction().begin();     

     <your database changes> 

     em.getTransaction().commit();       
    } 
    catch(Throwable th) { 
     em.getTransaction().rollback(); 
     throw th; 
    }   
} 

Phương pháp # 2: JdbcTemplate

Trong trường hợp bạn cần thay đổi ở mức độ thấp hoặc công việc của bạn đơn giản là đủ, bạn có thể làm điều đó với JDBC và các truy vấn bằng tay:

@Autowired 
private JdbcTemplate jdbcTemplate; 

và sau đó ở đâu đó trong phương pháp của bạn:

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

Lưu ý phụ: Tôi khuyên bạn nên tránh xa @Transactional trừ khi bạn sử dụng JTA hoặc dựa vào JpaTransactionManager.

+0

Điều đó không đúng! @Transactional không cần JTA. Người quản lý giao dịch cũng có thể là một org.springframework.orm.jpa.JpaTransactionManager sử dụng EntityManager để xử lý các giao dịch! –

+0

Cảm ơn bạn đã phản hồi. Tôi đã đưa ra đề xuất về @Transactional cụ thể hơn bây giờ. Nó không ảnh hưởng đến nội dung và tính chính xác của các khuyến nghị làm việc về cách xử lý vấn đề trong một chủ đề nền. – alexzender

+0

Tôi vẫn không đồng ý vì việc xử lý giao dịch của riêng bạn có thể rất cồng kềnh. Trong ví dụ của bạn khi gọi hàm em.getTransaction(). Rollback(); giao dịch có thể không hoạt động. Bạn phải kiểm tra điều này! –

Các vấn đề liên quan