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.
| @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:
| 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:
| /data/{id}
/data/list/today
/data/list/open
|
Routes After:
| /data/{id}
/data?created=today
/data?status=open
|
Thats it for now, hopefully I will have more to come soon...