How to flush data into db inside active spring transaction? - java

I want to test hibernate session's save() method using spring testing framework.
#Test method is :
#Test
#Transactional
public void testSave() {
User expected = createUser();
getGenericDao().currentSession().save(expected);
User actual = getUser(generatedId);
assertUsersEqual(expected,actual);
}
I want to flush user into database. I want my user to be in database after this method
getGenericDao().currentSession().save(expected);
Then I want to go to database using spring data framework and fetch this saved user by next line:
User actual = getUser(generatedId);
I tried to use hibernate flush method like:
currentSession().setFlushMode(MANUAL);
//do saving here
currentSession().flush();
It does not flush my user into database!
However if I don't make use of #Transactional spring annotation and save my user in programmatic spring transaction I achieve what I want. Unfortunately then user saved into db
is not rollbacked as there is no spring #Transactional. Therefore my test method changes the db and behaviour of subsequent test methods.
So I need to flush my user into db inside test method(not at the end) and at the end of test method rollback all changes to db.
UPDATE suggestion to prepare method as follows:
#Transactional
public void doSave(User user){
getGenericDao().currentSession().save(user);
}
And call doSave inside testSave is doing nothing. Still I have no user in db after executing this method. I set breakpoint and check my db from command-line.
UPDATE Thanks very much for response. The problem is that method flush() does not put my user into database.I tried Isolation.READ_UNCOMMITTED and it does not put my user into database. I can achieve what I want but only if I turn off spring transaction on #Test method and do saving in programmatic transaction. BUT then #Test method is not rolled back leaving saved user for subsequent #Test methods. Here #Test method to save user is not as dangerous as #Test method to delete user, because it is not rolled back. So there must spring transactional support for #Test method with which I can't anyway put my user(or delete) into db. Actually user is put(or deleted) into db only after #Test method ends and transaction for #Test method is comitted. So I want to save my User into db in the middle of #Test method and roll back it at the end of #Test method
Thank you!

Finally I stuck to the following solution:
First, my #Test methods are not running within spring #Transactional support. See this article to know how dangerous it may be. Next, instead of using #Repository beans inside #Test methods I autowire #Service beans which use #Transactional annotation.
The miracle is that #Test method like this
#Test
#Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testSave() {
Answer created = createAnswer();
Long generatedId = answerService.save(created);
//at this moment answer is already in db
Answer actual=getAnswerById(generatedId);
... }
puts my Answer object into database (just after answerService.save(created);) and method getAnswerById goes to DB and extracts it to check if save was correct.
To eliminate changes made to database in #Test method I recreate database by JdbcTestUtils.executeSqlScript

Have a look here with warning about #Transactional tests (Spring Pitfalls: Transactional tests considered harmful). I've used #org.springframework.test.context.jdbc.Sql to re-populate DB in my service tests and #Transactional for controllers.
ConstraintViolationException for controller update test with invalid data have been thrown only when transaction is committed. So I've found 3 options:
2.1 Annotate test with #Commit or with #Transactional(propagation = Propagation.NEVER). Be aware of DB change.
2.2 Use TestTransaction
Code:
TestTransaction.flagForCommit();
TestTransaction.end();
2.3 Use TransactionTemplate
Code:
#Autowired
private PlatformTransactionManager platformTransactionManager;
#Test(expected = Exception.class)
public void testUpdate() throws Exception {
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
String json = ...
transactionTemplate.execute(ts -> {
try {
mockMvc.perform(put(REST_URL + USER_ID)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk());
...
} catch (Exception e) {
e.printStackTrace();
}
return null;
});

You should also take care of the importedpackage :
in my case I imported
import javax.transaction.Transactional;
instead of
import org.springframework.transaction.annotation.Transactional;

If flush is not working then it very much depend on your database isolation level.
Isolation is one of the ACID properties of database, that defines how/when the changes made by one operation become visible to other concurrent operations.
I believe your isolation level is set to Read Committed or Repeatable Read.

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.

Spring testing DB rollback

I am #new to spring and faced some problem while running some tests. I have a few test-classes with the following code which should rollback my (in memory h2) database:
#Autowired
PlatformTransactionManager txm;
TransactionStatus txstatus;
#BeforeEach
public void setupDB() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txstatus = txm.getTransaction(def);
assumeTrue(txstatus.isNewTransaction());
txstatus.setRollbackOnly();
}
#AfterEach
public void rollback() {
txm.rollback(txstatus);
}
My problem is, that if one test class has finished, I get a JdbcSQLIntegrityConstraintViolationException (Unique index or primary key violation:..), because my Database is not rollbacked accordingly and Insert statements are executed again, because the database didnt get cleared. Does anyone has a tip how to fix that? Is there a way to rollback the inserts or not to do the inserts after they have been done ones?
You can just annotate your test class with
#Transactional
and Spring will handle everything (which means each test will run in its own transaction which will be rolled back after).
You can also use
#DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
but this is heavy because the whole Spring context must be recreated.
A simple solution is to use the DirtiesContext annotation. This annotation has several options. You can use the below line in your test class:
#DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
The context will be removed and recreated before the test class execution.

How can I persist entities during a SpringBootTest integration test

I am writing an integration test for a SpringBoot 2 RestController. I want to test 404 behaviour and creation of entities. However, when I try to create entities and persist them before or during a test, they are not persisted in the SpringBoot context. By that I mean they are visible in the test context (during debugging of the test) but not for the Controller (ie it does not find them and my tests fail). What am I doing wrong?
How can I persist entities and flush the context during a test so that code that is called during an integration test sees them? I don't want to use a #before annotation to populate a database because I want to do it in my #test methods.
Here is my code. Thanks
#RunWith(SpringRunner.class)
#Transactional
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class InvoiceControlllerIT extends GenericControllerIT {
#Autowired
EntityManager entityManager;
#Test
#Transactional
public void cascadesChildEntityAssociationOnCreate() throws IOException {
assertThat(invoicerRepository.count(), equalTo(0L));
assertThat(invoiceRepository.count(), equalTo(0L));
assertThat(invoiceeRepository.count(), equalTo(0L));
// create an invoicee
Invoicee savedInvoicee = invoiceeRepository.save(new Invoicee());
assertThat(invoiceeRepository.count(), equalTo(1L));
// create an invoicer
Invoicer savedInvoicer = invoicerRepository.save(new Invoicer());
assertThat(invoicerRepository.count(), equalTo(1L));
// THIS IS THE PROBLEM, FLUSHING DURING THE TEST DOES NOT EFFECT THE CONTROLLERS ABILITY TO SEE THE NEWLY CREATED ENTITIES
entityManager.flush();
// create input
InvoiceInputDto inputDto = InvoiceInputDto
.builder()
.invoicee(savedInvoicee.getId())
.invoicer(savedInvoicer.getId())
.name("test-name")
.build();
// make call
ResponseEntity<InvoiceDto> response = template.postForEntity(url("/invoices", TOKEN), inputDto, InvoiceDto.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.CREATED));
assertThat(response.getBody().getName(), equalTo(inputDto.getName()));
// check associations
assertThat(invoiceeRepository.findById(savedInvoicee.getId()).get().getInvoices(), hasSize(1));
}
}
According to the docs:
If your test is #Transactional, it rolls back the transaction at the
end of each test method by default. However, as using this arrangement
with either RANDOM_PORT or DEFINED_PORT implicitly provides a real
servlet environment, the HTTP client and server run in separate
threads and, thus, in separate transactions. Any transaction initiated
on the server does not roll back in this case
(source: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications)
Since the test transaction is separate from the HTTP server transaction, the controller won't see changes made from within the test method until the test transaction is actually committed. Conversely, you won't be able to roll back changes made as a result to the server call.
You will seriously make your life easier by providing a mock implementation for whatever service/repository your controller uses. Alternatively, you could use a tool like DBUnit to setup and tear down the database around each test case.
This worked for me:
#Inject
private EntityManagerFactory entityManagerFactory;
#BeforeEach
void setUp() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(someEntity());
entityManager.getTransaction().commit();
}

See all DML changes in Oracle session before committing

I'm writing a test harness for an Oracle (10g) stored procedure. I'm planning to use a #Transactional test to run the stored procedure, so that the transaction will be rolled back when the test ends. So my test looks like:
#ContextConfiguration(locations = "classpath:spring/ITestAssembly.xml")
#RunWith(SpringJUnit4ClassRunner.class)
public class ContentGenerationRunnerTest {
#Autowired
private JdbcTemplate jdbcTemplate;
#Test
#Transactional
public void contentIncShouldRun() throws Exception {
new ContentGenerationRunner().runVclBec(jdbcTemplate);
}
}
I can assert that the correct updates have been made, since the changes local to the test's session will be visible within the test method.
However to make more strict assertions, it'd be handy to be able to check for a complete list of DML statements that have been called in the session, but not yet committed. Is there a way I can see this?
Related question:
https://dba.stackexchange.com/questions/2994/oracle-any-way-to-view-uncommited-changes-to-a-particular-table

Spring unit test case is not rolling back insertion of a record

The following test cases are functionally working properly, but one of the test methods having to create a new article in the database doesn't rollback at the end of the test case execution.
I expect it to work that way. For a test case that update article actually rollbacks the update at the end of test case execution.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(value = "/applicationContext-test.xml")
#TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
#Transactional
public class PriceRepositoryTest {
#Resource(name ="repository")
private PriceRepository repository;
#Test
public void testGetAll() throws Exception {
Assert.assertEquals(8, repository.getAll().size());
}
#Test
#Rollback
public void shouldSaveNewArticle(){
Article article = new Article();
article.setName("Article");
article.setPrice(33);
repository.save(article);
Assert.assertEquals(9, repository.getAll().size());
}
#Test
#Rollback
public void shouldUpdateArticle(){
Article article = repository.getArticle(4);
article.setPrice(33);
repository.update(article);
Assert.assertEquals(String.valueOf(33.0), String.valueOf(repository.getArticle(4).getPrice()));
}
}
Is your DAO perhaps also marked with #Transactional? If it is, that's the problem: the transactionality on a different layer doesn't know about your local transaction configuration.
If repository.update(article) is #Transactional, it may or may not start a new transaction (depending on the value of the propagation attribute), but it will commit the transaction after execution, and there's nothing your test method can do to intercept that.
That's one of the reasons why transactions should be started on the service level, not the DAO level.
(If that's not the case, I humbly apologize)
I also encountered this issue and spent some hours trying to find the root cause. The issue was a variant of the problems described here. In my case, the application calls stored procedures through Spring, and one of these procedures contained a COMMIT statement.
Commits should always be controlled by the app, if there is a stray commit somewhere in a stored procedure, Spring can't control the transaction and tests won't roll back.
I just encountered this with my unit tests all set to rollback and my record was still showing up in the database after the test finished. The reason was there was that in the DAO a call to the entitymanager to flush() was in the method - which forced the transaction to commit.
em.persist(jpaServer);
em.flush(); //commits the record no matter what spring is setup to do
taking the flush out and confirmed no record. Tested the same code with the test annotated to #Rollback(false) and confirmed the record (proving the flush was not needed)
Ben
www.nimbits.com

Categories