복붙노트

[SPRING] 업데이트 대신 Spring JPA / Hibernate 트랜잭션 강제 삽입

SPRING

업데이트 대신 Spring JPA / Hibernate 트랜잭션 강제 삽입

수정 됨. 기본 리포지토리 클래스를 확장하고 삽입 메서드를 추가하면보다 우아한 솔루션이 엔티티에 지속성을 구현하는 것처럼 보입니다. 가능한 해결책 2를 참조하십시오.

JfaTransactionManager를 사용하여 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 메소드를 작성할 수 있습니다. 일부 엔티티는 기본 Spring FindOne (ID id) 메소드를 사용할 수 없으므로 OrderLinePK 객체와 복합 기본 키를 가지고 있습니다.

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.3.2 장을 기반으로 http://docs.spring.io/spring-data/jpa/docs/1.4.1.RELEASE/reference/html/repositories.html

삽입하기 전에 행의 존재를 확인하기 위해 추가 검색을 수행하는 것처럼 가장 효율적인 것은 아니지만 기본 키입니다.

저장할뿐만 아니라 삽입 메소드를 추가하기 위해 저장소를 확장하십시오. 이것은 첫 번째 상처입니다.

엔티티뿐만 아니라 삽입물에 키를 전달해야합니다. 이 문제를 피할 수 있습니까?

실제로 데이터가 반환되기를 원하지 않습니다. entitymanager에는 존재하는 메소드가 없습니다 (행 존재를 확인하기 위해 카운트 (*)가 있습니까?)

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"));
    }    

}

사용자 정의 저장소 팩토리 빈

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)

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 ()를 구현해야합니다.

import java.io.Serializable; import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table; import 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 + " ]";
    }



}

New ()가 추상화 된 것은 좋지만 할 수 있다고는 생각하지 않습니다. getId는 엔티티가 다른 ID를 가질 수 없습니다.이 ID는 복합 PK를가집니다.

해결법

  1. ==============================

    1.전에는 그렇게 해본 적이 없지만 조금 해킹했을 때 아마 그 일을 할 수있을 것입니다.

    전에는 그렇게 해본 적이 없지만 조금 해킹했을 때 아마 그 일을 할 수있을 것입니다.

    엔티티에 대한 Persistable 인터페이스가 있습니다. 그것은 메소드가 boolean isNew () 메소드를 가지며, 구현시 엔티티가 데이터베이스에 신규인지 아닌지를 "평가"하는 데 사용됩니다. 그 결정을 기본으로 EntityManager는 Repository에서 .save ()를 호출 한 후에 해당 엔티티에서 .merge () 또는 .persist ()를 호출해야합니다.

    그런 식으로 진행하면 isNew ()를 구현하여 항상 true를 반환하면 .persist ()가 호출되지 않아야하고 오류가 발생해야합니다.

    틀 렸으면 말해줘. 불행히도 현재 라이브 코드에서 테스트 할 수 없습니다.

    지속성에 대한 설명서 : http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

  2. ==============================

    2.기본 키를 제외한 모든 항목을 복제 한 다음 복제 된 개체를 저장하는 복제 개체를 만들지 마십시오.

    기본 키를 제외한 모든 항목을 복제 한 다음 복제 된 개체를 저장하는 복제 개체를 만들지 마십시오.

    PK가 없으므로 업데이트 대신 삽입이 발생합니다.

  3. ==============================

    3.이게 도움이 되나요?

    이게 도움이 되나요?

    PK 컬럼 정의에 대해 updatable = false를 설정하십시오. 예:

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

    ID를 업데이트 할 수 없게 설정하면 JPA가 기본 키에 대한 업데이트를 중단하게됩니다.

  4. from https://stackoverflow.com/questions/37253175/spring-jpa-hibernate-transaction-force-insert-instead-of-update by cc-by-sa and MIT license