SSM框架CRUD小案例

Posted zhanp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SSM框架CRUD小案例相关的知识,希望对你有一定的参考价值。

1.数据库准备

部门tbl_dept

技术图片

员工tbl_emp

技术图片

建立员工和部门的外键

技术图片

2.在IDEA创建SSM项目环境

2.1配置Web模块

技术图片

技术图片

最上面的图是错误示范,注意!!! 在Tomcat配置了项目路径,就不需要再webapp这里配置项目路径,不然是找不到这里面的资源的!!!!!!!

技术图片

技术图片

2.2 引入Maven的SSM相关依赖

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <!-- mvn mybatis-generator:generate -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                    <!--这个是指代从哪里加载generatorConfig文件-->
                    <configurationFile>
                        src/main/resources/generator/generatorConfig.xml
                    </configurationFile>
                </configuration>
                <dependencies>
                    <!-- 数据库驱动 -->
                    <!--mysql-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>${mysql.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>

    </build>

    <properties>
        <project.build.source>UTF-8</project.build.source>
        <!--版本同一管理-->
        <mybatis.version>3.2.8</mybatis.version>
        <mybatis.spring.version>1.2.2</mybatis.spring.version>
        <mysql.version>5.1.32</mysql.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.2.17.RELEASE</version>
        </dependency>
        <!--spring-tx-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>3.2.17.RELEASE</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>3.2.1.RELEASE</version>
        </dependency>
                <!--面向切面AOP依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>



        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
        </dependency>


        <!--连接池-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!--mybatis-->
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.version}</version>
        </dependency>

        <!--日志用log4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>



        <!--junit测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>


    </dependencies>

注意1:spring的相关依赖版本要一致,不然maven加载其他相关依赖的时候,会有不同版本的相同依赖也会加载进来,很可能会冲突。而且从maven仓库又下载就会很慢。

注意2: 当你在spring配置文件中死活都不出来某个标签的时候,很可能你在maven里导错包,首先需要重新导入正确的依赖。然后把这个xml上面错误的xsd约束删除掉,就可以了。

applicationContext-service.xml

    <context:component-scan base-package="cn.zhanp.ssm_crud.service"></context:component-scan>

    <!--配置事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* cn.zhanp.ssm_crud.service..*(..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
    </aop:config>


    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

注意点3:当你在IDEA中无法识别另外一个如applicationContext-dao中的数据源的引用bean时,应该在IDEA的spring Module中把spring配置文件都添加进去,同一个applicationContext就可以识别了

技术图片

2.3 部署到Tomcat

技术图片

技术图片

2.4 配置MBG插件,逆向工程

            <!-- mvn mybatis-generator:generate -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                    <configurationFile>src/main/resources/generator/generatorConfig.xml</configurationFile>
                </configuration>
                <dependencies>
                    <!-- 数据库驱动 -->
                    <!--mysql-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>${mysql.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

技术图片

2.5 编写SSM各层的配置文件和数据库文件以及日志文件

springmvc.xml

    <!--加载spring的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:</param-value>
    </context-param>
    <!--监听器,加载context-param里的配置文件-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!--加载springmvc的配置文件-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

总结:别忘了监听器,不配置监听器,根本就没人去帮你加载那几个配置文件

mybatis-config.xml

<configuration>

    <settings>
        <!--全局下划线转驼峰命名-->
        <setting name="mapUnderscoreToCamelCase" value="true"></setting>
        <!-- mybatis3的 SQL日志打印方式 -->
        <!--<setting name="logImpl" value="log4j"/>-->
    </settings>

    <!--其余配置都在spring的xml中配置,通过属性注入到sqlSessionFactory的bean中,不在这里写-->
</configuration>

applicationContext-dao.xml

    <!--配置数据源-->
    <context:property-placeholder location="classpath:db/db.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>

        <!-- c3p0连接池的私有属性 -->
        <property name="maxPoolSize" value="30" />
        <property name="minPoolSize" value="10" />
        <!-- 关闭连接后不自动commit -->
        <property name="autoCommitOnClose" value="false" />
        <!-- 获取连接超时时间 -->
        <property name="checkoutTimeout" value="10000" />
        <!-- 当获取连接失败重试次数 -->
        <property name="acquireRetryAttempts" value="2" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="typeAliasesPackage" value="cn.zhanp.ssm_crud.model"></property>
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
    </bean>

    <!--注册一个特殊的sqlSession用于测试的时候支持批量插入-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"></constructor-arg>
        <constructor-arg value="BATCH"></constructor-arg>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <property name="basePackage" value="cn.zhanp.ssm_crud.dao"></property>
    </bean>
</beans>

总结:sqlSessionTemplate,用于注册一些特殊的sqlSession

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

总结2: error-------通配符的匹配很全面, 但无法找到元素 ‘aop:config‘ 的声明。

解决:依旧是把xsd依赖重新写入(一定要注意mvc和aop这些的xsd约束引入是否正确)

总结3: 提示Error creating bean with name ‘org.springframework.cache.interceptor.CacheInterceptor

技术图片

又是xsd依赖引入错误

2.6 把依赖添加入到Artifact中

3.Spring单元测试模拟数据测试mapper

总结:

<!--在spring单元测试中,由于引入validator而导致的Tomcat7及以下的EL表达式版本不一致-->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>8.5.24</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>8.5.24</version>
    <scope>provided</scope>
</dependency>

4. 配置pageHelper,实现分页

参考官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

mybatis-config.xml

    <!--配置分页插件,也就是指定拦截器-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
        <!--分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.2</version>
        </dependency>

5. 分页的后台代码

    @RequestMapping(value = "/emps",method = {RequestMethod.GET,RequestMethod.POST})
    public ModelAndView getEmps(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){

//        实现分页,默认大小一页显示5条
        PageHelper.startPage(page_num, 5);

        List<EmployeeCustom> list = employeeService.getAllWithDept();

//        用pageInfo去包装查询后的结果,第二种构造器,第二参数为,连续显示的页数(以当前页为中心,1,2,3,4,5这样)
        PageInfo<EmployeeCustom> pageInfo = new PageInfo<>(list,5);



        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("empList");

        modelAndView.addObject("pageInfo",pageInfo);

        return modelAndView;
    }

总计:

  1. 在controller的方法里 要有一个@RequestParam(value="",defaultValue="") xxx

    要求从前端传入第几页pageNumber, 又或者传两个,一个是页码,一个是每页的数目size,

    因为我们很可能会首页就跳转到这里查看,所以很可能没携带页码参数,所以设置一个默认值

    defaultValue,这样就可以实现了。

  2. PageHelper.startPage(page_num, 5);

    用静态方法去让mybatis设置的pageHelper的拦截器对紧跟着的第一个select()方法进行分页,并设置size

  3. PageInfo pageInfo = new PageInfo<>(list,5);

    用pageInfo 来包装查询后的结果,这样可以把结果包装进pageInfo对象中,

    并且第二个参数是navigatepageNums: 是以当前分页为基数,计算导航页码的个数

    举个例子:

    比如当前页码为1,那么navigatepageNums五个,就是1,2,3,4,5。

    当前页码为5,那么navigatepageNums五个,就是3,4,5,6,7 尽可能地以5为中心展开分页导航

    技术图片

    并且能够获取相关分页的其他参数,比如total啊,判断是不是首页啊,有没有上一页,下一页啊这些方法。

    //PageInfo包含了非常全面的分页属性
    assertEquals(1, page.getPageNum());
    assertEquals(10, page.getPageSize());
    assertEquals(1, page.getStartRow());
    assertEquals(10, page.getEndRow());
    assertEquals(183, page.getTotal());
    assertEquals(19, page.getPages());
    assertEquals(1, page.getFirstPage());
    assertEquals(8, page.getLastPage());
    assertEquals(true, page.isFirstPage());
    assertEquals(false, page.isLastPage());
    assertEquals(false, page.isHasPreviousPage());
    assertEquals(true, page.isHasNextPage());

6. bootstrap后台界面显示

技术图片

总结的小问题:一开始不出现字体图标,为什么呢?

答:因为我只在项目中引入了bootstrap的css和js,而字体图标虽然是以class来标识,但是底层实现是要根据

fonts来匹配图标文件的,后缀为这种:

技术图片

所以要引入bootstrap的fonts文件,并且和js文件夹在同等级目录结构

注意:SpringMVC静态文件的配置,一定要配置过滤器,不要拦截这个

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!--加载springmvc的配置文件-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:spring/springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

     <servlet-mapping>
         <servlet-name>default</servlet-name>
         <url-pattern>*.js</url-pattern>
         <url-pattern>*.css</url-pattern>
         <url-pattern>/assets/*"</url-pattern>
         <url-pattern>/images/*</url-pattern>
         <url-pattern>/static/*</url-pattern>
     </servlet-mapping>

作用:在web.xml文件中经常看到这样的配置default,这个配置的作用是:对客户端请求的静态资源如图片、JS文件等的请求交由默认的servlet进行处理,也就是不经过DispatcherServlet的拦截

        <div class="row">
            <table class="table table-hover">
                <tr>
                    <th>#</th>
                    <th>empName</th>
                    <th>gender</th>
                    <th>email</th>
                    <th>deptName</th>
                    <th>操作</th>
                </tr>
                <c:forEach var="page" items="${pageInfo.list}">
                    <tr>
                        <td>${page.empId}</td>
                        <td>${page.empName}</td>
                        <td>${page.gender}</td>
                        <td>${page.email}</td>
                        <td>${page.department.deptName}</td>
                        <td>
                            <button type="button" class="btn btn-sm btn-primary">
                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
                                修改
                            </button>
                            <button type="button" class="btn btn-sm btn-danger">
                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
                                删除
                            </button>
                        </td>
                    </tr>
                </c:forEach>
            </table>
        </div>
        <div class="row">
            <div class="col-md-6">
                当前总记录数:${pageInfo.total}
            </div>
            <div class="col-md-6" style="text-align: center">
                <nav class="pagination">
                    <ul class="pagination">
                        <li>
                            <a href="${pageContext.request.contextPath}/emps?pn=1">首页</a>
                        </li>
                        <%--判断上一页是否可用--%>
                        <c:if test="${pageInfo.hasPreviousPage==true}">
                            <li>
                                <a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.prePage}" aria-label="Previous">
                                    <span aria-hidden="true">&laquo;</span>
                                </a>
                            </li>
                        </c:if>


                        <c:forEach items="${pageInfo.navigatepageNums}" var="pageNum">
                            <li>
                                <a href="${pageContext.request.contextPath}/emps?pn=${pageNum}">${pageNum}</a>
                            </li>
                        </c:forEach>
                            <c:if test="${pageInfo.hasNextPage==true}">
                                <li>
                                    <a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.nextPage}" aria-label="Previous">
                                        <span aria-hidden="true">&raquo;</span>
                                    </a>
                                </li>
                            </c:if>
                            <li>
                                <a href="${pageContext.request.contextPath}/emps?pn=${pageInfo.pages}">末页</a>
                            </li>
                    </ul>
                </nav>
            </div>
        </div>

7.统一JSON 响应类的编写

前提:

  1. 为什么要JSON?因为可以针对不同的前端,不管是浏览器,小程序,安卓移动端等等,都可以通过JSON

数据来达到数据交互的效果。而不用拘泥于以前的浏览器页面html和jsp这些。

  1. 有了JSON,就可以在实际项目中快速分工。后端传JSON数据,前端通过Ajax向后端提供的接口获取JSON数据,解析JSON数据,渲染进页面中,即可完成交互。而不需要像以前那样,通过JSP等后端模板引擎,来在服务端渲染。造成前后端的开发耦合。
//    要导入jackson包,SpringMVC的ResponseBody才能起作用

    @RequestMapping(value = "/emps",method = {RequestMethod.GET,RequestMethod.POST})
    @ResponseBody
    public Msg getEmpsWithJson(@RequestParam(value = "pn",defaultValue = "1") Integer page_num){
        //        实现分页,默认大小一页显示5条
        PageHelper.startPage(page_num, 5);

        List<EmployeeCustom> list = employeeService.getAllWithDept();

//        用pageInfo去包装查询后的结果,第二种构造器,第二参数为,连续显示的页数(以当前页为中心,1,2,3,4,5这样)
        PageInfo<EmployeeCustom> pageInfo = new PageInfo<>(list,5);

//        封装进JSON 响应类
        Msg msg = Msg.success().add("pageInfo", pageInfo);

        return msg;
    }
public class Msg {
    private int code;   //代码
    private String msg; //信息

    //用户要返回给浏览器的数据,最好用一个map对象,前端可以直接取出这个map['key']这样的方式来取出需要的数据
    //而且设置为Map的好处,通过返回Msg对象,可以进行链式添加,map中有多个键值对,可以传多个对象
    private Map<String,Object> extend = new HashMap<>();

    //返回Msg对象,进行链式操作
    public static Msg success(){
        Msg msg = new Msg();
        msg.setCode(200);
        msg.setMsg("处理成功");
        return msg;
    }

    public static Msg fail(){
        Msg msg = new Msg();
        msg.setCode(500);
        msg.setMsg("处理失败");
        return msg;
    }

    //添加要返回的主体数据(先通过success和fail,拿到Msg,就可以进行链式操作了)
    public Msg add(String key,Object value){
        this.getExtend().put(key,value);
        return this;
    }
//此处省略getter,setter

8.用vue重构Jquery拼接的前端分离页面

window.baseURL = "http://localhost:8080/ssm_crud/"

$(document).ready(function () {
    window.empList = new Vue({
        el:"#empList",
        data:{
            emps:[]
        },
        method:{

        },
        created(){
            window.getEmpsIndex();
            // this.$forceUpdate() ;
        }

    })
})

function getEmpsIndex() {
    console.log(baseURL+"emps");
    $.get(baseURL+"emps",
        {
            pn:1
        },
        function (result) {
            //一定要加上设置为window.xxx  ,不然他就是一个新的引用,而不是上面的vue对象
            window.empList.emps = result.extend.pageInfo.list;
        });
}

总结:

  1. 为了让页面一开始就触发函数,获取首页数据,需要用到vue的生命周期函数

  2. 而且需要注意到JS的作用域,如果写成empList.emps = result.extend.pageInfo.list;

    而不是window.empList.emps,那么JS就会新建一个empList对象,这样vue对象实际上没有获取到值。

    这也是我以前用的方法,用window,保证操作的是同一个vue对象。

  3. 上面的方法的原因其实是:我忘记了ES5的特性,因为在里面的那个function函数中,this已经指向了全局变量window, 而不是当前的vue实例了,所以设置不了data值。

    要用let self = this来保存this的引用,又或者用ES6的箭头函数

        methods:{
            getEmps:function (pn) {
                $.get(baseURL+"emps",
                    {
                        pn:pn
                    },
                    function (result) {
                        console.log(this);  //查看可知这个this又指向了window对象

                        self.emps = result.extend.pageInfo.list;
                        self.pageInfo = result.extend.pageInfo;
                    
                        console.log("pages"+self.pageInfo.pages);   //测试
                        console.log("pn"+pn);
                    });
            }
        }
    <div id="empList" class="container">
        <h1>SSM_CRUD</h1>
        <div class="row" style="text-align: center">
            <div class="col-md-offset-8">
                <button class="btn btn-info">新增</button>
                <button class="btn btn-danger">删除</button>
            </div>
        </div>
        <div class="row">
            <table class="table table-hover">
                <tr>
                    <th>#</th>
                    <th>empName</th>
                    <th>gender</th>
                    <th>email</th>
                    <th>deptName</th>
                    <th>操作</th>
                </tr>
                <!--用vue重构列表渲染-->
                <!--<c:forEach var="page" items="${pageInfo.list}">-->
                    <tr v-for="emp in emps">
                        <td>{{emp.empId}}</td>
                        <td>{{emp.empName}}</td>
                        <td>{{emp.gender}}</td>
                        <td>{{emp.email}}</td>
                        <td>{{emp.department.deptName}}</td>
                        <td>
                            <button type="button" class="btn btn-sm btn-primary">
                                <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
                                修改
                            </button>
                            <button type="button" class="btn btn-sm btn-danger">
                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
                                删除
                            </button>
                        </td>
                    </tr>
                <!--</c:forEach>-->
            </table>
        </div>
        <!--分页界面代码-->
        <div class="row">
            <div class="col-md-6">
                当前总记录数:{{pageInfo.total}}
            </div>
            <div class="col-md-6" style="text-align: center">
                <nav class="pagination">
                    <ul class="pagination">
                        <li>
                            <span @click="getEmps(1)">首页</span>
                        </li>
                        <li v-if="pageInfo.hasPreviousPage">
                            <span aria-hidden="true" @click="getEmps(pageInfo.prePage)">&laquo;</span>
                        </li>
                        <li v-for="pageNum in pageInfo.navigatepageNums">
                            <span @click="getEmps(pageNum)">{{pageNum}}</span>
                        </li>
                        <li v-if="pageInfo.hasNextPage">
                            <span aria-hidden="true" @click="getEmps(pageInfo.nextPage)">&raquo;</span>
                        </li>
                        <li>
                            <span @click="getEmps(pageInfo.pages)">末页</span>
                        </li>
                    </ul>
                </nav>
            </div>
        </div>
    </div>

实现选中当前分页,当前分页高亮加深的效果。

                        <li v-for="pageNum in pageInfo.navigatepageNums" :class="{active:pageNum==pageInfo.pageNum}">
                            <span @click="getEmps(pageNum)">{{pageNum}}</span>
                        </li>

总结 : 动态class的语法一定要加{}

9. 添加员工

简介:通过以模态框的方式来编写 添加员工的界面。 在点击新增按钮后,让模态框显示。

怎么做出来的:通过bootstrap官方文档,拉取组件样式和 模态框model 的JS插件来完成这个界面。

细节:部门名称:是以下拉菜单的形式,所以要ajax获取部门数据。

总结:

  1. 因为有些我不用完全的form表单提交的方式,比如部门数据,是用的ul配li,所以在JS里面获取前端的所有表单数据,然后再手动submit上去校验。也不难,用下JQuery获取值和DOM结点即可

  2. 需要注意的技巧是:

    1. 获取单选按钮选中后的value值,用$(‘input[name="gender"]:checked‘).attr(‘value‘);

    2. 获取下拉菜单(但又不是select这种写法)选中的值,view层的

    3. 绑定事件

          <ul class="dropdown-menu" aria-labelledby="dLabel">
                 <!--要获取部门数据id,和name-->
                    <li v-for="dept in depts" class="pointer"        @click="getSelectedDept(dept.deptId,dept.deptName)">
                          <span>{{dept.deptName}}</span>
                     </li>
           </ul>
    4. 选择部门下拉菜单我是以按钮的形式

                <button class="btn btn-sm btn-primary" id="dedt_btn"
                        type="button" data-toggle="dropdown"
                        aria-haspopup="true" aria-expanded="false">
                      选择部门
                    <span class="caret"></span>
                 </button>

配合JS

              //获取选择的部门id
              getSelectedDept:function (dept_id,dept_name) {
                  this.selected_dept_id = dept_id;
                  //按钮提示的回显
                  $("#dedt_btn").text(dept_name);
                  console.log(this.selected_dept_id)
              },
  ```

界面代码:

    <style type="text/css">
        li span{
            /*设置悬浮可点*/
            cursor: pointer;
        }
        #add_emp li:hover{
            display: block;
            background: #8c8c8c;
            padding: 10px;
            /*设置悬浮可点*/
            cursor: pointer;
        }
    </style>

<!-- Modal -->
<div class="modal fade" id="add_emp" tabindex="-1" role="dialog" aria-labelledby="add_empLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="myModalLabel">添加员工</h4>
            </div>
                <div class="modal-body">
                    <form class="form-horizontal">
                        <div class="form-group">
                            <label for="inputName" class="col-sm-2 control-label">名字</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" id="inputName" name="name" placeholder="name">
                            </div>
                        </div>
                        <div class="form-group">
                            <label  class="col-sm-2 control-label">性别</label>
                            <div class="col-sm-10">
                                <label class="radio-inline">
                                    <input type="radio" name="gender"  value="M"> 男
                                </label>
                                <label class="radio-inline">
                                    <input type="radio" name="gender" value="F"> 女
                                </label>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="inputEmail" class="col-sm-2 control-label">Email</label>
                            <div class="col-sm-10">
                                <input type="email" class="form-control" id="inputEmail" placeholder="Email">
                            </div>
                        </div>
                        <!--下拉菜单-->
                        <div class="form-group">
                            <label for="inputEmail" class="col-sm-2 control-label">选择部门</label>
                            <div class="dropdown col-sm-10">
                                <button class="btn btn-sm btn-primary" id="dedt_btn" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                    选择部门
                                    <span class="caret"></span>
                                </button>
                                <ul class="dropdown-menu" aria-labelledby="dLabel">
                                    <!--要获取部门数据id,和name-->
                                    <li v-for="dept in depts" class="pointer" @click="getSelectedDept(dept.deptId,dept.deptName)">
                                        <span>{{dept.deptName}}</span>
                                    </li>
                                </ul>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="form-group">
                    <div class="modal-footer">
                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary" @click="add_emp_submit">提交</button>
                    </div>
                </div>
        </div>
    </div>
</div>
            //切换模态框的显示
            add_emp_toggle:function () {
                this.getDepts();
                $("#add_emp").modal({
                    //设置 点击除模态框以外的其他地方,不会让模态框消失掉
                    backdrop:"static"
                });
            },
            //获取部门信息
            getDepts:function () {
                let self = this;
                $.get(baseURL+"depts",
                    function (result) {
                        self.depts = result.extend.depts;
                    })
                },
            //获取选择的部门id
            getSelectedDept:function (dept_id,dept_name) {
                this.selected_dept_id = dept_id;
                //按钮提示的回显
                $("#dedt_btn").text(dept_name);
                console.log(this.selected_dept_id)
            },
            //增加员工 提交
            add_emp_submit:function () {
                let empName = $('#inputName').val();
                let gender = $('input[name="gender"]:checked').attr('value');
                let email = $('#inputEmail').val();
                let dId = this.selected_dept_id;

                console.log(empName)
                console.log(gender)
                console.log(email)
                console.log(dId)

                $.post(baseURL+"/emps/add",
                    {
                        empName:empName,
                        gender:gender,
                        email:email,
                        dId:dId
                    },
                    function (result) {
                        if(result.code==200){
                            //重新获取第一页数据,刷新页面
                            window.getEmpsIndex();
                            $("#add_emp").modal('hide');
                        }
                    })
            }

添加员工 提交数据后 回显最后一页的实现:

                    //这个是添加员工的提交后的 回调函数
                    function (result) {
                        if(result.code==200){
                            //获取最后一页数据,刷新页面
                            let lastPage = result.extend.lastPage;
                            console.log("lastPage:"+lastPage)
                            self.getEmps(lastPage);
                            $("#add_emp").modal('hide');
                        }
                    })
    //要拿到最后一页
    @Override
    public int getLastPage(int size) {
        EmployeeExample employeeExample = new EmployeeExample();
        EmployeeExample.Criteria criteria = employeeExample.createCriteria();
        criteria.andEmpIdIsNotNull();
        int num = employeeMapper.countByExample(employeeExample);

        if(num%size==0){
            return num/size;
        }else{
            return num/size+1;
        }
    }
    @RequestMapping(value = "/emps/add",method = RequestMethod.POST)
    @ResponseBody
    public Msg addEmp(Employee employee){
        employeeService.addEmployee(employee);

        //要拿到最后一页
        //总员工数
        int lastPage = employeeService.getLastPage(5);

        return  Msg.success().add("lastPage",lastPage);
    }

9.1 添加员工的校验状态

名字和邮箱的校验和前端显示

前端校验页面的用法

bootstrap摘录:

技术图片

<div class="form-group has-success">
  <label class="control-label" for="inputSuccess1">Input with success</label>
  <input type="text" class="form-control" id="inputSuccess1" aria-describedby="helpBlock2">
  <span id="helpBlock2" class="help-block">A block of help text that breaks onto a new line and may extend beyond one line.</span>
</div>
<div class="form-group has-warning">
  <label class="control-label" for="inputWarning1">Input with warning</label>
  <input type="text" class="form-control" id="inputWarning1">
</div>
<div class="form-group has-error">
  <label class="control-label" for="inputError1">Input with error</label>
  <input type="text" class="form-control" id="inputError1">
</div>
            //模态框显示的事件中
            add_emp_toggle:function () {
                this.getDepts();
                $("#add_emp").modal({
                    //点击背景不删除
                    backdrop:"static"
                });

                /**
                 * 前端JQuery校验
                 * 1.用户名交给后端检查校验正则表达式,再查看是否重名
                 * 2.其他的前端校验正则即可
                 */
                $("#inputName").focusout(function () {
                    let self = this;
                    let name = $(this).val();
                    $.post(baseURL+"namechecked",{
                            empName:name
                        },
                        function (result) {
                            //因为修改的时候,是添加样式,所以可能重复has-success和has-error一起,是不正确的
                            $(self).parent().removeClass("has-success has-error");
                            $(self).next().text("");
                            if(result.code==200){

                                window.empList.vali_name = true;
                                //说明成功,正则匹配,且用户名可用
                                $(self).parent().addClass("has-success");
                                let msg = result.extend.message;
                                $(self).next().text(msg);

                            }else{
                                window.empList.vali_name = false;

                                //说明不成功,正则不匹配或者用户名不可用,要拿出错误信息,添加相关样式,以及不许提交
                                console.log($(this));
                                $(self).parent().addClass("has-error");
                                let msg = result.extend.message;
                                $(self).next().text(msg);
                            }
                        });
                });

                //正则表达式判断其他表单参数
                $("#inputEmail").focusout(function () {
                    $(this).parent().removeClass("has-success has-error");
                    $(this).next().text("");
                    let email = $(this).val();
                    let regx = /^[a-zA-Z0-9_.-][email protected][a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$/

                    // 长度不限,可以使用英文(包括大小写)、数字、点号、下划线、减号,首字母必须是字母或数字;
                    console.log(regx.test(email));
                    if(regx.test(email)){
                        window.empList.vali_email = true;

                        //符合
                        //1. 给父元素添加bootstrap 校验成功样式
                        $(this).parent().addClass("has-success")

                    }else{
                        window.empList.vali_email = false;
                        //不符合
                        // 1. 给父元素添加bootstrap 校验错误样式
                        $(this).parent().addClass("has-error");
                        // 2. 在其下面添加span的提示内容
                        $(this).next().text("邮件格式错误!首字母必须是字母或数字");
                    }
                });
            },
    @RequestMapping(value = "/namechecked",method = RequestMethod.POST)
    @ResponseBody
    public Msg nameChecked(@RequestParam(value = "empName",required = true) String name){
        //1. 先正则 2. 后判断是否重复

        String regx = "^[[[\\u4e00-\\u9fa5]a-zA-Z0-9]+[-|a-zA-Z0-9._]$]{2,7}";
        if(name.matches(regx)){
            boolean nameChecked = employeeService.getNameChecked(name);
            if(nameChecked){
                Msg msg = Msg.success().add("message", "用户名可用");
                return msg;
            }else{
                Msg msg = Msg.fail().add("message", "用户名不可用");
                return msg;
            }
        }else{
            Msg msg = Msg.fail().add("message", "请填写2-7位中文或者数字或英文");
            return msg;
        }
    }

选择部门和提交表单时候的校验

            //获取选择的部门id
            getSelectedDept:function (dept_id,dept_name) {
                this.selected_dept_id = dept_id;
                //按钮的回显
                $("#dedt_btn").text(dept_name);

                //删掉span提示信息
                $("#dept_span").text('');
                console.log(this.selected_dept_id)
            },
            //增加员工 提交
            add_emp_submit:function () {
                let self = this;

                let empName = $('#inputName').val();
                let gender = $('input[name="gender"]:checked').attr('value');
                let email = $('#inputEmail').val();
                let dId = this.selected_dept_id;

                if(self.selected_dept_id==''){
                    $("#dedt_btn").next().css("color","red");
                    $("#dedt_btn").next().text("请选择部门");
                    self.vali_dept = false;
                }else{
                    self.vali_dept = true;
                    //全都校验成功,才可以提交
                    if(self.vali_name&&self.vali_dept&&self.vali_email){
                        $.post(baseURL+"emps/add",
                            {
                                empName:empName,
                                gender:gender,
                                email:email,
                                dId:dId
                            },
                            function (result) {
                                if(result.code==200){
                                    //获取最后一页数据,刷新页面
                                    let lastPage = result.extend.lastPage;
                                    console.log("lastPage:"+lastPage)
                                    self.getEmps(lastPage);
                                    $("#add_emp").modal('hide');

                                    //清空表单数据
                                    $("#add_emp_form")[0].reset();
                                    $("#add_emp_form").find("*").removeClass("has-error has-success");
                                    $("#add_emp_form").find(".help-block").text('');
                                    //清空校验状态位
                                    self.vali_name = false;
                                    self.vali_dept = false;
                                    self.vali_email = false;
                                    //清空选中的部门
                                    self.selected_dept_id = "";
                                    $("#dedt_btn").text("选择部门");
                                }
                            })
                    }else{
                        alert("请正确填写表单中的信息,再提交");
                    }
                    //提交表单

                }
                // console.log(empName)
                // console.log(gender)
                // console.log(email)
                // console.log(dId)


            }

总结:校验页面的编写

  1. 用正则(前后端都可以,用后端进行重名校验

  2. 通过后端或者前端校验结束后拿到提示信息,根据匹配情况给前端页面设置样式(比如success,error)以及

    span里的提示信息

  3. 然后还要注意当重新聚焦到(通过聚焦focusOut()判断正则和重名)的时候,要注意先清空success,error的class样式,还有清空span里的提示信息

  4. 我是通过标志位判断每个表单参数是否匹配,都匹配了才可以提交Ajax请求提交表单。

  5. 记住:提交完表单后,要在回调函数里清空表单的数据,以及前端界面的一些校验提示的样式和提示信息。

    还要清空我设置的校验标志位,以及这里的下拉菜单选中的部门id

9.2JSR303后端校验

技术图片

技术图片

防君子,不防小人的前端JS校验,要通过后端的校验来保证表单参数的正确性。这个时候JSR303应运而生!

        <!--JSR303校验-->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>

这里要注意点:如果使用Tomcat7及以下的版本,那么validator和Tomcat lib中的EL表达式语法是不匹配的。会提示找不到Class EL....之类的异常

解决方法:下载

技术图片

往tomcat的lib中添加了el-api-3.0.0的jar包

public class Employee {
    private Integer empId;

    @Pattern(regexp = "^[[[\\\\u4e00-\\\\u9fa5]a-zA-Z0-9]+[-|a-zA-Z0-9._]$]{2,7}",message = "请填写2-7位中文或者数字或英文")
    private String empName;

    private String gender;

    @Pattern(regexp = "^[a-zA-Z0-9_.-][email protected][a-zA-Z0-9-]+(\\\\.[a-zA-Z0-9-]+)*\\\\.[a-zA-Z0-9]{2,6}$",message = "邮件格式错误!首字母必须是字母或数字")
    private String email;
    @RequestMapping(value = "/emps/add",method = RequestMethod.POST)
    @ResponseBody
    public Msg addEmp(@Valid Employee employee, BindingResult result) {
        HashMap<String, Object> map = new HashMap<>();
        //正则校验有误
        if (result.hasErrors()) {
            List<FieldError> errors = result.getFieldErrors();
            for (FieldError error : errors) {
                map.put(error.getField(), error.getDefaultMessage());
            }
            return Msg.fail().add("errorFields", map);
        }

        //重名校验有误
        String empName = employee.getEmpName();
        boolean nameChecked = employeeService.getNameChecked(empName);
        if (!nameChecked) {
            map.put("empNameDuplicated", "已存在该用户名");
            return Msg.fail().add("errorFields", map);
        } else {
            //校验正确
            employeeService.addEmployee(employee);

            //要拿到最后一页
            //总员工数
            int lastPage = employeeService.getLastPage(5);

            return Msg.success().add("lastPage", lastPage);
        }
    }

如何拿到返回的后端校验返回的信息,去做其他的处理

            //增加员工 提交
            add_emp_submit:function () {
                let self = this;

                let empName = $('#inputName').val();
                let gender = $('input[name="gender"]:checked').attr('value');
                let email = $('#inputEmail').val();
                let dId = this.selected_dept_id;

                if(self.selected_dept_id==''){
                    $("#dedt_btn").next().css("color","red");
                    $("#dedt_btn").next().text("请选择部门");
                    self.vali_dept = false;
                }else{
                    //全都校验成功,才可以提交
                    if(self.vali_name&&self.vali_dept&&self.vali_email){
                        $.post(baseURL+"emps/add",
                            {
                                empName:empName,
                                gender:gender,
                                email:email,
                                dId:dId
                            },
                            function (result) {
                                //先进入后端校验,防君子,不防小人,所以需要后端JSR303校验
                                if(result.code==500){
                                    //后端校验,有误
                                    let error_emp_name = result.extend.errorFields.empName;
                                    let error_emp_email = result.extend.errorFields.email;
                                    let error_emp_name_duplicated = result.extend.errorFields.empNameDuplicated;

                                    //说明这个数据校验有误
                                    if(error_emp_name!=undefined){
                                        $("#inputName").parent().addClass("has-error");
                                        $("#inputName").next().text(error_emp_name);
                                    }
                                    if(error_emp_email!=undefined){
                                        $("#inputEmail").parent().addClass("has-error");
                                        $("#inputEmail").next().text(error_emp_email);
                                    }
                                    if(error_emp_name_duplicated!=undefined){
                                        $("#inputName").parent().addClass("has-error");
                                        $("#inputName").next().text(error_emp_name_duplicated);
                                    }
                                } else if(result.code==200){
                                        //获取最后一页数据,刷新页面
                                        let lastPage = result.extend.lastPage;
                                        console.log("lastPage:"+lastPage)
                                        self.getEmps(lastPage);
                                        $("#add_emp").modal('hide');

                                        //清空表单数据
                                        $("#add_emp_form")[0].reset();
                                        $("#add_emp_form").find("*").removeClass("has-error has-success");
                                        $("#add_emp_form").find(".help-block").text('');
                                        //清空校验状态位
                                        self.vali_name = false;
                                        self.vali_dept = false;
                                        self.vali_email = false;
                                        //清空选中的部门
                                        self.selected_dept_id = "";
                                        $("#dedt_btn").text("选择部门");
                                    }
                            })
                    }else{
                        alert("请正确填写表单中的信息,再提交");
                    }

10. 修改员工

10.1 模态框

总结:我上面的添加员工 那个下拉菜单有问题,,,是自己写的,而不是用select组件,所以浪费了很多时间获取数据和绑定事件。下面对修改框做了调整

        <!-- 修改员工Modal -->
        <div class="modal fade" id="update_emp_modal" tabindex="-1" role="dialog" aria-labelledby="update_empLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                        <h4 class="modal-title">修改员工</h4>
                    </div>
                    <div class="modal-body">
                        <form class="form-horizontal" id="update_emp_form">
                            <div class="form-group">
                                <label class="col-sm-2 control-label">名字</label>
                                <div class="col-sm-10">
                                    <p class="form-control-static" id="update_Name">{{emp.empName}}</p>
                                </div>
                            </div>
                            <div class="form-group">
                                <label  class="col-sm-2 control-label">性别</label>
                                <div class="col-sm-10">
                                    <label class="radio-inline">
                                        <input type="radio" name="gender"  value="M" :checked="'M'==emp.gender?'checked':''"> 男
                                    </label>
                                    <label class="radio-inline">
                                        <input type="radio" name="gender" value="F" :checked="'F'==emp.gender?'checked':''"> 女
                                    </label>
                                </div>
                            </div>
                            <div class="form-group">
                                <label for="update_Email" class="col-sm-2 control-label">Email</label>
                                <div class="col-sm-10">
                                    <input type="email" class="form-control" name="email" id="update_Email" v-bind:value="emp.email">
                                    <span class="help-block"></span>
                                </div>
                            </div>
                            <!--下拉菜单-->
                            <div class="form-group">
                                <label for="inputEmail" class="col-sm-2 control-label">选择部门</label>
                                <div class="col-sm-10">
                                    <select class="form-control" name="dId">
                                        <option v-for="dept in depts" v-bind:value="dept.deptId">{{dept.deptName}}</option>
                                    </select>
                                </div>
                            </div>
                        </form>
                    </div>
                    <div class="form-group">
                        <div class="modal-footer">
                            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                            <button type="button" class="btn btn-primary" id="update_emp_button" @click="update_emp_submit(pageInfo.pageNum)">提交</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>

10.2 修改员工的JS

把校验函数封装一下,代码复用。增加,修改都可以用。通过传入相应的选择器即可。

            //封装前端校验邮件函数,通过传入相应的(输入框的)选择器
            validate_email:function(ele){
                //正则表达式判断其他表单参数
                $(ele).focusout(function () {
                    $(ele).parent().removeClass("has-success has-error");
                    $(ele).next().text("");
                    let email = $(ele).val();
                    let regx = /^[a-zA-Z0-9_.-][email protected][a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$/

                    // 长度不限,可以使用英文(包括大小写)、数字、点号、下划线、减号,首字母必须是字母或数字;
                    console.log(regx.test(email));
                    if(regx.test(email)){
                        window.empList.vali_email = true;

                        //符合
                        //1. 给父元素添加bootstrap 校验成功样式
                        $(ele).parent().addClass("has-success")

                    }else{
                        window.empList.vali_email = false;
                        //不符合
                        // 1. 给父元素添加bootstrap 校验错误样式
                        $(ele).parent().addClass("has-error");
                        // 2. 在其下面添加span的提示内容
                        $(ele).next().text("邮件格式错误!首字母必须是字母或数字");
                    }
                });
            },

模态框的显示:包含了修改员工的信息的回显,以及表单校验,和模态框显示

            //修改模态框的显示
            updateEmp:function(empId){
                let self = this;
                //获取该员工的信息
                $.get(baseURL+"emps/"+empId,
                    function (result) {
                        self.emp = result.extend.emp;
                        let gender = result.extend.emp.gender;
                        let dId = result.extend.emp.dId;
                        console.log("gender:"+gender)
                        //给下拉菜单赋值
                        $("#update_emp select").val(dId);

                        //要把empId传递到修改模态框的提交
                        $("#update_emp_button").attr("edit-id",empId);
                    });

                //获取部门数据
                this.getDepts();

                //显示模态框
                $("#update_emp_modal").modal({
                    backdrop:"static"
                });

                //表单校验
                this.validate_email("#update_Email");
            },

提交修改员工表单: 前后端遵循Restful API风格,修改用put请求

            update_emp_submit:function(pageNum){
                let empId = $("#update_emp_button").attr("edit-id");
                let self = this;

                //邮件格式符合
                if(self.vali_email){

                    $.ajax({
                        url:baseURL+"emps/"+empId,
                        type:"PUT",
                        data:$("#update_emp_form").serialize(),
                        success:function (result) {
                            if(result.code==200){
                                //修改成功
                                //跳转到修改的员工的本页
                                self.getEmps(pageNum);

                                //清掉表单数据以及状态位
                                self.vali_email = false;
                                $("#update_emp_modal").find('*').removeClass("has-success has-error");
                                $("#update_emp_modal .help-block").text("");
                                $("#update_emp_modal").modal('hide');

                            }else{
                                //后端校验邮件。。。没必要啊
                            }
                        }
                    });
                }else{
                    //邮件有误,不得更新
                    alert("请正确填写邮件!")
                }
            },

10.3 Restful API对put请求的使用

首先引入一些能支持Restful API的过滤器,比如通过携前端提交表单时带_method参数可以把表面上为POST请求转换处理成Rest风格的put或delete

    <!-- 浏览器不支持put,delete等method,由该filter将/xxx?_method=delete转换为标准的http delete方法 -->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

因为Tomcat对于前端提交的表单,内部的实现是:将POST请求的报文,把请求体里的参数封装成一个map。

然后request.getParam("")就是从map里获取键值对。而通过查看Tomcat的源码可知:

    /**
     * Comma-separated list of HTTP methods that will be parsed according
     * to POST-style rules for application/x-www-form-urlencoded request bodies.
     */
    protected String parseBodyMethods = "POST";
            //当Tomcat判断不是POST请求,直接返回,不会对请求体进行处理。
            //所以request.getParam()根本获取不到参数
            if( !getConnector().isParseBodyMethod(getMethod()) ) {
                success = true;
                return;
            }

所以需要一个转换器,以过滤器的方式来增强request对象,让这个转换器替Tomcat识别put请求,并完成put请求报文的封装。让增强后的request调用getParameter()后能获取表单数据。

    <filter>
        <filter-name>HttpMethodPutFilter</filter-name>
        <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HttpMethodPutFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

控制器:

    //获取某一位员工的信息
    @RequestMapping(value = "/emps/{empId}",method = RequestMethod.GET)
    @ResponseBody
    public Msg getEmp(@PathVariable(value = "empId") int empId){
        Employee emp = employeeService.getEmp(empId);
        return Msg.success().add("emp",emp);
    }

    //修改一位员工的信息
    @RequestMapping(value = "/emps/{empId}",method = RequestMethod.PUT)
    @ResponseBody
    public Msg updateEmp(@PathVariable(value = "empId") int empId, Employee employee){
        employeeService.updateEmp(empId,employee);
        return Msg.success();
    }

11. 删除员工

//    删除一位员工
    @RequestMapping(value = "/emps/{empId}",method = RequestMethod.DELETE)
    @ResponseBody
    public Msg deleteEmp(@PathVariable(value = "empId") int empId){
        employeeService.deleteEmp(empId);
        return Msg.success();
    }
<button type="button"  class="btn btn-sm btn-danger"     @click="del_emp(emp.empId,emp.empName)">
                 <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
                                删除
                            </button>
            //删除单个员工
            del_emp:function (empId,empName) {
                let self = this;

                if(confirm("你确认删除【"+empName+"】吗?"))
                $.ajax({
                    url:baseURL+"emps/"+empId,
                    type:"DELETE",
                    success:function (result) {
                        if(result.code==200){
                            //删除成功
                           self.getEmps(self.pageInfo.pageNum);
                        }
                    }
                });
            }

12.批量删除

JS批量删除

            //批量删除
            del_emp_batch:function () {
                let self = this;

                let checkAll = $("#selectAll").prop("checked");
                //可以删除二合一。 多个用-相连接,后端再分离出每个要删除的员工Id
                let empIds = new Array();

                if(checkAll){
                    for(let i=0;i<self.emps.length;i++){
                        console.log(empIds)
                        empIds.push(self.emps[i].empId);
                    }
                    console.log("empIds:"+empIds)

                    let old_empIds = empIds.join(',');
                    //用-连起来
                    console.log(typeof old_empIds)
                    console.log(typeof empIds)
                    empIds = empIds.join('-');


                    if(confirm("你确认删除【"+old_empIds+"】吗")){
                        $.ajax({
                            url:baseURL+"emps/batch/"+empIds,
                            type:"DELETE",
                            success:function (result) {
                                if(result.code=200){
                                    self.getEmps(self.pageInfo.pageNum+1);

                                    //把选中框的状态关掉
                                    $("#selectAll").prop("checked",false);
                                    $("input[name=del_emp]:checked").prop("checked",false);
                                }
                            }
                        });
                    }
                }else{
                    //拿到所有选中的节点
                    let del_emps = $("input[name=del_emp]:checked");
                    $.each(del_emps,function (index,del_emp) {
                        empIds.push($(del_emp).attr("del-id"));
                    })

                    let old_empIds = empIds.join(',')

                    empIds = empIds.join('-');

                    if(confirm("你确认删除【"+old_empIds+"】吗")){
                        $.ajax({
                            url:baseURL+"emps/batch/"+empIds,
                            type:"DELETE",
                            success:function (result) {
                                if(result.code=200){
                                    self.getEmps(self.pageInfo.pageNum);

                                    //把选中框的状态关掉
                                    $("#selectAll").prop("checked",false);
                                    $("input[name=del_emp]").prop("checked",false);
                                }
                            }
                        });
                    }
                }
            }

全选,非全选:

//全选和全不选
selectAll:function () {
    let checkAll = $("#selectAll").prop("checked");
    if(checkAll){
        //为该页多选框也选中
        $("input[name='del_emp']").prop("checked",true);
    }else{
        $("input[name='del_emp']").prop("checked",false);
    }
},

//选中某一个,当该页所有的都被选中,则全选框自动变checked
selectOne:function (empName,empId) {
    let checkAll = $("input[name=del_emp]:checked").length == $("input[name=del_emp]").length;
    if(checkAll){
        $("#selectAll").prop("checked",true);
    }else{
        $("#selectAll").prop("checked",false);
    }
},

JQuery相关语法

<script type="text/javascript">

var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"

document.write(arr.join())
</script>
<head>
    <title></title>
    <script src="jquery-1.9.0.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $('input:hidden').each(function (index, obj) {
                alert(obj.name + "..." + obj.value);
            });
        });
    </script>
</head>
<body>
<input type="hidden" value="1" name="a"/>
<input type="hidden" value="2" name="b"/>
<input type="hidden" value="3" name="c"/>
</body>

上面这段代码用到了input集合的索引,有用到了input集合的dom对象,可以通过该对象,拿到其对应的属性如:name,value等;

$.each()方法

  1. 该方法处理一维数组,代码如下:
$.each(["aaa","bbb","ccc"],function(index,value){
     alert(i+"..."+value);
});

控制器:

//批量删除按钮触发的
@RequestMapping(value = "/emps/{empIds}",method = RequestMethod.DELETE)
@ResponseBody
public Msg deleteEmp(@PathVariable(value = "empIds") String empIds){
    if(empIds.contains("-")){
        ArrayList<Integer> ids = new ArrayList<>();

        String[] split = empIds.split("-");
        for(String empId:split){
            int id = Integer.parseInt(empId);
            ids.add(id);
            employeeService.deleteEmpList(ids);
        }
    }else{
        employeeService.deleteEmp(Integer.parseInt(empIds));

    }
    return Msg.success();
}

总结:多个id用-连接起来,而不是封装成数组直接传过去,让Springmvc做参数绑定。记住这种技巧。

13.文件上传

总结:

  1. 要在spring配置多媒体解析器

    因为MultipartResolver依赖于Apache的这个jar包

            <!--文件上传的依赖-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>1.3.3</version>
            </dependency>
    

    spring注册Bean

    <!--配置多媒体解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property>
        <property name="maxInMemorySize" value="#{1024*1024*1024}"></property>
        <property name="maxUploadSize" value="#{1024*1024*5}"></property>
    </bean>
@Controller
public class FileController {

    @RequestMapping(value = "/file",method = RequestMethod.POST)
    public String uploadFile(@RequestParam(value = "file") MultipartFile multipartFile, Model model, HttpServletRequest request){

        String basePath = "E:/";

        if(multipartFile!=null&&!multipartFile.isEmpty()){
            //1.获取原始文件名
            String originalFilename = multipartFile.getOriginalFilename();

            //2.获取前缀
            String originalFilenamePrefix = originalFilename.substring(0, originalFilename.lastIndexOf('.'));

            //3.封装新的文件名  名字+时间戳
            String newFileName = originalFilenamePrefix + new Date().getTime()
                    + originalFilename.substring(originalFilename.lastIndexOf('.'));

            //4. 打开文件流
            File file = new File(basePath+newFileName);

            //5. 保存文件
            try {
                multipartFile.transferTo(file);
                model.addAttribute("fileName",originalFilename);

            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("上传失败");
            }
        }
        return "upload/uploadSuc";
    }

14. 拦截器(登陆认证)

拦截器实现的原理:

  1. 实现HandlerInterceptor接口。编写拦截器
  2. 配置拦截器和HandlerMapping的映射关系
  3. 编写控制器
    <!--拦截器 和HandlerMapping之间的配置 在HandlerMapping找到相应的Handler之前,指定某个拦截器进行拦截-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="cn.zhanp.ssm_crud.intercepetor.LoginInteceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
public class LoginInteceptor implements HandlerInterceptor {

    /**
     * 这个方法 是在 进入Handler方法之前执行的
     * 应用:在这里进行身份认证,身份权限认证等等 不过要放行(登陆页面的url)
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        //1. 如果是登陆页面,放行
        String requestURI = httpServletRequest.getRequestURI();
        if(requestURI.contains("login") || requestURI.contains("toLogin")){
            return true;
        }else{
            HttpSession session = httpServletRequest.getSession();
            String username = (String)session.getAttribute("username");
            if(username!=null){
                return true;
            }else{
//                回到登陆页面
                httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/toLogin");
                return false;
            }
        }


    }

    /**
     * 这个方法 是在Handler方法之后,返回ModelAndView之前执行的
     * 应用: 公用的一些数据模型(ModelAndView)可以在这里设置,比如导航菜单
     */

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 这个方法是在 Handler执行完成后执行
     * 应用: 捕捉异常把
     */

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }

以上是关于SSM框架CRUD小案例的主要内容,如果未能解决你的问题,请参考以下文章

SSM框架整合以及书籍管理CRUD系统

SSM框架整合以及书籍管理CRUD系统

ssm 框架实现增删改查CRUD操作(Spring + SpringMVC + Mybatis 实现增删改查)

SSM框架下的CRUD项目搭建和配置

SSM面向CRUD编程专栏 3关于黑马程序员最全SSM框架教程视频,P37集老师跳过的模块创建以及tomcat下载安装配置和运行等诸多问题

黑马程序员最全SSM框架用户角色案例(SSM整合版)