I have a #Transactional method which invokes another method on a common spring bean, that has long been used to perform batch operations, across mappers. The problem now is that, if there is an error in the #Transactional method, the DMLs executed on the batch are not rolled back, as they were executed on a different session, with it's own transaction.
public class HamsterRepo{
...
#Autowired
BatchOperationBean<Hamsters> idioticBatchBean;
#Transactional
public void saveHamsters(List<Hamsters> hams){
idioticBatchBean.executebatch("saveHamsters", hams);
}
}
public class BatchOperationBean<T>{
#Autowired
SqlSessionFactory sqlFactory;
public void executebatch(String mapperId, List<T> ts){
SqlSession sqlSession =
this.sqlSessionFactory.openSession(ExecutorType.BATCH,
TransactionIsolationLevel.SERIALIZABLE);
try(sqlSession){
for(T t in ts){
sqlSession.update(mapperId , t);
}
sqlSession.commit();
// Clean Up stuff and Exception Handling...
}
}
}
Now, Is there a way to relate the two Spring Tx and SqlSessionFactory? Will injecting SqlSession, instead of the factory help? Or is there a way to obtain the SqlSession from Spring Tx? Or a way in Spring to identify & execute queries across mappers without an SqlSesion?
PS: Using Spring 4.1.7, Mybatis: 3.4.4, Mybatis-spring : 1.3.1
Looks like the the way to link Spring Transaction and Mybatis SqlSession is the SqlSessionTemplate
Thread safe, Spring managed, SqlSession that works with Spring transaction management to ensure that that the actual SqlSession used is the one associated with the current Spring transaction. In addition, it manages the session life-cycle, including closing, committing or rolling back the session as necessary based on the Spring transaction configuration.
Related
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!
Iam using Spring's #Transactional annotation for my DAO classes/methods for DB operations.
Hibernate ORM configured with PostgreSQL
SessionFactory is created with #Bean in configuration and retreived using #Autowired in DAO classes.
#Autowired
SessionFactory sessionFactory;
#Transactional(value = "myTransactionManager")
public int getTotalClientsInDatabase() {
Query countTotalClientsQuery = sessionFactory.getCurrentSession()
.getNamedQuery(DeviceInfo.COUNT_TOTAL_CLIENTS);
return Integer.parseInt(countTotalClientsQuery.uniqueResult().toString());
}
Whenever getTotalClientsInDatabase is called, a Transaction is opened always.
I want to prevent opening of a Transaction as it is a SELECT(DDL) query.
If #Transactional is removed Exception is thrown saying sessionFactory is not synchronized.
If readOnly=true is added performance is reduced.
#Transactional(value = "myTransactionManager", readOnly=true)
Is there any way to stop Session from opening a Transaction??
Good practice would be in your case to mark the transaction with a propagation level of SUPPORTS:
Support a current transaction, execute non-transactionally if none
exists.
So if the parent method is not in a transactional context, this method will also not be, but can.
#Transactional(propagation=Propagation.SUPPORTS, value = "myTransactionManager")
I am trying to understand the transactions management and try to use its power in my already existing application developed in Struts 2, EJB 3 and hibernate 5.2.
Now I have ejb in my business layer like below
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER)
public class MyEJb implements ejbxyz {
#Override
public void method(){
Dao dao=new Dao() //Dao class is simple java class
dao.fooMethod(); //this method updates some record
dao.barMethod(); // this method updates some other record
}
}
public class Dao{
fooMethid(){
Session session=sessFactory.openSession();
session.beginTransaction();
session.update(x);
}
barMethod(){
try{
Session session=sessFactory.getCurrentSession();
session.getNamedQuery("xyz").executeUpdate();
}catch(HibernateException ex){
session.getTransaction.rollback();
}
}
}
I understand that Transaction management should be done at service layer(at ejb in my case). But how can I achieve this over there. ?
Now the dependency is if barMethod() fails to update the record then I need to rollback the changes made in fooMethod. So basically I need both the methods to be done in one transaction.
When I execute the application it throws the below exception
Exception while barMethod getNamedQuery is not valid without active transaction
Its because I am not beginning any transaction in barMethod. But then I really dont want to start a new transaction and want to continue with the older transaction started in fooMethod.
Container managed transactions are indeed suported out of the box for EJB beans. However, your Dao class is not a managed bean - it is a regular pojo that you instantiate manualy - therefore it does not participate in any transaction started by your other ejb.
So move your Dao to separate file, annotate it with #Stateless and then inject it into your service using #EJB private Dao dao;
There is more to transactions in Ejb container though. You can control the transaction support on method level via #TransactionAttribute annotation, that specifies how should the container invoke your method with regard to transaction. That way you can control, whether your method requires its own transaction, or if it shall participate in a transaction initiated by the caller(e.g. when invoked from ejb bean). For more info have a look at official Java EE tutorial
I have this code and I have a question :
#Service
class SimpleServiceImpl implements SimpleService{
#PersistenceContext
private EntityManager em;
#Transactional
public void doSmth(){
// here I want to have a new session
Session session = em.unwrap(Session.class).getSessionFactory().openSession();
// do smth in new session
session.close();
}
What about a transaction ??? Actually as I understand if I open session I have to manage this session explicitly and I have to close it. But what's going on with transaction? Should I begin a new transaction and manage it by myself or my action s continue in the same transaction which method doSmth began?
You have annotated with #Transactional that's why Spring will handle transaction using AOP concept.
If you use a Transactional annotation your method will be done within one transaction.
For more information you could also check this: spring transactional what happens in background.
I have a comprehension question. I cannot understand how the database actions from the Neo4jTemplate like "getOrCreateNode()" belong to the surrounding transction. How is it implemented? The Neo4jTemplate would be shared in a multi-threaded environment? I cannot see a distinct membership of the transaction. I would understand if the actions are directly in the transaction object (e.g. tx.getOrCreateNode()).
#Service
public class TestService {
#Autowired
private Neo4jTemplate template;
public void save(IndexedTriple triple) {
GraphDatabase gdb = template.getGraphDatabase();
Transaction tx = gdb.beginTx();
Node subject = gdb.getOrCreateNode()
...
tx.success();
tx.finish();
}
}
Thanks in advance.
The below extract from the reference documentation pretty much sums it up. Use the spring transaction manager instead of using the Neo4j transactions and let spring take care of demarcation. Also, the transaction management is completely thread-safe. For you, I suggest using #Transactional annotation. If there is an existing transaction already, then spring joins that existing transaction as well.
Transactions
The Neo4jTemplate provides implicit transactions for some of its
methods. For instance save uses them. For other modifying operations
please provide Spring Transaction management using #Transactional or
the TransactionTemplate.