How to connect #ManyToMany relationships in Spring - java

I am creating an application that uses a many to many relationship between employees and shifts. However, I am having difficulties in understanding how I can assign/connect an employee to a shift.
#Data
#Entity
public class Employee {
private #Id #GeneratedValue long employeeID;
private String Name;
#OneToMany(cascade = CascadeType.ALL)
private Set<Shift> shifts;
private Employee() {
}
public Employee(long employeeID, String Name) {
this.employeeID = employeeID;
this.Name = Name;
}
public Employee(long employeeID, String Name, Set<Shift> shifts) {
this.employeeID = employeeID;
this.Name = Name;
this.shifts = shifts;
}
public void setShift(Set<Shift> shifts) {
this.shifts = (Set<Shift>) shifts;
}
}
#Data
#Entity
public class Shift {
private #Id #GeneratedValue long Id;
private String shifts;
private Set<Employee> employee;
private Shift() {
}
public Shift(String shifts) {
this.shifts = shifts;
}
public Shift(String shiftPeriod,Set<Employee> employee ) {
this.shifts = shifts;
this.employee=employee;
}
public void setEmployee(Set<Employee> employee) {
this.employee = employee;
}
}
#Component
public class DatabaseLoader implements CommandLineRunner {
private final EmployeeRepository repository;
#Autowired
public DatabaseLoader(EmployeeRepository repository) {
this.repository = repository;
}
#Override
public void run(String... strings) throws Exception {
Shift shift = new Shift("Friday Morning");
Employee employee = new Employee(0001, "Adam Smith");
employee.setShift(shift);
this.repository.save(employee);
}
}
public interface ShiftRepository extends CrudRepository<Shift, Long>
public interface EmployeeRepository extends CrudRepository<Employee, Long>
Entities added into employees and shifts are saved but is there a way I can assign a shift to an employee in the DatabaseLoader class, as I've been stuck on finding a solution to this.
I know that I haven't included a method which attempts to connect employee and shifts but I don't how to approach this problem.
Thanks in advance
**EDIT: The new problem I have now is that I get the following message when trying to deploy in spring:
Unable to build Hibernate SessionFactory: Could not determine type for: java.util.Set, at table: shift, for columns: [org.hibernate.mapping.Column(employee)]

In my opinion:
A shift is an independent entity. It does not depend on any employees.
So Shift must not have a reference to Employee.
On the other hand an employee depend on several shifts, so Employee must have unidirectional #OneToMany association with Shift.
#OneToMany
private List<Shift> shifts;
And don't use cascading here because Employee and Shift are independent entities.
UPDATE
To "add a shift to an employee" we can use for example an Employee setter:
#Entity
public class Employee {
//...
private String name;
//...
#OneToMany
private List<Shift> shifts;
//...
public Employee(String name) {
this.name = name;
}
//...
public setShifts(Shift... shifts) {
this.shifts = Arrays.asList(shifts);
}
//...
}
then
Shift monday = shiftRepository.save(new Shift("Monday"));
Shift friday = shiftRepository.save(new Shift("Friday"));
Employee adamSmith = new Employee("Adam Smith");
adamSmith.setShifts(monday, friday);
employeeRepository.save(adamSmith);

It seems that you are using Hibernate under the hood, then all you have to do is to set the objects you want to save properly, like this:
#Component
public class DatabaseLoader implements CommandLineRunner {
private final EmployeeRepository repository;
private final ShiftRepository shiftRepository;
#Autowired
public DatabaseLoader(EmployeeRepository repository, ShiftRepository shiftRepository) {
this.repository = repository;
this.shiftRepository=shiftRepository;
}
#Override
public void run(String... strings) throws Exception {
Shift shift = new Shift("Friday Morning");
Employee employee = new Employee(0001, "Adam Smith");
employee.setShift(shift)
this.repository.save(employee);
}
The only difference is that I attached between the two objects and only then I try to save them. It's worth to mention that it's important to use Cascade.All or Cascade.persiste in order that hibernate will do the inserts on both entities.

Related

Relation vs View

Based on my research we have two way for getting related data from two or more tables.
For example if we have 2 tables like below:
#Entity
public class User {
#PrimaryKey public long userId;
public String name;
public int age;
}
#Entity
public class Library {
#PrimaryKey public long libraryId;
public long userOwnerId;
}
If we want to load all data we have two options:
1. #Embedded and #Relation
By adding this class:
public class UserAndLibrary {
#Embedded public User user;
#Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
public Library library;
}
And add DAO method:
#Transaction
#Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();
More information in Android Documentation
2. #DatabaseView
#DatabaseView("SELECT user.id, user.name, user.age, " +
"library.libraryId FROM user " +
"INNER JOIN library ON user.userId = library.libraryId)
public class UserAndLibrary {
public long userId;
public String name;
public int age;
public long libraryId;
}
and a associating
#Database(entities = {User.class, Library.class},
views = {UserAndLibrary.class},
version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
What is the difference between two options?

How to get Entity from RestController depending on mapping URL

I have MyEntity class:
#Entity
#Table("entities)
public class MyEntity {
#ID
private String name;
#Column(name="age")
private int age;
#Column(name="weight")
private int weight;
...getters and setters..
}
In #RestController there are 2 #GetMapping methods.
The first:
#GetMapping
public MyEntity get(){
...
return myEntity;
}
The second:
#GetMapping("url")
public List<MyEntity> getAll(){
...
return entities;
}
It's needed to provide:
1. #GetMapping returns entity as it's described in MyEntity class.
2. #GetMapping("url") returns entities like one of its fields is with #JsonIgnore.
UPDATE:
When I return myEntity, client will get, for example:
{
"name":"Alex",
"age":30,
"weight":70
}
I want in the same time using the same ENTITY have an opportunity depending on the URL send to client:
1.
{
"name":"Alex",
"age":30,
"weight":70
}
2.
{
"name":"Alex",
"age":30
}
You could also use JsonView Annotation which makes it a bit cleaner.
Define views
public class View {
static class Public { }
static class ExtendedPublic extends Public { }
static class Private extends ExtendedPublic { }
}
Entity
#Entity
#Table("entities)
public class MyEntity {
#ID
private String name;
#Column(name="age")
private int age;
#JsonView(View.Private.class)
#Column(name="weight")
private int weight;
...getters and setters..
}
And in your Rest Controller
#JsonView(View.Private.class)
#GetMapping
public MyEntity get(){
...
return myEntity;
}
#JsonView(View.Public.class)
#GetMapping("url")
public List<MyEntity> getAll(){
...
return entities;
}
Already explained here:
https://stackoverflow.com/a/49207551/3005093
You could create two DTO classes, convert your entity to the appropriate DTO class and return it.
public class MyEntity {
private String name;
private int age;
private int weight;
public PersonDetailedDTO toPersonDetailedDTO() {
PersonDetailedDTO person = PersonDetailedDTO();
//...
return person;
}
public PersonDTO toPersonDTO() {
PersonDTO person = PersonDTO();
//...
return person;
}
}
public class PersonDetailedDTO {
private String name;
private int age;
private int weight;
}
public class PersonDTO {
private String name;
private int age;
}
#GetMapping
public PersonDTO get() {
//...
return personService.getPerson().toPersonDTO();
}
#GetMapping("/my_url")
public PersonDetailedDTO get() {
//...
return personService.getPerson().toPersonDetailedDTO();
}
EDIT:
Instead of returning an Entity object, you could serialize it as a Map, where the map keys represent the attribute names. So you can add the values to your map based on the include parameter.
#ResponseBody
public Map<String, Object> getUser(#PathVariable("name") String name, String include) {
User user = service.loadUser(name);
// check the `include` parameter and create a map containing only the required attributes
Map<String, Object> userMap = service.convertUserToMap(user, include);
return userMap;
}
As an example, if you have a Map like this and want
All Details
userMap.put("name", user.getName());
userMap.put("age", user.getAge());
userMap.put("weight", user.getWeight());
Now if You do not want to display weight then you can put only two
parameters
userMap.put("name", user.getName());
userMap.put("age", user.getAge());
Useful Reference 1 2 3

MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY'

I am trying to create two entities (student, university) with a unidirectional #ManyToOne relationship between them. University can have many students.
I don't want to save them seperately, i want to save student and university should be saved because of #Cascade.
During saving second student i get exception:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY'
I don't know how to resolve that problem.
My code:
#Entity
public class Student {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne(cascade=CascadeType.ALL)
private University university;
public Student(String name, University university) {
this.name = name;
this.university = university;
}
public Student() {
}
}
#Entity
public class University {
#Id
private Long id;
private String name;
public University(Long id, String name) {
this.id = id;
this.name = name;
}
public University() {
}
}
#Repository
public interface StudentRepository extends JpaRepository<Student,Long> {
}
#SpringBootApplication
public class App implements CommandLineRunner
{
#Autowired
private StudentRepository studentRepository;
#Autowired
private UniversityRepository universityRepository;
public static void main( String[] args )
{
SpringApplication.run(App.class);
}
#Override
public void run(String... strings) throws Exception {
Student student = new Student("pawel", new University(1L,"pw"));
Student student1 = new Student("gawel", new University(1L,"pw"));
studentRepository. save(student);
studentRepository.save(student1);
}
}
I explored that if beside university reference i set student id manually than everything works.
code that works:
#Entity
public class Student {
#Id
private Long id;
private String nazwa;
#ManyToOne(cascade=CascadeType.ALL)
private University university;
public Student(Long id,String nazwa, University university) {
this.id=id;
this.nazwa = nazwa;
this.university = university;
}
public Student() {
}
public Student(String nazwa) {
this.nazwa = nazwa;
}
}
#Entity
public class University {
#Id
private Long id;
private String name;
public University(Long id, String name) {
this.id = id;
this.name = name;
}
public University() {
}
}
#Repository
public interface StudentRepository extends JpaRepository<Student,Long> {
}
#SpringBootApplication
public class App implements CommandLineRunner
{
#Autowired
private StudentRepository studentRepository;
public static void main( String[] args )
{
SpringApplication.run(App.class);
}
#Override
public void run(String... strings) throws Exception {
Student student = new Student(1L,"pawel", new University(1L,"pw"));
Student student1 = new Student(2L,"gawel", new University(1L,"pw"));
studentRepository. save(student);
studentRepository.save(student1);
}
}
Why that code with manual setting of student id works? Why there is a problem with code when i use #generatedValue?
#Result
The difference was because when #GeneratedValue is used, hibernate create queries of save, comparatively when i was assigning id manually, hibernate first check if entity exist in database if yes, entity gets updated if not hibernate save it for the first time.
You have no sequence generator defined. You can do it in your database, then simply use (assuming its name is "my_seq"):
#SequenceGenerator(name="seq",sequenceName="my_seq")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
I can see, that you're using MySQL, then you can use the identity generation method:
#GeneratedValue(strategy = GenerationType.IDENTITY)
together with auto_increment in your id column definition:
create table student(
id int not null auto_increment,
...
)
Edit:
Try saving the university first. Set this as body of your run method:
University uni = new University(1L,"pw");
University savedUni = universityRepository.save(uni);
Student student = new Student("pawel", savedUni);
Student student1 = new Student("gawel", savedUni);
studentRepository.save(student);
studentRepository.save(student1);
If you are saving only Students everyone with the new instance of University, then Students entity are cascading and inserting that instance every time, so that constraints are violated. This happens, because new University(1L,"pw") is not managed and hibernate treats it as a new entity - and because you did not provide an id - it was set as some default (0L). Using University savedUni = universityRepository.save(uni); makes hibernate recognize the entity, so that no additional insert is done.

How to handle base class updates with mongo jpa in a Springboot app?

how can I properly handle an update of the base class when using inheritance for mongo entities?
Base class:
#Document
public class User {
#Id
private String id;
#UniqueElements
protected String uid;
protected String photoUrl;
protected Address homeAddress;
protected Gender gender;
protected List<Kid> kids;
protected GeneralInfo generalInfo;
protected List<Address> addresses;
protected String instanceId;
public User() {
}
public User(User user) {
this.uid = user.getUid();
this.photoUrl = user.getPhotoUrl();
this.homeAddress = user.getHomeAddress();
this.gender = user.getGender();
this.kids = user.getKids();
this.generalInfo = user.getGeneralInfo();
this.addresses = user.getAddresses();
this.instanceId = user.getInstanceId();
}
}
Children:
#Document
public class Sitter extends User{
private int childCareExperienceYrs;
private AgeRange preferredKidsAgeRange;
private List<Skill> skills;
private String talents;
private LivingSpace livingSpace;
#JsonProperty
private boolean pets;
#JsonProperty
private boolean hasCar;
#JsonProperty(value = "available")
private boolean available;
}
#Document
public class Parent extends User{
}
The properties inherited from User are saved in the children documents.
When I update a User property the child tables are not updated.
For example, when changing the user address, the 'Sitter' document will still have the old address.
How to make any change from the base class to reflect on child classes too?
Does this situation require an update query for each child when the base class is changed?

How to design database model and use save method of JpaRepository to save entity if an entity has List<Items> in it?

JAVA SPRING :I am exploring JPA and am not sure of optimized way to design db and using save() of repository to save entity data right away into DB. Specifically, I have a basic class viz. Movie -
package com.kurshit.moviesmgr.vo;
import java.util.List;
public class Movie {
long movieId;
String title;
String yearOfRelease;
List<String> genere;
public Movie(long movieId, String title, String yearOfRelease, List<String> genere) {
super();
this.movieId = movieId;
this.title = title;
this.yearOfRelease = yearOfRelease;
this.genere = genere;
}
public Movie() {
// TODO Auto-generated constructor stub
}
public long getMovieId() {
return movieId;
}
public void setMovieId(long movieId) {
this.movieId = movieId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getYearOfRelease() {
return yearOfRelease;
}
public void setYearOfRelease(String yearOfRelease) {
this.yearOfRelease = yearOfRelease;
}
public List<String> getGenere() {
return genere;
}
public void setGenere(List<String> genere) {
this.genere = genere;
}
}
Each movie has list of Genere - List- it falls under, like - Action, Comedy, etc.
I am trying to create an interface that extends JpaRepository and use the inbuilt save method to save the Movie Data into DB.
I am not sure about how I should design my DB - As in, Questions like -
1. Shall I create two different tables for Movie and Genere wherein Movie table references to Genere ?
2. Shall I create just onw table and store all Genere's list as a single CSV in one column ?
3. Can I use repository's save() right away to save and map this data into respective tables.
Would really appreciate if someone can share any sources or sample code to refer or can offer any help.
Thanks much!
First of all, you should search look up #Entity annotation so that you can tell your ORM to create the necesary table for that entity.
Secondly, you need to ask yourself, how this application will work. It would be best in my opinion to create a genre entity as well, linked to Movie through a #ManyToMany relationship.
Try looking over the simple entity example here
https://spring.io/guides/gs/accessing-data-jpa/
First variant - with 'genre' as enum (if your genre is a fixed list):
#Data // it's Lombok annotation: https://projectlombok.org/features/Data
#NoArgsConstructor
#Entity
public class Movie implements Serializable {
#Id #GeneratedValue
private Integer id;
private String title;
private Integer yearOfRelease;
#ElementCollection
#Enumerated(EnumType.STRING)
#Column(name = "genre")
private List<Genre> genres;
public Movie(String title, Integer yearOfRelease, List<Genre> genres) {
this.title = title;
this.yearOfRelease = yearOfRelease;
this.genres = genres;
}
}
public enum Genre {
ACTION, COMEDY, ...;
}
public interface MovieRepo extends JpaRepository<Movie, Integer> {
}
In this case you create your movie like this:
Movie movie = new Movie("Title", 2000, Arrays.asList(ACTION, COMEDY));
movieRepo.save(movie);
Second variant - 'genre' as independent entity:
#Data
#NoArgsConstructor
#Entity
public class Movie implements Serializable {
// the same as previous one...
#ManyToMany
private List<Genre> genres;
}
#Data
#NoArgsConstructor
#Entity
public class Genre implements Serializable {
#Id private String name;
public Genre(String name) {
this.name = name
}
}
public interface MovieRepo extends JpaRepository<Movie, Integer> {
}
public interface GenreRepo extends JpaRepository<Genre, String> {
}
In this case you first create genres:
List<Genre> genres = genreRepo.saveAll(Arrays.asList(
new Genre("Action"),
new Genre("Comedy"),
));
Then create movie:
Movie movie = new Movie("Title", 2000, genres);
movieRepo.save(movie);
More info to read: Hibernate ORM User Guide - Collections

Categories