Why do transactions in spring boot tests behave strange? - spring

I am testing a Spring-Boot-Application with graphQL endpoints.
I have a setup-method in my integration tests that loads a database with some predefined users:
#BeforeEach
protected void setUp() {
deleteFromTables(
jdbcTemplate,
"tableA",
"tableB",
...
);
deleteFromTables(jdbcTemplate, "users");
userRepository.saveAll(getPredefinedUsers());
}
In one of my integration tests, I want to find out if one of my entities has a user attached to it. It is a many-to-many-relationship between the entity and the user:
#ManyToMany private Set<User> recipients;
When at the end of the test I want to test that the entity was created with the right user, I get a LazyInitializationException, because the recipient is loaded lazily.
So what I did was putting a #Transactional-annotation on my test-method (or class, I tried both ways)
Now happens a strange thing:
The users I created in my setUp-method are getting lost.
They are still there inside the setup-Method (I can load them with the respository), I can also load them in the test-method itself, but they are lost as soon as I enter the graphQl-Controller, which looks like this:
#GraphQLMutation(name = "createProblem", description = "create a problem ")
#Transactional(isolation = SERIALIZABLE)
public Problem createProblem(... some arguments) {
List<User> users = userRepository.findAll();
}
My guess is that the test-method and the controller have different transactions, so that what happens in one transaction is not seen in the other one. But I have no idea why this is the case and if I can test the controller without switching my ManyToMany-Relationship to EAGER.
Any help is appreciated.
Matthias

Related

Access other service's API

In our Angular + Spring boot application application, we have 2 Controllers (2 Services are internally referenced). In first controller, We are sending a File from UI and reading the content of the file , query an external application and retrieve a set of data and return only a sub-set of Data, for entering as recommendation for UI fields. why we are returning only sub-set of data received from the external application? Because, we need only those sub-set data for showing recommendations in UI.
Once the rest of the fields are filled, then, we call another controller to generate a report. But, for generation of files, the second service requires the rest of the data from external application, which is received by the first service. I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application. I also like to avoid calling the external application again to retrieve the same data again in the second service. My question is how to fetch the data received by the first service in the second service?
For example:
First controller (ExternalApplicationController), which delegates loading of loading/importing of data from files
public class Department{
private Metadata metadata; // contains data such as name, id, location, etc.,
private Collection<Employee> employees; // the list of employees working in the department.
}
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files) {
return this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files)).getMetadata();
}
}
The first service (ExternalApplicationImportService), which delegates the request to the external application for loading of department data.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
public Department loadDepartmentDetails(File file){
return app.loadDepartmentDetails(file);
}
}
The Metadata from the ExternalApplicationController is used to populated UI fields and after doing some operations (filling up some data), user requests to generate a report(which contains details from the employees of that department)
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(){
generationService.generateAnnualReports();
}
}
#Service
public class ReportGenerationService{
public void generateAnnualReports(){
//here I need access to the data loaded in the ExternalApplicationImportService.
}
}
So, I would like to access the data loaded in the ExternalApplicationImportService in the ReportGenerationService.
I also see that there would be more services created in the future and might need to access the data loaded in the ExternalApplicationImportService.
How can this be designed and achieved?
I feel that I'm missing something how to have a linking between these services, for a given user session.
Thanks,
Paul
You speak about user session. Maybe you could inject the session of your user directly in your controllers and "play" with it?
Just adding HttpSession as parameter of your controllers' methods and spring will inject it for you. Then you just have to put your data in the session during the first WS call. And recover it from the session at the second WS call.
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
generationService.generateAnnualReports();
}
}
Alternatively for the second call you could use:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(#SessionAttribute("<name of your session attribute>") Object yourdata){
generationService.generateAnnualReports();
}
}
You are starting from a wrong assumption:
I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application.
That is not correct: by default, Spring will create your bean as singleton, a single bean definition to a single object instance for each Spring IoC container.
As a consequence, every bean in which you inject ExternalApplicationImportService will receive the same instance.
To solve your problem, you only need a place in where temporarily store the results of your external app calls.
You have several options for that:
As you are receiving the same bean, you can preserve same state in instance fields of ExternalApplicationImportService.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
// Maintain state in instance fields
private Department deparment;
public Department loadDepartmentDetails(File file){
if (department == null) {
department = app.loadDepartmentDetails(file);
}
return department;
}
}
Better, you can use some cache mechanism, the Spring builtin is excellent, and return the cached result. You can choose the information that will be used as the key of the cached data, probably some attribute related to your user in this case.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
#Cacheable("department")
public Department loadDepartmentDetails(File file){
// will only be invoked if the file argument changes
return app.loadDepartmentDetails(file);
}
}
You can store the information returned from the external app in an intermediate information system like Redis, if available, or even in the application underlying database.
As suggested by Mohicane, in the Web tier, you can use the http sessions to store the attributes you need to, directly as a result of the operations performed by your controllers, or even try using Spring session scoped beans. For example:
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files, HttpSession session) {
Deparment department = this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files));
session.setAttribute("department", department);
return deparment.getMetadata();
}
}
And:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
Department department = (Department)session.setAttribute("department");
// Probably you need pass that information to you service
// TODO Handle the case in which the information is not present in the session
generationService.generateAnnualReports(department);
}
}
In my opinion, the second of the proposed approaches is the best one but all are valid mechanisms to share your data between the two operations.
my recommendation for you will be to revisit your design of classes and build a proper relationship between them. I feel you need to introduce the extra logic to manage your temporal data for report generation.
#Mohicane suggested to use HTTP Session in above answer. It might be a possible solution, but it has an issue if your service needs to be distributed in the future (e.g. more than one runnable instance will serve your WEB app).
I strongly advise:
creating a separate service to manage Metadata loading process, where you will have load(key) method
you need to determine by yourself what is going to be a key
both of your other services will utilize it
this service with method load(key) can be marked by #Cacheable annotation
configure your cache implementation. As a simple one you can use In-Memory, if a question becomes to scale your back-end app, you can easily switch it to Redis/DynamoDB or other data storages.
Referances:
Spring Caching
Spring Caching Guide

Insert Same data to multiple databases using spring controller and jpa

I have a requirement to write a simple application to write some values to DB.
Basically this is some repetitive task which has to be done quite often and I want to build a simple Spring boot app with a UI exposed so that it can be done in an automatic way.
I have an Entity Class with a simple POJO MyClient and I have written a Controller and Service Classes and am able to GET and POST To DB:
My App.properties looks like below:
spring.datasource.url=jdbc:oracle:thin:#db-host-1:1521/xxx.xx.intern
spring.datasource.username=root
spring.datasource.password=root
//Controller Class
#GetMapping("/clients")
public List<MyClient> retrieveAllClientVersions(){
return myClientService.listAllClientVersions();
}
#RequestMapping(value = "/client/add", method = RequestMethod.POST)
#ResponseBody
void addNewClientVersion(#RequestBody MyClient myClient){
myClientService.addNewClientVersion(myClient);
}
//Service Class
private MyClientRepository myClientRepository;
#Autowired
public MyClientService(MyClientRepository myClientRepository){
this.myClientRepository=myClientRepository;
}
public List<MyClient> listAllClientVersions(){
List<MyClient> myClients=new ArrayList<>();
myClientRepository.findAll().forEach(myClients::add);
return myClients;
}
public void addNewClientVersion(MyClient myClient){
myClient.setReleaseKeyVersion(RELEASE_KEY_VERSION);
myClient.setClientVersion(myClient.getClientVersion());
myClient.setDescription(DESCRIPTION);
myClient.setReleaseCertDn(DGV_RELEASE_CERT_DN);
myClient.setStatus(STATUS);
myClient.setClientSecurityProfileDbId(CLIENT_SECURITY_PROFILE_DB_ID);
myClient.setIssuerDbId(ISSUER_DB_ID);
myClientRepository.save(myClient);
}
We have around 50 test environments where I need to run the same query. I wanted to create a UI where I can have check boxes against all environment with buttons like GET and POST.
Whatever environment user selects from check boxes and say POST the "Insert" should run on all those environments.
How can this be handled? Is there a way that based on Query Parameter in POST Request the Insert can be run on different DB. How do we connect to different DB at runtime? What could be best way to do this?

Spring-data-JPA - Executing complex multi join queries

I have a requirement for which I need to execute a bunch of random complex queries with multiple joins for reporting purposes. So I am planning to use entitymanager native query feature directly. I just tried and it seems to work.
#Service
public class SampleService {
#Autowired
private EntityManager entityManager;
public List<Object[]> execute(String sql){
Query query = entityManager.createNativeQuery(sql);
return query.getResultList();
}
}
This code is invoked once in every 30 seconds. Single threaded - scheduled process.
Question:
Should I be using entity manager or entity manager factory?
Should I close the connection here? or is it managed automatically?
How to reduce the DB connection pool - as it is not multi threaded app or Should I not be worried about that?
Any other suggestions!?
Should I be using entity manager or entity manager factory?
Injecting EntityManager Vs. EntityManagerFactory
EntityManager looks fine in this instance.
Should I close the connection here? or is it managed automatically?
No I dont think you need to as the manager handles this.
How to reduce the DB connection pool - as it is not multi threaded app or Should I not be worried about that?
I doubt you need concern yourself with the connection pools unless you are expecting large volumes and your application is running slowly under load. Try doing some bench marking you may have much more capacity than you need and be prematurely optimising your app.
It more likely you would you increase it number of connections rather than decrease. To increase the number of connections you do that in the application.properties (or application.yml)
Any other suggestions!?
Rather than a generic method I would consider having a separate repository class outside of the service and have that repository method do something specific. Make a method return a specific result or thing rather than pass in any sql.
As a rough outline of two seperate classes (files) something like this
#Service
public class SampleService {
#Autowired
private MyAuthorNativeRepository myAuthorNaviveRepository;
public List<Author> getAuthors(){
return myAuthorRepository.getAuthors();
}
}
#Service
public class MyAuthorNativeRepository {
#Autowired
private EntityManager entityManager;
public List<Author> getAuthors(){
Query q = entityManager.createNativeQuery("SELECT blah blah FROM Author");
List<Author> authors = new ArrayList();
for (Object[] row : q.getResultList()) {
Author author = new Author();
author.setName(row[0]);
authors.add(author);
}
return authors;
}
}

spring crud repo not reading data inserted by test case

I am trying to write my first integration test with spring boot application. So to write proper test cases I am using #Transactional provided by spring so that state of my in memory data base remains same before and after execution the test.
Thing is #Transactional is working as expected my test execution starts it inserts some data for testing into db and when execution finish it rollbacks the changes done by it but the problem is when the test execution goes to the code I am testing it tries to fetch data persisted by test but does not find any data there. I've shared my code for better understanding.
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class MyFirstControllerTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private MyFirstRepository myFirstRepo;
#Test
public void testMethod() {
//insert some data.
Dummy data = new DummyData();
myFirstRepo.save(data);
//hits the rest route to get data.
ResponseEntity<String> response =
this.restTemplate.getForEntity("/dummys", String.class); // did not find any data in db.
myFirstRepo.findAll(); //working - fetched the data inserted in test.
}
}
Below is my controller.
#Controller
#Validated
public class MyFirstController {
#Autowired
private MyFirstRepository myFirstRepo;
#GetMapping("/dummys")
public ResponseEntity<List<DummyDataDto>> getDummyData() {
List<DummyData> data = myFirstRepo.findAll(); //does not finds any data
//convert list of dummy data to list of dummydata dto.
return new ResponseEntity<DummyDataDto>(List<DummyDataDto>, HttpStatus.OK);
}
}
This is occurring because of the isolation level of in memory database that you have been using. I assume that default isolation level of that database in Read Committed.
If you are using H2 then you may find details of isolation level here.
All you need to use Read Uncommited isolation level for integration test, if your business requirement says to do so.

ZK & Spring - Safe to use Executions.getCurrent() in Spring Bean?

I want to create a utility Bean for common URL parsing in my ZK Composers. However, I want to make sure it is safe to use things like Executions.getCurrent() in a Spring managed Bean. I'm pretty sure it is as Executions.getCurrent() is static to begin with.
Here's what I'm thinking of doing..
#Component
public MyUrlBean {
// TODO I will, of course, program to an interface here =)
private static final String MY_OBJECT_URL_PARAMETER = "my_obj";
public MyObject getMyObjectFromURL() {
Execution ex = Executions.getCurrent();
String value = ex.getParameter(MY_OBJECT_URL_PARAMETER)
// ... db fetch and the like
}
}
..used like so..
#VariableResolver(DelegatingVariableResolver.class)
public MyComposer extends SelectorComposer<Window> {
#WireVariable
public MyUrlBean myUrlBean;
#Override
public void doAfterCompose(Window component) {
MyObject myObject = myUrlBean.getMyObjectFromURL();
// ...
}
}
So, doing things this way, should everything work fine or should I anticipate problems with user sessions clashing or the like?
Spring beans are NOT static singletons, correct? Instead they are instance classes that are autowired to save computation time of actually newing up objects, correct? If that is the case then there definitely won't be clashes between users like this.
Anyway, as I mentioned, Executions.getCurrent() is static. Hmm, how does that work with multiple users accessing a webapp?
Yes, it's safe.
I don't have much official sources to link here, but for what it's worth, my previous team has been using this in almost every page (to get a user context) of an app serving over 3000 users in production with no recorded problem in two years.

Resources