2013-06-21 5 views
0

У меня есть класс, который имеет следующие поля:JPA: какой-либо полный пример использования @Version для предотвращения одновременных изменений в одной записи?

@Version 
private Long version = 1L; 

public Long getVersion() { 
    return version; 
} 

public void setVersion(Long version) { 
    this.version = version; 
} 

Я использую Spring Web + Hibernate. Я надеюсь увидеть относительно полный пример использования @Version для предотвращения одновременных изменений в одной записи.

Поиск в Google и поиск SO. Не удалось найти какой-либо. Я использовал поле скрытой формы HTML для поля версии, и оно не работает.

Любая информация/указатель действительно оценена.

Спасибо и приветствую!

Update 1

Вот что я сделал. Я загрузил одну и ту же запись в два окна браузера. Я смог увидеть, что в HTML-форме обоих окон есть скрытое поле версии с тем же значением. Я отправил форму в одно окно и проверил базу данных и заметил, что поле «Версия» было увеличено. Затем я представил другое окно. Ничего не произошло, и запись была успешно обновлена ​​/ сохранена. Я ожидал, что какое-то исключение было брошено.

Нужно ли настраивать комбинацию весна/спящий режим, чтобы она работала, помимо определения поля Версии?

Update 2

я сделал следующее, чтобы проследить, как Hibernate обновить запись:

hibernate.show_sql=true 

log4j.logger.org.hibernate.type=trace 

Теперь я могу видеть, как Hibernate обновить запись. Я заметил, что в момент Hibernate обновления записи с помощью следующего SQL

update ... where id=? and version=? 

Hibernate всегда использует номер последней версии из базы данных, чтобы связать, хотя я могу подтвердить, что в веб-слой записи по-прежнему имеет старый номер версии плюс другие поля с новыми данными перед сохранением записи.

Почему?

Я чувствую, что оптимистическая блокировка как-то отключена в моем приложении, но для этого я не могу использовать конфигурацию Hibernate. Я использую Hibernate 4.2.1.Final и Spring 3.2.2.RELEASE.

Есть ли способ явного включения/отключения блокировки спящего режима спящего режима?

Обновление 3

Ниже приводится объекты данных и уровень базы данных кода. «friendGroupDao» автоматически подключается к уровню транзакционных услуг, где этот объект DAO вызывается для загрузки объекта на веб-уровень и сохраняет объект в базе данных. Связанные методы уровня обслуживания просто вызывают friendGroupDao.load() и friendGroupDao.save() без какого-либо дополнительного кода.

-------------- объекты домена -------------

@MappedSuperclass 
public abstract class BaseObject implements Serializable { 

    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id; 

    public Long getId() { 
     return id; 
    } 

    public void setId(Long id) { 
     this.id = id; 
    } 

    @Version 
    private Long version = 1L; 

    public Long getVersion() { 
     return version; 
    } 

    public void setVersion(Long version) { 
      this.version = version; 
    } 

    public BaseObject() { 
    } 

} 

@Entity 
public class FriendGroup extends BaseObject { 

    @Column(columnDefinition = "NVARCHAR(120)") 
    private String name; 

    public String getName() { 
     return name; 
    } 

    public void setName(String accountName) { 
     this.name = accountName; 
    } 
} 

--------------- database ------------- 

public interface BaseObjectDao<T extends BaseObject> { 

    void delete(T o); 

    T load(Long id); 

    void save(T o); 
} 

public class BaseObjectDaoImpl<T extends BaseObject> implements 
     BaseObjectDao<T> { 

    private Class<T> domainClass; 

    @Autowired 
    protected SessionFactory sessionFactory; 

    public BaseObjectDaoImpl() { 
     this.domainClass = (Class<T>) BaseObject.class; 
    } 

    public BaseObjectDaoImpl(Class<T> domainClass) { 
     this.domainClass = domainClass; 
    } 

    public SessionFactory getSessFactory() { 
     return sessionFactory; 
    } 

    public void setSessFactory(SessionFactory sf) { 
     this.sessionFactory = sf; 
    } 

    public void delete(T object) { 
     getSession().delete(object); 
    } 

    public T load(Long id) { 
     return (T) getSession().get(domainClass, id); 
    } 

    public void save(T object) {  
     getSession().saveOrUpdate(object); 
    } 

    public Session getSession() { 
     return sessionFactory.getCurrentSession(); 
    } 
} 

public interface FriendGroupDao extends BaseObjectDao<FriendGroup> { 
} 

@Repository("friendGroupDao") 
public class FriendGroupDaoImpl extends BaseObjectDaoImpl<FriendGroup> implements 
    FriendGroupDao { 

    public FriendGroupDaoImpl() { 
     super(FriendGroup.class); 
    } 
} 
+0

Что вы подразумеваете под словом «Это не работает»? Что вы ожидаете и что происходит? – andih

+1

Есть несколько вопросов или ответов, относящихся к аннотации '@ Version'. Например http://stackoverflow.com/questions/1838646/jpa-version-how-to-use-it или http://stackoverflow.com/questions/2572566/java-jpa-version-annotation – andih

+0

andih, я читаю первое сообщение до моего поста. Вторая статья полезна. Возможно, именно поэтому я сделал этот пост. Я не уверен, как это работает. – curious1

ответ

3

Вот пример того, как вы получите OptimisticLockException (код может быть записан в лучшем/более элегантный способ, но для демонстрационных целей я считаю, что это нормально)

package com.example.jpa; 


    import java.util.concurrent.Callable; 

    import javax.persistence.EntityManager; 
    import javax.persistence.EntityManagerFactory; 
    import javax.persistence.Persistence; 

    public class JPASample { 

     private EntityManager entityManager; 

     private Semaphore semaphore; 

     void run(String [] args) throws Exception { 
      EntityManagerFactory factory = Persistence.createEntityManagerFactory("JPASample"); 
      entityManager = factory.createEntityManager(); 

      try { 

       FirstEntity entity = createEntity(); 

       ReadValueCmd readValueCmd = new ReadValueCmd(entity.getId()); 
       entityManager.getTransaction().begin(); 
       FirstEntity e1 = readValueCmd.call(); 
       entityManager.getTransaction().commit(); 
       entityManager.detach(e1); 

       entityManager.getTransaction().begin(); 
       FirstEntity e2 = readValueCmd.call(); 
       entityManager.getTransaction().commit(); 
       entityManager.detach(e2); 

       e1.setValue(e1.getValue()+1); 
       e2.setValue(e1.getValue()+1); 

       UpdateEntityCmd updateCmd1 = new UpdateEntityCmd(e1); 
       entityManager.getTransaction().begin(); 
       updateCmd1.call(); 
       entityManager.getTransaction().commit(); 

       UpdateEntityCmd updateCmd2 = new UpdateEntityCmd(e2); 
       entityManager.getTransaction().begin(); 
       updateCmd2.call(); 
       entityManager.getTransaction().commit(); 



      } finally { 
       entityManager.close(); 
      }  
     } 

     public static void main(String[] args) throws Exception{ 
      new JPASample().run(args); 
     } 

     private class ReadValueCmd implements Callable<FirstEntity> { 

      private long id; 

      private ReadValueCmd(long id) { 
       this.id = id; 
      } 

      @Override 
      public FirstEntity call() throws Exception { 
       FirstEntity entity; 
       entity = entityManager.find(FirstEntity.class, id); 
       System.out.println("entity read: " + entity); 

       return entity; 
      } 

     } 

     private class UpdateEntityCmd implements Callable<FirstEntity> { 

      private FirstEntity entity; 

      private UpdateEntityCmd(FirstEntity entity) { 
       this.entity = entity; 
      } 

      @Override 
      public FirstEntity call() throws Exception { 
       entity = (FirstEntity)entityManager.merge(entity); 
       System.out.println("entity merged: " + entity); 

       return entity; 
      } 
     } 



    private FirstEntity createEntity() { 
     FirstEntity entity = new FirstEntity(); 
     entity.setName("initialName"); 
     entityManager.getTransaction().begin(); 
     entityManager.persist(entity); 
     entityManager.getTransaction().commit(); 

     return entity; 
    } 
    } 


package com.example.jpa; 

import java.io.Serializable; 
import java.math.BigInteger; 

import javax.persistence.*; 

@Entity 
public class FirstEntity implements Serializable { 


    private static final long serialVersionUID = 1L; 

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

    @Version 
    private long versionField; 

    private long value; 

    public long getId() { 
     return firstEntityId; 
    } 

    @Override 
    public String toString() { 
     return "FirstEntity [firstEntityId=" + firstEntityId 
       + ", versionField=" + versionField + ", value=" + value 
       + ", name=" + name + "]"; 
    } 

    public long getValue() { 
     return value; 
    } 

    public void setValue(long value) { 
     this.value = value; 
    } 

    private String name; 

    public FirstEntity() { 
     super(); 
    } 

    public void setName(String newName) { 
     name = newName; 
    } 

    public String getName() { 
     return name; 
    } 

    public long getVersionField() { 
     return versionField; 
    } 

    public void setVersionField(long versionField) { 
     this.versionField = versionField; 
    } 
} 

результатом является то, что, как

Исключение в потоке "основного" javax.persistence.OptimisticLockException на org.hibernate.ejb.AbstractEntityManagerImpl.wrapStaleStateException (AbstractEntityManagerImpl.java:1403) на org.hibernate.ejb.AbstractEntityManagerImpl.convert (AbstractEntityManagerImpl.java:1319) на org.hibernate.ejb.AbstractEntityManagerImpl.convert (AbstractEntityManagerImpl.java:1300) на org.hibernate.ejb.AbstractEntityManagerImpl.convert (AbstractEntityManagerImpl.java:1306) на org.hibernate.ejb.AbstractEntityManagerImpl.merge (AbstractEntityManagerImpl.java:888) at com.example.jpa.JPASample $ UpdateEntityCmd.call (JPASample.java:124) на com.example.jpa.JPASample.run (JPASample.java:47) на com.example.jpa.JPASample.main (JPASample.java:61) Вызвано: org.hibernate.StaleObjectStateException: строка обновлена ​​или удален другой транзакции (или отображение неспасенного-значение было неправильным): [com.example.jpa.FirstEntity # 1] на org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached (DefaultMergeEventListener.java:303) в org.hibernate.event.internal.DefaultMergeEventListener.onMerge (DefaultMergeEventListener.java:151) на org.hibernate.event.internal.DefaultMergeEventListener.onMerge (DefaultMergeEventListener.java:76) в org.hibernate.internal.SessionImpl.fireMerge (SessionImpl.java:903) в org.hibernate.internal.SessionImpl.merge (SessionImpl.java:887) при org.hibernate.internal.SessionImpl.merge (SessionImpl.java:891) в org.hibernate.ejb.AbstractEntityManagerImpl.merge (AbstractEntityManagerImpl. java: 879) ... 3