Modelling my SpringBoot application, using Hibernate and PostgreSQL as database.
Need help to properly establish relation ManyToMany.
Class News:
#Entity(name = "news")
public class News implements Serializable {
private Long id;
private Date date;
private String text;
private String author;
private Set<HashTag> hashTags = new HashSet<HashTag>(0);
private byte[] image;
public News() {
}
public News(Date date, String text, String author, Set<HashTag> hashTags, byte[] image) {
this.date = date;
this.text = text;
this.author = author;
this.hashTags = hashTags;
this.image = image;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "news_id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "DD/MM/YYYY")
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
#Lob
#Type(type = "org.hibernate.type.TextType")
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "news_hashTag",
joinColumns = #JoinColumn(name = "news_id"),
inverseJoinColumns = #JoinColumn(name = "hashtag_id"))
public Set<HashTag> getHashTags() {
return hashTags;
}
public void setHashTags(Set<HashTag> hashTags) {
this.hashTags = hashTags;
}
#Lob
public byte[] getImage() {
return image;
}
public void setImage(byte[] image) {
this.image = image;
}
And class HashTag:
#Entity(name = "hashTag")
public class HashTag implements Serializable {
private Long id;
private String name;
private String description;
private Set<News> news = new HashSet<News>(0);
public HashTag() {
}
public HashTag(String name, String description) {
this.name = name;
this.description = description;
}
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column(name = "hashtag_id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "hashTags")
public Set<News> getNews() {
return news;
}
public void setNews(Set<News> news) {
this.news = news;
}
When I try save news like this:
Set<HashTag> hashTags = new HashSet<>();
hashTags.add(new HashTag("HashTag 1");
hashTags.add(new HashTag("HashTag 2");
News news = new News();
news.text = "Some text";
news.author = "Some author";
news.date = new Date();
news.hashTags = hashTags;
newsService.save(news);
I'm getting error:
ERROR: null value in column "news_id" violates not-null constraint
Lets see what we have:
On the owning side of the n-m relation:
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "news_hashTag",
joinColumns = #JoinColumn(name = "news_id"),
inverseJoinColumns = #JoinColumn(name = "hashtag_id"))
public Set<HashTag> getHashTags() {
return hashTags;
}
You do not provide the exclusion of null values because documentation specifies:
boolean nullable() default true;
This is wrong for the relationtable as well as the id. You must:
Set the ID to nullable=false.
Set all JoinColumns of the JoinTable to nullable=false.
There is another problem: You make a insert and an update by newsService.save(news);, you specify a Generator for the ID but have no Generator, this is ok in other databases who provide something like a nextval in Oracle. In PostGreSQL you really should use a sequence-field like this:
#Id
#Column(name = "hashtag_id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HashTagIDGenerator")
#SequenceGenerator(name = "HashTagIDGenerator", sequenceName = "SEQ_HASHTAG_ID")
Have a good time.
Related
Im creating an application in spring boot. I want to have multiple entity mapping for one table.
one entity retrieve the table without any join and the other entity retrieve the table with join.
This is the table schema:
book : {"id":1, "title":"math1", "author_id":1}
author: {"id":1, "name": "james"}
thank you.
Ive made it.
#MappedSuperclass
public abstract class BookBase<T extends BookBase> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false, updatable = false, name = "id")
private Integer id;
#Column(name = "title")
private String title;
public Integer getId() {
return this.id;
}
public T setId(Integer id) {
this.id = id;
return (T) this;
}
public String getTitle() {
return this.title;
}
public T setTitle(String title) {
this. title = title
return (T) this;
}
}
#Entity
#Table
public class Book extends BookBase<Book> {
#Column(name = "author_id")
private Integer authorId;
public Integer getAuthorId() {
return authorId;
}
public void setAuthorId(Integer authorId) {
this.authorId = authorId;
}
}
#Entity
#Table
public class BookJoinAuthor extends BookBase<BookJoinAuthor> {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "author_id")
private Author author;
public Author getAuthor() {
return author;
}
public void setAuthorId(Author author) {
this.author = author;
}
}
#Entity
#Table
public abstract class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false, updatable = false, name = "id")
private Integer id;
#Column(name = "name")
private String name;
public Integer getId() {
return this.id;
}
public T setId(Integer id) {
this.id = id;
return (T) this;
}
public String getName() {
return this.name;
}
public T setName(String name) {
this. name = name
return (T) this;
}
}
I have a user and a movie model:
user:
#Entity(name = "User")
#Table(name = "USER")
public class User {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
#SequenceGenerator(name = "user_seq", sequenceName = "user_seq", allocationSize = 1)
private Long id;
#Column(name = "USERNAME", length = 50, unique = true)
#NotNull
#Size(min = 4, max = 50)
private String username;
#Column(name = "PASSWORD", length = 100)
#NotNull
#Size(min = 4, max = 100)
private String password;
#Column(name = "FIRSTNAME", length = 50)
#NotNull
#Size(min = 4, max = 50)
private String firstname;
#Column(name = "LASTNAME", length = 50)
#NotNull
#Size(min = 4, max = 50)
private String lastname;
#Column(name = "EMAIL", length = 50)
#NotNull
#Size(min = 4, max = 50)
private String email;
#Column(name = "ENABLED")
#NotNull
private Boolean enabled;
#Column(name = "LASTPASSWORDRESETDATE")
#Temporal(TemporalType.TIMESTAMP)
#NotNull
private Date lastPasswordResetDate;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_AUTHORITY",
joinColumns = {#JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
inverseJoinColumns = {#JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")})
private List<Authority> authorities;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public List<Authority> getAuthorities() {
return authorities;
}
public void setAuthorities(List<Authority> authorities) {
this.authorities = authorities;
}
public Date getLastPasswordResetDate() {
return lastPasswordResetDate;
}
public void setLastPasswordResetDate(Date lastPasswordResetDate) {
this.lastPasswordResetDate = lastPasswordResetDate;
}
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;
public Set<Movie> getMovies() {
return movies;
}
public void setMovies(Set<Movie> movies) {
this.movies = movies;
}
}
movie:
#Entity(name = "Movie")
#Table(name = "movie")
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
public Movie(){}
public Movie(Integer id, String name ) {
this.id = id;
this.name = name;
}
#ManyToMany(mappedBy = "movies")
private Set<User> users;
public Set<User> getUsers() {
return users;
}
public void addUser(User user){
System.out.println("ADD MOVIE: " + user);
users.add(user);
user.getMovies().add(this);
}
public void setUsers(Set<User> users) {
this.users = users;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString(){
return "id: " + id + "name: " + name;
}
}
I've set up a many to many relation between these models. With, if I am correct, the user as the owner of the relation.
In my MovieController.java I have:
#RequestMapping(value = "/", method = RequestMethod.POST)
public Movie createMovie(#RequestBody Movie movie){
return movieService.createMovie(movie);
}
This calls the MovieService.java:
#Override
public Movie createMovie(Movie movie) {
return movieRepository.save(movie);
}
And this calls the MovieRepository.java:
#Repository
public interface MovieRepository extends CrudRepository<Movie, Serializable> {}
When I call the post methode from my front-end a movie record is saved in my movie table, but no record is created in the user_movie table. Doesn't Hibernate do this implicit since I set up a Many to Many relation between user and movie?
For the first view, your code is correct.
The problem can be in GenerationType.SEQUENCE (try to use GenerationType.AUTO for User's id), or you need to add #Transactional to your controller.
You save the movie and in order to also have the user saved the cascade has to be set in the movie. Otherwise you can keep the cascade in user and save him.
You need to put the cascade to the entity on which you call save to cascade it.
Movie{
#ManyToMany(mappedBy = "movies", cascade={CascadeType.ALL})
private Set<User> users;
public Set<User> getUsers() {
return users;
}
}
User {
#ManyToMany
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;
}
Don't forget to add the user to movie and vice versa before saving.
As with all bi-directional relationships it is your object model's and application's responsibility to maintain the relationship in both direction. There is no magic in JPA, if you add or remove to one side of the collection, you must also add or remove from the other side, see object corruption. Technically the database will be updated correctly if you only add/remove from the owning side of the relationship, but then your object model will be out of synch, which can cause issues.
see here: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany
Here is my entitiy:
#Entity
#Table(name = "remind")
public class Remind {
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
private long id;
#Column(name = "title", nullable = false, length = 50)
private String title;
#Column(name = "remind_date", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date remindDate;
public Remind() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getRemindDate() {
return remindDate;
}
public void setRemindDate(Date remindDate) {
this.remindDate = remindDate;
}
//---Dependency---->
#OneToOne(optional = false)
#JoinColumn(name="id_user", unique = true, nullable = true, updatable = false)
private Users user;
public void setUser(Users user) {
this.user = user;
}
public Users getUser() {
return user;
}
}
And here is another entity:
#Entity
#Table(name = "users")
public class Users {
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
private long id;
#Column(name = "name", nullable = false, length = 50)
private String name;
public Users() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String title) {
this.name = name;
}
//---Dependency---->
#OneToOne(optional = false, mappedBy="user")
public Remind remind;
public Remind getRemind() {
return remind;
}
public void setRemind(Remind remind) {
this.remind = remind;
}
Here is diagram that Idea shows me:
But when I use in RemindController.class:
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseBody
public List<Remind> getReminder() {
List<Remind> list = remindRepository.findAll();
return list;
}
I have this result... infinity loop:
What am I do wrong? It seems like diagram is ok. Help me please(
It's not jpa problem. It's a problem with serializing your entity to json. You need to explicitly mark the relation as bidirectional so the serializer can skip one end.
For Spring I'm assuming you're using jackson.
Take a look at answers to this question.
I have two classes Task and TaskComponents. TaskComponents contains a task mapped by a Task_ID. I am trying to select a task joined to the TaskComponents table. I have tried many different SQL statements but all of them come back with QuerySyntaxException Path Expected to join.
Task POJO
#Entity
#Table(name = "task")
public class Task implements Serializable {
#Id
#Column(name = "ID")
private int Id;
#Column(name = "Name")
private String name;
#Column(name = "Description")
private String description;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "Site_ID")
private Site site;
public int getId() {
return Id;
}
public void setId(int Id) {
this.Id = Id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Site getSite() {
return site;
}
public void setSite(Site site) {
this.site = site;
}
}
TaskComponents POJO
#Entity
#Table(name = "taskcomponents")
public class TaskComponents implements Serializable {
#Id
#Column(name = "ID")
private int Id;
#Column(name = "VersionName")
private String versionName;
#Column(name = "Live", nullable = false)
#Type(type = "org.hibernate.type.NumericBooleanType")
private boolean live;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "Task_ID")
private Task task;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "Group_ID")
private GroupDB group;
public int getId() {
return Id;
}
public void setId(int Id) {
this.Id = Id;
}
public String getVersionName() {
return versionName;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
public Task getTask() {
return task;
}
public void setTask(Task task) {
this.task = task;
}
public GroupDB getGroup() {
return group;
}
public void setGroup(GroupDB group) {
this.group = group;
}
}
And my attempted queries.
Query query = session.createQuery("SELECT t FROM Task t INNER JOIN TaskComponents tc ON t.Id=tc.task.Id");
Query query = session.createQuery("SELECT t FROM Task t INNER JOIN TaskComponents tc ON t=tc.task");
You shouldn't use explicitely ON to define the join, Hibernate will infer it from the mapping, just write you query as
SELECT tc.task FROM TaskComponents tc INNER JOIN tc.task
this is what is referred to behind your error message Path expected for join the query expects a property defined path from one entity to another
I have two beans user and virtualdomain
#Entity
#Table(name = "tblUser")
public class User implements Serializable {
private Long id;
private String username;
private String deleteflag;
private Set<VirtualDomain> virtualdomainset;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "username", length = 50, nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "deleteflag")
public String getDeleteflag() {
return deleteflag;
}
public void setDeleteflag(String deleteflag) {
this.deleteflag = deleteflag;
}
#ManyToMany(targetEntity = VirtualDomain.class, cascade = {CascadeType.PERSIST},fetch=FetchType.EAGER)
#JoinTable(name = "tblUserDomainRel", joinColumns = #JoinColumn(name = "userid"), inverseJoinColumns = #JoinColumn(name = "domainid"))
public Set<VirtualDomain> getVirtualdomainset() {
return virtualdomainset;
}
public void setVirtualdomainset(Set<VirtualDomain> virtualdomainset) {
this.virtualdomainset = virtualdomainset;
}
}
#Entity
#Table(name = "tblVirtualDomain")
public class VirtualDomain {
private Long id;
private String domainname;
private String deleteflag;
private Set<User> userset;
#Id
#JoinColumn(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "domain_name")
public String getDomainname() {
return domainname;
}
public void setDomainname(String domainname) {
this.domainname = domainname;
}
#Column(name = "deleteflag")
public String getDeleteflag() {
return deleteflag;
}
public void setDeleteflag(String deleteflag) {
this.deleteflag = deleteflag;
}
#ManyToMany(cascade = {CascadeType.ALL},fetch=FetchType.EAGER, mappedBy = "virtualdomainset", targetEntity = User.class)
public Set<User> getUserset() {
return userset;
}
public void setUserset(Set<User> userset) {
this.userset = userset;
}
}
Now when I delete some user i use to set the deleteflag which means that the data remains in the database.
My requirement is that the user whose delete flag is set must be removed from the tblUserDomainRel table so how to write that delete query.
just remove the virtualDomain from the collection on the User and remove the other side for completeness
// on User
public void removeVirtualDomain(VirtualDomain vd){
virtualDomainset.remove(vd);
vd.getUserset().remove(this)
}
this will remove the relationship record. Or to remove a user from all virtual Domains:
// on User
public void removeFromAllVirtualDomains(){
for( VirtualDomain vd : virtualdomainset ){
vd.getUserset().remove(this);
}
virtualDomainset.clear();
}