I have an entity User, that can have exactly one Company. I have a Company, that can be assigned to multiple User objects.
Currently if I want to persist a User, I need to get the Company (as it may exist without any User being assigned to it) and assign it. Further more I have to add the User manually to the Company using Company#addUser. Afterwards I save run CompanyRepository.save(company) (which should suffice to persist the User, too, I think, because I am using cascade = CascadeType.PERSIST).
Is there a way to say, that if I get the User and assign a Company to it, the "back-reference" is dealt with automatically? Or do I always have to get the Company and use Company#addUser to add that reference?
My entities look like this (I omitted more properties and reduced it to the most important properties and methods):
Company.java
package com.portal.user.persistence;
(imports omitted)
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder (toBuilder = true)
#Entity
#Table (name = "companies")
public class Company {
#Id
#GeneratedValue (generator = "uuid")
#GenericGenerator (name = "uuid", strategy = "uuid2")
#Column (name = "id")
private String id;
#Column (name = "ucid")
private String ucid;
#OneToMany (fetch = FetchType.LAZY, mappedBy = "company", cascade = CascadeType.PERSIST)
private List<User> users;
public void addUser(#NonNull User user) {
if (users == null) {
users = new ArrayList<>();
}
users.add(user);
}
public void removeUser(#NonNull User user) {
users.remove(user);
}
}
User.java
package com.portal.user.persistence;
(imports omitted)
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder (toBuilder = true)
#Entity
#Table (name = "users")
public class User {
#Id
#GeneratedValue (generator = "uuid")
#GenericGenerator (name = "uuid", strategy = "uuid2")
#Column (name = "id")
private String id;
#ManyToOne (cascade = CascadeType.PERSIST)
private Company company;
}
There are a lot of answers to your question, based on the implementation you would like to achieve.
The first way is to remove the #OneToMany relation in Company and the user list. In this way you would only have to manage one side of the relation, and when you need to search for all users in a company you could use a custom query performing a left join on users and companies tables.
The second way, keeping both side of the relation, is to implement a method 'setCompany' inside the User class like the following:
public void setCompany(Company c){
c.addUser(this);
this.company = c;
}
However in my experience, the first solution fits better since less relations will lead to a lot less work to do later on, especially regarding DTO conversion and deletion of elements from the DB.
Related
I have 2 entities:
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "source_company")
public class SourceCompany {
#Id
#EqualsAndHashCode.Include
private UUID id;
private String name;
#OneToMany( mappedBy = "company")
private final Set<SourceUser> users = new HashSet<>();
#Column(name = "version")
#Version
private Long version;
}
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "source_user")
public class SourceUser {
#Id
#EqualsAndHashCode.Include
private UUID id;
private String name;
#Column(name = "version")
#Version
private Long version;
//ref
#ManyToOne
#JoinColumn(name = "fk_source_company")
private SourceCompany company;
}
Is it correct to save in this way (only 2 save)?
#Test
public void testSourceUserSave() {
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUser.setCompany(sourceCompany);
sourceCompany.getUsers().add(sourceUser);
sourceCompanyRepository.save(sourceCompany);
sourceUserRepository.save(sourceUser);
assertNotNull(sourceUser);
assertEquals(sourceUser.getCompany().getId(), sourceCompany.getId());
assertEquals(sourceCompany.getUsers().stream().findFirst().get().getId(), sourceUser.getId());
}
or I need to save the user (without company) and the company (without user) and after that to update the user with a save and the company (without save because is not the owner) like this (3 save):
#Test
public void testSourceUserSave() {
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUserRepository.save(sourceUser);
sourceCompanyRepository.save(sourceCompany);
sourceUser.setCompany(sourceCompany);
sourceCompany.getUsers().add(sourceUser);
sourceUserRepository.save(sourceUser);
assertNotNull(sourceUser);
assertEquals(sourceUser.getCompany().getId(), sourceCompany.getId());
assertEquals(sourceCompany.getUsers().stream().findFirst().get().getId(), sourceUser.getId());
}
It seems, looking in the db, that the first way works, so in future can I update only the owner side (I mean update and save) and so can I update the not-owner side only in the object without save it again?
Thanks in advance
You usually tend to save only one of the objects. This can be done adding the
#ManyToOne(cascade = CascadeType.PERSIST)
to the mapping annotation. This makes sure that the nested entities get persisted too
You would need to do just:
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUser.setCompany(sourceCompany);
sourceUserRepository.save(sourceUser);
One more thing to note is that the .save method actually returns an entity itself. That entity is the persisted entity just created. Basically if you manage everything within a single transactional method any modification to the persisted entity within that method (transaction) will be applied without calling any save, merge or update method
I suggest reading about the #Transactional annotation
I have the following situations with multiple OneToOne reletanships:
#Table(name = "User")
public class User {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Account")
public class Account {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Settings")
public class Settings{
#OneToOne()
#JoinColumn(name = "userColumnName")
private User user;
#OneToOne()
#JoinColumn(name = "accountColumnName")
private Account account;
}
Now, the issue here is that I have to create and save each model independently, because they are created as a result of StreamEvent capturing. Also, Hibernate will create automatically userColumnName and accountColumnName. What I would really need to do is to have something this:
Is this possible to implement with Hibernate? Could someone provide an example?
Do
#JoinColumn(name="userColumnName", insertable=false,updatable=false),
#JoinColumn(name="accountColumnName", insertable=false,updatable=false),
And Add two more fields in Settings Entity for these tow column and Map with same Column
I'm working with JPA, Spring boot.
Using #OneToMany annotation, when I fetch orders containing cart items.
My domain codes are below.
Order:
#Data
#Entity
#Table(name="\"order\"")
#ToString
public class Order {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="order_id")
#Getter #Setter
private List<Cart> carts;
public void addCart(Cart cart) {
if (this.carts == null) {
carts = new ArrayList<Cart>();
}
carts.add(cart);
}
}
Cart:
#Data
#Entity
#Table(name="cart")
#ToString
public class Cart {
#Id
#GeneratedValue
#Getter #Setter
#Column(name="id")
private Long id;
#Column(name = "order_id")
#Getter #Setter
private Long orderId;
}
This works very well when I fetch only one order, but doesn't work when I fetch more than two orders. I mean when I fetch only one order, carts field's size is 1 or more, but when I fetch two or more orders, the carts field's size is 0.
How can I solve this problem? Thank you in advance!
It's nearly impossible to find out what the fetching problem is without seeing the code which is loading / querying your entities. So could you please add it to your question?
Meanwhile there are at least some things you could improve to have cleaner entities and faster code, maybe this also can help a little to hunt down the problem.
At first, you are using redundant configurations, respective annotations you are recreating the default values with.
I assume that is lombok what you are using, right? You could remove the #Getter and #Setter annotations on your fields in both entities and add it once on class level to avoid the declaration on every single field, but since you are using #Data you don't need it at all. Same with #toString, #Data is a convenience annotations for all of it (and a little more).
The JavaDoc of #Data says:
Equivalent to {#code #Getter #Setter #RequiredArgsConstructor #ToString #EqualsAndHashCode}.
Then, although the #Table(name="\"order\"") on the order entity is needed because order is a reserved word in some DBMS, the #Table(name="cart") on the cart entity is the default.
Up next, I would not recommend lazy initialization of collections, because in general there is no benefit to do that compared to the penalty it causes while checking for null before every access. Just initialize it within declaration and you will never have to care about handling null again. Beside of that you should think about using a Set instead a List for the carts (so you will have no duplicates).
Also think about the FetchType because EAGER is only useful if you work with detached entities and want to initialize the relation in every case. LAZY is the default for #OneToMany and should be preferred.
A thing you already improved is the #JoinColumn, that will prevent Hibernate (I brazenly assume you are using Hibernate) creating a join table. But even more important is thinking about turning the #OneToMany into a #ManyToOne on the other side or making it bidirectional. That would gain some performance on reading (it also prevents the join table so less joins are needed, faster indexing is possible) and writing time (relation managed by parent side).
So what do you think about this:
#Data
#Entity
#Table(name="\"order\"")
public class Order {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Cart> carts = new HashSet<>();
}
and this:
#Data
#Entity
public class Cart {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Order order;
}
Order class:
public class Order {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#OneToMany(mappedBy="orderId" fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Getter #Setter
private Set<Cart> carts;
}
Cart class:
public class Cart {
#Id
#GeneratedValue
#Getter #Setter
#Column(name="id")
private Long id;
#ManyToOne
#Getter #Setter
private Order orderId;
}
For example: JPA OneToMany and ManyToOne Relationships
I just wrote it to change.
I hope that it will work
I have a Company entity that I fetch with a JPQL query with Hibernate. The entity has a many-to-many association with a Keyword entity. Since the join table has an additional column is_active, this table has been mapped to a CompanyKeyword entity. So the association is like this:
Company <-- CompanyKeyword --> Keyword
Now, the association from the Company entity is lazy, and it is not initialized by my JPQL query, as I want to avoid creating a cartesian product performance problem. That is why I want to initialize the association after running the JPQL query, e.g. like this:
#Service
class CompanyServiceImpl implements CompanyService {
#Autowired
private CompanyRepository companyRepository;
#Transactional
public Company findOne(int companyId) {
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.companyKeywords());
return company;
}
}
For a "normal" many-to-many association, this would work great, as all of the associated entities would be fetched in a single query. However, since I have an entity between Company and Keyword, Hibernate will only initialize the first part of the association, i.e. from Company to CompanyKeyword, and not from CompanyKeyword to Keyword. I hope that makes sense. I am looking for a way to initialize this association all the way without having to do something like this:
Company company = this.companyRepository.findOneWithSomeCustomQuery(companyId);
Hibernate.initialize(company.getCompanyKeywords());
for (CompanyKeyword ck : company.getCompanyKeywords()) {
Hibernate.initialize(ck.getKeyword());
}
The above code is neither clean, nor good in terms of performance. If possible, I would like to stick to my current approach of using a JPQL query to fetch my Company entity and then initializing certain associations afterwards; it would take quite a bit of refactoring to change this in my project. Should I just "manually" fetch the association with a second JPQL query, or is there a better way of doing it that I haven't thought of?
Below are my mappings. Thanks in advance!
Company
#Entity
#Table(name = "company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Size(max = 20)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyKeyword> companyKeywords = new HashSet<>();
// Getters and setters
}
CompanyKeyword
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyKeyword implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Keyword.class)
#JoinColumn(name = "keyword_id")
private Keyword keyword;
#Column(nullable = true)
private boolean isActive;
// Getters and setters
}
CompanyKeywordPK
public class CompanyServicePK implements Serializable {
private Company company;
private Service service;
public CompanyServicePK() { }
public CompanyServicePK(Company company, Service service) {
this.company = company;
this.service = service;
}
// Getters and setters
// hashCode()
// equals()
}
Keyword
#Entity
#Table(name = "keyword")
public class Keyword {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// Fields and getters/setters
}
You'll indeed need to execute an additional JPQL query, fetching the company with its companyKeyWords and with the keyword of each CompanyKeyWord.
You could also doing it by simply looping and initializing every entity, and still avoid executing too many queries, by enabling batch fetching.
I have two persistence entity: User and UserDetail. They have one-to-one relationship. I use hibernate annotations. But I am getting in my database several objects of user information for one same user. Apparently my knowledge of Hibernate annotations are not so good to solve this problem.
User class:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private UserDetail userDetail;
// setters and getters
}
UserDetail class:
#Entity
#Table(name = "USER_DETAIL")
public class UserDetail {
#OneToOne
#JoinColumn(name = "USER_ID")
private User user;
// other fields
}
I use this in my code as follows:
UserDetail userDetail = new UserDetail();
userDetail.setInfo(info);
userDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(userDetail);
And everything works properly. Here's what my table USER_DETAIL:
But when I try to change user information, I get an incorrect behavior. I get following table after I again set user information:
UserDetail newUserDetail = new UserDetail();
newUserDetail.setInfo(newInfo);
newUserDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(newUserDetail);
Why the same two objects of information correspond to one user?? I have One-To-One relationship. How can I avoid this? What am I doing wrong?
If you want to modify an existing UserDetail, then you must set its ID, or get it from the session and modify it. Else, Hibernate thinks it's a new one that must be saved, since it doesn't have any ID.
UserDetail existingUserDetail = session.get(UserDetail.class, theUserDetailId);
existingUserDetail.setInfo(newInfo);
To make sure you don't save two UserDetail instances for the same user, you should add a unique constraint on the USER_ID column of the UserDetail database table.