First Spring MVC Application
I am trying the tutorial for Spring MVC in NetBeans.The tutorial recommends implementing a service, separate from the controller, to handle the business logic.
- Set up a Spring MVC project in NetBeans and run it.
- The tutorial uses Spring 3 and SimpleFormController, which is not available in Spring 4. Select Spring 3 when creating the project.
- All .htm files are set to map to the dispatcher.
- The welcome file is set to redirect.jsp
- Redirect.jsp contains a redirect to index.htm.
- The dispatcher servlet is part of the Spring framework, part of Spring MVC.
- The name of the dispatcher servlet is 'dispatcher', so its associated config file is named 'dispatcher-servlet'.
- The dispatcher config file defines three beans, for URL mapping, a view resolver, and a view controller.
- The URL mapping associates names or references for beans. Use urlMap to associate a URL to a bean reference. Use mappings to associate a URL with a bean name.
- The view resolver expands names into full paths.
- The index controller is the controller that displays the index page. It is a class from Spring. It is accessed with a logical name. The view resolver will expand the logical name to a full path.
- The application has a view that accepts a name and a view that displays the name.
- The controller decides which view to display. The controller sends a model to the view. The model contains data for the view to display. The name of the controller is HelloController.
- Business logic is separated from the controller into a service. A service is a logical category, it is just another java class. The business logic will process the name from the form.
- Create the java class for the service named HelloService. The service has a method that echoes 'Hello some-name!'. The parameter is some-name.
- The tutorial makes use of the deprecated Simple Form Controller, but recommends using annotated controllers. It must use Spring 3 for this, not Spring 4.
- The SimpleFormController defines a command, a success view and a form view.
- The command is the name of the class to send to the form. The form will use tags defined in Spring that use properties from a bean to populate the form.
- The sucessView is the view to show if validation succeeds.
- The formView is the view for entering data and the view to show if errors exist.
- Create the Name class to pass to the form. Add a property to the class for a value. Properties are variables with setters and getters.
- The onSubmit command is overridden in the SimpleFormController. It accepts the request, response, command, and errors. The command is the object that has the data from the form. The errors can be filled with error messages and sent back to the form. The method returns a ModelAndView which is returned from the getSuccessView method. Objects can be added to the model with addObject, like adding a value to a map.
- In HelloController, create a property for the service. The getter is not needed.
- Add a bean definition to applicationContext.xml for HelloService.
- Add a bean definition to dispather-servlet.xml for HelloController.
- The helloView echoes the helloMessage using EL (Expression Language).
- The nameView contains the form. Add a taglib for the Spring tags.
- Use spring:nested path to associate the command with the form.
- In the form, use spring:bind to associate a field with a property in the bean. In the body of the tag, use status.expression and status.value in EL to retrieve the name and value of the associated property.
- Instead of changing the entry point of the application, I modified the index.jsp with a link to hello.htm, from the current location.
- The mapping for the hello controller is handled by ControllerClassNameHandlerMapping from the Spring framework. Add a bean definition to dispatcher-servlet.xml for this bean. The default mapping is from HelloController to hello.htm.
Step-by-Step Spring MVC Application
My next attempt will be another tutorial from Netbeans, Spring MVC Step-byStep.The tutorial is using Spring 2.5, I am using Spring 4.
Part 1 - Hello Example
The first change is that the default page is index.jsp in the tutorial, but redirect.jsp in Spring 4.The tutorial recommends changing the welcome file list and editing the index page so it does not redirect, but generates a static page. I chose not to do that.
The tutorial uses GlassFish, I am using Tomcat.
I created the project using the NetBeans templated for a Spring MVC project.
The tutorial recommends adding a bean with the name of 'hello.html', but the name should be resolved normally through the ControllerClassHandlerMapping bean that was added to the dispatcher-servlet.xml automatically when NetBeans created the Spring MVC project. Update: the tutorial starts implementing relationships manually and then later introduces a view resolver.
In the handleRequest method, the ModelAndView is created with "hello.jsp", it should be created with "hello", then the Mapping controller will find it.
Part 2 - Inventory Example
Instead of changing HelloController, I implemented InventoryController as a new controller.- Create an include file for jstl/core and jstl/fmt. The taglib for Spring forms is now part to the Spring framework, so it does not have to be included.
- At this point, the tutorial uses a InternalResourceViewResolver class to decouple the view from the controller, by using a logical name for the view page.
Part 3 - Business Logic
- Add a class for a Product, with price and description.
- Add am interface for a ProductManager for handling products. It contains a business method for increasing the price for all products, and a method to retrieve all products. Add other methods for manipulating the product class.
- The controller will access the interface and Spring will supply an appropriate implementation. (Interfaces also allow for JDK proxying.)
- Create a concrete class that implements the manager interface.
Part 4 - Web Interface
- Create a controller. Add a product manager property. Return a view that displays the records from the list in the product manager.
- Display the records in the view.
- Use a resource bundle to customize the appearance of the form.
- Create some static test data.
- Use spring-form.tld to bind the form data in the view to the model. The tutorial shows how to install the form taglib, but it is part of Spring 4.
- Add a style sheet for displaying errors in red.
- The tutorial redirects to home.htm, but I created a new controller for inventory, so I redirected to inventory.htm.
- Create a price increase class. Add a property for an integer percentage.
- Create a validator that extends org.springframework.validation.Validator. Override the supports and validate methods. Test that the percentage is not too small or too large.
- Add a form controller, that displays the form, accepts a percentage and modifies the product prices. In the controller, test the BindingResult parameter after the call, to see if errors exist.
- Aarrgghhh! I have built this in Spring 4, but it requires SimpleFormController from Spring 3! I may be saved. A post on converting from SimpleFormController uses the exact example from this tutorial. The example is not exactly the same: pass the price increase object to the validator; priceIncrease should be priceincrease when validation fails; redirect to inventory.htm, not home.htm, to show the inventory with the increased prices.
- I used annotations to implement the controller in Spring 4.
- I modified the dispatcher-servlet.xml file to add support for annotation scanning and for the MVC annotations. The file begins as:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <mvc:annotation-driven /> <context:component-scan base-package="springapp"/>
- I modified the dispatcher-servlet.xml file to add support for annotation scanning and for the MVC annotations. The file begins as:
- The controller is mapped with the logical name 'increase', but it will be entered as 'increase.htm' and then resolve to 'increase'.
- One method handles GET, the other handles POST.
- In the GET handler, populate the model with the products from the product manager.
- In the POST handler, validate that the percentage is good, then use the product manager to increase the price. Use the ModelAttribute annotation to fill a parameter with the values of the elements from the form.
- Resolve to the inventory, to see the changes in the prices.
- The product manager and validator will be injected by Spring.
- Add more messages to the resource bundle for the error processing.
- Add a link to the price increase controller in the inventory page.
@Controller @RequestMapping("/increase") //controller namer will resolve increase.htm to here public class IncreaseController { @Autowired ProductManager productManager; @Autowired private ProductPriceIncreaseValidator priceIncreaseValidator; @RequestMapping(method = RequestMethod.POST) public String onSubmit( @ModelAttribute("priceIncrease") ProductPriceIncrease priceIncrease, BindingResult result) { priceIncreaseValidator.validate(priceIncrease, result); if (result.hasErrors()) { return "priceincrease"; } // Validator has succeeded. // Perform necessary actions and return to success page. productManager.increasePrice(priceIncrease.getPercentage()); return "redirect:/inventory.htm"; } @RequestMapping(method = RequestMethod.GET) public String initializeForm(ModelMap model) { // Perform and Model / Form initialization model.addAttribute("prod", productManager.getProducts()); ProductPriceIncrease priceIncrease = new ProductPriceIncrease(); priceIncrease.setPercentage(20); model.addAttribute("priceIncrease", priceIncrease); return "priceincrease"; } }
Part 5 - Adding Data Persistence
- I used mysql instead of hsql.
- Create the table and populate it with some data.
- Create a product data access object (DAO) interface. The interface defines the functionality of access to the database. It is similar to the concept of the product manager interface. By defining the methods that manipulate the database using an interface, one of several implementations could be injected by Spring.
- Create the class that implements the DAO interface.
- The tutorial extends the class from SimpleJdbcDaoSupport and uses SimpleJdbcTemplate, which are deprecated. The replacements are JdbcDaoSupport and JdbcTemplate.
-
The saveProduct method did not work as described in the tutorial. The MapSqlParameter class threw a Non-Serializable error. I used an object array to substitute positional parameters, instead of named parameters. When I tried to pass an array of types, I received an error that parameter 3 was missing.
@Override public void saveProduct(Product prod) { logger.info("Saving product: " + prod.getDescription()); // int count = getJdbcTemplate().update( // "update products set description = :description, price = :price where id = :id", // new MapSqlParameterSource() // .addValue("description", prod.getDescription()) // .addValue("price", prod.getPrice()) // .addValue("id", prod.getId())); int count = getJdbcTemplate().update( "update products set description = ?, price = ? where id = ?", new Object[] {prod.getDescription(), prod.getPrice(), prod.getId()}); // new Object[] {Types.VARCHAR, Types.DECIMAL, Types.INTEGER} // ); logger.info("Rows affected: " + count); }
- Add an id property and toString method to the product class.
- The testing example used a deprecated class. I found a resource at Spring By Example that helped translate to annotation syntax.
- @ContextConfiguration has a locations attribute that expects an array of strings containing the location of a config file.
- @TransactionConfiguration can specify an addtional config file that apppends -context.xml to the name of the class. I did not use one. I placed all the configuration in the context file.
- JdbcTestUtils has methods that are analogous to the examples from the tutorial.
package springapp.repository; import java.util.List; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; import springapp.domain.Product; import static org.junit.Assert.assertEquals; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.test.jdbc.JdbcTestUtils; @ContextConfiguration(locations={"classpath:test-context.xml"}) @TransactionConfiguration @Transactional(readOnly=true) public class ProductDaoTransactionUnitTest extends AbstractTransactionalJUnit4SpringContextTests { //protected final Log logger = LogFactory.getLog(getClass()); protected static int SIZE = 3; @Autowired protected ProductDao productDao = null; Resource resource; @Test @Transactional(readOnly=false) public void testMain() { resource = new ClassPathResource("load_data.sql"); JdbcTestUtils.deleteFromTables(jdbcTemplate, "products"); JdbcTestUtils.executeSqlScript(jdbcTemplate, resource, true); testListProduct(); testSaveProduct(); } public void testSaveProduct() { List
products = productDao.getProductList(); assertEquals("wrong number of products?", 3, products.size()); for (Product p : products) { p.setPrice(200.12); productDao.saveProduct(p); } List updatedProducts = productDao.getProductList(); for (Product p : updatedProducts) { assertEquals("wrong price of product?", 200.12, p.getPrice(), 0.001); } } private void testListProduct() { List products = productDao.getProductList(); assertEquals("wrong number of products?", 3, products.size()); } } - test-context.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> <!-- the test application context definition for the jdbc based tests --> <bean id="productDao" class="springapp.repository.JdbcProductDao"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="customerDAO" class="springapp.repository.JdbcProductDao"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven/> </beans>
- next 1
- next 2
Part 6 - Integrating the Web Layer with the Persistence Layer
- Modify the SimpleProductManager so it is backed by the DAO, instead of a List. Update the list in the DAO after the price increases.
- The tutorial adds an applicationContext.xml by adding a listener to web.xml for the Spring ContextLoaderListener. This step was already done by the Spring MVC template.
- Move the product manager and DAO classes to the application context XML file.
- The commons-dbcp is now commons-dbcp2. I could also use org.springframework.jdbc.datasource.DriverManagerDataSource as the data source class.
- I downloaded aopalliance, aspectjweaver and aspectj JAR files and added them to the library.
- Once I enabled transactions, I was not allowed to run the query from the tutorial. The advice indicates that save methods in the ProductManager can write to the database, but the method in the ProductManager starts with increase. I changed the advice from save* to increase* and it worked.
- To agree with the testing for the DAO, I changed the main application context to use transaction annotations, too. After updating applicationContext.xml, the only change was to add Transactional(readOnly=true) to the SimpleProductManager and add Transactional(readOnly=false).
Spring MVC Hibernate
The next tutorial is an integration of Hibernate with Spring MVC. It uses Spring 3 and Hibernate 3. I will attempt it with Spring 4 and Hibernate 4.
- The syntax for creating the table is incorrect. The primary key is EMPID, not ID. A comma is missing before the primary key statement.
- The controller class has syntax errors. The Map declaration in listEmployees is missing a comma between String and Object. The prepareListofBean method has the wrong capitalization for EmplyeeBean when declaring the List.
- The dispatcher servlet is named sdnext. It is not necessary to indicate the name of the config file, if it is the servletName-context.xml.
- Errors in XML for empty element for property-placeholder, component-scan and annotation-drive. Made each into a singleton element.
- Build failed. Could not find TransactionInterceptor. Added aopalliance.jar to resolve dependency.
- Build failed. Could not find FilterDefinition. This is related to using Hibernate 4 instead of Hibernate 3. Changed the config file to resolve dependency.
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> --other properties here </bean> <bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
- Build failed. Added mysql driver.
- The web app runs, but the Hibernate session was not created. Add @Transactional to the controller class.
- Cannot find save.html. The action in the add.html form should be to save.html, not /sdnext/save.html. I added the name of the web app with ${pageContext.request.contextPath}.
- The value fields in the form input are ignored. I modified the handler in the controller so it accepts the ModelMap first, then the employee and binding results. Using put for the command did not work, but addAttribute did. I removed the local map and used the ModelMap instead.
- The column name for the address field is incorrect. In Employee it is empaddress, in the database it is address.
- After these changes, I can add a record to the database and it is displayed in the add page.
- The edit command does not list the current values in the bean, except for the id. It has the same problem with the ModelMap. Also, the commandName for the form is command, not employee.
- The method names for edit and delete are backwards.
- The edit method is accessed from the table of employees. The link uses a query string parameter. I changed it to a path variable and retrieved the id from it. The method must use the id to retrieve the bean from the database. The bean is not in the command object. I changed the link so that it includes the name of the web app with ${pageContext.request.contextPath}.
- I modified delete the same as edit. Do not send a null command to the form, send a default employee.
- I am able to add, list, edit and delete employees.
No comments:
Post a Comment