AbstractRoutingDataSource doesn't change connection - java

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.

Related

Why transactional does not rollback when RuntimeException occur?

I want to test a non-transactional method in the service layer when inner methods are transactional separately. I want to know what will happen if some method throws an exception. Does it properly roll back or not? I also tried rollbackFor but it did not help. Any suggestions?
Spring v-2.5.1
app.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create-drop
server.error.include-message=always
server.error.include-binding-errors=always
Controller
#PostMapping("/test")
public void test(){
service.test();
}
Service
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
public void test() {
User user1 = new User();
String encodedPassword = passwordEncoder.encode("123");
user1.setUsername("username");
user1.setPassword(encodedPassword);
user1.setFirst_name("name1");
user1.setLast_name("family1");
save(user1);
User update = update(user1.getUsername());
throw new RuntimeException();// I expect Spring rollback all data for save and update methods
//but it seems data has been committed before an exception occur.
}
#Transactional
public void save(User user) {
if (userExists(user.getUsername()))
throw new ApiRequestException("Username has already taken!");
else {
User user1 = new User();
String encodedPassword = passwordEncoder.encode(user.getPassword());
user1.setUsername(user.getUsername());
user1.setPassword(encodedPassword);
user1.setFirst_name(user.getFirst_name());
user1.setLast_name(user.getLast_name());
user1.setCreate_date(user.getCreate_date());
user1.setModified_date(user.getModified_date());
user1.setCompany(user.getCompany());
user1.setAddresses(user.getAddresses());
userRepository.save(user1);
// throw new RuntimeException(); Similarly I expect rollback here.
}
}
#Transactional
public User update(String username) {
User userFromDb = userRepository.findByUsername(username);
userFromDb.setFirst_name("new username");
userFromDb.setLast_name("new lastname");
return userRepository.save(userFromDb);
}
}
Due to way spring proxying works by default, you can not call a "proxied" method from within the instance.
Consider that when you put #Transactional annotation on a method, Spring makes a proxy of that class and the proxy is where the transaction begin/commit/rollback gets handled. The proxy calls the actual class instance. Spring hides this from you.
But given that, if you have a method on the class instance (test()), that calls another method on itself (this.save()), that call doesn't goes through the proxy, and so there is no "#Transactional" proxy. There is nothing to do the rollback when the RuntimeException occurs.
There are ways to change the how Spring does the proxying which would allow this to work, but it has changed over the years. There are various alternatives. One way is to create create separate classes. Perhaps UserServiceHelper. The UserServiceHelper contains #Transactional methods that get called by UserService. This same issue occurs when different #Transactional isolations and propagations are needed.
Related answers and info:
Spring #Transaction method call by the method within the same class, does not work?
Does Spring #Transactional attribute work on a private method?
Spring Transaction Doesn't Rollback
Your example code is not very clear as to what you are trying to do. Often, #Transactional would be put on the service class and apply to all public methods (like test()).
To begin with, I'm not sure if you understand what a transaction represents. If there's a unit of work that contains several functionalities to be executed, and you either want them to completely fail or completely pass - that's when you use a transaction.
So in the case of test() method - why should underlying mechanism (in this case Hibernate) care about rolling back something that happened within save() and update() when the test() itself isn't a transaction?
Sorry for asking more than answering, but as far as I can see - you need to annotate test() with the annotation, and not the individual methods. Or you can annotate them all.

#Transactional only on one method

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.

Spring Cache - #CacheEvict, #CachePut not working while calling from the another method from the same class

Spring cache is not working when calling cached method from another method of the same class.
Here is an example to explain my problem in clear way.
Cached service class:
class testServiceImpl{
#CachePut(key = "#result.id", condition = "#result != null")
public List<String> create(String input) {
......
}
#CacheEvict(key="#id")
public void deleteById(Long id) {
.....
}
public void multiDelete(String input) {
if(condition...){
deleteById(2); //Cache is not Evicted here i.e. the records are still present in getAll call but not in Database.
}else{
create(input); //New Data is persisted in DB but the same has not been updated in Cache.
}
#Transactional
#Cacheable
public Map<Long, String> getAll() {
...
}
I have tried using the below solutions as well but could not succeed.
//Create a new object of the same class and use the same. In this case, the data is not persisted in DB i.e. it is not deleting the data from DB.
testServiceImpl testService;
...
public void multiDelete(String input) {
if(condition...){
testService.deleteById(2);
}else{
testService.create(input);
}
Can someone please help me to solve this issue ?
When you call a method from your service, you are actually calling it through a proxy. The autowired bean is wrapped in a proxy that intercepts the call and handles the cache annotations only for that method.
When you make the call internally, it is made directly on the service object, and without the proxy wrapper, the cache annotation is not processed.
See Understanding AOP Proxies
A working alternative is to use AspectJ that will weave the spring aspects that handle cache annotations directly into the code without using any Spring proxies, so you can call internal methods and cache annotation will be handled as expected.

Creating a post commit when using transaction in Spring

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

How do you refactor a #Transactional method to split out non-transactional parts

I have a data access class which runs as part of a stand-alone java application. It is currently working which means that a transaction manager is defined but I want to refactor the class to reduce the scope of the transaction but if I do I get org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here which implies that moving the #Transactional has somehow stopped it from being recognised.
My original version had the refactored methods being private but I found a recommendation to change that to public as in some cases the annotation would not be picked up.
public class DoStuff {
#Transactional
public void originalMethod() {
// do database stuff
...
// do non-database stuff that is time consuming
...
}
}
What I want to do is refactor to the following
public class DoStuff {
public void originalMethod() {
doDatabaseStuff()
doNonDatabaseStuff()
}
#Transactional
public void doDatabaseStuff() {
...
}
public void doNonDatabaseStuff() {
...
}
}
Edit:
You need to understand how Spring proxying works to understand why your refactoring does not work.
Method calls on the object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, any method calls that it may make on itself, are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.
#Transactional uses Spring AOP, Spring uses proxies. This means that when you call an #Transactional method from another class, Spring will use a proxy, so the transactional advice will be applied. However, if you call the method from the same class, spring will use the "this" reference instead of the proxy, so that transactional advice will not be applied.
Original Answer:
Here is what worked for me in similar scenario.
public class DoStuff implement ApplicationContextAware {
private ApplicationContext CONTEXT;
public void setApplicationContext(ApplicationContext context) throws BeansException {
CONTEXT = context;
}
public void originalMethod() {
getSpringProxy().doDatabaseStuff()
doNonDatabaseStuff()
}
private DoStuff getSpringProxy() {
return context.getBean(this.getClass());
}
#Transactional
public void doDatabaseStuff() {
...
}
public void doNonDatabaseStuff() {
...
}
}
Explanation:
Make the class ApplicationContextAware, so it has a reference to the context
When you need to call a transactional method, fetch the actual spring proxy from the context
Use this proxy to call your method, so that #Transactional is actually applied.
Your approach looks like it should work just fine, I expect the issue is related to Spring proxies.
The reason that I asked about interfaces is related to the default method by which Spring applies transactional behaviour - JDK dynamic proxies.
If the actual definition of your class is:
public class DoStuff implements Doable {
public void originalMethod() {
}
}
public interface Doable {
public void originalMethod();
}
If this is indeed the structure, when you moved to the new structure Spring is not able to proxy the new doDatabaseStuff method.
Your options to fix this:
Add the new methods to your interface to ensure that Spring can proxy them
Move to using CGLIB based proxies (these do not rely on interfaces)

Categories