接上文内容,上一节中的示例中完成了支持分页的商品列表查询功能,不过我们的目标是打造一个商品管理后台,本节中还需要补充添加、修改、删除商品的功能,这些功能依靠Mybatis操作数据库,并通过SpringMVC的数据验证功能检查数据合法性。既然是后台,那么肯定还需要验证和登录,这部分使用拦截器(interceptor)来实现。此外,我们还需要解决诸如中文处理、静态资源过滤等经常会造成麻烦的小问题。

从头阅读传送门

来看一下完成的效果,点击原商品列表功能/product/list,首先提示登录

如果登录出错,做相应的提示

登录成功后进入商品列表页面

选择任意商品,点击修改,打开修改商品页面。名称和价格两个属性输入框存储原值以方便修改

修改内容不符合规范则触发SpringMVC的验证提示

点击退出则重回登录页。

接下来叙述实现的主要环节,先回到petstore-persist模块,为商品管理增加插入(insert)、更新(update)、删除(delete)三个方法。

ProductMapper.Java

  1. void addProduct(Product product);
  2. void updateProduct(Product product);
  3. void deleteProduct(int id);

在Product.xml中增加匹配三个方法的SQL映射:

  1. <insert id="addProduct" parameterType="com.example.petstore.persist.model.Product"
  2. useGeneratedKeys="true" keyProperty="id">
  3. insert into t_product(p_name,p_price) values(#{name},#{price})
  4. </insert>
  5. <update id="updateProduct" parameterType="com.example.petstore.persist.model.Product">
  6. update t_product set
  7. p_name=#{name},p_price=#{price} where p_id=#{id}
  8. </update>
  9. <delete id="deleteProduct" parameterType="int">
  10. delete from t_product where p_id=#{id}
  11. </delete>

下面切换到petstore-web模块,先添加一个拦截器检查用户是否登录。SpringMVC的拦截器可以看作是Controller的守门警卫,其定义的preHandle方法和postHandle方法分别守在Controller的进出口,可以拦截住进出的客人(Request)做诸如登记、检查、对输出信息再处理等操作。我们这里的拦截器非常简单,在preHandle中检查用户的Session中是否携带登录信息,检查通过则进入目标页面,否则重新分派到登录页面。

  1. public class AuthorityInterceptor implements HandlerInterceptor {
  2. @Override
  3. public void afterCompletion(HttpServletRequest request,
  4. HttpServletResponse response, Object handler, Exception ex) {
  5. }
  6. @Override
  7. public void postHandle(HttpServletRequest request, HttpServletResponse response,
  8. Object handler, ModelAndView modelAndView) {
  9. }
  10. @Override
  11. public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
  12. Object handler) throws Exception {
  13. String backUrl = request.getServletPath().toString();
  14. String sessionName = "adminUser";
  15. String currentUser = (String)request.getSession(true).getAttribute(sessionName);
  16. if(currentUser != null && currentUser.equals("admin")) {
  17. return true;
  18. }
  19. response.sendRedirect(request.getContextPath() + "/admin/toLogin?backUrl=" + backUrl);
  20. return false;
  21. }
  22. }

在配置文件中启用拦截器,本例中是spring-mvc.xml

  1. <mvc:interceptors>
  2. <mvc:interceptor>
  3. <mvc:mapping path="/**" />
  4. <mvc:exclude-mapping path="/admin/**" />
  5. <mvc:exclude-mapping path="/css/**" />
  6. <mvc:exclude-mapping path="/js/**" />
  7. <bean class="com.example.petstore.web.interceptor.AuthorityInterceptor "/>
  8. </mvc:interceptor>
  9. </mvc:interceptors>

配置文件中除了指定拦截器的完整类名外,还设置了拦截规则,这里设置为拦截所有请求,但设置了三个例外。

/admin/目录下的请求为登录相关处理,例如登录页面,这些需要能被未登录的用户访问。/css/和/js/目录下为静态文件,不应该被拦截。而且,静态文件也不应该交给SpringMVC的前置控制器DispatcherServlet处理,这样会造成不必要的性能损耗。所以还应该在spring-mvc.xml中加入:

  1. <mvc:resources mapping="/css/**" location="/css/"/>
  2. <mvc:resources mapping="/js/**" location="/js/"/>

这是告知SpringMVC,不要处理对这两个目录的请求。

(注:处理静态文件的访问应该交给Nginx或Apache等Web服务器,如果仅使用Tomcat等Java容器更好的方式是通过在web.xml中设置servlet-mapping来处理。这里只是演示SpringMVC功能)

被拦截的请求交给登录控制器AdminController处理

  1. @Controller
  2. @SessionAttributes("adminUser")
  3. @RequestMapping("/admin")
  4. public class AdminController {
  5. @RequestMapping(value="/toLogin")
  6. public String toLogin(@ModelAttribute("adminModel") AdminModel adminModel) {
  7. return "authority/login";
  8. }
  9. @RequestMapping(value="/login", method = {RequestMethod.GET, RequestMethod.POST})
  10. public String login(Model model, @ModelAttribute("adminModel") AdminModel adminModel, @RequestParam("backUrl") String backUrl) throws IOException {
  11. boolean valid = true;
  12. String message = null;
  13. if(adminModel == null) {
  14. message = "非法操作";
  15. valid = false;
  16. } else if (!adminModel.getUsername().equals("admin")) {
  17. message = "用户名不存在";
  18. valid = false;
  19. } else if (!adminModel.getPassword().equals("123456")) {
  20. message = "密码不正确";
  21. valid = false;
  22. }
  23. if(!valid) {
  24. ErrorModel errorModel = new ErrorModel();
  25. errorModel.setMessage(message);
  26. errorModel.setPage("返回上一页", "javascript:history.back();");
  27. model.addAttribute("errorModel", errorModel);
  28. return "comm/error";
  29. } else {
  30. model.addAttribute("adminUser", adminModel.getUsername());
  31. if(StringUtils.isBlank(backUrl)) {
  32. return "redirect:/product/list";
  33. } else {
  34. return "redirect:" + backUrl;
  35. }
  36. }
  37. }
  38. @RequestMapping(value="/logout")
  39. public String logout(ModelMap modelMap, SessionStatus sessionStatus, @ModelAttribute("adminModel") AdminModel adminModel) throws IOException {
  40. sessionStatus.setComplete();
  41. return "authority/login";
  42. }
  43. }

AdminController定义了三个方法,toLogin直接返回登录视图,即登录页。logout先删除Session中的登录信息,再返回登录视图,这时用户重归未登录状态。login方法处理登录页中提交的登录请求,登录和授权不在本文介绍范围,这里只演示性的检查了用户名和密码为指定值则通过登录。注意这个方法中并没有直接操作Session,帮我们完成工作的是类名上的@SessionAttributes注解,对于它声明的属性名,在发生向ModelMap中写入时会转存到Session中,并一直有效直到调用SessionStatue.setComplete。

登录成功后就可以执行增加、删除和修改的操作了。先看删除,通过ajax方式调用Controller接口处理:

  1. <td><a href="javascript:void(0);" onclick="javascript:delProduct(${item.id});">删除</a></td>
  1. <script type="text/javascript">
  2. function delProduct(id) {
  3. if(!window.confirm("确定要删除吗?")) {
  4. return false;
  5. }
  6. $.ajax({
  7. data:"id=" + id,
  8. type:"GET",
  9. dataType: 'json',
  10. url:"<c:url value='/product/delete'/>",
  11. error:function(data){
  12. alert("删除失败");
  13. },
  14. success:function(data){
  15. if(data.code > 0) {
  16. alert("删除成功");
  17. document.forms[0].submit();
  18. } else {
  19. alert("删除失败");
  20. }
  21. }
  22. });
  23. }
  24. </script>
  1. @RequestMapping(value="/delete", method = {RequestMethod.GET})
  2. @ResponseBody
  3. public Map<String, String> delete(@RequestParam(value="id") int id) throws IOException {
  4. this.productService.deleteProduct(id);
  5. Map<String, String> map = new HashMap<String, String>(1);
  6. map.put("code", "1");
  7. return map;
  8. }

添加和删除操作导向同一个JSP处理,通过是否携带id参数区分

  1. <a href="<c:url value='/product/toAddOrUpdate'/>">添加新商品</a>
  1. <a href="<c:url value='/product/toAddOrUpdate'/>?id=${item.id}">修改</a>
  1. @RequestMapping(value="/toAddOrUpdate", method = {RequestMethod.GET})
  2. public String toAddOrUpdate(Model model, @RequestParam(value="id", defaultValue="0") int id) {
  3. if(id > 0) {
  4. Product product = this.productService.selectById(id);
  5. if(product != null) {
  6. model.addAttribute("productModel", product);
  7. } else {
  8. ErrorModel errorModel = new ErrorModel();
  9. errorModel.setMessage("商品不存在或已下架");
  10. Map<String, String> pages = new HashMap<String, String>();
  11. pages.put("返回上一页", "javascript:history.back();");
  12. errorModel.setPages(pages);
  13. model.addAttribute("errorModel", errorModel);
  14. return "comm/error";
  15. }
  16. } else {
  17. model.addAttribute("productModel", new Product());
  18. }
  19. return "product/addOrUpdate";
  20. }

如果请求中带有id参数,则预读出商品信息并存入ModelMap,在修改页面中显示这些信息方便用户浏览和修改。

添加/修改商品页面:

  1. <form:form action="${pageContext.request.contextPath}/product/addOrUpdate" method="POST" modelAttribute="productModel">
  2. <c:if test="${productModel.id==0}">
  3. 添加新商品
  4. </c:if>
  5. <c:if test="${productModel.id!=0}">
  6. 修改商品信息, 商品ID: ${productModel.id}
  7. </c:if>
  8. <div></div>
  9. <form:hidden path="id" />
  10. <div>商品名称: <form:input path="name" autocomplete="off" placeholder="商品名称" /><form:errors path="name" cssClass="error" /></div>
  11. <div>商品价格: <form:input path="price" autocomplete="off" placeholder="商品价格" /><form:errors path="price" cssClass="error" /></div>
  12. <div><button type="submit">提交</button></div>
  13. </form:form>

在用户提交商品信息时,通常我们希望做一下检查以避免用户提交了不符合规定的内容。SpringMVC对服务器端验证提供了优秀的支持方案。我们来看对商品名称的检查:

第一步,在addOrUpdate.jsp中添加<form:errors path="name" cssClass="error" />用于显示错误提示信息。

第二步,修改Model类Product.java,在name属性上添加注解@Pattern(regexp="[\\u4e00-\\u9fa5]{4,30}"),即限定为4至30个中文字符。

第三步,在Controller中检查BindingResult的实例是否包含错误:

  1. @RequestMapping(value="/addOrUpdate", method = {RequestMethod.POST})
  2. public String addOrUpdate(Model model, @Valid @ModelAttribute("productModel") Product productModel, BindingResult bindingResult) {
  3. if(bindingResult.hasErrors()) {
  4. return "product/addOrUpdate";
  5. }
  6. int id = productModel.getId();
  7. if(id > 0) {
  8. this.productService.updateProduct(productModel);
  9. } else {
  10. this.productService.addProduct(productModel);
  11. }
  12. return list(model, new SearchModel(), PagingList.DEFAULT_PAGE_INDEX, PagingList.DEFAULT_PAGE_SIZE);
  13. }

做了这些还不够,还需要hibernate的帮助,增加一个依赖

  1. <dependency>
  2. <groupId>org.hibernate</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>5.2.4.Final</version>
  5. </dependency>

到这里主要的代码就完成了,不过还要处理一下烦人的中文字符乱码问题,修改web.xml,添加:

  1. <filter>
  2. <filter-name>encodingFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  4. <init-param>
  5. <param-name>encoding</param-name>
  6. <param-value>UTF-8</param-value>
  7. </init-param>
  8. </filter>
  9. <filter-mapping>
  10. <filter-name>encodingFilter</filter-name>
  11. <url-pattern>/*</url-pattern>
  12. </filter-mapping>

最后,介绍一下使用Maven的jetty-maven-plugin插件来测试项目,为此,还要稍稍改造一下petstore-persist模块。把src/main/java/com/example/petstore/persist/model/Product.xml移动到资源目录,即 src/main/resources/com/example/petstore/persist/model/Product.xml

修改Maven配置文件,如D:\Maven\conf\settings.xml,添加

  1. <pluginGroups>
  2.   <pluginGroup>org.mortbay.jetty</pluginGroup>
  3. </pluginGroups>

修改petstore-parent下的pom.xml,添加

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.apache.maven.plugins</groupId>
  5. <artifactId>maven-compiler-plugin</artifactId>
  6. <version>2.3.2</version>
  7. <configuration>
  8. <source>1.8</source>
  9. <target>1.8</target>
  10. <encoding>utf8</encoding>
  11. </configuration>
  12. </plugin>
  13. </plugins>
  14. </build>

修改petstore-web下的pom.xml,添加

  1. <build>
  2. <finalName>petstore-web</finalName>
  3. <plugins>
  4. <plugin>
  5. <groupId>org.mortbay.jetty</groupId>
  6. <artifactId>jetty-maven-plugin</artifactId>
  7. <version>8.1.16.v20140903</version>
  8. <configuration>
  9. <scanIntervalSeconds>10</scanIntervalSeconds>
  10. <webAppConfig>
  11. <contextPath>/petstore</contextPath>
  12. </webAppConfig>
  13. </configuration>
  14. </plugin>
  15. </plugins>
  16. </build>

打开CMD命令窗口,切换工作目录都petstore-parent源码目录下,执行

mvn clean install

把petstore-persist模块安装到本地Maven仓库中,然后切换到petstore-web目录下,执行

mvn jetty:run

该命令启动jetty服务器并默认绑定8080端口,如果8080已被占用,可以指定其他端口

mvn jetty:run -Djetty.port=9999

启动成功后,打开浏览器输入 http://localhost:8080/petstore/index.jsp

那么,你是否顺利看到站点首页了呢?

本节源码下载

(完)

从头开始基于Maven搭建SpringMVC+Mybatis项目(4)的更多相关文章

  1. 从头开始基于Maven搭建SpringMVC+Mybatis项目(3)

    接上文内容,本节介绍基于Mybatis的查询和分页功能,并展示一个自定义的分页标签,可重复使用以简化JSP页面的开发. 从头阅读传送门 在上一节中,我们已经使用Maven搭建好了项目的基础结构,包括一 ...

  2. 从头开始基于Maven搭建SpringMVC+Mybatis项目(1)

    技术发展日新月异,许多曾经拥有霸主地位的流行技术短短几年间已被新兴技术所取代. 在Java的世界中,框架之争可能比语言本身的改变更让人关注.近几年,SpringMVC凭借简单轻便.开发效率高.与spr ...

  3. 从头开始基于Maven搭建SpringMVC+Mybatis项目(2)

    接上文内容,本节介绍Maven的聚合和继承. 从头阅读传送门 互联网时代,软件正在变得越来越复杂,开发人员通常会对软件划分模块,以获得清晰的设计.良好的分工及更高的可重用性.Maven的聚合特性能把多 ...

  4. maven搭建springmvc+mybatis项目

    上一篇中已经成功使用maven搭建了一个web项目,本篇描述在此基础上怎么搭建一个基于springmvc+mybatis环境的项目. 说了这么久,为什么那么多人都喜欢用maven搭建项目?我们都知道m ...

  5. Maven搭建SpringMVC+Mybatis项目详解

    前言 最近比较闲,复习搭建一下项目,这次主要使用spring+SpringMVC+Mybatis.项目持久层使用Mybatis3,控制层使用SpringMVC4.1,使用Spring4.1管理控制器, ...

  6. Maven搭建SpringMVC+MyBatis+Json项目(多模块项目)

    一.开发环境 Eclipse:eclipse-jee-luna-SR1a-win32; JDK:jdk-8u121-windows-i586.exe; MySql:MySQL Server 5.5; ...

  7. Maven搭建SpringMVC+Hibernate项目详解 【转】

    前言 今天复习一下SpringMVC+Hibernate的搭建,本来想着将Spring-Security权限控制框架也映入其中的,但是发现内容太多了,Spring-Security的就留在下一篇吧,这 ...

  8. Maven搭建SpringMVC + SpringJDBC项目详解

    前言 上一次复习搭建了SpringMVC+Mybatis,这次搭建一下SpringMVC,采用的是SpringJDBC,没有采用任何其他的ORM框架,SpringMVC提供了一整套的WEB框架,所以如 ...

  9. Maven搭建SpringMVC+Hibernate项目详解

    前言 今天复习一下SpringMVC+Hibernate的搭建,本来想着将Spring-Security权限控制框架也映入其中的,但是发现内容太多了,Spring-Security的就留在下一篇吧,这 ...

随机推荐

  1. 为什么还坚持.NET? 找一门适合自己的语言去做编程

    为什么还坚持.NET? 找一门适合自己的语言去做编程 接触了.NET快十二年了,现在专注于分布式服务的开发. 中间经历过各种编程语言的诱惑,ios等. 前几年才对自己有比较明确的定位 技术上:找到适合 ...

  2. 商城项目回顾整理(二)easyUi数据表格使用

    后台主页: 商品的数据表格展示 引入用户表数据表格展示 引入日志表数据表格展示 引入订单表数据表格展示 后台主页代码: <%@ page language="java" co ...

  3. 一些常用的vim编辑器快捷键:

    一些常用的vim编辑器快捷键: h」.「j」.「k」.「l」,分别控制光标左.下.上.右移一格. 按「ctrl」+「b」:屏幕往“后”移动一页. 按「ctrl」+「f」:屏幕往“前”移动一页. 按「c ...

  4. Xamarin Android 中Acitvity如何传递数据

    在xamarin android的开发中,activity传递数据非常常见,下面我也来记一下在android中activity之间传递数据的几种方式, Xamarin Android中Activity ...

  5. python基础知识——字符串详解

    大多数人学习的第一门编程语言是C/C++,个人觉得C/C++也许是小白入门的最合适的语言,但是必须承认C/C++确实有的地方难以理解,初学者如果没有正确理解,就可能会在使用指针等变量时候变得越来越困惑 ...

  6. 4.sass的分支结构、循环结构、函数

    分支结构 在sass里,可以使用@if让我们根据一些条件来应用特定的样式 结构: @if 条件 { } 如果条件为真的话,括号里的代码就会释放出来 例如: $use-refixes:true; .ro ...

  7. Yum database disk image is malformed

    使用 yum update 时使用Ctrl+C 后,再用yum 安装其他软件的时候收到:Yum database disk image is malformedyum clean dbcache 清除 ...

  8. git pull与git fetch的区别

    git pull: 取回远程主机某个分支的更新,再与本地的指定分支合并. 用法: git pull <远程仓库> <远程分支名>:<本地分支名> // 如 git ...

  9. Java Error : type parameters of <T>T cannot be determined during Maven Install

    遇到了一个问题如下: Caused by the combination of generics and autoboxing. 这是由于泛型和自动装箱联合使用引起的. 可以查看以下两个回答:   1 ...

  10. python爬取大众点评

    拖了好久的代码 1.首先进入页面确定自己要抓取的数据(我们要抓取的是左侧分类栏-----包括美食.火锅)先爬取第一级分类(美食.婚纱摄影.电影),之后根据第一级链接爬取第二层(火锅).要注意第二级的p ...