How do you test Spring @Transactional by simply not falling into a sleeping level 1 cache or doing a manual flash session?

Using Spring + Hibernate and transactional annotations.

I am trying to verify the following:

  • calling a method that modifies the user object, then calling the @Transactional service method to save it
  • read the object back from the database and make sure that the values ​​are correct after the method

The first problem I encountered was reading the User object in step 2, which had just returned Hibernate 1 in the cache and had not actually read from the database.

Therefore, I manually deleted the object from the cache using a session to force it to read from the database. However, when I do this, the values ​​of the object are never stored in the unit test (I know that it returns after the test is completed due to the settings I specified).

I tried to manually clear the session after calling the @Transactional service @Transactional , and the DID committed the changes. However, I did not expect this. I thought the @Transactional service method @Transactional provide the transaction and be @Transactional before it was returned. I know that in general, Spring will decide when to do this management, but I thought the "unit of work" in the @Transactional method is a method.

Anyway, now I'm trying to figure out how I would test the @Transactional method as a whole.

There is no junit validation method here:

 @RunWith(SpringJUnit4ClassRunner.class) @Transactional @TransactionConfiguration(transactionManager = "userTransactionManager", defaultRollback = true) @WebAppConfiguration() @ContextConfiguration(locations = { "classpath:test-applicationContext.xml", "classpath:test-spring-servlet.xml", "classpath:test-applicationContext-security.xml" }) public class HibernateTest { @Autowired @Qualifier("userSessionFactory") private SessionFactory sessionFactory; @Autowired private UserService userService; @Autowired private PaymentService paymentService; @Autowired private QueryService queryService; @Autowired private NodeService nodeService; @Autowired private UserUtils userUtils; @Autowired private UserContext userContext; @Test public void testTransactions() { // read the user User user1 = userService.readUser(new Long(77)); // change the display name user1.setDisplayName("somethingNew"); // update the user using service method that is marked @Transactional userService.updateUserSamePassword(user1); // when I manually flush the session everything works, suggesting the // @Transactional has not flushed it at the end of the method marked // @Transactional, which implies it is leaving the transaction open? // session.flush(); // evict the user from hibernate level 1 cache to insure we are reading // raw from the database on next read sessionFactory.getCurrentSession().evict(user1); // try to read the user again User user2 = userService.readUser(new Long(77)); System.out.println("user1 displayName is " + user1.getDisplayName()); System.out.println("user2 displayName is " + user2.getDisplayName()); assertEquals(user1.getDisplayName(), user2.getDisplayName()); } } 

If I manually cleared the session, the test will succeed. However, I expected the @Transactional method @Transactional take care of committing and clearing the session.

The service method for updateUserSamePassword is here:

 @Transactional("userTransactionManager") @Override public void updateUserSamePassword(User user) { userDAO.updateUser(user); } 

The DAO method is here:

 @Override public void updateUser(User user) { Session session = sessionFactory.getCurrentSession(); session.update(user); } 

SesssionFactory auto-increments:

 @Autowired @Qualifier("userSessionFactory") private SessionFactory sessionFactory; 

I am using the context configuration of an XML application. I have:

 <context:annotation-config /> <tx:annotation-driven transaction-manager="userTransactionManager" /> 

and

 <bean id="userDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${user.jdbc.driverClass}"/> <property name="jdbcUrl" value="${user.jdbc.jdbcUrl}" /> <property name="user" value="${user.jdbc.user}" /> <property name="password" value="${user.jdbc.password}" /> <property name="initialPoolSize" value="3" /> <property name="minPoolSize" value="1" /> <property name="maxPoolSize" value="17" /> </bean> <bean id="userSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="userDataSource" /> <property name="configLocation" value="classpath:user.hibernate.cfg.xml" /> </bean> <bean id="userTransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="dataSource" ref="userDataSource" /> <property name="sessionFactory" ref="userSessionFactory" /> </bean> 

There is also a check on the components of services and dao classes. As I said, this works in production.

I thought that if I have a method marked with @Transactional that by the end of this method (like the update method here), Spring would force Session to commit and reset.

I see only a few options:

  • I have configured something incorrectly, although this works for me in general (just not unit tests). Any guesses? Any ideas on how to check this?

  • Something in the unit test configuration is not behaving as the application should have been.

  • Transactions and sessions do not work. My only conclusion is that Spring leaves the transaction and / or session open after calling this update method. Therefore, when I manually preempt a user in a Session object, these changes have not yet been committed.

Can anyone confirm that this is the expected behavior? Shouldn't @Transaction force and hide a session? If not, how can I test a method labeled @Transactional and that the methods really work with transactions?

Ie, how do I rewrite unit test here?

Any other ideas?

+6
source share
2 answers

Here I came across. Consider this code in a test method:

  String testDisplayNameChange = "ThisIsATest"; User user = userService.readUser(new Long(77)); user.setDisplayName(testDisplayNameChange); user = userService.readUser(new Long(77)); assertNotEquals(user.getDisplayName(), testDisplayNameChange); 

Note that the userService.readUser method is marked @Transactional in the service class.

If this test method is marked @Transactional, the test fails. If it is NOT, he succeeds. Now I'm not sure if / when the Hibernate cache is actually involved. If the test method is transactional, each read occurs in one transaction, and I believe that they only got to the level 1 cache (and actually do not read from the database). However, if the verification method is NOT transactional, then each reading occurs in its own transaction, and each of them falls into the database. Thus, the sleeping level 1 cache is tied to session / transaction management.

Take aways:

  • Even if the testing method calls several transactional methods in another class, if this testing method is transactional, all these calls occur in one transaction. The test method is a "unit of work." However, if the verification method is NOT transactional, each call to the transactional method inside this test is performed inside its own transaction.

  • My test class was tagged with @Transactional, so each method will be transactional unless tagged with an annotation label such as @AfterTransaction. I could just as easily NOT tag the @Transactional class and mark each @Transactional method

  • Hibernate level 1 cache is transaction bound when using Spring @Transactional. That is, subsequent readings of the object within the same transaction will fall into the sleeping level 1 cache, and not into the database. Please note that there is a level 2 cache and other mechanisms that you can configure.

I will have the @Transactional testing method, then use @AfterTransaction according to another method in the test class and send the raw SQL to evaluate the values ​​in the database. This will go all the way to the ORM and hibernate cache, ensuring that you are comparing the actual values ​​in the database.

The simple answer was to simply remove @Transactional from my test class. Yay

+3
source

Q

Can anyone confirm that this is the expected behavior? shouldn't @Transaction force commit and flash on the session? If not, how will the method marked by @Transactional be checked, and what methods really work with transactions?

A

This is the expected behavior . spring transaction support unit test by design rolls back the transaction after the test completes. This is by design.

An implicit transaction boundary is created for each test (each method with @Test), and as soon as the test is completed, a rollback is performed.

The consequence of this is that after the completion of all tests, there is no data that is actually changed. That is, the goal is to be more "single" and less "integration". You should read the spring documentation on why this is beneficial.

If you really want to check the stored data and view the data outside the boundaries of the transaction, I recommend a more comprehensive test, for example a functional / integration test, such as selenium, or a hit on the external WS / REST API, if you have one.

Q

Ie, how do I rewrite unit test here?

A

Your unit test does not work because you are session.evict and not a flash. Withdrawal will cause the changes to not be synchronized, even if you already called session.update in a transaction and hibernation operation. This is more understandable with raw SQL, because hibernation refuses to continue to perform all operations, so it waits until the session is cleared or closed to interact with the database for better performance. If you are session.flush , although SQL will be launched immediately (i.e., the update will really happen), then you can evict if you want to force re-read, but re-read in transactions. I'm really sure that flush will lead to eviction, so there is no need to call evict to make it re-read, but I could be wrong.

+1
source

Source: https://habr.com/ru/post/977303/


All Articles