Not able fetch parent object inside a child object - spring-boot

I have established a one to many relationships which are working fine but there is some loophole
I.e
I'm able to fetch the parent and list of children associated with the parent (Parent by parent Id).
I'm able to fetch a child but not able to fetch the parent associated with that child not even a single field.
Inside Company Model class
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#EqualsAndHashCode.Exclude
#JsonIgnoreProperties("company")
private Set<User> users = new HashSet<User>();
Inside User Model
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "company_id")
#JsonBackReference
#EqualsAndHashCode.Exclude
private Company company;
on top of company class
#Data
#Entity
#NoArgsConstructor
#EqualsAndHashCode(callSuper = true)
on top of the User class
#Data
#Entity
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)

Related

How to write a query to find which seats are booked and which are not

I am using Spring JPA
I have 3 entities
1- Event
2- Seat
3- Reservation
Event has one to many relation with Seat and Reservation
Reservation has a one to many relation with Seat
(Event will have the seats created after its creation and then it will be assigned to the particular event)
(When users make reservations, each reservation can have multiple seats for the particular event)
What I've done so far:
The Event class
#Entity
#Data
#NoArgsConstructor #AllArgsConstructor
public class Event {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#OneToMany(mappedBy = "event", fetch = FetchType.LAZY)
private List<Seat> seats;
#OneToMany(mappedBy = "event", fetch = FetchType.LAZY)
private List<Reservation> reservations;
}
The Seat class
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Seat {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
private String seatCode;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "event_seats")
private Event event;
#ManyToOne
#JoinTable(name = "seat_reservation")
private Reservation reservation;
}
The Reservation class
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Reservation {
#Id
#GeneratedValue(strategy = AUTO)
private Long id;
#ManyToOne
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "event_reservations")
private Event event;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "reservation")
private Collection<Seat> seats;
}
**Is the tables and relations design correct?
When I create a reservation and add seats to it, the linking table (reservation_seats) doesn't get updated.
And how to write a query to determine which Event seats are booked and which are not?**
There is more than one way to do this using JPA depending of your knowledge.
Options Like Native Query, JPQL or JPA Criteria.
The native query would be like this:
With reservation:
SELECT * FROM seat s WHERE s.id IN (SELECT seat_id FROM seat_reservation)
With no reservation:
SELECT * FROM seat s WHERE s.id NOT IN (SELECT seat_id FROM seat_reservation)
There may be other ways to do this, but I think it's enough.

Hibernate mapping user relation to entities

Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.

OneToMany does not return values saved from other entity

I have entity structure:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements= new ArrayList<>();
}
#Entity
#Table(name = "user_agreements")
public class UserAgreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "agreement_id")
private Agreement agreement;
}
#Entity
#Table(name = "agreements")
public class Agreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agreement", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements = new ArrayList<>();
}
I am using Spring Boot with JpaRepository. When I use AgreementRepository extends JpaRepository<Agreement, Long> to save Agreement and related UserAgreement, it works well and cascades necessary fields to DB:
agreement.getUserAgreements().add(new UserAgreement(user, agreement, status));
agreementRepository.save(agreement);
However, after save, if try to retrieve user.getActiveUserAgreements(), I get empty list. It does not refresh.
How to force User entity to get List<UserAgreement> which was saved from other side?
From the Wikibooks: OneToMany
The relationship is bi-directional so, as the application updates one
side of the relationship, the other side should also get updated, and
be in sync. In JPA, as in Java in general it is the responsibility of
the application, or the object model to maintain relationships. If
your application adds to one side of a relationship, then it must add
to the other side.
That means you need to assign the UserAgreement to the User when you create the relation.
It looks like many-to-many association. You might probably drop UserAgreement class. Anyway, to support it you have to write helper methods addAgreement(), removeAgreement() etc. See more details here https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Hibernate insert causes update of another table

I have a model that looks like this:
#Entity
#Table(name = "A")
class A {
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "A_categories", joinColumns = {
#JoinColumn(name = "A_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "category_id",
nullable = false, updatable = false) })
private List<Category> categories;
}
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id")
#GeneratedValue
private Integer id;
#Column(name = "category_name")
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private List<A> a;
}
So there is a many-to-many relationship between A and Category. Now categories are static, and cannot be changed by a user. From the UI, the user will try to save an entity A, and each can have one or more categories. So the JSON that comes back looks a little like this:
{A: {categories: [{id: 1}, {id: 2}, {id: 3}]}}
Now when I try to save this A object (after jackson has unmarshalled to java), I just want entries to be made in the join table, A_categories, for each category the new entity has.
However, the Category entity itself also gets updated. So if you notice, the JSON does not have any category_name, and so the database entry for each Category will also get updated to a null entry for the name.
How can I prevent this from happening?
Two different approaches:
1) Set managed categories before merging.
a.setCategories(readAllByIds(a.getCategories()))
private Collection<Category> readAllByIds(Collection<Category> categories) {
Collection<Category> result = new ArrayList();
for (Category category : categories) {
result.add(entityManager.getReference(Category.class, category.getId()));
}
return result;
}
EntityManager.getReference returns proxy, so the additional benefit is that no database round trips are executed for reading the associated categories.
With this solution you are not merging the deserialized categories into the persistence context, thus Hibernate will not synchronize their state with the database.
2) Do not cascade any operations from A to categories (remove cascade attribute).
This way, neither PERSIST nor MERGE will be cascaded and Hibernate will just use ids of the detached Category instances to store the data into the relationship table.
Sidenote: Generally, cascading REMOVE or ALL in a many-to-many association makes no sense (if you remove an A you probably don't want to remove all the categories it belongs to).
#Column has the attributes insertable and updatable. You can set them to false:
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id", insertable=false, updatable=false)
#GeneratedValue
private Integer id;
#Column(name = "category_name", insertable=false, updatable=false)
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private List<A> a;
}
You can try adding this
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id")
#GeneratedValue
private Integer id;
#Column(name = "category_name")
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories", cascade=CascadeType.DETACH)
private List<A> a;
}
with the cascade.DETACH should not save changes when you save A entity, but let me know if is not working to make an example modifying the ManyToMany relationship with this DETACH action

How to load OneToMany Collections data in response using FetchType.LAZY?

I have created a sample jHipster sample app( url: http://jhipster.github.io/creating_an_app.html), using entity sub-generator I have created an Event entity which has OneToMany relationship with EventImages, EventTickets and EventQuestions when I retrieve all(app running in local machine, the api url: http://127.0.0.1:8080/api/events ) events I couldn't find EventImages, EventTickets and EventQuestions data in response.
Event Entity
#Entity
#Table(name = "JHI_EVENT")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Event implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/* other fields */
#OneToMany(mappedBy = "event")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private Set<EventTicket> eventTickets = new HashSet<>();
#OneToMany(mappedBy = "event")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private Set<EventImage> eventImages = new HashSet<>();
#OneToMany(mappedBy = "event")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private Set<EventQuestion> eventQuestions = new HashSet<>();
/* getter and setters */
}
EventImages entity
#Entity
#Table(name = "JHI_EVENTIMAGE")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class EventImage implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "image_url")
private String imageUrl;
#ManyToOne
private Event event;
/* getters and setters */
}
similarly, EventTickets and EventQuestions entities.
After some research i found that i need to remove #JsonIgnore annotation to load OneToMany Collections data using lazy fetch, the response i got is null for EventImage, EventTicket and EventQuestions, as below.
[ {
"id": 1,
"title": "First Event",
"eventVenue": "xyz",
"startDate": "2015-05-28T10:10:00Z",
"endDate": "2015-06-20T10:10:00Z",
"eventTickets": null,
"eventImages": null,
"eventQuestions": null
} ]
Then I found I need use #JsonManagedReference and #JsonBackReference on parent/child relation, but need to use fetch = Fetch.EAGAR (I want load OneToMany Collections when I set FetchType.LAZY which is default, as an when Event entity is called).
Event entity when I used #JsonManagedReference
#Entity
#Table(name = "JHI_EVENT")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Event implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/* other fields */
#OneToMany(mappedBy = "event", fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonManagedReference
private Set<EventTicket> eventTickets = new HashSet<>();
#OneToMany(mappedBy = "event", fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonManagedReference
private Set<EventImage> eventImages = new HashSet<>();
#OneToMany(mappedBy = "event", fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonManagedReference
private Set<EventQuestion> eventQuestions = new HashSet<>();
/* getter and setters */
}
EventImage entity when I used #JsonBackReference
#Entity
#Table(name = "JHI_EVENTIMAGE")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class EventImage implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "image_url")
private String imageUrl;
#ManyToOne
#JsonBackReference
private Event event;
/* getters and setters */
}
How to load OneToMany Collections lazily i.e. EventImages, EventTickets and EventQuestions in response when Event entity requested i.e http://127.0.0.1:8080/api/events REST call is made.
Thanks
I don't actually understand your question, but I try to explain how JPA works.
As you mentioned, using JPA you can either load collections LAZILY or EAGERLY.
LAZY means that you do not load the collection from the beginning. The collection is only loaded when you access the collection (this only works in the same transaction where the entity is loaded or attached).
EAGER means that the collection is loaded from the beginning (as the entity itself is loaded).
So if you want to provide the collection by the REST service, then you have to load the collections during the transaction.
This can be done in several ways:
One way is to define the FetchType of the collection to EAGER
Another way is to LAZY load the collection and after loading the entity, access the collection (for example by calling size() -> event.getEventImages().size();)
Another way is to load the entity and the collection with a JPQL-Query (SELECT e FROM Event JOIN FETCH e.eventImages ...)
There are even more ways to achieve this depending on the JPA implementation you are using
So, if I understood your question rigth, then you could Annotate your Spring-Data-DAO-Find-Method with
#Query("SELECT e FROM Event JOIN FETCH e.eventTickets, JOIN FETCH e.eventImages, JOIN FETCH e.eventQuestions")

Resources