Spring @Transaction phương thức gọi bằng phương thức trong cùng một lớp, không hoạt động?


109

Tôi mới tham gia Giao dịch mùa xuân. Một điều gì đó mà tôi thấy thật kỳ quặc, có lẽ tôi đã hiểu đúng về điều này.

Tôi muốn có một giao dịch xung quanh mức phương thức và tôi có một phương thức người gọi trong cùng một lớp và có vẻ như nó không giống như vậy, nó phải được gọi từ lớp riêng biệt. Tôi không hiểu làm thế nào mà có thể được.

Nếu bất cứ ai có ý tưởng làm thế nào để giải quyết vấn đề này, tôi sẽ đánh giá rất cao. Tôi muốn sử dụng cùng một lớp để gọi phương thức giao dịch có chú thích.

Đây là mã:

public class UserService {

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
            // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                    .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            addUser(user.getUserName, user.getPassword);
        }
    } 
}

Hãy nhìn vào TransactionTemplatecách tiếp cận: stackoverflow.com/a/52989925/355438
Lu55

Về lý do tại sao tự gọi không hoạt động, hãy xem 8.6 Cơ chế ủy quyền .
Jason Law

Câu trả lời:


99

Đó là một hạn chế của Spring AOP (đối tượng động và cglib ).

Nếu bạn định cấu hình Spring để sử dụng AspectJ để xử lý các giao dịch, mã của bạn sẽ hoạt động.

Cách thay thế đơn giản và có lẽ tốt nhất là cấu trúc lại mã của bạn. Ví dụ một lớp xử lý người dùng và một lớp xử lý từng người dùng. Sau đó, xử lý giao dịch mặc định với Spring AOP sẽ hoạt động.


Mẹo cấu hình để xử lý giao dịch với AspectJ

Để cho phép Spring sử dụng AspectJ cho các giao dịch, bạn phải đặt chế độ thành AspectJ:

<tx:annotation-driven mode="aspectj"/>

Nếu bạn đang sử dụng Spring với phiên bản cũ hơn 3.0, bạn cũng phải thêm cái này vào cấu hình Spring của mình:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>

Cảm ơn bạn đã thông tin. Tôi đã cấu trúc lại mã ngay bây giờ, nhưng bạn có thể vui lòng gửi cho tôi một ví dụ bằng cách sử dụng AspectJ hoặc cung cấp cho tôi một số liên kết hữu ích. Cảm ơn trước. Mike.
Mike

Đã thêm cấu hình AspectJ giao dịch cụ thể trong câu trả lời của tôi. Tôi hy vọng nó sẽ giúp.
Espen

10
Thật tốt! Btw: Sẽ rất tuyệt nếu bạn có thể đánh dấu câu hỏi của tôi là câu trả lời tốt nhất để cho tôi một số điểm. (dấu kiểm màu xanh lá cây)
Espen

2
Cấu hình khởi động mùa xuân: @EnableTransactionManagement (mode = ConsultMode.ASPECTJ)
VinyJones

64

Vấn đề ở đây là, các proxy AOP của Spring không mở rộng mà thay vào đó là bọc phiên bản dịch vụ của bạn để chặn các cuộc gọi. Điều này có hậu quả là bất kỳ lệnh gọi nào tới "this" từ bên trong phiên bản dịch vụ của bạn đều được gọi trực tiếp trên phiên bản đó và không thể bị chặn bởi proxy gói (proxy thậm chí không biết về bất kỳ lệnh gọi nào như vậy). Một giải pháp đã được đề cập. Một cách tiện lợi khác là chỉ cần để Spring đưa một phiên bản của dịch vụ vào chính dịch vụ đó và gọi phương thức của bạn trên phiên bản được tiêm vào, đó sẽ là proxy xử lý các giao dịch của bạn. Nhưng hãy lưu ý, điều này cũng có thể có tác dụng phụ xấu, nếu hạt dịch vụ của bạn không phải là một hạt đơn:

<bean id="userService" class="your.package.UserService">
  <property name="self" ref="userService" />
    ...
</bean>

public class UserService {
    private UserService self;

    public void setSelf(UserService self) {
        this.self = self;
    }

    @Transactional
    public boolean addUser(String userName, String password) {
        try {
        // call DAO layer and adds to database.
        } catch (Throwable e) {
            TransactionAspectSupport.currentTransactionStatus()
                .setRollbackOnly();

        }
    }

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            self.addUser(user.getUserName, user.getPassword);
        }
    } 
}

3
Nếu bạn chọn đi theo hướng này (cho dù đây là thiết kế tốt hay không là một vấn đề khác) và không sử dụng constructor injection, chắc chắn bạn cũng thấy câu hỏi này
Jeshurun

Điều gì xảy ra nếu UserServicecó phạm vi singleton? Nếu nó là cùng một đối tượng thì sao?
Yan Khonski

26

Với Spring 4, có thể Tự động tự động

@Service
@Transactional
public class UserServiceImpl implements UserService{
    @Autowired
    private  UserRepository repository;

    @Autowired
    private UserService userService;

    @Override
    public void update(int id){
       repository.findOne(id).setName("ddd");
    }

    @Override
    public void save(Users user) {
        repository.save(user);
        userService.update(1);
    }
}

2
CÂU TRẢ LỜI TỐT NHẤT !! Thx
mjassani

2
Hãy sửa cho tôi nếu tôi sai nhưng một mô hình như vậy thực sự dễ xảy ra lỗi, mặc dù nó hoạt động. Nó giống như một buổi giới thiệu các khả năng của Spring, phải không? Ai đó không quen thuộc với hành vi "cuộc gọi bean này" có thể vô tình xóa bean tự động tự động (sau cùng là các phương pháp có sẵn thông qua "this"), điều này có thể gây ra sự cố mà thoạt nhìn khó phát hiện. Nó thậm chí có thể đến được môi trường sản xuất trước khi nó được tìm thấy).
pidabrow

2
@pidabrow bạn nói đúng, đó là một kiểu chống đối rất lớn và nó không rõ ràng ngay từ đầu. Vì vậy, nếu bạn có thể, bạn nên tránh nó. Nếu bạn phải sử dụng phương pháp cùng lớp sau đó cố gắng sử dụng các thư viện AOP mạnh hơn như AspectJ
Almas Abdrazak

21

Bắt đầu từ Java 8 có một khả năng khác, mà tôi thích hơn vì những lý do được đưa ra dưới đây:

@Service
public class UserService {

    @Autowired
    private TransactionHandler transactionHandler;

    public boolean addUsers(List<User> users) {
        for (User user : users) {
            transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
        }
    }

    private boolean addUser(String username, String password) {
        // TODO
    }
}

@Service
public class TransactionHandler {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T runInTransaction(Supplier<T> supplier) {
        return supplier.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T runInNewTransaction(Supplier<T> supplier) {
        return supplier.get();
    }
}

Cách tiếp cận này có những ưu điểm sau:

1) Nó có thể được áp dụng cho các phương pháp riêng tư . Vì vậy, bạn không cần phải phá vỡ tính đóng gói bằng cách đặt một phương thức ở chế độ công khai chỉ để đáp ứng các giới hạn của Spring.

2) Có thể gọi cùng một phương thức trong quá trình truyền giao dịch khác nhau và tùy thuộc vào người gọi để chọn phương thức phù hợp. So sánh 2 dòng này:

transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));

3) Nó rõ ràng, do đó dễ đọc hơn.


Điều đó thật tuyệt! Nó tránh tất cả những cạm bẫy mà Spring giới thiệu với chú thích của nó. Yêu nó!
Frank Hopkins

Nếu tôi mở rộng TransactionHandlernhư một lớp con và lớp con gọi hai phương thức này trong TransactionHandlerlớp siêu, liệu tôi có thể nhận được những lợi ích @Transactionalnhư dự định không?
tom_mai78101

6

Đây là giải pháp của tôi để tự gọi :

public class SBMWSBL {
    private SBMWSBL self;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void postContruct(){
        self = applicationContext.getBean(SBMWSBL.class);
    }

    // ...
}

0

Bạn có thể tự động tải BeanFactory trong cùng một lớp và thực hiện

getBean(YourClazz.class)

Nó sẽ tự động hỗ trợ lớp của bạn và tính đến @Transactional hoặc chú thích aop khác của bạn.


2
Nó được coi là một thực hành xấu. Ngay cả việc tiêm bean đệ quy vào chính nó cũng tốt hơn. Sử dụng getBean (clazz) là một kết hợp chặt chẽ và phụ thuộc mạnh mẽ vào các lớp ApplicationContext mùa xuân bên trong mã của bạn. Ngoài ra, việc lấy đậu theo lớp có thể không hoạt động trong trường hợp gói đậu vào mùa xuân (lớp có thể bị thay đổi).
Vadim Kirilchuk

0

Vấn đề liên quan đến cách các lớp và proxy tải mùa xuân. Nó sẽ không hoạt động, cho đến khi bạn viết phương thức / giao dịch bên trong của mình trong một lớp khác hoặc chuyển đến lớp khác rồi lại đến lớp của bạn và sau đó viết phương thức chuyển đổi lồng nhau bên trong.

Tóm lại, các proxy mùa xuân không cho phép các tình huống mà bạn đang gặp phải. bạn phải viết phương thức giao dịch thứ 2 trong lớp khác


0

Đây là những gì tôi làm cho các dự án nhỏ với việc chỉ sử dụng các lệnh gọi phương thức trong cùng một lớp. Tài liệu trong mã được khuyến khích thực hiện, vì nó có thể trông lạ đối với đồng nghiệp. Nhưng nó hoạt động với các singleleton , dễ kiểm tra, đơn giản, nhanh chóng đạt được và giúp tôi có được nhạc cụ AspectJ đầy đủ. Tuy nhiên, để sử dụng nhiều hơn, tôi khuyên bạn nên giải pháp AspectJ như được mô tả trong câu trả lời Espens.

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }

    @Transactional
    public void addUser(String username, String password) {
        // call database layer
    }

    public void addUsers(List<User> users) {
        for (User user : users) {
            _personDao.addUser(user.getUserName, user.getPassword);
        }
    }
}
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.