Spring Boot MongoRepository #Rollback for tests - java

I wrote a Test for a MongoRepository in Spring Boot, and the test works fine. The only problem is that when the test is over, I want a rollback, so that there will be no change in the database caused by the test.
// package...
// imports...
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MetistrafficApplication.class)
#Rollback(true)
public class AppRepositoryTests {
#Autowired
private AppRepository appRepository;
#Test
public void insertTest() {
App app = new App("test");
App appInserted = appRepository.save(app);
assertThat(appInserted.getName(), equalTo(app.getName()));
}
}
I put #Transactional before #Rollback, but get this error:
java.lang.illegalstateexception:Failed to retrieve PlatformTransactionManager for #Transactional test for test context
When I searched for the error, I couldn't find any code with MongoRepository. So, how can I solve this?
EDIT: After adding #Transactional("PlatformTransactionManager"), the error I get is changed to this:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'PlatformTransactionManager' is defined: No matching PlatformTransactionManager bean found for qualifier 'PlatformTransactionManager' - neither qualifier match nor bean name match!

As far as I know, there isn't an implementation of Spring's TransactionManager for MongoDB since it is not transactional in the ACID sense. So no, you cannot use #Transactional annotations with MongoDB and you'll have to do all the cleanup manually or else use DBUnit and add your own extensions for MongoDB.
EDIT:
As Petter mentioned in his answer, starting with MongoDB 4.0, MongoDB has support for ACID transactions and you can find the official SpringData examples on GitHub and also have the feature's release post in Spring's developer blog

Now you can use #Transactional with mongo. Take a look at this example: https://www.baeldung.com/spring-data-mongodb-transactions
You'll need mongo 4.0. Also need to enable mongo replication (mongod --replSet rs0)
Then you'll need to add this bean to your spring application
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
This is enough to use #Transactional in your code.

I guess you use try catch block. Its better if you can avoid try catch. Anyway if you need to rollback you can do it like this.
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Related

How to test a transaction method using JOOQ

I'm trying to do some tests to see if my transactional methods are working fine. However I do not fully understand whether or not I should mock the database or not and how JOOQ comes into this equation. Below is the Service class with the transaction of adding a role into the databse.
#Service
public class RoleService implements GenericRepository<Role>
{
#Autowired
private DSLContext roleDSLContext;
#Override
#Transactional
public int add(Role roleEntry)
{
return roleDSLContext.insertInto(Tables.ROLE,
Tables.ROLE.NAME,
Tables.ROLE.DESCRIPTION,
Tables.ROLE.START_DATE,
Tables.ROLE.END_DATE,
Tables.ROLE.ID_RISK,
Tables.ROLE.ID_TYPE,
Tables.ROLE.ID_CONTAINER)
.values(roleEntry.getName(),
roleEntry.getDescription(),
roleEntry.getStartDate(),
roleEntry.getEndDate(),
roleEntry.getIdRisk(),
roleEntry.getIdType(),
roleEntry.getIdContainer())
.execute();
}
}
I'm using MySQL and the connection to the database is made using the spring config file
spring.datasource.url=jdbc:mysql://localhost:3306/role_managementverifyServerCertificate=false&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
I'm assuming I don't have to reconnect to the database everytime I'm testing the transaction and closing the connection after it finishes. I know that there is
MockDataProvider provider = new MockDataProvider()
but I don't understand how it works.
What is the best way to test the before mentioned method?
Disclaimer
Have you read the big disclaimer in the jOOQ manual regarding mocking of your database?
Disclaimer: The general idea of mocking a JDBC connection with this jOOQ API is to provide quick workarounds, injection points, etc. using a very simple JDBC abstraction. It is NOT RECOMMENDED to emulate an entire database (including complex state transitions, transactions, locking, etc.) using this mock API. Once you have this requirement, please consider using an actual database product instead for integration testing, rather than implementing your test database inside of a MockDataProvider.
It is very much recommended you use something like testcontainers to integration test your application, instead of implementing your own "database product" via the mock SPI of jOOQ (or any other means of mocking).
If you must mock
To answer your actual question, you can configure your DSLContext programmatically, e.g. using:
#Bean
public DSLContext getDSLContext() {
if (testing)
return // the mocking context
else
return // the actual context
}
Now inject some Spring profile value, or whatever, to the above configuration class containing that DSLContext bean configuration, and you're all set.
Alternatively, use constructor injection instead of field injection (there are many benefits to that)
#Service
public class RoleService implements GenericRepository<Role> {
final DSLContext ctx;
public RoleService(DSLContext ctx) {
this.ctx = ctx;
}
// ...
}
So you can manually construct your service in the test that mocks the database:
RoleService testService = new RoleService(mockingContext);
testService.add(...);
But as you can see, the mocking is completely useless. Because what you want to test is that there's a side effect in your database (a record has been inserted), and to test that side effect, you'll want to query the database again, but unless you mock that as well, or re-implement an entire RDBMS, you won't see that record in the database. So, again, why not just integration test your code, instead?

"BasicBatchConfigurer" has protected access - Spring Batch/Spring Boot not persisting data to database

I recently migrated my spring boot/batch Java application from spring-boot/spring-framework (respectively) 1.x.x/4.x.x to => 2.x.x/5.x.x (2.2.4/5.2.3 to be specific). The problem is something is definitely wrong (in my opinion) with the transaction/entity manager, as when the .saveAll() method is called from the JpaRepository class of my database persistance layer, it jumps into the SpringAOP framework/libarary code and into a infinite loop. I see it returning a "DefaulTransaction" object from a method (invoke()). My application on 1.x.x/4.x.x when it worked, would return the actual ArrayList here of my entities. I am using spring-boot-starter, spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-batch, and hibernate/hibernate-envers/hibernate-entitymanager (also of course many other dependencies, let me know if you would like me to list them).
After some research, I'm finding people are saying that Spring Batch #EnableBatchProcessing annotation sets up a default transaction manager, which if I'm using JPA could be causing issues. Reference:
https://github.com/spring-projects/spring-boot/issues/2363
wilkinsona suggested defining this Bean in my #Configuration class:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
return new BasicBatchConfigurer(dataSource, entityManagerFactory);
}
I'm getting an error when I do this because its saying the BasicBatchConfigurer() has protected access. What is the best way to instantiate this?
I also saw some people saying removing the #EnableBatchProcessing annotation fixes the persistance to database issue, but when I remove this, I lose the ability to Autowire my JobBuilderFactory and StepBuilderFactory. Is there a way to remove the annotation and get these objects in my code so I can at-least test if this works? Sorry, I'm not completely a master with Spring Batch/Spring.
In my #Configuration class, I am using the PlatformTransactionManager. I am setting up my JobRepository something like this.:
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager,
#Qualifier("dataSource") DataSource dataSource) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
I can provide any other information if needed. Another question is - if I was using the same code basically, transaction manager, entity manager etc.. how was old my code working on 1.x.x? Could I have a wrong dependency somewhere in my pom.xml such that my new migrated code is using a wrong method or something from the wrong dependency?
By default, #EnableBatchProcessing configures Spring Batch to use a DataSourceTransactionManager if you provide a DataSource. This transaction manager knows nothing about your JPA context. So if you want to use a JPA repository to save data, you need to configure Spring Batch to use a JpaTransactionManager.
Now in order to provide a custom transaction manager, you need to register a BatchConfigurer and override the getTransactionManager() method, something like:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
};
}
This is explained in the Configuring A Job section and in the Javadoc of #EnableBatchProcessing.

Questions regarding Spring boot and JPA

I was working on a project using Spring boot, Spring MVC, and Hibernate. I encountered this problem which had already taken me 2 days.
My project was an imitation of twitter. When I started to work on the project, I used the JPA to get the Hibernate Session. Here is the code in my BaseDaoImpl class:
#Autowired
private EntityManagerFactory entityManagerFactory;
public Session getSession(){
return entityManagerFactory.createEntityManager().unwrap(Session.class);
}
In my Service class, I used the #Transactional annotation:
#Service("userServ")
#Transactional(propagation=Propagation.REQUIRED, readOnly=false,rollbackFor={Exception.class, RuntimeException.class})
public class UserServImpl implements IUserServ {}
And finally, an overview of my main class:
#SpringBootApplication
#EnableTransactionManagement
#EntityScan(basePackages = {"edu.miis.Entities"})
#ComponentScan({"edu.miis.Controllers","edu.miis.Service","edu.miis.Dao"})
#EnableAutoConfiguration
#Configuration
public class FinalProjectSpringbootHibernateDruidApplication {
public static void main(String[] args) {
SpringApplication.run(FinalProjectSpringbootHibernateDruidApplication.class, args);
}
}
When I was using this setting, everything seemed fine - until I was able to move up to a the extent where I started to add "post" function. I could add posts and comments into the database. However, I could not do this a lot of times. Every time I added up to 4 posts, the program ceased to run - no exceptions, no errors - the page just got stuck there.
I looked up online, realizing that the problem was probably due to the entityManagerFactory. I was told that entityManagerFactory.createEntityManager().unwrap(Session.class)opens new Hibernate sessions, instead of the traditional sessionFactory.getCurrentSession() that returns an existing session.
So I started to work on it. I changed my Dao configuration into this:
#Autowired
private EntityManagerFactory entityManagerFactory;
public Session getSession(){
Session session = entityManagerFactory.unwrap(SessionFactory.class).getCurrentSession();
return session;
}
My idea was to use the autowired EntityManagerFactory to return a Hibernate SessionFactory so that the getCurrentSession method can be then used.
But then I got problem:
Since I configured to this setting, any operation that involves input from controller to the service-dao-database invokes an exception: No Transaction Is In Progress
But the weird thing is: although the system broke due to no visible transaction in progress, Hibernate still generates new SQL statements, and data still get synchronized into the database.
Can anybody help me over how to get this issue resolved?
Sincerely thanks!
Following #M. Deinum's suggestion, I finally had this issue resolved.
The reason why the #Transactional annotation didn't work in my code in the first place, was because in my original code, I used plain Hibernate features - Session, SessionFactory, getCurrentSession() etc.
In order for these features to work, I need to specifically configure the transaction manager into a Hibernate Transaction Manager (under default setting, Spring boot autowires a JPA transaction manager).
But the problem is: most of methods that were used to support Hibernate features are now deprecated. Now, the JPA method is the mainstream.
Use EntityManager instead of Session/
Use EntityManager.persist instead of Session.save
Use EntityManager.merge instead of Session.update
Use EntityManager.remove instead of Session.remove.
That's all.
Thanks!

Spring Boot with spring-boot-starter-data-jpa in unit test needs mandatory #DataJpaTest

I'm testing Spring Boot capabilities as a newbie in this area. I have a simple app with basic dependencies.
sping-boot-starter-parent 1.5.7
sping-boot-starter
sping-boot-starter-data-jpa
sping-boot-starter-test
Then there is simple Application class with #SpringBootApplication annotation.
Then I have a simple DummyService with #Service annotation.
Then I have created a simple test DummyServiceTest with one #Test method and #RunWith(SpringRunner.class) and #SpringBootTest annotations.
#SpringBootTest is the key problem. With spring-boot-starter-data-jpa dependency and this annotation test requires even #DataJpaTest annotation. Without it the framework doesn't solve HibernateJpaAutoConfiguration or DataSource or other injection dependencies even if the test doesn't require using data.
Can I suppress it somehow? I'm not any kind of Spring guru so my guess is that there is some simple configuration to handle this problem.
P.S. Ok, back on trees. Even with #DataJpaTest that test doesn't solve data dependencies. I tried add #AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) and it doesn't work either. I tried add #Transactional with the same result. It's a little bit beyond ridiculous.
If you aren't using JPA yet, then comment out / remove the dependency from build.gradle until you are using JPA. You need to define a datasource and other configuration details before the Hibernate and/or JPA configuration will complete successfully. Every application dependency gets resolved while #SpringApplicationConfiguration code is running, even if your current "hello world" test doesn't require JPA data.
My current unit tests actually have #SpringBootTest commented out. Here's a simplified view of how things are set up and working in my app's JPA related tests:
#RunWith(SpringJUnit4ClassRunner)
#SpringApplicationConfiguration(classes = DaemonApplication)
#ActiveProfiles('local')
//#SpringBootTest
#Transactional
public abstract class AbstractJpaTest extends AbstractTransactionalJUnit4SpringContextTests {
#BeforeTransaction
public void setupData() throws Exception {
deleteFromTables('User', 'User_Session', 'User_Handshake');
}
}
and then
class UserHandshakeRepositoryIntegrationTest extends AbstractJpaTest {
#Autowired UserHandshakeRepoImpl handshakeRepository;
#Test
public void testSave() {
UserHandshake handshake = handshakeRepository.save(new UserHandshake());
assertThat(handshake.getId(), is(notNullValue()));
}

How can I make Spring testcontext framework use multiple data sources?

I'm trying to integration test my application with Spring TestContext framework. I have done this by extending AbstractTransactionalJUnit4SpringContextTests, as usual. However, my application has three different data sources (with names like xDataSource, yDataSource, zdataSource), så when I try to run the test, the autowiring of data source in AbstractTransactionalJUnit4SpringContextTests won't work, since it looks for a Data Source with autowire-by-type, but finds three, so it does not know which one to choose.
Is there any way to get Spring TestContext Framework to use three data sources? If so; how?
OK, I figured it out. The answer to this question is twofold. Firstly, extending AbstractTransactionalJUnit4SpringContextTests won't work. This is because it needs a single data source for creating the SimpleJdbcTemplate for verifying stuff with simple JDBC queries in the test. Since I don't use this feature in this test, I could replace extends AbstractTransactionalJUnit4SpringContextTests with the collowing configuration:
#ContextConfiguration(locations = "classpath:applicationContext.xml")
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class
})
#Transactional
public class IntegrationTest {
...
}
The combination of these annotations gives the same setup as extending AbstractTransactionalJUnit4SpringContextTests.
The second part was understanding that since I have three data sources, I also need all three so be referenced by the same PlatformTransactionManager. I have distributed transactions. This is impossible with a DataSourceTransactionManager, so I had to use a JtaTransactionManager.
The AbstractTransactionalJUnit4SpringContextTests class is autowired to a single data source only to allow the convenience of providing an injected JdbcTemplate object. You can override the setDataSource(DataSource dataSource) method from AbstractTransactionalJUnit4SpringContextTests in your test subclass and specify the data source to use like this:
#Resource(name = "dataSource")
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
You just have to provide the name of the one data source Spring should use for the jdbcTemplate convenience methods. If extending AbstractTransactionalJUnit4SpringContextTests is more convenient than other methods mentioned above, then you can force it to work by just choosing one of your data sources.
I found these details in Spring Jira ticket #SPR-4634.
You can define one of the data-sources as primary="true" in your xml, and it will be chosen.
If you need all threem then you cannot rely on autowiring - use ReflectionTestUtils to set it manually in your tests.

Categories