Using URI template patterns

In the previous chapters, you saw how to map a particular URL to a Controller's method; for example, if we entered the URL http://localhost:8080/webstore/market/products, we would map that request to the list method of ProductController and list all the product information in the web page.

What if we want to list only a subset of products based on category, for instance, if we want to display only the products that fall under the category of laptops? If the URL entered is http://localhost:8080/webstore/market/products/Laptop, and similarly if the URL is http://localhost:8080/webstore/market/products/Tablet, we will like to show only tablets on the web page.

One way to do this is to have a separate request mapping method in the Controller for every unique category. But it won't scale if we have hundreds of categories; in that case we have to write hundreds of request mapping methods in the Controller. So how do we do that in an elegant way?

The Spring MVC URI template patterns feature is the answer. If you look at the following URLs carefully, the only part of the URL that changes is the category type (Laptop and Tablet). Other than that, everything is the same:

  • http://localhost:8080/webstore/products/Laptop
  • http://localhost:8080/webstore/products/Tablet

So we can define a common URI template for these URLs, which might look like http://localhost:8080/webstore/products/{category}. Spring MVC can leverage this fact and make the template portion ({category}) of the URL a variable, which is called a path variable in the Spring MVC world.

Time for action - showing products based on category

Let's add a category-wise View to our products page using the path variable.

  1. Open the ProductRepository interface and add one more method declaration to getProductsByCategory:
          List<Product> getProductsByCategory(String category); 
    
  2. Open the InMemoryProductRepository implementation class and add an implementation for the previously declared method as follows:
         @Override 
         public List<Product> getProductsByCategory(String category) { 
            String SQL = "SELECT * FROM PRODUCTS WHERE CATEGORY =      :category"; 
            Map<String, Object> params = new HashMap<String, Object>(); 
            params.put("category", category); 
     
            return jdbcTemplate.query(SQL, params, new 
            ProductMapper()); 
         } 
    
  3. Similarly open the ProductService interface and add one more method declaration to getProductsByCategory:
          List<Product> getProductsByCategory(String category); 
    
  4. Open the ProductServiceImpl service implementation class and add an implementation as follows:
          public List<Product> getProductsByCategory(String category) { 
             return productRepository.getProductsByCategory(category); 
          } 
    
  5. Now open our ProductController class and add one more request mapping method as follows:
          @RequestMapping("/products/{category}") 
          public String getProductsByCategory(Model model, 
          @PathVariable("category") String productCategory) { 
             model.addAttribute("products", 
             productService.getProductsByCategory(productCategory)); 
             return "products"; 
          } 
    
  6. Now run our application and enter the following URL: http://localhost:8080/webstore/market/products/Tablet. You should see the following screen:
    Time for action - showing products based on category

    Screen showing products by category with the help of path variables

What just happened?

Step 5 is the most important in the whole sequence, because all the steps prior to step 5 are a prerequisite for it. What we are doing in step 5 is nothing but one normal way of adding a list of product objects to the model.

model.addAttribute("products", productService.getProductsByCategory(productCategory)); 

One thing you need to notice here is the getProductsByCategory method from productService; we need this method to get the list of products for the given category. And productService as such cannot give the list of products for the given category; it will ask the repository.

That's why in step 4 we used the productRepository reference to get the list of products by category in the ProductServiceImpl class. Notice the following line from ProductServiceImpl:

return productRepository.getProductsByCategory(category); 

Another important thing you need to notice in the step 5 code snippet is the @RequestMapping annotation's request path value:

@RequestMapping("/products/{category}") 

By enclosing a portion of a request path within curly braces, we are indicating to Spring MVC that it is a URI template variable. According to the Spring MVC documentation, a URI template is a URI-like string containing one or more variable names. When you substitute values for these variables, the template becomes a URI.

For example, the URI template http://localhost:8080/webstore/market/products/{category} contains the category variable. Assigning the value laptop to the variable yields http://localhost:8080/webstore/market/products/Laptop. In Spring MVC ,we can use the @PathVariable (org.springframework.web.bind.annotation.PathVariable) annotation to read a URI template variable.

Since we have the @RequestMapping("/market") annotation on the ProductController level, the actual request path for the getProductsByCategory method will be /market/products/{category}. So at runtime, if we provide a web request URL such as http://localhost:8080/webstore/market/products/Laptop, then the category path variable will have the value laptop. Similarly for the web request http://localhost:8080/webstore/market/products/Tablet, the category path variable will have the value tablet.

Now how do we retrieve the value stored in the URI template path variable category? As we already mentioned, the @PathVariable annotation will help us to read that variable. All we need to do is simply annotate the getProductsByCategory method's parameter with the @PathVariable annotation as follows:

public String getProductsByCategory(@PathVariable("category") String productCategory, Model model) { 

Spring MVC will read whatever value is present in the category URI template variable and assign it to the productCategory method parameter. So we have the category value in a variable; we just pass it to productService to get the list of products in that category. Once we get that list of products, we simply add it to the Model and return the same View name that we used to list all the products.

The value attribute in the @PathVariable annotation should be the same as the variable name in the path expression of the @RequestMapping annotation. For example, if the path expression is "/products/{identity}", then to retrieve the path variable identity you have to form the @PathVariable annotation as @PathVariable("identity").

Tip

If the @PathVariable annotation is specified without any value attribute, it will try to retrieve a path variable with the name of the variable it has been annotated with.

For example, if you specify simply @PathVariable String productId, then Spring will assume that it should look for a URI template variable {productId} in the URL. A request mapping method can have any number of @PathVariable annotations.

Finally in step 6, when we enter the URL http://localhost:8080/webstore/market/products/Tablet, we see information about Google's Nexus 7, which is a tablet. Similarly, if you enter the URL http://localhost:8080/webstore/products/Laptop, you will able to see Dell's Inspiron laptop's information.

Pop quiz - request path variable

If we have a web application called webstore with the following request mapping on the Controller class level and in the method level, which is the appropriate request URL?

@RequestMapping("/items") 
public class ProductController { 
... 
@RequestMapping(value = "/type/{type}", method = RequestMethod.GET) 
public String productDetails(@PathVariable("type") String productType, Model model) { 
  1. http://localhost:8080/webstore/items/electronics
  2. http://localhost:8080/webstore/items/type/electronics
  1. http://localhost:8080/webstore/items/productType/electronics
  2. http://localhost:8080/webstore/type/electronics

For the following request mapping annotation, which are the correct methods' signatures to retrieve the path variables?

@RequestMapping(value="/manufacturer/{ manufacturerId}/product/{productId}") 
  1. public String productByManufacturer(@PathVariable String manufacturerId, @PathVariable String productId, Model model)
  2. public String productByManufacturer (@PathVariable String manufacturer, @PathVariable String product, Model model)
  3. public String productByManufacturer (@PathVariable("manufacturer") String manufacturerId, @PathVariable("product") String productId, Model model)
  4. public String productByManufacturer (@PathVariable("manufacturerId") String manufacturer, @PathVariable("productId") String product, Model model)
..................Content has been hidden....................

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