views:

184

answers:

3

I am new to Spring Transaction. Some thing that I found really odd, probably I did understand this properly. I wanted to have a transactional around method level and I have a caller method within the same class and it seems like it does not like that, it has to be called from the separate class. I don't understand how is that possible. If anyone has an idea how to resolve this issue, I would greatly appreciate. I would like to use the same class to call the annotated transactional method.

Thanks in advance, Mike

Here is the code:

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);
        }
    } 
}
+1  A: 

first of all, you should label this java, spring!

putting an annotation on a method will force spring to intercept each call to the addUser method of your service class.

if you are making a call within the service, spring is not aware of it, so you have to put one around that too.

usually you put @Transactional annotations at the end of your transaction, which might be a webservice-method or something like that, so you can rollback each change during that call.

phoet
Thanks for the information. I don't want to add @Transactional annotations at the end because it will rollback all the user information in the List. I want to rollback only those failed to process the database, and keep on processing..Please let me know if it does not make sense. Thanks in advance.Mike
Mike
no, that does not make sense. you can only roll back ALL changes within one transaction. that is exactly what a transaction is supposed to be!if you don't want to do this, you have to put the transaction where you want it to be rolled back, but i would discourage using that kind of approach.you should build something that is recoverable from a rollback and use something like devide and conquer to find the malicious data if you are working on batches.
phoet
+2  A: 

It's an limitation with Spring AOP. (dynamic objects and CGLIB)

If you configure Spring to use AspectJ to handle the transactions, your code will work.

The simple and probably best alternative is to refactor your code. For example one class that handles users and one that process each user. Then default transaction handling with Spring AOP will work.

Configuration tips for handling transactions with AspectJ

To enable Spring to use AspectJ for transactions, you must set the mode to AspectJ:

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

If you're using Spring with an older version than 3.0, you must also add this to your Spring configuration:

<bean class="org.springframework.transaction.aspectj
        .AnnotationTransactionAspect" factory-method="aspectOf">
    <property name="transactionManager" ref="transactionManager" />
</bean>
Espen
Thank you for the information. I refactored the code for now, but could you please send me an example using AspectJ or provide me with some helpful links. Thanks in advance. Mike.
Mike
Added transaction specific AspectJ configuration in my answer. I hope it helps.
Espen
Thank you Espen for all your help. It works!!
Mike
That's good! Btw: It would be nice if you can mark my question as the best answer to give me some points. (green checkmark)
Espen
A: 

The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call). One solutions is already mentioned. Another nifty one would be to simply have Spring inject an instance of the service into the service itself, and call your method on the injected instance, which will be the proxy that handles your transactions. But be aware, that this may have bad side effects too, if your service bean is not a singleton:

<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);
        }
    } 
}
Kai