#Entity
public class User{
#ElementCollection
#Enumerated(EnumType.STRING)
#CollectionTable(name = "SEC_USER_ROLES",
joinColumns =
#JoinColumn(name = "USER_ID", referencedColumnName = "ID"))
#Column(name = "ROLE_NAME")
private List<Role> roles;
[...]
}
public enum Role {
ROLE_SUPER_ADMIN,
ROLE_ADMIN,
ROLE_ARB,
ROLE_AP;
[...]
}
With this mapping, when I try do delete one ROLE, for example ROLE_ARB, it always ends up with deleting the role and inserting it once again.
DELETE FROM SEC_USER_ROLES WHERE ((USER_ID = ?) AND (ROLE_NAME = ?))
bind => [9451, ROLE_ADMIN]
INSERT INTO SEC_USER_ROLES (USER_ID, ROLE_NAME) VALUES (?, ?)
bind => [9451, ROLE_ADMIN]
I tried to solve the problem with #OrderColumn (name="USER_ID") but then the mapping of the User_id is not correct.
Any idea would be appreciated.
The Roles are represented as selectManyCheckbox
The ManagedBean prepares the entity (User)
...
List<String> selectedroles = this.getSelectedItems();
List<Role> newroles = new ArrayList<Role>();
if (selectedroles != null) {
for (String r : selectedroles) {
newroles.add(Role.valueOf(r));
}
getEntity().setRoles(newroles);
...
security.save(getEntity());
and the EJB makes updates if it is an existing Entity
EntityManager em;
...
this.em.merge(user);
So when someone deselects all (previous selected) Roles there is always one Role left in the database which is not deleted, because of the delete/insert behavior I described before.
#OrderColumn solved the problem
Related
I am working on a Spring JPA, microservices-based project where I have two entities, UserEntity and RegionEntity, associated via a many-to-many relationship.
Below the (simplified) code for the two entities:
#EqualsAndHashCode(of = {"id"})
#Data
#Entity
#Table(name = "users")
#Audited
public class UserEntity implements Serializable {
#Id
#Column(name = "userId", columnDefinition = "int", unique = true, nullable = false)
private Long id;
#NotAudited
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "users_regions",
joinColumns = #JoinColumn(name = "userId", columnDefinition = "int", nullable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "regionId", columnDefinition = "varchar(50)", nullable = false, updatable = false))
private Set<RegionEntity> regions = new HashSet<>();
}
#EqualsAndHashCode(exclude = {"users"})
#Entity
#Table(name = "regions")
#Data
#Audited
public class RegionEntity {
#Id
#Column(name = "name", columnDefinition = "varchar(50)", unique = true, nullable = false)
private String name;
#ManyToMany(mappedBy = "regions", fetch = FetchType.EAGER)
private Set<UserEntity> users = new HashSet<>();
}
Now, I have a route for persisting instances of a third entity, CallEntity, that makes use of both the previous entities. It may be useful to know that CallEntity has also a many-to-many association with RegionEntity, and a many-to-one with user.
Here's in short the method invoked by the route.
public CallDTO myMethod(String userName, /* ... more stuff */) {
UserProjection userProj = userConsumer.findByUserName(userName, UserBasicProjection.class); // from another repository, retrieve a user projection
UserEntity user = userRepository.findById(user.getId()).orElse(null); // use that projection to initialise the user
Set<RegionEntity> userRegions = user != null ? user.getRegions() : null;
CallEntity newCall = new CallEntity();
/ * some actions, mainly setting values for newCall using userRegions and user... */
CallEntity call = callRepository.save(newCall);
return callMapper.toDTO(call);
}
The problem is that Hibernate automatically deletes the lines related to the user-region pair in the associated table when saving the CallEntity object. So for example, if the user has an id of 1234 and has regions A, B and C associated,
user | region
-------------
1234 | A
-------------
1234 | B
-------------
1234 | C
-------------
then all three lines will be deleted in the table after this line of code:
CallEntity call = callRepository.save(newCall);
is executed.
This is the Hibernate version currently in use:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>5.3.12.Final</version>
</dependency>
This is the console printout after execution:
Hibernate:
/* get current state package-name.UserEntity */ select
userent_.userId
from
user userent_
where
userent_.userId=?
Hibernate:
/* get current state package-name.RegionEntity */ select
regione_.name
from
region regione_
where
regione_.name=?
Hibernate:
/* insert package-name.CallEntity
*/ insert
into
call
(id, userid, regionid)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate:
select
last_insert_id()
Hibernate:
/* delete collection package-name.UserEntity.regions */ delete
from
users_regions
where
userId=?
Hibernate:
/* insert collection
row package-name.CallEntity.userRegions */ insert
into
call_region
(id, name)
values
(?, ?)
I found that if I change the direction of the relationship (the owning entity being, in this case, RegionEntity) the problem disappears. However, this is by no means a viable solution, as it would impact other parts of the project.
Also, I found that a similar question was asked before on this site (ManyToMany assoicate delete join table entry), but unfortunately the answer was not satisfactory. I tried adding and using convenience methods to correctly establish the association (as the answer from the linked question suggests), but that just didn't work.
Any help would be immensely appreciated.
Thank you.
Finally, the problem was solved by wrapping the set of regions in a copy.
That is, by replacing this line:
Set userRegions = user != null ? user.getRegions() : null;
with:
Set userRegions = userxpc != null ? Set.copyOf(userxpc.getRegions()) : null;
I have an entity as below. I am curious if it is possible to create a relationship as I will be describing with the example:
I am creating 2 Person entities Michael and Julia.
I am adding Julia to Michael's friends set.
After that I am retrieving Michael as a JSON response and Julia is available in the response. But when I am retrieving Julia, her friends set is empty. I want to create the bidirectional friendship relation by saving just one side of the friendship. I would like to get Michael on Julia's friends set without doing any other operations. I think that it must be managed by Hibernate. Is it possible and how should I do it?
#ToString(exclude = "friends") // EDIT: these 2 exclusion necessary
#EqualsAndHashCode(exclude = "friends")
public class Person{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name",unique = true)
private String name;
#JsonIgnoreProperties("friends") // EDIT: will prevent the infinite recursion
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "FRIENDSHIP",
joinColumns = #JoinColumn(name = "person_id",
referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "friend_id",
referencedColumnName = "id"))
private Set<Person> friends;
Here is my service layer code for creating a friendship:
#Override
public Person addFriend(String personName, String friendName)
throws FriendshipExistsException, PersonNotFoundException {
Person person = retrieveWithName(personName);
Person friend = retrieveWithName(friendName);
if(!person.getFriends().contains(friend)){
person.getFriends().add(friend);
return repository.save(person);
}
else{
throw new FriendshipExistsException(personName, friendName);
}
}
Related Question:
N+1 query on bidirectional many to many for same entity type
Updated the source code and this version is working properly.
// Creating a graph to help hibernate to create a query with outer join.
#NamedEntityGraph(name="graph.Person.friends",
attributeNodes = #NamedAttributeNode(value = "friends"))
class Person {}
interface PersonRepository extends JpaRepository<Person, Long> {
// using the named graph, it will fetch all friends in same query
#Override
#EntityGraph(value="graph.Person.friends")
Person findOne(Long id);
}
#Override
public Person addFriend(String personName, String friendName)
throws FriendshipExistsException, PersonNotFoundException {
Person person = retrieveWithName(personName);
Person friend = retrieveWithName(friendName);
if(!person.getFriends().contains(friend)){
person.getFriends().add(friend);
friend.getFriends().add(person); // need to setup the relation
return repository.save(person); // only one save method is used, it saves friends with cascade
} else {
throw new FriendshipExistsException(personName, friendName);
}
}
If you check your hibernate logs, you will see:
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)
Hibernate: insert into friendship (person_id, friend_id) values (?, ?)
I'm having a big problem for which I can't find an answer.
I have a OneToOne bidirectional Lazy fetched relation between two entities, but when I query one of them, it eagerly fetch the other, even though I'm explicitly saying that it's Lazy.
According to what I've found, almost all from 2010 and back, says that that is the way hibernate behaves on OneToOne relations. This is my first time using Hibernate ever but I highly doubt that is true, and if it was that it still is true. But i can't find any recent information about it. Post1, Post2, Post3 as you can see, they are not really recent. So I wanted to know if that's still the case or maybe guide me to where I can find the answer, examples, anything would help really.
Here's what I'm doing, just in case I'm doing something wrong.
Note the classes has fewer attributes than the original, but all of them are columns of the entity, no FK, PK, or something like that.
User Entity
#Entity
#Table(name = "\"User\"")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", unique = true, nullable = false)
private long id;
#Column(name = "Username", nullable = false, unique = true, length = 100)
private String username;
#Valid
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
#JsonBackReference
private UserProfile userProfile;
//Getters and Setters
}
UserProfile Entity
#Entity
#Table(name = "UserProfile")
public class UserProfile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotBlank
#Size(max = 100)
#Column(name = "FirstName", nullable = false, unique = false, length = 100)
private String firstName;
#NotBlank
#Size(max = 100)
#Column(name = "LastName", nullable = false, unique = false, length = 100)
private String lastName;
#Valid
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "UserId")
#JsonManagedReference
private User user;
//Getters and Setters
}
Test controller method
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity<Void> test(){
User user = userService.findUserByUsername("admin");
if(user.getUserProfile() == null){
return new ResponseEntity<Void>(HttpStatus.OK);
}
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
And the UserService simply calls the UserDao method, which is
#Override
public User findByUsername(String username) {
Criteria criteria = createEntityCriteria();
criteria.add(Restrictions.eq("username", username));
return (User) criteria.uniqueResult();
}
The result of that service is always Conflict. Even though I'm calling the UserService (Which is Transactional) and the direct relation is in UserProfile. The controller is not transactional.
The log:
2016-02-29 18:50:58 DEBUG SQL::logStatement:92 -
select
this_.Id as Id1_2_0_,
this_.Username as Username5_2_0_
from
Public.[User] this_
where
this_.Username=?
Hibernate:
select
this_.Id as Id1_2_0_,
this_.Username as Username5_2_0_
from
Public.[User] this_
where
this_.Username=?
2016-02-29 18:50:58 DEBUG Loader::debugf:384 - Result set row: 0
2016-02-29 18:50:58 DEBUG Loader::getRow:1514 - Result row: EntityKey[com.portal.model.user.User#1]
2016-02-29 18:50:58 DEBUG TwoPhaseLoad::doInitializeEntity:144 - Resolving associations for [com.portal.model.user.User#1]
2016-02-29 18:50:58 DEBUG Loader::loadEntity:2186 - Loading entity: [com.portal.model.user.UserProfile#1]
2016-02-29 18:50:58 DEBUG SQL::logStatement:92 -
select
userprofil0_.id as id1_18_0_,
userprofil0_.FirstName as FirstNam5_18_0_,
userprofil0_.LastName as LastName7_18_0_,
userprofil0_.UserId as UserId10_18_0_
from
Public.UserProfile userprofil0_
where
userprofil0_.UserId=?
Hibernate:
select
userprofil0_.id as id1_18_0_,
userprofil0_.FirstName as FirstNam5_18_0_,
userprofil0_.LastName as LastName7_18_0_,
userprofil0_.UserId as UserId10_18_0_
from
Public.UserProfile userprofil0_
where
userprofil0_.UserId=?
Is there something wrong? Am I doing something wrong?
Thanks for any help you can provide! Let me know if you need more info!
I am building a sample for ManyToMany relationship between: User(1) - ()AccessLevel() - (1)Role
I have implemented 3 classes in Java with hibernate implementation as follow:
Class User
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
#Column(name="USER_ID")
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "access_level", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
Class Role
#Entity
#Table(name="role")
public class Role {
#Id
#GeneratedValue
#Column(name="role_id")
private Integer id;
#Column(name="role_name")
private String roleName;
Class AccessLevel
#Entity
#Table(name="access_level")
public class AccessLevel {
#Id
#GeneratedValue
private Integer id;
#Column(name="role_id")
private Integer roleId;
#Column(name="user_id")
private Integer userId;
Problem:
When I am persisting the User bean using merge method then an exception arise:
#Service
public class UserServiceImpl implements UserService {
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public void save(User user) {
em.merge(user);
}
Exception
org.springframework.web.util.NestedServletException: Request process
ing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into access_level (user_id, role_id) values (?, ?)]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
As you can see hibernate is trying to run this query:
insert into access_level (user_id, role_id) values (?, ?)
From my point of view it seems like hibernate is not generating the primary key for AccessLevel even though I have added the #GeneratedValue to the id attribute.
Note:
I am working on production environment with Postgresql and evelopment environment with HSQL database that creates all schemas from the begining based on the entities description. Both environments generate same issue.
Regards,
Cristian Colorado
Reason:
It seems for ManyToMany relationships you do not need to define a class for the "Joining Table". Therefore if I eliminate AccessLevel entity the logic would work perfectly fine. I explain further:
Explanation:
When I defined the User class I also described the relationship with Role:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "access_level", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
Important thing here is I have told hibernate that User entity will relate to Role entity through a table known as "access_level" and such table will have user_id and role_id columns in order to join previous entities.
So far this is all hibernate needs in order to work the many to many relationship, therefore when mergin it uses that information to create and tun this script:
insert into access_level (user_id, role_id) values (?, ?)
Now, the problem cames when I defined a new entity for AccessLevel:
#Entity
#Table(name="access_level")
public class AccessLevel {
#Id
#GeneratedValue
private Integer id;
#Column(name="role_id")
private Integer roleId;
#Column(name="user_id")
private Integer userId;
Now I am telling hibernate that there is a table "access_level" related to AccessLevel entity and it has 3 columns, the most important would be Id which is primary key.
So I defined "access_level" twice!
Solution:
I eliminated the Entity for access_level table.
I re-write my production script in order to have "access_level" with
user_id/role_id columns only.
Note: It would be good to know how to add a primary key to the joining table without generating issues. An alternative would be adding a composed primary key in database(user_id/role_id) which would be independient from hibernate.
Why do you need a PK column in the join table? There will be a composite PK composed of user_id and role_id. Now, as you have discovered a JoinTable for #ManyToMany will only ever have two columns and at some point you may require additional data about this relationship.
e.g.
user_id
role_id
date_granted
You may then want to use your AccessLevel entity however you replace the #ManyToMany with #OneToMany from User to AccessLevel and optionally from Role > AccessLevel.
The Hibernate docs themselves advise against #ManyToMany:
Do not use exotic association mappings:
Practical test cases for real many-to-many associations are rare. Most
of the time you need additional information stored in the "link
table". In this case, it is much better to use two one-to-many
associations to an intermediate link class. In fact, most associations
are one-to-many and many-to-one. For this reason, you should proceed
cautiously when using any other association style.
My question is pretty similar to this one Hibernate Bi-Directional ManyToMany Updates with Second Level cache
I've class as shown below
#Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
#Entity
public class A{
private int id;
private List<B> listB;
...
#Cache (usage = CacheConcurrencyStrategy.TRANSACTIONAL)
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity = B.class)
#JoinTable(name = "A_B", joinColumns = { #JoinColumn(name = "a_id") },
inverseJoinColumns = { #JoinColumn(name = "b_id") })
public List<B> getListB() {
return listB ;
}
}
#Cache (usage = CacheConcurrencyStrategy.TRANSACTIONAL)
#Entity
public class B{
private int id;
private List<A> listA;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity = A.class)
#JoinTable(name = "A_B", joinColumns = { #JoinColumn(name = "b_id") }, inverseJoinColumns = { #JoinColumn(name = "a_id") })
public List<a> getListA() {
return listA ;
}
public void addToA(A a) {
a.getListB().add(this);
}
}
As expected in a Many to Many relation, I've been updating both sides of the bi-directional relation .
Now the problem I face is, duplicate entries popup when I try to add/update a item in the collection. The following is the code I use to persist the entity...
b.getA().clear() ;
...
...
b.getListA().add(A) ;
b.addToA(A) ;
em.saveOrUpdate(b) ;
The following are the queries fired by Hibernate which are obtained from the logs.
delete from A_B where b_id=?
insert into A_B (b_id, a_id) values (?, ?)
insert into A_B (b_id, a_id) values (?, ?)
delete from A_B where a_id=?
insert into A_B (a_id, b_id) values (?, ?)
insert into A_B (a_id, b_id) values (?, ?)
insert into A_B (a_id, b_id) values (?, ?)
insert into A_B (a_id, b_id) values (?, ?)
Where am I going wrong here ?
How to get rid of the duplicates that are being inserted ?
Cache is being flushed properly but the duplicate entries are the only problem !
This is a classic!
The problem is that both of your mappings are owners, when one should be the owner, and one should be inverse. Because both are owners, changes to either will result in insertions to the database; with one owner and one inverse, there will only be one set of insertions.
You should be able to rewrite B::getListA as:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "listB")
public List<A> getListA()
And have everything work.
Note that only the owning side as the #JoinTable annotation. As a rule, any given bit of the database is mapped in exactly one place in a JPA application. If you ever find yourself mapping something twice, take a long hard look to see if there's a better way to do it.
Incidentally, you don't need the targetEntity attributes; a JPA provider can work that out from the generics.