error refreshing JPA Entity - java

I have the following domain model
Currency ----< Price >---- Product
Or in English
A Product has one or more Prices. Each Price is denominated in a particular Currency.
Price has a composite primary key (represent by PricePK below) which is composed of the foreign keys to Currency and Product. The relevant sections of the JPA-annotated Java classes are below (getters and setters mostly omitted):
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Currency {
#Id
private Integer ix;
#Column
private String name;
#OneToMany(mappedBy = "pricePK.currency", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Collection<Price> prices = new ArrayList<Price>();
}
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Product {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "pricePK.product", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Collection<Price> defaultPrices = new ArrayList<Price>();
}
#Embeddable
public class PricePK implements Serializable {
private Product product;
private Currency currency;
#ManyToOne(optional = false)
public Product getProduct() {
return product;
}
#ManyToOne(optional = false)
public Currency getCurrency() {
return currency;
}
}
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Price {
private PricePK pricePK = new PricePK();
private BigDecimal amount;
#Column(nullable = false)
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
#EmbeddedId
public PricePK getPricePK() {
return pricePK;
}
#Transient
public Product getProduct() {
return pricePK.getProduct();
}
public void setProduct(Product product) {
pricePK.setProduct(product);
}
#Transient
public Currency getCurrency() {
return pricePK.getCurrency();
}
public void setCurrency(Currency currency) {
pricePK.setCurrency(currency);
}
}
When I try to refresh an instance of Product, I get a StackOverflowError, so I suspect there's some kind of cycle (or other mistake) in the mapping above, can anyone spot it?

I've seen this error a few times, but I can't remember the exact solution. I have the idea that you need to remove mapping from PricePK (both #ManyToOne) and replace that with #AssociationOverrides on Price.
#Entity #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#AssociationOverrides({
#AssociationOverride(name = "pricePK.product",
joinColumns = #JoinColumn(name = "product_id")),
#AssociationOverride(name = "pricePK.currency",
joinColumns = #JoinColumn(name = "currency_id"))
})
public class Price extends VersionedEntity {
[...]
}
please check that the column names are ok, as I can't see the id columns on Product or Currency.

I encountered a similar problem and it turned out to be caused by the CascadeType.ALL defined in the OneToMany annotation. When you refresh product, it is going to try to refresh the prices that are in the persistent context.
Depending on the situation, you may be able to get by without having the prices refreshed when the product is refreshed in the entity manager, such as:
#OneToMany(mappedBy = "pricePK.product", cascade = {
CascadeType.PERSIST, CascadeType.MERGE
}, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Collection<Price> defaultPrices = new ArrayList<Price>();

Shouldn't you just declare the relations from Price to Product and Currency as ManyToOne directly in the Price entity, and annotate Price with #IdClass(PricePK)?
I don't know how Hibernate handles this though, but I have successfully implemented this using OpenJPA. In that case, PricePK must declare its fields with the same names as in Price, but using the simple type instead (Integer instead of Currency or Product). In Price, you might need to annotate product and currency with #Id. Note that this is out of the JPA spec (#IdClass only supports simple fields).

Related

Hibernate insert null values ​in composite foreign key

I have two simple domain objects as follows:
USER:
#Entity
#Table(name="USER")
#IdClass(UserPK.class)
public class User implements Serializable {
//...
#Id
#Column(name = "FISCALCODE")
private String fiscalCode;
#Id
#Column(name = "USERNUMBER")
private String userNumber;
#OneToMany(mappedBy="user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Items> items;
// getters and setters
}
UserPK:
public class UserPK implements Serializable {
#Column(name = "FISCALCODE")
private String fiscalCode;
#Column(name = "USERNUMBER")
private String userNumber;
// getter and setter
}
ITEMS:
#Entity
#Table(name="ITEMS")
public class Items implements Serializable {
//...
#Id
#Column(name = "ID_ITEM")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_item_generator")
#SequenceGenerator( name = "id_item_generator", sequenceName = "ITEM_SEQ", allocationSize = 1)
private Integer id;
#ManyToOne
#JoinColumns({
#JoinColumn(name="FISCALCODE"),
#JoinColumn(name="USERNUMBER")
})
private User user;
// getters and setters
}
DB Table:
user { fiscalcode, usernumber, other columns... } // fiscalcode+usernumber = PK
items { id, fiscalcode, usernumber, other columns... } // fiscalcode,usernumber is a foreign key
CONTROLLER:
#RequestMapping(value="/user", method = RequestMethod.POST, produces = "application/json")
public Object postUser(#RequestBody(required = false) User user){
//connection etc..
session.save(user);
//...
return new ResponseEntity<>(HttpStatus.OK);
}
Why when I run the command session.save(user) Hibernate insert value null in the columns FISCALCODE and USERNUMBER of the ITEMS table?
I tried to set the ManyToOne and the JoinColumns on the getter, but the result is the same.
EDIT: i have added my method for POST operation
As it's stated in the documentation for #JoinColumns:
When the JoinColumns annotation is used, both the name and the referencedColumnName elements must be specified in each such JoinColumn annotation.
So, you should correct your mapping like below:
#ManyToOne
#JoinColumns({
#JoinColumn(name="FISCALCODE", referencedColumnName = "FISCALCODE"),
#JoinColumn(name="USERNUMBER", referencedColumnName = "USERNUMBER")
})
private User user;
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
So, you should have in your User entity the addItem() and removeItem() utility methods that synchronize both ends whenever a child element is added or removed like below.
#Entity
#Table(name="USER")
#IdClass(UserPK.class)
public class User implements Serializable {
//...
#OneToMany(mappedBy="user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Items> items;
// getters and setters
public void addItem(Items item) {
this.items.add(item);
item.setUser(this);
}
public void removeItem(Items item) {
this.items.remove(item);
item.setUser(null);
}
}
Example of saving:
User user = new User();
user.setFiscalCode("Code1");
user.setUserNumber("User1");
// ...
Items item1 = new Items();
Items item2 = new Items();
// ...
user.addItem(item1);
user.addItem(item2);
session.save(user);

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.

Java hibernate: #OneToMany association doesn't work

In my code i have a oneToMany relation between customer class and item class. This means that, a customer may have one or many items.
Here is the customer code:
#Entity
#Data
public class customer {
#Id
#GeneratedValue
int id;
String name;
String lastname;
#Embedded
Address address;
#OneToMany
#Column(name="ITEM_ID")
List<item> item;
}
and it's the item class:
#Entity
#Data
public class item {
#Id
#GeneratedValue
int id;
String name;
String Serialnumber;
int price;
#ManyToOne
customer customer;
}
Then i have made some tests to try my queries in the models.
insert into item(id,name,Serialnumber,price) values(1,'bike','123',200);
insert into item(id,name,Serialnumber,price) values(2,'car','123',200);
insert into customer(id,name,lastname,Country,City,Street,No,item_id)
values(1,'Salman','Lashkarara','Iran','Tehran','Shariati','12',1);
insert into customer(id,name,lastname,Country,City,Street,No,item_id)
values(2,'Saba','Lashkarara','Iran','Tehran','Shariati','12',2);
insert into customer(id,name,lastname,Country,City,Street,No,item_id)
values(3,'Saba','Lashkarara','Iran','Tehran','Shariati','12',1);
But when i run my code, i face with the following error:
Column "ITEM_ID" not found; SQL statement:
insert into customer(id,name,lastname,Country,City,Street,No,item_id) values(1,'Salman','Lashkarara','Iran','Tehran','Shariati','12',1)
Please pay especial attention, that it is a java mvc-spring application and i create my models using the code, so there is no database to check the field item_id.
As you can see i have already added the #Column(name="ITEM_ID") to define the column.
You have to use #JoinColumn for association columns:
#OneToMany
#JoinColumn(name="ITEM_ID")
List<item> item;
some other options
#OneToMany(cascade=CascadeType.All, fetch=FetchType.EAGER)
#JoinColumn(name="ITEM_ID")
List<item> item;
in Item class
#ManyToOne(mappedBy="item")
customer customer;
you could do this i also have user class and bcr class, one user have many bcr so below code will help you
bcr.java
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_who_enter_demand", nullable = false)
public User getUserByUserWhoEnterDemand() {
return this.userByUserWhoEnterDemand;
}
public void setUserByUserWhoEnterDemand(User userByUserWhoEnterDemand) {
this.userByUserWhoEnterDemand = userByUserWhoEnterDemand;
}
user.java
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userByUserWhoEnterDemand")
public Set<BudgetControlRegister> getBudgetControlRegistersForUserWhoEnterDemand() {
return this.budgetControlRegistersForUserWhoEnterDemand;
}
public void setBudgetControlRegistersForUserWhoEnterDemand(Set<BudgetControlRegister> budgetControlRegistersForUserWhoEnterDemand) {
this.budgetControlRegistersForUserWhoEnterDemand = budgetControlRegistersForUserWhoEnterDemand;
}
You can't map a table column without table:
#Entity
#Table(name = "ITEM_TABLE")
public class item {
...
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "CUSTOMER_ID_ITEM_TABLE")
private customer customer;
...
}
#Entity
#Table(name = "CUSTOMER_TABLE")
public class customer {
...
#OneToMany
#Column(name="ITEM_ID")
List<item> item;
}

Hibernate many to many mapping, join table with PK and extra columns

I've done the necessary changes to my models outlined here. However, I don't know what to put on my join table entity.
Note that my join table has a surrogate key , and two extra columns (date and varchar).
What I've got so far is:
User.java
#Entity
#Table (name = "tbl_bo_gui_user")
#DynamicInsert
#DynamicUpdate
public class User implements Serializable {
private String id;
private String ntName;
private String email;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
Group.java
#Entity
#Table (name = "tbl_bo_gui_group")
#DynamicInsert
#DynamicUpdate
public class Group implements Serializable {
private String id;
private String groupName;
private String groupDesc;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.group", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
The problem is that I don't know what to put on my join table entity. Here it is.
GroupUser.java
#Entity
#Table (name = "tbl_bo_gui_group_user")
#DynamicInsert
#DynamicUpdate
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "pk.group",
joinColumns = #JoinColumn(name = "id")) })
public class GroupUser implements Serializable {
private String id;
private User userId;
private Group groupId;
private Date dateCreated;
private String createdBy;
// constructors and getters and setters for each property
// What now? ? No idea
}
user to group would be a Many-To-Many relation. Now, you are splitting that up into Two One-To-Many Relations. Therefore your Mapping Entity simple needs to complete the Many-To-Many relation, by using Many-To-One:
public class GroupUser implements Serializable {
private String id;
#ManyToOne
private User userId;
#ManyToOne
private Group groupId;
private Date dateCreated;
private String createdBy;
}
See also this example: Mapping many-to-many association table with extra column(s) (The Answer with 38 upvotes)

Spring Hibernate product - category relationship

I've read many tutorials about spring-hibernate relationships but I'm a bit confused about how to use them in my case... I've product/category entities defined as follow:
Product
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#Column
private int category;
.
.
.
Category
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
.
.
.
So, I'd like in the product list page, under the voice "category" would appear the category name, and in the product form the category list...
In my case a product fits only one category so if I'm right it should be a #ManyToOne but I don't know how to implement this... in my product database I've the categoryId field, but if I mark the category entity field as #OneToMany it will not be stored to the db...
EDIT
I've changed like this (as suggested):
Product.class
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
#Column
#Size (max = 255)
private String description;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
Category.class
#Entity
#Table(name = "categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
#Column
#Size (max = 255)
private String description;
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade = CascadeType.ALL)
private Set<Product> products = new HashSet<Product>();
Controller
#RequestMapping(value = "/add/", method = RequestMethod.POST)
public String addProduct(
#ModelAttribute(value = "product") #Valid Product product,
BindingResult result, ModelMap model, Category category) {
if (result.hasErrors()) {
return "forms/productForm";
}
try {
category.addProduct(product);
product.setCategory(category);
// Add product to db
productService.addProduct(product);
} catch (Exception e) {
log.error("/add/---" + e);
return "redirect:/product/deniedAction/?code=0";
}
return "redirect:/admin/product/";
}
I also added a #initbinder on the product controller to translate the data from the product form string to Category... but now when I save a product it automatically saves a category instead of attach the existing selected one...
As the Product will have only one Category and Category will have a list of Products, you can relate these two by creating a Foreign Key in the Product table to refer to the primary key in the Category table:
Category Table: id, name, other fields...
Product Table: id, category_id (FK), and other fields.
And the mapping can be defined as below:
public class Category {
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.LAZY, mappedBy = "category", cascade = CascadeType.ALL)
private Set<Product> products = new HashSet<Product>();
...
}
public class Product {
//Here JoinColumn states that this entity is the owner of the relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
...
}
The mappedBy attribute tells Hibernate that the collection is a mirror image of the many-to-one association on the other side. Its like telling Hibernate that it should propagate changes made at the Product end of the association to the database, ignoring changes made only to the products collection that you have in the Category. Thus if we only call category.getProducts().add(product), no changes will be made persistent. As the association is bidirectional, you have to create the link on two sides, not just one.
For your convenience, you can add one addProduct method in the Category class to save the association:
public void addProduct(Product product) {
product.setCategory(this);
products.add(product);
}
You appear to have a one-to-many relationship between Category and Product (one category has many products)
In Java (and OO generally) you'd expect the Category class to contain a list of Products, so the Category can be said to 'own' products.
In SQL it's the other way round - you'd expect Product table to hold a foreign key reference to a Category, so here, the Product can be said to 'own' a Category.
Looks like your using JPA, so you could have something like this:
Category class:
#Entity
public class Category {
//other stuff...
#OneToMany(cascade=CascadeType.ALL, mappedBy="category")
private Set<Product> products;
}
Product class:
#Entity
public class Product {
//other stuff...
#ManyToOne
private Category category;
}
so you have this:
Product{
atributtes...
#ManyToOne
Category category; --so every product has a category
}
Category {
attributtes...
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="id_Product")
private List<Product> products;
}
try this, if not we can look another solution..
You are right, you should use #ManyToOne because "...a product fits only one category...".
In Product entity declare a Category field instead of int category and annotate it with #ManyToOne. Also add #JoinColumn to specify the name of product.category_id column in the database.
Product:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
.
.
.

Categories