Spring Data REST is an awesome project that provides mechanisms to expose the resources underlying a
Spring Data based repository as REST resources.
Exposing a service with a linked resource
Consider two simple JPA based entities, Course and Teacher:
@Entity
@Table(name = "teachers")
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Size(min = 2, max = 50)
@Column(name = "name")
private String name;
@Column(name = "department")
@Size(min = 2, max = 50)
private String department;
...
}
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Size(min = 1, max = 10)
@Column(name = "coursecode")
private String courseCode;
@Size(min = 1, max = 50)
@Column(name = "coursename")
private String courseName;
@ManyToOne
@JoinColumn(name = "teacher_id")
private Teacher teacher;
....
}
essentially the relation looks like this:
Now, all it takes to expose these entities as REST resources is adding a @RepositoryRestResource annotation on their JPA based Spring Data repositories this way, first for the "Teacher" resource:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import univ.domain.Teacher;
@RepositoryRestResource
public interface TeacherRepo extends JpaRepository<Teacher, Long> {
}
and for exposing the Course resource:
@RepositoryRestResource
public interface CourseRepo extends JpaRepository<Course, Long> {
}
With this done and assuming a few teachers and a few courses are already in the datastore, a GET on courses would yield a response of the following type:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/courses{?page,size,sort}",
"templated" : true
}
},
"_embedded" : {
"courses" : [ {
"courseCode" : "Course1",
"courseName" : "Course Name 1",
"version" : 0,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/courses/1"
},
"teacher" : {
"href" : "http://localhost:8080/api/courses/1/teacher"
}
}
}, {
"courseCode" : "Course2",
"courseName" : "Course Name 2",
"version" : 0,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/courses/2"
},
"teacher" : {
"href" : "http://localhost:8080/api/courses/2/teacher"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}
and a specific course looks like this:
{
"courseCode" : "Course1",
"courseName" : "Course Name 1",
"version" : 0,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/courses/1"
},
"teacher" : {
"href" : "http://localhost:8080/api/courses/1/teacher"
}
}
}
If you are wondering what the "_links", "_embedded" are - Spring Data REST uses
Hypertext Application Language(or HAL for short) to represent the links, say the one between a course and a teacher.
HAL Based REST service - Using RestTemplate
Given this HAL based REST service, the question that I had in my mind was how to write a client to this service. I am sure there are better ways of doing this, but what follows worked for me and I welcome any cleaner ways of writing the client.
First, I
modified the RestTemplate to register a custom Json converter that understands HAL based links:public RestTemplate getRestTemplateWithHalMessageConverter() {
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> existingConverters = restTemplate.getMessageConverters();
List<HttpMessageConverter<?>> newConverters = new ArrayList<>();
newConverters.add(getHalMessageConverter());
newConverters.addAll(existingConverters);
restTemplate.setMessageConverters(newConverters);
return restTemplate;
}
private HttpMessageConverter getHalMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
halConverter.setObjectMapper(objectMapper);
return halConverter;
}
The Jackson2HalModule is provided by the Spring HATEOS project and understands HAL representation.
Given this shiny new RestTemplate, first let us
create a Teacher entity:
Teacher teacher1 = new Teacher();
teacher1.setName("Teacher 1");
teacher1.setDepartment("Department 1");
URI teacher1Uri =
testRestTemplate.postForLocation("http://localhost:8080/api/teachers", teacher1);
Note that when the entity is created, the response is a http status code of 201 with the Location header pointing to the uri of the newly created resource, Spring RestTemplate provides a neat way of posting and getting hold of this Location header through an API. So now we have a teacher1Uri representing the newly created teacher.
Given this teacher URI, let us now
retrieve the teacher, the raw json for the teacher resource looks like the following:
{
"name" : "Teacher 1",
"department" : "Department 1",
"version" : 0,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/teachers/1"
}
}
}
and to retrieve this using RestTemplate:
ResponseEntity<Resource<Teacher>> teacherResponseEntity
= testRestTemplate.exchange("http://localhost:8080/api/teachers/1", HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Teacher>>() {
});
Resource<Teacher> teacherResource = teacherResponseEntity.getBody();
Link teacherLink = teacherResource.getLink("self");
String teacherUri = teacherLink.getHref();
Teacher teacher = teacherResource.getContent();
Jackson2HalModule is the one which helps unpack the links this cleanly and to get hold of the Teacher entity itself. I have previously explained ParameterizedTypeReference
here.
Now, to a more tricky part,
creating a Course. Creating a course is tricky as it has a relation to the Teacher and representing this relation using HAL is not that straightforward. A raw POST to create the course would look like this:
{
"courseCode" : "Course1",
"courseName" : "Course Name 1",
"version" : 0,
"teacher" : "http://localhost:8080/api/teachers/1"
}
Note how the reference to the teacher is a URI, this is how HAL represents an embedded reference specifically for a POST'ed content, so now to get this form through RestTemplate -
First to create a Course:
Course course1 = new Course();
course1.setCourseCode("Course1");
course1.setCourseName("Course Name 1");
At this point, it will be easier to handle providing the teacher link by dealing with a json tree representation and adding in the teacher link as the teacher uri:
ObjectMapper objectMapper = getObjectMapperWithHalModule();
ObjectNode jsonNodeCourse1 = (ObjectNode) objectMapper.valueToTree(course1);
jsonNodeCourse1.put("teacher", teacher1Uri.getPath());
and posting this should create the course with the linked teacher:
URI course1Uri = testRestTemplate.postForLocation(coursesUri, jsonNodeCourse1);
and to retrieve this newly created Course:
ResponseEntity<Resource<Course>> courseResponseEntity
= testRestTemplate.exchange(course1Uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Course>>() {
});
Resource<Course> courseResource = courseResponseEntity.getBody();
Link teacherLinkThroughCourse = courseResource.getLink("teacher");
This concludes how to use the RestTemplate to create and retrieve a linked resource, alternate ideas are welcome.
If you are interested in exploring this further, the entire sample is available at
this github repo - and the test is
hereReferences:
Hypertext Application Language(or HAL for short)HAL SpecificationSpring
RestTemplate