Reusing same key with JPA - java

I have a Class EntityA that contains a List of EntityB.
#Entity
public class EntityA {
#Id
private long id;
#OneToMany(mappedBy = "entityAId", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public final List<EntityB> listOfB = new ArrayList<>();
...
}
#Entity
public class EntityB {
#Id
private long entityAId;
#Id
private String name;
private String value;
...
}
What I want to do is:
EntityA myEntityA = entityManager.find(EntityA.class, 1);
// myEntityA.listOfB contains one element [entityAId = 1, name = "foo", value = "bar"]
myEntityA.listOfB.clear();
myEntityA.listOfB.add(new EntityB(1, "foo", "baz");
entityManager.persist(myEntityA);
// commit transaction here
But I get "javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session"
Adding an additional unique ID-field in EntityB "fixes" the problem, but I don't want an additional column in my db. Is there a better way?

Related

How to remove children from parent entity record in JPA?

I have Product entity and ProductRating entity, each Product can have many ProductRatings. When Product is deleted I want to have associated ratings deleted too, but nothing works so far (also orphanRemoval set to true)...
Classes:
#Getter
#Setter
#Entity
#Table(name = "PRODUCT")
public class Product extends AbstractEntity<Long> {
#Column(nullable = false)
private String name;
private String description;
#Column(nullable = false)
#Min(value = 0)
private Float cost;
#OneToMany(mappedBy = "product",
orphanRemoval = true, cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
//#OnDelete(action = OnDeleteAction.CASCADE)
#Fetch(value = FetchMode.SELECT)
private Set<ProductRating> productRatings;
}
#Getter
#Setter
#Entity
#Table(name = "PRODUCT_RATING")
public class ProductRating extends Rating {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
#NotNull(message = "Rating must be in context of Product")
private Product product;
}
After Product deletion ratings stay with deleted Product's ID
AbstractEntity implementation:
#Getter
#Setter
#MappedSuperclass
public abstract class AbstractEntity<I> implements Serializable {
private static final long serialVersionUID = 1700166770839683115L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
private I id;
}
In the #OneToMany relation you need to add the cascade type delete: cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
Or if you don't mind having all cascade types you can just put: cascade = CascadeType.ALL
EDIT:
Also check the name of the Product primary key in the database.
It should match the defined in the #JoinColumn annotation of ProductRating
The default database field for the attribute id of the Product class would be product_id.
However you have defined the id in AbstractEntity as name = "ID" so the #JoinColumn should be something like: #JoinColumn(name = "ID")
My alternative approach to fix this problem is to:
On parent-side relation create method with #PreRemove annotation
in this method iterate over collection with #[One/Many]ToMany annotation and call delete(obj) method for corresponding repository on child
On child-side relation create method with #PreRemove annotation
In this method set parent to null

JPA: Reference column in the child entity is null when using unidirectional #OneToMany

I have two entity classes.
Order.java
#Entity
#Table(name = "order_table")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, insertable=false, updatable=false)
private Set<Item> items;
// getters & setters & toString
Item.java
#Entity
#Table(name = "item")
public class Item implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "order_id", nullable = false)
private Long orderId;
// getters & setters && toString
I created a test class like this:
#Test
public void createOrderWithItems() {
Item item = new Item();
item.setName("Iron Man");
Order order = new Order();
order.setName("Toy");
order.getItems().add(item);
Order created = service.createOrder(order);
Order orderById = service.getOrderById(order.getId());
System.out.println("Created Order: " + orderById);
Item itemById = service.getItemById(item.getId());
System.out.println("Created item: " + itemById);
Assert.notNull(created.getId(), "Order ID is Null");
}
Test is green but if you check output, you'll see that orderId field in the Item class is null.
Created Order: Order{id=1, name='Toy', items=[Item{id=2, name='Iron Man', orderId=null}]}
Created item: Item{id=2, name='Iron Man', orderId=null}
Does JPA not update this column in the db automatically? Is this column is redundant? If so, how can I retrieve this information from test code?
You need to set orderId explicitly.
item.setOrderId(order.getId());
order.getItems().add(item);
You can create a method addItem(Item item) in your Order class and hide this logic within it.
Cascading will create an entry in db but it won't initialize field. JPA annotations just indicate to JPA provider how to perform mapping between entity and table.
Moreover, check your annotations. #JoinColumn should be used in the entity which owns the relationship (the corresponding table has column as a foreign key). Check the top answer for this question for detailed explanations: What's the difference between #JoinColumn and mappedBy when using a JPA #OneToMany association

Many to many with extra column only returns one row

I have Order's which consist of a bunch of Product's and a quantity.
My Order entity:
#Entity(name = "orders") // Select * from order order by... NO!
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "product", cascade = ALL, orphanRemoval = true, fetch = EAGER)
private List<OrderEntity> orderEntities = new ArrayList<>();
...
The relation between orders and products is as follows:
#Entity
public class OrderEntity implements Serializable {
#Id
#ManyToOne
#JsonIgnore
private Order order;
#Id
#ManyToOne(fetch = EAGER)
private Product product;
private int quantity;
...
My product is as follows:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderEntity> orderEntity;
...
The problem is that when I select an order by it's id I only get one OrderEntity back even though I can see that there are several associated with the order by looking at the database table. Anyone got a clue about what I'm doing wrong?
I think you have configured the mappedBy incoretly.. the other way round actually.
So you should have it like this:
Order:
#Entity(name = "orders") // Select * from order order by... NO!
public class Order {
#OneToMany(mappedBy = "order", cascade = ALL, orphanRemoval = true, fetch = EAGER)
private List<OrderEntity> orderEntities = new ArrayList<>();
Product:
#Entity
public class Product {
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderEntity> orderEntity;

Orders Products and Order_Line Hibernate Relationship with Composite Keys

Ok so today I have spent all day trying to figure out how to map a relationship between Orders, Products, and OrderLine with hibernate using annotations all java config.
I want OrderLine to be a junction table with extra fields, that joins Products and Orders.
Orders has a OneToMany relationsbhip with OrderLine - One order has many order lines.
Each Order has one or more OrderLines, Each OrderLine has one Order, Each OrderLine has one Product, Each Product can have 0 or more OrderLines.
From what I have been following along with tutorials, there are two ways to do it. One being with #Embeddable, #EmbeddedId annotations and the other being #IdClass annotations, I have tried both with no success I will post my code for my shot at #IdClass method.
My orders class
#Entity
#Table(name="orders")
public class Order {
#Id
#Column(name = "order_id")
#OneToMany(fetch = FetchType.EAGER, mappedBy = "order")
private Set<OrderLine> orderLines = new HashSet<>();
...some extra properties relevant to all orders
public Order(){}
...setters/getters
}
My Products class
#Entity
#Table(name="products")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "product_id")
public Integer product_id; // primary key
#OneToMany(fetch = FetchType.EAGER, mappedBy = "product")
private Set<OrderLine> orderLines = new HashSet<>();
...other properties
public Product() {
}
...setters/getters
}
Here is my OrderLine Class
#Entity
#IdClass(OrderLineId.class)
#Table(name="order_line")
public class OrderLine implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "product_id", insertable= false, updatable= false)
public Integer product_id;
#Id
#Column(name = "order_id", insertable= false, updatable= false)
public Integer order_id;
#ManyToOne
#JoinColumn(name = "product_id", insertable = false, updatable = false)
private Product product;
#ManyToOne
#JoinColumn(name = "order_id", insertable = false, updatable = false)
private Order order;
#Column(name = "product_quantity", unique = false, nullable = false)
private int productQuantity;
...additional fields associated with each OrderLine
And finally my OrderLineId implementation
public class OrderLineId implements Serializable {
private static final long serialVersionUID = 1L;
private Integer order_id;
private Integer product_id;
public OrderLineId(){}
public OrderLineId(Integer order_id, Integer product_id) {
this.order_id = order_id;
this.product_id = product_id;
}
#Column(name = "order_id")
public Integer getOrder() {
return this.order_id;
}
public void setOrder(Integer order_id) {
this.order_id = order_id;
}
#Column(name = "product_id")
public Integer getProduct() {
return this.product_id;
}
public void setProduct(Integer product_id) {
this.product_id = product_id;
}
#Override
public int hashCode() {
return order_id + product_id;
}
#Override
public boolean equals(Object obj) {
if(obj instanceof OrderLineId){
OrderLineId orderLineId = (OrderLineId) obj;
return orderLineId.order_id == order_id && orderLineId.product_id == product_id;
}
return false;
}
}
Here is the exception I am getting
MySQLSyntaxErrorException: Unknown column 'orderlines0_.order' in 'field list'
when I visit the end point below
#RequestMapping(value = "/testorder", method = RequestMethod.GET)
public ModelAndView testOrder(){
Order order = orderService.findOrderById(23);
Product product = productService.findProduct(2);
OrderLine ol = new OrderLine(product, order);
ol.setSize("large");
ol.setProductQuantity(30);
orderService.saveOrderLine(ol);
return null;
}
Please can somebody help me, this is driving me crazy....
Thank you
After going through your question and code, I think you have got your design wrong.
You say
want OrderLine to be a junction table with extra fields, that joins Products and Orders.
However you have only two variables, Order and Product in your OrderLine Class.
If I am not wrong, What you really need is a many-to-many table.
Your table order_line would contain two columns order_id and product_id, having foreign key to table orders and products respectively.
Your Order class would be:
#Entity
#Table(name="orders")
public class Order {
#Id
//id of table Order
#ManyToMany(
targetEntity = Product.class,
cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinTable(name = "order_line",
joinColumns =
#JoinColumn(name = "order_id", nullable = false, updatable = false),
inverseJoinColumns =
#JoinColumn(name = "product_id", nullable = false, updatable = false))
public Set<Product> products= new HashSet(0);
...some extra properties relevant to all orders
public Order(){}
...setters/getters
}
Your Product Class would look like:
#Entity
#Table(name="products")
public class Product implements Serializable {
#Id
#Column(name = "product_id")
public Integer product_id; // primary key
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "products", targetEntity = Order.class) private Set<Order> order = new HashSet<>();
...other properties
public Product() {
}
...setters/getters
}
As you can see, from Order entity you can get 'products' and from Product entity you can get 'orders'. This should server your purpose. No need to use 'OrderLine' class.
Other major errors in code:
- #Id is used to represent Primary key. You have used #Id multiple times for single class.
- In 'Order' class #Id given to Set, and #Id is used along with #OneToMany, which won't work.
Code provided would help you if, indeed what you need is many-to-many table.
I got it to work on my last try last night by doing this change
in OrderLine I removed the product_id and order_id fields and placed the #Id annotation over both ManyToOne relationships like so
#Entity
#IdClass(OrderLineId.class)
#Table(name="order_line")
public class OrderLine implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "product_id", insertable = false, updatable = false)
private Product product;
#Id
#ManyToOne
#JoinColumn(name = "order_id", insertable = false, updatable = false)
private Order order;
#Column(name = "product_quantity", unique = false, nullable = false)
private int productQuantity;
...additional fields associated with each OrderLine
I did not think this woudl work but correct table with correct values was updated, is this valid?
Thank you
Ok I figured it out - I ended up using embeddable method instead based on this guys excellent tutorial here
He goes through 3 major ways to implement many to many assosiations - a simple junction only table, a junction table (with additional fields) which is the method I needed to implement, and finally a less popular method where the class is mapped as a component.

JPA: Column should be unique inside its parent.

I have 2 tables:
#Entity
public class A implements Serializable {
#Column
#NotNull
#Size(min = 1)
#Pattern(regexp = "[A-Za-z]{1}[A-Za-z0-9_]*")
private String token;
#ManyToOne(optional = true)
#JoinColumn(name = "b_id", referencedColumnName = "id")
private B b;
...
}
#Entity
public class B implements Serializable {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "b")
private List<A> a;
...
}
The Token in entity A should be UNIQUE within B.
For example there is a Token1 within B1 and also a Token1 within B2, but there can
be a second Token1 inside B1.
I'm using JPA with Hibernate.
How do I achieve this?
By creating a UniqueConstraint on the combination of the foreign key and the token.
On entity A, something akin to the following:
#Table(
name="a",
uniqueConstraints={
#UniqueConstraint(columnNames={"b_id", "token"})
}
)
#Entity
public class A implements Serializable {
// ...
}

Categories