Spring JpaRepository - отсоединить и присоединить сущность
Я использую весеннюю загрузку и спящий режим над jpa. Я использую интерфейс JpaRepository для реализации своих репозиториев. Как и в следующем UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
}
Я хочу добиться следующего
- Загрузите сущность пользователя.
- Изменить состояние объекта сущности, например, user.setName("foo")
- Выполните вызов веб-службы внешней системы. Сохранить результат звонка в БД
- Только после успешного ответа на этот вызов веб-службы, сохраните новое состояние пользователя в хранилище.
Все вышеперечисленные шаги не выполняются в одной транзакции, т. Е. Внешний сервисный вызов вне транзакции.
Когда я сохраняю свой результат веб-сервиса в БД через его репозиторий, мои изменения в сущности пользователя также сохраняются. Насколько я понимаю, это связано с очисткой нижележащего контекста персистентности на шаге № 3. После некоторого Google, я думаю, я смогу достичь своей цели, если смогу отсоединить свою пользовательскую сущность на шаге один и заново подключить ее на шаге 4. Пожалуйста, подтвердите если мое понимание верно и как я могу этого достичь? В интерфейсе JpaRepository нет способа отсоединить сущность.
Ниже приведен код для иллюстрации
public void updateUser(int id, String name, int changeReqId){
User mUser = userRepository.findOne(id); //1
mUser.setName(name); //2
ChangeRequest cr = changeRequestRepository.findOne(changeReqId);
ChangeResponse rs = userWebService.updateDetails(mUser); //3
if(rs.isAccepted()){
userRepository.saveAndFlush(mUser); //4
}
cr.setResponseCode(rs.getCode());
changeRequestRepository.saveAndFlush(cr); //this call also saves the changes at step 2
}
Спасибо
4 ответа
Если вы используете JPA 2.0, вы можете использовать EntityManager#detach(), чтобы отделить одну сущность от контекста постоянства. Кроме того, Hibernate имеет Session#evict(), который служит той же цели.
поскольку JpaRepository
не предоставляет саму эту функциональность, вы можете добавить к ней собственную реализацию, что-то вроде этого
public interface UserRepositoryCustom {
...
void detachUser(User u);
...
}
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
...
}
@Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
...
@PersistenceContext
private EntityManager entityManager;
@Override
public void detachUser(User u) {
entityManager.detach(u);
}
...
}
Я не пробовал этот код, но вы должны быть в состоянии заставить его работать. Вы могли бы даже попытаться ухватиться за EntityManager
в вашем классе обслуживания (где updateUser()
это с @PersistenceContext
и избегайте суматохи добавления пользовательской реализации в хранилище.
Использование пользовательской реализации, как предлагает @Predrag Maric, явно является правильным ответом на этот вопрос. Тем не менее, я считаю, что выполнять отсоединение на уровне службы намного лучше, поскольку обычно он знает, должен ли объект быть отсоединен или нет.
Просто подключите его с помощью
@Service
class ConsumerServiceImpl {
@PersistenceContext
private EntityManager entityManager
...
entityManager.detach(en)
entityManager.clear()
отключит все объекты JPA, так что это может быть не подходящим решением во всех случаях, если у вас есть другие объекты, которые вы планируете поддерживать на связи.
Чисто
/**
* Clear the persistence context, causing all managed
* entities to become detached. Changes made to entities that
* have not been flushed to the database will not be
* persisted.
*/
public void clear();
entityManager.detach(entity);
Удалить данную сущность из контекста постоянства
отрывать
/**
* Remove the given entity from the persistence context, causing
* a managed entity to become detached. Unflushed changes made
* to the entity if any (including removal of the entity),
* will not be synchronized to the database. Entities which
* previously referenced the detached entity will continue to
* reference it.
* @param entity entity instance
* @throws IllegalArgumentException if the instance is not an
* entity
* @since Java Persistence 2.0
*/
public void detach(Object entity);
Хотя принятый ответ Предрага Марика верен и будет работать, я нашел его не очень гибким, когда вы хотите добавить такую функцию во все интерфейсы вашего репозитория, поэтому я использую следующий подход с моим настраиваемым компонентом фабрики репозитория:
- Создайте промежуточный интерфейс для функции отсоединения:
@NoRepositoryBean // this annotation is important here if the package you are putting this interface in is scanned for repositories (is in basePackage)
public interface DetachableJpaRepository<T, TID> extends JpaRepository<T, TID> { // you can also extend other repositories here, like JpaSpecificationExecutor
void detach(T entity);
}
- Создайте реализацию промежуточного интерфейса:
public class DetachableJpaRepositoryImpl<T, TID> extends SimpleJpaRepository<T, TID> implements DetachableJpaRepository<T, TID> {
private final EntityManager entityManager;
public DetachableJpaRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
@Override
public void detach(T entity) {
entityManager.detach(entity);
}
}
- Создайте свою собственную фабрику репозиториев:
public class DetachableJpaRepositoryFactory<T, TID> extends JpaRepositoryFactory {
public DetachableJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
@Override
protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
return new DetachableJpaRepositoryImpl<T, TID>((Class<T>) information.getDomainType(), entityManager);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return DetachableJpaRepository.class;
}
}
- Создайте собственный фабричный компонент репозитория, чтобы объявить использование фабрики, которую вы создали выше:
public class DetachableJpaRepositoryFactoryBean<R extends JpaRepositoryImplementation<T, TID>, T, TID> extends JpaRepositoryFactoryBean<R, T, TID> {
public DetachableJpaRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new DetachableJpaRepositoryFactory<T, TID>(entityManager);
}
}
- Объявите пользовательский фабричный bean-компонент для конфигурации spring:
@EnableJpaRepositories(repositoryFactoryBeanClass = DetachableJpaRepositoryFactoryBean.class)
public class MyApplication {...}
- Теперь вы можете использовать новый промежуточный интерфейс в своих репозиториях:
@Repository
public interface UserRepository extends DetachableJpaRepository<User, Long> {
}
- И
detach
метод доступен для вас в этих репозиториях:
@Autowired
private UserRepository userRepository;
...
userRepository.detach(user);
Я нашел этот подход очень гибким, в том числе для добавления других функций в ваши пользовательские репозитории.
Кроме того, я не согласен с ответом Chayne PS, imo вы не должны использовать диспетчер сущностей на сервисном уровне, это ответственность уровня dao за управление состоянием объектов, то, что Chayne P.S предлагает, является плохим дизайном imo, сервисный уровень не должен знать об этом менеджер сущности.