Есть ли у вас @Transactional
аннотация для самого метода или класса обслуживания?
Это может объяснить наблюдаемое поведение.
Когда метод выполняется в транзакции, объекты приобретаются или объединяются/ сохраненные из / в базу данных кэшируются до конца транзакции (обычно это конец метода). Это означает, что любой вызов сущности с таким же идентификатором будет возвращен непосредственно из кэша и не попадет в базу данных.
Вот несколько статей о кэшировании и прокси Hibernate:
. Обратитесь к вашему примеру:
- сначала вызовите
findById(id)
, а затем getOne(id)
возвращает ту же сущностьобъект для обоих - сначала вызов
getOne(id)
, а затем findById(id)
возвращает один и тот же прокси для обоих
Это потому, что они используют один и тот же id
и выполняются в одной транзакции.
Документация по getOne()
гласит, что она может вернуть an instance
вместо ссылки (HibernateProxy), поэтому можно ожидать, что она возвращает объект:
T getOne (ID ID)
Возвращает ссылку на сущность с заданным идентификатором.
В зависимости от того, как реализован поставщик сохраняемости JPA, очень вероятно, что он всегда вернет экземпляр и вызовет исключение EntityNotFoundException при первом доступе. Некоторые из них сразу же отклонят недействительные идентификаторы.
Параметры: id - не должно быть нулевым.
Возвраты: ссылка на сущность с заданным идентификатором.
Документация по findById()
с другой стороны не имеет никаких указаний в том направлении, что она может вернуть что-либо, кромеOptional
сущности или пусто Optional
:
Необязательный findById (идентификатор id)
Извлекает объект по его идентификатору.
Параметры: id - mustне быть нулевым
Возвращает: сущность с заданным идентификатором или Необязательный # empty (), если ничего не найдено
Я потратил некоторое время на поиск лучшего объяснения, но не смог найти его, поэтомуЯ не уверен, является ли это ошибкой в реализации findById()
или просто (плохо) документированной функцией.
В качестве обходного пути к проблеме я мог бы предложить:
- Не приобретайте один и тот же объект дважды в одном и том же транзакционном методе. :)
- Избегайте использования
@Transactional
, когда не нужно. Транзакциями можно управлять и вручную. Вот несколько хороших статей на эту тему: - Отключение первой загруженной сущности / прокси-сервера перед (повторной) загрузкой другим способом:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
@Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.detach(getOne); // removes getOne from the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
Аналогично 3-му подходу, но вместо удаления одного объекта из кэша удалите все сразу, используя метод
clear()
:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
@Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.clear(); // clears the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
Статьи по теме:
РЕДАКТИРОВАТЬ:
Вот простой проект демонстрация проблемы или функции (в зависимости от точки зрения).