This question already has answers here:
Spring Data JPA Update #Query not updating?
(5 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
Let's suppose to have this situation:
We have Spring Data configured in the standard way, there is a Respository object, an Entity object and all works well.
Now for some complex motivations I have to use EntityManager (or JdbcTemplate, whatever is at a lower level than Spring Data) directly to update the table associated to my Entity, with a native SQL query. So, I'm not using Entity object, but simply doing a database update manually on the table I use as entity (it's more correct to say the table from which I get values, see next rows).
The reason is that I had to bind my spring-data Entity to a MySQL view that makes UNION of multiple tables, not directly to the table I need to update.
What happens is:
In a functional test, I call the "manual" update method (on table from which the MySQL view is created) as previously described (through entity-manager) and if I make a simple Respository.findOne(objectId), I get the old object (not updated one). I have to call Entitymanager.refresh(object) to get the updated object.
Why?
Is there a way to "synchronize" (out of the box) objects (or force some refresh) in spring-data? Or am I asking for a miracle?
I'm not ironical, but maybe I'm not so expert, maybe (or probably) is my ignorance. If so please explain me why and (if you want) share some advanced knowledge about this amazing framework.
If I make a simple Respository.findOne(objectId) I get old object (not
updated one). I've to call Entitymanager.refresh(object) to get
updated object.
Why?
The first-level cache is active for the duration of a session. Any object entity previously retrieved in the context of a session will be retrieved from the first-level cache unless there is reason to go back to the database.
Is there a reason to go back to the database after your SQL update? Well, as the book Pro JPA 2 notes (p199) regarding bulk update statements (either via JPQL or SQL):
The first issue for developers to consider when using these [bulk update] statements
is that the persistence context is not updated to reflect the results
of the operation. Bulk operations are issued as SQL against the
database, bypassing the in-memory structures of the persistence
context.
which is what you are seeing. That is why you need to call refresh to force the entity to be reloaded from the database as the persistence context is not aware of any potential modifications.
The book also notes the following about using Native SQL statements (rather than JPQL bulk update):
■ CAUTION Native SQL update and delete operations should not be
executed on tables mapped by an entity. The JP QL operations tell the
provider what cached entity state must be invalidated in order to
remain consistent with the database. Native SQL operations bypass such
checks and can quickly lead to situations where the inmemory cache is
out of date with respect to the database.
Essentially then, should you have a 2nd level cache configured then updating any entity currently in the cache via a native SQL statement is likely to result in stale data in the cache.
In Spring Boot JpaRepository:
If our modifying query changes entities contained in the persistence context, then this context becomes outdated.
In order to fetch the entities from the database with latest record.
Use #Modifying(clearAutomatically = true)
#Modifying annotation has clearAutomatically attribute which defines whether it should clear the underlying persistence context after executing the modifying query.
Example:
#Modifying(clearAutomatically = true)
#Query("UPDATE NetworkEntity n SET n.network_status = :network_status WHERE n.network_id = :network_id")
int expireNetwork(#Param("network_id") Integer network_id, #Param("network_status") String network_status);
Based on the way you described your usage, fetching from the repo should retrieve the updated object without the need to refresh the object as long as the method which used the entity manager to merge has #transactional
here's a sample test
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ApplicationConfig.class)
#EnableJpaRepositories(basePackages = "com.foo")
public class SampleSegmentTest {
#Resource
SampleJpaRepository segmentJpaRepository;
#PersistenceContext
private EntityManager entityManager;
#Transactional
#Test
public void test() {
Segment segment = new Segment();
ReflectionTestUtils.setField(segment, "value", "foo");
ReflectionTestUtils.setField(segment, "description", "bar");
segmentJpaRepository.save(segment);
assertNotNull(segment.getId());
assertEquals("foo", segment.getValue());
assertEquals("bar",segment.getDescription());
ReflectionTestUtils.setField(segment, "value", "foo2");
entityManager.merge(segment);
Segment updatedSegment = segmentJpaRepository.findOne(segment.getId());
assertEquals("foo2", updatedSegment.getValue());
}
}
Related
the JPA optimistic locking doesn't throw an OptimisticLockException/StaleStateException where i would expect it.
Here is my setup:
i am using spring boot with spring data envers. So my repository are versioned, which should not influence the optimistic locking behaviour. In my entities the property version (Long) is annotated with #Version. My application consists of 3 layers:
persistence-layer
business-layer
transfer-layer
To map objects between the layers i use mapstruct.
When a request is received by the controller in the transfer-layer, the JSON-Payload is mapped to an business-layer object to process business rules to it. The version is always mapped through the whole lifecycle.
When i reach the persistence-layer, i use the ID of the object to find the corresponding entity in my database. The signature of my save-method looks like this:
#Transactional
public Entity saveEntity(BOEntity boEntity){
Entity e = entityRepository.findById(boEntity.getId());
entityMapper.updateEntity(boEntity, e);
entityRepository.save(e);
}
When the same entity is loaded by my clients, (e.g. two browser-tabs) each of them has the same version of the entity. Changes are made and saved in both clients.
The version is contained in the boEntity object and mapped into the entity.
Due to the findById call the entity is managed. The entitymanager will try to merge the entity and succeeds in both requests to do so.
The state of the entity of the first request is merged (with version 1). Hibernate calls the executeUpdate method and writes to the database. The version is increased to 2.
Now the second request delivers the entity in the former state with version 1. The save-method is called and the entity is retrieved from the persistence-context. It has the version 2, which is overwritten by the boEntity object with version 1.
When the entityManager now merges the entity, no exception is thrown.
My expectation is the second request to fail because of an old version.
Isn't it possible to overwrite the version of the entity?
I already read a lot of blog entries, but couldn't find any hint to do the trick.
The default JPA optimistic locking mechanism only works when a managed object is flushed but was changed in the meantime. What you want has to be coded manually. Just add the logic to your saveEntity method:
#Transactional
public Entity saveEntity(BOEntity boEntity){
Entity e = entityRepository.findById(boEntity.getId());
if (boEntity.getVersion() != e.getVersion()) {
throw new OptimisticLockException();
}
entityMapper.updateEntity(boEntity, e);
entityRepository.save(e);
}
I use spring data and hibernate. I have an Entity (TestEntity). I made a custom hibernate type that deserializes one String field to two columns.
If I persist an entity and then change it everything works fine and hibernate sends update query (it makes my type work and update query to DB "splits" my old column to two new).
But my goal is to make this king of migration for every record. I can't use an ordinary DB migration because there is some logic in my custom type.
I want to make something like this:
// here I persist all my entities
List<TestEntity> entities = entityRepository.findAll();
for (TestEntity entity : entities) {
// This piece of code does nothing, because when hibernate merges two entities, it understands, that nothing changed, so it won't send update query.
entityRepository.save(entity);
}
But I want him to send update query, although nothing has changed. Moreover, I want this hibernate behaviour to be in one place only (for example, I will create controller to execute this DB update). What is a solution to my problem? Is there any approach to its solving?
I don't understand why you need it but you need to detach the entity from the session for this to work.
As far as I understand, you need the EntityManger:
#PersistenceContext
private EntityManager entityManager;
...
List<TestEntity> entities = entityRepository.findAll();
for (TestEntity entity : entities) {
entityManager.detach(entity);
entityRepository.save(entity); // or entityManager.unwrap(Session.class).saveOrUpdate();
}
See Spring JpaRepository - Detach and Attach entity
I'm going to become mad with JPA...
I have a JAX-WS Webservice like that
#WebService
public class MyService
{
#EJB private MyDbService myDbService;
...
System.out.println(dmrService.read());
...
}
My EJB contains
#Stateless
public class MyDbService
{
#PersistenceContext(unitName="mypu")
private EntityManager entityManager;
public MyEntity read()
{
MyEntity myEntity;
String queryString = "SELECT ... WHERE e.name = :type";
TypedQuery<MyEntity> query = entityManager.createQuery(queryString,MyEntity.class);
query.setParameter("type","xyz");
try
{
myEntity= query.getSingleResult();
}
catch (Exception e)
{
myEntity= null;
}
return myEntity;
}
In my persistence.xml the mypu has transaction-type="JTA" and a jta-data-source
If I call the webservice, it's working. The entity is retrieved from the db.
Now, using an external tool, I'm changing the value of one field in my record.
I'm calling the webservice again and ... the entity displayed contains the old value.
If I'm deploying again, or if I'm adding a entityManager.refresh(myEntity) after the request, I have the good value again.
In #MyTwoCents answer, Option 2 is to NOT use your 'external' tool for changes, use your application instead. Caching is of more use if your application knows about all the changes going on, or has some way of being informed of them. This is the better option, but only if your application can be the single access point for the data.
Forcing a refresh, via EntityManager.refresh() or through provider specific query hints on specific queries, or by invalidating the cache as described here https://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching#How_to_refresh_the_cache is another option. This forces JPA to go past the cache and access the database on the specific query. Problems with this are you must either know when the cache is stale and needs to be refreshed, or put it on queries that cannot tolerate stale data. If that is fairly frequent or on every query, then your application is going through all the work of maintaining a cache that isn't used.
The last option is to turn off the second level cache. This forces queries to always load entities into an EntityManager from the database data, not a second level cache. You reduce the risk of stale data (but not eliminate it, as the EntityManager is required to have its own first level cache for managed entities, representing a transactional cache), but at the cost of reloading and rebuilding entities, sometimes unnecessarily if they have been read before by other threads.
Which is best depends entirely on the application and its expected use cases.
Don't be mad its fine
Flow goes like this.
You fired a query saying where type="xyz"
Now Hibernate keeps this query or state in cache so that if you fire query again it will return same value if state is not changes.
Now you are updating detail from some external resource.
Hibernate doesnt have any clue about that
So when you fire query again it returns from catch
When you do refresh, hibernate gets detail from Database
Solution :
So you can either add refresh before calling get call
OR
Change the Table value using Hibernate methods in Application so that Hibernate is aware about changes.
OR
Disable Hibernate cache to query each time from DB (not recommended as it will slow down stuff)
I am developing a web application using JSF2, JPA2, EJB3 via JBoss7.1.
I have an Entity(Forum) which contains a list of child entities(Topic).
When I tried to get the list of Topics by forumId for the first time the data is being loaded from DB.
List<Topic> topics = entityManager.find(Forum.class, 1).getTopics();
After that I am adding few more child entities(Topics) to Forum and then again I am trying to retrieve list of Topics by forumId. Nut I am getting the old cached results only. The newly inserted child records are not being loaded from DB.
I am able to load the child entities(Topics) by using following methods:
Method1: Calling entityManager.clear() before entityManager.find()
Method2: Using
em.createQuery("select t from Topic t where t.forum.forumId=?1", Topic.class);
or
em.createQuery("SELECT t FROM Topic t JOIN t.forum f WHERE f.forumId = ?1", Topic.class);
I am aware of setting the QueryHints on NamedQueries. But em.find() method is in a super CrudService which is being extended by all DAOs(Stateless EJBs). So setting QueryHints won't work for me.
So I want to know how can i make em.find() method to load data from DB instead of Cache?
PS: I am using Extended Persistence Context type.
#PersistenceContext(unitName="forum", type=PersistenceContextType.EXTENDED)
protected EntityManager em;
You can specify the behavior of individual find operations by setting additional properties that control the entity managers interaction with the second level cache.
Map<String, Object> props = new HashMap<String, Object>();
props.put("javax.persistence.cache.retrieveMode", CacheRetrieveMode.BYPASS);
entityMgr.find(Forum.class, 1, props).getTopics();
Is it possible that the relation between Forum and Topic was only added in one direction in your entity beans? If you set the forum id on the topic, you should also add this topic to the Forum object to have consistent data inside the first level cache. You should also make sure that you are not using two different entity managers for the update and find. The first level cache is only kept per entity manager, another em can still contain an older version of the entitiy.
Probably unrelated, but with JPA2 you also have a minimal api to evict entities from the second level cache, which could be used after an update:
em.getEntityManagerFactory().getCache().evict(Forum.class, forumId);
Put #Cacheable(false) within the Forum.class.
I have problems updating entities in Googles App Engine.
EntityManager em = ... // constructed like in the doc
MyEntity myE = new MyEntity();
myE.setType("1"); // String
em.persist(myE);em.refresh(myE);
myE.setType("2");
em.merge(myE);em.refresh(myE);
I expect a entity with type="2", but there is only one entity with type="1" :-(
That's the correct behaviour, let me explain (I assume that all your code runs in the same persistence context / transaction).
# This line sets the value in the in-memory object without changing the database
myE.setType("2");
# this line doesn't do anything here, as the entity is already managed in the current
# persistence context. The important thing to note is that merge() doesn't save the
# entity to the DB.
em.merge(myE);
# This reloads the entity from the DB discarding all the in-memory changes.
em.refresh(myE);
It's because merge creates a new instance of your entity, copies the state from the supplied entity, and makes the new copy managed. You can find more info on merge vs. persist here and a full discussion about it here
I was facing similar issue too. My issue is solved after my put the Reresh() after Commit().
It would be something like:
em.getTransaction().begin();
//Code to update the entity
em.persist(myE);
em.getTransaction().commit();
em.refresh(myE)
This will ensure the updated entity in JPA Cache gets refreshed with the updated data.
Hope this helps.