I'm using spring-boot.
I have a class named Datum and a class named User. A user can have many datum. So the relation is one-to-many from user's perspective. Now I want to design those classes.
Here is what I have tried :
public class Datum{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private User user;
...
}
And :
public class User{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Set<Datum> data = new HashSet<>();//i have tried list-arrayList too
...
}
But this gives me org.hibernate.AnnotationException: Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: error.
What are wrong here?
public class Datum{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private User user;
...
}
This is not a OneToMany relationship. This is a ManyToOne, by looking at your User class.
This will do the job:
public class Datum {
...
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private User user;
...
}
As a user can have many datum, so in user class relation will be like
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Datum> datums = new ArrayList<>()
And in Datum class no relation is needed.
The One to Many relationship is a Many to One on the other side. You should map it in this way:
public class Datum{
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
}
And:
public class User{
...
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<Datum> data = new HashSet<>();
...
}
As you can see, the relation is a Many to One on the Datum side. Furthermore it should have a FetchType.LAZY to increase performance and, if can't be a Datum without user, you should also ad "orphanRemoval = true".
Related
I am developing an application for vehicle stock tracking system using spring boot, angular and mysql. Multiple images of the vehicle will be loaded from the interface. Normally, when there is only one image, I define a field of type byte [] with #lob annotation. But how can I keep it in the database when more than one image comes in. I think a relational structure is required but I couldn't.
public class User extends BaseEntity{
#Column(name = "TC_NUM", unique = true)
#NotNull
private String tcNum;
#Column(name = "EMAIL", unique = true)
#NotNull
private String email;
#Column(name = "USERNAME", unique = true)
#NotNull
private String username;
#Column(name = "PASSWORD")
#NotNull
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "User_ROLES",
joinColumns = #JoinColumn(name = "USER_ID"), inverseJoinColumns = #JoinColumn(name = "ROLE_ID"))
#NotNull
private Set<Role> roles;
}
Yes, you need to have One to Many relation.
Add another db table and entity for vehicle images. Let's say we called it VehicleImage:
#Entity
#Table(name="vehicle_image")
public class VehicleImage{
#Id
private Long id;
#Lob
#Column(name = "image", columnDefinition="BLOB")
private byte[] image;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
public VehicleImage() {}
// getters and setters
}
And add mapping to your User class like that:
public class User extends BaseEntity{
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<VehicleImage> vehicleImages;
public void addVehicleImage(VehicleImage vehicleImage) {
vehicleImages.add(vehicleImage);
vehicleImage.setUser(this);
}
public void removeVehicleImage(VehicleImage vehicleImage) {
vehicleImages.remove(vehicleImage);
vehicleImage.setUser(null);
}
//rest of your class
}
As you can see I've also added two utility methods to User class. For details, see this great post by Vlad Mihalcea -> https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
Now you can persist your images like that:
User user = new User();
user.addVehicleImage(
new VehicleImage (imageBytesArray)
);
entityManager.persist(user);
Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.
I have this method:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
movieRepository.save(movie);
userRepository.save(new HashSet<User>(){{
add(new User(current_user, new HashSet<Movie>(){{
add(movie);
}}));
}});
return movieRepository.save(movie);
}
When I run my application and call that function I get this error:
Found shared references to a collection: com.movieseat.model.security.User.movies
In my User model I have:
#ManyToMany
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;
And in my Movie model I have:
#ManyToMany(mappedBy = "movies", cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
What produces the error?
As I understand your code, you're trying to create a Movie in database and bind it to the current User. Correct me if I'm wrong.
At first, as you may learn from Hibernate User Guide, bidirectional #ManyToMany association should be defined and used differently.
A bidirectional #ManyToMany association has an owning and a mappedBy side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.
Secondly, you should not use CascadeType.ALL on #ManyToMany associations:
For #ManyToMany associations, the REMOVE entity state transition
doesn’t make sense to be cascaded because it will propagate beyond the
link table. Since the other side might be referenced by other entities
on the parent-side, the automatic removal might end up in a
ConstraintViolationException.
For example, if #ManyToMany(cascade = CascadeType.ALL) was defined and
the first person would be deleted, Hibernate would throw an exception
because another person is still associated with the address that’s
being deleted.
So, we should move cascade to the owning side, change cascade type, provide helper methods to the User and update only the owning side (User) of the association in our business logic. Let's change the code.
User model:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies = new HashSet<>();
public void addMovie(Movie movie) {
movies.add(movie);
movie.getUsers().add(this);
}
public void removeMovie(Movie movie) {
movies.remove(movie);
movie.getUsers().remove(this);
}
Movie model:
#ManyToMany(mappedBy = "movies")
private Set<User> users = new HashSet<>();
And business logic:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
current_user.addMovie(movie);
userRepository.save(current_user);
return movie;
}
I am having an issue in setting up a one to many relationship in my annotated object.
I have the following:
#MappedSuperclass
public abstract class MappedModel
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id",nullable=false,unique=true)
private Long mId;
then this
#Entity
#Table(name="customer")
public class Customer extends MappedModel implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -2543425088717298236L;
/** The collection of stores. */
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<Store> stores;
and this
#Entity
#Table(name="store")
public class Store extends MappedModel implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -9017650847571487336L;
/** many stores have a single customer **/
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn (name="customer_id",referencedColumnName="id",nullable=false,unique=true)
private Customer mCustomer;
what am i doing incorrect here
The mappedBy attribute is referencing customer while the property is mCustomer, hence the error message. So either change your mapping into:
/** The collection of stores. */
#OneToMany(mappedBy = "mCustomer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<Store> stores;
Or change the entity property into customer (which is what I would do).
The mappedBy reference indicates "Go look over on the bean property named 'customer' on the thing I have a collection of to find the configuration."
I know the answer by #Pascal Thivent has solved the issue. I would like to add a bit more to his answer to others who might be surfing this thread.
If you are like me in the initial days of learning and wrapping your head around the concept of using the #OneToMany annotation with the 'mappedBy' property, it also means that the other side holding the #ManyToOne annotation with the #JoinColumn is the 'owner' of this bi-directional relationship.
Also, mappedBy takes in the instance name (mCustomer in this example) of the Class variable as an input and not the Class-Type (ex:Customer) or the entity name(Ex:customer).
BONUS :
Also, look into the orphanRemoval property of #OneToMany annotation. If it is set to true, then if a parent is deleted in a bi-directional relationship, Hibernate automatically deletes it's children.
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "USER_ID")
Long userId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sender", cascade = CascadeType.ALL)
List<Notification> sender;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "receiver", cascade = CascadeType.ALL)
List<Notification> receiver;
}
public class Notification implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "NOTIFICATION_ID")
Long notificationId;
#Column(name = "TEXT")
String text;
#Column(name = "ALERT_STATUS")
#Enumerated(EnumType.STRING)
AlertStatus alertStatus = AlertStatus.NEW;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SENDER_ID")
#JsonIgnore
User sender;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "RECEIVER_ID")
#JsonIgnore
User receiver;
}
What I understood from the answer. mappedy="sender" value should be the same in the notification model. I will give you an example..
User model:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "**sender**", cascade = CascadeType.ALL)
List<Notification> sender;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "**receiver**", cascade = CascadeType.ALL)
List<Notification> receiver;
Notification model:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sender", cascade = CascadeType.ALL)
List<Notification> **sender**;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "receiver", cascade = CascadeType.ALL)
List<Notification> **receiver**;
I gave bold font to user model and notification field. User model mappedBy="sender " should be equal to notification List sender; and mappedBy="receiver" should be equal to notification List receiver; If not, you will get error.
I have 'Course' and 'Student' entities. They have many-to-many relation. So, i have COURSE_STUDENT(contains 'student_id' and 'course_id' columns) table. I want to register students to courses with a button.(For example; a student lists courses and click Register button to register a specific course).
When i want to create new courses, i use courseRepository and courseMapper which comes from JHipster by default.
But i don't have repository and mapper files for COURSE_STUDENT. Because it is not actually a main entity. It is created for many-to-many relation.
How can i register students to courses?
Git repo:https://github.com/canberkizgi/monolithic-mucs
My course entity:
#Entity
#Table(name = "course")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description")
private String description;
#ManyToOne
private Instructor instructor;
#ManyToMany(fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="courses_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="students_id", referencedColumnName="id"))
private Set<Student> students = new HashSet<>();
Student entity:
#Entity
#Table(name = "student")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(unique = true)
private User user;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "students")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Course> courses = new HashSet<>();
For example; Createcourse function with Mapper and Repository
#PostMapping("/courses")
#Timed
public ResponseEntity<CourseDTO> createCourse(#Valid #RequestBody CourseDTO courseDTO) throws URISyntaxException {
log.debug("REST request to save Course : {}", courseDTO);
if (courseDTO.getId() != null) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new course cannot already have an ID")).body(null);
}
Course course = courseMapper.toEntity(courseDTO);
course = courseRepository.save(course);
CourseDTO result = courseMapper.toDto(course);
return ResponseEntity.created(new URI("/api/courses/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
The relationship is owned by the course entity. Thats because on the student side the #ManyToMany annotation has a mappedBy attribute. This means, that the database will reflect the set in the course. You need to add students to that set to save the relationship. That change needs to be done within a transaction.
That being said it would probably be best to follow DDD here. I would create a registerTo method in the student class that would take the course as a parameter. I would then call this.courses.add(course) and course.getStudents().add(this) in that method.