I have a Service class like below:
#Service("MyService")
public class MyService {
#Autowired
MyDao dao;
public void process() {
getFromDao();
// getMoreFromDao();
// process();
// if all good, then
doStuff();
}
public void getFromDao() {
// do some stuff
dao.getData();
}
#Transactional(transactionManager="simpleDatasourceTxMgr", propagation=Propagation.REQUIRED)
public void doStuff() {
dao.saveData(1);
dao.saveData(2);
dao.saveData(3);
}
}
The DAO called is:
#Repository
public class MyDao {
#Autowired
#Qualifier("myjdbcTemplate")
NamedParameterJdbcTemplate jdbcTemplate;
public void saveData(obj a) {
jdbcTemplate.execute("Query", ...);
}
}
I want my doStuff() method in the service class to run within a transaction and rollback everything if there is an exception in the saveData() method. But this is not running in transaction.
If I add #Transaction to a DAO method looks like it runs in separate transaction. Is this correct?
Update: I have added a process() method to my Service and I call getFromDao() and doStuff() from process(). process() is called from the controller. So looks like if I make the service class #Transactional, then everything executes within a transaction. But I don't want getFromDao() to execute in transaction.
We use just JDBC and no Hibernate.
You can place the #Transactional annotation before an interface
definition, a method on an interface, a class definition, or a public
method on a class. However, the mere presence of the #Transactional
annotation is not enough to activate the transactional behavior. The
#Transactional annotation is simply metadata that can be consumed by
some runtime infrastructure that is #Transactional-aware and that can
use the metadata to configure the appropriate beans with transactional
behavior. In the preceding example, the
element switches on the transactional behavior.
Or if you want annotations you can enable it with
It is not sufficient to tell you simply to annotate your classes with
the #Transactional annotation, add #EnableTransactionManagement to
your configuration, and then expect you to understand how it all
works. This section explains the inner workings of the Spring
Frameworkâs declarative transaction infrastructure in the event of
transaction-related issues.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html
Related
I need to make a #Scheduled method that has a list of schemas and for each schema, deletes rows from 2 tables.
#Scheduled(fixedDelay = 10000)
public void scheduleFixedDelayTask() {
List<String> presentSchemas = getPresentSchemas();
for (String schema : presentSchemas) {
deleteFromCustomerTables(schema);
}
}
I've defined deleteFromCustomerTables as #Transactional(propagation = Propagation.REQUIRES_NEW) and inside it i use the EntityManager to delete rows from 2 tables.
In order to make it work i need to add #Transactional to scheduleFixedDelayTask, otherwise i recive a TransactionRequiredException.
My problem is that i do not want the whole scheduler to be #Transactional, if something goes wrong in one schema i do not want to do a rollback of all schemas.
I've also tried without #Transactional and with :
Session session = entityManager.unwrap(Session.class);
Transaction t = session.beginTransaction();
//exec delete
t.commit();
session.close();
But i still recieve TransactionRequiredException.
Do you have a solution?
You have to be sure that you configured the transaction manager something like this:
#Configuration
#EnableTransactionManagement
public class TransactionConfig {
#Bean
#Primary
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
You have to be sure that method deleteFromCustomerTables is not in the same component, something like this:
#Component
class Component1 {
void scheduleFixedDelayTask(){...}
}
#Component
class Component2 {
void deleteFromCustomerTables(){...}
}
But at a very high level, Spring creates proxies for classes that
declare #Transactional on the class itself or on members. The proxy is
mostly invisible at runtime. It provides a way for Spring to inject
behaviors before, after, or around method calls into the object being
proxied. Transaction management is just one example of the behaviors
that can be hooked in. Security checks are another. And you can
provide your own, too, for things like logging. So when you annotate a
method with #Transactional, Spring dynamically creates a proxy that
implements the same interface(s) as the class you're annotating. And
when clients make calls into your object, the calls are intercepted
and the behaviors injected via the proxy mechanism.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
#Transactional
public void save(String name) {
method1(name);
method2(name);
}
public void method1(String name){
userRepository.save(name)
}
public void method2(String name) {
roleRepository.save(name);// Error
}
}
public interface UserService {
void save(String name) throws Exception;
}
#PostMapping("/save")
public void save() throws Exception {
userService.save("SomeThing");
}
I have 2 methods in the save method. As you can see, the method2 has an error.
I run the program, the first method prints to the database. However, if the second method is incorrect, I want the rollback process to fall and the first method not to be written to the database. How can I do it?
I tried to use #Transaction annotation on method1 and method2 but it error continued. And i tried Propagation.REQUIRES_NEW, and Propagation.REQUIRED, TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); never changed.
This is totally logical what you are trying to do. Just rollingback when one part of your flow fails is normal.
I'll give more background to make sure you have everything:
#Transactional only rollback, by default, when a RuntimeException is thrown. You can customize this. Check the doc, it is very easy to find out. Be careful, make sure you use org.springframework.transaction.annotation.Transactional. You have more options with this one.
Understanding propagation is key. By default, the propagation of #Transactional is Required, which means it will either joins an existing transaction, or creates a new one. Requires_New always create a new one. In your case, Propagation.REQUIRED is the good one.
Once you are in a Transactional method, adding #Transactional on other method inside your class and make inner call won't have any effect. The transaction starts when you enter in your method from the outside of your class. Once you in your class, the others annotations won't affect the runtime.
Three things/questions to consider:
What kind of error is throwing the RoleRepository.save(...) ?
What is the context ? Is it a test ? Is it a running application ? Could you provide more code ?
As said in one comment, read this: Spring Transaction Management with Hibernate and MySQL, Global and Local. You could have some issues with that as well...
I use AbstractRoutingDataSource to change data source dynamically and ThreadLocal to set up currentLookupKey. It works nice when I use only one data source per http request. I use JpaRepository
#Component
#Primary
public class RoutingDataSource extends AbstractRoutingDataSource {
#Autowired
private DatabaseMap databaseMap;
#Override
public void afterPropertiesSet() {
setTargetDataSources(databaseMap.getSourcesMap());
setDefaultTargetDataSource(databaseMap.getSourcesMap().get("DEFAULT"));
super.afterPropertiesSet();
}
#Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getDatabaseType();
}
}
public class DatabaseContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDatabaseType(String string) {
contextHolder.set(string);
}
public static String getDatabaseType() {
return (String) contextHolder.get();
}
public static void clearDatabaseType() {
contextHolder.remove();
}
}
When I try to get data in my REST controller I get data only from one database.
Some code in my REST controller
DatabaseContextHolder.setDatabaseType("db1");
//here I get data from db1 as expected
//I use JpaRepository
DatabaseContextHolder.clearDatabaseType();
DatabaseContextHolder.setDatabaseType("db2");
//here I should get data from db2 but get from db1
I tried to debug and it looks like Spring obtains data source only once in http request.
This method is called only once.
#Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
Is there any way to force Spring to change data source.
Your problem could be related with transaction delimitation.
When you define a #Transactional annotation in your code, Spring will create on your behalf all the stuff necessary to begin and end, and commiting or rollback if required, a transaction.
As you can see in the doBegin method in the source code of the DataSourceTransactionManager class - the same applies to other transaction managers - , Spring obtains a Connection when the transaction is initialized - this is why the method getConnection is invoked only once - , and it will reuse that connection across all the underlying operations against the database within that transaction (it makes sense for ACID preservation).
So, if you need to connect to several data sources within the same request processing, you can define different methods in you service code, every one annotated with a #Transactional annotation, and change the underlying data source as you require before invoke them:
DatabaseContextHolder.setDatabaseType("db1");
// Invoke a service method annotated with #Transactional
// It can use the underlying JpaRepositories that you need
DatabaseContextHolder.clearDatabaseType();
DatabaseContextHolder.setDatabaseType("db2");
// Invoke again another (or the same, what you need) service method
// annotated with #Transactional, You should get data from db2 this time
My suspicion here is you have a method annotated with #Transactional annotation. Before calling that transactional method, you first specify one datasource key and you call the transactional method.Inside the transactional method, you first call repository and it works as expected with datasource look up key you set. However then you set different key, inside the transactional method, and call another repository and it still uses the key you set first time.
DataSource will be chosen by the framework when the transaction starts so if you are using #Transactional annotation, whatever switching you do inside the method is useless. Because the datasource would have been chosen by proxy created for #Transactional annotation. Best option is to have the branching logic in non transactional service or use TransactionTemplate instead of #Transactional
For example, make sure YourRestController does not have class level #Transactional as well as no #Transactional annotation in this yourRestControllerMethod, you will keep them to your service.
#RestController
public class YourRestController {
#Autowired
TransactionalService transactional
public void yourRestControllerMethod(){
//set the datasource look up key to A1 and then
transactional.methodA1();
//change datasource look up key to A2 and then
transactional.methodA2();
}
}
#Service
public class TransactionalService {
#Transactional
public void methodA1(){
}
#Transactional
public void methodA2() {
}
}
I had the same issue, none of the above solution could fix it.. but making my Service method final (in my REST Controller)
public final Response
Set spring.jpa.open-in-view to false.
I don't get how it works the transactional annotations of Spring. So I made the next test with no practical sense but I It shows my problem:
public class TransactionalTest {
public void noTransaction(){
required();
}
#Transactional(propagation = Propagation.SUPPORTS)
public void supports(){
required();
}
#Transactional(propagation = Propagation.REQUIRED)
public void transaction(){
required();
}
#Transactional(propagation = Propagation.REQUIRED)
public void required(){
mandatory();
}
#Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.MANDATORY)
public void mandatory(){
doSomething();
}
private void doSomething(){
//I don't feel like to do something.
}
}
Methods noTransaction, supports and transaction call to the same method: required but only the last one (transaction) works properly. The two others give me back the message No existing transaction found for transaction marked with propagation 'mandatory'.
In the real case, I have some non transactional methods which calls to transactional methods annotated with REQUIRED (It works fine). but if a non transactional method calls to a transactional method (annotated with REQUIRED) which it calls to another transactional method annoted with MANDATORY, it fails.
Why this behaviour and how can avoid it? I annotated annotate all the method which calls to a transaccional method just in case?
If you're using the default AOP method, Spring Proxy AOP, the advice is implemented by wrapping the annotated class with a proxy object that intercepts and advises the calls. When you use self-calls like this, you're bypassing the proxy, and the advice doesn't get applied.
If you really do need to have advice applied on self-calls, you need to use AspectJ AOP, which actually modifies the class in question instead of decorating it.
Due to certain reasons i have manually performed transaction commit and roll back using Spring PlatformTransactionManager, what i need to do is setup a hook so that a post commit action takes place after transaction has been committed.
By looking at:
void commit(TransactionStatus status) throws TransactionException;
I cant see how i can determine a transaction was successful other than assumming it so if no expception are thrown.
And i could use AOP as one option, but what about programmitcally doing it, maybe using callback method?
You could get exactly what you want by a simpler way, with TransactionSynchronizationManager and TransactionSynchronization
With TransactionSynchronizationManager, you have static methods to get information about current transaction, and you can register a TransactionSynchronization wich allows you to automatically do a post-commit as you call that
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
void afterCommit(){
//do what you want to do after commit
}
})
Be aware that the TransactionSynchronization is on a per-thread basis (which is often not a problem for a basic web request).
Credit to Grooveek's answer and Alex's comment under it - I put this here because the combined suggestions provide a solid and cleaner solution that is hard to find around the net.
Using Spring 4+. if you need a callback on a #Transactional method after it successfully commits just add that in the beginning of the method:
#Service
public class OneService {
#Autowired
OneDao dao;
#Transactional
public void a transactionalMethod() {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
public void afterCommit(){
//do stuff right after commit
System.out.println("commit!!!");
}
});
//do db stuff
dao.save();
}
}
Since Spring 4.2 it has been possible to define listeners for post commit events (or more generally transaction synchronization events e.g. rollbacks) using annotation based configuraton. This is based off event handling in core spring. It is easier to test code using this approach since you avoid a direct dependency on TransactionSynchronizationManager, which will likely not be active in a unit test. You can easily test that your transactional service publishes an event and also that your listener performs the right action when you receive an event.
So without further ado this is how to set it up:
In this example we'll assume you have a Customer entity and a CustomerRepository (and ORM to go with it).
First you need a new event type NewCustomerEvent:
// NewCustomerEvent.java
// Just a regular pojo for the event
public class NewCustomerEvent {
public String email;
// constructor, setters and getters omitted
}
Then you define a listener using #TransactionalEventListener. By default this will execute after a sucessful commit but this can be changed using the phase parameter:
// NewCustomerEventListener.java
#Component
public class NewCustomerEventListener {
#TransactionalEventListener
public void handleNewCustomerEvent(NewCustomerEvent newCustomerEvent) {
// handle new customer event
}
}
Finally you augment your transaction service with an ApplicationEventPublisher on which you call publish once all the transaction statements have been sent.
// CustomerRespositoryService.java
#Service
public class CustomerRepositoryService {
#Inject
private ApplicationEventPublisher applicationEventPublisher;
#Inject
private CustomerRepository customerRepository;
#Transactional
public void createCustomer(String email) {
Customer customer = new Customer(email);
customerRespotory.save(customer);
applicationEventPublisher.publish(new NewCustomerEvent(email));
}
}
See also:
https://dzone.com/articles/simpler-handling-of-asynchronous-transaction-bound
https://dzone.com/articles/transaction-synchronization-and-spring-application
In one of my projects because of certain reasons I also had to use PlatformTransactionManager. So I forced to use org.springframework.transaction.support.TransactionTemplate.
http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/transaction/support/TransactionTemplate.html
The main benefit is that if you have implemented PlatformTransactionManager correctly, you don't need to bother with manual commit/rollback. At least source code of TransactionTemplate may help you if you need more specific thing.
It's pretty simply to use:
config.xml
<bean name="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="platformTransactionManager"/>
</bean>
MyServiceImpl.java
#Service
public class MyServiceImpl implements MyService {
#Autowired
private TransactionTemplate transactionTemplate;
public Entity getSomethingWithTx(final long id) {
return transactionTemplate.execute(new TransactionCallback<Entity>() {
#Override
public Entity doInTransaction(TransactionStatus status) {
//TODO implement
}
});
}