问题
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 directly EntityManager
(or JdbcTemplate
, whatever is at a lower level than spring data) to update the table associated to my Entity
, with a native sql query. So I'm not using Entity
object, but simply doing a db update manually on the table I use as entity (is 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 table I need to update.
What happens is:
In a functional test I call "manual" update method (on table from which the mysql view is created) previously described (through entity-manager) AND
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?
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.
回答1:
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.
回答2:
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());
}
}
回答3:
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);
来源:https://stackoverflow.com/questions/33825429/spring-data-refresh-entity-after-manual-backend-query-update