How to flush data into db inside active spring transaction?
Asked Answered
M

4

22

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!

Mayamayakovski answered 23/6, 2013 at 17:40 Comment(5)
Can you give us your GenericDao class also? EDIT: actually, I think I see the problem. The current transaction needs to be committed before the data is actually saved to the database. So you need two @Transactional methods: one to save, and another to load.Leftwich
Please look if I did correctlyMayamayakovski
Close. I mean have two transactional functions called by a non-transactional function. That should work...Leftwich
If I make my testSave method non-transactional I will have no rollback after testSave method ends. Thus saved user will remain.Mayamayakovski
No Session found for current thread. This is hibernate exception when calling method save of GenericDao. @Transactional on doSave does not work!Mayamayakovski
M
15

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

Mayamayakovski answered 28/6, 2013 at 9:42 Comment(1)
Instead of using JdbcTestUtils you can apply @DirtiesContext annotation.Imputable
H
7
  1. 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.
  2. 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;
        });
Heiney answered 25/9, 2017 at 22:22 Comment(0)
A
4

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;

Australia answered 10/6, 2014 at 11:53 Comment(0)
R
1

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.

Raynard answered 23/6, 2013 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.