I am currently working on a school project that allows the user to add audio courses to their library and listen to the lessons of each Course. I'll try to explain the problem with an example:
When I create a new course entity with my POST-Request, there are no lessons added in yet.
After I created the course, I add lessons to the course also via POST-Request.
So, my database (PostgreSQL) would have one entry in the course table, two entries in the lesson table
and a reference table to connect those two (hibernate generated)
The problem comes when I try to add the course to the user. It doesn't throw a warning or expectation when it adds the course and the result after the merge of the user also delivers the expected result:
HTTP/1.1 200 OK
Content-Length: 674
Content-Type: application/json
{
"courses": [
{
"description": "A memoir by the creator of NIKE",
"id": 2,
"lessons": [
{
"audioUrl": "http://localhost:8080",
"description": "A Tutorial on how to create your own Audally's",
"duration": "00:12:54",
"id": 1,
"name": "TutorialNr2"
},
{
"audioUrl": "http://localhost:8080",
"description": "A Tutorial on how to create your own Audally's",
"duration": "00:12:54",
"id": 2,
"name": "TutorialNr1"
}
],
"name": "Shoe Dog - Phil Knight",
"pictureUrl": "https://images.unsplash.com/photo-1556906781-9a412961c28c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80"
}
],
"email": "[email protected]",
"id": 2,
"password": "password",
"subscriptions": [],
"userName": "jane"
}
Response code: 200 (OK); Time: 80ms; Content length: 674 bytes
But after I make another request to get everything of the user, I get this instead:
{
"courses": [
{
"description": "A memoir by the creator of NIKE",
"id": 2,
"lessons": [
{
"audioUrl": "http://localhost:8080",
"description": "A Tutorial on how to create your own Audally's",
"duration": "00:12:54",
"id": 1,
"name": "TutorialNr2"
},
{
"audioUrl": "http://localhost:8080",
"description": "A Tutorial on how to create your own Audally's",
"duration": "00:12:54",
"id": 2,
"name": "TutorialNr1"
}
],
"name": "Shoe Dog - Phil Knight",
"pictureUrl": "https://images.unsplash.com/photo-1556906781-9a412961c28c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80"
},
{
"description": "A memoir by the creator of NIKE",
"id": 2,
"lessons": [
{
"audioUrl": "http://localhost:8080",
"description": "A Tutorial on how to create your own Audally's",
"duration": "00:12:54",
"id": 1,
"name": "TutorialNr2"
},
{
"audioUrl": "http://localhost:8080",
"description": "A Tutorial on how to create your own Audally's",
"duration": "00:12:54",
"id": 2,
"name": "TutorialNr1"
}
],
"name": "Shoe Dog - Phil Knight",
"pictureUrl": "https://images.unsplash.com/photo-1556906781-9a412961c28c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80"
}
],
"email": "[email protected]",
"id": 2,
"password": "password",
"subscriptions": [],
"userName": "jane"
}
Response code: 200 (OK); Time: 50ms; Content length: 1247 bytes
It just displays the course times the number of lessons that the course has. in this case two lessons.
if I were to add another lesson then it would go on like that even though there are no multiple references of this in the database.
Here's my code that I use:
@Path("/courses")
@Produces(APPLICATION_JSON)
@Transactional
@ApplicationScoped
public class CourseResource {
@Inject
CourseRepository courseRepository;
@GET
public List<Course> getAll(){
return courseRepository.findAll().list();
}
@POST
public Response addCourse(Course course){
if(courseRepository.findAll().stream().anyMatch(course1 ->
course1.name.equals(course.name) &&
course1.description.equals(course.description) &&
course1.lessons.equals(course.lessons))){
return Response
.status(406,"Course already exists!")
.build();
}
Course entry = new Course();
entry.copyProperties(course);
courseRepository.persist(entry);
return Response.ok(entry).build();
}
}
@Path("/lessons")
@Produces(APPLICATION_JSON)
@Transactional
@ApplicationScoped
public class LessonResource {
@Inject
LessonRepository lessonRepository;
@Inject
CourseRepository courseRepository;
@POST
@Path("addLessonsToCourse/{cid}")
public Response addLessonToCourse(@PathParam("cid") Long cid,Lesson[] lessons){
Course read = courseRepository.findById(cid);
if(read == null)return Response.noContent().build();
Arrays.stream(lessons)
.forEach(l -> {
Lesson created = new Lesson();
created.copyProperties(l);
lessonRepository.persist(created);
read.addLessons(lessonRepository.findById(created.id));
});
courseRepository.getEntityManager().merge(read);
return Response.ok(courseRepository.findById(cid)).build();
}
}
package com.audally.backend.boundary;
import io.quarkus.security.identity.SecurityIdentity;
import org.jboss.resteasy.annotations.cache.NoCache;
import javax.annotation.security.RolesAllowed;
import com.audally.backend.control.CourseRepository;
import com.audally.backend.control.UserRepository;
import com.audally.backend.entity.Course;
import com.audally.backend.entity.User;
import org.jose4j.json.internal.json_simple.JSONObject;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.json.bind.annotation.JsonbTransient;
import javax.transaction.Transactional;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@Path("/users")
@Produces(APPLICATION_JSON)
@Transactional
@ApplicationScoped
public class UserResource {
@Inject
UserRepository userRepository;
@Inject
CourseRepository courseRepository;
@JsonbTransient
private JSONObject businessuser;
@GET
@Path("/{UserId}")
public Response getUser(@PathParam("UserId") Long uid){
return Response.ok(userRepository.findById(uid)).build();
}
@GET
@Path("/{UserId}/courses")
public Response getCoursesOfUser(@PathParam("UserId") Long uid){
User user = userRepository.findById(uid);
if(user == null){
return Response
.status(202,"Course already exists in the User!")
.build();
}
businessuser = new JSONObject();
businessuser.merge("courses",user.courses.stream().filter(distinctByKey(course -> course.name))
.collect(Collectors.toList()),(o, o2) -> o = o2);
return Response.ok(businessuser.get("courses")).build();
}
@POST
@Path("{UserId}/courses/{CourseId}")
public Response addCourseToUser(@PathParam("UserId") Long uid
,@PathParam("CourseId") Long cid){
User user = userRepository.findById(uid);
Course course = courseRepository.findById(cid);
if(user.courses.contains(course)){
return Response
.status(406,"Course already exists in the User!")
.build();
}
if(user == null){
return Response
.status(204,"User was not found!")
.build();
}
else if(course == null){
return Response
.status(204,"Course was not found!")
.build();
}
user.addCourses(course);
userRepository.getEntityManager().merge(user);
return Response.ok(userRepository.findById(uid)).build();
}
@POST
@Transactional
@Path("addUser")
public Response addUser(User user){
User entry = new User();
if(userRepository.find("email",user.email).count() == 1){
return Response
.status(406,"User email already exists!")
.build();
}
entry.copyProperties(user);
userRepository.persist(entry);
return Response.ok(entry).build();
}
public static <T> Predicate<T> distinctByKey(
Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
I hotfixed the problem by throwing a distinct (in the method: getCoursesOfUser) to every course that is the same in there but I just want to know if there is a better way to fix this problem?
For reference:
All Repositories implement PanacheRepositoryBase<Enitity,Long> and have no further code added to them.
package com.audally.backend.entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import org.hibernate.validator.constraints.URL;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.transaction.Transactional;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.persistence.*;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "courses",schema = "audally")
public class