web-dev-qa-db-ja.com

親と子を一度に保存する方法(JPAおよびHibernate)

私のシナリオをお見せします。

これは私の親オブジェクトです:

@Entity
@Table(name="cart")
public class Cart implements Serializable{  

    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Id
    @Column(name="id")
    private Integer id; 

    @OneToMany(mappedBy="cart", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<CartItem> cartItems; 

    ...
}

これは私の子オブジェクトです:

@Entity
@Table(name="cart_item")
public class CartItem implements Serializable{  

    @GeneratedValue(strategy=GenerationType.IDENTITY)   
    @Id
    @Column(name="id")
    private Integer id;     

    @ManyToOne
    @JoinColumn(name="cart_id", nullable=false)
    private Cart cart;

    ...
}

データベースを見るとわかるように、テーブルcart_item(子オブジェクト)のフィールドcart_idには、フィールドへの外部キーid ofテーブルcart(親オブジェクト)。

enter image description here

これは私がオブジェクトを保存する方法です:

1)JSONオブジェクトを読み取るrestControllerがあります。

@RestController
@RequestMapping(value = "rest/cart")
public class CartRestController {

    @Autowired
    private CartService cartService;    

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.CREATED)
    public void create(@RequestBody CartDto cartDto) {
        cartService.create(cartDto);
    }
}

2)これはCartServiceです。これは単なるInterfaceです。

public interface CartService {  
    void create(CartDto cartDto); 
}

これはCartServiceの実装です:

import org.springframework.transaction.annotation.Transactional;

    @Service
    @Transactional
    public class CartServiceImpl implements CartService {   
        @Autowired
        private CartDao cartDao;

        @Override
        public void create(CartDto cartDto) {
            cartDao.create(cartDto);
        }
    }

CartDaoは別のインターフェースであり、その実装のみを示します。

@Repository
public class CartDaoImpl implements CartDao {

    @Autowired 
    private SessionFactory sessionFactory;

    // in this method I save the parent and its children
    @Override
    public void create(CartDto cartDto) {       

        Cart cart = new Cart(); 

        List<CartItem> cartItems = new ArrayList<>();                   

        cartDto.getCartItems().stream().forEach(cartItemDto ->{     
            //here I fill the CartItem objects;     
            CartItem cartItem = new CartItem();         
            ... 
            cartItem.setCart(cart);
            cartItems.add(cartItem);                
        });
        cart.setCartItems(cartItems);

        sessionFactory.getCurrentSession().save(cart);                  
    }
}

新しいcartおよびそのcart_item sを保存しようとすると、次のエラーが発生します。

SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/webstore] threw 
exception [Request processing failed; nested exception is 
org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException: Object of 
class     
[com.depasmatte.webstore.domain.CartItem] with identifier [7]: optimistic locking failed; 
nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by 
another transaction (or unsaved-value mapping was incorrect) : 
[com.depasmatte.webstore.domain.CartItem#7]] with root cause
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction
 (or unsaved-value mapping was incorrect) : [com.depasmatte.webstore.domain.CartItem#7]

エラーはHibernateがa cart_itemを保存しようとしたときにidcartの事実に依存すると思います=まだ存在しません!

親オブジェクトとその子をオンショットで保存する正しい方法は何ですか?ありがとうございました

5
MDP

これがルールのリストです。親エンティティとその子をワンショットで格納できるようにするために、従う必要があります。

  • カスケードタイプPERSISTを有効にする必要があります(_CascadeType.ALL_も問題ありません)
  • a 双方向関係両側に正しく設定する必要があります。例えば。親のコレクションフィールドにはすべての子が含まれ、各子にはその親への参照があります。
  • データ操作はトランザクションのスコープ内で実行されます。 オートコミットモードなしIS許可されています。
  • 親エンティティのみを保存する必要があります手動(カスケードモードのため、子は自動的に保存されます)

マッピングの問題:

  • 両方のエンティティから@Column(name="id")を削除します
  • make setter for cartItemsprivate。 HibernateはListの独自の実装を使用しているため、セッターを介して直接変更しないでください
  • リストを初期化するprivate List<CartItem> cartItems = new ArrayList<>();
  • _nullable = false_内で_@JoinColumn_の代わりに@ManyToOne(optional = false)を使用します
  • コレクションには_fetch = FetchType.LAZY_を優先
  • 関係を設定するには、ヘルパーメソッドを使用することをお勧めします。例えば。クラスCartにはメソッドが必要です:

    _public void addCartItem(CartItem item){
        cartItems.add(item);
        item.setCart(this);
    }
    _

設計の問題:

  • dTOをDAO層に渡すのはよくありません。 DTOとエンティティの間の変換は、サービスレイヤーより上でも行うことをお勧めします。
  • Spring Data JPAリポジトリ を使用したメソッドsaveのような定型文を避ける方がはるかに優れています。
2
Taras

誰がこの関係を所有しているかというこの質問にとって非常に重要である議論から、1つの重要なことが明らかに欠落しています。この関係の所有者が子エンティティに行くことを意味する親エンティティにmappedByを配置すると、明示的にプロパティを設定してこの関係を埋める必要があります。そうしないと、この関係シップは構築されません。 JoinColumnアノテーションを親の上に置きます。これにより、関係の所有者が親であることが保証され、親エンティティが自動的に保存されるときにこの関係が確立されます

1
sqzaman

この投稿を確認しましたか? 行は別のトランザクションによって更新または削除されました(または未保存の値のマッピングが正しくありませんでした)

あなたはこれで適切な答えを見つけるかもしれません、あなたの問題はあなたのgetCurrentSessionから来ていると思います、たとえhibernateがスレッドセーフではないのでセッションを使用していても、セッションは軽量で非スレッドセーフなオブジェクトです。ここから何かを掘る必要があります。

実際、あるスレッド/セッションがオブジェクトをデータベースに保存するときに、別のスレッド/セッションが同じ操作を試みると、IDがすでに存在するため操作が不可能であるため、この種のエラーが発生します。

乾杯!

1
Palencar

メソッドがトランザクションであることを確認してください。メソッドシグネチャの上に@Transactionalアノテーションを使用して、メソッドをトランザクション対応にすることができます。

1
Alien

サービスが使用されているので、これが質問に直接関連しないことはわかっていますが、同様の問題が発生したときにgoogleがここに連れてきました。私の場合、Spring JPAリポジトリを使用していました。 @ org.springframework.transaction.annotation.Transactionalを使用してリポジトリインターフェイスに注釈を付けてください。

0
gmode