接上文内容,上一节中的示例中完成了支持分页的商品列表查询功能,不过我们的目标是打造一个商品管理后台,本节中还需要补充添加、修改、删除商品的功能,这些功能依靠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. spring boot自定义starter

    1.spring boot 项目中自定义jar包 2.项目目录 3.src/main/java 下面写自己的方法,重点是 resources 下面的文件,在resources下面新建文件夹名字为 ME ...

  2. 微信小程序路过

    应该算是入门篇, 从我怎么0基础然后沿着什么方向走,遇到的什么坑,如何方向解决,不过本人接触不是很多,所以也就了解有限. 小程序的前提: 1.小程序大小不允许超过2M.(也就是本地图片,大图精图不要在 ...

  3. Android项目实战(三十五):多渠道打包

    多渠道打包: 可以理解为:同时发布多个渠道的apk.分别上架不同的应用商店.这些apk带有各自渠道的标签,用于统计分析各个商店的下载次数等数据. 实现步骤 一.添加友盟渠道标签 添加位置:app目录下 ...

  4. 写给自己的web总结——关于html的知识总结

    相信每个前端工程师初识前端之时,最先接触的都是html吧! html的全称是hyperText markup language, 超文本标记语言,在网页中所有的文字,图片,架构等都是由html来编写的 ...

  5. 【python】内部函数

  6. Jrebel简单的热部署一个web工程

    前言:博主最近在做Hybris开发,漫长的启动时间大大的拖累了项目的进度,而Jrebel的出现就是为了减少项目重启的时间或者说修改了代码后直接不用重启就可以看到修改的结果,但是Hybris的部署一直没 ...

  7. java操作时间,将当前时间减一年,减一天,减一个月

    在Java中操作时间的时候,常常遇到求一段时间内的某些值,或者计算一段时间之间的天数 Date date = new Date();//获取当前时间 Calendar calendar = Calen ...

  8. Thomas Hobbes: Leviathan

    Man is distinguished, not only by his reason, but by this singular passion from other animals, which ...

  9. Bilateral Filter

    最近在看图像风格化的论文的时候,频繁遇到 Bilateral Filter.google 一波后,发现并不是什么不得了的东西,但它的思想却很有借鉴意义. 简介 Bilateral Filter,中文又 ...

  10. [编织消息框架][netty源码分析]1分析切入点

    在分析源码之前有几个疑问 1.BOSS线程如何转交给handle(业务)线程2.职业链在那个阶段执行3.socket accept 后转给上层对象是谁4.netty控流算法 另外要了解netty的对象 ...