Je commence à vous montrer mon scénario.
Ceci est mon objet parent:
@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;
...
}
Voici mon objet enfant:
@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;
...
}
Comme vous pouvez le voir en regardant la base de données, dans le tableau cart_item (objet enfant) le champ cart_id a une clé étrangère dans le champ id de la table cart (objet parent).
Voici comment je sauvegarde l'objet:
1) il y a un restController qui lit un objet JSON:
@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) Ceci est le CartService , c'est juste une interface :
public interface CartService {
void create(CartDto cartDto);
}
Voici l'implémentation de 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 est juste une autre interface, je vous montre seulement son implémentation:
@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);
}
}
Lorsque j'essaie d'enregistrer un nouveau panier et ses cart_item s j'obtiens cette erreur:
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]
Je suppose que l'erreur dépend du fait que lorsque Hibernate essayez d'enregistrer le a cart_item , l'ID du panier n'existe pas encore !
Quelle est la bonne façon d'enregistrer un objet parent et son générateur sur le coup? Je vous remercie
Voici la liste des règles que vous devez suivre, afin de pouvoir stocker une entité parent avec ses enfants en une seule fois:
PERSIST
doit être activé (CascadeType.ALL
convient également)Problèmes de mappage:
@Column(name="id")
des deux entitéscartItems
privé . Depuis Hibernate utilise sa propre implémentation de List
, et vous ne devriez jamais le changer directement via setterprivate List<CartItem> cartItems = new ArrayList<>();
@ManyToOne(optional = false)
au lieu de nullable = false
dans le @JoinColumn
fetch = FetchType.LAZY
pour les collectionsil est préférable d'utiliser la méthode d'assistance pour établir des relations. Par exemple. la classe Cart
doit avoir une méthode:
public void addCartItem(CartItem item){
cartItems.add(item);
item.setCart(this);
}
Problèmes de conception:
save
avec référentiels Spring Data JPAUne chose importante est clairement absente de la discussion qui est très importante pour cette question qui est à qui appartient cette relation. vous mettez mappedBy dans l'entité parent, ce qui signifie que le propriétaire de cette relation va à l'entité enfant, il doit remplir cette relation en définissant explicitement la propriété sinon cette relation ne sera pas construite. Mettez l'annotation JoinColumn au-dessus de Parent, cela garantira que le propriétaire de la relation est parent, il établira cette relation lorsque l'entité parent sera enregistrée automatiquement
Avez-vous vérifié ce post? La ligne a été mise à jour ou supprimée par une autre transaction (ou le mappage des valeurs non enregistrées était incorrect)
Vous pouvez trouver une réponse appropriée dans celle-ci, je pense que votre problème vient de votre getCurrentSession, même si vous utilisez des sessions car hibernate n'est pas thread-safe, une session est toujours un objet léger et non threadsafe. Vous devriez creuser quelque chose d'ici.
En fait, lorsqu'un thread/session enregistre un objet dans la base de données, si un autre essaie la même opération, il déclenchera ce type d'erreur car les identifiants existent déjà, donc les opérations sont impossibles.
À votre santé!
Assurez-vous que votre méthode est transactionnelle. vous pouvez rendre la méthode transactionnelle en utilisant @Transactional
annotation au-dessus de la signature de la méthode.
Je sais que cela ne concerne pas directement la question, car les services sont utilisés, mais Google m'a amené ici quand j'ai eu un problème similaire. Dans mon cas, j'utilisais les référentiels Spring JPA. Assurez-vous d'annoter l'interface du référentiel avec @ org.springframework.transaction.annotation.Transactional