I found this question when researching the transaction behavior of data-rest.
I needed control over the transaction behavior of the default controllers, especially during serialization of relationships, so i could mark transactions as read-only and route them to read-replicas of our database.
By default every call to a repository has its own transaction and database access during events or resource-assembly is managed by hibernate directly, outside of these transactions. There is no intended way for users to control transactions.
Spring does however provide methods to do that using aop-interceptors. These interceptors use a property source do decide weather or not a method requires a transaction. Springs own Transactional-Annotation is nothing more than one of these property-sources.
The following Configuration creates an advisor for the default rest-controllers that creates customized transactions for some Methods:
@Configuration
@ConditionalOnClass(name = {
"org.springframework.data.rest.webmvc.RepositoryEntityController",
"org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController",
"org.springframework.data.rest.webmvc.RepositorySearchController"
})
public class RepositoryRestControllerTransactionConfiguration {
/**
* A custom Transaction Advisor that starts customized transactions based on what data-rest controller method is called
*
* @param transactionManager
* @return
*/
@Bean
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionManager transactionManager) {
//Specify transaction management methods and attribute values required for transaction control
MethodMapTransactionAttributeSource source = new MethodMapTransactionAttributeSource();
Map<String, TransactionAttribute> methodMap = new HashMap<>();
// methods are identified by their FQDN, simple placeholders using * are possible
// methods that should only require reads
methodMap.put("org.springframework.data.rest.webmvc.RepositoryEntityController.get*", createTransactionRule(true));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryEntityController.headForItemResource", createTransactionRule(true));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController.follow*", createTransactionRule(true));
methodMap.put("org.springframework.data.rest.webmvc.RepositorySearchController.execute*", createTransactionRule(true));
// methods that will require write
methodMap.put("org.springframework.data.rest.webmvc.RepositoryEntityController.put*", createTransactionRule(false));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryEntityController.patch*", createTransactionRule(false));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryEntityController.delete*", createTransactionRule(false));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryEntityController.post*", createTransactionRule(false));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController.delete*", createTransactionRule(false));
methodMap.put("org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController.create*", createTransactionRule(false));
source.setMethodMap(methodMap);
source.afterPropertiesSet();
// Generate an AOP Advisor that controls transactions
// Advice for transaction control (TransactionInteceptor)
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(source);
advisor.setAdvice(new TransactionInterceptor(transactionManager, source));
return advisor;
}
private RuleBasedTransactionAttribute createTransactionRule(boolean readOnly) {
RuleBasedTransactionAttribute rule = new RuleBasedTransactionAttribute();
rule.setReadOnly(readOnly);
return rule;
}
}