Join fetching #ManyToOne nested inside #OneToMany - java

I have created the following entities to manage a persistent shopping cart:
ShoppingCart.java:
#Entity
public class ShoppingCart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#PrivateOwned
#OneToMany(mappedBy = "cart", cascade = CascadeType.ALL)
#OrderBy("creationTimestamp")
private List<ShoppingCartItem> items;
public ShoppingCart() {}
// Getters and setters...
}
ShoppingCartItem.java:
#Entity
#IdClass(ShoppingCartItemId.class)
public class ShoppingCartItem {
#Id
#ManyToOne
private Item item;
#Id
#ManyToOne
private ShoppingCart cart;
private int quantity;
#Column(precision = 17, scale = 2)
private BigDecimal price;
#Temporal(TemporalType.TIMESTAMP)
private Date creationTimestamp;
protected ShoppingCartItem() {}
#PrePersist
protected void prePersist() {
creationTimestamp = new Date();
}
public ShoppingCartItem(ShoppingCart cart, Item item, int quantity) {
this.cart = cart;
this.item = item;
this.quantity = quantity;
this.price = item.getPrice();
}
// Getters and setters...
}
Item.java:
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Brand brand;
private String model;
private String variant;
private String description;
#Column(precision = 17, scale = 2)
private BigDecimal price;
private int availability;
protected Item() {}
// Constructors, getters and setters...
}
When I issue the the following JPQL query:
SELECT c FROM ShoppingCart c JOIN FETCH c.items WHERE c.id = :id
I notice that all the ShoppingCartItems in the same ShoppingCart are retrieved as expected in a single query but the #ManyToOne private Item item; field is not in the join and a separate query for each ShoppingCartItem is issued to fetch that field when accessed.
Using EclipseLink, is there a way to have also the Items join fetched when join/batch fetching the ShoppingCartItems? How do I change the query and/or code?

If you are using EclipseLink you can take a look at the #BatchFetch and #JoinFetch annotations.

While the left join fetchs with aliases seems to be ignored, I've found this query hint that do the job:
Query query = entityManager.createQuery("SELECT c FROM ShoppingCart c WHERE c.id = :id");
query.setHint("eclipselink.left-join-fetch", "c.items.item.brand");
This is probably better than the annotation approach as it can be specified per single query.
UPDATE
Use of this hint broke #OrderBy("creationTimestamp") so ShoppingCartItems aren't returned in the order they were inserted anymore. This is probably due to a bug in EclipseLink but I think it doesn't really hurt so much since I actually need to have the items ordered only when showing the cart to the user and not, for example, when the user logs in and items in the anonymous cart must be transferred to the user cart.

Related

How do I map cart-cartItem-item entities with JPA using Spring annotations?

I'm a beginner at JPA and table relationships and I've watched a few tutorials and consider the below to be correct(?). But I'd appreciate if a more experienced eye took a look at the below if it seems right.
I have a Cart, CartItem and an Item class. A Cart can have many CartItems. A CartItem can have one Item and also is part of one Cart. An Item can be in many CartItems.
I'm trying to get the relationship between the tables right but I would appreciate some help since it doesn't seem to work. I'm getting an error Cannot invoke "java.util.List.iterator()" because the return value of "...Cart.getItems()" is null. I'm assuming it's because I've set up the relationship of my tables incorrectly?
Cart:
#Entity
#Table(name="my_cart")
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="cart_id")
private String cartId;
#OneToMany(targetEntity = CartItem.class, cascade = CascadeType.ALL)
#JoinColumn(name="cart_item_foreign_key")
#Column(name="cart_items")
private List<CartItem> cartItems;
#Column(name="cart_total_number_of_items")
private long totalNumberOfItems;
#Column(name="cart_total_price")
private double totalPrice;
// + getters and setters and constructor
CartItem:
#Entity
#Table(name="cart_items")
public class CartItem {
#Id
#Column(name="cartitem_id")
private String itemId;
#Column(name="cart_item_name")
private String productName;
#Column(name="cart_item_description")
private String itemDescription;
#Column(name="cart_item_quantity")
private int itemQuantity;
#Column(name="cart_item_price")
private double itemPrice;
#ManyToOne
Cart cart;
#OneToOne
private Item product;
Item:
#Entity
#Table(name="my_items")
public class Item {
#Id
#Column(name="item_id")
private String itemId;
#Column(name="item_name", nullable = false)
private String name;
#Column(name="item_description", nullable = false)
private String description;
#Column(name="item_price", nullable = false)
private Double price;
#OneToOne(mappedBy = "item")
CartItem cartItem;
Could someone with more experience please point me in the right direction with the above table relationships? Thank you
In the Cart class, when you use #JoinColumn, you can omit #Column, in #JoinColumn there is a name attribute that is the column name.

JPA 1:N relationship removing child does not remove it from parent

I have the following objects:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Entity(name="Group")
public class Group {
#Id
#GeneratedValue
#NotNull
#Column(name = "GROUP_ID")
private Long id;
#Column(name="NAME")
private String name;
#OneToMany(
targetEntity = Product.class,
mappedBy = "groupId",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
private List<Product> products = new ArrayList<>();
public Group(String name) {
this.name = name;
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity(name="Product")
public class Product {
#Id
#GeneratedValue
#NotNull
#Column(name="PRODUCT_ID")
private Long id;
#Column(name="NAME")
private String name;
#Column(name="DESCRIPTION")
private String description;
#Column(name="PRICE")
private double price;
#ManyToMany
#JoinTable(
name = "JOIN_PRODUCT_CART",
joinColumns = {#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "PRODUCT_ID")},
inverseJoinColumns = {#JoinColumn(name = "CART_ID", referencedColumnName = "CART_ID")}
)
private List<CartEntity> carts = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "GROUP_ID")
private Group groupId;
public Product(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
public Product(String name, String description, double price, Group groupId) {
this(name, description, price);
this.groupId = groupId;
}
public void addToCart(CartEntity cart) {
this.carts.add(cart);
cart.getProductsList().add(this);
}
public void addGroup(Group group) {
group.getProducts().add(this);
this.groupId = group;
}
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "cart")
public class CartEntity {
#Id
#NotNull
#GeneratedValue
#Column(name = "CART_ID")
private Long id;
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
public void addProduct(Product product) {
productsList.add(product);
product.getCarts().add(this);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartEntity that = (CartEntity) o;
return id.equals(that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Now, when I have the following test:
public class ProductDaoTestSuite {
#Autowired
private ProductDao productDao;
#Autowired
private CartDaoStub cartDaoStub;
#Autowired
private GroupDao groupDao;
#Test
public void testDeleteProduct() {
// Given
Product product = new Product("test", "testProduct", 100.0);
Group group = new Group("group1");
CartEntity cart = new CartEntity();
product.addGroup(group);
cart.addProduct(product);
// When
groupDao.save(group);
productDao.save(product);
cartDaoStub.save(cart);
Long groupId = group.getId();
Long productId = product.getId();
Long cartId = cart.getId();
productDao.deleteById(productId);
// Then
Assert.assertTrue(cartDaoStub.findById(cartId).isPresent());
Assert.assertEquals(0, cartDaoStub.findById(cartId).get().getProductsList().size());
Assert.assertTrue(groupDao.findById(groupId).isPresent());
Assert.assertEquals(0, groupDao.findById(groupId).get().getProducts().size());
Following product deletion, I would expect association with it in group and cart to disappear (product to disappear from their List relationship fields). However, that is not happening at the moment. When I use Group/Cart Dao to pull group & cart from the DB after product deletion, they still have product in their Lists, while product when pulled from the DB is returned as null.
I have tried to add "orphanRemoval = true" value for #OneToMany adnotation, but it did not seem to work for Group entity.
What am I doing wrong?
I have started experimenting with adding all types of cascade (except for REMOVE) to #ManyToOne on Product class, but so far no luck.
For 1:N, yours should work just fine with minor adjustment.
The reason why it fails: Upon doing "groupDao.save(group);" this group is now in the persistence context and calling "groupDao.findById(groupId).get().getProducts().size()" would return the copy which is from the persistence context.
To solve this: simply add: entityManager.flush(); and entityManager.clear(); before the Assert
I would like to demonstrate it with this Integration Test
#Test
#Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
ProductGroup group = groupRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
Assert.assertEquals(1, group.getProducts().size());
Product product = productRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
productRepository.delete(product);
entityManager.flush();
entityManager.clear();
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
}
If we are to remove the first 2 lines, then we won't need to flush and clear. Like this.
#Test
#Transactional
public void deleteProduct_groupShouldNowBeEmpty() {
Product product = productRepository.findById("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
productRepository.delete(product);
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
}
For N:M, since there would be another table where product is being referenced, then we would need to delete the records from that table first before deleting the product.
N:M is a bit tricky so if I can suggest domain changes, here how I'll do it. (The integration test is at the bottom.)
I'll add a separate entity: CartItem
which is associated to a Product and Cart
#Entity
public class CartItem {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
#ManyToOne
private Product product;
#ManyToOne
private Cart cart;
public String getId() {
return id;
}
// Required by JPA
protected CartItem() {}
}
And for the Product Entity: add a bidirectional relationship with CartItem
#Entity
public class Product {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String name;
private String description;
private BigDecimal price;
#ManyToOne
private ProductGroup group;
#OneToMany(mappedBy = "product")
private List<CartItem> cartItems;
public List<CartItem> getCartItems() {
return cartItems;
}
// Required by JPA
protected Product() {}
}
Then, retrieve the product (using Join Fetch to avoid N+1, since later will be looping through each cartItem)
public interface ProductRepository extends JpaRepository<Product, String> {
#Query("SELECT product FROM Product product JOIN FETCH product.cartItems")
Optional<Product> findProduct(String Id);
}
create another query inside CartItemRepository to delete cartItems in bulk by ids
public interface CartItemRepository extends JpaRepository<CartItem, String> {
#Modifying
#Query("DELETE FROM CartItem cartItem WHERE cartItem.id IN :ids")
void deleteByIds(#Param("ids") List<String> ids);
}
Lastly here's the integration test to wrap everthing up:
#Test
#Transactional
public void deleteProduct_associatedWithCart() {
Cart cart = cartRepository.findById("0001").get();
Assert.assertEquals(1, cart.getCartItems().size());
Product product = productRepository.findProduct("0001").orElseThrow(() -> new IllegalArgumentException("id not found"));
List<String> cartItemIds = product.getCartItems().stream()
.map(CartItem::getId)
.collect(Collectors.toList());
cartItemRepository.deleteByIds(cartItemIds);
productRepository.delete(product);
entityManager.flush();
entityManager.clear();
Assert.assertEquals(0, productRepository.findAll().size());
Assert.assertEquals(0, groupRepository.findById("0001").get().getProducts().size());
Assert.assertEquals(0, cartItemRepository.findAll().size());
Assert.assertEquals(0, cartRepository.findById("0001").get().getCartItems().size());
}
I've used DBUnit for this integration test so I think it would also be helpful to share the dataset.
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<product_group id="0001" name="product group with 1 product"/>
<product id="0001" group_id="0001" />
<cart id="0001" />
<cart_item id="0001" product_id="0001" cart_id="0001" />
</dataset>
When you remove an entity, this state transition should be propagated from parent to child, not the other way around.
In this case, you need to move that functionally to the Group entity, something like this:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Entity(name="Group")
public class Group {
#Id
#GeneratedValue
#NotNull
#Column(name = "GROUP_ID")
private Long id;
#Column(name="NAME")
private String name;
#OneToMany(
targetEntity = Product.class,
mappedBy = "groupId",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY, // Always prefer LAZY initialized Collections to EAGER ones
orphanRemoval = true
)
private List<Product> products = new ArrayList<>();
public Group(String name) {
this.name = name;
}
public void addProduct(Product product){
product.setGroupId(this);
this.products.add(product);
}
public void removeProduct(Product product){
product.setGroupId(null);
this.products.remove(product);
}
If you want to remove a Product, you only need to invoke the removeProduct method and save the parent entity:
Group group = new Group("group1");
Product product = new Product("test", "testProduct", 100.0);
group.addProduct(product);
groupDao.save(group);
On the other hand, we have the many-to-many relation between Product and CartEntity.
First, if you configure the entity CartEntity with Cascade.ALL as in your example:
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
It will have a probably undesired effect: if you remove the CartEntity, it will remove all the Products associated with the entity as well, even if other CartEntitys are still associated to them. Vlad Mihalcea explain it in great detail in this article.
To avoid that problem, the best option will be just define the relationship as follows:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
This will give us a CartEntity like this:
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "cart")
public class CartEntity {
#Id
#NotNull
#GeneratedValue
#Column(name = "CART_ID")
private Long id;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "carts")
private List<Product> productsList = new ArrayList<>();
public void addProduct(Product product) {
productsList.add(product);
product.getCarts().add(this);
}
public void removeProduct(Product product) {
productsList.remove(product);
product.getCarts().remove(this);
}
public void removeProducts() {
for(Product product : new ArrayList<>(products)) {
removeProduct(product);
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartEntity that = (CartEntity) o;
return id.equals(that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Please, note the inclusion of the removeProduct and removeProducts methods.
With this code, if you need to remove a CartEntity, just do the following:
cart.removeProducts();
cartDao.remove(cart);
And if you need to remove a Product from the CartEntity (will only remove the relation):
cart.removeProduct(product);
cartDao.save(cart);
If you need to propagate the Product remove to the CartEntity, I think that the best option will be create a business method that takes care of the whole process. Think in something like:
public void removeProduct(Product product){
Group group = product.getGroupId();
group.removeProduct(product);
final List<CartEntity> carts = product.getCarts();
if (carts != null) {
for(CartEntity cart : new ArrayList<>(carts)) {
cart.removeProduct(product);
cartDao.save(cart);
}
}
groupDao.save(group);
}
It will remove the association, you just need to do small adjustments.
1:N. When you remove Product, you don't have to do anything else in order to remove its association with Group, because the product itself holds the association (in DB column product.group_id). You just need to commit the transaction. And next time when you load a group from the DB it for sure will not contain this product.
N:M. There is no way to automatically remove the association because it is stored in a separate table and you don't have a separate entity for it. (YOU SHOULD NOT USE CascadeType.ALL for N:M relations). What you want to do is remove the association before you remove the product. Just add another helper method to Product.
public void removeFromCarts() {
carts.forEach(c -> c.getProducts().remove(this));
carts.clear();
}
So finally, in order to remove a product and all the associations with it. You will need to do the following:
product.removeFromCarts();
productDao.deleteById(productId); // not sure why you remove by id (not pass object)
*please note that you need to commit transaction and close the session. So you cannot rely on the test. In real app when you do what I described, it will work
**N:M is tricky. For instance, you should better use Set instead of List to avoid unexpected SQL under the hood. Also going down the road, I recommend you to consider splitting N:M into two N:1 and 1:M and have a dedicated Entity for a link table
Not sure I follow. Hibernate does not automatically maintain the inverse association for you. You can make it sensitive to changes on the owning side of the association, but that's as far as it goes.
As to why your test fails, cartDaoStub.findById(cartId) probably returns the same copy of the CartEntity that you already have loaded into the persistence context. Try calling entityManager.flush() followed by entityManager.clear() before making the assertion and the issue will probably go away.

Spring Data JPA fetches all columns when mapped model is included

Interface based projection - I want to select only few columns from Product and ProductImages DB tables.
Issue: If I mention mapped model in interface public Set<ProductImagesDBResult> getProductImages(); it fetches all columns.
from both Product and ProductImages tables.
Note: If I comment this out, it fetches only selected fields.
Entity models Product.java
#Entity
#Table(name = "products")
#EntityListeners(AuditingEntityListener.class)
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
#JoinColumn(name = "product_id")
private Set<ProductImage> productImages;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
private String sku;
private String slug;
private String title;
private String description;
private BigDecimal oldPrice;
private BigDecimal newPrice;
private int status;
/* getters and setters */
}
Entity model ProductImage.java
#Entity
#Table(name = "product_images")
public class ProductImage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "product_id")
#ManyToOne
#JsonIgnore
private Product product;
private String image;
private int isDefault;
private int imgOrder;
}
ProductDetailsDBResult.java (Projection interface)
public interface ProductDetailsDBResult {
public long getId();
public String getSlug();
public String getTitle();
public Set<ProductImagesDBResult> getProductImages();
interface ProductImagesDBResult {
public long getId();
public String getImage();
}
/* getters and setters */
}
ProductRepository.java
public interface ProductRepository extends JpaRepository<Product, Long> {
Optional<ProductDetailsDBResult> findBySlug(String slug);
}
ProductService.java
ProductDetailsDBResult p = productRepository.findBySlug(slug)
.orElseThrow(() -> new RecordNotFoundException("Invalid product slug " + slug));
The issue is:
When this line is commented out:
public interface ProductDetailsDBResult {
/* other get methods ... */
// public Set<ProductImagesDBResult> getProductImages();
}
Logs show (correct SQL as only mentioned columns are fetched):
Hibernate: select product0_.id as col_0_0_, product0_.slug as col_1_0_, product0_.title as col_2_0_ from products product0_ where product0_.slug=?
But when this line is uncommented: public Set<ProductImagesDBResult> getProductImages();
SQL fetches all fields from both Product, Category and ProductImages tables: (Product is mapped with Category and ProductImages models)
Hibernate: select product0_.id as id1_5_, product0_.category_id as categor11_5_, product0_.created_at as created_2_5_, product0_.description as descript3_5_, product0_.new_price as new_pric4_5_, product0_.old_price as old_pric5_5_, product0_.sku as sku6_5_, product0_.slug as slug7_5_, product0_.status as status8_5_, product0_.title as title9_5_, product0_.updated_at as updated10_5_ from products product0_ where product0_.slug=?
Hibernate: select category0_.id as id1_0_0_, category0_.description as descript2_0_0_, category0_.is_featured as is_featu3_0_0_, category0_.preview_image as preview_4_0_0_, category0_.slug as slug5_0_0_, category0_.title as title6_0_0_ from categories category0_ where category0_.id=?
Hibernate: select productima0_.product_id as product_5_4_0_, productima0_.id as id1_4_0_, productima0_.id as id1_4_1_, productima0_.image as image2_4_1_, productima0_.img_order as img_orde3_4_1_, productima0_.is_default as is_defau4_4_1_, productima0_.product_id as product_5_4_1_ from product_images productima0_ where productima0_.product_id=?
How can I fetch only the mentioned columns from both tables?
Any help would be appreciated. Thanks in advance! :)

Specifying join condition "ON columns" in hibernate HQL?

I am learning Hibernate, and I have a question about basic HQL join syntax. I am following this tutorial. Say I have a Product and Category entity,
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
private Set<Product> products;
public Category() {
}
public Category(String name) {
this.name = name;
}
#Id
#Column(name = "CATEGORY_ID")
#GeneratedValue
public long getId() {
return id;
}
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
public Set<Product> getProducts() {
return products;
}
// other getters and setters
}
#Entity
#Table(name = "PRODUCT")
public class Product {
private long id;
private String name;
private String description;
private float price;
private Category category;
public Product() {
}
public Product(String name, String description, float price,
Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
#Id
#Column(name = "PRODUCT_ID")
#GeneratedValue
public long getId() {
return id;
}
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
public Category getCategory() {
return category;
}
// other getters and setters
}
So I have to join category and Product, I will something like this in sql
select * from Category A inner join Product B on A.id=B.category_id,
In HQL, it seems we drop the "on" condition, the HQL for the above query is
String hql = "from Product p inner join p.category";
Query query = session.createQuery(hql);
Why is on not required in HQL?
If you have an association (for an example #ManyToOne), you don't need on, Hibernate will add it to the SQL.
It was a problem prior to Hibernate 5.1, If you don't have an association. From Hibernate 5.1 you can use ad hoc joins:
How to join unrelated entities with JPA and Hibernate
Apart that, HQL also defines a with clause to qualify the join conditions:
Hibernate docs: Explicit joins

JPA many to many relationship : zero or more

I'm new to JPA so I have a question about a many to many relationship (with a zero to more implementation) :
If you have a relationship like :
Like stated a product can excist without a order (normally they will be added as new products arrive) later on it can be used on 1 or more orders. An order must contain at least 1 or more products.
you must state the relationship
#Entity(name = "ORDERS")
public class Order {
#Id
#Column(name = "ORDER_ID", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private long orderId;
#Column(name = "CUST_ID")
private long custId;
#Column(name = "TOTAL_PRICE", precision = 2)
private double totPrice;
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="ORDER_DETAIL",
joinColumns=
#JoinColumn(name="ORDER_ID", referencedColumnName="ORDER_ID"),
inverseJoinColumns=
#JoinColumn(name="PROD_ID", referencedColumnName="PROD_ID")
)
private List<Product> productList;
...............
The other attributes and getters and setters goes here
}
and
#Entity(name = "PRODUCT")
public class Product {
#Id
#Column(name = "PROD_ID", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private long prodId;
#Column(name = "PROD_NAME", nullable = false,length = 50)
private String prodName;
#Column(name = "PROD_DESC", length = 200)
private String prodDescription;
#Column(name = "REGULAR_PRICE", precision = 2)
private String price;
#Column(name = "LAST_UPDATED_TIME")
private Date updatedTime;
#ManyToMany(mappedBy="productList",fetch=FetchType.EAGER)
private List<Order> orderList;
...............
The other attributes and getters and setters goes here
}
I wonder the zero to many relation is it still possible to just persist products who aren't linked (at the moment) to a order?
But when a order uses a product the orderlist in product should be updated and the productlist in orde also. How do I enforce this or does JPA this for me?
You can still persist a product without order.
You have to update the other side of the relationship by hand. JPA won't do that for you. (of course if you save one order and then refetch the product your collection of order will be updated)
EDIT
To explain second point:
Product persitentProduct = ... //some product
Order newOrder = new Order();
newOrder.getProducts().add(persitentProduct);
//at this point : persistentProduct.getOrders().contains(newOrder)==false
entityManager.persist(newOrder);
//at this point nothing has changed on the other side of the relationship:
// i.e. : persistentProduct.getOrders().contains(newOrder)==false
You would then get something like :
public class Order {
private List products;
...
public void addProduct(Product product) {
this.products.add(product);
if !(product.getOrders().contains(product) {
product.getOrders().add(this);
}
}
...
}
public class Product {
private List orders;
...
...
}
right? Or do I see this wrong

Categories