I have this entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Data
#Entity
#Table(name = "visits")
public class Visit {
#EqualsAndHashCode.Include
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull
#Column(nullable = false, updatable = false)
private long id;
#Column
private LocalDate date;
#Column
private LocalTime startTime;
#Column
private LocalTime endTime;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "client_id")
private Client client;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
private Employee employee;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "services_booked",
joinColumns = {#JoinColumn(name = "visit_id")},
inverseJoinColumns = {#JoinColumn(name = "service_id")}
)
private Set<Service> servicesBooked = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "visit")
private Set<TimeSlot> timeSlots = new HashSet<>();
}
My controller performs delete action on service bean into transaction:
#Transactional
#DeleteMapping("/{id}")
public ResponseEntity<DeleteVisitResponse> deleteVisit(#PathVariable("id") long visitId,
#RequestAttribute(USER_AUTH) UserAuthDto userAuthDto) {
// some logic
Optional<Visit> visit = visitService.findVisitById(visitId);
// check isPresent via Optional.map
visitService.deleteVisit(visit.get());
// constructing the response
}
And service bean just deletes it:
#Override
public void deleteVisit(#NonNull Visit visit) {
visitRepository.delete(visit);
}
But actually it does not delete it. It performs sequential selects to resolve chained entities. Here are log records of that deletion: https://gist.github.com/bvn13/906582ad39720e033c24ddd6f59f906c
That's all. Why it cannot perform deleting operation?
Related
I created two simple entities for trying out the java persistence manytomany mapping. But whatever I try, the jointable won't be populated with a mapping and remains empty.
UserClass:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(targetEntity = Order.class ,fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "users_orders",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "order_id", referencedColumnName = "id")
)
#JsonIgnoreProperties(value = "orderUsers")
private Set<Order> userOrders = new HashSet<>();
}
OrderClass:
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(mappedBy = "userOrders", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "userOrders")
private Set<User> orderUsers = new HashSet<>();
}
I added Getter/Setter/Constructor via Lombok.
Create and save an user. Create an order, add the user and save it. But still the jointable remains empty.
Any ideas?
I have next classes:
#Entity
#Table
public class Lesson implements ModelEntity {
#Id
#Column(name = "lesson_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "course_id")
private Course course;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lesson_type_id")
private LessonType lessonType;
private LocalDate date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "time_slot_id")
private TimeSlot timeSlot;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "auditorium_id")
private Auditorium auditorium;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "lesson_teacher", joinColumns = #JoinColumn(name = "lesson_id"), inverseJoinColumns = #JoinColumn(name = "person_id"))
private Set<Teacher> teachers = new HashSet<>();;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "lesson_group", joinColumns = #JoinColumn(name = "lesson_id"), inverseJoinColumns = #JoinColumn(name = "group_id"))
private Set<Group> groups = new HashSet<>();
}
#Entity
#Table(name = "groups")
public class Group implements ModelEntity {
#Id
#Column(name = "group_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "group_name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "faculty_id")
private Faculty faculty;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "group")
private List<Student> students;
}
#Entity
#Table
public class TimeSlot implements ModelEntity {
#Id
#Column(name = "time_slot_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "time_slot_number")
private Integer number;
#Column(name = "time_slot_name")
private String name;
#Column(name = "time_slot_start")
private LocalTime startTime;
#Column(name = "time_slot_end")
private LocalTime endTime;
}
I wrote method, that find all Groups_id by Date and TimeSlot_id not connected to Lesson with CriteriaBuilder API, it works perfect:
#Override
public Set<Integer> getBusyGroupsId(int lessonId, LocalDate date, int timeSlotId) {
logger.debug("getBusyGroupsId() with agruments {}, {}, {}.", lessonId, date, timeSlotId);
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> query = criteriaBuilder.createQuery(Integer.class);
Root<Lesson> root = query.from(Lesson.class);
List<Predicate> predicates = new ArrayList<>();
Join<Lesson, TimeSlot> timeSlotJoin = root.join("timeSlot", JoinType.LEFT);
predicates.add(criteriaBuilder.equal(timeSlotJoin.get("id"), timeSlotId));
predicates.add(criteriaBuilder.equal(root.get("date"), date));
if (nonNull(lessonId)) {
predicates.add(criteriaBuilder.notEqual(root.get("id"), lessonId));
}
query.where(predicates.toArray(new Predicate[] {}));
SetJoin<Lesson, Group> joinGroup = root.joinSet("groups");
query.multiselect(joinGroup.get("id"));
TypedQuery<Integer> result = entityManager.createQuery(query);
return result.getResultStream().collect(Collectors.toSet());
}
But after that I think- what about JPA, can it be easier?
I tried something like that, but it doesnt work:
public Set<Integer> findGroupIdByIdNotAndDateEqualsAndTimeSlotIdEquals(Integer lessonId, LocalDate date, Integer timeSlotId);
How to fix it?
Also I stacked with writing method with JPA that should find all Lesson by Group_id and Date(or startDate-endDate) and sort it: first by date, second- by TimeSlot_number.
Can it be written with JPA?
Thanks in advance.
Don't throw stones, I'm just getting to know Spring JPA.
I am using spring-boot-starter-data-jpa 1.5.1.RELEASE which internally uses hibernate-core 5.0.11.Final
My entity looks like this:
AreaDto
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH)
#JoinColumn(name = "AREA_ROWID")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FESTIVAL", joinColumns = {
#JoinColumn(name = "AREA_ROWID", referencedColumnName = "AREA_ROWID")}, inverseJoinColumns = {
#JoinColumn(name = "FESTIVAL_ROWID", referencedColumnName = "FESTIVAL_ROWID")})
private Collection<ActionDto> festivalActions;
}
FestivalDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
ActionDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}
I'm trying to make sense of the below ideas:
What is the strategy used by hibernate to decide on the festival_rowid (or festival_row ids) used to get all the associated action? How will hibernate generated SQL query vary if i change festivalActions fetch strategies between LAZY and EAGER? I know about proxying, collection proxying and all, my question is specific to how those sql is generated and how it may have an impact on deciding the value of bind parameter.
Is my mapping accurate or should I be using a multimap for this relationship since an area could have multiple festival and each festival could have multiple actions
Background:
I am getting below error which goes away if I change the fetch type from LAZY to EAGER. Hoping to understand the behaviour for gaining some confidence in the fix. I have read SO and error
org.hibernate.HibernateException: More than one row with the given identifier was found: data.dto.ActionDto#280856b5
This mapping does not make much sense. You can't map festivalActions this way because there is no way to persist the state properly through such a mapping. Also festival in AreaDto should be mapped by the area in FestivalDto. Try the following instead:
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
public Collection<ActionDto> getFestivalActions() {
return festival.stream().flatMap(f -> f.actions.stream()).collect(Collectors.toList());
}
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}
I have following scenario: There are companies and employees. Each company has a set of employees. Each employee can work for several companies. So I implemented following relationships:
Company.class:
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private List<Employee> employees;
Employee.class:
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "employee_id") , inverseJoinColumns = #JoinColumn(name = "company_id") )
#ManyToMany(fetch = FetchType.LAZY)
private List<Company> companies;
Obviously, to work for several companies, each employee should have several not overlapping schedules assigned for each company he or she works.
Also, there should be a list of schedules for each combination Company-Employee, as sometimes old schedule expires, and new schedule becomes effective.
So I also have Schedule.class, which is supposed to have child to parent #ManyToOne relationships both to Company and Employee, and should work following way: each Schedule, and thus, List<Schedule> should correspond to exactly one combination of Company and Employee instances.
How to implement this relationship?
Update 1
I only have in mind adding #OneToMany Schedule relationship to each Company and Employee, but then I need to put instances of Schedule both to Company and Employee each time, and this way just don't look right, also it's not obvious for me now how to fetch it back.
So any help will be appreciated.
This post was updated to show real-life scenario I have, not just generic Entity1, Entity2, Entity3 names for classes.
Update 2
I accepted the answer, but I cannot use it if Schedule contain Lists.
According to my plan, Schedule should contain List<Vacation> to know the set of Vacations over a year, and List of Days, each of which shows start of particular week day, break, and end of this day. Those Days are also unique for each Schedule instance.
It was supposed to be something like below, but obviously now I don't have schedule_id, so how to connect those lists to Schedule?
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "schedule_id")
private List<Vacation> vacations;
#JoinTable(name = "schedule_week", joinColumns = #JoinColumn(name = "schedule_id") , inverseJoinColumns = #JoinColumn(name = "day_id") )
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Day> week;
How to include those lists right?
I would like to suggest the following solution.
An embeddable class that contains the Company and Employee for a particular schedule.
#Embeddable
public class ScheduleOwner implements Serializable{
#MapsId("id")
#ManyToOne(cascade = CascadeType.ALL)
Company c;
#MapsId("id")
#ManyToOne(cascade = CascadeType.ALL)
Employee e;
}
The Schedule class is embedding a ScheduleOwner instance.
#Entity
public class Schedule {
#EmbeddedId
ScheduleOwner owner;
String description;
}
The Company and Employee classes(no change done to them)
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private List<Employee> employees;
}
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "employee_id") , inverseJoinColumns = #JoinColumn(name = "company_id") )
#ManyToMany(fetch = FetchType.LAZY)
private List<Company> companies;
}
UPDATE 1
Below is how you could save and fetch results.
Employee e1 = new Employee();
Company c1 = new Company();
c1.employees.add(e1);
e1.companies.add(c1);
ScheduleOwner so = new ScheduleOwner();
so.c = c1;
so.e = e1;
Schedule s = new Schedule();
s.owner = so;
session.save(c1);
session.save(e1);
session.save(s);
// below query will fetch from schedule, where company id = 9
Schedule ss = (Schedule) session.createQuery("From Schedule sh where sh.owner.c.id = 9").uniqueResult();
UPDATE 2
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id", referencedColumnName="id")
, inverseJoinColumns = #JoinColumn(name = "employee_id", referencedColumnName="id"))
#ManyToMany(fetch = FetchType.LAZY)
List<Employee> employees = new ArrayList<>();
String name;
}
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "employees")
List<Company> companies = new ArrayList<>();
String name;
}
#Entity
public class Schedule {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int schedule_id;
#ManyToOne
#JoinColumn(name = "company_id", insertable = false, updatable = false)
private Company company;
#ManyToOne
#JoinColumn(name = "employee_id", insertable = false, updatable = false)
private Employee employee;
String description;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "schedule")
List<Vacation> vacations;
}
#Entity
public class Vacation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int vacation_id;
#ManyToOne
#JoinColumn(name = "schedule_id" )
Schedule schedule;
#OneToMany(mappedBy = "vacation")
List<Day> days;
}
Day entity directly relates to Vacation. Not to Schedule.
#Entity
public class Day {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name = "vacation_id")
Vacation vacation;
}
Hope this helps.
First of all, thanks for be interested in this question.
The scenario is like that: there is an entity Usuario (user) which has several Role. When I delete an User, all Roles related are deleted too.
The code for Role is:
#Entity
#Table(name = "marte_role")
#XmlRootElement
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String nombre;
#ManyToMany(
fetch = FetchType.EAGER,
targetEntity = Usuario.class,
cascade = { CascadeType.ALL })
#JoinTable(
name = "marte_usuario_role",
joinColumns = { #JoinColumn(name = "role_id") },
inverseJoinColumns = { #JoinColumn(name = "usuario_id") })
#JsonIgnore
private List<Usuario> users = new ArrayList<Usuario>();
... Getters/setters/builders...
And the code for Usuario is:
#Entity
#Table(name = "marte_usuario")
#XmlRootElement
public class Usuario implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
private String password;
private String email;
private boolean enabled;
#ManyToMany(
fetch = FetchType.EAGER
, targetEntity = Role.class
, cascade = { CascadeType.ALL })
#JoinTable(
name = "marte_usuario_role"
, joinColumns = { #JoinColumn(name = "usuario_id") }
, inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<Role>();
#Transient
private int numRoles;
It seems to me that is related with CascadeType.ALL. I've tested with CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE, instead of CascadeType.ALL and then the entity is NOT deleted.
Does anyone know what I am doing wrong?
Thanks in advance for your answers.
CascadeType.ALL include also CascadeType.REMOVE, that's why your entities are removed with this annotation.
You're not doing anything wrong. You specify CascadeType.ALL, which means all operations, including delete, are cascaded to related entities. If you don't want that to happen, don't use CascadeType.ALL.
Solved!
The answers provided are both correct: remove CascadeType.ALL, but just in the Role entity. With this change is possible to remove an Usuario, without deleting all the Role related.
#Entity
#Table(name = "marte_role")
#XmlRootElement
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String nombre;
#ManyToMany(
fetch = FetchType.EAGER,
targetEntity = Usuario.class
)
#JoinTable(
name = "marte_usuario_role",
joinColumns = { #JoinColumn(name = "role_id") },
inverseJoinColumns = { #JoinColumn(name = "usuario_id") })
#JsonIgnore
private List<Usuario> users = new ArrayList<Usuario>();
...
Thanks!