ManyToMany RelationShip NULL not allowed for column - java

I am trying to make a #ManyToMany relationship between two entities.
Post Entity:
#Entity
#Table(name="Post")
public class Post {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_label", joinColumns = #JoinColumn(name = "POST_ID", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "LABEL_ID", referencedColumnName = "id"))
List<Label> labels = new ArrayList<Label>() ;
// getters and setters
}
And Label Entity:
#Entity
#Table(name="Label")
public class Label{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
long id;
String name;
String color;
#ManyToMany(mappedBy = "labels")
List<Post> posts = new ArrayList<Post>() ;
// getters and setters.
}
When I try to save a Post, appears this error:
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "LABEL_ID"; SQL statement:
insert into noticia (id, date, description, facebookid, postedonfacebook, postedonfake, publishdate, subtitle, title, type, visitorscount) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23502-194]
And similar error when I try to save a Label:
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "NOTICIA_ID"; SQL statement:
insert into label (id, color, name) values (null, ?, ?) [23502-194]
Point 1: I tried to save a Post without adding no Label (same error).
I tried to save a Label without adding no Post (same error)
Point 2: I tried to save a Post adding Label (same error).
I tried to save a Label adding Post (same error)

Your mapping looks fine.
The problem could be in the way you are saving your entities.
Try the following code to persist your Post and its Label child entities.
Post post = new Post();
List<Label> labels = new ArrayList<>();
Label firstLabel = new Label();
firstLabel.setColor("MAROON");
firstLabel.setName("MAROONIFIED");
labels.add(firstLabel);
Label secondLabel = new Label();
secondLabel.setColor("BLUE");
secondLabel.setName("BLUEFIED");
labels.add(secondLabel);
post.setLabels(labels);
postRepository.save(post);
Also check out this answer for h2 specific problem.

Related

Hibernate automatically deleting lines in table without expressly being told so? (#ManyToMany association, Spring JPA)

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;

Many to many relationship for same type entity

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 (?, ?)

JPA / Hibernate "simple" OneToMany Unidirictional / Clear of set does not work

I don't know what I am doing wrong, but apparently I am not able to create a simple OneToMany relationship with hibernate.
Here are my tables how they look in DB:
I will only show relevant part, so the question does not get to bloated.
My User Looks like
#Entity(name = "CORE_USER")
public class User extends AbstractPersistentObject {
...
#ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinTable(name = "CORE_USER_TO_ROLE",
joinColumns = { #JoinColumn(name = "USER_ID") },
inverseJoinColumns = { #JoinColumn(name = "ROLE_ID") })
private Set<UserRole> roles = new HashSet<UserRole>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private Set<UserRoleParam> userRoleParams = new HashSet<UserRoleParam>();
...(getter and setters)
}
Here Core user role param entity
#Entity(name = "CORE_USER_ROLE_PARAM")
public class UserRoleParam extends AbstractPersistentObject {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ROLE_ID")
private UserRole userRole;
#Column(name = "ROLE_PARAMETER")
private String paramter;
...(getter and setter)....
}
UserRole entity
#Entity(name="CORE_USER_ROLE")
public class UserRole extends AbstractPersistentObject {
#Enumerated(EnumType.STRING)
#Column(name = "ROLE_NAME", length = 30, nullable=false, unique=true)
private UserRoleEnum roleName;
...(getter and setters)
}
When I do this in my test:
#Test
#Transactional(propagation = Propagation.NEVER)
public void saveUserRoleParametersTest() throws Exception {
// load an user which exists and check params is zero
UserDTO userDTO = userService.getUserDTO(Users.DE678_ACTIVE_ALLROLES.getObject().getId());
Assert.assertNotNull(userDTO);
Assert.assertNotNull(userDTO.getUserRoleParams());
Assert.assertEquals(0, userDTO.getUserRoleParams().size());
Map<UserRoleEnum, List<String>> userRoleParams = new HashMap<>();
userRoleParams.put(UserRoleEnum.BASIC, new ArrayList<>());
userRoleParams.get(UserRoleEnum.BASIC).add("BASIC_PARAM");
// save params to user
userService.saveUserRoleParameters(Users.DE678_ACTIVE_ALLROLES.getObject().getId(), userRoleParams);
userDTO = userService.getUserDTO(Users.DE678_ACTIVE_ALLROLES.getObject().getId());
Assert.assertNotNull(userDTO);
Assert.assertNotNull(userDTO.getUserRoleParams());
Assert.assertEquals(1, userDTO.getUserRoleParams().size());
Assert.assertEquals(1, userDTO.getUserRoleParams().get(UserRoleEnum.BASIC).size());
Assert.assertTrue(userDTO.getUserRoleParams().get(UserRoleEnum.BASIC).contains("BASIC_PARAM"));
// delete params of user
userService.saveUserRoleParameters(Users.DE678_ACTIVE_ALLROLES.getObject().getId(), null);
userDTO = userService.getUserDTO(Users.DE678_ACTIVE_ALLROLES.getObject().getId());
Assert.assertNotNull(userDTO);
Assert.assertNotNull(userDTO.getUserRoleParams());
Assert.assertEquals(0, userDTO.getUserRoleParams().size());
}
here is also the user service method which I invoke:
#Override
public void saveUserRoleParameters(final String userId, final Map<UserRoleEnum, List<String>> userRoleParams) throws UserNotFoundException {
User user = userDAO.get(userId);
if (user == null) {
throw new UserNotFoundException(userId);
}
if (userRoleParams == null || userRoleParams.isEmpty()) {
user.getUserRoleParams().clear();
} else {
List<UserRole> roles = userDAO.getUserRolesByEnums(userRoleParams.keySet());
Map<UserRoleEnum, UserRole> enumToEntity = new HashMap<>();
roles.stream().forEach(r -> enumToEntity.put(r.getRoleName(), r));
for (Entry<UserRoleEnum, List<String>> entry : userRoleParams.entrySet()) {
UserRoleParam urp = new UserRoleParam(enumToEntity.get(entry.getKey()), entry.getValue().stream().collect(Collectors.joining(";")));
user.getUserRoleParams().add(urp);
}
}
userDAO.saveOrUpdate(user);
}
The problem is that my test fails on first service method call of saveUserRoleParameters which is (EDIT: now with sql log enabled):
DEBUG [main] 12.05.17 08:46:50.264 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: select user0_.id as id1_0_0_, user0_.version as version2_0_0_, user0_.ACTIVE as ACTIVE1_38_0_, user0_.APP_LANG as APP_LANG2_38_0_, user0_.DEFAULT_MODULE as DEFAULT_3_38_0_, user0_.ORGA_UNIT as ORGA_UNI4_38_0_, user0_.USER_FULL_NAME as USER_FUL5_38_0_, user0_.USER_NAME as USER_NAM6_38_0_ from CORE_USER user0_ where user0_.id=?
DEBUG [main] 12.05.17 08:46:50.270 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: select userrole0_.id as id1_0_, userrole0_.version as version2_0_, userrole0_.ROLE_NAME as ROLE_NAM1_41_ from CORE_USER_ROLE userrole0_ where userrole0_.ROLE_NAME in (?)
DEBUG [main] 12.05.17 08:46:50.287 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: select userrolepa0_.USER_ID as USER_ID3_0_0_, userrolepa0_.id as id1_42_0_, userrolepa0_.id as id1_0_1_, userrolepa0_.version as version2_0_1_, userrolepa0_.ROLE_PARAMETER as ROLE_PAR1_42_1_, userrolepa0_.ROLE_ID as ROLE_ID2_42_1_ from CORE_USER_ROLE_ROLE_PARAM userrolepa0_ where userrolepa0_.USER_ID=?
DEBUG [main] 12.05.17 08:46:50.290 org.hibernate.engine.jdbc.spi.SqlStatementLogger#logStatement: insert into CORE_USER_ROLE_PARAM (version, ROLE_PARAMETER, ROLE_ID, id) values (?, ?, ?, ?)
WARN [main] 12.05.17 08:46:50.291 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: SQL Error: 23502, SQLState: 23502
ERROR [main] 12.05.17 08:46:50.291 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: NULL nicht zulässig für Feld "USER_ID"
NULL not allowed for column "USER_ID"; SQL statement:
insert into CORE_USER_ROLE_ROLE_PARAM (version, ROLE_PARAMETER, ROLE_ID, id) values (?, ?, ?, ?) [23502-175]
WARN [main] 12.05.17 08:46:50.291 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: SQL Error: 23502, SQLState: 23502
ERROR [main] 12.05.17 08:46:50.292 org.hibernate.engine.jdbc.spi.SqlExceptionHelper#logExceptions: NULL nicht zulässig für Feld "USER_ID"
NULL not allowed for column "USER_ID"; SQL statement:
Shouldn't jpa put the UserId where it belongs? What I want is a Unidirectional relationship where User knows about UserRoleParams but not the other way around. Like in the example here http://www.objectdb.com/api/java/jpa/annotations/relationship
EDIT#2:
I found a solution. I added following on User Entity:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID", nullable = false)
private Set<UserRoleParam> userRoleParams = new HashSet<UserRoleParam>();
Now I have the issue that clearing the set will not be persisted. My test fails as the second check if the Set is empty fails. It shows that the Parameters are still set.
The fields User.userRoleParams and UserRoleParam.user are part of a bidirectional relation. To do that you must add
mappedBy="user" to the #OneToMany side.
You cannot have those two parts as independent relations (i.e unidirectional 1-N and unidirectional N-1) reusing the same FK column ("USER_ID").
As for your documentation you linked to in the comments, the only use of unidirectional 1-N has no field on the other side (and you do have a field on the other side of the relation).
User has changed their question since this answer! Why do I bother?

DataIntegrityViolationException persisting one to many relation

Boot,Jpa and hibernate to persist a one-many relation between venues and events.
The error i'm retrieving is
org.h2.jdbc.JdbcSQLException: NULL not allowed for column "VENUE_LOCATION"; SQL statement:
insert into event (id, date, description, media_ref, num_ratings, performer, performer_media, title, total_rating) values (null, ?, ?, ?, ?, ?, ?, ?, ?) [23502-192]
I've tried saving the parent(Venue) class first and exclusively but that produces the same error.
Venue
public class Venue
{
#Id
private String name;
#Id
private String location;
#OneToOne(mappedBy = "venue",cascade = CascadeType.ALL)
#JoinColumn(name="id")
private VenueUser venueUser;
private String mediaRef;
private int rating;
#OneToMany(fetch=FetchType.LAZY,mappedBy = "venue",cascade = CascadeType.ALL)
private Set<Event> events;
//Constructors getters and setters below
Event
#Entity
public class Event
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String title;
private String description;
private String performer;
private String[] performerMedia;
private Calendar[] date;
#Transient
private double avgRating;
private int numRatings;
private int totalRating;
private String mediaRef;
#MapsId("name")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="Venue_name",referencedColumnName = "name"),
#JoinColumn(name="venue_location",referencedColumnName = "location")
})
private Venue venue;
//Constructors getters and setters below
Controller
#RequestMapping(value = "/event",method=RequestMethod.POST)
public ResponseEntity addEvent(#RequestBody Event event)
{
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); //get logged in username
Venue venue = userVenueRepository.findByEmail(name).getVenue();
event.setVenue(venue);
venue.addEvent(event);
if(eventRepository.saveAndFlush(event).equals(event)&&venueRepository.saveAndFlush(venue).equals(venue))
{
return new ResponseEntity(HttpStatus.CREATED);
}
else
{
return new ResponseEntity(HttpStatus.CONFLICT);
}
}
insert into event (id, date, description, media_ref, num_ratings, performer, performer_media, title, total_rating) values (null, ?, ?, ?, ?, ?, ?, ?, ?)
You need to set id to your Event entity. Better use #GeneratedValue annotation with AUTO, like here https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java
or use class AbstractPersistable as parent entity.
Error says that location field of Venue entity is null which cannot be as it is primary key.
You have two options for persisting Event object.
First persist Venue [Parent] Entity and then Persist as many Event [Child] entities as you want.Set venue field in event entity. You need to save Parent entity only once.
If you want to persist both parent and child at once, you can specify cascade = CascadeType.PERSIST in Event Entity and then save child entity.
Ok so I managed to fixed this and in hindsight I shouldn't of blindly followed a tutorial, I wasn't to sure what the #MapsId did so I removed it and everything started working.If anyone could explain what #MapsId does and why it was causing problems that would be appreciated.
You can try this too.
this way you don't need to add parent entry inside child object.
Remove mappedBy form Venue entity
Then add below code inside the Venue entity before Set<Event>
#JoinColumns({
#JoinColumn(name="Venue_name",referencedColumnName = "name"),
#JoinColumn(name="venue_location",referencedColumnName = "location")
})
Remove #JoinColumns and #MapsId from Event entity
Then you don't need to write
event.setVenue(venue);
Hope it helps.

Hibernate Bi-Directional Many to Many association creates duplicates

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.

Categories

Resources