JPA Join with childEntity.childForeignEntity - jpa

I do not know how it is called in one word, but let me explain in details.
Lets assume I have following tables/schema in my database:
And following classes accordingly:
1.Post
#Entity
#Table(name = "posts")
public class Post {
#Id
private Long id;
#Column(name = "text")
private String text;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "post")
private Set<PostComment> postComments = new HashSet<>();
}
2.Post Comments
#Entity
#Table(name = "post_comments")
public class PostComment {
#Id
private Long id;
#Column(name = "post_id")
private Long postId;
#Column(name = "user_id")
private Long userId;
#Column(name = "text")
private String text;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="post_id")
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private User user;
}
3.User
#Entity
#Table(name = "users")
public class User {
#Id
private Long id;
#Column(name = "some_attributes")
private String someAttributes;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<PostComment> postComments = new HashSet<>();
}
How can I join Post with User via PostComment, so in my Post entity I could get all users commented:
#Entity
#Table(name = "posts")
public class Post {
....
//# join with post_comments.user_id
private Set<User> users = new HashSet<>();
....
}

Well, just get PostComment.user where PostComment.post equals your post.
#Query("select pc.user from PostComment pc where pc.post = :post")
List<User> getUsersWithComments(#Param("post") Post post);
Seems to work for me. Gives me the following SQL:
Hibernate: select user1_.id as id1_2_, user1_.some_attributes as some_att2_2_ from post_comments postcommen0_ inner join users user1_ on postcommen0_.user_id=user1_.id where postcommen0_.post_id=?
I don't know what this is all about:
#Column(name = "post_id")
private Long postId;
#Column(name = "user_id")
private Long userId;
or this
#JoinColumn(name="user_id")
#JoinColumn(name="post_id")
and you shouldn't do this:
= new HashSet<>();
and while we're at it this is redundant.
fetch = FetchType.LAZY,

Related

Unable to delete entity using Spring Data Jpa

I have a bi-directional mapping between Customer, Order and LineItems. When trying to delete using deleteById method of Spring Data JPA, the entities are not getting deleted and I do not see any exception.
#Entity
#Table(name = "customers")
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Data
public class Customer implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotEmpty(message = "customer name cannot be empty")
private String name;
#Column(unique = true)
#Email(message = "customer email address should be valid email address")
private String email;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
private Set<Order> orders;
private String password;
#Data
#NoArgsConstructor
#Builder
#AllArgsConstructor
#EqualsAndHashCode(exclude = {"customer", "lineItems"})
#ToString(exclude = {"customer", "lineItems"})
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Min(value = 2000, message = "Min order value should be 2000 INR")
#Max(value = 10_000, message = "Max order value can be 10000 INR")
private double price;
#PastOrPresent(message = "Order date cannot be in future")
private LocalDate date;
#ManyToOne
#JoinColumn(name="customer_id", nullable = false)
#JsonBackReference
private Customer customer;
#OneToMany(mappedBy = "order",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true
)
private Set<LineItem> lineItems;
//scaffolding code
// set the bidirectional mapping
public void addLineItem(LineItem lineItem){
if(this.lineItems == null){
this.lineItems = new HashSet<>();
}
this.lineItems.add(lineItem);
lineItem.setOrder(this);
}
}
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "order")
#ToString(exclude = "order")
#Builder
#AllArgsConstructor
#Entity
#Table(name="line_items")
public class LineItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private int qty;
private double price;
private String name;
#JsonIgnore
#ManyToOne
#JoinColumn(name="order_id", nullable = false)
private Order order;
}
this.orderRepository.deleteById(orderId);
Error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.classpath.ordersapi.model.Customer.orders, could not initialize proxy - no Session\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:612)\r\n\tat org.hibernate.collection.internal

Get only one field from the related table

I have following connection between tables Image:
#Entity
#Table(name = "image")
#Data
public class Image {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private byte[] image;
#OneToOne(mappedBy = "avatar")
private Personal personal;
}
and Personal
#Entity
#Table(name = "personal")
#Data
public class Personal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String position;
private String phone;
private String email;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "avatar_id", referencedColumnName = "id")
private Image avatar;
}
I want to get back from my service Personal entities with ONLY id field from Image table. Repositories and services are standard from tutorials - without extra code or overrides
If read-only is ok use a DTO or interface projection.

JPA dataIntegrityViolationException occurs for multiple parents

I am working on simple spring security demo and want to put user and role info into db. Here is the simple structure of my entity.
#Entity
#Table(name = "users")
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long userId;
#OneToMany(mappedBy = "users", fetch = FetchType.LAZY, orphanRemoval=true, cascade = CascadeType.ALL)
private List<UserRoleMapping> userRoleMapping;
}
//
#Entity
#Table(name = "user_role_mapping")
public class UserRoleMapping {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_role_mapping_id")
private Long userRoleMappingId;
#ManyToOne(fetch = FetchType.LAZY)
private Users users;
#ManyToOne(fetch = FetchType.LAZY)
private UserRole userRole;
}
//
#Entity
#Table(name = "users_role")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private Long roleId;
#Column(name = "role")
private String role;
#Column(name = "role_desc")
private String roleDesc;
#OneToMany(mappedBy = "userRole", fetch = FetchType.LAZY, orphanRemoval=true)
private List<UserRoleMapping> userRoleMapping;
}
In my use case, I have to make sure when the user is created then I have to insert subsequence userRoleMapping. And If the userRole is removed then the userRoleMapping must be deleted as well.
So I put CascadeType.ALL and orphanRemoval=true in users entity, and orphanRemoval=true in userRole entity.
However, when I run userRoleRepository.delete(userRole). I have dataIntegrityViolationException.
I did some researches on it and understand it is a kind of jpa constrains to make sure we delete the parents (users) as well.
May I ask if there are any workaround for my use case?
Thanks
Here is the solution to my use cases. Instead of using OneToMany in both entity, I should use ManyToMany relationship. And it is quite make sense as I don't care (for now) the mapping in java logic. Here is my code
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long userId;
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST })
#JoinTable
private Set<UserRole> userRole;
}
//
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private Long roleId;
#Column(name = "role")
private String role;
#Column(name = "role_desc")
private String roleDesc;
#OneToMany(mappedBy = "userRole", fetch = FetchType.LAZY)
private Set<Users> users;
}

How to fetch entities by objects value in JPA criteria api query

I am using JPA with JSF datatable with lazy loading.
Here One car can be owned by many users. So when i logged in to the application i want the cars which is owned by the user logged in(assume it as userId=1).
I have a mapping table "Cars_User" that contains carId and userId columns.
My Entities are like this
My Car Class
#Entity
#Table(name="car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#Transient
private boolean myCar;
#NotNull
#Size(min = 1, max = 50)
public String name;
#OneToMany(cascade = { CascadeType.REFRESH }, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinTable(name = "Cars_User", joinColumns = #JoinColumn(name = "carId"), inverseJoinColumns = #JoinColumn(name = "userId"))
private List<User> carUsers = new ArrayList<User>();
getters ...
setters ...
}
User Class
#Entity(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
private String lastName;
}
I have found one answer for Lists of String collection in this link but how can be achieved in my case.
I wanted to do get all Cars entities in criteria api that contains the logged in user id "userId" in carUsers Lists. can anyone please help?
I found the solution. I have passed the logged in user Object "user" in isMember function. This may help for somebody.
CriteriaBuilder criteriaBuilder = em.getEntityManagerFactory().getCriteriaBuilder();
CriteriaQuery<Car> criteria = criteriaBuilder.createQuery(Car.class);
Root<Car> root = criteria.from(Car.class);
criteria.where(criteriaBuilder.isMember(user, root.get(Car_.carUsers)));
List<Car> cars = em.createQuery(criteria).getResultList();

java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST

I have two 2 classes in relation many to many.
#Entity
#Table(name = "recipies")
public class Recipie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String url;
private String image;
#ManyToMany
#JoinTable(
name = "recipie_ingredients",
joinColumns = {
#JoinColumn(name = "recipie_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")})
private List<Ingredient> ingredients = new ArrayList<>();
#Entity
#Table(name = "ingredients")
public class Ingredient implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(mappedBy = "ingredients")
private List<Recipie> recipies;
I would like to create a new recipie this way:
List<Ingredient> ingredientsList = new ArrayList<>();
String ingredientName = "example";
Ingredient ingredient = ingredientsDao.findIngredientByName(ingredientName);
if (ingredient == null) {
ingredient = new Ingredient();
ingredient.setName(ingredientName);
}
ingredientsList.add(ingredient);
.....
recipie.setIngredients(ingredientsList);
recipiesDao.addRecipie(recipie);
If ingredient doesn't exist in database, occur errors like this
Caused by: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
Is there any way to Ingredient objects created in the table automatically?
I try add CascadeType.PERSIST but It also doesn't work
#ManyToMany(mappedBy = "ingredients", cascade = CascadeType.PERSIST)
private List<Recipie> recipies;
First of all, for a bidirectional relationship, both sides need to be updated, so:
recipe.getIngredients().add(ingredient);
ingredient.getRecipes().add(recipe);
Then, you can set the cascade to PERSIST on the side of the relationship which you are passing to save(). So if you are saving the recipe, you should mark the Recipe.ingredients with
#ManyToMany(cascade = CascadeType.PERSIST)
(Side note, it's spelled "recipe", not "recipie")
As mentioned by #Gimby, you need to assign both sides of the relationship.
When dealing with #Many... sided relationships I always initialise the collection (which you've done on one side):
#Entity
#Table(name = "recipies")
public class Recipie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String url;
private String image;
#ManyToMany
#JoinTable(
name = "recipie_ingredients",
joinColumns = {
#JoinColumn(name = "recipie_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")})
private List<Ingredient> ingredients = new ArrayList<>();
...
}
#Entity
#Table(name = "ingredients")
public class Ingredient implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(mappedBy = "ingredients")
private List<Recipie> recipies = new ArrayList<>();
...
}
And then a slight variation in your logic:
String ingredientName = "example";
Ingredient ingredient = ingredientsDao.findIngredientByName(ingredientName);
if (ingredient == null) {
ingredient = new Ingredient();
ingredient.setName(ingredientName);
}
...
// Don't forget to assign both sides of the relationship
recipe.getIngredients().add(ingredient);
ingredient.getRecipies().add(recipe);
recipiesDao.addRecipe(recipe);
This should then cascade persist/update correctly.
The real fun will begin when you try to figure out how to associate a quantity with the ingredient...