Spring Boot Application cannot run test cases involving Two Databases correctly - either get detached entities or no inserts

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Spring Boot Application cannot run test cases involving Two Databases correctly - either get detached entities or no inserts



I am trying to write a Spring Boot app that talks to two databases - primary which is read-write, and secondary which is read only.


primary


secondary



This is also using spring-data-jpa for the repositories.



Roughly speaking this giude describes what I am doing: https://www.baeldung.com/spring-data-jpa-multiple-databases



And this documentation from spring:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html#howto-two-datasources



I am having trouble understanding some things - I think about the transaction managers - which is making my program error out either during normal operation, or during unit tests.



I am running into two issues, with two different TransactionManagers that I do not understand well



1)



When I use JPATransactionManager, my secondary entities become detached between function calls. This happens in my application running in full Spring boot Tomcat, and when running the JUnit test as SpringRunner.


JPATransactionManager



2)



When I use DataSourceTransactionManager which was given in some tutorial my application works correctly, but when I try to run a test case with SpringRunner, without running the full server, spring/hibernate will not perform any inserts or updates on the primaryDataSource.


DataSourceTransactionManager



--



Here is a snippet of code for (1) form a service class.


@Transactional
public List<PrimaryGroup> synchronizeAllGroups()
Iterable<SecondarySite> secondarySiteList = secondarySiteRepo.findAll();
List<PrimaryGroup> allUserGroups = new ArrayList<PrimaryGroup>(0);
for( SecondarySite secondarySite: secondarySiteList)
allUserGroups.addAll(synchronizeSiteGroups( secondarySite.getName(), secondarySite));

return allUserGroups;

@Transactional
public List<PrimaryGroup> synchronizeSiteGroups(String sitename, SecondarySite secondarySite)

// GET all secondary groups
if( secondarySite == null)
secondarySite = secondarySiteRepo.getSiteByName(sitename);


logger.debug("synchronizeGroups started - siteId:", secondarySite.getLuid().toString());

List<SecondaryGroup> secondaryGroups = secondarySite.getGroups();// This shows the error because secondarySite passed in is detached
List<PrimaryGroup> primaryUserGroups = primaryGroupRepository.findAllByAppName(sitename);
...
// modify existingUserGroups to have new data from secondary
...
primaryGroupRepository.save(primaryUserGroups );
logger.debug("synchronizeGroups complete");
return existingUserGroups;



I am pretty sure I understand what is going on with the detached entities with JPATransactionManager -- When populateAllUsers calls populateSiteUser, it is only carrying over the primaryTransactionManager and the secondary one gets left out, so the entities become detached. I can probably work around that, but I'd like to see if there is any way to have this work, without putting all calls to secondary* into a seperate service layer, that returns non-managed entities.


primaryTransactionManager


secondary*



--



Here is a snippet of code for (2) from a controller class


@GetMapping("synchronize/secondary")
public String synchronizesecondary() throws UnsupportedEncodingException
synchronizeAllGroups(); // pull all the groups
synchronizeAllUsers(); // pull all the users
synchronizeAllUserGroupMaps(); // add the mapping table
return "true";



This references that same synchronizeAllGroups from above. But when I am useing DataSourceTransactionManager I do not get that detached entity error.
What I do get instead, is that the primaryGroupRepository.save(primaryUserGroups ); call does not generate any insert or update statement - when running a JUnit test that calls the controller directly. So when synchronizeAllUserGroupMaps gets called, primaryUserRepository.findAll() returns 0 rows, and same with primaryGroupRepository


synchronizeAllGroups


DataSourceTransactionManager


primaryGroupRepository.save(primaryUserGroups );


synchronizeAllUserGroupMaps



That is to say - it works when I run this test case:


@RunWith(SpringRunner.class)

@SpringBootTest(classes=app.DApplication.class, properties="spring.profiles.active=local,embedded")
@AutoConfigureMockMvc
public class MockTest

@Autowired
private MockMvc mockMvc;


@Test
public void shouldSync() throws Exception
this.mockMvc.perform(get("/admin/synchronize/secondary")).andDo(print()).andExpect(status().isOk());






But it does not do any inserts or updates when I run this test case:


@RunWith(SpringRunner.class)

@SpringBootTest(classes=app.DApplication.class, properties="spring.profiles.active=local,embedded", webEnvironment=WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class ControllerTest

@Autowired AdminController adminController;
@Test
public void shouldSync() throws Exception
String o = adminController.synchronizesecondary();




Here are the two configuration classes
Primary:


@Configuration
@EnableTransactionManagement
@EntityScan(basePackageClasses = app.primary.dao.BasePackageMarker.class )
@EnableJpaRepositories(
transactionManagerRef = "dataSourceTransactionManager",
entityManagerFactoryRef = "primaryEntityManagerFactory",
basePackageClasses = app.primary.dao.BasePackageMarker.class
)
public class PrimaryConfig

@Bean(name = "primaryDataSourceProperties")
@Primary
@ConfigurationProperties("app.primary.datasource")
public DataSourceProperties primaryDataSourceProperties()
return new DataSourceProperties();


@Bean(name = "primaryDataSource")
@Primary
public DataSource primaryDataSourceEmbedded()
return primaryDataSourceProperties().initializeDataSourceBuilder().build();



@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("primaryDataSource") DataSource primaryDataSource)
return builder
.dataSource(primaryDataSource)
.packages(app.primary.dao.BasePackageMarker.class)
.persistenceUnit("primary")
.build();

@Bean
@Primary
public DataSourceTransactionManager dataSourceTransactionManager( @Qualifier("primaryDataSource") DataSource primaryDataSource)
DataSourceTransactionManager txm = new DataSourceTransactionManager(primaryDataSource);
return txm;





And Secondary:


@Configuration
@EnableTransactionManagement
@EntityScan(basePackageClasses=app.secondary.dao.BasePackageMarker.class ) /* scan secondary as secondary database */
@EnableJpaRepositories(
transactionManagerRef = "secondaryTransactionManager",
entityManagerFactoryRef = "secondaryEntityManagerFactory",
basePackageClasses=app.secondary.dao.BasePackageMarker.class

)
public class SecondaryConfig

private static final Logger log = LoggerFactory.getLogger(SecondaryConfig.class);

@Bean(name = "secondaryDataSourceProperties")
@ConfigurationProperties("app.secondary.datasource")
public DataSourceProperties secondaryDataSourceProperties()
return new DataSourceProperties();


@Bean(name = "secondaryDataSource")
public DataSource secondaryDataSourceEmbedded()
return secondaryDataSourceProperties().initializeDataSourceBuilder().build();



@Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource)
return builder
.dataSource(secondaryDataSource)
.packages(app.secondary.dao.BasePackageMarker.class)
.persistenceUnit("secondary")
.build();

@Bean
public DataSourceTransactionManager secondaryTransactionManager( @Qualifier("secondaryDataSource") DataSource secondaryDataSource)
DataSourceTransactionManager txm = new DataSourceTransactionManager(secondaryDataSource);
return txm;






In my real application, the secondary data source - since it is read-only - is used during real run time, and during the unit test I am writing.



I have been having trouble getting spring to initialize both data sources, so I have not attached a complete example.



thanks for any insight people an give me.



Edit: I have read some things that say to use Jta Transaction Manager when using multiple databases, and I have tried that. I get an error when it tries to run the transaction on my second read-only database when I go to commit to the first database


Caused by: org.postgresql.util.PSQLException: ERROR: prepared transactions are disabled
Hint: Set max_prepared_transactions to a nonzero value.



In my case, I cannot set that, because the database is a read-only datbase provided by a vendor, we cannot change anything, and I really sho9udn't be trying to include this database as part of transactions, I just want to be able to call both databases in one service call.





You have the method annotated with @Transactional this means it uses the primary transaction manager. Is that really what you want?
– Simon Martinelli
Aug 13 at 16:08





@SimonMartinelli - Because the other database is readonly, the only database that needs transaction functionality is the primary database. I believe that is the problem using JpaTransacitonManager, the entities for the second datasource get detached in calls from one @transactional function to another, since it is only the primaryTransactionmanager that is active.
– Matthew Carlson
Aug 13 at 22:47



@transactional





Yes this is the behavoir
– Simon Martinelli
Aug 14 at 8:12





@SimonMartinelli - this does appear to be the behavior with JpaTransactionManager. What i would like to find out is why the same code does not throw this error with DataSourcetransactionManager, and wether ther are any supported ways to explicitly allow the read-only database to maintain the session, throughout the read-write databases transactions.
– Matthew Carlson
Aug 14 at 16:41


JpaTransactionManager


DataSourcetransactionManager









By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

Creating a leaderboard in HTML/JS