Why Publish and Addendum?
My YouTube video "REST Web Services Made Easy - Part 2" has been well received, but with one reservation: some people wish that it contained the step-by-step project creation and coding instructions that I have provided in other videos. Here's the original video:
My reasons for not including those is simply that the code is so simple and watching me type code in during a video can be a little boring. Also, I included a link to the actual code in the video description, which I store on GitHub, so that anyone can download it and modify it to suit their needs. But people just want more.
Well, what the heck, I'm nothing if not willing to please. So this post is an addendum to that video which provides step-by-step instructions for creating the project and code for the video above.
Step-by-step Instructions
I'll be using the Netbeans 8.0.1 IDE for these instructions but you could use any IDE or text editor to perform similar tasks.
First, you'll need to create a new, standalone Java application project. In Netbeans you do this by mousing over to the File menu and selecting "New Project...". Then, from the New Project dialog box select the "Maven" category and the "Java Application" project type:
Next, modify the screen below by providing an appropriate project name, location and "Group Id" which is typically a domain name for a production application, but you can use whatever you want for practice.
For this example we'll use "RestHotel" for the Project Name, and "edu.wctc.spring.boot.rest.demo1" for Group Id. Leave the "Version" alone for now as well as the Package which should have changed with the changes made so far.
Now just click "Finish" to see your project. The next step is to edit the Maven "pom.xml" file which is located under "Project Files" in Netbeans.
With pom.xml open in your editor replace everything in the file with the code below. IMPORTANT: make sure your "groupId" and "artifactId" match.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>edu.wctc.spring.boot.rest.demo1</groupId> <artifactId>RestHotel</artifactId> <version>1.0.3-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.1.4.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <start-class>edu.wctc.spring.boot.rest.demo1.SampleDataRestApplication</start-class> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-rest-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <url>http://repo.spring.io/libs-snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-milestones</id> <url>http://repo.spring.io/libs-snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project>
What this code does is provide a way to import all the third party libraries that you will need to run this project. After making the changes, save everything and then do a clean and build in Netbeans. If all goes well you're ready to start creating class files.
Next, save the changes you made to pom.xml and then go to the "Source Packages" folder in your project and right-click on the package name below that. From the popup menu select "New > Java Class:
On the next screen enter the class name "SampleDataRestApplication" and click "Finish". Your class should now be open in your editor. We need to add some annotations, import statements and start-up code so that Spring Boot understands how to work with your application. To save time, just copy and paste everything below, replacing all in your current class file:
package edu.wctc.spring.boot.rest.demo1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; /** * This is how Spring Boot starts an application, in this case a * web application that uses Spring Data and JPA to automatically create * all the C.R.U.D. operations and map them to RESTful endpoints. See * the HotelRepository class under the service package. * * @author Jim Lombardo */ @Configuration @ComponentScan @EnableJpaRepositories @Import(RepositoryRestMvcConfiguration.class) @EnableAutoConfiguration public class SampleDataRestApplication { public static void main(String[] args) { SpringApplication.run(SampleDataRestApplication.class, args); } }
Here's a quick explanation of the code above:
the @Configuration annotation is Spring Boot's way of saying that this class provides configuration information.
@ComponentScan tells Spring to look for classes with special Spring annotations and manage objects created from those classes.
@EnableJpaRespositories tells Spring to use the Spring Data-JPA libraries to automatically create database-access code for your project. (Links are provided in the video description for more information about all of these Spring features.)
@Import(...) tells Spring to import a dependent library.
@EnableAutoCofniguration tells Spring Boot to automatically configure everything based on sensible defaults provided by the Spring Boot system. You an always override these, but rarely need to do this. Again, see the Sprint Boot documentation for more details.
What we're left with is a standard Java main method that is used to start your application. But that's where Spring Boot does some additional magic. The single line of code inside the main method tells Spring Boot to run your main class which means that Spring Boot will launch your program, not as a standalone Java application but rather as a Web Application. And it will do that by first deploying your app to an embedded copy of the Tomcat application server. Think about how cool that is. No need to setup and manage a server -- Spring Boot takes care of the grunt work.
Ok, so now we have a basic Spring Boot web application setup. But how do we get it to talk to a database? Spring (http://spring.io) offers some additional magic to make this easy. It's called Spring Data-JPA. And it easily integrates with another great Spring tool -- Spring Data-Rest. Put those two together and you can easily connect to a database and get full C.R.U.D. (Create, Retrieve, Update and Delete) database functionality exposed as a set of REST web services. Note: if you're unfamiliar with JPA (Java Persistence Architecture or REST, there are links to more information in the video notes for the YouTube video above. A discussion of these is beyond the scope of this article.) So let's get started...
First we need to have a JPA Entity class for each database table. And speaking of database tables, Spring Boot makes it easy to get started developing a database-connected project by automatically using an embedded database -- in this case, it will be H2 (http://www.h2database.com/). You can see the com.h2.database artifact called out in the pom.xml dependencies above.
To create a JPA entity class you can either code it manually (which we'll do) or use Netbeans to automatically reverse engineer an existing database table into a JPA entity class. Should you choose to do this, here are the basic steps:
However, for this project we'll hand code the entity class ourselves. Here's the source code:
Once you have JPA entity classes for your database tables all you have to do is create Spring Data-JPA "repository" classes, one for each entity class. Here's the code for our Hotel repository clsss:
Let's examine the code above in a bit more detail.
First, notice that this is a Java interface that extends another interface, "JpaRepository", which is an interface in the Spring Data=JPA toolset. What's so amazing about this interface is that using it, Spring will automatically create, in-memory a Java class and an instance of the class that contains full C.R.U.D. methods to access your database. See the Spring-Data-JPA documentation for more information on how this works.
Next, notice the @RepositoryResResource annotation. This comes from Spring's Data-REST toolset and it automatically turns all those C.R.U.D. methods into REST endpoints, in the HATEOS style. It's pretty amazing, really. All of this functionality for so little investment in coding effort. Just let Spring do most of the work!
One of the limitations of RESTful web services is the fact that they are not automatically available to client-side programs running on a different server, in a different domain. This is referred to as the "same origin security policy". Such "cross-domain" requests are forbidden by web browsers, per the same-origin security policy. However, we can get around this restriction by creating a server-side "Cross Origin Resource Sharing (CORS) filter. This is a mechanism that allows many resources (e.g., fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain from which the resource originated.
Here's the code for a CORS filter for our program:
The only remaining things to do are to configure the server and provide some sample data in our database.
To configure the server we need a "application.yml" file. It contains the following:
The "ddl-auto: create" line informs Spring Boot to automatically look for an run an SQL-DDL script ("import.sql") in the root of the classpath. Here it is:
The program is nearly ready to run. Just perform a "Clean and Build" in Netbeans by selecting your project and then clicking the "Clean and Build" selection from the "Run" menu. This will cause Maven to generate a ".jar" file in the "target" source directory. Open this directory in a command prompt (Windows) or terminal (OSX or Linux) and list out the files. You should see two ".jar" files:
Making the Database Connection
First we need to have a JPA Entity class for each database table. And speaking of database tables, Spring Boot makes it easy to get started developing a database-connected project by automatically using an embedded database -- in this case, it will be H2 (http://www.h2database.com/). You can see the com.h2.database artifact called out in the pom.xml dependencies above.
To create a JPA entity class you can either code it manually (which we'll do) or use Netbeans to automatically reverse engineer an existing database table into a JPA entity class. Should you choose to do this, here are the basic steps:
However, for this project we'll hand code the entity class ourselves. Here's the source code:
package edu.wctc.spring.boot.rest.demo1.domain; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * Just a normal JPA entity class that is mapped to an in-memory H2 * database table. * * @author Jim Lombardo */ @Entity public class Hotel implements Serializable { private static final long serialVersionUID = 5L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String city; private String name; private String address; private String zip; protected Hotel() { } public Hotel(String city, String name, String address, String zip) { this.city = city; this.name = name; this.address = address; this.zip = zip; } public Hotel(Long id, String name, String city, String address, String zip) { this.id = id; this.city = city; this.name = name; this.address = address; this.zip = zip; } public String getCity() { return this.city; } public void setCity(String city) { this.city = city; } public String getName() { return this.name; } public String getAddress() { return this.address; } public void setName(String name) { this.name = name; } public void setAddress(String address) { this.address = address; } public void setZip(String zip) { this.zip = zip; } public String getZip() { return this.zip; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public int hashCode() { int hash = 7; hash = 23 * hash + (this.id != null ? this.id.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Hotel other = (Hotel) obj; if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) { return false; } return true; } @Override public String toString() { return "Hotel{" + "id=" + id + ", city=" + city + ", name=" + name + ", address=" + address + ", zip=" + zip + '}'; } }
Once you have JPA entity classes for your database tables all you have to do is create Spring Data-JPA "repository" classes, one for each entity class. Here's the code for our Hotel repository clsss:
package edu.wctc.spring.boot.rest.demo1.service; import edu.wctc.spring.boot.rest.demo1.domain.Hotel; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; /** * This is a special Spring Data-JPA interface that Spring uses to create * an implementation at runtime, which provides all the C.R.U.D. operations * and, due to the annotation, RESTful endpoints. * * @author Jim Lombardo */ @RepositoryRestResource(collectionResourceRel = "hotels", path = "hotels") public interface HotelRepository extends JpaRepository<Hotel,Long> { }
Let's examine the code above in a bit more detail.
First, notice that this is a Java interface that extends another interface, "JpaRepository", which is an interface in the Spring Data=JPA toolset. What's so amazing about this interface is that using it, Spring will automatically create, in-memory a Java class and an instance of the class that contains full C.R.U.D. methods to access your database. See the Spring-Data-JPA documentation for more information on how this works.
Next, notice the @RepositoryResResource annotation. This comes from Spring's Data-REST toolset and it automatically turns all those C.R.U.D. methods into REST endpoints, in the HATEOS style. It's pretty amazing, really. All of this functionality for so little investment in coding effort. Just let Spring do most of the work!
Making RESTful Web Service Available to Multiple Clients
Here's the code for a CORS filter for our program:
package edu.wctc.spring.boot.rest.demo1.filter; 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; /** * A simple CORS filter to allow all main HTTP methods access to * cross domain web services. * * @author https://spring.io/guides/gs/rest-service-cors/ */ @Component public class SimpleCORSFilter implements Filter { @Override 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, PUT, PATCH, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "content-type"); chain.doFilter(req, res); } @Override public void init(FilterConfig filterConfig) { } @Override public void destroy() { } }
Remaining Configuration Details
To configure the server we need a "application.yml" file. It contains the following:
server: port: 8080 ddl-auto: create
The "ddl-auto: create" line informs Spring Boot to automatically look for an run an SQL-DDL script ("import.sql") in the root of the classpath. Here it is:
-- Sample dataset containing a number of Hotels in various Cities across the world. -- -- ================================================================================================= -- AUSTRALIA -- Brisbane insert into hotel(address, city, name, zip) values ('William & George Streets', 'Brisbane', 'Conrad Treasury Place', '4001'); -- ================================================================================================= -- CANADA -- Montreal insert into hotel(address, city, name, zip) values ('1228 Sherbrooke St', 'Montreal', 'Ritz Carlton', 'H3G1H6'); -- ================================================================================================= -- SPAIN -- Barcelona insert into hotel(address, city, name, zip) values ('Passeig del Taulat 262-264', 'Barcelona', 'Hilton Diagonal Mar', '08019'); -- ================================================================================================= -- SWITZERLAND -- Neuchatel insert into hotel(address, city, name, zip) values ('Esplanade Leopold-Robert 2', 'Neuchatel', 'Hotel Beaulac', '2000');
Ready to Run
- RestHotel-1.0.3-SNAPSHOT.jar
- RestHotel-1.0.3-SNAPSHOT.jar.original
We'll use the first one which is an executable jar file. to launch it, simple enter the following at a command prompt (make sure you have Java setup in your O/S execution path and that your command prompt or terminal is set so that the working directory is the aforementioned target directory):
java -jar RestHotel-1.0.3-SNAPSHOT.jar
... next you'll see lots of status messages being output in the terminal or command window, and when it stops outputting, you're ready to talk to the web services. If you're on a Mac or Linux machine you can use CURL to access the web services. If you're on Windows you'll need a tool like "cURL" or similar program which can be found on the web.
Here's a sample curl command that calls the web service to retrieve a list of all hotel records:
All of the code for this project, including documentation and client-side code, is available for download from my GitHub site. Just point your browser at: https://github.com/jlombardo/RestHotel.git. Have fun!
curl http://localhost:8080/hotels
All of the code for this project, including documentation and client-side code, is available for download from my GitHub site. Just point your browser at: https://github.com/jlombardo/RestHotel.git. Have fun!