My problem is simple, I want to rollback all the db changes that were caused by invocation of my service method. I have a problem with correct annotating. I'm not really sure where to put #Transactional and where should I set Propagation to REQUIRES_NEW or MANDATORY. I will paste a code that I wrote, which apart from the fact that SQLException is thrown does not rollback db changes.
The code:
#Service
public class SwapInsertService {
#Autowired
MyDao myDao;
#Transactional(propagation = Propagation.REQURIES_NEW, rollbackFor = SQLException.class)
public void insertToManyTables(MyData data) throws SQLException {
insertToSpecificTables(data);
myDao.insertTwo(data);
}
#Transactional(propagation = Propagation.MANDATORY)
public void insertToSpecificTables(MyData data) throws SQLException {
myDao.insertOne(data);
}
}
#Repository
public class MyDaoImpl implements MyDao {
#Transactional(propagation = Propagation.MANDATORY)
public void insertOne(MyData data) throws SQLException {
// INSERT
}
#Transactional(propagation = Propagation.MANDATORY)
public void insertTwo(MyData data) throws SQLException {
// throws SQLException
}
}
Let's assume insertTwo throws SQLException . I would like to rollback all the previous inserts. What am I doing wrong?
My understanding:
Propagation.REQURIES_NEW creates new transaction.
Propagation.MANDATORY supports current transaction.
insertTwo throws error, insertToManyTables rollbacks everything and voilĂ . Unfortunately it is not that easy.
EDIT: It's important to mention that i'm using CGLIB for proxies.
Check whether AUTO_COMMIT is set to false in data source configuration in spring specific xml
Related
My project has Open session in view enabled and we cannot disable it at this point. We are using spring boot.
I have a method that does db read calls and rest calls. I do NOT need transactional boundary over this method. How do I disable the transaction in this method. I have tried using
#Transaction(propagation=Propagation.NEVER
and
#Transaction(propagation=Propagation.NOT_SUPPORTED)
But the transaction still seems to exist. I know this because there are some lazily loaded relationships that gets loaded if I access them with in this method.
public void doSomething() {
makeDbCall();
makeRestCall();
makeDbCallAgain();
}
The easiest and safest way that I can think of is to customize the OpenEntityManagerInViewInterceptor to just exclude the request that you do not want to apply OSIV on it. Something like :
public class MyOpenEntityManagerInViewInterceptor extends OpenEntityManagerInViewInterceptor {
private boolean isExcludeOsiv(WebRequest request){
//refer to https://stackoverflow.com/questions/51921635/how-to-get-requests-uri-from-webrequest-in-spring for getting the URL path from the WebRequest for checking
}
#Override
public void preHandle(WebRequest request) throws DataAccessException {
if(isExcludeOsiv(request)){
return ;
}
super.preHandle(requset);
}
#Override
public void afterCompletion(WebRequest request, #Nullable Exception ex) throws DataAccessException {
if(isExcludeOsiv(request)){
return ;
}
super.afterCompletion(requset,ex);
}
}
And use the following configuration to disable the default OSIV setting provided by spring-boot but use this customised OpenEntityManagerInViewInterceptor instead :
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addWebRequestInterceptor(new MyOpenEntityManagerInViewInterceptor());
}
}
I want to do some DB related actions in service method. Initialy it looks like this:
#Override
#Transactional
public void addDirectory(Directory directory) {
//some cheks here
directoryRepo.save(directory);
rsdhUtilsService.createPhysTable(directory);
}
Firs method directoryRepo.save(directory); is just simple JPA save action, second one rsdhUtilsService.createPhysTable(directory); is JDBCTemplate stored procedure call from it's own service. The problem is: if any exceptions accures within JPA or SimpleJdbcCall action, transaction will rollback and nothig related to JPA won't be persited, but if exception occures only within JPA action, result of SimpleJdbcCall won't be affected by transaction rollback.
To illustrate this behaviour I've remove JAP action, mark #Transactional as (readOnly = true) and moved all JDBCTemplate related logic from another service to current one.
#Service
public class DirectoriesServiceImpl implements DirectoriesService {
private final DirectoryRepo directoryRepo;
private final MapSQLParamUtils sqlParamUtils;
private final JdbcTemplate jdbcTemplate;
#Autowired
public DirectoriesServiceImpl(DirectoryRepo directoryRepo, MapSQLParamUtils sqlParamUtils, JdbcTemplate jdbcTemplate) {
this.directoryRepo = directoryRepo;
this.sqlParamUtils = sqlParamUtils;
this.jdbcTemplate = jdbcTemplate;
}
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
}
As a result #Transactional annotation is ignored and I can see new records persisted in DB.
I've got only one DataSource configured via application.properties, and here is how JDBCTemlate configured
#Component
class MapSQLParamUtils {
private final DataSource dataSource;
#Autowired
MapSQLParamUtils(DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
}
So my questions are: why do #Transactional ignored by SimpleJdbcCall and how to configure JPA and JDBCTemlate to use same transaction manager.
UPDATE:
This is how I use this service in controller
#RestController
#RequestMapping(value = "/api/v1/directories")
public class DirectoriesRESTControllerV1 {
private final DirectoriesService directoriesService;
#Autowired
public DirectoriesRESTControllerV1(DirectoriesService directoriesService) {
this.directoriesService = directoriesService;
}
#PostMapping
#PreAuthorize("hasPermission('DIRECTORIES_USER', 'W')")
public ResponseEntity createDirectory(#NotNull #RequestBody DirectoryRequestDTO createDirectoryRequestDTO) {
Directory directoryFromRequest = ServiceUtils.convertDtoToEntity(createDirectoryRequestDTO);
directoriesService.addDirectory(directoryFromRequest);
return ResponseEntity.noContent().build();
}
}
As mentioned earlier, the problem here is that JPA does not execute sql queries at once repository methods called. To enforce it you can use explicit entityManager.flush():
#Autowired
private javax.persistence.EntityManager entityManager;
...
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
entityManager.flush();
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
To see real SQL queries by hibernate you can enable option show_sql, in case if your application is spring-boot, this configuration enables it:
spring.jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging.level:
org.hibernate.SQL: DEBUG
Regarding transaction manager. In case if entityManager flush is not enough, you may need the composite transaction manager, that handles both JPA and DataSource. Spring data commons has ChainedTransactionManager. Note: you should be careful with it. I used it this way in my project:
#Bean(BEAN_CONTROLLER_TX)
public PlatformTransactionManager controllerTransactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(BEAN_ANALYTICS_TX)
public PlatformTransactionManager analyticsTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* Chained both 2 transaction managers.
*
* #return chained transaction manager for controller datasource and analytics datasource
*/
#Primary
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier(BEAN_CONTROLLER_TX) PlatformTransactionManager controllerTransactionManager,
#Qualifier(BEAN_ANALYTICS_TX) PlatformTransactionManager analyticsTransactionManager) {
return new ChainedTransactionManager(controllerTransactionManager, analyticsTransactionManager);
}
Please try this :
#Transactional(rollbackFor = Exception.class)
public void addDirectory(Directory directory){
#Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database.
So if you throw an Exception or a subclass of it, always use the above with the #Transactional annotation to tell Spring to roll back transactions if a checked exception occurs.
It's very simple, just use the following with #Transactional:
#Transactional(rollbackFor = Exception.class)
I have a method, 'databaseChanges', which call 2 operations: A, B in iterative way. 'A' first, 'B' last.
'A' & 'B' can be Create, Update Delete functionalities in my persistent storage, Oracle Database 11g.
Let's say,
'A' update a record in table Users, attribute zip, where id = 1.
'B' insert a record in table hobbies.
Scenario: databaseChanges method is been called, 'A' operates and update the record. 'B' operates and try to insert a record, something happen, an exception is been thrown, the exception is bubbling to the databaseChanges method.
Expected: 'A' and 'B' didn't change nothing. the update which 'A' did, will be rollback. 'B' didn't changed nothing, well... there was an exception.
Actual: 'A' update seems to not been rolled back. 'B' didn't changed nothing, well... there was an exception.
Some Code
If i had the connection, i would do something like:
private void databaseChanges(Connection conn) {
try {
conn.setAutoCommit(false);
A(); //update.
B(); //insert
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (Exception ei) {
//logs...
}
} finally {
conn.setAutoCommit(true);
}
}
The problem: I don't have the connection (see the Tags that post with the question)
I tried to:
#Service
public class SomeService implements ISomeService {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private NamedParameterJdbcTemplate npjt;
#Transactional
private void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
}
My AppConfig class:
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
#Configuration
public class AppConfig {
#Autowired
private DataSource dataSource;
#Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(dataSource);
}
}
'A' makes the update. from 'B' an exception is been thrown. The update which been made by 'A' is not been rolled back.
From what i read, i understand that i'm not using the #Transactional correctly.
I read and tried several blogs posts and stackverflow Q & A without succeess to solve my problem.
Any suggestions?
EDIT
There is a method that call databaseChanges() method
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Which method should be annotated with #Transactional,
changes()? databaseChanges()?
#Transactional annotation in spring works by wrapping your object in a proxy which in turn wraps methods annotated with #Transactional in a transaction. Because of that annotation will not work on private methods (as in your example) because private methods can't be inherited => they can't be wrapped (this is not true if you use declarative transactions with aspectj, then proxy-related caveats below don't apply).
Here is basic explanation of how #Transactional spring magic works.
You wrote:
class A {
#Transactional
public void method() {
}
}
But this is what you actually get when you inject a bean:
class ProxiedA extends A {
private final A a;
public ProxiedA(A a) {
this.a = a;
}
#Override
public void method() {
try {
// open transaction ...
a.method();
// commit transaction
} catch (RuntimeException e) {
// rollback transaction
} catch (Exception e) {
// commit transaction
}
}
}
This has limitations. They don't work with #PostConstruct methods because they are called before object is proxied. And even if you configured all correctly, transactions are only rolled back on unchecked exceptions by default. Use #Transactional(rollbackFor={CustomCheckedException.class}) if you need rollback on some checked exception.
Another frequently encountered caveat I know:
#Transactional method will only work if you call it "from outside", in following example b() will not be wrapped in transaction:
class X {
public void a() {
b();
}
#Transactional
public void b() {
}
}
It is also because #Transactional works by proxying your object. In example above a() will call X.b() not a enhanced "spring proxy" method b() so there will be no transaction. As a workaround you have to call b() from another bean.
When you encountered any of these caveats and can't use a suggested workaround (make method non-private or call b() from another bean) you can use TransactionTemplate instead of declarative transactions:
public class A {
#Autowired
TransactionTemplate transactionTemplate;
public void method() {
transactionTemplate.execute(status -> {
A();
B();
return null;
});
}
...
}
Update
Answering to OP updated question using info above.
Which method should be annotated with #Transactional:
changes()? databaseChanges()?
#Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
someLogicBefore();
databaseChanges();
someLogicAfter();
}
Make sure changes() is called "from outside" of a bean, not from class itself and after context was instantiated (e.g. this is not afterPropertiesSet() or #PostConstruct annotated method). Understand that spring rollbacks transaction only for unchecked exceptions by default (try to be more specific in rollbackFor checked exceptions list).
Any RuntimeException triggers rollback, and any checked Exception does not.
This is common behavior across all Spring transaction APIs. By default, if a RuntimeException is thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not a RuntimeException) is thrown, then the transaction will not be rolled back.
It depends on which exception you are getting inside databaseChanges function.
So in order to catch all exceptions all you need to do is to add rollbackFor = Exception.class
The change supposed to be on the service class, the code will be like that:
#Service
public class SomeService implements ISomeService {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private NamedParameterJdbcTemplate npjt;
#Transactional(rollbackFor = Exception.class)
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
In addition you can do something nice with it so not all the time you will have to write rollbackFor = Exception.class. You can achieve that by writing your own custom annotation:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(rollbackFor = Exception.class)
#Documented
public #interface CustomTransactional {
}
The final code will be like that:
#Service
public class SomeService implements ISomeService {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private NamedParameterJdbcTemplate npjt;
#CustomTransactional
private void databaseChanges() throws Exception {
A(); //update
B(); //insert
}
}
The first code you present is for UserTransactions, i.e. the application has to do the transaction management. Usually you want the container to take care of that and use the #Transactional annotation. I think the problem in you case might be, that you have the annotation on a private method. I'd move the annotation to the class level
#Transactional
public class MyFacade {
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
Then it should rollback properly. You can find more details here
Does Spring #Transactional attribute work on a private method?
Try this:
#TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {
#TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
What you seem to be missing is a TransactionManager. The purpose of the TransactionManager is to be able to manage database transactions. There are 2 types of transactions, programmatic and declarative. What you are describing is a need for a declarative transaction via annotations.
So what you need to be in place for your project is the following:
Spring Transactions Dependency (Using Gradle as example)
compile("org.springframework:spring-tx")
Define a Transaction Manager in Spring Boot Configuration
Something like this
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
return new DataSourceTransactionManager(dataSource);
}
You would also need to add the #EnableTransactionManagement annotation (not sure if this is for free in newer versions of spring boot.
#EnableTransactionManagement
public class AppConfig {
...
}
Add #Transactional
Here you would add the #Transactional annotation for the method that you want to participate in the transaction
#Transactional
public void book(String... persons) {
for (String person : persons) {
log.info("Booking " + person + " in a seat...");
jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
}
};
Note that this method should be public and not private. You may want to consider putting #Transactional on the public method calling databaseChanges().
There are also advanced topics about where #Transactional should go and how it behaves, so better to get something working first and then explore this area a bit later:)
After all these are in place (dependency + transactionManager configuration + annotation), then transactions should work accordingly.
References
Spring Reference Documentation on Transactions
Spring Guide for Transactions using Spring Boot - This has sample code that you can play with
I am getting this bug when I run a test case(Junit) for spring application.
I searched for this problem and I got the information that whenever a lazy initialization occurs and my application tries to get second level data while session is closed(object become detached) then this error occurs, we can't make initialization as EAGER as its performance issue.
My testing class contains :
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTestClass extends AbstractControllerTest {
#Rule
public TestName testMethodName = new TestName();
#Before
public void setUp() throws Exception
{
super.setUp();
}
#After
public void tearDown() throws Exception
{
super.tearDown();
}
#Test
public void myTestMethod ()
{
assertTrue("Response Validating",validate(baseEntity,perform()));
}
}
Is there a way that can I put method assertTrue("Response Validating",validate(baseEntity,perform())); in a transaction can bind with current session or with new session so that my detached object become persistent object and then My application can get second level data also. I searched for this problem and I found a solution on link : http://www.jroller.com/RickHigh/entry/hibernate_spring_simulating_an_opensessioninviewfilter
but this link does not fulfil my requirement as it requires target object on which transaction is to be created.
#Test
#Transactional
public void myTestMethod ()
{
assertTrue("Response Validating",validate(baseEntity,perform()));
}
Annotate myTestMethod with #Transactional (assuming you're using annotation-based configuration).
I got the solution for this problem. I was implementing OpenSessionInViewFilter in my testing code to overcome this problem but was doing silly mistake.
Please take a look on following code :
#Autowired
BeanFactory bf;
#Before
public void setUp() throws Exception
{
sessionFactory = (SessionFactory) bf.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
#After
public void tearDown() throws Exception
{
super.tearDown();
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
Session session = sessionHolder.getSession();
SessionFactoryUtils.closeSession(session);
}
Earlier I was not using
session.setFlushMode(FlushMode.NEVER) this was the mistake.
BTW thanks
I'm developing a web app with Spring MVC and hibernate for persistence.
Given my DAO where GenericDao has a SessionFactory member attribute:
#Repository
public class Dao extends GenericDao {
public void save(Object o) {
getCurrentSession().save(o);
}
}
And a Service class
#Service
public class MyService {
#Autowired
Dao dao;
#Transactional
public void save(Object o) {
dao.save(o);
}
}
I want to inform my user if a persistence exception occurs (constraint, duplicate, etc). As far as I know, the #Transactional annotation only works if the exception bubbles up and the transaction manager rolls back so I shouldn't be handling the exception in that method. Where and how should I catch an exception that would've happened in the DAO so that I can present it to my user, either directly or wrapped in my own exception?
I want to use spring's transaction support.
Spring provides Exception Handlers.
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-exceptionhandlers
So you could have something like this in your controller to handle a ConstraintViolationException
#ExceptionHandler(ConstraintViolationException.class)
public ModelAndView handleConstraintViolationException(IOException ex, Command command, HttpServletRequest request)
{
return new ModelAndView("ConstraintViolationExceptionView");
}
After chasing around the issue for a while, I solved this by using an exception handler (as described in another answer) and the rollbackFor property of the #Transactional annotation:
#Transactional(rollbackFor = Exception.class)
My exception handler is still called and writes the response accordingly, but the transaction is rolled back.