2014-02-12 1 views
1

Я использую Spring-Data JPA и Spring-MVC с интерфейсом RESTful. Я пытаюсь реализовать базовый контроллер CRUD. Мне сложно понять, как реализовать «Обновление».Есть ли простой способ обновить объект Spring-Data с помощью Spring MVC & JSON

Мой основной метод управления является прямым:

@RequestMapping(method=RequestMethod.POST, value="updateUser", produces=MediaType.APPLICATION_JSON_VALUE) 
@ResponseBody 
public User update(@RequestBody final User user){ 
    userRepository.save(user); 
    return user; 
} 

Однако, это только кажется, чтобы обеспечить «создать» - не «обновление». Каждый раз, когда вызывается метод, в БД создается новый пользователь, даже если я укажу User PK в объекте JSON.

С быстрым взглядом на класс SimpleJpaRepository он создает новый объект, когда отсутствует поле «версия». Тем не менее, если я заставляю поле «версии», чтобы иметь значение, я получаю исключение (не удивительно):

Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.ia.domain.User#5] 
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303) 
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151) 
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76) 
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914) 
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:898) 
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:902) 
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:889) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:606) 

Я понимаю, что один из вариантов являются первым запросом к БД для существующего объекта пользователя (основанный на представленную PK), затем скопируйте все поля поверх и затем сохраните этот объект, но это не похоже на правильный путь. Я предполагаю, что для меня должен быть способ «объединить» мой объект User и просто обновить его, но я не совсем уверен, как это сделать.

Есть ли простой способ сделать это?

+0

Рассмотрите, что найди сначала, а затем обновите – Koitoer

+0

@Koitoer - Я могу определенно сначала выполнить поиск, а затем обновить, но тогда мне нужно скопировать все поля из объекта POSTED JSON, и я не уверен, есть ли у вас быстрый/легкий/эффективный способ сделать это. –

ответ

1

После дополнительных исследований и отладки и проб & ошибок, оказывается, решение очень простое. Значение поля version сохраняемого объекта должно соответствовать тому, что в настоящее время находится в БД. Если значения не совпадают с Hibernate, предполагается, что что-то еще обновило БД и, следовательно, выбрасывает org.hibernate.StaleObjectStateException.

Обеспечение того, чтобы отправляемый JSON имел соответствующее значение PK и версии в качестве строки в БД, тогда repo.save(user) обновит существующую строку. Если поле версии равно null, объект сохраняется как новая строка.

-1

Я считаю, что вы хотите использовать userRepository.merge (пользователь).

+0

Я думаю, что SimpleJpaRepository не имеет слияния, это не EnityManager – Koitoer

+0

Spring [JpaRepository] (http://docs.spring.io/spring-data/jpa/docs/1.4.3.RELEASE/api/org/springframework/ data/jpa/repository/JpaRepository.html) не имеет такого метода (хотя родной JPA/Hibernate do) – gerrytan

+0

Ах, я пропустил, что это SimpleJpaRepo. Это то, что я получаю для чтения слишком быстро и позволяю моему мозгу заполнить слишком много пробелов. –

0

Я не думаю, что вы должны «заставить поле версии иметь значение». Они там для оптимистической блокировки. Другая транзакция может изменить вашу запись, а версия, над которой вы работаете, устарела.

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

+0

Согласен; Я просто пытался определить, как определяются весенние данные, если это новый объект или нет. 'SimpleJpaRepository' проверяет, имеет ли поле версии значение null или нет. Если он равен нулю, то по определению он рассматривается как новый объект. Если не null, он пытается слить его. Учитывая, что 'spring-data' не имеет слияния как часть своих репозиториев, я не уверен, как я должен привязывать сущность к транзакции. –

1

отслеживания ошибок в коде спящем я нашел там проблема, связанная с версией

else if (isVersionChanged(entity, source, persister, target)) { 
299    if (source.getFactory().getStatistics().isStatisticsEnabled()) { 
300     source.getFactory().getStatisticsImplementor() 
301       .optimisticFailure(entityName); 
302    } 
303    throw new StaleObjectStateException(entityName, id); 
304   } 

/VersionChanges является

342  boolean changed = ! persister.getVersionType().isSame(
343    persister.getVersion(target), 
344    persister.getVersion(entity) 
345 ); 

ошибка исходит от другой версии объекта, Рассмотрите возможность использования для сохранения стандартного метода JPARepository.

@Transactional 
public <S extends T> S save(S entity) 

обновление Также ваш вопрос с кодом для userRepository, как я думаю, saveUser ваша собственная реализация

+0

Это было опечатка в моем первоначальном вопросе. Я отредактировал, чтобы отразить соответствующий вызов. Я также заметил, что если я установил версию в ту же самую версию в БД, то save() будет работать. Первоначально я пытался увеличить версию из JSON, которая фактически была частью процесса Hibernate. –