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

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")

Related

Is there a way to self-reference an entity using Spring JPA

I'm using Spring JPA and MySQL as the database. I have trouble with self-referencing its own entity.
I know the code below would do self-referencing, but it actually creates a new table to do so.
#Getter
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "member_id")
private Long id;
#ManyToMany
#JoinColumn(name = "followings_id")
private List<Member> followings = new ArrayList<Member>();
#ManyToMany
#JoinColumn(name = "followers_id")
private List<Member> followers = new ArrayList<Member>();
#ManyToMany
#JoinColumn(name = "blocked_id")
private List<Member> blocked = new ArrayList<Member>();
...
}
Question: I'm wondering if I can do this in a single table(which would be the member table) without creating a new table to do many-to-many self-referencing.
It is possible,
Instead of using the #ManyToMany annotation, you can use the #OneToMany and #ManyToOne annotations to create the self-referencing relationship
#Getter
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "member_id")
private Long id;
#OneToMany(mappedBy = "follower")
private List<Follow> followings = new ArrayList<>();
#OneToMany(mappedBy = "following")
private List<Follow> followers = new ArrayList<>();
#OneToMany(mappedBy = "blocker")
private List<Block> blocked = new ArrayList<>();
}
#Entity
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Follow {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "follower_id")
private Member follower;
#ManyToOne
#JoinColumn(name = "following_id")
private Member following;
}
#Entity
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Block {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "blocker_id")
private Member blocker;
#ManyToOne
#JoinColumn(name = "blocked_id")
private Member blocked;
}
Now Follow and Block entities represent the many-to-many relationships between Member entities and follower and following properties in the Follow entity represent the two sides of the many-to-many relationship, and the same is for blocked and blocker.

How to send POST request with Many-to-many relationship in Spring?

I'm trying to add a order with equipment list, here's my entities:
the order entity
#Entity #Table(name = "orders") public class Order extends Ticket{
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
the equipment entity
#Entity #Table(name = "equipments") public class Equipment extends DateAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 30)
private String name;
#NotNull
private Long nbr_piece ;
#OneToMany(mappedBy = "equipment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
and the order_equipment entity
#Entity #Table(name = "order_equipment") public class OrderEquipment extends DateAudit { #Id
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#Id
#ManyToOne
#JoinColumn(name = "equipment_id")
private Equipment equipment;
#NotBlank
#Column(name = "quantity")
private Long quantity;}
here is the add function in the orderController
#PostMapping("/orders")
public Order createOrder(#Valid #RequestBody Order Order){
Order.setObservateurEmail(Order.getObservateurEmail());
Order.setObject(Order.getObject());
Order.setDescription(Order.getDescription());
return orderRepository.save(Order);
}
I have seen a mistakes there, lemme try to help you. Since you issue is not clear, please lemme know if it does/does not work:
You have two bidirectional mappings there:
Order (with ALL cascade) <-> OrderEquipment
Equipment (with ALL cascade) <-> OrderEquipment
You are using #JoinColumn for both of them, even though they are bidirectional. Please take a look at this. You should always use the mappedBy attribute when defining bidirectional relationships.
Now, you are receiving an Order object from a POST request, making changes to 3 attributes and then saving it. Since the mapping between the Order and OrderEquipment have the CascadeType.ALL attribute, any save on the Order object will save all OrderEquipment children associated. If the Order object you are receiving already have OrderEquipment children, your method will also save/update them.
Your POST mapping looks good to me, just take care with your table relationship definitions.
Take a look at this answer to check how a lits of entities should be formatted on a JSON POST.

Register data into Many-to-Many Relation Table

I have 'Course' and 'Student' entities. They have many-to-many relation. So, i have COURSE_STUDENT(contains 'student_id' and 'course_id' columns) table. I want to register students to courses with a button.(For example; a student lists courses and click Register button to register a specific course).
When i want to create new courses, i use courseRepository and courseMapper which comes from JHipster by default.
But i don't have repository and mapper files for COURSE_STUDENT. Because it is not actually a main entity. It is created for many-to-many relation.
How can i register students to courses?
Git repo:https://github.com/canberkizgi/monolithic-mucs
My course entity:
#Entity
#Table(name = "course")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description")
private String description;
#ManyToOne
private Instructor instructor;
#ManyToMany(fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="courses_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="students_id", referencedColumnName="id"))
private Set<Student> students = new HashSet<>();
Student entity:
#Entity
#Table(name = "student")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(unique = true)
private User user;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "students")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Course> courses = new HashSet<>();
For example; Createcourse function with Mapper and Repository
#PostMapping("/courses")
#Timed
public ResponseEntity<CourseDTO> createCourse(#Valid #RequestBody CourseDTO courseDTO) throws URISyntaxException {
log.debug("REST request to save Course : {}", courseDTO);
if (courseDTO.getId() != null) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new course cannot already have an ID")).body(null);
}
Course course = courseMapper.toEntity(courseDTO);
course = courseRepository.save(course);
CourseDTO result = courseMapper.toDto(course);
return ResponseEntity.created(new URI("/api/courses/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
The relationship is owned by the course entity. Thats because on the student side the #ManyToMany annotation has a mappedBy attribute. This means, that the database will reflect the set in the course. You need to add students to that set to save the relationship. That change needs to be done within a transaction.
That being said it would probably be best to follow DDD here. I would create a registerTo method in the student class that would take the course as a parameter. I would then call this.courses.add(course) and course.getStudents().add(this) in that method.

JPA add new entity to collection, after transaction, the entity still has no id

UserInfoEntity user = userRepository.findOne(1L);
ActivityInfoEntity entity = new ActivityInfoEntity();
entity.setUser(user);
user.getActivities().add(entity);
userRepository.save(user);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
System.out.println(entity.getId());// null
System.out.println(user.getActivities().size());// 1
}
});
Even after commit, the entity has no id, but add to collection create a new entity.
If I do activityRepository.save(entity); will add double to collectionHHH-6776.I need entity.getId() in this function to do something else.If I return entity, the returned entity will have an id, what's wrong ?
EDIT
saveAndFlush still null
Spring-boot 1.3.3
EDIT
The service is annotated with #Transactional.
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "activity_info")
#Where(clause = "is_del = 0")
public class ActivityInfoEntity{
#Id
#GeneratedValue
private Long id;
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Fetch(FetchMode.SUBSELECT)
#OneToMany(mappedBy = "activity", cascade = CascadeType.ALL, orphanRemoval = true)
List<UserInfoEntity> users;
}
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "user_info")
#Where(clause = "is_del = 0")
public class UserInfoEntity{
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "activity_id", nullable = false, updatable = false)
private ActivityInfoEntity activity;
}
EDIT
HHH-6776 Hibernate inserts duplicates into #OneToMany collection

HIbernate + JPA OneToMany Lazy loading not working if no foreign key specified in the db

Hibernate lazy loading is not working in my code. It loads the entire data even it is specified as FetchType LAZY
#Transactional(propagation = Propagation.NEVER)
public OrderItem getItem(String itemId) throws Exception {
OrderItem item = itemDao.find(OrderItem.class, Integer.parseInt(itemId));
if (item == null) {
throw new Exception(502, "We are unable to load item for #" + itemId);
}
return item;
}
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name = "id_order_detail")
#Fetch(value= FetchMode.JOIN)
#JsonManagedReference
private Set<OrderItemStateChangeEntry> itemStateHistory;
I could not able to lazy load the contents. There is no foreign key constraint set in the db. And its not possible to set as the many parent data not present in the system.
Can somebody help me on this
Update
Added my class and reference. But lazy load work
#Entity
#Table(name = "ps_orders")
#AttributeOverrides({
#AttributeOverride(name="id",column=#Column(name="id_order")),
#AttributeOverride(name="createTime",column=#Column(name="date_add")),
#AttributeOverride(name="updateTime",column=#Column(name="date_upd"))
})
public class Order extends BaseEntity{
#Column(name = "id_carrier")
private Integer carrier = 0;
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(fetch = FetchType.LAZY,cascade={CascadeType.PERSIST, CascadeType.MERGE}, mappedBy="order")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private Set<OrderStateChangeEntry> orderHistory;
//Getters and Setters
}
#Entity
#Table(name = "ps_order_history")
#EnableBouncerProfile
public class OrderStateChangeEntry implements java.io.Serializable{
public OrderStateChangeEntry(){}
public OrderStateChangeEntry(Order order){
this.order = order;
}
#Id
#Column(name = "id_order_history")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="id_order", nullable=false)
#JsonBackReference
private Order order;
//Getters and Setters
}
It is because of your
#Fetch(value= FetchMode.JOIN)
it disables the lazy loading ...
As you specify the fetch mode in your #OnetoMany relationship, i would say that you can simply remove that line above.

Resources