web-dev-qa-db-ja.com

ManyToMany関係で永続化するために渡されるSpringデータJPAおよびhibernateデタッチエンティティ

既に永続化されている他のオブジェクトと多対多の関係を持つオブジェクトを永続化しようとしています。

ここに私の永続化されたオブジェクトがあります(これらはMySqlであるdbに既に永続化されています):

製品

@Entity
@Table(name="PRODUCT")
public class Product {
    private int productId;
    private String productName;
    private Set<Reservation> reservations = new HashSet<Reservation>(0);

    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    public int getProductId() {
        return productId;
    }

    public void setProductId(int productId) {
        this.productId = productId;
    }

@Column(nullable = false)
    public String getProduct() {
        return product;
    }
    public void setProduct(String product) {
        this.product = product;
    }

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "products")
    public Set<Reservation> getReservations() {
        return reservations;
    }
    public void setReservations(Set<Reservation> reservations) {
        this.reservations = reservations;
    }
}

ここに私が作成しようとしている私の永続化されていないオブジェクトがあります

@Entity
@Table(name = "RESERVATION")
public class Reservation {

    private int reservationId;

    private Set<Product> products = new HashSet<Product>(0);

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getReservationId() {
        return reservationId;
    }

    public void setReservationId(int reservationId) {
        this.reservationId = reservationId;
    }

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "product_reservation", joinColumns = { @JoinColumn(name = "reservationId", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "productId", 
            nullable = false, updatable = false) })
    public Set<Product> getProducts() {
        return products;
    }

    public void setProducts(Set<Product> products) {
        this.products = products;
    }
}

これは私のReservationServiceクラスで、製品名の配列を受け取り、名前を使用して製品を検索し、それらを予約オブジェクトに入れます。

@Service
public class ReservationServiceImpl implements ReservationService {

    @Autowired
    private ProductDAO productDAO;
    @Autowired
    private ReservationDAO reservationDAO;

    @Transactional
    public void createReservation(String[] productNames) {

            Set<Product> products = new HashSet<Product>();
            for (String productName : productNames) {
                Product pi = productDAO.findByProductName(productName);
                products.add(pi);
            }
            Reservation reservation = new Reservation();
            reservation.setProducts(products);
            reservationDAO.save(reservation);   ---> Here I am getting detached entity passed to persist
    }
}

これが私のProductDAOインターフェースです:

public interface ProductDAO extends JpaRepository<Product, Integer> {

    public Product findByProductName(String productName);
}

これは私の春の設定ファイルです:

@Configuration
@PropertySource(value = { "classpath:base.properties" })
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.reservation.dao")
public class RepositoryConfig {

    @Autowired
    private Environment env;

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        EntityManagerFactory factory = entityManagerFactory().getObject();
        return new JpaTransactionManager(factory);
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(Boolean.valueOf(env
                .getProperty("hibernate.generate.ddl")));
        vendorAdapter.setShowSql(Boolean.valueOf(env
                .getProperty("hibernate.show_sql")));

        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.hbm2ddl.auto",
                env.getProperty("hibernate.hbm2ddl.auto"));
        jpaProperties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setDataSource(dataSource());
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.reservation.service.domain");
        factory.setJpaProperties(jpaProperties);
        factory.afterPropertiesSet();
        factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
        return factory;
    }

    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }

    @Bean
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.username"));
        dataSource.setPassword(env.getProperty("jdbc.password"));
        return dataSource;
    }
}

完全なスタックトレースを次に示します。

重大:パス[/ web]を持つコンテキストのサーブレット[dispatcher]のServlet.service()は例外をスローしました[リクエスト処理に失敗しました。ネストされた例外はorg.springframework.dao.InvalidDataAccessApiUsageException:永続化に渡された分離されたエンティティ:com.reservation.service.domain.Product;ネストされた例外はorg.hibernate.PersistentObjectException:永続化に渡された分離されたエンティティ:com.reservation.service.domain.Product]であり、根本原因org.hibernate.PersistentObjectException:永続化に渡された分離されたエンティティ:com.reservation.service.domain.Product org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.Java:141)

26
nspessot

同じ問題が発生したため、_cascade = CascadeType.PERSIST_を削除して解決しました。

あなたのケースでは、CascadeType.ALL_を使用します。これは、ドキュメントによると、PERSISTを使用するのと同等です。

関連するエンティティに伝播される一連のカスケード可能な操作を定義します。値cascade = ALLはcascade = {PERSIST、MERGE、REMOVE、REFRESH、DETACH}と同等です。

つまり、reservationDAO.save(reservation)で予約を保存しようとすると、関連するProductオブジェクトも永続化しようとします。ただし、このオブジェクトはこのセッションにアタッチされていません。そのため、エラーが発生します。

49
Renato Borges

例外は、予約を保存するときに休止状態が関連製品を保持しようとすることです。製品のIDに注釈が付けられているため、IDがない場合にのみ製品の永続化が成功します。

_@GeneratedValue(strategy=GenerationType.AUTO)
_

ただし、リポジトリから製品を取得したため、IDはnullではありません。

問題を解決するための2つのオプションがあります。

  1. 予約の製品の_(cascade = CascadeType.ALL)_を削除します
  2. または、製品のIDで@GeneratedValue(strategy=GenerationType.AUTO)を削除します
23
My Pham

関係の両側がコード内で適切に維持されるようにする必要があります。

以下のように予約を更新してから、対応するメソッドを製品に追加します。

@Entity
@Table(name = "RESERVATION")
public class Reservation {

    private int reservationId;

    private Set<Product> products = new HashSet<Product>(0);

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getReservationId() {
        return reservationId;
    }

    public void setReservationId(int reservationId) {
        this.reservationId = reservationId;
    }

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "product_reservation", joinColumns = { @JoinColumn(name = "reservationId", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "productId", 
            nullable = false, updatable = false) })
    public Set<Product> getProducts() {

        //force clients through our add and remove methods
        return Collections.unmodifiableSet(products);
    }

    public void addProduct(Product product){

        //avoid circular calls : assumes equals and hashcode implemented
        if(! products.contains(product){
            products.add(product);

            //add method to Product : sets 'other side' of association
            product.addReservation(this);
        }
    }

    public void removeProduct(Product product){

        //avoid circular calls: assumes equals and hashcode implemented: 
        if(product.contains(product){
            products.remove(product);

            //add method to Product: set 'other side' of association: 
            product.removeReservation(this);
        }
    }
}

そして製品:

public void addReservation(Reservation reservation){

    //assumes equals and hashcode implemented: avoid circular calls
    if(! reservations.contains(reservation){
        reservations.add(reservation);

        //add method to Product : sets 'other side' of association
        reservation.addProduct(this);
    }
}

public void removeReservation(Reservation reservation){

    //assumes equals and hashcode implemented: avoid circular calls
    if(! reservations.contains(reservation){
        reservations.remove(reservation);

        //add method to Product : sets 'other side' of association
        reservation.reomveProduct(this);
    }
}

これで、製品または予約のいずれかでsaveを呼び出すことができ、すべてが期待どおりに機能するはずです。

2
Alan Hay

@ManytoMany関係の@ CascadeType.ALLを削除してください。これは私にとってはうまくいきました。

1
karthik yadav

entityManager.merge()は良いオプションです。セッション内の切り離されたオブジェクトをマージします。 cascadeTypeを変更する必要はありません。

1
Pragyan Chand

私はあなたの注釈が少し間違っていると感じています。それほどではありませんが、 例7.24ここ を見て、それが注釈と一致するかどうかを確認してください。ただし、Collectionを使用しても問題はないため、Setデータ型は無視してください。あなたはcascade=CascadeType.ALLProductコレクションにありますが、それが問題かどうかは判断できません。

実際の例外は、Productのコレクションを保存しようとしても、Productオブジェクトが実際に保存されていないということです。それがあなたの注釈に問題があると思う理由です。

それを試してみて、どこに行くか教えてください。

0
JamesENL