Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring: Save object with foreign keys with a POST request

I'm kinda new to Spring (REST) and I'm building a simple REST webservice. I use a RestController to map HTTP requests. I use this simple method for accepting a POST request:

@RequestMapping(value = "/grade/create", method = RequestMethod.POST)
public ResponseEntity<Grade> createGrade(@RequestBody Grade grade)
{
    dao.createGrade(grade);

    return new ResponseEntity<Grade>(grade, HttpStatus.OK);
}

dao in this case is a class with a @Repository annotation. A request shou;d look like this:

{
    "gradeType": "REGULAR",
    "grade": "10",
    "passed": 1,
    "userId": 1,
    "exam_id": 1,
    "user_id": 3
}

The problem is, that Grade has 2 foreign keys, for user and exam. I want to be able to just pass the ID's of the foreign entities and let Hibernate take care of the rest. However, currently I'm getting this as a response:

{
  "timestamp": 1484758525821,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.springframework.dao.InvalidDataAccessResourceUsageException",
  "message": "could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement",
  "path": "/grade/create"
}

How can I solve this? I've heard something about a JpaRepository, can I use that to accomplish this?

My Grade modal is as follows:

@Entity
@Table(name = "grades")
@NamedQueries(value = {
        @NamedQuery(name = "Grade.get", query = "SELECT c FROM Grade c WHERE id = :id"),
        @NamedQuery(name = "Grade.getAll", query = "SELECT c FROM Grade c"),
        @NamedQuery(name = "Grade.getAllByUser", query = "SELECT g FROM Grade g INNER JOIN Exam e ON g.exam.id = e.id INNER JOIN Course c ON e.course.id = c.id WHERE g.user.id = :id"),

})
public class Grade {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "type")
    private String gradeType;

    @Column(name = "grade")
    private String grade;

    @Column(name = "passed")
    private int passed;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "exam_id")
    private Exam exam;

    private int examId;

    private int userId;

    public Grade(int id, String gradeType, String grade, int passed, User user, Exam exam)
    {
        setId(id);
        setGradeType(gradeType);
        setGrade(grade);
        setPassed(passed);
        setUser(user);
        setExam(exam);
    }

    public Grade() {
    }

And my repository...

@Repository
public class GradeDao {

    @PersistenceContext
    private EntityManager em;

    @Transactional
    public List<Grade> getallGrades() {
        return em.createNamedQuery("Grade.getAll", Grade.class)
                .getResultList();
    }

    public List<Grade> getLimitedGradesByUser(int limit, int user_id) {
        return em.createNamedQuery("Grade.getAllByUser", Grade.class)
                .setParameter("id", user_id)
                .setMaxResults(limit)
                .getResultList();
    }

    @Transactional
    public Grade getGrade(int id) {
        return em.createNamedQuery("Grade.get", Grade.class).setParameter("id", id).getSingleResult();
    }

    @Transactional
    public Grade createGrade(Grade grade) {
        grade = em.merge(grade);
        em.persist(grade);

        return grade;
    }
}

Thanks

like image 631
Roemer Avatar asked Jan 18 '17 16:01

Roemer


1 Answers

How can I solve this? I've heard something about a JpaRepository, can I use that to accomplish this?

Yes, you can do this with JpaRepository. You can start adding an Interface
public interface GradeRepository extends PagingAndSortingRepository<Grade, Long>, same for User and Exam
Then when you POST your Grade in your controller(I would suggest a Service for that)
1. Create a new Grade()
2. Use setters to link your User and Exam by @Autowire-ing your UserRepository and ExamRepository and calling findById
3. Then fetch your other fields from your POST using setters
4. Call .save(grade) from your @Autowired GradeRepository

Please note that User and Exam should already exist in the DB to be able link them

Also I would highly suggest you to never pass direct Entities via HTTP instead use DTOs and in your Grade json never pass the exam id. Try finding the exam by other fields, not the table id (ex: findByName)

I implemented a fully functioning SpringBoot App with JPA and Security, you might have a look https://github.com/hodispk/internship

like image 76
Alexandru Hodis Avatar answered Nov 15 '22 04:11

Alexandru Hodis