半暖商城第一季--SpringMVC+Mybatis本地环境开发

2017-03-10  本文已影响294人  Alibct

1. 前言

在前一篇教程中我们介绍了如何使用IntelliJ IDEA搭建本地开发环境,同时也集成了本地服务器和远程数据库。在本教程中我们将使用本地开发环境进行一些开发测试,以此确保本地开发环境稳定可靠。

2. 本地开发环境测试

2.1 添加自定义404 Not Found页面

在实际开发中一些静态页面有时是需要自己定义的例如:404 not found界面,那么如何实现在SpringMVC+Mybatis的框架中,让用户在请求不存在的页面时出现自己自定义的页面呢?下面就来探讨一下。

    <!--配置错误页面-->
    <error-page>
        <error-code>404</error-code>
        <location>/static/view/404.html</location>
    </error-page>

上面的配置其实我在上一篇教程中已经添加了,这里做一下说明,在web.xml中error-page标签表示配置错误界面,error-code标签表示错误码类型,这里我们需要配置404错误,所以错误码就选择404,location标签是指自定义的错误静态界面存放的位置,这里我们放在之前新增的view文件夹中。

这里有一个坑,我在配置404界面的时候死活不出中文,全是乱码,去网上找了好久,才发现需要在web.xml中配置htm和html的编码方式,如果使用的404界面是jsp格式的则不会出现此问题,但是既然问题出现了,这里我就提一下,解决的办法有两种。

第一种:在自己项目的web.xml中添加以下配置(同样的,在前一篇教程中我已经添加了,这里做一个提醒):

    <!-- 防止html页面出现中文乱码 -->
    <mime-mapping>
        <extension>htm</extension>
        <mime-type>text/html;charset=utf-8</mime-type>
    </mime-mapping>

    <mime-mapping>
        <extension>html</extension>
        <mime-type>text/html;charset=utf-8</mime-type>
    </mime-mapping>

第二种:配置全局属性,这种配置方法是在自己的服务器中。因为我们调试的是在本地服务器,所以本地服务器需要配置。但是项目完成后又会部署到远程服务器,所以远程服务器也需要配置。具体配置方法如下:

打开~/tomcat/conf/web.xml,'~'表示你'tomcat'所在的目录地址,请用你具体的'tomcat'所在地址进行替换。并在web.xml中添加以下配置:

    <!-- 防止html页面出现中文乱码 -->
    <mime-mapping>
        <extension>htm</extension>
        <mime-type>text/html;charset=utf-8</mime-type>
    </mime-mapping>

    <mime-mapping>
        <extension>html</extension>
        <mime-type>text/html;charset=utf-8</mime-type>
    </mime-mapping>

至此就可以解决htm和html页面在显示时出现中文乱码的问题

下面按下绿色的运行按钮,等待浏览器跳出来,然后输入一个不存在的地址就能看到你自己定义的404啦,我的404如下(闲丑的憋说话):

404.html404.html

3. 登录教程实践

在写登录教程之前,我们先来聊聊SpringMVC。

先看一个SpringMVC结构的整个请求过程。

SpringMVC请求SpringMVC请求

用户发出请求,第一个接收到请求的是前端控制器,前端控制器调用处理器映射器,处理器映射根据相应的请求查找处理器适配器,然后处理器适配器就开始适配能够处理请求的处理器,处理器再到底层调用业务类处理相应的业务,业务类处理完成后会返回ModelAndView给前端控制器,前端控制器再请求视图解析器解析ModelAndView最后将解析的结果返回给浏览器响应用户。以上就是整个处理过程

在整个请求链中需要我们程序员实现的就是一个处理器,也就是Controller和一个业务类,其它的SpringMVC底层都有实现,我们只需要关注我们的Controller和业务的逻辑实现就可以了。

那么要实现这个登录功能我们需要写什么呢?

但是实际开发中因为集成了Mybatis同时又需要遵循高内聚低耦合的设计思想,所以我们在实现具体登录业务的过程中会将整个业务微粒化,保证每一个单元高内聚,单元与单元之间低耦合。好了废话不多说,show the code!

3.1 数据库持久层开发

3.1.1 在开始开发之前,我们先来看一下上一篇教程中最后实现的项目结构图。

00520052

上图中标记的两个文件夹就是持久层的文件所要放置的位置。在以前没有Mybatis的时候我们是自己写dao接口自己实现dao接口,最后自己调用dao接口。有了Mybatis之后,我们只需要写接口和sql语句,Mybatis会自动帮我们实现接口,这样能省好多事,关键是只要你sql语句没问题,接口就不会出什么大问题,何乐而不为呢?

在进行数据库持久层开发之前我们需要准备一个数据库和一张表。
我们就是用上一篇中在IntelliJ IDEA中配置好的数据库中心。

00460046

第一步:刷新数据库以连接数据库
第二步:打开命令行窗口以键入sql语句
第三步:键入sql语句
第四步:执行sql语句

注意:一次只能执行一条语句,在键入第二条语句之前需要先删除第一条语句(不太智能)

CREATE TABLE ADMINISTRATOR (
  admin_id VARCHAR(100) NOT NULL UNIQUE COMMENT '管理员ID',
  admin_name VARCHAR(100) NOT NULL UNIQUE COMMENT '管理员名称',
  password VARCHAR(41) NOT NULL COMMENT '密码',
  privilege_level TINYINT NOT NULL DEFAULT 1 COMMENT '权限等级',
  status BOOLEAN NOT NULL DEFAULT TRUE COMMENT '账号是否可用',
  create_time DATETIME NOT NULL COMMENT '管理员创建时间',
  PRIMARY KEY(admin_id)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='管理员表';

准备完成开始进行数据库持久层开发。

3.1.2 编写实体类Administrator.java

看代码,这里不想多说

Administrator.java 该文件在~/java/entity/

package cn.semiwarm.admin.entity;
import java.io.Serializable;
import java.sql.Timestamp;

/**
 * 管理员类
 * Created by alibct on 2017/3/9.
 */
public class Administrator implements Serializable {

    private String adminId; // 管理员ID
    private String adminName; // 管理员名称
    private String password; // 密码
    private Integer privilegeLevel; // 权限等级默认一级,最高十级
    private Boolean status; // 是否可用
    private Timestamp createTime; // 创建时间

    public String getAdminId() {
        return adminId;
    }

    public void setAdminId(String adminId) {
        this.adminId = adminId;
    }

    public String getAdminName() {
        return adminName;
    }

    public void setAdminName(String adminName) {
        this.adminName = adminName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getPrivilegeLevel() {
        return privilegeLevel;
    }

    public void setPrivilegeLevel(Integer privilegeLevel) {
        this.privilegeLevel = privilegeLevel;
    }

    public Boolean getStatus() {
        return status;
    }

    public void setStatus(Boolean status) {
        this.status = status;
    }

    public Timestamp getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Timestamp createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Administrator{" +
                "adminId='" + adminId + '\'' +
                ", adminName='" + adminName + '\'' +
                ", password='" + password + '\'' +
                ", privilegeLevel=" + privilegeLevel +
                ", status=" + status +
                ", createTime=" + createTime +
                '}';
    }
}

3.1.3 编写基本操作接口

为了让每一个xxxMapper.java接口都能实现增删改查这几个基本操作,我们先创建一个基本接口

BaseMapper.java 该文件在~/java/mapper/

接口中使用范型

package cn.semiwarm.admin.mapper;
import java.io.Serializable;
import java.util.List;

/**
 * 描述:定义基本查询方法的接口
 * Created by alibct on 2017/2/23.
 */
public interface BaseMapper<T> {
    /**
     * 增加方法
     *
     * @param t 要增加的对象
     * @return 受影响的行数
     */
    int add(T t);

    /**
     * 删除方法
     *
     * @param t 要删除的对象
     * @return 受影响的行数
     */
    int delete(T t);

    /**
     * 更新方法
     *
     * @param t 要更新的对象
     * @return 受影响的行数
     */
    int update(T t);

    /**
     * 根据id查询某个对象
     *
     * @param id 要查询对象的id
     * @return 要查询的对象
     */
    T findById(Serializable id);

    /**
     * 查询所有对象
     *
     * @return 所有对象
     */
    List<T> findAll();
}

3.1.4 编写AdministratorMapper接口

AdministratorMapper.java 该文件在~/java/mapper/

AdministratorMapper.java接口继承BaseMapper.java接口以规范接口中的方法

package cn.semiwarm.admin.mapper;
import cn.semiwarm.admin.entity.Administrator;
import java.io.Serializable;
import java.util.List;

/**
 * 管理员基本操作
 * Created by alibct on 2017/3/9.
 */
public interface AdministratorMapper extends BaseMapper<Administrator>{

    int add(Administrator administrator);

    int delete(Administrator administrator);

    int update(Administrator administrator);

    Administrator findById(Serializable id);

    List<Administrator> findAll();
}

3.1.5 编写AdministratorMapper.xml文件

该文件中存放的是对数据库中ADMINISTRATOR表的基本操作sql语句

AdministratorMapper.xml 该文件在~/resources/mapper/

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace是指明Mybatis扫描的目录,即cn.semiwarm.admin.mapper.xxxMapper -->
<mapper namespace="cn.semiwarm.admin.mapper.AdministratorMapper">
    <insert id="add" parameterType="cn.semiwarm.admin.entity.Administrator">
        INSERT INTO ADMINISTRATOR (admin_id, admin_name, password, privilege_level, status, create_time)
        VALUE (#{adminId}, #{adminName}, password(#{password}), #{privilegeLevel}, #{status}, #{createTime})
    </insert>

    <delete id="delete" parameterType="cn.semiwarm.admin.entity.Administrator">
        DELETE FROM ADMINISTRATOR
        WHERE admin_id = #{adminId}
    </delete>

    <update id="update" parameterType="cn.semiwarm.admin.entity.Administrator">
        UPDATE ADMINISTRATOR
        SET
        admin_name      = #{adminName},
        password        = password(#{password}),
        privilege_level = #{privilegeLevel},
        status          = #{status}
        WHERE admin_id = #{adminId}
    </update>

    <select id="findById" parameterType="String" resultType="cn.semiwarm.admin.entity.Administrator">
        SELECT *
        FROM ADMINISTRATOR
        WHERE admin_id = #{adminId}
    </select>

    <select id="findAll" resultType="cn.semiwarm.admin.entity.Administrator">
        SELECT *
        FROM ADMINISTRATOR
    </select>
</mapper>

这个文件中有以下几点需要说明

  1. <insert></insert>标签对应数据库的INSERT语法
  2. <delete></delete>标签对应数据库的DELETE语法
  3. <update></update>标签对应数据的UPDATE语法
  4. <select></select>标签对应数据库的SELECT语法
  5. 标签中的id中的值对应接口中的方法名
  6. 标签中的parameterType对应接口中的形参的类型
  7. 标签中的resultType对应接口中方法的返回值类型,即使在接口中返回值类型是List<E>类型,这里的返回值类型也是List<E>类型中的范型,例如在接口中findAll方法的返回值是List<Administrator>,但是在.xml文件中id为findAll的语句中的resultType还是为Administrator类型。
  8. 标签里面的sql语句需要的参数用#{value}替换,value的值就是parameterType中填写的类型的属性名。例如在findById的方法中,参数应该是id,所以SELECT语句中的参数就是AdministratoradminId属性。Mybatis可以自动识别实体类中的属性,并将用该属性值替换占位符。
  9. password(#{password})表示将Administrator类中的password对应的属性值进行密码加密后存储到数据库,这种加密方式是不可逆的,也就是说如果需要在代码中进行比较password是否相同需要在写查询语句的时候对password使用密码加密后进行比较,如果加密后的秘文相等则说明密码相等。md5的加密方式也是不可逆的。

3.1.6 单元测试

以上三个文件写完之后就可以进行单元测试,养成写好代码就测试好习惯能够有效减少bug的存在。

/src/test/java/文件夹中创建AdministratorMapperTest.java

AdministratorMapperTest.java

import cn.semiwarm.admin.entity.Administrator;
import cn.semiwarm.admin.mapper.AdministratorMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.sql.Timestamp;
import java.util.UUID;

/**
 * 管理员测试类
 * Created by alibct on 2017/3/10.
 */
@RunWith(SpringJUnit4ClassRunner.class) // 注入Spring测试类
@ContextConfiguration("classpath:spring/spring-*.xml") // 注入Spring的所有配置文件,如果没有配置对会出现'failed load context application'的错误,如果出现请把之前的两个教程中的配置文件好好查看一下
public class AdministratorMapperTest {
    // 注入AdministratorMapper接口
    @Autowired
    private AdministratorMapper administratorMapper;
    // 标记为测试类
    @Test
    public void testAdd() { // 光标放在这里右键'Debug',然后看控制台的打印日志,出现添加成功后就OK了
        Administrator admin = new Administrator();
        admin.setAdminId(UUID.randomUUID().toString());
        admin.setAdminName("admin");
        admin.setPassword("admin");
        admin.setPrivilegeLevel(10);
        admin.setStatus(true);
        admin.setCreateTime(new Timestamp(System.currentTimeMillis()));
        int result;
        result = administratorMapper.add(admin);

        if (result > 0) {
            System.out.println("添加成功!");
        } else {
            System.out.println("添加失败!");
        }
    }
}

具体说明我都注释了。

此时的项目结构如下:

00530053

另外这里提一下,之前我看过黑马的SpringMVC教程,里面的讲解的和Mybatis的集成时是把xxxMapper.xml文件和xxxMapper.java文件放在~/java/mapper/包内,但是我用了这种方法在做单元测试的时候愣是不能成功,并且在spring-dao.xml文件中我也按照教程进行了相应的配置但是还是没能成功,如果有知道怎么把这两个文件放在同一个文件夹中并且测试成功的童鞋可以告诉我哈,感激不尽。

3.1.7 编写业务代码

到这里我们才真正的开始关心登录业务的具体逻辑,由此可见前期准备工作还真是不少呢

3.1.7.1 编写BaseService.java接口,同样的为了防止以后业务类中会出现一些基本的业务操作,我们在此留下接口,方便扩展

BaseService.java 该文件在~/java/service/

package cn.semiwarm.admin.service;
/**
 *  基本功能接口,防止以后会出现一些基本功能
 *  可扩展性高,耦合性低
 * Created by alibct on 2017/2/24.
 */
public interface BaseService<T> {
}
3.1.7.2 编写AdministratorService.java接口,完成一些复杂的业务操作,例如登录,暂时就放了登录方法以后需要的请自行实现,我只是示范,示范,示范!

AdministratorService.java 该文件在~/java/service/

package cn.semiwarm.admin.service;
import cn.semiwarm.admin.entity.Administrator;
import org.springframework.web.servlet.ModelAndView;

/**
 * 管理员相关服务
 * Created by alibct on 2017/3/9.
 */
public interface AdministratorService extends BaseService<Administrator>{
    ModelAndView signIn(Administrator administrator) throws Exception;
}
3.1.7.3 编写AdministratorServiceImpl.java接口实现类,完成登录业务的具体实现过程

在实现接口之前,我们先来思考一下当前的问题。

  1. 登录界面呢?好像还没写...
  2. 如何获取用户输入的用户名和密码?
  3. 如何检查合法性?
  4. 出错了怎么提示用户?
半暖-登录界面半暖-登录界面

至于你们自己怎么解决,自己想去(果断甩锅顺便秀一下自己的登录界面,哈哈)

<div class="login-card">
    <div class="login-form">
        <h1 class="logo hide-text">半暖</h1>
        <form action="<%=request.getContextPath()%>/signIn" method="post" accept-charset="utf-8">
            <div class="group-inputs">
                <div class="input-wrapper account">
                    <input type="text" name="adminName" aria-label="请输入用户名" placeholder="请输入用户名" required>
                </div>
                <div class="input-wrapper password" style="margin-top: -1px">
                    <input type="password" name="password" aria-label="请输入密码" placeholder="请输入密码" required>
                </div>
            </div>
            <div class="button-wrapper">
                <button class="sign-button" type="submit">登录</button>
            </div>
        </form>
    </div>
</div>

用户名的那个输入框的name属性填写的就是Administrator类中的adminName属性名。密码输入框的name属性就填写Administrator类中的password属性名。Spring会自动将这些属性名对应的属性值封装成实体类,我们只需要调用就好了。

好,下面就开始一个个解决问题。

登录界面你们自己写

先在/src/main/webapp/WEB-INF/下新增一个jsp文件夹用于存放.jsp文件,然后在jsp文件夹中添加一个main.jsp文件用于成功转跳和不成功时候的提示信息

这里需要先下载jquery-3.1.1.min.js文件(点我下载)并引入到项目的静态资源文件夹中(~/webapp/static/js/)

<%--
  Created by IntelliJ IDEA.
  User: alibct
  Date: 2017/3/9
  Time: 下午2:14
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<script type="text/javascript" src="../../static/js/jquery-3.1.1.min.js"></script>
<head>
    <title>半暖-后台管理界面</title>
</head>
<body>
后台管理主界面<br />
管理员信息:${message} 
</body>
</html>

编写AdministratorServiceImpl.java 该文件在~/java/service/impl/

package cn.semiwarm.admin.service.impl;
import cn.semiwarm.admin.entity.Administrator;
import cn.semiwarm.admin.mapper.AdministratorMapper;
import cn.semiwarm.admin.service.AdministratorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.ModelAndView;

/**
 * 管理员业务实现类
 * Created by alibct on 2017/3/10.
 */
@Service("administratorService") // Service注解
public class AdministratorServiceImpl implements AdministratorService {

    private final AdministratorMapper administratorMapper;

    // 注入Mapper接口
    @Autowired
    public AdministratorServiceImpl(AdministratorMapper administratorMapper) {
        this.administratorMapper = administratorMapper;
    }

    public ModelAndView signIn(Administrator administrator) throws Exception {

        ModelAndView view = new ModelAndView("main"); // 实例化jsp界面就是前面写的那个main.jsp

        Administrator administratorInfo = administratorMapper.verifyAdministratorByName(administrator);

        if (null != administratorInfo) {
            view.addObject("message",administratorInfo.toString());
        } else {
            view.addObject("message","用户名或密码有误!");
        }

        return view;
    }
}

signIn方法中的形参administrator就是Spring获取form表单中对应属性名的属性值而封装成的实体类,我们直接拿着这个实体类去验证登录就可以了。

我在AdministratorMapper.java接口中添加了一个验证方法并在AdministratorMapper.xml文件中添加了对应的sql语句。所以直接在这里调用验证方法验证合法性就好了,其实就是拿着用户名和密码去查数据库中是否有这条数据的,简单的判断没有那么复杂。

view.addObject("message","用户名或密码有误!");这句代码就是使用key/value的方式将提示信息封装在ModelAndView中之后在main.jsp中就可以使用${message}来获取value对应的值了。

编写AdministratorController.java类 该文件在~/java/controller/

package cn.semiwarm.admin.controller;
import cn.semiwarm.admin.entity.Administrator;
import cn.semiwarm.admin.service.impl.AdministratorServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * 管理员控制器
 * Created by alibct on 2017/3/10.
 */
@Controller // 标记该类是控制器
public class AdministratorController {

    private final AdministratorServiceImpl administratorService;

    // service注入
    @Autowired
    public AdministratorController(AdministratorServiceImpl administratorService) {
        this.administratorService = administratorService;
    }

    // 请求登录映射路径
    @RequestMapping(value = "/signIn", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
    public ModelAndView signIn(Administrator administrator) throws Exception{
        return administratorService.signIn(administrator);
    }
}

处理器调用底层服务类,实现相应的业务

启动tomcat分别输入正确登录验证和错误登录验证查看效果

正确显示页面

正确正确

错误信息页面

错误错误

至此我们就迈出了后台管理系统开发的第一步,虽然教程中还有很多漏洞和缺陷,但我会在之后一点点当作提示贴出来,这个系列会详细记录我的开发过程,请感兴趣的小伙伴多多关注!

参照:Clone丶记忆

GitHub项目地址

上一篇下一篇

猜你喜欢

热点阅读