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);
}
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 just hit a really strange case which I can't explain to myself. I have have the following scenario:
Hibernate version: 5.4.9
Spring data version: 2.2.3
So the following method is wrapped in a transaction and it only saves the entity
#Transactional
public Bookmark create(Entity entity) {
return repository.save(entity);
}
Here I registered a PostInsertEventListener. Based on some logic it uses the same repository to query the underlying table. I removed the logic in order to make the example more readable.
#Component
public class EntityListener implements PostInsertEventListener {
#Autowired
private EntityRepository repository;
#Autowired
private EntityManagerFactory entityManagerFactory;
#PostConstruct
private void init() {
final EventListenerRegistry registry = ((SessionFactoryImplementor) entityManagerFactory.unwrap(SessionFactory.class)).getServiceRegistry()
.getService(EventListenerRegistry.class);
registry.appendListeners(EventType.POST_INSERT, this);
}
#Override
public void onPostInsert(PostInsertEvent event) {
if (event.getEntity() instanceof Entity) {
repository.findByFieldOneAndFieldTwoIsNotNull(event.getEntity().fieldOne());
}
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
So when I invoke the create(Entity entity) method the onPostInsert(PostInsertEvent event) is triggered(as expected) but when this line is invoked repository.findByFieldOneAndFieldTwoIsNotNull(event.getEntity().fieldOne());
then another insert is executed and the onPostInsert(PostInsertEvent event) is triggered again. And of course at some point this leads to StackOverflowException.
Can someone come up with an idea why another insert is executed when I'm reading data using findBy query?
So i have a progress on that issue. When I execute repository.findByFieldOneAndFieldTwoIsNotNull(event.getEntity().fieldOne()); in a new separate transaction then everything is fine. So it seems that executing queries in the entity listener in the same transaction that the insert was executed on is leading to an infinite recursion which leads to a StackOverflowException. But I can't figure it out why is this happening.
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.
What I want is to implement the Repository pattern in a JPA/Hibernate application. I have a generic interface that describes the basic contract of my repositories:
public interface EntityRepository<Entity extends Object, EntityId> {
Entity add(Entity entity);
Entity byId(EntityId id);
void remove(Entity entity);
void removeById(EntityId id);
void save();
List<Entity> toList();
}
And here is an implementation of such an interface:
public class EntityRepositoryHibernate<Entity extends Object, EntityId>
implements Serializable,
EntityRepository<Entity, EntityId> {
private static final long serialVersionUID = 1L;
#Inject
protected EntityManager entityManager;
protected Class<Entity> entityClass;
public EntityRepositoryHibernate(Class<Entity> entityClass) {
this.entityClass = entityClass;
}
public EntityManager getEntityManager() {
return entityManager;
}
#Override
public Entity add(Entity entity) {
entityManager.persist(entity);
return entity;
}
#SuppressWarnings("unchecked")
#Override
public Entity byId(EntityId id) {
DetachedCriteria criteria = criteriaDAO.createDetachedCriteria(entityClass);
criteria.add(Restrictions.eq("id", id));
return (Entity)criteriaDAO.executeCriteriaUniqueResult(criteria);
}
#Override
public void remove(Entity entity) {
if(entity==null)
return;
entityManager.remove(entity);
}
#Override
public void removeById(EntityId id) {
remove(byId(id));
}
#Override
public List<Entity> toList() {
throw new UnsupportedOperationException("toList() not implemented in "+entityClass.getName());
}
#Override
public void save() {
entityManager.flush();
}
}
All methods are working fine, except save(), so this is the focus here.
As far as I understand, Hibernate is able to track all changes in any instance returned by a query (the byId() method). So, the idea of the save() method is to save any instances that where retrieved and changed, that's why the method does not receives any parameters, it is supposed to save everything that has to be saved (which means, any persistent instance that was retrived and somehow updated while the repository lives.
In a possible scenario, I could call byId() 10 times to retrieve 10 different instances and change only 4 of them. The idea is that by calling save() once, those 4 instances would be saved in the data server.
Problem is when I call flush() I receive an exception stating that there is no transaction active. Since I'm using a JTA persistence unit, it's illegal to open the transation programatically by calling entityManager.getTransaction().
Considering that, what to do to fix the code?
First of all, it seems that your are missunderstanding the purpose of EntityManager.flush method. It doesn't commit any changes managed by persistence context, just sends SQL instructuions to the database. I mean, for the same JTA transaction, when you retrieve and modify some entity instances the changes/SQL instructions are cached waiting to be sent to the database. If the underlying transaction is commited this changes are flushed to the database along with the commit instruction. If you invoke flush before transaction is commited, only flush the changes until the invokation point (well, some SQL instruction could have been flushed previously by reasons out of this matter) but not the commit instruction is sent.
How to fixed?
I suggest you to don't mix Repository Pattern with Transaction manipulation.
Looks like you are using Container Managed Transactions (javaee tutorial) so just erase the save method and let container to manage the transactions. This will change your focus, you now have to care about rolling back transactions (throwing exception or invoking setRollbackOnly) but you don't need to explicit commmit.
In JPA:
consider the following example, which uses a Container managed transaction scoped Entity manager.
public class ItemDAOImpl implements ItemDAO {
#PersistenceContext(unitName="ItemService")
EntityManager em;
LoggingService ls;
public void createItem(Item item) {
em.persist(item);
ls.log(item.getId(), "created item");
}
// ...
}
public class LoggingService implements AuditService {
#PersistenceContext(unitName="ItemService")
EntityManager em;
public void log(int itemId, String action) {
// verify item id is valid
if (em.find(Item.class, itemId) == null) {
throw new IllegalArgumentException("Unknown item id");
}
LogRecord lr = new LogRecord(itemId, action);
em.persist(lr);
}
}
Am I right in supposing that ls.log() method
will use the transaction of the calling method.
I'm pretty confused about these things right now, can you help?
If you are in EJBs, then very probably those methods will use the same transaction, because of the default transaction propagation method. Just check how they are configured, as it seems they are configured in an XML file.