marcelorocks
marcelorocks

Reputation: 2035

Spring + Hibernate RESTful Resource

I am new to Spring, and I am trying to create a RESTful resource to use on my API. So far, I was able to list elements (GET /people.json), show a specific element (GET /people/{id}.json), create a new element (POST /people.json), and delete an element (DELETE /people/{id}.json). My last step is to work on the update method (PUT /people/{id}.json, but I have no idea how to do it yet.

Here is the code I have for my controller:

package com.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.model.Person;
import com.example.service.PersonService;

import java.util.Map;

@Controller
public class PersonController {

    @Autowired
    private PersonService personService;

    @ResponseBody
    @RequestMapping(value = "/people.json", method = RequestMethod.GET)
    public String all(Map<String, Object> map) {
        return "{\"people\": " + personService.all() + "}";
    }

    @ResponseBody
    @RequestMapping(value = "/people/{personId}.json", method = RequestMethod.GET)
    public String show(@PathVariable("personId") Integer personId) {
        Person person = personService.find(personId);
        return "{\"person\": "+person+"}";
    }


    @ResponseBody
    @RequestMapping(value = "/people.json", method = RequestMethod.POST)
    public String create(@ModelAttribute("person") Person person, BindingResult result) {
        personService.create(person);
        return "{\"person\": "+person+"}";
    }

    @ResponseBody
    @RequestMapping(value = "/people/{personId}.json", method = RequestMethod.PUT)
    public String update(@PathVariable("personId") Integer personId, @ModelAttribute("person") Person person, BindingResult result) {
        personService.update(personId, person);
        return "{\"person\": "+person+"}";
    }    

    @ResponseBody
    @RequestMapping(value = "/people/{personId}.json", method = RequestMethod.DELETE)
    public String delete(@PathVariable("personId") Integer personId) {
        personService.delete(personId);
        return "{\"status\": \"ok\", \"message\": \"\"}";
    }
}

And here is the code for my service:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.model.Person;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import java.util.List;

@Service
public class PersonServiceImpl implements PersonService {

    @PersistenceContext
    EntityManager em;

    @Transactional
    public List<Person> all() {
        CriteriaQuery<Person> c = em.getCriteriaBuilder().createQuery(Person.class);
        c.from(Person.class);
        return em.createQuery(c).getResultList();
    }

    @Transactional
    public Person find(Integer id) {
        Person person = em.find(Person.class, id);
        return person;
    }

    @Transactional
    public void create(Person person) {
        em.persist(person);
    }

    public void update(Integer id, Person person) {
        // TODO: How to implement this?
    }

    @Transactional
    public void delete(Integer id) {
        Person person = em.find(Person.class, id);
        if (null != person) {
            em.remove(person);
        }
    }   
}

So, for the Spring, hibernate experts out there, what is the best way to accomplish what I need? It is also important that the resource update only updates the actual changed attributes

Thanks,

Upvotes: 2

Views: 1844

Answers (1)

Anthony Accioly
Anthony Accioly

Reputation: 22461

Change your PersonService update method signature so that it returns a Person, also, you don't need an separate id, just use the Person object to hold the id. Them implement the method like this:

public Person update(Person person) {
    // you might want to validate the person object somehow
    return em.merge(person);
}

Also update your web service layer accordingly. Either change it so that personId is no longer there, or keep it and set the Person id with it:

public String update(@PathVariable("personId") Integer personId, 
        @ModelAttribute("person") Person person, BindingResult result) {
    person.setId(personId);
    Person updated = personService.update(person);
    // You may want to use a JSON library instead of overriding toString.
    return "{\"person\": " + updated + "}";
}   

One small gotcha, merge may also persist new entities. So, if you want to make sure that update only updates existing ids, change its body to something like:

public Person update(Person person) {
    if(em.find(Person.class, person.getId()) == null) {
        throw new IllegalArgumentException("Person " + person.getId() 
                + " does not exists");
    }
    return em.merge(person);
}

It is also important that the resource update only updates the actual changed attributes

If you want hibernate to update only attributes that are different between your JSON object and the database there is a handy combo of dynamicUpdate and selectBeforeUpdate properties on @org.hibernate.annotations.Entity (check out the Hibernate Manual). Only enable this if you really need the behavior (which is non standard behavior and may decrease performance).

@org.hibernate.annotations.Entity(dynamicUpdate = true, selectBeforeUpdate = true)

On Hibernate 4.0 those properties where deprecated, you can use individual annotations instead:

@DynamicUpdate
@SelectBeforeUpdate

Upvotes: 1

Related Questions