Spring boot manage multiple transactions - java

I have spring boot app with multiple databases and in services I want to talk to those databases and combine info from 2 of them in the same method.
#Service
#Transactional("transactionManager1")
public class Service1 {
#Service
#Transactional("transactionManager2")
public class Service2 {
I have some methods in Service1 that also call methods from Service2 to get some data from other database.
It worked fine until I added threading. In Service1 I added CompletableFuture.supplyAsync(....) that calls method from Service1 that in turn eventually calls Service2 methods. And at some point it just throws me TransactionException.
To fix this I thought I would manually create transaction before CompletableFuture.supplyAsync(....) and commit it afterwards. When I was searching how to do that I got idea from CompletableFuture vs Spring Transactions and started writing the following code (I use PlatformTransactionManager instead):
#TimeLimiter(name = "method1")
public CompletableFuture<Void> method1Async(long arg1) {
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = platformTransactionManager.getTransaction(txDef);
CompletableFuture<Void> x = null;
try {
x = CompletableFuture.supplyAsync(() -> {
Void y = this.method1(arg1);
platformTransactionManager.commit(txStatus);
return y;
})
.toCompletableFuture();
} catch (Exception e){
platformTransactionManager.rollback(txStatus);
}
return x;
}
But after still getting exception about transaction I realized that since I use multiple databases I probably need multiple transaction managers. But when I was trying to learn those transaction managers I didn't notice any way to tell for which database I want it.
How Do I create another transaction for transactionManager2?

Related

Get number of inserts, updates, deletes for entity using org.hibernate.SessionFactory per Session per thread

I have the following scenario:
I have one controller which based on a path variable calls a different service.
In every service there is a transactional method where some import logic is happening(call one external api, get a csv file, parse it, convert it to entity and save it in database).
Additionally in every service I want to keep statistics of how many entities will be updated, inserted and deleted. For that reason I am using the org.hibernate.SessionFactory . One example of how I am using that is:
#Service
#Slf4j
public class MarketReportImporterImpl extends Support implements MarketReportImporter {
#Override
#Transactional
public void importMarketReports(ImporterLog importerLog) {
try {
String export = getCsvFile();
Session session = getCurrentSessionAndClearSessionFactoryStatistics();
// parse the csv and save the entities
flushSession(session);
setSuccessfulImport(session, importerLog);
} catch (Exception e) {
log.error("Failed to import market reports. Unable to parse export", e);
getTelemetryClient().trackException(e);
importerLogService.setFailedImport(importerLog, e.getMessage());
}
}
and the methods getCurrentSessionAndClearSessionFactoryStatistics() and setSuccessfulImport(session, importerLog); are in the Support class:
#Component
public abstract class Support {
private final ImporterLogService importerLogService;
#PersistenceContext
private EntityManager entityManager;
public Support(ImporterLogService importerLogService) {
this.importerLogService = importerLogService;
}
public void flushSession(Session session) {
session.flush();
}
public void setSuccessfulImport(Session session, ImporterLog importerLog) {
Statistics statistics = session.getSessionFactory().getStatistics();
int entityInsertCount = (int) statistics.getEntityInsertCount();
int entityDeleteCount = (int) statistics.getEntityDeleteCount();
int entityUpdateCount = (int) statistics.getEntityUpdateCount();
importerLogService.setSuccessfulImport(importerLog, entityUpdateCount, entityDeleteCount, entityInsertCount);
}
public Session getCurrentSessionAndClearSessionFactoryStatistics() {
Session session = entityManager.unwrap(Session.class);
SessionFactory sessionFactory = session.getSessionFactory();
sessionFactory.getStatistics().clear();
return session;
}
This works perfectly fine when calling it for one importer. But If from the frontend I am calling quickly two importers (meaning two threads run in parallel) there will be mix in the results. The session.getSessionFactory().getStatistics(); will have mix results from the first importer and from the second importer and I want to have a clear result only for the current session. For example service A and service B are running in parallel and in service A I am saving entities of type aa and in service B of type bb. In each service I want to know how many entities are saved, updated or deleted meaning in service A -> how many of type aa and in service B -> how many of type bb . As I understand every thread should open a session on it's own and then for every session I should get the correct sessionFactory and the correct results. But as it turns out this sessionFactory I guess (not sure in this statement) it belongs to every session and that is why I have mix results.
My question is if there is a way to separate somehow the sessionFactory and have clear vision of which entity how many times is saved,deleted,updated even in multithreaded environment.
If you want the statistics of a session, then call the getStatistics() method on session, which will give you the SessionStatistics. A SessionFactory should only exist once and statistics there are across all sessions.

Spring/Hibernate, share Session between two Physical Transactions

Does Spring and Hibernate supports Session sharing between two different (nested or sequenced) physical Transactions ?
I know that Spring supports nested transactions, but it is the same Physical Transaction just with save points, i.e. nested transaction is separated logically :
From Spring docs:
PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to.
So, can i achive behavior similar to:
#Transactional
void invokeOuterTransaction() {
invokeInnerTransaction();
}
#Transactional
void invokeInnerTransaction() {
// here are the same Session as in 'invokeOuterTransaction',
// but this transaction is new PHYSICAL transaction
}
So, exploring this issue, using setup below, i discovered, that Hibernate Session "per request" and not "per transaction" is quite interesting.
Setup:
Hibernate 5, Spring 5, PostgreSQL
Below is a quick java-like pseudo-code for short:
#Controller {
#Inject FirstService fServ;
#Inject SecondService sServ;
#RequestMapping
handleHttpRequest() {
fServ.invokeFirstTransactional();
sServ.invokeSecondTransactional();
}
}
FirstService {
#Transactional
void invokeFirstTransactional() {
// Session object system hashcode = 187000543
// Thread object system hashcode = 167000522
// Transaction_ID in database = 650
}
}
SecondService {
#Transactional
void invokeSecondTransactional() {
// Session object system hashcode = 187000543
// Thread object system hashcode = 167000522
// Transaction_ID in database = 651
}
}
As you can see - same Hibernate Session, Same Thread, but DIFFERENT PHYSICAL transactions !

#Transactional(isolation = Isolation.SERIALIZABLE) retry mechanism

#Transactional(isolation = Isolation.SERIALIZABLE)
I have that annotation on several methods in my spring project. If there is an exception due to the "serialize access problems" what is the best approach if I want to retry the specific transaction. There is annotation #Retryable but it is not very straightforward to me how to use it so that the transaction will rollback and then retry only for that specific exception and just rollback for the other runtime exceptions. Thanks in advance.
A simple solution is to have a method that is the "entry point" for performing your logic; which delegates the actual logic to a transactional method. Typically a nice way of doing this is to have one class that has the Transactional annotations and that does the work and another which is the interface for clients to interact with that delegates; providing a form of indirection.
private static final int MAX_RETRY = 5;
public void doWork(T... parameters) {
doWork(0, parameters);
}
private void doWork(int retryLevel, T... parameters) {
if (retryLevel == MAX_RETRY) {
throw new MaximumRetryCountException(); //or any other exception
} else {
try {
//Get your Spring context through whatever method you usually use
AppContext().getInstance().getBean(classInterestedIn.class).doTransactionalMethod(parameters);
} catch (ExceptionToRetryFor e) {
doWork((retryLevel + 1), parameters);
}
}
}
#Transactional(isolation = Isolation.SERIALIZABLE)
public void doTransactionalMethod(parameters) {
...
}
Please note you may run into problems calling a Transactional method from a different method within that same class (i.e. calling this.doTransactionalMethod()) hence the invocation of Transactional Method is through the Spring Application Context. This is due to the way Spring AOP wraps classes to engage transactional semantics. See: Spring #Transaction method call by the method within the same class, does not work?

Combining JBehave with SpringJUnit4ClassRunner to enable transaction rollback

Essence:
How can I auto-rollback my hibernate transaction in a JUnit Test run with JBehave?
The problem seems to be that JBehave wants the SpringAnnotatedEmbedderRunner but annotating a test as #Transactional requires the SpringJUnit4ClassRunner.
I've tried to find some documentation on how to implement either rollback with SpringAnnotatedEmbedderRunner or to make JBehave work using the SpringJUnit4ClassRunner but I couldn't get either to work.
Does anyone have a (preferably simple) setup that runs JBehave storries with Spring and Hibernate and transaction auto-rollback?
Further infos about my setup so far:
Working JBehave with Spring - but not with auto-rollback:
#RunWith(SpringAnnotatedEmbedderRunner.class)
#Configure(parameterConverters = ParameterConverters.EnumConverter.class)
#UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = false, ignoreFailureInView = false)
#UsingSpring(resources = { "file:src/main/webapp/WEB-INF/test-context.xml" })
#UsingSteps
#Transactional // << won't work
#TransactionConfiguration(...) // << won't work
// both require the SpringJUnit4ClassRunner
public class DwStoryTests extends JUnitStories {
protected List<String> storyPaths() {
String searchInDirectory = CodeLocations.codeLocationFromPath("src/test/resources").getFile();
return new StoryFinder().findPaths(searchInDirectory, Arrays.asList("**/*.story"), null);
}
}
In my test steps I can #Inject everything nicely:
#Component
#Transactional // << won't work
public class PersonServiceSteps extends AbstractSmockServerTest {
#Inject
private DatabaseSetupHelper databaseSetupHelper;
#Inject
private PersonProvider personProvider;
#Given("a database in default state")
public void setupDatabase() throws SecurityException {
databaseSetupHelper.createTypes();
databaseSetupHelper.createPermission();
}
#When("the service $service is called with message $message")
public void callServiceWithMessage(String service, String message) {
sendRequestTo("/personService", withMessage("requestPersonSave.xml")).andExpect(noFault());
}
#Then("there should be a new person in the database")
public void assertNewPersonInDatabase() {
Assert.assertEquals("Service did not save person: ", personProvider.count(), 1);
}
(yes, the databaseSetupHelper methods are all transactional)
PersonProvider is basicly a wrapper around org.springframework.data.jpa.repository.support.SimpleJpaRepository. So there is access to the entityManager but taking control over the transactions (with begin/rollback) didn't work, I guess because of all the #Transactionals that are done under the hood inside that helper class.
Also I read that JBehave runs in a different context?session?something? which causes loss of controll over the transaction started by the test? Pretty confusing stuff..
edit:
Editet the above rephrasing the post to reflect my current knowledge and shortening the whole thing so that the question becomes more obvious and the setup less obstrusive.
I think you can skip the SpringAnnotatedEmbedderRunner and provide the necessary configuration to JBehave yourself. For example instead of
#UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = false, ignoreFailureInView = false)
you can do
configuredEmbedder()
.embedderControls()
.doGenerateViewAfterStories(true)
.doIgnoreFailureInStories(false)
.doIgnoreFailureInView(false);
Besides: why do you want to rollback the transaction? Typically you are using JBehave for acceptance tests, which run in a production-like environment. For example you first setup some data in the database, access it via Browser/Selenium and check for the results. For that to work the DB transaction has to be committed. Do you need to clean-up manually after your tests, which you can do in #AfterStories or #AfterScenario annotated methods.
I made it work by controlling transaction scope manually, rolling it back after each scenario. Just follow the official guide how to use Spring with JBehave and then do the trick as shown below.
#Component
public class MySteps
{
#Autowired
MyDao myDao;
#Autowired
PlatformTransactionManager transactionManager;
TransactionStatus transaction;
#BeforeScenario
public void beforeScenario() {
transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
#AfterScenario
public void afterScenario() {
if (transaction != null)
transactionManager.rollback(transaction);
}
#Given("...")
public void persistSomething() {
myDao.persist(new Foo());
}
}
I'm not familiar with JBehave, but it appears you're searching for this annotation.
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true).
You could also set defaultRollback to true in your testContext.

JPA Error : Transaction is currently active

When I run my unit tests in isolation they work fine ie. (omitted the asserts)
#Test
public void testSave()
{
EntityManagerHelper emh = new EntityManagerHelper();
LevelDAO dao = new LevelDAO();
Level l = new Level();
l.setName("aname");
emh.beginTransaction();
dao.save(l);
emh.commit();
}
then running this individual test below no problem
#Test
public void testUpdate()
{
EntityManagerHelper emh = new EntityManagerHelper();
LevelDAO dao = new LevelDAO();
Level l = new Level();
l.setName("bname");
l.setLevelid(1);
emh.beginTransaction();
dao.update(l);
emh.commit();
}
When they run at same time in sequence I recieve that error - Transaction is currently active. Is there a way to allow each unit test to run only after a transaction from previous piece of work is not active? Should I be looking at Spring instead?
Update
The EntityManagerHelper gains access to the persistence context like so
emf = Persistence.createEntityManagerFactory("bw_beta");
threadLocal = new ThreadLocal<EntityManager>();
which looks like the problem
So a hacky workaround was to use define locally ie.
EntityManagerFactory factory = Persistence.createEntityManagerFactory("bw_beta");
EntityManager entityManager = factory.createEntityManager();
entityManager.getTransaction().begin();
dao.save(l);
entityManager.persist(l);
entityManager.getTransaction().commit();
Pretty sure there's a better way - maybe using Spring?
Pretty sure there's a better way - maybe using Spring?
Yes, Spring cleans it up a lot and gives you control on what you'd like to run within a transaction without polluting the actual test.
With Spring, your tests would look something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:META-INF/conf/spring/application-context.xml",
"classpath:META-INF/conf/spring/test-datasource-spring-config.xml" })
#TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
public class LevelDaoTest {
#Resource( name="levelDao" )
LevelDao levelDao;
#Test
public void shouldSaveNewLevels() {
Level l = new Level();
l.setName("aname");
levelDao.save(l);
// assert
}
#Test
public void shouldUpdateExistingLevels() {
Level l = new Level(); // or I would assume, you'd read this level back from DB, or set a proper ID, so the DAO will know to update it.. But that is besides the point
l.setName("bname");
levelDao.update(l);
// assert
}
}
Take a look at Spring Documentation under Testing => Transaction Management to get more details.
P.S. From your example:
dao.save(l);
entityManager.persist(l);
Looks really strange, as usually you would encapsulate entityManager within a DAO, so all you'd need to do is dao.save(l)
For anyone that may be having this issue here is how I resolved it. I was doing multiple saves and I kept getting this error. You do not want to begin multiple transactions without checking if it is active.
if(!entityManager.getTransaction().isActive())
entityManager.getTransaction().begin();
dao.save(l);
entityManager.persist(l);
entityManager.getTransaction().commit();
I implemented a Singleton approach to handle it.

Categories