Spring boot #Transactional doesn't rollback - spring-boot

I am using Spring boot application, on that i am trying to achieve Transactional management. But Spring doesn't rollback the data which saved in same method.
Code base: https://github.com/vinothr/spring-boot-transactional-example
Can any one help me?
This is my repository class for 'Test' entity.
#Repository
public interface TestRepository extends CrudRepository<com.example.demo.Test, Long> {
}
I have created one end-point which used to save the data to 'Test' entity. After save happen, I thrown RunTimeException, but it is not rollbacking the saved value
#GetMapping("/test")
#Transactional
public void create() {
System.out.println("test");
final Test p = createTest();
testRepository.save(p);
final Test p1 = createTest();
testRepository.save(p1);
throw new RuntimeException();
}

It works fine after I changed into 'InnoDB' engine because I was using 'MyISAM' engine which doesn't support transaction.
ALTER TABLE my_table ENGINE = InnoDB;

Try indicate #Transactional(rollbackFor = RuntimeException.class)

Related

Spring #Transactional method with save() command

please can somebody help me?
I have experience with JPA, but not so with Spring, that hides many aspects, that are visible in JPA (for example Hibernate implementation).
Often I was used to work in JPA in this mode (one global transaction) - I will try to explain on saving header (method_A) and its items (method_B) - with result in posting all or nothing. I would like to reach this effect via Spring persistence. I know, that method with #Transactional annotation gets the session from outside, if this exists. My problem is, that I think, that the nested implemented save() method of default Spring repository interface (CrudRepository for example) will open its own transaction anyway - and this is, what I don't want - simply I need to force this save() method to get it from outside. And so I am not sure, if only #Transactional annotation is enough to force this behavior.
This is my JPA code, that works properly in Hibernate:
root_method() {
Transaction tx = session.beginTransaction();
method_A(session);
tx.commit();
}
method_A(Session session) {
Header header = new Header();
session.save(header);
for (Item item : getJustAllocatedItems()) {
item.setHeader(header);
method_B(session, item);
}
}
method_B(Session session, Item item) {
session.save(item);
}
I am sorry, that this is not pure Java, but for explanation purposes I hope it is enough. I will try to mirror Spring code in brute form:
#Transactional
root_method() {
// ... root_method should make overal transaction frame on background (I suppose, that root_method is not called from another method with #Transactional ann.)
method_A();
}
#Transactional
method_A() {
Header header = new Header();
headerRepository.save(header);
for (Item item : getJustAllocatedItems()) {
item.setHeader(header);
method_B(item);
}
}
#Transactional
method_B(Item item) {
itemRepository.save(item);
}
... so I do not think, that save() methods of repositories (in both A and B method) will receive and use transaction from outside - am I right? - and if it is so, please can somebody interpret my JPA code from first part to appropriate Spring representation. Thanks so much.
If you call repository method without transaction, then repository
will create transaction:
Creating new transaction with name [org...SimpleJpaRepository.save]
Committing JPA transaction on EntityManager
If you use transactional annotation (Note that it should be in separate service), then save will reuse transaction:
Creating new transaction with name [com...HeaderService.createHeader]
Committing JPA transaction on EntityManager
Please note, mathods, annotated with #Transactional, should be in different classes (or you should autowire current class using setter). Then Spring will be able to use proxy. Spring wraps service with #Transactional annoaions into proxy.
Enable jpa logging:
logging.level.org.springframework.orm.jpa=DEBUG
logging.level.org.springframework.transaction=DEBUG
This is example implementation of your classes hierarcy:
#Service
#AllArgsConstructor
public class HeaderService {
HeaderRepository headerRepository;
ItemService itemService;
#Transactional
public void methodA() {
Header header = new Header();
headerRepository.save(header);
for (Item item : getJustAllocatedItems()) {
item.setHeader(header);
itemService.methodB(item);
}
}
}
#Service
#AllArgsConstructor
public class ItemService {
ItemRepository itemRepository;
#Transactional
void methodB(item) {
itemRepository.save(item);
}
}
public interface HeaderRepository extends CrudRepository<Header, Long> { }
public interface ItemRepository extends CrudRepository<Item, Long> { }

JPA - Spanning a transaction over multiple JpaRepository method calls

I'm using SpringBoot 2.x with SpringData-JPA accessing the database via a CrudRepository.
Basically, I would like to call the CrudRepository's methods to update or persist the data. In one use case, I would like to delete older entries from the database (for the brevity of this example assume: delete all entries from the table) before I insert a new element.
In case persisting the new element fails for any reason, the delete operation shall be rolled back.
However, the main problem seems to be that new transactions are opened for every method called from the CrudRepository. Even though, a transaction was opened by the method from the calling service. I couldn't get the repository methods to use the existing transaction.
Getting transaction for [org.example.jpatrans.ChairUpdaterService.updateChairs]
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
I've tried using different Propagation. (REQUIRED, SUPPORTED, MANDATORY) on different methods (service/repository) to no avail.
Changing the methods #Transactional annoation to #Transactional(propagation = Propagation.NESTED) sounded that this would just do that, but didn't help.
JpaDialect does not support savepoints - check your JPA provider's capabilities
Can I achieve the expected behaviour, not using an EntityManager directly?
I also would like to avoid to having to be using native queries as well.
Is there anything I have overlooked?
For demonstration purposes, I've created a very condensed example.
The complete example can be found at https://gitlab.com/cyc1ingsir/stackoverlow_jpa_transactions
Here are the main (even more simplified) details:
First I've got a very simple entity defined:
#Entity
#Table(name = "chair")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Chair {
// Not auto generating the id is on purpose
// for later testing with non unique keys
#Id
private int id;
#Column(name = "legs", nullable = false)
private Integer legs;
}
The connection to the database is made via the CrudRepository:
#Repository
public interface ChairRepository extends CrudRepository<Chair, Integer> {
}
This is being called from another bean (main methods here are updateChairs and doUpdate):
#Slf4j
#Service
#AllArgsConstructor
#Transactional
public class ChairUpdater {
ChairRepository repository;
/*
* Initialize the data store with some
* sample data
*/
public void initializeChairs() {
repository.deleteAll();
Chair chair4 = new Chair(1, 4);
Chair chair3 = new Chair(2, 3);
repository.save(chair4);
repository.save(chair3);
}
public void addChair(int id, Integer legCount) {
repository.save(new Chair(id, legCount));
}
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
#Transactional
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
}
The goal, I want to achieve is demonstrated by these two test cases:
#Slf4j
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(ChairUpdater.class)
public class ChairUpdaterTest {
private static final int COUNT_AFTER_ROLLBACK = 3;
#Autowired
private ChairUpdater updater;
#Autowired
private ChairRepository repository;
#Before
public void setup() {
updater.initializeChairs();
}
#Test
public void positiveTest() throws UpdatingException {
updater.updateChairs(3, 10);
}
#Test
public void testRollingBack() {
// Trying to update with an invalid element
// to force rollback
try {
updater.updateChairs(3, null);
} catch (Exception e) {
LOGGER.info("Rolled back?", e);
}
// Adding a valid element after the rollback
// should succeed
updater.addChair(4, 10);
assertEquals(COUNT_AFTER_ROLLBACK, repository.findAll().spliterator().getExactSizeIfKnown());
}
}
Update:
It seems to work, if the repository is not extended from either CrudRepository or JpaRepository but from a plain Repository, definening all needed methods explicitly. For me, that seems to be a workaround rather than beeing a propper solution.
The question it boils down to seems to be: Is it possible to prevent SimpleJpaRepository from opening new transactions for every (predefined) method used from the repository interface? Or, if that is not possible, how to "force" the transaction manager to reuse the transaction, opened in the service to make a complete rollback possible?
Hi I found this documentation that looks will help you:
https://www.logicbig.com/tutorials/spring-framework/spring-data/transactions.html
Next an example take from the previous web site:
#Configuration
**#ComponentScan
#EnableTransactionManagement**
public class AppConfig {
....
}
Then we can use transactions like this:
#Service
public class MyExampleBean{
**#Transactional**
public void saveChanges() {
**repo.save(..);
repo.deleteById(..);**
.....
}
}
Yes this is possible. First alter the #Transactional annotation so that it includes rollBackFor = Exception.class.
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
#Transactional(rollbackFor = Exception.class)
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
This will cause the transaction to roll back for any exception and not just RuntimeException or Error.
Next you must add enableDefaultTransactions = false to #EnableJpaRepositories and put the annotation on one of your configuration classes if you hadn't already done so.
#Configuration
#EnableJpaRepositories(enableDefaultTransactions = false)
public class MyConfig{
}
This will cause all inherited jpa methods to stop creating a transaction by default whenever they're called. If you want custom jpa methods that you've defined yourself to also use the transaction of the calling service method, then you must make sure that you didn't annotate any of these custom methods with #Transactional. Because that would prompt them to start their own transactions as well.
Once you've done this all of the repository methods should be executed using the service method transaction only. You can test this by creating and using a custom update method that is annotated with #Modifying. For more on testing please see my answer in this SO thread. Spring opens a new transaction for each JpaRepository method that is called within an #Transactional annotated method

Spring H2 Test DB does not reset before each test

EDIT: As C. Weber suggested in the comments, the solution is to add #Transactional to the test class.
I have some tests that use an H2 in-memory DB. I need to reset the DB before each test. Although my SQL scripts are run each a test is executed, the DB is not properly reset, resulting in a missing needed entry after a delete test.
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase(replace=Replace.ANY, connection=EmbeddedDatabaseConnection.H2)
public class RepositoryTests {
#Autowired
private Repository repository;
#Autowired
private DataSource dataSource;
#Before
public void populateDb() {
Resource initSchema = new ClassPathResource("database/schema.sql");
Resource initData = new ClassPathResource("database/data.sql");
DatabasePopulator dbPopulator = new ResourceDatabasePopulator(initSchema, initData);
DatabasePopulatorUtils.execute(dbPopulator, dataSource);
}
#Test
public void testMethod1() {
// ...
repository.delete("testdata");
}
#Test
public void testMethod2() {
// ...
Object test = repository.get("testdata");
// is null but should be an instance
}
}
schema.sql drops all tables before recreating them. data.sql inserts all needed test data into the DB.
Running the testMethod2 alone succeeds. However, running all tests makes the test fail with a NullPointerException.
I have successfully tried to use #DirtiesContext, however this is not an option because I can't afford to have a 20 second startup for each 0.1 second test.
Is there another solution?
The Spring Test Framework provides a mechanism for the behaviour you want for your tests. Simply annotate your Test class with #Transactional to get the default rollback behaviour for each test method.
There are ways to configure the transactional behaviour of tests and also some pitfalls (like using RestTemplate inside test method), which you can read more about in the corresponding chapter of the Spring manual.
Spring Test Framework

Making a method transactional in Spring

I use a hibernate as JPA provider
#RestController
public class RestController {
private final TestService testService;
#PostMapping(value = "/file/{entityId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void test(#PathVariable #NotNull UUID entityId) {
testService.delete(entityId);
}
}
class TestService {
#AutoWired
EntityRepository repo; // <- Crud repository from Spring Data
public void delete(UUID id2){
//if row not exists with id == id2
throw NoFoundException
// else
//remove from database using repo.
}
}
And how to resolve the following case:
"if row not exists with id == id2 " evaluated to false, because object exists in fact.
Other thread deleted that row.
"remove from database using repo" <- error, there is no such row because it was removed by other thread in the step 2.
You can use #Transactional on your Service methods to ensure your database operations run in a transaction. By default, you can roll back the transaction if you throw a unchecked exception inside the annotated method. You can also specify on which exceptions to rollback using #Transactional's rollbackFor Parameter
Not sure why you've got a delete method that is basically doing exactly the same as the SimpleJpaRepository delete method, so for starters I'd change your code to
repo.delete(entityId)
and get rid of test service.
If you are worried about getting a EmptyResultDataAccessException when there is no data to delete, either catch the exception and ignore it or use pessimistic locking on whatever else is doing deletes, as explained
here
You can use the annotation #Transaction for your service method delete(UUID id2).Default propagation of #Transaction is Propagation.REQUIRED which means that if you get an existing transaction it continues that and if you do not have existing transaction it will create one for you.

Why does OpenEntityManagerInViewFilter change #Transactional propagation REQUIRES_NEW behavior?

Using Spring 4.3.12, Spring Data JPA 1.11.8 and Hibernate 5.2.12.
We use the OpenEntityManagerInViewFilter to ensure our entity relationships do not throw LazyInitializationException after an entity has been loaded. Often in our controllers we use a #ModelAttribute annotated method to load an entity by id and make that loaded entity available to a controller's request mapping handler method.
In some cases like auditing we have entity modifications that we want to commit even when some other transaction may error and rollback. Therefore we annotate our audit work with #Transactional(propagation = Propagation.REQUIRES_NEW) to ensure this transaction will commit successfully regardless of any other (if any) transactions which may or may not complete successfully.
What I've seen in practice using the OpenEntityManagerInviewFilter, is that when Propagation.REQUIRES_NEW transactions attempt to commit changes which occurred outside the scope of the new transaction causing work which should always result in successful commits to the database to instead rollback.
Example
Given this Spring Data JPA powered repository (the EmployeeRepository is similarly defined):
import org.springframework.data.jpa.repository.JpaRepository;
public interface MethodAuditRepository extends JpaRepository<MethodAudit,Long> {
}
This service:
#Service
public class MethodAuditorImpl implements MethodAuditor {
private final MethodAuditRepository methodAuditRepository;
public MethodAuditorImpl(MethodAuditRepository methodAuditRepository) {
this.methodAuditRepository = methodAuditRepository;
}
#Override #Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditMethod(String methodName) {
MethodAudit audit = new MethodAudit();
audit.setMethodName(methodName);
audit.setInvocationTime(LocalDateTime.now());
methodAuditRepository.save(audit);
}
}
And this controller:
#Controller
public class StackOverflowQuestionController {
private final EmployeeRepository employeeRepository;
private final MethodAuditor methodAuditor;
public StackOverflowQuestionController(EmployeeRepository employeeRepository, MethodAuditor methodAuditor) {
this.employeeRepository = employeeRepository;
this.methodAuditor = methodAuditor;
}
#ModelAttribute
public Employee loadEmployee(#RequestParam Long id) {
return employeeRepository.findOne(id);
}
#GetMapping("/updateEmployee")
// #Transactional // <-- When uncommented, transactions work as expected (using OpenEntityManagerInViewFilter or not)
public String updateEmployee(#ModelAttribute Employee employee, RedirectAttributes ra) {
// method auditor performs work in new transaction
methodAuditor.auditMethod("updateEmployee"); // <-- at close of this method, employee update occurrs trigging rollback
// No code after this point executes
System.out.println(employee.getPin());
employeeRepository.save(employee);
return "redirect:/";
}
}
When the updateEmployee method is exercised with an invalid pin number updateEmployee?id=1&pin=12345 (pin number is limited in the database to 4 characters), then no audit is inserted into the database.
Why is this? Shouldn't the current transaction be suspended when the MethodAuditor is invoked? Why is the modified employee flushing when this Propagation.REQUIRES_NEW transaction commits?
If I wrap the updateEmployee method in a transaction by annotating it as #Transactional, however, audits will persist as desired. And this will work as expected whether or not the OpenEntityManagerInViewFilter is used.
While your application (server) tries to make two separate transactions you are still using a single EntityManager and single Datasource so at any given time JPA and the database see just one transaction. So if you want those things to be separated you need to setup two Datasources and two EntityManagers

Resources