I'm trying to make some hibernate stuff and create sql scripts automatically based on the hibernate annotations. Here is what I have:
Schedule class:
#Entity
#Table(name = ScheduleTableConsts.TABLE_NAME)
public class Schedule implements Serializable {
#Id
#Column(name = ScheduleTableConsts.ID_COLUMN)
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name = ScheduleSlotTableConsts.ID_COLUMN)
private List<ScheduleSlot> scheduleSlots;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name = LessonTableConsts.ID_COLUMN)
private List<Lesson> lessons;
Constructors, getters, setters...
ScheduleSlot class:
#Entity
#Table(name = ScheduleSlotTableConsts.TABLE_NAME,
uniqueConstraints = {#UniqueConstraint(columnNames = {TimeSlotTableConsts.ID_COLUMN,
PlaceSlotTableConsts.ID_COLUMN})})
public class ScheduleSlot implements Serializable {
#Id
#Column(name = ScheduleSlotTableConsts.ID_COLUMN)
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToOne
#JoinColumn(name = TimeSlotTableConsts.ID_COLUMN)
private TimeSlot timeSlot;
#OneToOne
#JoinColumn(name = PlaceSlotTableConsts.ID_COLUMN)
private PlaceSlot placeSlot;
Constructors, getters, setters...
Lesson class:
#Entity
#Table(name = LessonTableConsts.TABLE_NAME)
public class Lesson implements Serializable {
#Id
#Column(name = LessonTableConsts.ID_COLUMN)
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(fetch = FetchType.LAZY)
private Set<Professor> professors;
#OneToMany(fetch = FetchType.LAZY)
private Set<Course> courses;
#OneToMany(fetch = FetchType.LAZY)
private Set<Group> groups;
#Column(name = LessonTableConsts.LAB_COLUMN)
private boolean lab;
Constructors, getters, setters...
What I'm trying to achieve is to let schedule know about it's slots and lessons and not to let slots and lessons know about the schedule they are belong to. The code above seems to be ok, but when I'm trying to generate sql script (using maven and hibernate3-maven-plugin for that purposes) and run it the following happens:
It creates a SCHEDULE table with no pointers to SCHEDULESLOT or LESSON tables;
It creates SCHEDULESLOT and LESSON tables with no pointers to SCHEDULE table.
Could somebody, please, tell me what I am doing wrong?
Thank in advance!
There is a thing you are forgetting to notice:
When using #OneToMany annotation, you cannot simply use #JoinColumn. However, on the other side, I mean #ManyToOne: you can use #JoinColumn.
It should be clear to you why this works this way, and not the other way around. (How can an entity keep so many IDs in just one column?)
So you have two options here:
1) Use a specific table to store relation between your One and Many entities. Something like this:
#OneToMany(cascade=CascadeType.ALL)
#JoinTable(name = "SCHEDULE_SLOTS_MAPPING", joinColumns = #JoinColumn(name = "SCHEDULE_ID"), inverseJoinColumns = #JoinColumn(name = "SCHEDULE_SLOT_ID"))
private List<ScheduleSlot> scheduleSlots;
2) Use the #JoinColumn on the Many side of the relationship: (e.g. your ScheduleSlot class)
#ManyToOne
#JoinColumn(name="SCHEDULE_ID")
private Schedule schedule;
Related
I am trying to stop my relationship making new tables. I have tried multiple approaches to this problem, but there seems to be an error every way I turn. For instance when I try the following code:
//other variables
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<User> users= new ArrayList<>();
I get the following error:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`eb322`.`#sql-3140_2e7`, CONSTRAINT `FK20sqpkpotyyf5wx4jfmp519lu` FOREIGN KEY (`user_id`) REFERENCES `year` (`year_id`))
I have checked all my tables and indexes in the database and I cannot find this constraint anywhere. How do I go about removing it. I basically want to have my schema be like this:
Year will have a list of all students, teachers. When a student is enrolled they will be added to that year etc.
If I don't add the join Column I simply get another table saying
Year.students
How do I combine these together.
This is my student class just incase there's something wrong here:
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int User_id;
}
How I am adding data to year table
//get data about student
Student s = ssrepo.findByName(name);
Year y = yyrepo.findByYear(year);
List<Student> students = y.getStudents();
students.add(s);
yyrepo.save(y)
You seem to be using Unidirectional OneToMany relationship
Hibernate uses an association table to map the relationship so when you remove #JoinColumn annotation an association table is created.
As Year has one to many relationship with student, the type of the List should be List<Student> instead of List<User>
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Student> users= new ArrayList<>();
And using OneToMany Unidirectional association is normally not recommended because of its performance issues. You can consider using bidirectional association. It would be something as follows
public class Year {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "YEAR_ID")
private Long id;
#Column(name = "TYPE_ID")
private Long typeId
#Column(name = "TYPE")
private Boolean type // 1 or 0 to know if typeId is of student or teacher
#Column(name = "YEAR")
private Date year
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Student> students;
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Teacher> teachers;
}
public class Teacher{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "TEACHER_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "STUDENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
There are two ways to do this. The first is bidirectional. Where you do the mapping in the two entities. here in this link.(https://dzone.com/articles/introduction-to-spring-data-jpa-part-4-bidirection)
hava exemples.
public class MyClass {
#OneToMany(mappedBy = "myClass", fetch = FetchType.LAZY,
cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<User> users;
}
mappedBy is to say who is the dominate in the relationship. In this case, MyClass has the strongest relationship.
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private MyClass myClass;
}
I believe that this is the best way, because her realities are apparent in both entities. There is a way to do it in a unidirectional way. Exemple in link (How to define unidirectional OneToMany relationship in JPA)
I have a table PATIENT which has some fields. There's also a CONTACT table that has a field called 'patientId' that needs to store PATIENT's ID (which is autogenerated), and a PATIENT_CONTACT table that only relates the two tables.
Now, here comes the tricky part. There are three other tables: CONTACT_ADDRESS, CONTACT_PHONE, CONTACT_EMAIL. A row in CONTACT will have the same ID as one (and only one) of CONTACT_ADDRESS, CONTACT_PHONE and CONTACT EMAIL. How do I get this all to work?
I have tried so many approaches, this is what I have right now:
#Entity
#Table(name = "patient", schema = "patient")
public class PatientEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
//... more fields
#OneToOne
private ContactEmailEntity contactEmailEntity;
#OneToOne
private ContactAddressEntity contactAddressEntity;
#OneToOne
private ContactPhoneEntity contactPhoneEntity;
}
The three CONTACT_* classes are similar and they look like this:
#Table(name = "contact_address", schema = "patient")
public class ContactAddressEntity {
#Id
#Column(name = "id")
private Long id;
// ... more fields
#OneToOne(cascade = {CascadeType.ALL})
#MapsId
private ContactEntity contact;
}
And my CONTACT class looks like this:
#Table(name = "contacto", schema = "paciente")
public class ContactEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
//... more fields
Can you see things that don't look right or could be done better? I get all sorts of errors with every approach. My latest one is:
ERROR: column patientent0_.contact_address_entity_contact_id does not exist
when trying to do a simple patient find. Please, any help is appreciated!
I'm writing an API using Spring Boot and Hibernate where my persisted entity objects are also used as DTOs sent to and from the client. This is a simplified version of a typical entity I use:
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#ElementCollection
#CollectionTable(name = "GROUP_STUDENT",
joinColumns = #JoinColumn(name = "GROUP_ID"))
#Column(name="STUDENT_ID")
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}
and this is the associated class:
#Entity
#Table(name = "GROUP")
public class Group {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
private Set<Student> students = new HashSet<>();
// getters and setters
}
As you can see, there is a #ManyToMany association between Student and Group.
Since I send objects like these to the client, I choose to send only the id's of the associations and not the associations themselves. I've solved this using this answer and it works as expected.
The problem is this. When hibernate tries to persist a Student object, it inserts the groups as expected, but it also tries to insert the groupIds into the mapping table GROUP_STUDENT. This will of course fail because of the unique constraint of the mapping table composite id. And it isn't possible to mark the groupIds as insertable = false since it is an #ElementCollection. And I don't think I can use #Formula since I require a Set and not a reduced value.
This can of course be solved by always emptying either the groups of the groupIds before saving or persisting such an entity, but this is extremely risky and easy to forget.
So what I want is basically a read only groupIds in the Student class that loads the data from the GROUP_STUDENT mapping table. Is this possible? I'm grateful for any suggestions and glad to ellaborate on the question if it seems unclear.
I've managed to solve this by making the id-collection #Transient and populating it using #PostLoad:
#Entity
#Table(name = "STUDENT")
public class Student {
#PostLoad
private void postLoad() {
groupIds = groups.stream().map(Group::getId).collect(Collectors.toSet());
}
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Transient
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}
The scenario is the following
I have 2 tables, Company and Activity. A company can have one or more activities. One of these activities is a "primary" activity, and all others become secondary.
To handle this, I created 2 entities (Activity, Company) and a third entity for the join table, which is CompanyActivity
I used this tutorial as a starting point
Below my code (getters and setters omitted)
Company.java
#Entity
#Table(name = "T_COMPANY")
public class Company {
#Id
#Column(name = "COM_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private List<CompanyActivity> activities = new ArrayList<>();
}
Activity.java
#Entity
#Table(name = "T_ACTIVITY")
public class Activity {
#Id
#Column(name = "ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String description;
private boolean availableOnline;
}
CompanyActivity.java
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Adding activities for a company works without a problem. The children collection contains the newly added activities, and there is always one marked as primary as expected.
The problem happens when updating a company.
When I add a new activity, all previous existing activities are persisted again.
When I remove an activity, it is not removed from the table.
I'm using this code to update a company' activities
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
In this code, newActivities have the new activities that should be considered (this collection does not have the previous ones, I just replace them all)
I tried adding orphanRemoval=true to the #OneToMany(cascade = CascadeType.ALL, mappedBy = "company") on Company, but this deletes the activity type when no other company is using it, which is wrong as they should be available always.
Can you please help me sync the activities collection on Company without removing elements from Activity table ?
Thanks a lot!
I solved it. Here are the steps I followed.
First, I changed my Join table entity cascade types as follows
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Then, I added again the "orphanRemoval" property to Company mapping, and changed my CascadeTypes too, as follows
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "empresa", orphanRemoval = true)
private List<CompanyActivity> activities = new ArrayList<>();
With these changes, my mapping works as expected with the same code I used to replace the relationships.
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
Thanks :)
The way you created your entities is not correct. You don't need to create an entity for your join table (CompanyActivity/T_COMPANY_ACTIVITY). Instead you should be using the #JoinTable on your activities entity. Something like below:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
#JoinTable(
name = "T_COMPANY_ACTIVITY",
joinColumns = #JoinColumn(name = "COM_ID"),
inverseJoinColumns = #JoinColumn(name = "ACT_ID")
)
private List<CompanyActivity> activities = new ArrayList<>();
for more detailed explanation on how One-to-Many/Many-to-One with Join tables work here: http://www.codejava.net/frameworks/hibernate/hibernate-one-to-many-association-on-join-table-annotations-example
I have two tables which have Many-to-Many relations which have a JoinTable USER_SERVICES as below.
#Entity
public class User implements Serializable {
#NotNull
#Column(unique=true)
private String username;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_SERVICES",
joinColumns = {#JoinColumn(name = "id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "", referencedColumnName = "name")})
private Set<Services> services;
// Getters and Setters
}
#Entity
public class Services implements Serializable {
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long serviceId;
#NotNull
#Column(unique=true)
private String name;
//Getters and Setters
}
The above code creates a table USER_SERVICES, but I also want to have a Many-to-Many relation on the table USER_SERVICES with another table RATINGS which would result in another table USER_SERVICES_RATINGS. how can I define this relation with Hibernate/JPA annotations?
Bi-Directional Many to Many using user managed join table object (Relatively common)
Commonly used when you want to store extra information on the join object such as the date the relationship was created.
public class Foo{
private UUID fooId;
#OneToMany(mappedBy = "bar", cascade=CascadeType.ALL)
private List<FooBar> bars;
}
public class Bar{
private UUID barId;
#OneToMany(mappedBy = "foo", cascade=CascadeType.ALL)
private List<FooBar> foos;
}
#Entity
#Table(name="FOO_BAR")
public class FooBar{
private UUID fooBarId;
#ManyToOne
#JoinColumn(name = "fooId")
private Foo foo;
#ManyToOne
#JoinColumn(name = "barId")
private Bar bar;
//You can store other objects/fields on this table here.
}
You need to create an explicit UserServices entity and setup the relationship to the Ratings entity per your needs.
Remember that in hibernate you model relationships between entities (i.e. your java objects), not db tables.