- on Thu 23 October 2014
- category Projects
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