Hibernate insert causes update of another table - spring

I have a model that looks like this:
#Entity
#Table(name = "A")
class A {
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "A_categories", joinColumns = {
#JoinColumn(name = "A_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "category_id",
nullable = false, updatable = false) })
private List<Category> categories;
}
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id")
#GeneratedValue
private Integer id;
#Column(name = "category_name")
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private List<A> a;
}
So there is a many-to-many relationship between A and Category. Now categories are static, and cannot be changed by a user. From the UI, the user will try to save an entity A, and each can have one or more categories. So the JSON that comes back looks a little like this:
{A: {categories: [{id: 1}, {id: 2}, {id: 3}]}}
Now when I try to save this A object (after jackson has unmarshalled to java), I just want entries to be made in the join table, A_categories, for each category the new entity has.
However, the Category entity itself also gets updated. So if you notice, the JSON does not have any category_name, and so the database entry for each Category will also get updated to a null entry for the name.
How can I prevent this from happening?

Two different approaches:
1) Set managed categories before merging.
a.setCategories(readAllByIds(a.getCategories()))
private Collection<Category> readAllByIds(Collection<Category> categories) {
Collection<Category> result = new ArrayList();
for (Category category : categories) {
result.add(entityManager.getReference(Category.class, category.getId()));
}
return result;
}
EntityManager.getReference returns proxy, so the additional benefit is that no database round trips are executed for reading the associated categories.
With this solution you are not merging the deserialized categories into the persistence context, thus Hibernate will not synchronize their state with the database.
2) Do not cascade any operations from A to categories (remove cascade attribute).
This way, neither PERSIST nor MERGE will be cascaded and Hibernate will just use ids of the detached Category instances to store the data into the relationship table.
Sidenote: Generally, cascading REMOVE or ALL in a many-to-many association makes no sense (if you remove an A you probably don't want to remove all the categories it belongs to).

#Column has the attributes insertable and updatable. You can set them to false:
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id", insertable=false, updatable=false)
#GeneratedValue
private Integer id;
#Column(name = "category_name", insertable=false, updatable=false)
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private List<A> a;
}

You can try adding this
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id")
#GeneratedValue
private Integer id;
#Column(name = "category_name")
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories", cascade=CascadeType.DETACH)
private List<A> a;
}
with the cascade.DETACH should not save changes when you save A entity, but let me know if is not working to make an example modifying the ManyToMany relationship with this DETACH action

Related

Hibernate mapping user relation to entities

Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.

Inner join on two tables in spring boot

I have 2 entities and want to perform an inner join on the ID of these two tables. How do I do that? After joining the tables, how do I get the values?
First entity: Employee.java
#Entity
#Table(name = "emp")
public class Employee {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "language", nullable = false)
private String language;
Second entity: Username.java
#Entity
#Table(name = "users")
public class Username {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "name", nullable = false)
private String name;
Thanks
I don't know it's helpful for your or not but,
You have to give relationship between those table first(Here i defined bidirectional relationship).
I suppose there is #OneToOne mapping. As like follow,
In Employee Table,
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "username_id")
private Username username;
#OneToOne(mappedBy = "employee")
private Employee employee;
Same way whenever you need those data base on requirement then Place Query as following way in your Employee Repository,
#Query(nativeQuery = true, value="<your-join-query>")
public Employee getEmployeeAllDetails();
For more brief detail follow this kind of tutorials which give you better idea regurding working mechenisum.
https://howtodoinjava.com/
https://www.baeldung.com/

JPA Specification - search simultaneously in main table rows and child rows with relation OneToMany

I have two entities. One of them is a child of the other one with a relation with OneToMany. Is it possible to implement search criteria that looks up simultaneously in both the main entity and all the child entities?
Example: I have a Company with many employees. If I search with some text, I want to retrieve all the companies, which title contains that text or its employee's names contain that text.
Here are the example entities:
#Entity
public class Company extends AbstractEntity {
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String companyName;
#OneToMany(mappedBy = “company”, cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
protected Set<Employee> employees = new HashSet<>();
}
#Entity
public class Employee extends AbstractEntity {
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “company_id”, nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Company company;
}
Here is the example query, that I want to transform into Specification criteria
#Query(value = “SELECT DISTINCT c from Company c left outer join c.employees e
WHERE c.companyName LIKE CONCAT('%',:text,'%')
or e.firstName LIKE CONCAT('%',:text,'%')
or e.lastName LIKE CONCAT('%',:text,'%')”)
If you are using Spring JPA data repository, your interface method would look like this.
Company findByCompanyNameConatainingOrEmployeesFirstNameConatainingOrEmployeeslastNameConataining(String searchTextCompanyTitle, String searchTextEmployeeFName, String searchTextEmployeeLName);
If you are not using data repository, please explain your data access design to get an accurate answer.

OneToMany does not return values saved from other entity

I have entity structure:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements= new ArrayList<>();
}
#Entity
#Table(name = "user_agreements")
public class UserAgreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "agreement_id")
private Agreement agreement;
}
#Entity
#Table(name = "agreements")
public class Agreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agreement", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements = new ArrayList<>();
}
I am using Spring Boot with JpaRepository. When I use AgreementRepository extends JpaRepository<Agreement, Long> to save Agreement and related UserAgreement, it works well and cascades necessary fields to DB:
agreement.getUserAgreements().add(new UserAgreement(user, agreement, status));
agreementRepository.save(agreement);
However, after save, if try to retrieve user.getActiveUserAgreements(), I get empty list. It does not refresh.
How to force User entity to get List<UserAgreement> which was saved from other side?
From the Wikibooks: OneToMany
The relationship is bi-directional so, as the application updates one
side of the relationship, the other side should also get updated, and
be in sync. In JPA, as in Java in general it is the responsibility of
the application, or the object model to maintain relationships. If
your application adds to one side of a relationship, then it must add
to the other side.
That means you need to assign the UserAgreement to the User when you create the relation.
It looks like many-to-many association. You might probably drop UserAgreement class. Anyway, to support it you have to write helper methods addAgreement(), removeAgreement() etc. See more details here https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Spring MVC - loading data from database

I have two entities which are in many to many relation and I can't load Set <Category> categories. These fields are filled in the database.
#Entity
#Table(name="Product")
public class Product {
#Id
#GeneratedValue
private int idProduct;
private String status;
private String name;
#ManyToMany(fetch = FetchType.EAGER, mappedBy= "products")
private Set <Category> categories;
}
#Entity
#Table(name="Category")
public class Category {
#Id
#GeneratedValue
private int idCategory;
private String name;
#ManyToMany(fetch = FetchType.EAGER)
private Set <Product> products;
}
This returns nothing int the view and the loop doesn't rotate even once.
<c:forEach items="${product.categories}" var="items">
<p>${items.name}</p>
</c:forEach>
I join the schema. Could someone write what to do to make it work, please?
enter image description here
This not works.
#Entity
#Table(name="Category")
public class Category {
#Id
#GeneratedValue
private int idCategory;
private String name;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "Product_Category", joinColumns = {
#JoinColumn(name = "Category_idCategory", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "Product_idProduct",
nullable = false, updatable = false) })
private Set <Product> product;
Hibernate infers the sql that it needs to create from the annotation on the objects. Your product entity is telling hibernate to get information about the SQL join from the Category entity. This is from the mappedBy clause in the #ManyToMany annotation.
When it goes to the Category entity, it doesn't find what it needs, and it just gives an empty set.
Most #ManyToMany annotations are done with a join table. Here is a sample join table annotation
#JoinTable(name = "product_to_category", joinColumns = {
#JoinColumn(name = "category_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "product_id",
nullable = false, updatable = false) })
Depending on your schema, you might need to adjust the above annotation to get it working. It will give you a good start.

Resources