Fork me on GitHub

logo
Blog / NanoJuice

a random developers blog



Writing a Spring Boot REST Application

After a few failed attempts and many wasted hours at over-complicating things, I cut my losses and decided to try a different approach. I found a really handy guide from coderpills here which was great because they offer the working source via github here.

Why use Annotations

Primarily because they make life easier, To date I haven't had to create a single config file (except for the Application.properties file with database credentials), jacksons mapping config or hibernate mapping config.

The following annotations make auto configuration possible.

1
2
3
@ComponentScan
@EnableAutoConfiguration
public class Application {

Setting up a simple CORS filter using annotations

By default, the tomcat webserver will reject any request that didnt originate from localhost. After reading heaps of articles, that reference XML config file fixes, I found this article on the Spring official documentation. You will need to scroll a decent way down until you find the heading "Filter requests for CORS".

It is as simple as creating a java class file in your project, you can call it whatever you like. Here is the example from the spring.io documentation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package hello;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}
}

Note, I did have to change Access-Control-Allow-Headers to:

1
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");

Using @RestController instead of @Controller

There were a few things that needed to be changed off the bat. Primarily due to the fact that im using SpringBoot v 1.1.8 which supports the @RestController annotation. @RestController provides automatic class mapping to json using the jacksons libraries meaning that you dont have to return the ResponseEntity object.

Example with @Controller annotation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Controller
@RequestMapping(value="/data")
public class SomeDataController {

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<MyEntityObject> getSomeData(@RequestBody MyObject object) {
    MyEntityObject myEntityObject = _someDaoFactory.load(object.id);

    ...

    return new ResponseEntity<myEntityObject>(HttpStatus.OK);
}

Example with @RestController annotation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@RestController
@RequestMapping(value="/data")
public class SomeDataController {

@RequestMapping(method = RequestMethod.GET)
public MyResponseClass getSomeData(@RequestBody MyObject object) {
    MyResponseClass response = new MyResponseClass();

    ...

    return response;
}

The flaw with the ResponseEntity object, is that it will only allow you to return database entity objects. With the design I am using, I need to be able to wrap the response with extra data so the ability to create Response classes was required.

Auto-generating hibernate classes

I used the tools built into IntelliJ IDEA to auto-generate my database classes, but for some reason it didn't auto tick any columns containing foreign keys. After re-generated the classes including the foreign key links, I started getting errors.

I found out that for some reason it auto-generated the Getter and Setter methods for the columns containing foreign keys, and generated a new method to load the referenced object.

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Basic
@Column(name = "RefTableID")
public Integer getRefTableId() {
    return refTableId;
}

public void setRefTableId(Integer refTableId) {
    this.refTableId = refTableId;
}

@ManyToOne
@JoinColumn(name = "RefTableID", referencedColumnName = "RefTableID")
public RefTableEntity getRefTableIdByRefTableId() {
    return refTableIdByRefTableId;
}

public void setRefTableIdByRefTableId(RefTableEntity refTableIdByRefTableId) {
    this.refTableIdByRefTableId = refTableIdByRefTableId;
}

The error is due to the @JoinColumn and @Column annotations having the same name attribute.

To solve this, I removed the getRefTableId() and setRefTableId() methods, and renamed the getRefTableIdByRefTableId and setRefTableIdByRefTableId to getRefTableId and setRefTableId.

For some reason, hibernate also auto-generates properties on all the referenced classes. Which is stupid considering referenced objects are loaded in your entiries. Meaning when you load a record from a table, you will get the reference objects containing a property including a list of all records from this table referencing the same id. I removed these entirely.

Routing properly

I fell in love with routes, and went a little overboard by giving any unique query its own route. This caused me to have a bad time when trying to interact with the REST api. I found this great article from Vinay Sahni called Best Practices for Designing a Pragmatic RESTful API. I would recomend giving this a read if you are unsure about the best practices with writing a API's.

Routes Before:

1
2
3
/data/{id}
/data/list/today
/data/list/open

Routes After:

1
2
3
/data/{id}
/data?created=today
/data?status=open

Thats it for now, hopefully I will have more to come soon...

  • Dean