© Felipe Gutierrez 2016

Felipe Gutierrez, Pro Spring Boot, 10.1007/978-1-4842-1431-2_8

8. Web Development with Spring Boot

Felipe Gutierrez

(1)Albuquerque, New Mexico, USA

Nowadays the web is the main channel for any type of application—from desktop to mobile devices, from social and business applications to games, and from simple content to streaming data. With this is mind, Spring Boot can help you easily develop the next generation of web applications.

This chapter shows you how to create Spring Boot web applications. You have already learned, with some examples in earlier chapters, what you can do with the web. You learned that Spring Boot makes it easier to create web apps with a few lines of code and that you don’t need to worry about configuration files or look for an application server to deploy your web application. By using Spring Boot and its auto-configuration, you can have an embedded application server like Tomcat or Jetty, which makes your app very distributable and portable.

Spring MVC

Let’s start talking about the Spring MVC technology and some of its features. Remember that the Spring Framework consists of about 20 modules or technologies, and the web technology is one of them. For the web technology, the Spring Framework has the spring-web, spring-webmvc, spring-websocket, and spring-webmvc-portlet modules.

The spring-web module has basic web integration features such as multipart file upload functionality, initialization of the Spring container (by using servlet listeners), and a web-oriented application context. The spring-mvc module (aka, the web server module) contains all the Spring MVC (Model-View-Controller) and REST services implementations for web applications. These modules provide many features, such as very powerful JSP tag libraries, customizable binding and validation, flexible model transfer, customizable handler and view resolution, and so on.

The Spring MVC is designed around the org.springframework.web.servlet.DispatcherServlet class. This servlet is very flexible and has a very robust functionality that you won’t find in any other MVC web frameworks out there. With the DispatcherServlet you have out-of-the-box several resolutions strategies, including View resolvers, Locale resolvers, Theme resolvers, and Exception handlers. In other words, the DispatcherServlet will take a HTTP request and redirect it to the right handler (the class marked with the @Controller and the methods that use the @RequestMapping annotations) and the right view (your JSPs).

Spring Boot Web Applications

You are going to continue using the Spring Boot Journal application, but with some modifications, so you can see the power of using the Spring MVC with Spring Boot. Let’s start by creating the journal app.

Open a terminal and execute the following commands:

$ mkdir spring-boot-journal
$ cd spring-boot-journal
$ spring init -d=web,thymeleaf,data-jpa,data-rest -g=com.apress.spring -a=spring-boot-journal --package-name=com.apress.spring -name=spring-boot-journal -x

These commands will be the initial template for the Spring Boot journal. Now you are getting familiar with the Spring Initializr. In this case you already know that you are going create a web application that will use the Thymeleaf templating engine for the views, the JPA for all the data access, and a new starter, the data-rest, which will allow to expose the data repositories as RESTful API.

Take a look at the pom.xml file, shown in Listing 8-1.

Listing 8-1. pom.xml
<?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>com.apress.spring</groupId>
        <artifactId>spring-boot-journal</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>


        <name>spring-boot-journal</name>
        <description>Demo project for Spring Boot</description>


        <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>1.3.2.RELEASE</version>
                <relativePath/> <!-- lookup parent from repository -->
        </parent>


        <properties>
                <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                <java.version>1.8</java.version>
        </properties>


        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-data-jpa</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-data-rest</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-thymeleaf</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                </dependency>


                                <!--  MYSQL -->
                <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                </dependency>                


                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
        </dependencies>


        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                        </plugin>
                </plugins>
        </build>


</project>

Listing 8-1 shows you the pom.xml file that you are going to be using for the Spring Boot journal app. Do you notice something different? You already know that spring-boot-starter-data-jpa, spring-boot-starter-data-rest, spring-boot-starter-data-web, and spring-boot-starter-data-thymeleaf are the starter poms because they were added as dependencies in the Spring Initializr. But note that there is now a MySQL dependency, which means that you need to have the MySQL server up and running in your system. If you want to install it, you can use brew for OS X/Linux:

$ brew install mysql

Or if you are using Windows you can find a version on the MySQL web site at http://dev.mysql.com/downloads/mysql/ .

Did you notice that in Listing 8-1, there is no <version> tag in the MySQL dependency? This is because the spring-boot-starter-parent pom has a dependency on the spring-boot-dependencies, where all the versions that work with Spring are declared—in this case the MySQL driver library. That’s why working with Spring Boot is so easy—you just add the right starter pom and don’t have to worry about third-party dependencies.

Let’s start by configuring the MySQL properties in the application. You can open and edit src/main/resources/application.properties to look like Listing 8-2.

Listing 8-2. src/main/resources/application.properties
#Spring DataSource
spring.datasource.url = jdbc:mysql://localhost:3306/journal
spring.datasource.username = springboot
spring.datasource.password = springboot
spring.datasource.testWhileIdle = true
spring.datasource.validationQuery = SELECT 1
#JPA-Hibernate
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

Listing 8-2 shows you the application.properties file that the journal app will use. As you can see, it’s very straightforward. You have two sections. The first section declares the values that the javax.sql.DataSource will use, such as the JDBC URL, the credentials, and testWhileIdle and validationQuery. These are useful for keeping the connection if it’s been idle for a long time. The second section declares all dependencies related to JPA and Hibernate. The show-sql will log all the SQL (you can turn this on and off). The hibernate.ddl-auto property will create the table (based on your declared entities annotated with @Entity) and when the app finishes, it will drop it. The other possible values are create and update (the update value is recommended for production environments). hibernate.name-strategy will use the best naming for your tables and fields in your database, and hibernate.dialect is useful for generating the SQL optimized for the database engine—in this case MySQL.

Note

In order to use the MySQL database and the credentials from Listing 8-2, don’t forget to create the journal database and add user privileges to the MySQL server. If you prefer, feel free to use your own credentials.

Next let’s add the domain src/main/java/com/apress/spring/domain/JournalEntry.java class; see Listing 8-3.

Listing 8-3. src/main/java/com/apress/spring/domain/JournalEntry.java
package com.apress.spring.domain;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;


import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;


import com.apress.spring.utils.JsonDateSerializer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;


@Entity
@Table(name="entry")
public class JournalEntry {


        @Id
        @GeneratedValue(strategy=GenerationType.AUTO)
        private Long id;
        private String title;
        private Date created;
        private String summary;


        @Transient
        private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");


        public JournalEntry(String title, String summary, String date) throws ParseException{
                this.title = title;
                this.summary = summary;
                this.created = format.parse(date);
        }


        JournalEntry(){}

        public Long getId() {
                return id;
        }


        public void setId(Long id) {
                this.id = id;
        }


        public String getTitle() {
                return title;
        }


        public void setTitle(String title) {
                this.title = title;
        }


        @JsonSerialize(using=JsonDateSerializer.class)
        public Date getCreated() {
                return created;
        }


        public void setCreated(Date created) {
                this.created = created;
        }


        public String getSummary() {
                return summary;
        }


        public void setSummary(String summary) {
                this.summary = summary;
        }


        @JsonIgnore
        public String getCreatedAsShort(){
                return format.format(created);
        }


        public String toString(){
                StringBuilder value = new StringBuilder("* JournalEntry(");
                value.append("Id: ");
                value.append(id);
                value.append(",Title: ");
                value.append(title);
                value.append(",Summary: ");
                value.append(summary);
                value.append(",Created: ");
                value.append(format.format(created));
                value.append(")");
                return value.toString();
        }
}

Listing 8-3 shows you the JournalEntry.java class. This class is a little different from the previous chapters. One of the differences is that the JournalEntry class is marked with the @Table(name="entry") annotation and with an attribute of name and value of entry. This will tell JPA/Hibernate that the table to generate will be named entry. The next difference is that the getDateMethod is marked with the @JsonSerialize(using=JsonDateSerializer.class) annotation.

The @JsonSerialize annotation has defined a JsonDateSerializer.class that will be used to serialize the data. This is a customized class that you will see soon. This is useful for printing out the date in a particular format, and this time you are going to use the standard ISO.DATE format that corresponds with this pattern: yyyy-MM-dd.

Also in Listing 8-3 you can see that the getCreatedAsShort() method is marked with @JsonIgnore, which will ignore the property when the JSON printout of the class is called. Next, consider the src/main/java/com/apress/spring/utils/JsonDateSerializer.java class. Remember that this class will serialize the date into a JSON object with a particular date format (ISO.DATE). See Listing 8-4.

Listing 8-4. src/main/java/com/apress/spring/utils/JsonDateSerializer.java
package com.apress.spring.utils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;


import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;


public class JsonDateSerializer extends JsonSerializer<Date>{

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

   @Override
    public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
            throws IOException, JsonProcessingException {
        String formattedDate = dateFormat.format(date);
        gen.writeString(formattedDate);
    }
}

Listing 8-4 shows you the JsonDateSerializer class that will be called by the JSON converter when needed. This will happen automatically inside the HttpMessaConverter<T> class handled by the Spring MVC. This class extends from the JsonSerializer class; it’s necessary to override the serialize method that will be called when the serialization happens. This serializer is based on the JSON Jackson library. This dependency is already included in the spring-boot-starter-web pom.

Next, let’s look at the src/main/java/com/apress/spring/repository/JournalRepository.java interface, which is the same one from previous chapters. See Listing 8-5.

Listing 8-5. src/main/java/com/apress/spring/repository/JournalRepository.java
package com.apress.spring.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.apress.spring.domain.JournalEntry;


public interface JournalRepository extends JpaRepository<JournalEntry, Long> { }

Listing 8-5 shows you the JournalRepository.java interface, which is the one that has all the JPA actions and all the CRUD (Create-Read-Update-Delete) actions. Of course, you are going to need to modify it to add some finders, but you will do that later in this chapter.

Let’s run the app and see what happens:

$ ./mvnw spring-boot:run

After you run this command and then open a browser and go to http://localhost:8080, you will get some kind of message. Most likely an error about opening a type: application/hal+json or a Save File As window because the browser doesn’t know how to handle this particular type of response.

What you are getting from the application is a HAL+JSON response. The HAL (Hypertext Application Language) is a representation of media, such as links. This is used by the HATEOAS (Hypermedia as the Engine of Application State) as a way to manage REST endpoints through media links, but how does the HATEOAS/HAL get here? Well, very simple. Remember that in the pom.xml file there is the spring-boot-starter-data-rest dependency. This dependency will include the JPA models as a way to expose through the HATEOAS media links for your REST API. This journal app is now an operational REST API web application.

Returning to the browser problem—how can you see the result of the application/hal+json format? If you want to see it right away, you can open a terminal and execute the following command:

$ curl -i http://localhost:8080
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 00:12:29 GMT


{
  "_links" : {
    "journalEntries" : {
      "href" : "http://localhost:8080/journalEntries{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://localhost:8080/profile"
    }
  }
}

After executing the cURL command, you should get the same output, which shows the HAL+JSON type format. If you want to use the browser, I suggest that you use Google Chrome and install the JSONView add-on. If you do so, you can see the HAL+JSON type in your browser. Safari and Firefox have the same plugin/add-on, but it doesn’t work properly all the time. See Figure 8-1.

A340891_1_En_8_Fig1_HTML.jpg
Figure 8-1. Google Chrome and the JSONView add-on at http://localhost:8080

Figure 8-1 shows you the browser view of the HAL+JSON type response. See that it defines several links, such as http://localhost:8080/journalEntries. You will see a JSON format that exposes the _links key with two additional entries: journalEntries (this is the plural of your JournalEntry domain class) and profile. You can click on those links, but if you click in the first reference—journalEntries (http://localhost:8080/journalEntries{?page,size,sort })—you will get an error, so you must adjust the URL to be only http://localhost:8080/journalEntries. You can actually add default values to the actual link but in this project we are not going to do that.

If you click on the http://localhost:8080/profile you will be redirected to the ALPS metadata. The ALPS is a data format for defining simple descriptions of application-level semantics. If you want to know more about ALPS you can go here to http://alps.io/ . See Figure 8-2.

A340891_1_En_8_Fig2_HTML.jpg
Figure 8-2. http://localhost:8080/journalEntries

Figure 8-2 shows the result of going to one of the URLs defined; in this case the http://localhost:8080/journalEntries URL. This is the result of using spring-boot-starter-data-rest and spring-boot-starter-data-jpa, where you defined your interface that extends from the JpaRepository interface (Listing 8-5).

Another thing to notice is the _embedded/journalEntries, which is actually the data that is pulled from MySQL server. By default the Spring Data REST will create the plural of the entity, so the JournalEntry class will become the journalEntries collection. Now, if you take a look at the MySQL server with the mysql shell, you will notice that table create was entry due the @Table annotation in the JournalEntry class. So far you don’t have any data.

You can stop the app by pressing Ctrl+C on your keyboard. Now, let’s add some data. Create the src/main/resources/data.sql file. See Listing 8-6.

Listing 8-6. src/main/resources/data.sql
INSERT INTO ENTRY(title,summary,created) VALUES('Get to know Spring Boot','Today I will learn Spring Boot','2016-01-02 00:00:00.00');
INSERT INTO ENTRY(title,summary,created) VALUES('Simple Spring Boot Project','I will do my first Spring Boot project','2016-01-03 00:00:00.00');
INSERT INTO ENTRY(title,summary,created) VALUES('Spring Boot Reading','Read more about Spring Boot','2016-02-02 00:00:00.00');
INSERT INTO ENTRY(title,summary,created) VALUES('Spring Boot in the Cloud','Learn Spring Boot using Cloud Foundry','2016-02-05 00:00:00.00');

You can run this application as usual:

$ ./mvnw spring-boot:run

And you can execute via the cURL command:

$ curl -i http://localhost:8080/journalEntries
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 02:22:54 GMT


{
  "_embedded" : {
    "entry" : [ {
      "title" : "Get to know Spring Boot",
      "created" : "2016-01-02",
      "summary" : "Today I will learn Spring Boot",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/journal/1"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/api/journal/1"
        }
      }
    }, {
      "title" : "Simple Spring Boot Project",
      "created" : "2016-01-03",
      "summary" : "I will do my first Spring Boot project",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/journal/2"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/api/journal/2"
        }
      }
    }, {
      "title" : "Spring Boot Reading",
      "created" : "2016-02-02",
      "summary" : "Read more about Spring Boot",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/journal/3"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/api/journal/3"
        }
      }
    }, {
      "title" : "Spring Boot in the Cloud",
      "created" : "2016-02-05",
      "summary" : "Learn Spring Boot using Cloud Foundry",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/journal/4"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/api/journal/4"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/journal"
    },
    "profile" : {
      "href" : "http://localhost:8080/api/profile/journal"
    },
    "search" : {
      "href" : "http://localhost:8080/api/journal/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 4,
    "totalPages" : 1,
    "number" : 0
  }
}

You will see something similar to the previous output. Or if you are using Google Chrome with the JSONView add-on, you should see something like Figure 8-3.

A340891_1_En_8_Fig3_HTML.jpg
Figure 8-3. http://localhost:8080/journalEntries

Click one of the links from any entry. For example, click http://localhost:8080/journalEntries/1 or use the cURL command:

$ curl -i http://localhost:8080/journalEntries/1
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 02:33:26 GMT


{
  "title" : "Get to know Spring Boot",
  "created" : "2016-01-02",
  "summary" : "Today I will learn Spring Boot",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/journalEntries/1"
    },
    "journalEntry" : {
      "href" : "http://localhost:8080/journalEntries/1"
    }
  }
}

Now, it comes the fun part! You can post a value to the REST API. Just execute the following command in a terminal window:

$ curl -i -X POST -H "Content-Type:application/json" -d '{ "title":"Cloud Foundry","summary":"Learn about Cloud Foundry and push a Spring Boot Application","created":"2016-04-05"}' http://localhost:8080/journalEntries
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/journalEntries/5
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 02:50:16 GMT


{
  "title" : "Cloud Foundry",
  "created" : "2016-04-05",
  "summary" : " Learn about Cloud Foundry and push a Spring Boot Application ",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/journalEntries/5"
    },
    "journalEntry" : {
      "href" : "http://localhost:8080/journalEntries/5"
    }
  }
}

Yes! You have the GET, POST, PUT, PATCH, and DELETE HTTP methods, which you can run against the http://localhost:8080/journalEntries URL.

Now stop your application (Ctrl+C). What about searching? Maybe you need to pass some parameters. Let’s modify the JournalRepository and add the method queries. See Listing 8-7.

Listing 8-7. src/main/java/com/apress/spring/repository/JournalRepository.java
package com.apress.spring.repository;

import java.util.Date;
import java.util.List;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;


import com.apress.spring.domain.JournalEntry;

public interface JournalRepository extends JpaRepository<JournalEntry, Long> {

        List<JournalEntry> findByCreatedAfter(@Param("after") @DateTimeFormat(iso = ISO.DATE) Date date);
        List<JournalEntry> findByCreatedBetween(@Param("after") @DateTimeFormat(iso = ISO.DATE) Date after,@Param("before") @DateTimeFormat(iso = ISO.DATE) Date before);
        List<JournalEntry> findByTitleContaining(@Param("word") String word);
        List<JournalEntry> findBySummaryContaining(@Param("word") String word);


}

Listing 8-7 shows you the new version of the JournalRepository.java interface. There are four query methods with parameters marked by the @Param and @DateTimeFormat annotations. @Param has a value that will define the parameter name to use for the URL. @DateTimeFormat is a helper for that parameter when the type is the date value, meaning that you will need to pass a date in the form of yyyy-mm-dd, which is the ISO date format.

Now you can run your application:

$ ./mvnw spring-boot:run

And execute the following command in a different terminal window:

$ curl -i http://localhost:8080/journalEntries

When you execute this command, you will see at the end of the response a new URL in the _links section:

...
"_links" : {
    "self" : {
      "href" : "http://localhost:8080/journalEntries"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/journalEntries"
    },
    "search" : {
      "href" : "http://localhost:8080/journalEntries/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 4,
    "totalPages" : 1,
    "number" : 0
  }
}

You will find the search element pointing to http://localhost:8080/journalEntries/search. You can query that URL with cURL or the browser:

 $ curl -i http://localhost:8080/journalEntries/search
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 03:05:31 GMT


{
  "_links" : {
    "findByCreatedAfter" : {
      "href" : "http://localhost:8080/journalEntries/search/findByCreatedAfter{?after}",
      "templated" : true
    },
    "findByTitleContaining" : {
      "href" : "http://localhost:8080/journalEntries/search/findByTitleContaining{?word}",
      "templated" : true
    },
    "findByCreatedBetween" : {
      "href" : "http://localhost:8080/journalEntries/search/findByCreatedBetween{?after,before}",
      "templated" : true
    },
    "findBySummaryContaining" : {
      "href" : "http://localhost:8080/journalEntries/search/findBySummaryContaining{?word}",
      "templated" : true
    },
    "self" : {
      "href" : "http://localhost:8080/journalEntries/search"
    }
  }
}

You can search using the GET HTTP method. After you added the methods, they were converted into an endpoint—that is, into RESTful API! So, by using the findByTitleContaining method, you can execute the following command:

$ curl -i http://localhost:8080/journalEntries/search/findByTitleContaining?word=Cloud
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 03:07:12 GMT


{
  "_embedded" : {
    "journalEntries" : [ {
      "title" : "Spring Boot in the Cloud",
      "created" : "2016-02-05",
      "summary" : "Learn Spring Boot using Cloud Foundry",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/journalEntries/4"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/journalEntries/4"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/journalEntries/search/findByTitleContaining?word=Cloud"
    }
  }
}

What about the dates? You added several methods to look for a date. Let’s get all the entries after 2016-02-01 (assuming you are using the data.sql as in Listing 8-6):

$ curl -i http://localhost:8080/journalEntries/search/findByCreatedAfter?after=2016-02-01
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 03:20:25 GMT


{
  "_embedded" : {
    "journalEntries" : [ {
      "title" : "Spring Boot Reading",
      "created" : "2016-02-02",
      "summary" : "Read more about Spring Boot",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/journalEntries/3"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/journalEntries/3"
        }
      }
    }, {
      "title" : "Spring Boot in the Cloud",
      "created" : "2016-02-05",
      "summary" : "Learn Spring Boot using Cloud Foundry",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/journalEntries/4"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/journalEntries/4"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/journalEntries/search/findByCreatedAfter?after=2016-02-01"
    }
  }
}

If you want to try findByCreatedBetween, you can execute the following command (the URL is now enclosed with double quotes for the two parameters—after and before):

$ curl -i "http://localhost:8080/journalEntries/search/findByCreatedBetween?after=2016-02-01&before=2016-03-01"
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Feb 2016 03:24:07 GMT


{
  "_embedded" : {
    "journalEntries" : [ {
      "title" : "Spring Boot Reading",
      "created" : "2016-02-02",
      "summary" : "Read more about Spring Boot",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/journalEntries/3"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/journalEntries/3"
        }
      }
    }, {
      "title" : "Spring Boot in the Cloud",
      "created" : "2016-02-05",
      "summary" : "Learn Spring Boot using Cloud Foundry",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/journalEntries/4"
        },
        "journalEntry" : {
          "href" : "http://localhost:8080/journalEntries/4"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/journalEntries/search/findByCreatedBetween?after=2016-02-01&before=2016-03-01"
    }
  }
}

This is amazing. By adding only a query method, you have all this functionality. As an exercise, you can test the findBySummaryContaining search.

You can the application by pressing Ctrl+C on your keyboard. Next, let’s create a web controller to show the entries journal in a nice way. See Listing 8-8.

Listing 8-8. src/main/java/com/apress/spring/web/JournalController.java
package com.apress.spring.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;


import com.apress.spring.repository.JournalRepository;

@RestController
public class JournalController {


        private static final String VIEW_INDEX = "index";

        @Autowired
        JournalRepository repo;


        @RequestMapping(value="/", method = RequestMethod.GET)
        public ModelAndView index(ModelAndView modelAndView){
                modelAndView.setViewName(VIEW_INDEX);
                modelAndView.addObject("journal", repo.findAll());
                return modelAndView;
        }
}

Listing 8-8 shows you the JournalController.java class. Let’s examine it:

  • @RestController. This class is marked with the @RestController annotation, making it available as a web controller for the DispatcherServlet.

  • @RequestMapping. This annotation is used over the index method that will become the request handler for all incoming requests at the root level. The index method has a modelAndView parameter that will be instantiated in the request. Inside the method, the modelAndView instance will set the view (index.html) and the model (journal) with all the elements found by calling the repo.findAll method. The index method will return the modelAndView instance.

  • @Autowired. The JournalRepository interface will be instantiated and used here by the index method. Remember that this class extends from the JpaRepository, which means that it will generate all the CRUD and search logic needed.

Before you run the application, make sure you have the same files as in Chapter 2. You need the following:

  • src/main/resources/static/css folder with all the CSS files (bootstrap-glyphicons.css, bootstrap.min.css, and style.css)

  • src/main/resources/templates folder with the index.html file, which is shown in Listing 8-9

Listing 8-9. src/main/resources/templates/index.html
<!doctype html>
<html lang="en-US" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8"></meta>
  <meta http-equiv="Content-Type" content="text/html"></meta>
  <title>Spring Boot Journal</title>
  <link rel="stylesheet" type="text/css" media="all" href="css/bootstrap.min.css"></link>
  <link rel="stylesheet" type="text/css" media="all" href="css/bootstrap-glyphicons.css"></link>
  <link rel="stylesheet" type="text/css" media="all" href="css/styles.css"></link>
</head>


<body>
<div class="container">
  <h1>Spring Boot Journal</h1>
  <ul class="timeline">
   <div th:each="entry,status : ${journal}">
    <li th:attr="class=${status.odd}?'timeline-inverted':''">
      <div class="tl-circ"></div>
      <div class="timeline-panel">
        <div class="tl-heading">
          <h4><span th:text="${entry.title}">TITLE</span></h4>
          <p><small class="text-muted"><i class="glyphicon glyphicon-time"></i> <span th:text="${entry.createdAsShort}">CREATED</span></small></p>
        </div>
        <div class="tl-body">
          <p><span th:text="${entry.summary}">SUMMARY</span></p>
        </div>
      </div>
    </li>
   </div>
  </ul>
</div>
</body>
</html>

Listing 8-9 shows you the index.html file. Remember that this file is using the Thymeleaf view engine. If you want to know more about the Thymeleaf engine, visit http://www.thymeleaf.org/ .

Now, if you rerun your application and point to the browser to http://localhost:8080, you will see something similar to Figure 8-4.

A340891_1_En_8_Fig4_HTML.jpg
Figure 8-4. http://localhost:8080

Figure 8-4 shows the result of having a web controller. You can still point to http://localhost:8080/journalEntries and see the HAL+JSON response, but I think it would be nice if you had a separate path for your REST calls, something like /api path. That’s one of the benefits of using Spring Boot; it’s so configurable. Go to the src/main/resources/application.properties file and add the following line to the end:

spring.data.rest.basePath=/api

If you are running you application, terminate it by pressing Ctrl+C. Then you can rerun your application. You should now have the HAL+JSON response in the http://localhost:8080/api URL. If you want to add more entries, you need to post to the http://localhost:8080/api/journalEntries URL.

After testing the new endpoint, you can stop your application.

That journalEntries path is too long, but it can be modified!. Let’s change it. Go to the src/main/java/com/apress/spring/repository/JournalRepository.java interface and make sure it looks like the final version shown in Listing 8-10.

Listing 8-10. Final Version of src/main/java/com/apress/spring/repository/JournalRepository.java
package com.apress.spring.repository;

import java.util.Date;
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;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.transaction.annotation.Transactional;


import com.apress.spring.domain.JournalEntry;

@Transactional
@RepositoryRestResource(collectionResourceRel = "entry", path = "journal")
public interface JournalRepository extends JpaRepository<JournalEntry, Long> {


        List<JournalEntry> findByCreatedAfter(@Param("after") @DateTimeFormat(iso = ISO.DATE) Date date);
        List<JournalEntry> findByCreatedBetween(@Param("after") @DateTimeFormat(iso = ISO.DATE) Date after,@Param("before") @DateTimeFormat(iso = ISO.DATE) Date before);
        List<JournalEntry> findByTitleContaining(@Param("word") String word);
        List<JournalEntry> findBySummaryContaining(@Param("word") String word);


}

Listing 8-10 shows you the final version of the JournalRepository interface. Two annotations were added. @Transactional makes all the REST calls transactional, which protects the data where there are concurrent calls to the REST API. The @RepositoryRestResource annotation modifies the path to journal and, instead of grabbing the plural names, it will call entry.

If you rerun your application, you will have a better URL to get to the REST API: http://localhost:8080/api/journal. Feel free to inspect the URL and search. It will now be the http://localhost:8080/api/journal/search URL.

How about that! You have a very cool journal application! Did you notice that you didn’t do anything in the web controller? In the past, you needed to create the save, delete, find, and update methods. But not any more; you have spring-data-rest and very good solution for a web application.

Now, you can stop your application by pressing Ctrl+C.

Playing with the HAL Browser

One of the newest features of spring-data-rest and the web components is that you can install a HAL browser that works out of the box. The only thing you need to do is add the following dependency to your pom.xml file.

<dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>

If you rerun your application, go to the http://localhost:8080/api/browser. You should get something similar to Figure 8-5.

A340891_1_En_8_Fig5_HTML.jpg
Figure 8-5. http://localhost:8080/api/browser

Figure 8-5 shows you the HAL browser, which is a very nice tool to inspect your REST API. Add /api/journal to the Explorer field and click the Go button. You should see all the journal entries. See Figure 8-6.

A340891_1_En_8_Fig6_HTML.jpg
Figure 8-6. /api/journal in the Explorer field

Figure 8-6 shows you the result of exploring the /api/journal. Inserting data is also easy. Note the Links section in Figure 8-5. Click the yellow icon belonging to the entry caption (in its NON-GET column). This will bring up the window you’ll use to input the data. See Figure 8-7.

A340891_1_En_8_Fig7_HTML.jpg
Figure 8-7. NON-GET action in journal entry form

Figure 8-7 shows you the journal entry form. As you can see, you have options. Feel free to click the links and go back to your home and see all the data. Nice work! You created a Spring Boot web application with a REST API.

Summary

This chapter showed you how to create a more robust journal application by using spring-data-rest and all its features. Earlier chapters showed you how to extend the AbstractController class to get the Spring MVC working, but since version 2.5 of the Spring Framework, in the web module (spring-web-mvc), you can use annotations instead, such as @Controller, @RestController, @RequestMapping, @ResponseBody, and so on.

It’s important to note that Spring Boot simplifies web development by removing all XML (Spring app context and web.xml) configuration files.

The next chapter discusses how to use security, and of course you are going to learn how to secure your journal application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.40.189