I was following a tutorial on Hibernate and saw the following code:
package com.websystique.spring.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class AbstractDao {
#Autowired
private SessionFactory sessionFactory;
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
public void persist(Object entity) {
getSession().persist(entity);
}
public void delete(Object entity) {
getSession().delete(entity);
}
}
I was wondering if persist() (or save() or delete()) can be used without a transaction? As it seems to be the case here.
you cant save or persist object without transaction you have to commit the transaction after saving the object otherwise it won't save in database.
Without transaction you can only retrieve object from database
As said you CAN'T save anything in the database without a active transaction.
It seens that you are using a container, in this case Spring.
Spring can control transactions by interceptos like JavaEE.
You can read more here: http://docs.jboss.org/weld/reference/2.4.0.Final/en-US/html/interceptors.html
Also this looks like a really poor example to demonstrate:
public class TransactionalInterceptor {
#Inject
private Session session;
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object result = null;
boolean openTransaction = !session.getTransaction().isActive();
if(openTransaction)
session.getTransaction().begin();
try {
result = ctx.proceed();
if(openTransaction)
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
throw new TransactionException(e);
}
return result;
}
}
Actually it's possible to persist without a transaction with Hibernate but it is strongly discouraged because of performance and data consistency issues.
application.properties:
hibernate.allow_update_outside_transaction=true
application.properties for Spring:
spring.jpa.properties.hibernate.allow_update_outside_transaction=true
As I had to use this settings for a very specific reason. I think it might be of use to some people even though this setting shouldn't be used in production code because of aforementioned reasons.
Related
im just working on a project to create, change user in my mysql database. Therefore i have UserService (REST) which creates a user and a GenericDAO class where i can persist users. In my DAO for each user i begin, persist and commit a transaction. Creating single users or find users works perfect.
Now i am facing with the problem to persist or update a list of users. Especially if one user can not be persisted (e.g. duplicates) the hole transaction should be rolled back. It doesnt work in my current setup.
My first idea is to outsource the commit in a separate method. With an loop over all users i only persist them. At the end of the loop i would call my method to commit everything. If a single or more users fails i can catch them with the rollback. Is that a good approach?
AbstractDAO (current)
public abstract class GenericDAO<T> implements IGenericDAO<T>{
#PersistenceContext
protected EntityManager em = null;
private CriteriaBuilder cb = null;
private Class<T> clazz;
public GenericDAO(Class<T> class1) {
this.clazz = class1;
this.em = EntityManagerUtil.getEntityManager();
this.em.getCriteriaBuilder();
}
public final void setClazz(Class<T> clazzToSet) {
this.clazz = clazzToSet;
}
public T create(T entity) {
try {
em.getTransaction().begin();
em.persist(entity);
em.getTransaction().commit();
return entity;
} catch (PersistenceException e) {
em.getTransaction().rollback();
return null;
}
}
public T find(int id) {
return em.find(this.clazz, id);
}
public List<T> findAll() {
return em.createQuery("from "+this.clazz.getName()).getResultList();
}
/** Save changes made to a persistent object. */
public void update(T entity) {
em.getTransaction().begin();
em.merge(entity);
em.getTransaction().commit();
}
/** Remove an object from persistent storage in the database */
public void delete(T entity) {
em.getTransaction().begin();
em.remove(entity);
em.getTransaction().commit();
}
Wouldn't the most convenient solution be to simply add methods like createAll()/updateAll()?
Adding separate public methods for starting and persisting the transaction like start() and commit() creates a whole bunch of problems because it means you suddenly introduce a stateful conversation between the Dao and its clients.
The Dao methods now need to be called in a certain order and, worse still, the state of the EntityManager transaction is retained. If you forget to commit() at the end of one service call using your Dao, a subsequent call is going to mistakenly assume a transaction was not yet started, and that call is going to fail 'for no apparent reason' (not to mention that the original call will appear completed when in reality the transaction was left hanging). This creates bugs that are hard to debug, and tricky to recover from.
EDIT As I already pointed out in the comment below this answer, getting programmatic transaction management right is tricky in a multi-layer application structure, and so, I would recommend to have a look at declarative transaction management.
However, if you insist on managing transactions yourself, I would probably introduce sth like a TransactionTemplate:
public class TransactionTemplate {
private EntityManager em; //populated in a constructor, for instance
public void executeInTransaction(Runnable action) {
try {
em.getTransaction().begin();
action.run();
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
} finally {
em.clear(); // since you're using extended persistence context, you might want this line
}
}
}
and use it in a service like so:
public class UserService {
private TransactionTemplate template;
private RoleDao roleDao;
private UserDao userDao; //make sure TransactionTemplate and all Daos use the same EntityManager - for a single transaction, at least
public void saveUsers(Collection<User> users, String roleName) {
template.executeInTransaction(() -> {
Role role = roleDao.findByName(roleName);
users.forEach(user -> {
user.addRole(role);
userDao.create(user);
});
// some other operations
});
}
}
(of course, using the above approach means only one layer - the service layer in this case - is aware of transactions, and so DAOs must always be called from inside a service)
I have method where I'm trying to change some entity, also in this method I want to save transaction information.
When any exception occurred I want to roll back saving entity but still want to save transaction.
So how to make one repository for entity transactional but repository for transactions not?
There code from repository
#Override
#Transactional(noRollbackFor=NotEnoughAmountInAccountException.class)
<T extends Transaction> T save(T transaction);
but it doesn't help.
Saving transaction placed in final block.
UPDATE
I solved it by using AOP. I create Transaction object in aspect advice and save it here, out of JPA transaction.
Do it in a new #Transactional
#Transactional(propagation=Propagation.REQUIRES_NEW)
<T extends Transaction> T save(T transaction);
This will save your transaction element even if the other #Transactional gets rolled back
I solved it by using AOP. I create Transaction object in aspect advice and save it here, out of JPA transaction.
It's #Transactional method
#SaveTransaction
#Transactional
public synchronized Transaction move(#NonNull String accountName, #NonNull String destinationName, #NonNull BigDecimal amount, String comment) {
checkAmountIsPositive(amount);
Account donor = getAccount(accountName);
Account acceptor = getAccount(destinationName);
if (!isEnoughAmount(accountName, amount)) throw new NotEnoughAmountInAccountException();
BigDecimal newDonorAmount = donor.getAmount().add(amount.negate());
BigDecimal newAcceptorAmount = acceptor.getAmount().add(amount);
donor.setAmount(newDonorAmount);
acceptor.setAmount(newAcceptorAmount);
accountRepository.save(donor);
accountRepository.save(acceptor);
return null;
}
And it's aspect advice
#Around("#annotation(com.softjourn.coin.server.aop.annotations.SaveTransaction)")
public Transaction saveTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
Transaction transaction = prepareTransaction(joinPoint);
try {
joinPoint.proceed();
transaction.setStatus(TransactionStatus.SUCCESS);
return transaction;
} catch (Throwable e) {
transaction.setStatus(TransactionStatus.FAILED);
transaction.setError(e.getLocalizedMessage());
throw e;
} finally {
transactionRepository.save(transaction);
}
}
Also it's important to make order of this advice higher than order of #Transactional so this advice will be over transaction.
Set #Order(100) on aspect class.
By default it's order is smaller so it's under transaction.
I got a method call from a stateless ejb which looks like the following
#Stateless
#Local(MyCrudService.class)
#TransactionAttribute(TransactionAttributeType.MANDATORY)
public class MyCrudServiceBean implements MyCrudService {
#EJB
private CrudService crudService;
#Override
public void writeLogEntry(StatementLog statementLog) {
try {
crudService.execute(statement.getSql());
} catch (Exception e) {
crudService.writeLogEntry(statementLog);
throw new MyApplicationException(e.getLocalizedMessage());
}
}
// ...
}
CrudSerivce:
#Stateless
#Local(CrudService.class)
#TransactionAttribute(TransactionAttributeType.MANDATORY)
#Interceptors(GenericFrepDataBaseUserInterceptor.class)
public class CrudServiceBean implements CrudService {
public static final String PERSISTENCE_UNIT_NAME = "name";
private EntityManager entityManager;
#PersistenceContext(unitName = PERSISTENCE_UNIT_NAME)
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public void execute(String sqlString) {
Query query = entityManager.createNativeQuery(sqlString);
query.executeUpdate();
}
#Override
public void writeLogEntry(StatementLog statementLog) {
entityManager.persist(entity);
}
// ....
}
Statement is an entity which got an sql which is invalid (like 'invalid sql'). On execution I get the following error
javax.ejb.EJBTransactionRolledbackException: JBAS011469
If I debug this, I can see that this happens in the line with the logging.
I think the problem is, that because I am getting an exception the transaction gets rolled back. Because of that it is not possible to write into the db, because there is no open session anymore. Is this the case? What's best practice then? To manually open a session by myself seems quite ugly to me.
Your method log.writeErrorInDb needs to start its own transaction, so that it can still operate when the main transaction is rolled back. Yes, if your Hibernate session is already closed, then your log class would need to be able to open its own session. However it would probably be better to have a transaction boundary covering this entire block of code, and bind the Hibernate session to that, then set your log method to require a new transaction, to ensure it can operate once the first transaction is marked for rollback. i.e. two transactions, but one session
Based on your code, you should be able to annotate your log method:
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void writeLogEntry(StatementLog statementLog) {
entityManager.persist(entity);
}
I am attempting to write to multiple databases using hibernate. I have encapsulated write and read/write sessions within a single session object. However, when I go to save I get a lot of errors that the objects are already associated with another session: "Illegal attempt to associate a collection with two open sessions"
Here is my code:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
writeOnlySession.saveOrUpdate(arg0);
}
}
I have tried evicting the object and flushing; however, that causes problems with "Row was updated or deleted by another transaction"... even though both sessions point to different databases.
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
readWriteSession.flush();
readWriteSession.evict(arg0);
writeOnlySession.saveOrUpdate(arg0);
writeOnlySession.flush();
writeOnlySession.evict(arg0);
}
}
In addition to the above, I have also attempted using the replicate functionality of hibernate. This was also unsuccessful without errors.
Has anyone successfully saved an object to two databases that have the same schema?
The saveOrUpdate tries to reattach a given Entity to the current running Session, so Proxies (LAZY associations) are bound to the Hibernate Session. Try using merge instead of saveOrUpdate, because merge simply copies a detached entity state to a newly retrieved managed entity. This way, the supplied arguments never gets attached to a Session.
Another problem is Transaction Management. If you use Thread-bound Transaction, then you need two explicit transactions if you want to update two DataSources from the same Thread.
Try to set the transaction boundaries explicitly too:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
Transaction readWriteSessionTx = null;
try {
readWriteSessionTx = readWriteSession.beginTransaction();
readWriteSession.merge(arg0);
readWriteSessionTx.commit();
} catch (RuntimeException e) {
if ( readWriteSessionTx != null && readWriteSessionTx.isActive() )
readWriteSessionTx.rollback();
throw e;
}
Transaction writeOnlySessionTx = null;
try {
writeOnlySessionTx = writeOnlySession.beginTransaction();
writeOnlySession.merge(arg0);
writeOnlySessionTx.commit();
} catch (RuntimeException e) {
if ( writeOnlySessionTx != null && writeOnlySessionTx.isActive() )
writeOnlySessionTx.rollback();
throw e;
}
}
}
As mentioned in other answers, if you are using Session then you probably need to separate the 2 updates and in two different transactions. The detached instance of entity (after evict) should be able to be reused in the second update operation.
Another approach is to use StatelessSession like this (I tried a simple program so had to handle the transactions. I assume you have to handle the transactions differently)
public static void main(final String[] args) throws Exception {
final StatelessSession session1 = HibernateUtil.getReadOnlySessionFactory().openStatelessSession();
final StatelessSession session2 = HibernateUtil.getReadWriteSessionFactory().openStatelessSession();
try {
Transaction transaction1 = session1.beginTransaction();
Transaction transaction2 = session2.beginTransaction();
ErrorLogEntity entity = (ErrorLogEntity) session1.get(ErrorLogEntity.class, 1);
entity.setArea("test");
session1.update(entity);
session2.update(entity);
transaction1.commit();
transaction2.commit();
System.out.println("Entry details: " + entity);
} finally {
session1.close();
session2.close();
HibernateUtil.getReadOnlySessionFactory().close();
HibernateUtil.getReadWriteSessionFactory().close();
}
}
The issue with StatelessSession is that it does not use any cache and does not support cascading of associated objects. You need to handle that manually.
Yeah,
The problem is exactly what it's telling you. The way to successfully achieve this is to treat it like 2 different things with 2 different commits.
Create a composite Dao. In it you have a
Collection<Dao>
Each of those Dao in the collection is just an instance of your existing code configured for 2 different data sources. Then, in your composite dao, when you call save, you actually independently save to both.
Out-of-band you said you it's best effort. So, that's easy enough. Use spring-retry to create a point cut around your individual dao save methods so that they try a few times. Eventually give up.
public interface Dao<T> {
void save(T type);
}
Create new instances of this using a applicationContext.xml where each instance points to a different database. While you're in there use spring-retry to play a retry point-cut around your save method. Go to the bottom for the application context example.
public class RealDao<T> implements Dao<T> {
#Autowired
private Session session;
#Override
public void save(T type) {
// save to DB here
}
}
The composite
public class CompositeDao<T> implements Dao<T> {
// these instances are actually of type RealDao<T>
private Set<Dao<T>> delegates;
public CompositeDao(Dao ... daos) {
this.delegates = new LinkedHashSet<>(Arrays.asList(daos));
}
#Override
public void save(T stuff) {
for (Dao<T> delegate : delegates) {
try {
delegate.save(stuff);
} catch (Exception e) {
// skip it. Best effort
}
}
}
}
Each 'stuff' is saved in it's own seperate session or not. As the session is on the 'RealDao' instances, then you know that, by the time the first completes it's totally saved or failed. Hibernate might want you to have a different ID for then so that hash/equals are different but I don't think so.
I am working on a java web application that calls database backend through hibernate.I use servlets,jsp and tomcat for test/deployment.Most books on java-ee suggested using Dao classes for database calls.As per examples given in books(Hibernate Recipes by Gary Mak),I created a generic base class and a specific subclass as below.
class BaseDao{
private Class persistentClass;
public BaseDao(Class persistentClass) {
super();
this.persistentClass = persistentClass;
}
public Object findById(Long id) {
SessionFactory factory = HibernateUtil.getSessionFactory();
Session session = factory.openSession();
Object object = null;
try {
object = (Object) session.get(persistentClass, id);
return object;
}
finally {
session.close();
}
}
#Override
public void saveOrUpdate(Object obj) {
SessionFactory factory = HibernateUtil.getSessionFactory();
Session session = factory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.saveOrUpdate(obj);
tx.commit();
}catch(HibernateException e){
if (tx != null) {
tx.rollback();
}
throw e;
}finally {
session.close();
}
}
}
class SaleOrderDao extends BaseDao{
public SaleOrderDao() {
super(SaleOrder.class);
}
#Override
public SaleOrder findSaleOrderById(Long saleOrderId){
SaleOrder so = (SaleOrder)findById(saleOrderId);
return so;
}
#Override
public void saveOrUpdateSaleOrder(SaleOrder so){
saveOrUpdate( so);
}
}
While going through the posts in this forum ,I came across Ryan Stewart's advice that beginning and ending transactions in a dao method is not recommended..Sadly, my project does not use any web framework that supports transaction management..and I am limited to using jsp,servlets and a servlet container..
Is there some way I can rewrite my dao implementations sothat transactions can be managed properly..I couldn't find anything in this regard from those books I read..
Hope somebody helps me with suggestions
sincerely,
Jim
Normally transactions should not be handled in the DAO. They should be handled by the service layer. One service method may include multiple DAO calls that are all in the same transaction.
Spring (as well as other DI frameworks) allows you to do that by simply annotating your service methods with #Transactional. Without spring you can still do that manually in the service layer