The Persistence layer

Since we had a single product, we just instantiated it in the controller itself and successfully showed the product information on our web page. But a typical web store would contain thousands of products, so all the product information for them would usually be stored in a database. This means we need to make our ProductController smart enough to load all the product information from the database into the model. But if we write all the data retrieval logic to retrieve the product information from the database in the ProductController itself, our ProductController will blow up into a big chunk of files. And logically speaking, data retrieval is not the duty of the controller because the controller is a Presentation layer component. And moreover, we want to organize the data retrieval code into a separate layer, so that we can reuse that logic as much as possible from the other controllers and layers.

So how do we retrieve data from a database in a Spring MVC way? Here comes the concept of the Persistence layer. A Persistence layer usually contains repository objects to access domain objects. A repository object sends queries to the data source for the data, then it maps the data from the data source to a domain object, and finally it persists the changes in the domain object to the data source. So typically, a repository object is responsible for CRUD (Create, Read, Update, and Delete) operations on domain objects. And the @Repository (org.springframework.stereotype.Repository) annotation is an annotation that marks the specific class as a repository. The @Repository annotation also indicates that SQLExceptions thrown from the repository object's methods should be translated into Spring's specific org.springframework.dao.DataAccessExceptions. Let's create a repository layer for our application.

Time for action - creating a repository object

Let's create a repository class to access our Product domain objects.

  1. Open pom.xml to add a dependency to spring-jdbc. In Group Id enter org.springframework, in Artifact Id enter spring-jdbc, and in Version enter 4.3.0.RELEASE. Select Scope as compile and then click on the OK button.
  2. Similarly, add the dependency for HyperSQL DB by clicking on the same Add button. This time, enter org.hsqldb for Group Id, hsqldbfor Artifact Id , 2.3.2 for Version, select Scope as compile, and save pom.xml.
  3. Create a class called RootApplicationContextConfig under the com.packt.webstore.config package in the src/main/java source folder and add the following code to it:
            package com.packt.webstore.config; 
     
            import javax.sql.DataSource; 
     
            import org.springframework.context.annotation.Bean; 
            import org.springframework.context.annotation
            .ComponentScan; 
            import org.springframework.context.annotation
            .Configuration; 
            import org.springframework.jdbc.core.namedparam
            .NamedParameterJdbcTemplate; 
            import org.springframework.jdbc.datasource.embedded
            .EmbeddedDatabase; 
            import org.springframework.jdbc.datasource.embedded
            .EmbeddedDatabaseBuilder; 
            import org.springframework.jdbc.datasource.embedded
            .EmbeddedDatabaseType; 
     
            @Configuration 
            @ComponentScan("com.packt.webstore") 
            public class RootApplicationContextConfig { 
     
               @Bean 
               public DataSource dataSource() { 
                  EmbeddedDatabaseBuilder builder = new 
                  EmbeddedDatabaseBuilder(); 
                  EmbeddedDatabase db = builder 
                     .setType(EmbeddedDatabaseType.HSQL) 
                     .addScript("db/sql/create-table.sql") 
                     .addScript("db/sql/insert-data.sql") 
                     .build(); 
                  return db; 
               } 
       
               @Bean 
               public NamedParameterJdbcTemplate getJdbcTemplate() { 
                  return new NamedParameterJdbcTemplate(dataSource()); 
               } 
            } 
    
  4. Create a folder structure called db/sql/ under the src/main/resources source folder and create a file called create-table.sql in it. Add the following SQL script to it:
            DROP TABLE PRODUCTS IF EXISTS; 
     
            CREATE TABLE PRODUCTS ( 
              ID VARCHAR(25) PRIMARY KEY, 
              NAME VARCHAR(50), 
              DESCRIPTION  VARCHAR(250), 
              UNIT_PRICE DECIMAL, 
              MANUFACTURER VARCHAR(50), 
              CATEGORY VARCHAR(50), 
              CONDITION VARCHAR(50), 
              UNITS_IN_STOCK BIGINT, 
              UNITS_IN_ORDER BIGINT, 
              DISCONTINUED BOOLEAN 
            ); 
    
  5. Similarly create one more SQL script file called insert-data.sql under the db/sql folder and add the following script to it:
            INSERT INTO PRODUCTS VALUES ('P1234', 'iPhone 6s', 'Apple          iPhone 6s smartphone with 4.00-inch 640x1136 display and 8-         megapixel rear 
            camera','500','Apple','Smartphone','New',450,0,false);
            INSERT INTO PRODUCTS VALUES ('P1235', 'Dell Inspiron',          'Dell Inspiron 14-inch Laptop (Black) with 3rd Generation          Intel Core processors', 
            700,'Dell','Laptop','New',1000,0,false);
            INSERT INTO PRODUCTS VALUES ('P1236', 'Nexus 7', 'Google          Nexus 7 is the lightest 7 inch tablet With a quad-core          Qualcomm Snapdragon™ S4 Pro processor', 
            300,'Google','Tablet','New',1000,0,false);
  6. Open DispatcherServletInitializer and change the getRootConfigClasses method's return value to return new Class[] { RootApplicationContextConfig.class };. Basically, your getRootConfigClasses method should look as follows after your change:
            @Override 
            protected Class<?>[] getRootConfigClasses() { 
               return new Class[] { RootApplicationContextConfig.class           }; 
            } 
    
  7. Create an interface called ProductRepository under the com.packt.webstore.domain.repository package in the src/main/java source folder. And add a single method declaration in the interface as follows:
            package com.packt.webstore.domain.repository; 
     
            import java.util.List; 
     
            import com.packt.webstore.domain.Product; 
     
            public interface ProductRepository { 
     
               List <Product> getAllProducts(); 
            } 
    
  8. Create a class called InMemoryProductRepository under the com.packt.webstore.domain.repository.impl package in the src/main/java source folder and add the following code to it:
            package com.packt.webstore.domain.repository.impl; 
     
            import java.sql.ResultSet; 
            import java.sql.SQLException; 
            import java.util.HashMap; 
            import java.util.List; 
            import java.util.Map; 
     
            import org.springframework.beans.factory.annotation
            .Autowired; 
            import org.springframework.jdbc.core.RowMapper; 
            import org.springframework.jdbc.core.namedparam
            .NamedParameterJdbcTemplate; 
            import org.springframework.stereotype.Repository; 
     
            import com.packt.webstore.domain.Product; 
            import com.packt.webstore.domain.repository
            .ProductRepository; 
     
            @Repository 
            public class InMemoryProductRepository implements 
            ProductRepository{ 
       
               @Autowired 
               private NamedParameterJdbcTemplate jdbcTemplate; 
     
               @Override 
               public List<Product> getAllProducts() { 
                  Map<String, Object> params = new HashMap<String, 
                  Object>(); 
                    List<Product> result = jdbcTemplate.query("SELECT * 
                    FROM products", params, new ProductMapper()); 
           
                    return result; 
               } 
     
             private static final class ProductMapper implements          
             RowMapper<Product> { 
               public Product mapRow(ResultSet rs, int rowNum)                 
               throws SQLException { 
               Product product = new Product(); 
                product.setProductId(rs.getString("ID")); 
                product.setName(rs.getString("NAME")); 
                product.setDescription(rs.getString("DESCRIPTION")); 
                product.setUnitPrice(rs.getBigDecimal("UNIT_PRICE")); 
                product.setManufacturer(rs.getString("MANUFACTURER")); 
                product.setCategory(rs.getString("CATEGORY")); 
                product.setCondition(rs.getString("CONDITION")); 
                product.setUnitsInStock(rs.getLong("UNITS_IN_STOCK")); 
                product.setUnitsInOrder(rs.getLong("UNITS_IN_ORDER")); 
                product.setDiscontinued(rs.getBoolean("DISCONTINUED")); 
                return product; 
               } 
             } 
           } 
    
  9. Open ProductController from the com.packt.webstore.controller package in the src/main/java source folder. Add a private reference to ProductRepository with the @Autowired (org.springframework.beans.factory.annotation.Autowired) annotation as follows:
            @Autowired 
            private ProductRepository productRepository; 
    
  10. Now alter the body of the list method as follows in ProductController:
            @RequestMapping("/products") 
            public String list(Model model) { 
               model.addAttribute("products", 
            productRepository.getAllProducts()); 
               return "products"; 
            } 
    
  11. Finally, open your products.jsp view file from src/main/webapp/WEB-INF/views/ and remove all the existing code and replace it with the following code snippet:
            <%@ taglib prefix="c" 
            uri="http://java.sun.com/jsp/jstl/core"%> 
     
            <html> 
            <head> 
            <meta http-equiv="Content-Type" content="text/html; 
            charset=ISO-8859-1"> 
            <link rel="stylesheet" 
       
            href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css
            /bootstrap.min.css"> 
            <title>Products</title> 
            </head> 
            <body> 
               <section> 
                  <div class="jumbotron"> 
                     <div class="container"> 
                        <h1>Products</h1> 
                        <p>All the available products in our store</p> 
                     </div> 
                  </div> 
               </section> 
     
               <section class="container"> 
                  <div class="row"> 
                     <c:forEach items="${products}" var="product"> 
                        <div class="col-sm-6 col-md-3"> 
                           <div class="thumbnail"> 
                              <div class="caption"> 
                                 <h3>${product.name}</h3> 
                                 <p>${product.description}</p> 
                                 <p>$${product.unitPrice}</p> 
            <p>Available ${product.unitsInStock} units in stock</p> 
                              </div> 
                           </div> 
                        </div> 
                     </c:forEach> 
                  </div> 
               </section> 
            </body> 
            </html> 
    
  12. Now run your application and enter the URL http://localhost:8080/webstore/products. You will see a web page showing product information as shown in the following screenshot:
    Time for action - creating a repository object

    Products page showing all the products info from the in-memory repository

What just happened?

The most important step in the previous section is step 8, where we created the InMemoryProductRepository class. Since we don't want to write all the data retrieval logic inside the ProductController itself, we delegated that task to another class called InMemoryProductRepository. The InMemoryProductRepository class has a single method called getAllProducts(), which will return a list of product domain objects.

As the name implies, InMemoryProductRepository is trying to communicate with an in-memory database to retrieve all the information relating to the products. We decided to use one of the popular in-memory database implementations called HyperSQL DB; that's why we added the dependency for that jar in step 2. And in order to connect and query the HyperSQL database, we decided to use the spring-jdbc API. This means we need the spring-jdbc jar as well in our project, so we added that dependency in step 1.

Having the required dependency in place, the next logical step is to connect to the database. In order to connect to the database, we need a data source bean in our application context. So in step 3, we created one more bean configuration file called RootApplicationContextConfig and added two bean definitions in it. Let's see what they are one by one.

The first bean definition we defined in RootApplicationContextConfig is to create a bean for the javax.sql.DataSource class:

@Bean 
public DataSource dataSource() { 
   EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); 
   EmbeddedDatabase db = builder 
         .setType(EmbeddedDatabaseType.HSQL) 
         .addScript("db/sql/create-table.sql") 
         .addScript("db/sql/insert-data.sql") 
         .build(); 
   return db; 
} 

In this bean definition, we employed EmbeddedDatabaseBuilder to build an in-memory database (HyperSQL) with a specified script file to create the initial tables and data to insert. If you watch closely enough, you can see that we are passing two script files to EmbeddedDatabaseBuilder:

  • create-table.sql: This file contains a SQL script to create a product table
  • insert-data.sql: This file contains a SQL script to insert some initial product records into the product table

So the next step is to create the specified script file in the respective directory so that EmbeddedDatabaseBuilder can use that script file in order to initialize the in-memory database. That's what we did in steps 4 and 5.

Okay, using the data source bean, we created and initialized the in-memory database, but in order to communicate with the database we need one more bean called NamedParameterJdbcTemplate; that is the second bean we defined in RootApplicationContextConfig. If you watch the bean definition for NamedParameterJdbcTemplate closely enough, you can see that we have passed the dataSource() bean as a constructor parameter to the NamedParameterJdbcTemplate bean:

@Bean 
public NamedParameterJdbcTemplate getJdbcTemplate() { 
   return new NamedParameterJdbcTemplate(dataSource()); 
} 

Okay, so far so good. We created a bean configuration file (RootApplicationContextConfig) and defined two beans in it to initialize and communicate with the in-memory database. But for Spring MVC to actually pick up this bean configuration file and create beans (objects), for those bean definitions we need to hand over this file to Spring MVC during the initialization of our application. That's what we did in step 6 through the DispatcherServletInitializer class:

@Override 
protected Class<?>[] getRootConfigClasses() { 
   return new Class[] { RootApplicationContextConfig.class }; 
} 

As I have already mentioned, we did all the previously specified steps in order to use the NamedParameterJdbcTemplate bean in our InMemoryProductRepository class to communicate with the in-memory database. You may then be wondering what we did in step 7. In step 1, we are just creating an interface called ProductRepository, which defines the expected behavior of a product repository. As of now, the only expected behavior of a ProductRepository is to return a list of product domain objects (getAllProducts), and our InMemoryProductRepository is just an implementation of that interface.

Why do we have an interface and an implementation for the product repository? Remember we are actually creating a Persistence layer for our application. Who is going to use our Persistence layer repository object? Possibly a controller object (in our case ProductController) from the Controller layer, so it is not best practice to connect two layers (Controller and Persistence) with a direct reference. Instead we can have an interface reference in the controller so that in future if we want to, we can easily switch to different implementations of the repository without making any code changes in the controller class.

That's the reason in step 9 that we had the ProductRepository reference in our ProductController not the InMemoryProductRepository reference. Notice the following lines in ProductController:

@Autowired 
private ProductRepository productRepository; 

Okay, but why is the @Autowired annotation here? If you observe the ProductController class carefully, you may wonder why we didn't instantiate any object for the productRepository reference. Nowhere could we see a single line saying something like productRepository = new InMemoryProductRepository().

So how come executing the line productRepository.getAllProducts() works fine without any NullPointerException in the list method of the ProductController class?

model.addAttribute("products", productRepository.getAllProducts() ); 

Who is assigning the InMemoryProductRepository object to the productRepository reference? The answer is that the Spring framework is the one assigning the InMemoryProductRepository object to the productRepository reference.

Remember you learned that Spring would create and manage beans (objects) for every @controller class? Similarly, Spring would create and mange beans for every @Repository class as well. As soon as Spring sees the annotation @Autowired on top of the ProductRepository reference, it will assign an object of InMemoryProductRepository to that reference, since Spring already created and holds the InMemoryProductRepository object in its object container (web application context).

Remember we configured the component scan through the following annotation in the web application context configuration file:

@ComponentScan("com.packt.webstore") 

And you learned earlier that if we configure our web application context with @ComponentScan annotation, it not only detects controllers (@controller), it also detects other stereotypes such as repositories (@Repository) and services (@Service) as well.

Since we added the @Repository annotation on top of the InMemoryProductRepository class, Spring knows that if any reference of the type productRepository has an @Autowired annotation on top of it, then it should assign the implementation object InMemoryProductRepository to that reference. This process of managing dependencies between classes is called dependency injection or wiring in the Spring world. So to mark any class as a repository object, we need to annotate that class with the @Repository (org.springframework.stereotype.Repository) annotation.

Okay, you understand how the Persistence layer works, but after the repository object returns a list of products, how do we show it in the web page? If you remember how we added our first product to the model, it is very similar to that instead of a single object. This time we are adding a list of objects to the model through the following line in the list method of ProductController:

model.addAttribute("products", productRepository.getAllProducts() ); 

In the previous code, productRepository.getAllProducts() just returns a list of product domain objects (List<Product> ) and we directly add that list to the model.

And in the corresponding view file (products.jsp), using the <C:forEach> tag, we loop through the list and show each product's information inside a styled <div> tag:

<c:forEach items="${products}" var="product"> 
<div class="col-sm-6 col-md-3" style="padding-bottom: 15px"> 
<div class="thumbnail"> 
      <div class="caption"> 
         <h3>${product.name}</h3> 
         <p>${product.description}</p> 
         <p>${product.unitPrice} USD</p> 
<p> Available ${product.unitsInStock} units in stock </p> 
      </div> 
   </div> 
</div> 
</c:forEach>

Again, remember the products text in the ${products} expression is nothing but the key that we used while adding the product list to the model from the ProductController class.

The for each loop is a special JavaServer Pages Standard Tag Library (JSTL) looping tag that will run through the list of products and assign each product to a variable called product (var="product") on each iteration. From the product variable, we are fetching information such as the name, description, and price of the product and showing it within <h3> and <p> tags. That's how we are finally able to see the list of products in the products web page.

Tip

The JSTL is a collection of useful JSP tags that encapsulates the core functionality common to many JSP applications.

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

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