Spring Boot JPA - OneToMany relationship and database structure - spring-boot

I wonder what database structure would be the best option in my case:
I have entity Questionnaire:
#Table(name = "questionnaire")
public class Questionnaire extends BaseEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "fieldStatus")
private List<QuestionnaireField > fieldStatusList;
}
#Table(name = "questionnaire_field")
public class QuestionnaireField extends BaseEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "questionnaire_id")
private Long questionnaireId;
#Column(name = "field_id")
private Long fieldId; //this is id related to the other table Field
#Column(name = "completed")
private boolean completed; //because I need some additional informations like completed I think I can't use ManyToMany between Questionnaire and Field
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name = "questionnaire_id")
private Questionnaire questionnaire;
As you see each Questionnaire can have multiple QuestionnaireFields, BUT each QuestionnaireField is of type Field (hence I added private Long fieldId). Table Field can have 10.000 different fields.
Summary:
one questionnaire can have e.g. 10 Fields, the second one 20 another Fields etc. To store fields related to some particular Questionnaire I created QuestionnaireField table with 2 columns: private Long questionnaireId; and private Long fieldId; . The question is if it is a good approach? That are plain columns not related to any Foreign Key... I try to find the best solution to save Questionnaire with related QuestionnaireFields that are a subset of a big Field table...

Related

How to save an object that contains a list of integers in a postgres database

i have an object called Cart i want to save it in the database with an array/list of integers that rappresents Product's id.
This is my Cart class at the moment:
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "customer_fk")
private Integer customerFk;
#Column(name = "product_list")
private List<Integer> productList;
}
i saw that postgres has the integer[] datatype
I'm using spring.jpa.hibernate.ddl-auto=create to create the database
How do i tell to spring and jpa to save that list as an integer in the DB?
(if i can)
Is this a good solution?
I actually found a solution that works, using Strings, but im too curious to see if find a way to do it with arrays
The canonical approach here would be to maintain a table of products and then map carts to products in a one to many relationship. You could try:
#Entity
#Table(name = "Cart")
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "customer_fk")
private Integer customerFk;
#Column(name = "product_list")
#OneToMany(mappedBy="cart", cascade = CascadeType.ALL)
private List<Product> productList;
}
#Entity
#Table(name = "Product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#ManyToOne
#JoinColumn(name = "FK_Cart")
private Cart cart;
// more fields, getters and setters
}
This is not a complete implementation, but the idea is that each product ID should exist in a dedicated products table, and each cart entity should track its product references via a foreign key relationship.

Troubles with Bidirectional One-To-One JPA

I'm coding a CRUD JPA web application. My goal is that a given parent Vehicle can only have a single child Driver, but during runtime this same Driver can instead be assigned to another Vehicle and vice versa. To my understanding, this could be accomplished via an OneToOne relationship.
I've tried some different approaches, but to no success. I can assign a Vehicle to a Driver just fine, but when I try to update/create a new Vehicle and give him a Driver, via controllers, nothing happens. I can only do it the other way around. I'm assuming this is because Vehicle is the parent and I can only create a new relation by updating a parent.
My question is, is it possible to make these updates bidirectional and how can I achieve that?
I've tried using a shared primary key, using a foreign key, using a join table. The result is always the same and I can't quite grasp why. I have an OneToMany relationship working on this application and it works as I expect it to work. I can update on one side or the other, delete on one side or the other. Both entities have been updated. OneToOne? Parent seems to have all the power.
This is what I'm working with right now:
Driver
#Entity
#Table(name= "drivers")
public class Driver {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private int age;
#OneToOne(mappedBy = "driver")
private Vehicle vehicle;
Vehicle
#Entity
#Table(name= "vehicles")
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String make;
#Column(nullable = false)
private String model;
#Column(nullable = false)
private int mileage;
#Column(nullable = false)
private int year;
#Column(nullable = false)
private int fuel;
#OneToOne
#JoinColumn(name = "driver_id")
private Driver driver;
And just for reference, this is the OneToMany relationship I have and that I'm happy with. I'd like my OneToOne to have the same behavior, except I don't need to save a list of entities, only one.
#Entity
#Table(name="stops")
public class Stop {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#ManyToOne
#JoinColumn(name="route_id")
private Route route;
#Entity
#Table(name="routes")
public class Route {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#OneToMany
#JoinColumn(name = "route_id")
private List<Stop> stops = new ArrayList<>();
Any tips would be appreciated, thank you for your time.

Is possible to create an entity with two primary key values?

I'm having a problem with one Entity which contains an unique_key so perhaps what I need is to create that Entity with two PrimaryKey is that even possible?
This is my Entity
#Entity
public class UserAnswerQuestion extends DateAudit {
#Id
#Column(name = "useranswerquestion_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "useranswerquestion_seq")
#SequenceGenerator(name = "useranswerquestion_seq", allocationSize = 1)
private Long id;
#ManyToOne
private User user;
#ElementCollection
private List<Answer> answerList;
#ManyToOne
private Question question;
private Boolean passed;
private Boolean shown;
public UserAnswerQuestion(){
}
....
And the problem when I try to create this Entity with another User it says :
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "uk_6v3tlg1gflua8r8d4wlqxo7v5"
Detail: Key (answer_list_answer_id)=(11) already exists.
So what I'd like to do is make User as a #Id if possible, and maybe it solves my problem...
EDIT
What I did is, create a class UserAnswerQuestionId like this :
public class UserAnswerQuestionId implements Serializable {
#Column(name = "useranswerquestion_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "useranswerquestion_seq")
#SequenceGenerator(name = "useranswerquestion_seq", allocationSize = 1)
private Long id;
#Column(name ="user_id")
private Long user_id;
public UserAnswerQuestionId(){
}
public UserAnswerQuestionId(Long user_id) {
this.user_id = user_id;
}
And then in the UserAnswerQuestion entity I changed it to :
#EmbeddedId
private UserAnswerQuestionId userAnswerQuestionId;
But the error now says :
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.pew.model.useranswers.UserAnswerQuestion
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:800) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:785) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
Edit 2
I'm reading carefully the error and looks like the problem is in the
#ElementCollection
private List<Answer> answerList;
How do I solve this to allow this element repeat on this Entity?
Perhaps I can define those #ElementCollection not to be Unique? so can be repeated on this Entity?
This is my Answer Entity
#Entity(name = "answer")
public class Answer extends DateAudit {
#Id
#Column(name = "answer_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "answer_seq")
#SequenceGenerator(name = "answer_seq", allocationSize = 1)
private Long id;
#Column(name = "answerToQuestion")
private String answerToQuestion;
Perhaps you may need to explain your usecase better. From contextual info, looks like you want to retrive Users and the Answers they provided for various questions.In that case you may just need an association table like below.
public class User extends DateAudit { //This assumes you are intested in retrieving User and their answer(s) in the domain model.
#Id
private Long id; //userid.
#ManyToMany
#JoinTable(name = “ANSWERS_FOR_QUESTIONS”, //More appropriate name may be : ANSWERS_BY_USERS
joinColumns = { #JoinColumn(name = “user_id”) }, //fk
inverseJoinColumns = { #JoinColumn(name = “question_id”) }) //fk
private Set<Answers> answers = new HashSet<Answers>();
Primary key is always only 1 and unique. It can be composite key made of multiple columns tuple.
Including your UserAnswerQuestionId by using #IdClass should work:
#Entity
#IdClass(UserAnswerQuestionId.class)
public class UserAnswerQuestion extends DateAudit {
#Id
private Long id;
#Id
private Long user_id;
#ElementCollection
private List<Answer> answerList;
#ManyToOne
private Question question;
private Boolean passed;
private Boolean shown;
public UserAnswerQuestion(){
}

FindBy and OrderBy doesn't order accordingly - Spring Data Jpa

I am using this method :
List<Client>findTop10ByGenderOrderBySurvey_Results_ScoreDesc(char gender);
The logic is this :
I have a Client Model, with a reference OneToMany to the Survey Model, and the Survey Model has a reference of OneToOne with the Results model which has the score field.
So one Client can have many surveys each of which has a score.
I wanted to order the Clients By their score, in descending order, and then get top10 Male Clients with highest score.
The method I'm using does filter By Gender, and returns 10 Clients.
But it returns the same Client more than once,because it has several surveys. And not in an ordered manner.
What am I doing wrong and how do I fix this ?
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "client_id")
private Long id;
#Column(name = "gender")
private char gender;
#OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
#JsonManagedReference
private List<Survey> survey= new ArrayList<Survey>();
}
public class Survey{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SURVEY_ID")
private Long Id;
#ManyToOne
#JsonBackReference
#JoinColumn(name = "client_id")
public Client client;
#OneToOne(cascade=CascadeType.ALL)
#JsonManagedReference
#JoinColumn(name = "surveyresult_id")
private Results surveyResults;
}
public class Results {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SURVEYRESULT_ID")
private Long Id;
private Double score;
}
To filter out duplicate results, use the distinct keyword:
List<Client> findDistinctTop10ByGenderOrderBySurvey_Results_ScoreDesc(char gender);
The OrderBy syntax is incorrect. To order by multiple properties, simply append them like this:
List<Client> findDistinctTop10ByGenderOrderBySurveyDescResultsDescScoreDesc(char gender);
Note: When method names become very long, it is a sign the query might be too complex to be a derived query. It is then recommended to use #Query with a shorter, higher-level method name to describe the query.

#ManyToMany with extra column - how to load via Spring Data?

I have many to many relation between User and Event. I need to have extra column in relational table. I did id:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy="user")
private Set<EventUser> eventUsers = new HashSet<>();
//
}
#Entity
public class Event {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
#OneToMany(mappedBy="event")
private Set<EventUser> eventUsers = new HashSet<>();
//
}
#Entity
#Table(name = "Event_User")
public class EventUser {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
private String reaction;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "event_id")
private Event event;
//
}
But now... I don't know how to load all events where user has concrete email. Before it I used method:
findByUsersEmail(String email);
Now I can't do this, because Event doesn't have Set users field.
Any ideas ?
What you need here is property-expressions.
Just a quick idea to start:
findByEventUsers_UserEmail(String email);
Note: Dont forget that creating queries by method names is a very limited approach and only used by trivial cases. In any other case, don't be afraid of using the #Query annotation on the method or write JPQL/Criteria API manually.

Resources