web-dev-qa-db-ja.com

Spring JPA /更新の代わりにHibernateトランザクション強制挿入

編集。基本リポジトリクラスを拡張し、挿入メソッドを追加すると機能しますが、より洗練されたソリューションは、エンティティにPersistableを実装しているように見えます。 可能な解決策2を参照してください


JpaTransactionManagerを使用してORMとしてHibernateを使用して_springframework.data.jpa_を使用してサービスを作成しています。

ここのチュートリアルの基礎に従ってください。 http://www.petrikainulainen.net/spring-data-jpa-tutorial/

私のエンティティリポジトリは_org.springframework.data.repository.CrudRepository_を拡張します

自動生成されたIDではなく意味のある主キーを使用するレガシーデータベースを使用しています

このような状況は実際には発生しないはずですが、テストのバグが原因で発生しました。オーダーテーブルには、OrderNumber(M000001など)の意味のあるキーがあります。主キー値はコードで生成され、保存前にオブジェクトに割り当てられます。レガシーデータベースは、自動生成されたIDキーを使用しません。

新しい注文を作成しているトランザクションがあります。バグのため、私のコードはデータベースにすでに存在する注文番号を生成しました(M000001)

Repository.saveを実行すると、既存の注文が更新されました。私が欲しいのは、挿入を強制し、主キーが重複しているためにトランザクションを失敗させることです。

保存を実行する前に検索を実行し、行が存在する場合は失敗するすべてのリポジトリにInsertメソッドを作成できます。一部のエンティティにはOrderLinePKオブジェクトとの複合主キーがあるため、ベーススプリングのFindOne(ID id)メソッドを使用できません。

春のJPAでこれを行うクリーンな方法はありますか?

以前、spring/Hibernateと独自のベースリポジトリを使用して、jpaリポジトリなしでテストサービスを作成しました。次のようにInsertメソッドとSaveメソッドを実装しました。

これは問題なく機能しているようです。 getSession().saveOrUpdateを使用するsaveメソッドは、既存の行が更新されている現在の状況を示しています。

getSession().saveを使用した挿入メソッドが、必要に応じて重複した主キーで失敗しました。

_@Override
public Order save(Order bean) {

    getSession().saveOrUpdate(bean);
    return bean;
}

@Override
public Order insert(Order bean) {
    getSession().save(bean);
    return bean;
}
_

考えられる解決策1

ここにある春のドキュメントの第1.3.2章に基づいています http://docs.spring.io/spring-data/jpa/docs/1.4.1.RELEASE/reference/html/repositories.html

挿入する前に行の存在を確認するために追加の取得を行っているため、おそらく最も効率的ではありませんが、それは主キーです。

リポジトリを拡張して、保存に加えて挿入メソッドを追加します。これが最初のカットです。

キーをエンティティだけでなくインサートにも渡す必要があります。これを回避できますか?

実際にデータを返したくありません。 entitymanagerにはexistsメソッドがありません(existsは、行の存在を確認するためにcount(*)を実行するだけですか?)

_import Java.io.Serializable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

/**
 *
 * @author Martins
 */
@NoRepositoryBean
public interface IBaseRepository <T, ID extends Serializable> extends JpaRepository<T, ID> {

    void insert(T entity, ID id);    

}
_

実装:カスタムリポジトリの基本クラス。注:このルートをたどると、カスタム例外タイプが作成されます。

_import Java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;


public class BaseRepositoryImpl<T, ID extends Serializable> 
        extends SimpleJpaRepository<T, ID> implements IBaseRepository<T, ID> {

    private final EntityManager entityManager;

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.entityManager = em;
    }


    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super (entityInformation, entityManager);
        this.entityManager = entityManager;

    }

    @Transactional
    public void insert(T entity, ID id) {

        T exists = entityManager.find(this.getDomainClass(),id);

        if (exists == null) {
          entityManager.persist(entity);
        }
        else 
          throw(new IllegalStateException("duplicate"));
    }    

}
_

カスタムリポジトリファクトリBean

_import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import Java.io.Serializable;

/**
 * This factory bean replaces the default implementation of the repository interface 
 */
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
  extends JpaRepositoryFactoryBean<R, T, I> {

  protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {

    return new BaseRepositoryFactory(entityManager);
  }

  private static class BaseRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {

    private EntityManager entityManager;

    public BaseRepositoryFactory(EntityManager entityManager) {
      super(entityManager);

      this.entityManager = entityManager;
    }

    protected Object getTargetRepository(RepositoryMetadata metadata) {

      return new BaseRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), entityManager);
    }

    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {

      // The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory
      //to check for QueryDslJpaRepository's which is out of scope.
      return IBaseRepository.class;
    }
  }
}
_

最後に、構成内のカスタムリポジトリ基本クラスを接続します

_// Define this class as a Spring configuration class
@Configuration

// Enable Spring/jpa transaction management.
@EnableTransactionManagement

@EnableJpaRepositories(basePackages = {"com.savant.test.spring.donorservicejpa.dao.repository"}, 
        repositoryBaseClass = com.savant.test.spring.donorservicejpa.dao.repository.BaseRepositoryImpl.class)
_

考えられる解決策2

patrykos91 による提案に従って

エンティティのPersistableインターフェースを実装し、isNew()をオーバーライドします

永続化フラグを設定するためのコールバックメソッドを管理するための基本エンティティクラス

_import Java.io.Serializable;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;


@MappedSuperclass
public abstract class BaseEntity implements Serializable{

    protected transient boolean persisted;


    @PostLoad
    public void postLoad() {
        this.persisted = true;
    }

    @PostUpdate
    public void postUpdate() {
        this.persisted = true;
    }

    @PostPersist
    public void postPersist() {
        this.persisted = true;
    }

}
_

次に、各エンティティはisNew()およびgetID()を実装する必要があります

java.io.Serializableをインポートします。インポートjavax.persistence.Column;インポートjavax.persistence.EmbeddedId;インポートjavax.persistence.Entity;インポートjavax.persistence.Table;インポートjavax.xml.bind.annotation.XmlRootElement; import org.springframework.data.domain.Persistable;

_@Entity
@Table(name = "MTHSEQ")
@XmlRootElement

public class Sequence extends BaseEntity implements Serializable, Persistable<SequencePK> {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected SequencePK sequencePK;
    @Column(name = "NEXTSEQ")
    private Integer nextseq;

    public Sequence() {
    }


    @Override
    public boolean isNew() {
        return !persisted;
    }

    @Override
    public SequencePK getId() {
        return this.sequencePK;
    }



    public Sequence(SequencePK sequencePK) {
        this.sequencePK = sequencePK;
    }

    public Sequence(String mthkey, Character centre) {
        this.sequencePK = new SequencePK(mthkey, centre);
    }

    public SequencePK getSequencePK() {
        return sequencePK;
    }

    public void setSequencePK(SequencePK sequencePK) {
        this.sequencePK = sequencePK;
    }

    public Integer getNextseq() {
        return nextseq;
    }

    public void setNextseq(Integer nextseq) {
        this.nextseq = nextseq;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (sequencePK != null ? sequencePK.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Sequence)) {
            return false;
        }
        Sequence other = (Sequence) object;
        if ((this.sequencePK == null && other.sequencePK != null) || (this.sequencePK != null && !this.sequencePK.equals(other.sequencePK))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.savant.test.spring.donorservice.core.entity.Sequence[ sequencePK=" + sequencePK + " ]";
    }



}
_

IsNew()を抽象化するのは良いことですが、私にはできないと思います。エンティティが異なるIDを持っているため、getIdはできません。これは、複合PKを持っていることがわかります。

8
MartinS

私は以前にそれをしたことはありませんが、少しハックすればおそらくその仕事をするでしょう。

エンティティにはPersistableインターフェースがあります。メソッドboolean isNew()があり、実装すると、エンティティがデータベースに新しいかどうかを「評価」するために使用されます。その決定に基づいて、EntityManagerは、Repositoryから.merge()を呼び出した後、そのエンティティで.persist()または.save()を呼び出すことを決定する必要があります。

そのように、isNew()を実装して常にtrueを返す場合、.persist()は何と呼ばれるべきではなく、後にエラーがスローされます。

私が間違っている場合は私を訂正してください。残念ながら、現在ライブコードでテストすることはできません。

Persistableに関するドキュメント: http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

2
patrykos91

主キー以外のすべてを複製する複製オブジェクトを作成して、この複製されたオブジェクトを保存してみませんか。

PKが存在しないため、更新ではなく挿入が発生します

1

これは役に立ちますか?

セットする updatable = falsePK列の定義。例:

@Id
@GeneratedValue
@Column(name = “id”, updatable = false, nullable = false)
private Long id;

IDを更新不可に設定すると、JPAが主キーの更新を実行できなくなります。

0
Daniel C.