how can I force the EJB to not flush everything after every single command, I want to do a transaction. I've read that this is done somehow declaratively. But how exactly?
#Stateless
public class SomeBean{
#PersistenceContext
EntityManager em;
public void doSomeStuffAndThenFlushToTheDb(){
em.persist(entity);
// it's flushed by now, I don't want that
em.persist(somethingElse);
// now I want to flush both
em.flush();
}
}
Hi, how can I force the EJB to not flush everything after every single command, I want to do a transaction.
First of all, you shouldn't get a flush after "every single command" and I'm actually surprised that you get a flush after the persist. Are you showing all the code? Second, EJB methods are transacted by default. But transaction and flush are not really related (the only link is that a flush will be done before the commit). Anyway...
If you would like to control the way a flush() call executes, you can change the default flush mode using EntityManager#setFlushMode(). The flush modes are as follows:
COMMIT - Flushing occurs only at transaction commit, or when flush() is called.
AUTO - (Default) Flushing occurs before any query execution.
So something like that should work:
#Stateless
public class SomeBean {
...
public void doSomeStuffAndThenFlushToTheDb(){
em.setFlushMode(COMMIT);
em.persist(entity);
em.persist(somethingElse);
em.flush(); // Causes flush
}
...
}
But as I said, I'm surprised by your example. The behavior you describe is not what I would expect.
First of all "flush" means only store it the second level cache (db driver). It is not stored directly within the DB. The entity is kept within the cache since the transaction is completed and a commit is sent. "Clear" means detach the entity. It does not mean clear the database. So afterwards you cannot persist this entity anymore. But the persist will still be done at the end of the transaction.
Related
I have a problem with accessing data inside a running transaction when the data came from another (supposedly closed) transaction. I have three classes like below, with an entity (called MyEntity) which also has another entity connected via Hibernate mapping called "OtherEntity" which has lazy loading set to true. Notice how I have two transactions:
One to load a list of entities
And a new transaction for each new item
However, this fails inside the loop with "No session" even though I have an active transaction inside the method (TransactionSynchronizationManager.isActualTransactionActive is true).
I don't really understand the problem. Seems to me the object which is used by the second transaction(s) "belong" to the first one even though the first transaction was supposed to finish? Maybe its a race condition?
#Service
class ServiceA {
#Autowired
private ServiceB serviceB;
#Autowired
private ServiceC serviceC;
public void test() {
List<MyEntity> allEntities = serviceC.loadAllEntities(); //First transaction ran, getting a list of entities, but due to lazy loading we havent loaded all the data
for(MyEntity i : allEntities) {
serviceB.doOnEach(i); //On each element a new transaction should start
}
}
}
#Service
class ServiceB {
#Transactional
public void doOnEach(MyEntity entity) {
System.out.println(TransactionSynchronizationManager.isActualTransactionActive()); //true, therefore we have an active transaction here
OtherEntity other = entity.getSomeOtherEntity(); //Want to load the "lazy loaded" entity here
//"No Session" exception is thrown here
}
}
#Service
class ServiceC {
#Autowired
private MyRepository myRepository;
#Transactional
public List<MyEntity> loadAllEntities() {
return myRepository.findAll();
}
}
A solution would be to re-load the "MyEntity" instance inside the "doOnEach" method, but that seems to me like a sub-optimal solution, especially on big lists. Why would I reload all the data which is already supposed to be there?
Any help is appreciated.
Obviously the real code is a lot more complicated than this but I have to have these kind of separate transactions for business reasons, so please no "solutions" which re-write the core logic of this. I just want to understand whats going on here.
After the call to loadAllEntities() finishes the Spring proxy commits the transaction and closes the associated Hibernate Session. This means you cannot have Hibernate transparently load the non-loaded lazy associations anymore without explicitly telling it to do so.
If for some reason you really want your associated entities to be loaded lazily the two options you have is either use Hibernate.initialize(entity.getSomeOtherEntity()) in your doOnEach() method or set the spring.jpa.open-in-view property to true to have the OpenSessionInViewInterceptor do it for you.
Otherwise it's a good idea to load them together with the parent entity either via JOIN FETCH in your repository query or via an Entity Graph.
References:
https://www.baeldung.com/spring-open-session-in-view
https://www.baeldung.com/hibernate-initialize-proxy-exception
To clarify further:
Spring creates a transaction and opens a new Session (A) before entering the loadAllEntities() method and commits/closes them upon returning. When you call entity.getSomeOtherEntity() the original Session (A) that loaded entity is gone (i.e. entity is detached) but instead there's a new Session (B) which was created upon entering the doOnEach() transactional method. Obviously Session (B) doesn't know anything about entity and its relations and at the same time the Hibernate proxy of someOtherEntity inside entity references the original Session (A) and doesn't know anything about Session (B). To make the Hibernate proxy of someOtherEntity actually use the current active Session (B) you can call Hibernate.initialize().
I Have a method using #Transactional annotation, and inside this method, i have one that persists one entity, and the next ones uses this persisted entity that is not yet persisted, because the method using #Transactional dont finished.
What is the best approach to do this? I Think about REQUIRED_NEW, but when this is a new transactional, if the external transaction fails, it will not fail all.
Thanks !!
Paulo
#Override
#Transactional
public Catalog updateCatalog(CatalogPrice catalog, Long id) {
CatalogEntity catalogEntity = CatalogEntity.findSingle(id);
Catalog catalog = catalogHand.updateCatalogPrice(catalog);
catalogEntity.sendToQueue(catalog);
return catalog;
}
Within the same transaction boundary - any changes you made (CREATE or UPDATE) will be visible. I believe you need to call flush() method between the method calls.
// Create code
entityManager.flush(); // If you use JPA, or it will be session.flush() for hibernate
// Update code goes here
Persistence context is flushed only when you call flush() explicitly or when you search for the entity or when the transaction commits. Only in these cases the changes you made will be available.
As per Session.setFlushMode(FlushMode) we can set FlushMode to the session. Now I am trying to test how the Flushmode.COMMIT mode works with a small example.
I have created an entity called Cat with just 2 properties id and name. Now here is the code that I am testing:
Session session = getSession();
session.setFlushMode(FlushMode.COMMIT);
Transaction tx = session.beginTransaction();
Cat cat = (Cat) session.get(Cat.class, 1);
cat.setName(name);
session.flush();
//tx.commit();
session.close();
From logs I can see that when the line session.flush() is executed then hibernate is issuing JDBC update call to database as:
Hibernate: update Cat set name=? where id=?
As I set the FlushMode to COMMIT, I am expecting that the update query will be executed only when I say tx.commit() but the flushing is happening at session.flush(). Can someone please explain why it is happening like this?
Note the Javadoc of Session#flush().
Force this session to flush. Must be called at the end of a unit of
work, before committing the transaction and closing the session
(depending on flush-mode, Transaction.commit() calls this method).
or the javadoc for FlushMode#MANUAL
The Session is only ever flushed when Session.flush() is explicitly
called by the application. This mode is very efficient for read only
transactions.
Setting a FlushMode simply defines when flush() will happen automatically (all but MANUAL). If you call flush() yourself, manually, you're overriding that behavior.
Have a JUNIT test set up as such
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/applicationContext.xml", "/applicationContext-security.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class BlahIntegrationTests{
#Test
public void testMappingsOfHugeObjectGraph(){
}
}
I'm attempting to test that my hibernate mappings (annotation driven and JPA based) are correct and when run like above my test passes (just asserts that an ID is created).
If I take the #Transactional away, I get errors with some of my relationships which I was expecting. Anyone have thoughts on why it's not failing when it's #Transactional?
EDIT: To Clarify, the exception that was thrown was regarding bad hibernate mappings (it's a very large object structure and I had borked some of them) upon saving of the object
If you remove #Transactional your test DB won't be empty for another test and therefore tests won't be isolated.
If you have it there then some tests may pass even though they should fail (as you described, or another example is if you insert entity wich duplicates some unique constraint).
Solution is to have #Transactional in its place and inject
#PersistenceContext
private EntityManager em;
and do following before you extract your data from database
em.flush();
em.clear();
The first line will cause synchronization between session and database (your provider usually waits till the end of the transaction).
The second line will remove all entities from session so all queries will go to the database.
And after the test everything is still rolled back so you have your database in original state.
Hope it helps.
If you take away #Transactional the Hibernate factory is running in its equivalent of auto-commit mode where each new access you make generates an entire new Session. So once you've retrieved an object it is immediately no longer associated with an open session and ineligible for Lazy Loading.
...or does it wait until the associated transaction is committed?
I'm using an HQL query in a loop like this:
tx.begin()
for(...)
{
session.getNamedQuery(...).list()
...
session.save(new MyEntity())
}
tx.commit()
The named query needs to be able to see the entities that were added with the save call. Will it work that way?
It depends on the flush mode of the session.
You can also manually flush it with session.flush()
The flush mode can be set in multiple ways - session.setFlushMode(..), entityManager.setFlushMode(..), or via xml configuration (org.hibernate.FlushMode).
The default value is AUTO:
The Session is sometimes flushed before query execution in order to ensure that queries never return stale state. This is the default flush mode.
Try it, if it doesn't, then call
session.flush()
to send the SQL to the DB. Regardless, it won't be committed until the call to
tx.commit()