Empiezo mostrando mi escenario.
Este es mi objeto padre:
@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;
...
}
Este es mi objeto secundario:
@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;
...
}
Como se puede ver mirando a la base de datos, en la tabla cart_item (objeto secundario) el campo cart_id tiene una clave externa al campo ID de la tabla de la cesta (objeto principal).
Así es como me ahorro el objeto:
1) hay un restController que lee un objeto 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) Esta es la CartService , que es sólo una Interfaz :
public interface CartService {
void create(CartDto cartDto);
}
Esta es la implementación 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 es sólo otra interfaz, os muestro sólo su aplicación:
@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);
}
}
Cuando trato de guardar una nueva compra y su cart_item s me sale este error:
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]
Supongo que el error depende del hecho de que cuando Hibernate tratar de salvar la de un cart_item , la identificación de la compra no existe todavía!
¿Cuál es la forma correcta de guardar un objeto de matriz y su chiquillos en el disparo? Gracias
Aquí está la lista de reglas que debe seguir, con el fin de ser capaz de almacenar una entidad matriz junto con sus hijos en una sola toma:
- tipo cascada
PERSIST
debe estar habilitado (CascadeType.ALL
es también muy bien) - una relación bidireccional debe ajustarse correctamente en ambos lados . Por ejemplo, los padres contiene todos los niños en su campo de recolección y cada niño tiene una referencia a su padre.
- la manipulación de datos se lleva a cabo en el ámbito de una transacción. NO SE PERMITE modo de confirmación automática.
- única entidad matriz debe guardarse manualmente (los niños se guardará automáticamente debido al modo en cascada)
Mapeo de cuestiones:
- eliminar
@Column(name="id")
de ambas entidades - hacer colocador de
cartItems
privada . Desde Hibernate está usando su propia implementación de laList
, y nunca se debe cambiar directamente a través de la moda - inicializar la lista
private List<CartItem> cartItems = new ArrayList<>();
- utilizar
@ManyToOne(optional = false)
en lugar denullable = false
dentro de la@JoinColumn
- prefieren
fetch = FetchType.LAZY
para las colecciones es mejor usar ayudante método para establecer relaciones. Por ejemplo, la clase
Cart
debe tener un método:public void addCartItem(CartItem item){ cartItems.add(item); item.setCart(this); }
Problemas de diseño:
- que no es bueno para pasar dtos a la capa DAO. Es mejor hacer la conversión entre las OTD y entidades, incluso por encima de la capa de servicio.
- que es mucho mejor para evitar tales como el método repetitivo
save
con repositorios de datos de Primavera de la APP