JpaRepository кэширует вновь созданный объект. Как его обновить?

У меня есть JpaRepository, сохраняющийся вновь созданный объект в приложении Spring MVC. Этот объект выглядит так (очень упрощен):

@Entity
public class Translation {

    .....

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Version version;

    ....

}

и объект Version:

@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;

}

Я создаю объект перевода, подобный этому

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

где 11 - это версия, которая существует в базе данных уже с самого начала. Обратите внимание, что я не устанавливаю значения для name и code ClientVersionDTO.

Затем у меня есть служба, которая сохраняет новый объект (я использую библиотеку dozer для преобразования DTO в объекты)

@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();
    }
}

Обратите внимание на мой комментарий: "t2.getVersion() возвращает версию, где значения не заданы" code "и" name ". - Я ожидал, что когда я извлечу данные из базы данных, я получаю a Version прямо из базы данных с установленными значениями code и name. Однако они не установлены. Таким образом, в основном то, что я получаю как объект t2.getVersion(), - это тот же объект, что и входной аргумент translationDTO.getVersion(). Как они могут повторно аннулировать объект Version?

UPDATE попытался переместить @Transactional в JpaRepository, но все тот же результат.

Ответы

Ответ 1

Если вы используете Hibernate, это ожидаемый результат. Когда вы вызываете translationRepository.saveAndFlush(translation) и translationRepository.findOne(t.getId()) один за другим, они попадают в тот же сеанс Hibernate, который поддерживает кеш всех объектов, над которыми он работал. Поэтому второй вызов просто возвращает объект, переданный в первый. В этих двух строках нет ничего, что вынудило Hibernate запускать запрос SELECT в базе данных для объекта Version.

Теперь спецификация JPA имеет метод refresh на интерфейсе EntityManager. К сожалению, Spring Data JPA не предоставляет этот метод, используя его интерфейс JpaRepository. Если бы этот метод был доступен, вы могли бы сделать t = translationRepository.saveAndFlush(translation), а затем versionRepository.refresh(t.getVersion()), чтобы заставить поставщика JPA синхронизировать объект версии с базой данных.

Реализация этого метода не составляет труда. Просто добавьте класс SimpleJpaRepository из Spring Data JPA и реализуйте метод самостоятельно. Подробнее см. добавление пользовательского поведения ко всем хранилищам данных Spring Data JPA.

Альтернативой было бы загрузить объект версии как versionRepository.findOne(version.getId()), прежде чем устанавливать его на перевод. Поскольку в коде вы можете указать код версии с жестким кодом, ваши версии кажутся статическими. Поэтому вы можете пометить свой объект Version как @Immutable и @Cacheable (первый - аннотация для гиберната). Таким образом, versionRepository.findOne(version.getId()) не должен попадать в базу данных каждый раз, когда он вызывается.