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.
Related
I am working on a Spring Boot portal using Spring Data JPA and Hibernate mapping and I am finding some difficulties trying to understand how exactly works the following code implemented by someone else (it works fine but JPA\Hibernate are not my cup of tea and I am missing something).
On the database I have this portal_user table representing the users of my application
As you can see this table contains the parent_id field that is a FK of the portal_user table itself. This is used to create a recursive relation between an user and its parent (you can see it is a classical refferal relationship: the parent is the user who bring another user into the system).
This portal_user DB table was mapped on this User entity class:
#Entity
#Table(name = "portal_user")
#Getter
#Setter
public class User implements Serializable {
private static final long serialVersionUID = 5062673109048808267L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
#NotNull(message = "{NotNull.User.firstName.Validation}")
private String firstName;
#Column(name = "middle_name")
private String middleName;
#Column(name = "surname")
#NotNull(message = "{NotNull.User.surname.Validation}")
private String surname;
#Column(name = "sex")
#NotNull(message = "{NotNull.User.sex.Validation}")
private char sex;
#Column(name = "birthdate")
#NotNull(message = "{NotNull.User.birthdate.Validation}")
private Date birthdate;
#Column(name = "tax_code")
#NotNull(message = "{NotNull.User.taxCode.Validation}")
private String taxCode;
#Column(name = "e_mail")
#NotNull(message = "{NotNull.User.email.Validation}")
private String email;
#Column(name = "pswd")
#NotNull(message = "{NotNull.User.pswd.Validation}")
private String pswd;
#Column(name = "contact_number")
#NotNull(message = "{NotNull.User.contactNumber.Validation}")
private String contactNumber;
#Temporal(TemporalType.DATE)
#Column(name = "created_at")
private Date createdAt;
#Column(name = "is_active")
private boolean is_active;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
#JsonManagedReference
private Set<Address> addressesList = new HashSet<>();
#ManyToMany(cascade = { CascadeType.MERGE })
#JoinTable(
name = "portal_user_user_type",
joinColumns = { #JoinColumn(name = "portal_user_id_fk") },
inverseJoinColumns = { #JoinColumn(name = "user_type_id_fk") }
)
Set<UserType> userTypes;
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
super();
this.firstName = firstName;
this.middleName = middleName;
this.surname = surname;
this.sex = sex;
this.birthdate = birthdate;
this.taxCode = taxCode;
this.email = email;
this.pswd = pswd;
this.contactNumber = contactNumber;
this.createdAt = createdAt;
this.is_active = is_active;
}
}
In particular this relationship seems to be handled by this field:
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
Then I have this service method used to insert a new object into the portal_user table taking into account the fact that the parent_id field could be set:
#Override
#Transactional
public User insertClientUser(User clientUser) throws DuplicateException, SubAgentUserNotExist, NotFoundException {
String subAgentEmail = null;
if(clientUser.getParent() != null) subAgentEmail = clientUser.getParent().getEmail();
User checkClientUserExist = this.getUserByemail(clientUser.getEmail());
if (checkClientUserExist != null) {
String MsgErr = String.format("User %s already registered in the system !!! "
+ "Impossible to use POST", clientUser.getEmail());
log.warning(MsgErr);
throw new DuplicateException(MsgErr);
}
log.info(String.format("UserServiceImpl --> insertClientUser client user: %s %s - subagent email: %s ",
clientUser.getFirstName(), clientUser.getSurname(), subAgentEmail));
// check if present because we could add a user without assigned subagent
if(subAgentEmail != null) {
User subAgentUser = this.getUserByemail(subAgentEmail);
if (subAgentUser == null) {
String errorMessage = String.format("Subagent user %s doesn't exist in the sistem !!! "
+ "Impossible to use POST", subAgentEmail);
log.warning(errorMessage);
throw new SubAgentUserNotExist(errorMessage);
}
clientUser.setParent(subAgentUser);
}
User insertedClientUser = userRepository.save(clientUser);
return insertedClientUser;
}
NOTE: the client is the user that I am inserting as a new record of the portal_user DB table while the subAgentUser is an user that yet exist into this DB table and that will be the parent of the user that I am inserting.
So basically, on my portal_user table I will have a new record (the client user) having the parent_id that will contain the ID of the subagent user.
How this method works is pretty simple:
Check if the current user that I have to insert doesn't exist into the portal_user table. If the user doesn't yet exist into the table it means that it have to be inserted.
It retrieve the subagent user (the parent) calling another service method and set it as parent property of the client user that we are inserting.
Finnally it save this client user into the portal_user DB table.
Ok it all pefrectly works but I can not understand how Hibernate is correctly setting the value of the parent_id field of this new inserted method. This parent_id field contains the PK of the parent object (the retrieved subAgentUser object.
If I explore my portal_user table after the execution of the previous service method I found what I expect:
The last row is the inserted object. As you can see the value of the parent_id FK field is 53 that is the PK of the expected parent user in the same table.
It works fine but I cannot understand how. Who said to Hibernate how to set the value of this FK? In particular I am confuserd because it seems that there is not a explicit mapping to this field into my User entity class, infact I have:
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("subagent")
private User parent;
So what am I missing? How it exactly work?
In Hibernate, #ManyToOne specifies a single-valued association to another entity class that has many-to-one multiplicity. In this context, it is associated with the same entity.
ORM maps the data from an object model to a relational model and vice versa. So the relationships are mapped using the entities. This helps us to traverse from parent to child objects easily. We can build multiple nested relationships and can be queried or create the objects by traversing using dot and chaining method.
As we know that each entity has its own lifecycle and once the association is set, Hibernate maps the foreign key with the primary identifier of the associated entity.
Here it assigns to the id column which is a primary key. However, it can be customized using #JoinColumn annotation.
Below is the sql that is generated by Hibernate after creating portal_user table to set the association.
alter table portal_user
add constraint FKdgqt4pnsjho6u58mtnackj4h8
foreign key (parent_id)
references portal_user
When subAgentUser entity is set to clientUser as below, hibernate maps the record to the primary identifier of the associated entity(subAgentUser) using parent_id column while flushing the query.
clientUser.setParent(subAgentUser);
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 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.
I know that there is many question about it but i can not find a good answered for my problem .
I am using Jboss as 7, Spring and Hibernate (4) as JPA 2.0 provider so i have got simple #OneToMany bi-directional relationship :
I have got super class person like that:
#MappedSuperclass
#Inheritance(strategy=InheritanceType.JOINED)
public abstract class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 1, max = 25)
#Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
private String name;
public Person(String name) {
super();
this.name = name;
}
And class Member:
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class Member extends Person implements Serializable
{
/** Default value included to remove warning. Remove or modify at will. **/
private static final long serialVersionUID = 1L;
#NotNull
#NotEmpty
#Email
private String email;
#NotNull
#Size(min = 10, max = 12)
#Digits(fraction = 0, integer = 12)
#Column(name = "phone_number")
private String phoneNumber;
#OneToMany(cascade=CascadeType.ALL , mappedBy="member" , fetch=FetchType.EAGER)
private List<Order> orders;
And also class Order:
#Entity
public class Order {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private float price;
#ManyToOne(optional=false)
private Member member;
private String name;
So i think that it is a good configuration, but i test this application in HSQL in memory and i have got error :
Hibernate: alter table Order drop constraint FK48E972E548C740B
2012-09-20 16:25:37 org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table Order drop constraint FK48E972E548C740B
2012-09-20 16:25:37 org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: Blad skladniowy w wyrazeniu SQL "ALTER TABLE ORDER[*] DROP CONSTRAINT FK48E972E548C740B "; oczekiwano "identifier"
Syntax error in SQL statement "ALTER TABLE ORDER[*] DROP CONSTRAINT FK48E972E548C740B "; expected "identifier"; SQL statement:
alter table Order drop constraint FK48E972E548C740B [42001-165]
And also :
Syntax error in SQL statement "CREATE TABLE ORDER[*] (ID INTEGER GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR(255), PRICE FLOAT NOT NULL, MEMBER_ID BIGINT NOT NULL, PRIMARY KEY (ID)) "; expected "identifier"; SQL statement:
And my JUnit test failed i dont know what is wrong with this configuration ...
this is my simply junit :
#Test
public void testInsertWithOrder(){
Order order = new Order(20.0f, "first stuff");
Order order2 = new Order(40.0f, "secondary stuff");
List<Order> orders = new ArrayList<Order>();
orders.add(order2);
orders.add(order);
Member member = new Member("Member name", "member23#gmail.com", "2125552141", orders);
memberDao.register(member);
List<Member> members = memberDao.findAllOrderedByName();
Assert.assertNotNull(members);
Assert.assertEquals(1, members.size());
}
Change table name from 'order' to something different, like PersonOrder
In your member in Order Class, there are missing #JoinColumn annotation. Try as below.
#ManyToOne(optional=false)
#JoinColumn(name = "memberId", referencedColumnName = "id")
private Member member;
#CycDemo
I am just figure it out and in my constuctor i now have got :
#OneToMany(cascade=CascadeType.ALL , mappedBy="member" , fetch=FetchType.EAGER)
private List<UOrder> orders = new ArrayList<UOrder>();
public Member(String name, String email, String phoneNumber ,List<UOrder> orders) {
super(name);
this.orders = orders;
this.email = email;
for(UOrder o : orders){
o.setMember(this);
}
this.orders = orders;
}
Ant this is it what i need :)))
I want to persist a mail entity which has some resources (inline or attachment). First I related them as a bidirectional relation:
#Entity
public class Mail extends BaseEntity {
#OneToMany(mappedBy = "mail", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MailResource> resource;
private String receiver;
private String subject;
private String body;
#Temporal(TemporalType.TIMESTAMP)
private Date queued;
#Temporal(TemporalType.TIMESTAMP)
private Date sent;
public Mail(String receiver, String subject, String body) {
this.receiver = receiver;
this.subject = subject;
this.body = body;
this.queued = new Date();
this.resource = new ArrayList<>();
}
public void addResource(String name, MailResourceType type, byte[] content) {
resource.add(new MailResource(this, name, type, content));
}
}
#Entity
public class MailResource extends BaseEntity {
#ManyToOne(optional = false)
private Mail mail;
private String name;
private MailResourceType type;
private byte[] content;
}
And when I saved them:
Mail mail = new Mail("asdasd#asd.com", "Hi!", "...");
mail.addResource("image", MailResourceType.INLINE, someBytes);
mail.addResource("documentation.pdf", MailResourceType.ATTACHMENT, someOtherBytes);
mailRepository.save(mail);
Three inserts were executed:
INSERT INTO MAIL (ID, BODY, QUEUED, RECEIVER, SENT, SUBJECT) VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE, MAIL_ID) VALUES (?, ?, ?, ?, ?)
INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE, MAIL_ID) VALUES (?, ?, ?, ?, ?)
Then I thought it would be better using only a OneToMany relation. No need to save which Mail is in every MailResource:
#Entity
public class Mail extends BaseEntity {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "mail_id")
private List<MailResource> resource;
...
public void addResource(String name, MailResourceType type, byte[] content) {
resource.add(new MailResource(name, type, content));
}
}
#Entity
public class MailResource extends BaseEntity {
private String name;
private MailResourceType type;
private byte[] content;
}
Generated tables are exactly the same (MailResource has a FK to Mail). The problem is the executed SQL:
INSERT INTO MAIL (ID, BODY, QUEUED, RECEIVER, SENT, SUBJECT) VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE) VALUES (?, ?, ?, ?)
INSERT INTO MAILRESOURCE (ID, CONTENT, NAME, TYPE) VALUES (?, ?, ?, ?)
UPDATE MAILRESOURCE SET mail_id = ? WHERE (ID = ?)
UPDATE MAILRESOURCE SET mail_id = ? WHERE (ID = ?)
Why this two updates? I'm using EclipseLink, will this behaviour be the same using another JPA provider as Hibernate? Which solution is better?
UPDATE:
- If I don't use #JoinColumn EclipseLink creates three tables: MAIL, MAILRESOURCE and MAIL_MAILRESOURCE. I think this is perfectly logic. But with #JoinColumn it has information enough for creating only two tables and, in my opinion, do only inserts, with no updates.
When you use a #JoinColumn in a OneToMany you are defining a "unidirectional" one to many, which is a new type of mapping added in JPA 2.0, this was not supported in JPA 1.0.
This is normally not the best way to define a OneToMany, a normal OneToMany is defined using a mappedBy and having a ManyToOne in the target object. Otherwise the target object has no knowledge of this foreign key, and thus the separate update for it.
You can also use a JoinTable instead of the JoinColumn (this is the default for OneToMany), and then there is no foreign key in the target to worry about.
There is also a fourth option. You could mark the MailResource as an Embeddable instead of Entity and use an ElementCollection.
See,
http://en.wikibooks.org/wiki/Java_Persistence/OneToMany
Mapped by defines owning side of the relation ship so for JPA it gives better way to handle associations. Join Column only defines the relationship column. Since JPA is completely reflection based framework I could think of the optimization done for Mapped by since it is easy find owning side this way.