Saving in one shot two parents and one child Spring JPA - spring

I have never had a case where I want to save multiple parents and one child in one shot. In my case, I have two parent entities and one child. The two parent entities have a foreign key on the child entity.
I have an example like this ->
#Entity
#Table("parentA")
public class ParentA
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToMany(cascade = CascadeType.ALL, mappedBy="parentA")
private List<Child> child;
// Getters and Setters and some methods
}
#Entity
#Table("ParentB")
public class ParentB
{
#Column("CODE")
private Long code;
#OneToMany(cascade = CascadeType.ALL, mappedBy="parentB")
private List<Child> child;
// Getters and Setters and some methods}
#Entity
#Table("Child")
public class Child
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column("ID")
private Long ID;
#Column("parentA_ID")
private Long parentAId;
#Column("code")
private String code;//from parentB
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID", referencedColumnName = "parentA_ID")
private ParentA parentA;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "code", referencedColumnName = "code")
private ParentB parentB;
// Getters and Setters and some methods}

You just add attribute cascade = CascadeType.PERSIST to Child fields:
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn
private ParentA parentA;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn
private ParentB parentB;
// ...
}
public interface ChildRepository extends JpaRepository<Child, Integer> {
}
Then the parent entities will be saved when you save the child entity:
ChildRepository childRepo;
// ...
var child = new Child();
var parentA = new ParentA();
var parentB = new ParentB();
parentA.setChild(List.of(child));
parentB.setChild(List.of(child));
child.setParentA(parentA);
child.setParentB(parentB);
childRepo.save(child);
You can see that a single save inserted all three rows:
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener -
Name:dataSource, Connection:4, Time:52, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into parenta values ( )"]
Params:[()]
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener -
Name:dataSource, Connection:4, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into parentb values ( )"]
Params:[()]
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener -
Name:dataSource, Connection:4, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into child (parenta_id, parentb_id) values (?, ?)"]
Params:[(3,3)]

Related

Can orphanRemoval be used for depth > 1?

Should all child1 and child2 (depth 2) be deleted, when a parent gets deleted?
Database is Informix, constraints are created in the child tables. Deletion of parent is performed with JpaRepository.deleteById(parent.getId()), both do nothing and no error message occurs (show_sql just lists selects). Spring version is 5.3.19, spring-data-jpa 2.6.4.
Current example code:
#Entity
#Table(name = "parent_table")
public class Parent
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.EAGER)
private Set<Child1> children = new HashSet<>();
}
#Entity
#Table(name = "child1_table")
public class Child1
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "parentid", referencedColumnName = "id", nullable = false)
private Parent parent;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "child1", fetch = FetchType.EAGER)
private Set<Child2> children = new HashSet<>();
}
#Entity
#Table(name = "child2_table")
public class Child2
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "child1id", referencedColumnName = "id", nullable = false)
private Child1 child1;
}
Update
added
#PreRemove
private void deleteChildren()
{
children.clear();
}
to Parent and Child1. Now children get deleted, but not the Parent.
In fact, Parent also had a parent and I had to remove this from it's Set too.
So the solution is:
Clear children Sets
#PreRemove
private void preRemove()
{
children.clear();
}
Remove the root entity from its parent in case it has a parent
#PreRemove
private void preRemove()
{
children.clear();
parentsParent.getParents().remove(this);
}

#One-to-Many relationship does not working in Spring

I have an Entity Recipe with a relationship OneToMany with Ingredients.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL) // se eliminiamo la Recipe eliminiamo anche notes
private Notes notes;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "recipe")
private Set<Ingredient> ingredients;
#ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "recipe_category",
joinColumns = #JoinColumn(name = "recipe_id"),
inverseJoinColumns = #JoinColumn(name = "category_id"))
private Set<Category> categories;
...getter and setter...
}
And an Entity Ingredient:
#Entity
public class Ingredient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
private int amount;
#ManyToOne
private Recipe recipe;
...getter and setter...
}
In order to test it I have used a controller to insert and retrieving all row:
#GetMapping({"","/"})
public List<Recipe> allRecipe() {
return recipeRepository.findAll();
}
#GetMapping("/insert")
public Recipe addRecipe() {
Set<Ingredient> ingredients = new HashSet<>();
ingredients.add(new Ingredient("ingredient-"+Math.random(), 10));
Recipe newRecipe = new Recipe("Recipe-"+Math.random(),
null, ingredients, null);
return recipeRepository.save(newRecipe);
}
The repository is a JPA Repository.
I do not have any errors, but when I try to retrieve an object I get no ingredients even though they are saved on the table (but with recipe_id = null).
How can I solve this problem?
Initialize your ingredients as
#OneToMany(cascade = CascadeType.ALL, mappedBy = "recipe")
private Set<Ingredient> ingredients = new HashSet<>();
Change your your controller to,
#GetMapping("/insert")
public Recipe addRecipe() {
Ingredient ingredient = new Ingredient("ingredient-"+Math.random(), 10));
Recipe newRecipe = new Recipe("Recipe-"+Math.random(),
null, null); //constructor without ingredient arg
newRecipe.getIngredients.add(ingredient);
ingredient.setRecipe(newRecipe);
return recipeRepository.save(newRecipe);
}

Cannot delete or update a parent row HIBERNATE one to many

I'm getting this error when I try to delete a pet. This pet, has visits (child) but I have defined CASCADE.ALL in pet entity. Any idea ?
ERROR:
Cannot delete or update a parent row: a foreign key constraint fails (web_customer_tracker.visits, CONSTRAINT visits_ibfk_1 FOREIGN KEY (pet_id) REFERENCES pets (pet_id))
#Entity
#Table(name = "pets")
public class Pet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "pet_id")
private int pet_id;
.....
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "visit_id")
private Set<Visit> visits;
Visit class:
#Entity
#Table(name = "visits")
public class Visit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "visit_id")
private int visit_id;
#ManyToOne
#JoinColumn(name = "pet_id")
private Pet pet;
....
you are using mappedBy in a wrong way
the mappedBy refers to the object name in the opposite side
like this
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "pet")
private Set<Visit> visits;
or if you want to map it by the JoinColum try this
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "pet_id")
private Set<Visit> visits;

access many to many relation in spring

I have a class called Tag:
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
...
}
And a class called Post
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "post_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tag> tags = new HashSet<>();
...
}
It creates another table named post_tags.
How can I write a Controller to access that table as it is not similar a repository?
Is there more easy and convenient way to implement ManyToMany relationship ?
My pom.xml
You don't need to access that relation table manually. You can load load all Tag entities, and then load all the referenced Post entities.
The relation table is enterily managed by your ORM frameork.
But, if you still want to access the relation table, you can use native queries in your Spring Data JPA repository, e.g.
#Query(value="select post_id, tag_id from post_tags", nativeQuery=true)
List<PostTag> loadPostTags();
PostTag class is not a jpa-managed entity and must match the structue of the returned table:
public class PostTag {
private long postId;
private long tagId;
// getter, setter
}
Use this way
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "post_id") })
private Set<Post> posts = new HashSet<>();
...
}
#Entity
#Table(name = "posts")
public class Post {
#Id
#Column(name = "post_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long postId;
...
}

Record not inserted while using #ManyToOne mapping

I have 2 tables 'orders' and 'orderlines' and used bidirectional OneToMany mapping.When i save the order, record is successfully inserted into table 'orders'.But my 'orderlines' table is empty.No record is inserted.
This is the save operation code in Controller.
#RequestMapping(value = "ordersuccess", method = RequestMethod.POST)
public String processOrder(#ModelAttribute("order") Order order,
#ModelAttribute("cart") Cart cart,
BindingResult result) {
if (!result.hasErrors()) {
Set<OrderLine> orderLines = new HashSet<OrderLine>();
for(CartLine c : cart.getCartLines()) {
OrderLine line = new OrderLine();
line.setOrder(order);
line.setProduct(c.getProduct());
line.setProductPrice(c.getProduct().getPrice());
line.setTotalPrice(c.getPrice());
orderLines.add(line);
order.setOrderLines(orderLines);
}
orderService.save(order);
orderLineService.save(orderLine);
}
return "ordersuccess";
}
Can someone point me what wrong i am doing.
EDIT:
OrderLine.java
public class OrderLine {
#Id
#GeneratedValue
#Column(name="orderline_id")
private int orderline_id;
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#ManyToOne(targetEntity = Product.class,
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JoinTable(
name="products",
joinColumns=#JoinColumn(name="product_id")
)
private Product product;
)
Order.java
public class Order {
#Id
#GeneratedValue
#Column(name="id")
private int id;
#OneToMany(mappedBy = "order")
private Set<OrderLine> orderLines;
//getter/setter
The orderLines object is created:
Set<OrderLine> orderLines = new HashSet<OrderLine>();
You then add lines to it:
orderLines.add(line);
But it never attributed to an order or sent to the service layer.
Also the OrderLine.product mapping should be like this
public class OrderLine {
#Id
#GeneratedValue
#Column(name="orderline_id")
private int orderline_id;
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "product_id")
private Product product;
}
and Order.orderLines should have a cascade:
public class Order {
#Id
#GeneratedValue
#Column(name="id")
private int id;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Set<OrderLine> orderLines;
}
You then need to save the orderLines:
order.setOrderLines(orderLines);
and save the order:
orderService.save(order);
When order is saved it will cascade the orderlines and the associated product too.
If you have bidirectional associations don't forget to set both sides.

Resources