Spring/Hibernate, share Session between two Physical Transactions - java

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 !

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 boot manage multiple transactions

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?

Prevent hibernate entity changes from being persisted

I am updating my application from Spring Boot 1.4.5 / Hibernate 4.3.5 to Spring Boot 2.0.9 / Hibernate 5.2.18 and code that used to work in the previous configuration is no longer working.
The scenario is as follows:
Start a transaction by entering a method annotated with #Transactional
Hydrate the entity
Change the entity
Make another query
Detect a problem. As a result of this problem, determine that changes should not persist.
Evict the entity
Exit the method / transaction
With Hibernate 4.3.5, calling entityManager.detach() would prevent the changes from being persisted. However, with Hibernate 5.2.18, I'm finding that changes are persisted even with this call. I have also tried to evict() from the session and I have tried to clear() all entities from the session (just to see what would happen).
So I ask - is it possible to discard entity changes in Hibernate 5.2.18 the way that I was able to do in Hibernate 4.3.5?
The relevant code is below...
#Entity
public class Agreement {
private Long agreementId;
private Integer agreementStateId;
#Id
#Column(name = "agreement_id")
public Long getAgreementId() {
return agreementId;
}
public void setAgreementId(Long agreementId) {
this.agreementId = agreementId;
}
#Basic
#Column(name = "agreement_state_id", nullable = false)
public Integer getAgreementStateId() {
return agreementStateId;
}
public void setAgreementStateId(Integer agreementStateId) {
this.agreementStateId = agreementStateId;
}
}
#Component
public class Repo1 {
#PersistenceContext(unitName = "rights")
private EntityManager entityManager;
public void evict(Object entity) {
entityManager.detach(entity);
}
public Agreement getAgreement(Long agreementId) {
// Code to get entity is here.
// Agreement with an agreementStateId of 5 is returned.
}
public void anotherQuery() {
// Code to make another query is here.
}
}
#Component
public class Service1 {
#Autowired
Repo1 repo;
#Transactional
public void doSomething() {
Agreement agreement = repo.getAgreement(1L);
// Change agreementStateId. Very simple for purposes of example.
agreement.setAgreementStateId(100);
// Make another query
repo.anotherQuery();
// Detect a problem here. Simplified for purposes of example.
if (agreement.getAgreementStateId() == 100) {
repo.evict(agreement);
}
}
}
I have found the problem and it has nothing to do with evict(). It turns out that an additional query was causing the session to flush prior to the evict() call.
In general, the application uses QueryDSL to make queries. Queries made in this way did not result in the session flushing prior to making a query. However in this case, the query was created via Session.createSQLQuery(). This uses the FlushMode already assigned to the session which was FlushMode.AUTO.
I was able to prevent the flush by calling setHibernateFlushMode(FlushMode.COMMIT) on the query prior to making the query. This causes the session FlushMode to temporarily change until after the query has been run. After that, the evict() call worked as expected.

What happen on Transaction.rollback in nested session/transactions?

Consider this two classes: EmployeeDetailDAOImpl and EmployeeDAOImpl. Assume if I want to create a new employee, I should also create a new record for EmployeeDetail.
Given the below implementation, I wonder if the outer transaction(EmployeeDAOImpl's tx) is rolled back due to any exceptions happened after the detailDAO.create(employeeId) call, will the transaction of new EmployeeDetail be rolled back as well?
public class SessionHandler {
public static getSession() {
return Configuration.buildSessionFactory().openSession(); //ignore the isConnected or other exception handling for now
}
}
public class EmployeeDetailDAOImpl {
public void create(Serializable employeeId) {
Session session = SessionHandler().getSession();
Transaction tx = session.beginTransaction();
try {
EmployeeDetail detail = new EmployeeDetail(employeeId);
session.save(detail );
} catch (Exception e) {
if (tx!= null) {
tx.rollback;
}
}
session.close();
}
}
public class EmployeeDAOImpl {
public void add(String name) {
Session session = SessionHandler().getSession();
Transaction tx = session.beginTransaction();
try {
Employee employee = new Employee(name);
Serializable employeeId= session.save(employee);
EmployeeDetailDAOImpl detailDAO = new EmployeeDetailDAOImpl();
detailDAO.create(employeeId);
//more things here, that may through exceptions.
} catch (Exception e) {
if (tx!= null) {
tx.rollback;
}
}
session.close();
}
}
Actually, none of the given answers is 100% correct.
It depends on the calling party/service.
If you are calling the methods from an EJB, you will have 1 transaction covering both method calls. That way, the transaction will roll back both operations in case of an exception. Reason behind this is that every method in EJB is transaction Required, unless specified otherwise in the annotation or ejb deployment descriptor.
If you are using spring or any other DI framework, then it depends on your configuration. In a normal setup, your calling transaction will be suspended, since the JPA EJB will create its own transaction. You can however use the JTATransactionManager (As specified here) to make sure that both your EJB and your Spring bean share the same transaction.
If you call the JPA methods from a POJO, then you will have to take care of the JTA transaction handling yourself.
Yes, it will rollback the entity Employee as well. It doesn't even depend on whether the other entities are related.
It depends on the scope of the transaction, which here includes both Employee and EmployeeDetails
You are creating two different transaction for each method.Hence rollback can not happen.
To rollback the transaction you require the propogation in Transaction.
You need to write the code like below::
#Transactional(propagation=Propagation.REQUIRED)
public void testRequired(User user) {
testDAO.insertUser(user);
try{
innerBean.testRequired();
} catch(RuntimeException e){
// handle exception
}
}
Below is link for more information of Propogation.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/transaction/annotation/Propagation.html
http://www.byteslounge.com/tutorials/spring-transaction-propagation-tutorial

Transaction management spring 3 - hibernate 3.5

I'm using spring 3 with hibernate 3.5.4
1- I want to create an object in transaction and save it to DB ( which passes successfully ).
2- I want to update some fields in that object (same object) and updates in in DB in another transaction (and here is the problem).
The problem is, is saves the object successfully in the first transaction but it doesn't update it in DB in the second one.
here is code example:
public String entry(String str){
Bill b = create(str);
b = update(b);
b = updateAgain(b);
return "DONE";
}
#Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public Bill create(String num){
Bill bill = new Bill();
bill.setBillNumber(num);
baseDao.saveObject(bill);
return bill;
}
#Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public Bill update(Bill bill){
bill.setRetailAmount(152.0);
baseDao.saveObject(bill);
return bill;
}
NOTE: I don't want to put the #transactional annotation on method "entry".
Thanks,
The annotation will not take affect if called on a method within the same class. AOP cannot intercept that through proxy. Move your entry method outside the class.
EDIT: Spring enables the Transactional annotation via annotation-driven AOP with proxies or sub-classing. When enabled with proxies, your proxy is out of the picture in a local method call. This blog post has a good explanation with pictures.

Categories