Mùa xuân - @Transactional - Điều gì xảy ra trong nền?


334

Tôi muốn biết những gì thực sự xảy ra khi bạn chú thích một phương thức với @Transactional? Tất nhiên, tôi biết rằng Spring sẽ gói phương thức đó trong Giao dịch.

Nhưng, tôi có những nghi ngờ sau:

  1. Tôi nghe nói rằng Spring tạo ra một lớp proxy ? Ai đó có thể giải thích điều này sâu hơn . Điều gì thực sự nằm trong lớp proxy đó? Điều gì xảy ra với lớp học thực tế? Và làm thế nào tôi có thể thấy lớp ủy nhiệm được tạo ra của Spring
  2. Tôi cũng đọc trong tài liệu mùa xuân rằng:

Lưu ý: Vì cơ chế này dựa trên proxy, nên chỉ các cuộc gọi phương thức 'bên ngoài' đến qua proxy sẽ bị chặn . Điều này có nghĩa là 'tự gọi', tức là một phương thức trong đối tượng đích gọi một số phương thức khác của đối tượng đích, sẽ không dẫn đến một giao dịch thực tế trong thời gian chạy ngay cả khi phương thức được gọi được đánh dấu bằng @Transactional!

Nguồn: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

Tại sao chỉ có các cuộc gọi phương thức bên ngoài sẽ nằm dưới Giao dịch mà không phải là phương thức tự gọi?


2
Thảo luận có liên quan ở đây: stackoverflow.com/questions/3120143/ từ
dma_k

Câu trả lời:


255

Đây là một chủ đề lớn. Tài liệu tham khảo Spring dành nhiều chương cho nó. Tôi khuyên bạn nên đọc những điều về Lập trìnhGiao dịch theo Định hướng , vì hỗ trợ giao dịch khai báo của Spring sử dụng AOP làm nền tảng.

Nhưng ở mức rất cao, Spring tạo ra các proxy cho các lớp khai báo @Transactional trên chính lớp đó hoặc trên các thành viên. Các proxy hầu như vô hình trong thời gian chạy. Nó cung cấp một cách để Spring thực hiện các hành vi trước, sau hoặc xung quanh các cuộc gọi phương thức vào đối tượng được ủy quyền. Quản lý giao dịch chỉ là một ví dụ về các hành vi có thể được nối vào. Kiểm tra bảo mật là một hành vi khác. Và bạn cũng có thể cung cấp của riêng bạn cho những thứ như đăng nhập. Vì vậy, khi bạn chú thích một phương thức với @Transactional , Spring sẽ tự động tạo một proxy thực hiện cùng (các) giao diện giống như lớp bạn đang chú thích. Và khi khách hàng thực hiện cuộc gọi vào đối tượng của bạn, các cuộc gọi sẽ bị chặn và các hành vi được thực hiện thông qua cơ chế proxy.

Giao dịch trong EJB hoạt động tương tự, nhân tiện.

Như bạn đã quan sát, thông qua, cơ chế proxy chỉ hoạt động khi có cuộc gọi đến từ một số đối tượng bên ngoài. Khi bạn thực hiện cuộc gọi nội bộ trong đối tượng, bạn thực sự thực hiện cuộc gọi thông qua tham chiếu " này ", bỏ qua proxy. Tuy nhiên, có nhiều cách để giải quyết vấn đề đó. Tôi giải thích một cách tiếp cận trong bài đăng trên diễn đàn này, trong đó tôi sử dụng BeanFactoryPostProcessor để đưa một thể hiện của proxy vào các lớp "tự tham chiếu" trong thời gian chạy. Tôi lưu tham chiếu này vào một biến thành viên gọi là " tôi ". Sau đó, nếu tôi cần thực hiện các cuộc gọi nội bộ yêu cầu thay đổi trạng thái giao dịch của chuỗi, tôi sẽ chuyển cuộc gọi qua proxy (ví dụ: " me.someMethod ()".) Bài đăng trên diễn đàn giải thích chi tiết hơn. Lưu ý rằngBeanFactoryPostProcessor bây giờ sẽ khác một chút, vì nó được viết lại trong khung thời gian Spring 1.x. Nhưng hy vọng nó mang lại cho bạn một ý tưởng. Tôi có một phiên bản cập nhật mà tôi có thể có sẵn.


4
>> Proxy hầu như vô hình trong thời gian chạy Oh !! Tôi tò mò muốn xem chúng :) Nghỉ ngơi .. câu trả lời của bạn rất toàn diện. Đây là lần thứ hai bạn giúp tôi..Cảm ơn tất cả sự giúp đỡ.
đỉnh

17
Không vấn đề gì. Bạn có thể thấy mã proxy nếu bạn bước qua với trình gỡ lỗi. Đó có lẽ là cách dễ nhất. Không có phép thuật; chúng chỉ là các lớp trong gói Spring.
Rob H

Và nếu phương thức có chú thích @Transaction đang triển khai giao diện thì lò xo sẽ ​​sử dụng API proxy động để thực hiện giao dịch và không sử dụng proxy. Tôi thích để các lớp giao dịch của tôi thực hiện các giao diện trong mọi trường hợp.
Michael Wiles

1
Tôi cũng đã tìm thấy lược đồ của tôi (sử dụng hệ thống dây điện rõ ràng để thực hiện theo cách tôi nghĩ), nhưng tôi nghĩ rằng nếu bạn làm theo cách đó, có lẽ bạn nên tái cấu trúc để bạn không nên tái cấu trúc để bạn không phải. Nhưng vâng, điều đó đôi khi có thể rất khó xử!
Donal Fellows

2
2019: Khi câu trả lời này đã cũ, bài đăng trên diễn đàn được giới thiệu không còn khả dụng, nó sẽ mô tả trường hợp khi bạn phải thực hiện một cuộc gọi nội bộ trong đối tượng mà không bỏ qua proxy, sử dụngBeanFactoryPostProcessor . Tuy nhiên, có một phương pháp tương tự (theo tôi) được mô tả trong câu trả lời này: stackoverflow.com/a/11277899/3667003 ... và các giải pháp khác trong toàn bộ chủ đề.
Z3d4s

195

Khi Spring tải các định nghĩa bean của bạn và đã được cấu hình để tìm kiếm các @Transactionalchú thích, nó sẽ tạo ra các đối tượng proxy này xung quanh bean thực tế của bạn . Các đối tượng proxy này là các thể hiện của các lớp được tạo tự động khi chạy. Hành vi mặc định của các đối tượng proxy này khi một phương thức được gọi chỉ là để gọi cùng một phương thức trên bean "đích" (tức là bean của bạn).

Tuy nhiên, proxy cũng có thể được cung cấp với các bộ chặn và khi có mặt các bộ chặn này sẽ được proxy gọi trước khi nó gọi phương thức của bean mục tiêu của bạn. Đối với các bean đích được chú thích @Transactional, Spring sẽ tạo một TransactionInterceptorvà chuyển nó đến đối tượng proxy được tạo. Vì vậy, khi bạn gọi phương thức từ mã máy khách, bạn đang gọi phương thức trên đối tượng proxy, đầu tiên sẽ gọi TransactionInterceptor(bắt đầu một giao dịch), từ đó gọi phương thức trên bean mục tiêu của bạn. Khi lệnh gọi kết thúc, các TransactionInterceptorcam kết / khôi phục giao dịch. Nó trong suốt đối với mã máy khách.

Đối với điều "phương thức bên ngoài", nếu bean của bạn gọi một trong các phương thức của chính nó, thì nó sẽ không được thực hiện thông qua proxy. Hãy nhớ rằng, Spring kết thúc bean của bạn trong proxy, bean của bạn không có kiến ​​thức về nó. Chỉ các cuộc gọi từ "bên ngoài" bean của bạn đi qua proxy.

cái đó có giúp ích không?


36
> Hãy nhớ rằng, Spring kết thúc bean của bạn trong proxy, bean của bạn không có kiến ​​thức về nó Điều này đã nói lên tất cả. Thật là một câu trả lời tuyệt vời. Cảm ơn đã giúp đỡ.
đỉnh

Giải thích tuyệt vời, cho proxy và đánh chặn. Bây giờ tôi hiểu mùa xuân thực hiện một đối tượng proxy để chặn các cuộc gọi đến một bean mục tiêu. Cảm ơn bạn!
dharag

Tôi nghĩ rằng bạn đang cố gắng mô tả hình ảnh này của tài liệu Mùa xuân và xem hình ảnh này giúp tôi rất nhiều: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/iêu
WesternGun

44

Là một người trực quan, tôi thích cân nhắc với sơ đồ trình tự của mẫu proxy. Nếu bạn không biết cách đọc mũi tên, tôi sẽ đọc cái đầu tiên như thế này: Clientthực thi Proxy.method().

  1. Máy khách gọi một phương thức trên mục tiêu theo quan điểm của mình và bị proxy chặn âm thầm
  2. Nếu một khía cạnh trước được xác định, proxy sẽ thực thi nó
  3. Sau đó, phương thức thực tế (đích) được thực thi
  4. Sau khi trả lại và sau khi ném là các khía cạnh tùy chọn được thực hiện sau khi phương thức trả về và / hoặc nếu phương thức ném ngoại lệ
  5. Sau đó, proxy thực thi khía cạnh sau (nếu được xác định)
  6. Cuối cùng, proxy trở lại máy khách đang gọi

Sơ đồ trình tự mẫu proxy (Tôi được phép đăng ảnh với điều kiện tôi đã đề cập đến nguồn gốc của nó. Tác giả: Noel Vaes, trang web: www.noelvaes.eu)


27

Câu trả lời đơn giản nhất là:

Trên bất kỳ phương thức nào bạn khai báo @Transactionalranh giới của giao dịch bắt đầu và kết thúc ranh giới khi phương thức hoàn thành.

Nếu bạn đang sử dụng cuộc gọi JPA thì tất cả các cam kết đều nằm trong ranh giới giao dịch này .

Hãy nói rằng bạn đang lưu thực thể1, thực thể2 và thực thể3. Bây giờ trong khi lưu thực thể3, một ngoại lệ xảy ra , khi enitiy1 và entity2 xuất hiện trong cùng một giao dịch, vì vậy entity1 và entity2 sẽ được khôi phục với entity3.

Giao dịch :

  1. entity1.save
  2. entity2.save
  3. entity3.save

Bất kỳ ngoại lệ nào cũng sẽ dẫn đến việc khôi phục tất cả các giao dịch JPA với DB. Giao dịch JPA Nội bộ được Spring sử dụng.


2
"Ngoại lệ An̶y̶ sẽ dẫn đến việc khôi phục tất cả các giao dịch JPA với DB." Lưu ý Chỉ có RuntimeException kết quả trong rollback. Đã kiểm tra ngoại lệ, sẽ không dẫn đến rollback.
Arjun

2

Có thể đã muộn nhưng tôi đã bắt gặp điều gì đó giải thích mối quan tâm của bạn liên quan đến proxy (chỉ các cuộc gọi phương thức 'bên ngoài' đến qua proxy sẽ bị chặn) một cách độc đáo.

Ví dụ, bạn có một lớp trông như thế này

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

và bạn có một khía cạnh, trông như thế này:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Khi bạn thực hiện nó như thế này:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Kết quả của việc gọi kick Offer ở trên mã đã cho ở trên.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

nhưng khi bạn thay đổi mã của bạn thành

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Bạn thấy đấy, phương thức bên trong gọi một phương thức khác để nó không bị chặn và đầu ra sẽ như thế này:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Bạn có thể vượt qua điều này bằng cách làm điều đó

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Đoạn mã được lấy từ: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


0

Tất cả các câu trả lời hiện tại đều đúng, nhưng tôi cảm thấy không thể chỉ đưa ra chủ đề phức tạp này.

Để có một lời giải thích toàn diện, thực tế, bạn có thể muốn xem hướng dẫn Spring @Transactional In-Depth này , cố gắng hết sức để quản lý giao dịch bằng ~ 4000 từ đơn giản, với rất nhiều ví dụ về mã.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.