I have a db structure similar to this on the picture
table questions has composite primary key (id, cate_id), where cate_id is foreign key to categories(cate_id)
table answers has composite primary key (id, question_id), where question_id is foreign key to questions(id)
from data perspective it looks like this:
CATEGORIES
cate_id
name
C1
Cat 1
C2
Cat 2
QUESTIONS
id
cate_id
question
Q1
C1
what ?
Q2
C1
why ?
Q1
C2
when ?
Q2
C2
who ?
ANSWERS
id
question_id
answer
A1
Q1
aaa
A2
Q1
bbb
A1
Q2
ccc
A2
Q2
ddd
Now I'm trying to map it to JPA model:
the category entity is the simplest:
#Entity
#Table(name = "categories")
public class Category {
#Id
#Column(name = "cate_id")
private Long id;
...
}
now I'm adding the QuestionId to reflect the composite key and Question entity:
#Embeddable
public class QuestionId implements Serializable {
#Column(name = "id")
private Long id;
#Column(name = "cate_id")
private Long categoryId;
//constructors, hashCode, equals, etc
}
#Entity
#Table(name = "questions")
public class Question {
#EmbeddedId
private QuestionId id;
#ManyToOne
#MapsId(value = "categoryId")
#JoinColumn(name = "cate_id")
private Category category;
}
all good so far
the problem starts when I try to add next level: the Answers entity
#Embeddable
public class AnswerId implements Serializable {
#Column(name = "id")
private Long id;
#Column(name = "question_id")
private Long questionId;
//constructors, hashCode, equals, etc
}
#Entity
#Table(name = "answers")
public class Answer {
#EmbeddedId
private AnswerId id;
#ManyToOne
#MapsId(value = "questionId")
#JoinColumn(name = "question_id", referencedColumnName = "id")
private Question question;
}
After creating the Answer entity I'm receiving this error on application startup
Caused by: org.hibernate.AnnotationException: Unable to find column reference in the #MapsId mapping: cate_id
For me it looks like the Answer entity needs access to Categories which makes no sense I guess.
And it has nothing to do with existing database configuration because I have the same result on H2 in-memory db and spring.jpa.hibernate.ddl-auto=create
any suggestions will be more than welcome
thanks in advance
I just noticed that my DB model makes no sense.
The question in the Answers table is not unique accross the system, so there are two ways of solving it:
add a category_id reference to answers table (which is what JPA is
complaining about)
or
make the question_id unique in the system through some sequence generator or anything else
Related
I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.
I have 3 sql tables:
Account (ID (BIGINT),...)
Card (ID (BIGINT),...)
RelationAccountCard (ACCOUNT (BIGINT), CARD(BIGINT), QUANTITY(int))
One account can have multiple cards, one card can have multiple account
#Data
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(
name = "RELATION_ACCOUNT_CARD",
joinColumns = #JoinColumn(name = "ACCOUNT"),
inverseJoinColumns = #JoinColumn(name = "CARD"))
private Set<Card> cardsOwned = new HashSet<>();
}
#Data
#Entity
public class Card {
#Id
private long id;
#JsonIgnore
#ManyToMany(mappedBy = "cardsOwned")
private java.util.Set<Account> accounts;
}
This code above is working if the field "QUANTITY" in relationAccountCard didn't exist. I would like to make the code works knowing that I added the "QUANTITY" field. I tried for hours yesterday without success, do you have an idea of how to do this?
Basically I need to replace private Set<Card> cardsOwned; by private Set<RelationAccountCard> relationAccountCards; but I don't know how to
The solution was to delete the composite primary key. To make (card,account) unique, and to add an ID.
Once it was done, I simply followed the answer of this stackoverflow post:
Mapping many-to-many association table with extra column(s)
I have two tables Quiz and Question which I want to associate with a #OneToMany relationship but the join table is not being created in mysql workbench database. Here are the entities :
Quiz.java
#Entity
public class Quiz {
// attributes :
private Integer idQuiz;
private String quizTopic;
#OneToMany
#JoinColumn(name = "quiz_Id")
private List<Question> questions;
// constructors
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "quiz_Id")
public Integer getIdQuiz() {
return idQuiz;
}
#Column(name = "quiz_topic")
public String getQuizTopic() {
return quizTopic;
}
//setters
}
Question.java
#Entity
public class Question {
// attributes
private Integer idQuestion;
private String value;
private String op1;
private String op2;
private String op3;
private String correctAnswer;
// constructors
// Getters :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "question_Id")
public Integer getIdQuestion() {
return idQuestion;
}
#Column(name = "question_value")
public String getValue() {
return value;
}
#Column(name = "question_op1")
public String getOp1() {
return op1;
}
#Column(name = "question_op2")
public String getOp2() {
return op2;
}
#Column(name = "question_op3")
public String getOp3() {
return op3;
}
#Column(name = "question_op4")
public String getCorrectAnswer() {
return correctAnswer;
}
//setters
}
no foreign keys are created
here is what I get after running
Hibernate: create table question (question_id integer not null auto_increment, question_op4 varchar(255), question_op1 varchar(255), question_op2 varchar(255), question_op3 varchar(255), question_value varchar(255), primary key (question_id)) engine=InnoDB
Hibernate: create table quiz (quiz_id integer not null auto_increment, quiz_topic varchar(255), primary key (quiz_id)) engine=InnoDB
application.properties:
spring.main.web-application-type=none
spring.application.ui.title=Quiz
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/quiz?createDatabaseIfNotExist=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=justpass
spring.jpa.show-sql=true
The annotation
#OneToMany
private ArrayList<Question> questions;
will not create a join table. It should create a foreign-key in the Question table to the Quiz table. This foreign key is not the same as the
private Integer idQuestion;
column.
For your solution it dependes wether you want to build a uni- or bidirectional #OneToMany relationship. If you want a unidirectional one-to-many relationship you have to define a #JoinColumn to tell Hibernate that it shall create a foreign key with the given name in the related table. Most of the time a unidirectional one to many relationship is the easiest and sufficient way to go.
Unidirectional
#OneToMany
#JoinColumn(name = "quiz_id")
private List<Question> questions;
By this Hibernate will create the foreign key with name "quiz_id" in the question table.
In the bidirectional case we are able to access the quiz from the question vice versa the questions from the quiz. In this case you will you will have to define the variable which shall represent the parent class in the child class. For example if the quiz shall be the parent class, you will define the annotation. #OneToMany(mappedBy = "quiz"). Additionally to this you will have to define the question to be the #ManyToOne side as well. All in all:
Bidirectional
Quiz.java:
#OneToMany(mappedBy = "quiz")
private List<Question> questions;
Question.java
#ManyToOne(fetch = FetchType.LAZY)
private Quiz quiz;
You will want to define the fetch type to be lazy due to performance reasons (I even ran into some overflow errors in the past, without the lazy method).
Keep in mind that if you are using the bidirectional mapping together with serialization libraries such as jackson, you will run into the JSON infinite recursion problem when de-/serializing from/to JSON. In this case you will want to use #JsonIdentityInfo to serialize the id only instead of the complete entity (which will lead to infinite recursion) or #JsonIgnore respectively #JsonManagedReference and #JsonBackReference to not serialize the child dependencies of an entity.
I also recommend to use the properties cascade defining the cascade type and orphanRemoval for the deletion of entities when you work with relationships, but I did not want to blow up my answer with unrelated information.
It looks suspicious that you try to mix up access strategies in the Quiz entity.
As it is stated in the documentation:
As a JPA provider, Hibernate can introspect both the entity attributes (instance fields) or the accessors (instance properties). By default, the placement of the #Id annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. When placed on the identifier getter, Hibernate will use property-based access.
So, instead of this:
#OneToMany
#JoinColumn(name = "quiz_Id")
private List<Question> questions;
put the #OneToMany and #JoinColumn(name = "quiz_Id") annotations on the appropriate getter:
#OneToMany
#JoinColumn(name = "quiz_Id")
public List<Question> getQuestions() {
return questions;
}
then try to recreate your schema by setting the spring.jpa.hibernate.ddl-auto property like this:
spring.jpa.hibernate.ddl-auto=create
I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.
I am trying to get the following type of mapping to work
Table event has the following columns:
id (PK)
prodgroup
errandtype
table errandtype : errandtype
table prodgroup: prodgroup
I have corresponding JPA classes
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
Prodgroup prodgroup;
// what mapping should go here?
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
Ok so questions are marked as comments in the code but I'll try to be explicit anyway.
In the above example I want my Prodgroup and ErrandType fields in the MyEvent class to be set to corresponding Prodgroup and Errandtype instances
I have tried #OneToOne relationships with #joincolumns and with mappedby attribute, but I just can't get it working and I've lost all sense of logical approach. My grasp of JPA entity mapping is clearly weak.
So can anyone bring some clarity?
It should be:
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "prodgroup_id", insertable = true, updatable = true)
Prodgroup prodgroup;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "errandtype_id", insertable = true, updatable = true)
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
FetchType Eager means the object will be always loaded (would be "Lazy" by default if not specified).
CascadeType.ALL means mearge/persist/remove will be also done to linked tables.
Sebastian
Your table columns event.prodgroup and event.errandtype are foreign keys to respective tables (prodgroup, errandtype). So you need #ManyToOne association (because many events may share one prodgroup or errantype).