0

次のフィールドを持つクラスがあります。

@Version
private Long version = 1L;

public Long getVersion() {
    return version;
}

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

私はSpring Web + Hibernateを使用しています。@Version を使用して同じレコードへの同時編集を防止する比較的完全な例が見られることを願っています。

SOをグーグルで検索しました。何も見つかりませんでした。バージョン フィールドに非表示の HTML フォーム フィールドを使用しましたが、機能しません。

どんな情報/ポインタも本当に感謝しています。

よろしくお願いします!

更新 1

これが私がしたことです。2 つのブラウザ ウィンドウに同じレコードをロードしました。両方のウィンドウの HTML フォームに同じ値の非表示のバージョン フィールドがあることを確認できました。1 つのウィンドウでフォームを送信し、データベースを確認したところ、[バージョン] フィールドが増加していることに気付きました。次に、別のウィンドウを送信しました。何も起こらず、レコードは正常に更新/保存されました。何らかの例外がスローされることを期待していました。

バージョン フィールドの定義に加えて、Spring/Hibernate の組み合わせを機能させるように構成する必要がありますか?

更新 2

Hibernate がレコードを更新する方法を追跡するために、次のことを行いました。

hibernate.show_sql=true

log4j.logger.org.hibernate.type=trace

これで、Hibernate がレコードを更新する方法を確認できました。Hibernate が次の SQL でレコードを更新する瞬間に気付きました

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

Hibernate は常にデータベースの最新バージョン番号を使用してバインドしますが、レコードを保存する前に、Web レイヤーでレコードに古いバージョン番号と新しいデータを含む他のフィールドがまだ残っていることを確認できます。

どうして?

アプリで楽観的ロックが何らかの形で無効になっているように感じますが、このための Hibernate 構成は行いません。Hibernate 4.2.1.Final と Spring 3.2.2.RELEASE を使用しています。

Hibernate 楽観的ロックを有効/無効にする明示的な方法はありますか?

アップデート 3

以下は、データ オブジェクトとデータベース層のコードです。「friendGroupDao」は、この DAO オブジェクトが呼び出されて Web レイヤーにオブジェクトをロードし、オブジェクトをデータベースに保存するトランザクション サービス レイヤーに自動配線されます。関連するサービス レイヤー メソッドは、コードを追加せずに単に 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);
    }
}
4

1 に答える 1

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. org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1306) の ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1300) com.example の org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:888) .jpa.JPASample$UpdateEntityCmd.call(JPASample.java:124) at com.example.jpa.JPASample.run(JPASample.java:47) at com.example.jpa.JPASample.main(JPASample.java:61) by: org.hibernate.StaleObjectStateException:行が別のトランザクションによって更新または削除されました (または未保存値のマッピングが正しくありませんでした): [com.example.jpa.FirstEntity#1] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303) at 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 つ以上FirstEntity#1] org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303) org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151) org.hibernate.event.internal org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) の .DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76) org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887) の org.hibernate. internal.SessionImpl.merge(SessionImpl.java:891) at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) ... 3 詳細FirstEntity#1] org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303) org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151) org.hibernate.event.internal org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) の .DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76) org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887) の org.hibernate. internal.SessionImpl.merge(SessionImpl.java:891) at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) ... 3 詳細org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) で org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) で onMerge(DefaultMergeEventListener.java:76) .SessionImpl.merge(SessionImpl.java:887) at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891) at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) ... 3 以上org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) で org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903) で onMerge(DefaultMergeEventListener.java:76) .SessionImpl.merge(SessionImpl.java:887) at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891) at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) ... 3 以上ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) ... 3 moreejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879) ... 3 more

于 2013-06-22T09:44:00.963 に答える