Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JpaRepository caches newly created object. How to refresh it?

I have a JpaRepository persisting newly created entity in Spring MVC app. This entity looks like this (very simplified):

@Entity public class Translation {      .....      @Id     @GeneratedValue(strategy = GenerationType.AUTO)     private long id;      @ManyToOne(fetch = FetchType.LAZY)     private Version version;      ....  } 

and Version entity:

@Entity public class Version {      @Id     @GeneratedValue(strategy = GenerationType.AUTO)     @Column(name = "id")     private long id;      @Column(name = "name")     private String name;      @Column(name = "version_code")     private long code;      @OneToMany(fetch = FetchType.LAZY, mappedBy = "version", cascade = {CascadeType.ALL}, orphanRemoval = true)     private Set<Translation> translations;  } 

I create a translation object like this

            TranslationDTO t = new TranslationDTO();             t.setText(translationText);             ClientVersionDTO version = new ClientVersionDTO();             version.setId(11);             t.setVersion(version); 

where 11 is a version that exists in the database already from the very beginning. Please notice that I do not set values for name and code of ClientVersionDTO.

Then I have a service that persists new object (I use dozer library to convert DTO to entities)

@Service @Transactional public class TranslationsServiceImpl implements TranslationsService {     @Override     public Long create(TranslationDTO translationDTO) {         Translation translation = translationsConverter.unconvert(translationDTO);         Translation t = translationRepository.saveAndFlush(translation);          Translation t2 = translationRepository.findOne(t.getId());          // !!!! t2.getVersion() returns version where no values are set to 'code' and 'name'          return t2.getId();     } } 

Please notice my comment "t2.getVersion() returns version where no values are set to 'code' and 'name'" - I was expecting so that when I fetch the data from the database, I would get a Version object right from the database with code and name values set. However they are not set. So basically what I get as a t2.getVersion() object is the same object as in input argument translationDTO.getVersion(). How can they I re-invalidate the Version object?

UPDATE tried moving @Transactional to JpaRepository, but still the same result.

like image 767
Sergei Ledvanov Avatar asked Jan 26 '15 08:01

Sergei Ledvanov


People also ask

What does flush do in JpaRepository?

Unlike save(), the saveAndFlush() method flushes the data immediately during the execution. This method belongs to the JpaRepository interface of Spring Data JPA.

What does EntityManager refresh do?

refresh. Refresh the state of the instance from the database, using the specified properties, and overwriting changes made to the entity, if any.

What is JPA refresh?

JPA - Refreshing an Entity Instance By invoking EntityManager#refresh(entity) , we can synchronize the current persistence context to the underlying database. In other words, by invoking this method, we can reload the state of a managed entity instance from the database.


1 Answers

If you are using Hibernate, this is the expected result. When you call translationRepository.saveAndFlush(translation) and translationRepository.findOne(t.getId()) one after the other, they hit the same Hibernate session which maintains a cache of all objects that it has worked on. Therefore, the second call simply returns the object passed to the first. There is nothing in those two lines that would have forced Hibernate to fire a SELECT query on the database for the Version entity.

Now, the JPA spec does have a refresh method on the EntityManager interface. Unfortunately, Spring Data JPA does not expose this method using its JpaRepository interface. If this method was available, you could have done t = translationRepository.saveAndFlush(translation) and then versionRepository.refresh(t.getVersion()) to force the JPA provider to synchronize the version entity with the database.

Implementing this method is not difficult. Just extend SimpleJpaRepository class from Spring Data JPA and implement the method yourself. For details see adding custom behaviour to all Spring Data JPA repositories.

An alternate would be to load the version entity as versionRepository.findOne(version.getId()) before setting it on the translation. Since you can hard-code version id in your code, your versions seem to be static. You can therefore mark your Version entity as @Immutable and @Cacheable (the former is a Hibernate-specific annotation). That way, versionRepository.findOne(version.getId()) should not hit the database every time it is called.

like image 67
manish Avatar answered Oct 07 '22 15:10

manish