Liệu thuộc tính Spring @Transactional có hoạt động trên một phương thức riêng không?


196

Nếu tôi có @Transactional -annotation trên một phương thức riêng tư trong một bean Spring, thì chú thích có ảnh hưởng gì không?

Nếu @Transactionalchú thích là một phương thức công khai, nó hoạt động và mở một giao dịch.

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Câu trả lời:


163

Câu hỏi không riêng tư hoặc công khai, câu hỏi là: Nó được gọi như thế nào và bạn sử dụng triển khai AOP nào!

Nếu bạn sử dụng (mặc định) Spring Proxy AOP, thì tất cả chức năng AOP được cung cấp bởi Spring (như @Transational) sẽ chỉ được tính đến nếu cuộc gọi đi qua proxy. - Đây thường là trường hợp nếu phương thức chú thích được gọi từ một bean khác .

Điều này có hai hàm ý:

  • Vì các phương thức riêng tư không được gọi từ một bean khác (ngoại lệ là sự phản chiếu), nên @Transactionalchú thích của chúng không được tính đến.
  • Nếu phương thức này là công khai, nhưng nó được gọi từ cùng một bean, nó cũng sẽ không được tính đến (câu lệnh này chỉ đúng nếu (mặc định) Spring Proxy AOP được sử dụng).

@See Tham khảo mùa xuân: Chương 9.6 9.6 Cơ chế ủy quyền

IMHO bạn nên sử dụng chế độ facJ, thay vì Spring Proxies, điều đó sẽ khắc phục vấn đề. Và AspectJ Aspect Aspect được dệt ngay cả thành các phương thức riêng tư (được kiểm tra cho Spring 3.0).


4
Cả hai điểm không nhất thiết phải đúng. Đầu tiên là không chính xác - các phương thức riêng tư có thể được gọi một cách phản xạ, nhưng logic khám phá proxy chọn không làm như vậy. Điểm thứ hai chỉ đúng với các proxy JDK dựa trên giao diện, nhưng không đúng với các proxy dựa trên lớp con CGLIB.
skaffman

@skaffman: 1 - tôi làm cho số liệu thống kê của mình chính xác hơn, 2. Nhưng Proxy mặc định là Giao diện dựa trên - phải không?
Ralph

2
Điều đó phụ thuộc vào việc mục tiêu có sử dụng giao diện hay không. Nếu không, CGLIB được sử dụng.
skaffman

canu cho tôi biết cộng hưởng hoặc một số tài liệu tham khảo tại sao cglib không thể nhưng facj có thể?
phil

1
Tham chiếu từ liên kết trong khối câu trả lời, nếu bạn muốn sử dụng Spring Proxies [môi trường mặc định], đặt chú thích vào doStuff () và gọi doPrivateStuff () bằng cách sử dụng ((Bean) AopContext.cienProxy ()). DoPrivateStuff (); Nó sẽ thực thi cả hai phương thức trong cùng một giao dịch nếu việc truyền bá được lấy lại [môi trường mặc định].
Michael Ouyang

219

Câu trả lời cho câu hỏi của bạn là không - @Transactionalsẽ không có tác dụng nếu được sử dụng để chú thích các phương thức riêng tư. Trình tạo proxy sẽ bỏ qua chúng.

Điều này được ghi lại trong Sổ tay mùa xuân chương 10.5.6 :

Phương thức hiển thị và @Transactional

Khi sử dụng proxy, bạn chỉ nên áp dụng @Transactionalchú thích cho các phương thức có khả năng hiển thị công khai. Nếu bạn chú thích các phương thức được bảo vệ, riêng tư hoặc có thể nhìn thấy gói với @Transactionalchú thích, không có lỗi nào được nêu ra, nhưng phương thức chú thích không thể hiện các cài đặt giao dịch được định cấu hình. Xem xét việc sử dụng AspectJ (xem bên dưới) nếu bạn cần chú thích các phương thức không công khai.


Bạn có chắc về điều này? Tôi sẽ không mong đợi nó sẽ làm cho một sự khác biệt.
willcodejavaforfood

Nếu kiểu proxy là Cglib thì sao?
hoa huệ

32

Theo mặc định, @Transactionalthuộc tính chỉ hoạt động khi gọi một phương thức chú thích trên một tham chiếu thu được từ applicationContext.

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

Điều này sẽ mở một giao dịch:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

Điều này sẽ không:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Tham khảo mùa xuân: Sử dụng @Transactional

Lưu ý: Trong chế độ proxy (là mặc định), chỉ các cuộc gọi phương thức 'bên ngoài' đến thông 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!

Hãy xem xét việc sử dụng chế độ AspectJ (xem bên dưới) nếu bạn mong muốn các lệnh tự thực hiện cũng được gói bằng các giao dịch. Trong trường hợp này, sẽ không có proxy ở nơi đầu tiên; thay vào đó, lớp đích sẽ được 'dệt' (tức là mã byte của nó sẽ được sửa đổi) để biến @Transactionalthành hành vi thời gian chạy trên bất kỳ loại phương thức nào.


Ý bạn là bean = new Bean ();?
willcodejavaforfood

Không. Nếu tôi tạo đậu bằng Bean mới (), chú thích sẽ không bao giờ hoạt động ít nhất nếu không sử dụng Aspect-J.
Juha Syrjälä

2
cảm ơn! Điều này giải thích hành vi kỳ lạ tôi đã quan sát. Khá trực quan hạn chế gọi phương thức nội bộ này ...
manuel aldana

Tôi đã học được "chỉ các cuộc gọi phương thức bên ngoài đến thông qua proxy sẽ bị chặn" một cách khó khăn
vào

13

Có, có thể sử dụng @Transactional trên các phương thức riêng tư, nhưng như những người khác đã đề cập, điều này sẽ không hoạt động. Bạn cần sử dụng AspectJ. Tôi phải mất một thời gian để tìm ra cách làm cho nó hoạt động. Tôi sẽ chia sẻ kết quả của tôi.

Tôi đã chọn sử dụng dệt thời gian biên dịch thay vì dệt thời gian tải vì tôi nghĩ đó là một lựa chọn tốt hơn về tổng thể. Ngoài ra, tôi đang sử dụng Java 8 nên bạn có thể cần điều chỉnh một số tham số.

Đầu tiên, thêm sự phụ thuộc cho facjrt.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

Sau đó, thêm plugin AspectJ để thực hiện việc dệt mã byte thực tế trong Maven (đây có thể không phải là một ví dụ tối thiểu).

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Cuối cùng thêm nó vào lớp cấu hình của bạn

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

Bây giờ bạn sẽ có thể sử dụng @Transactional trên các phương thức riêng tư.

Một lưu ý cho cách tiếp cận này: Bạn sẽ cần cấu hình IDE của mình để nhận biết về AspectJ nếu không, nếu bạn chạy ứng dụng qua Eclipse chẳng hạn, nó có thể không hoạt động. Hãy chắc chắn rằng bạn đã kiểm tra bản dựng Maven trực tiếp dưới dạng kiểm tra độ tỉnh táo.


nếu phương thức ủy quyền là cglib, thì không cần thực hiện giao diện mà phương thức này sẽ được công khai, vậy nó có thể sử dụng @Transactional trên các phương thức riêng tư không?
hoa huệ

Vâng, nó hoạt động trên các phương thức riêng tư, và không có giao diện! Miễn là AspectJ được cấu hình đúng, về cơ bản nó đảm bảo các trình trang trí phương thức làm việc. Và user536161 đã chỉ ra trong câu trả lời của anh ấy rằng nó thậm chí sẽ hoạt động trên các yêu cầu tự. Nó thực sự rất tuyệt, và chỉ một chút đáng sợ.
James Watkins

12

Nếu bạn cần bọc một phương thức riêng tư trong một giao dịch và không muốn sử dụng khía cạnh, bạn có thể sử dụng Giao dịch .

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}

Tốt để hiển thị TransactionTemplateviệc sử dụng, nhưng xin vui lòng gọi phương thức thứ hai ..RequiresTransactionchứ không phải ..InTransaction. Luôn đặt tên công cụ như thế nào bạn muốn đọc nó một năm sau đó. Ngoài ra tôi sẽ tranh luận để suy nghĩ nếu nó thực sự đòi hỏi một phương thức riêng tư thứ hai: Hoặc đưa nội dung của nó trực tiếp vào việc executetriển khai ẩn danh hoặc nếu điều đó trở nên lộn xộn thì đó có thể là một dấu hiệu để phân chia việc thực hiện thành một dịch vụ khác mà sau đó bạn có thể chú thích @Transactional.
Bị kẹt

@Stuck, phương thức thứ 2 thực sự không cần thiết, nhưng nó trả lời câu hỏi ban đầu là làm thế nào để áp dụng một giao dịch mùa xuân trên một phương thức riêng tư
loonis

vâng, tôi đã nêu lên câu trả lời nhưng muốn chia sẻ một số bối cảnh và suy nghĩ về cách áp dụng nó, bởi vì tôi nghĩ từ quan điểm kiến ​​trúc, tình huống này là một dấu hiệu tiềm năng cho một lỗ hổng thiết kế.
Bị kẹt

5

Tài liệu mùa xuân giải thích rằng

Trong chế độ proxy (là mặc định), chỉ các cuộc gọi phương thức bên ngoài đến thông qua proxy mới bị chặn. Điều này có nghĩa là tự gọi, trong thực tế, một phương thức trong đối tượng đích gọi một 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.

Hãy xem xét việc sử dụng chế độ AspectJ (xem thuộc tính chế độ trong bảng bên dưới) nếu bạn mong muốn các lệnh tự thực hiện cũng được gói bằng các giao dịch. Trong trường hợp này, sẽ không có proxy ở nơi đầu tiên; thay vào đó, lớp đích sẽ được dệt (nghĩa là mã byte của nó sẽ được sửa đổi) để biến @Transactional thành hành vi thời gian chạy trên bất kỳ loại phương thức nào.

Một cách khác là người dùng BeanSelfAware


bạn có thể thêm một tài liệu tham khảo BeanSelfAware? Nó không giống lớp học của mùa xuân
vào

@asgs Giả sử, đó là về việc tự tiêm (cung cấp một bean với một thể hiện của chính nó được bọc trong một proxy). Bạn có thể xem các ví dụ trong stackoverflow.com/q/3423972/355438 .
Lu55


1

Tương tự như cách @loonis đề xuất sử dụng TransactionTemplate, người ta có thể sử dụng thành phần trợ giúp này (Kotlin):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

Sử dụng:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

Không biết có TransactionTemplatesử dụng lại giao dịch hiện tại hay không nhưng mã này chắc chắn làm được.

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.