SSM-CRUD实战项目

1. 项目总览

SpringMVC + Spring + MyBatis

CRUD:增删改查

  • 功能:
  1. 分页

  2. 数据校验

    • jquery前端校验+JSR303后端校验
  3. ajax

  4. Rest风格的URI;使用HTTP协议请求方式的动词,来表示对资源的操作(GET查询, POST新增, PUT修改, DELETE删除)

  • 技术点:
  1. 基础框架-ssm
  2. 数据库-MySQL
  3. 前端框架-BootStrap:快速搭建简洁美观的界面
  4. 项目依赖管理——Maven
  5. 分页——pagehelper插件
  6. 逆向工程——MyBatis Generator(MBG)
  • 最终效果图

项目Git地址

2. SSM框架整合

  • Spring MVC 负责实现 MVC 设计模式

  • MyBatis 负责数据持久层

  • Spring负责管理SpringMVC和MyBatis所用到的相关对象的创建和依赖注入

    SSM框架整合,实际上是Spring与MyBatis的整合,因为SpringMVC是Spring的一个子模块

2.1 项目环境

  • IDEA 2021.2.3 Ultimate Edition
  • MySQL 8.0.26
  • Tomcat 10.0.11
  • Maven 3.8.3

2.2 基础环境搭建

  • 创建maven工程
  • 引入项目依赖的jar包
  • 引入bootstrap前端框架

2.3 配置文件*

  • 第一步:pom.xml 引入依赖jar包

        <dependencies>
    <!-- SpringMVC -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.18.RELEASE</version>
    </dependency> <!-- Spring JDBC 事务控制 -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.18.RELEASE</version>
    </dependency> <!-- Spring AOP 面向切面编程 -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.18.RELEASE</version>
    </dependency> <!-- Spring TestContext 用于注解导入Spring全局配置文件-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.18.RELEASE</version>
    </dependency> <!-- MyBatis -->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
    </dependency> <!-- MBG MyBatis逆向工程 -->
    <dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.7</version>
    </dependency> <!-- MyBatis整合Spring的适配包 -->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
    </dependency> <!-- MySQL驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
    </dependency> <!-- 数据库连接池 Druid -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
    </dependency> <!-- PageHelper 分页插件 -->
    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.0</version>
    </dependency> <!-- JSTL:JSP标签库-->
    <dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    </dependency> <!-- lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
    </dependency> <!-- 日志 -->
    <!-- 注意log4j与slf4j的版本兼容问题 -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
    </dependency> <!-- Junit -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>compile</scope>
    </dependency> <!-- JSP -->
    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
    </dependency> <!-- JSR303数据校验支持 -->
    <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.1.Final</version>
    </dependency> <!-- servlet -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
    </dependency> <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
    </dependency> <!-- Jackson 处理Json数据 -->
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.0</version>
    </dependency> </dependencies>
  • 第二步:WEB-INF文件夹下,web.xml 配置拦截器拦截浏览器请求

    • 启动spring容器,加载spring全局配置文件
    • 配置SpringMVC前端控制器DispatcherServlet,对浏览器发送的请求进行统一处理,指定springMVC配置文件的地址
    • 字符编码过滤器
    • 配置HiddenHttpMethodFilter,过滤请求方式
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">
    <!--1、启动 Spring 容器-->
    <!--
    配置ContextLoaderListener, 加载Spring父容器 (父类的initWebApplicationContext()方法中)
    可以从ServletContext中根据 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 这个key来找到Spring容器
    -->
    <context-param>
    <!-- 指定Spring配置文件位置 -->
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springApplicationConfig.xml</param-value>
    </context-param>
    <!-- 该监听器将根据contextConfigLocation参数加载Spring配置文件, 初始化Spring应用上下文 -->
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener> <!-- 2、配置SpringMVC的前端控制器 DispatcherServlet,对浏览器发送的请求统一进行处理 -->
    <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
    <init-param>
    <!-- contextConfigLocation为固定值 -->
    <param-name>contextConfigLocation</param-name>
    <!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
    <param-value>classpath:springMVC.xml</param-value>
    </init-param> <!--
    作为框架的核心组件,在启动过程中有大量的初始化操作要做
    而这些操作放在第一次请求时才执行会严重影响访问速度
    因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
    -->
    <load-on-startup>1</load-on-startup>
    </servlet> <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <!--
    设置springMVC的核心控制器所能处理的请求的请求路径
    / 所匹配的请求可以是/login或.html或.js或.css方式的请求路径
    但是 / 不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern> <!-- 注意,只有一个 / -->
    </servlet-mapping> <!--3、配置springMVC的字符编码过滤器,防止乱码;字符编码过滤器,一定要放在所有过滤器前面-->
    <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    <!-- 注意这里设置必须两个参数为true,可查看源码 -->
    <init-param>
    <param-name>forceResponseEncoding</param-name>
    <param-value>true</param-value>
    </init-param>
    <init-param>
    <param-name>forceRequestEncoding</param-name>
    <param-value>true</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> <!-- /* 是包含所有请求,/ 是不包括.jsp的请求 -->
    </filter-mapping> <!-- 4、使用Rest风格的URI,将页面普通的post请求转换为指定的delete或者put请求 -->
    <!--配置HiddenHttpMethodFilter,过滤请求方式,将POST请求转换为PUT、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>
    <!-- FormContentFilter:将PUT、PATCH、DELETE请求体解析为Servlet请求参数 -->
    <!-- 因为ajax发送put请求时,Tomcat一看是PUT,就不会封装请求体中的数据为map -->
    <!-- 可以发PUT请求 -->
    <filter>
    <filter-name>FormContentFilter</filter-name>
    <filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>FormContentFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 加载静态资源的另一种方式 -->
    <!-- <servlet-mapping>-->
    <!-- <servlet-name>default</servlet-name>-->
    <!-- <url-pattern>*.js</url-pattern>-->
    <!-- </servlet-mapping>-->
    <!-- <servlet-mapping>-->
    <!-- <servlet-name>default</servlet-name>-->
    <!-- <url-pattern>*.css</url-pattern>-->
    <!-- </servlet-mapping>-->
    <!-- <servlet-mapping>-->
    <!-- <servlet-name>default</servlet-name>-->
    <!-- <url-pattern>*.jpg</url-pattern>-->
    <!-- </servlet-mapping>-->
    </web-app>
  • 第三步:resources资源目录下,springMVC.xml 配置SpringMVC

    • 开启扫描组件,扫描注解配置的类
    • 配置视图解析器
    • 开放对静态资源的访问
    • 开启mvc注解驱动
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- SpringMVC的配置文件,包含网站跳转逻辑的控制及配置 -->
    <!--开启扫描组件-->
    <context:component-scan base-package="com.atguigu.ssmcrud"></context:component-scan> <!-- &lt;!&ndash;配置thymeleaf视图解析器,方便页面返回&ndash;&gt;-->
    <!-- <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">-->
    <!-- <property name="order" value="1"/> &lt;!&ndash;优先级设置&ndash;&gt;-->
    <!-- <property name="characterEncoding" value="UTF-8"/>-->
    <!-- <property name="templateEngine"> &lt;!&ndash;模板&ndash;&gt;-->
    <!-- <bean class="org.thymeleaf.spring5.SpringTemplateEngine">-->
    <!-- <property name="templateResolver">-->
    <!-- <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">-->
    <!-- &lt;!&ndash; 视图前缀 &ndash;&gt;-->
    <!-- <property name="prefix" value="/WEB-INF/views/"/>-->
    <!-- &lt;!&ndash; 视图后缀 &ndash;&gt;-->
    <!-- <property name="suffix" value=".html"/>-->
    <!-- <property name="templateMode" value="HTML5"/>-->
    <!-- <property name="characterEncoding" value="UTF-8" />-->
    <!-- </bean>-->
    <!-- </property>-->
    <!-- </bean>-->
    <!-- </property>-->
    <!-- </bean>--> <!-- 配置视图解析器 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
    </bean> <!--配置视图控制器,建立请求与跳转页面的映射关系-->
    <!-- <mvc:view-controller path="/" view-name="emps"></mvc:view-controller>--> <!--开放对静态资源的访问,将springMVC不能处理的请求交给tomcat-->
    <mvc:default-servlet-handler/> <!--能支持springmvc更高级的一些功能,JSR303校验,快捷的ajax...映射动态请求-->
    <mvc:annotation-driven /> </beans>
  • 第四步:resources文件夹下,配置数据库连接和日志文件,dbconfig.properties和log4j.properties

    dbconfig.properties

    mysql.driver=com.mysql.cj.jdbc.Driver
    mysql.url=jdbc:mysql://localhost:3306/mybatisdb?allowMultiQueries=true
    mysql.username=root
    mysql.password=wenhao

    log4j.properties

    log4j.rootLogger=DEBUG,A1
    
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
  • 第五步:resources文件夹下,springApplicationConfig.xml 全局配置文件,对Spring进行配置,与主要配置和业务逻辑有关

    • 开启扫描组件,这里无需扫描controller

    • 整合MyBatis

      1. 引入数据库配置文件 properties文件位置,classpath——>resources目录下
    1. 配置数据源(数据库连接池),可以使用c3p0或者Druid
    2. 配置Spring和MyBatis的整合:SqlSessionFactory,会指定MyBatis全局配置文件位置
    3. 配置sqlSession
    4. 配置扫描器,扫描自定义的mapper接口
    5. 配置一个可以进行批处理的sqlSession
    • 事务控制的配置(事务管理器)

      1. 建立与数据库连接池的映射
      2. 开启基于注解的事务
      3. 配置事务增强
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- spring的配置文件,这里主要配置和业务逻辑有关的 --> <!-- spring无需扫描controller,因为spring只进行组件扫描,对于控制层的处理则是交给springMVC处理 -->
    <context:component-scan base-package="com.atguigu.ssmcrud">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan> <!-- =======================整合MyBatis======================= -->
    <!-- 引入数据库的配置文件 -->
    <context:property-placeholder location="classpath:dbconfig.properties"/> <!-- 配置数据源,这里使用druid数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="${mysql.username}"></property>
    <property name="password" value="${mysql.password}"></property>
    <property name="url" value="${mysql.url}"></property>
    <property name="driverClassName" value="${mysql.driver}"></property>
    <property name="initialSize" value="5"></property>
    <property name="maxActive" value="10"></property>
    </bean> <!-- 配置和MyBatis的整合:SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--指定MyBatis全局配置文件的位置-->
    <property name="configLocation" value="classpath:mybatisConfig.xml"></property>
    <!--建立与数据库连接池的映射关系-->
    <property name="dataSource" ref="dataSource"></property>
    <!--指定mybatis,mapper文件的位置-->
    <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
    </bean> <!-- 配置扫描器,将MyBatis的Dao接口实现,并加入到ioc容器中,即扫描自定义的Mapper接口 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--扫描所有的dao接口的实现,加入到ioc容器中-->
    <property name="basePackage" value="com.atguigu.ssmcrud.dao"></property>
    </bean> <!--配置一个可以执行批量的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <constructor-arg name="executorType" value="BATCH"/>
    </bean> <!-- ==================事务控制的配置(事务管理器)====================== -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--控制数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
    </bean> <!--开启基于注解的事务,使用xml配置形式的事务-->
    <aop:config>
    <!--切入式表达式,service下所有的类以及所有的方法,都可作为切入点。哪些方法可能会被切入事务-->
    <aop:pointcut id="txPoint" expression="execution(* com.atguigu.ssmcrud.service..*(..))"/>
    <!--配置事务增强,切入的规则由txAdvice指定,切入的方法由txPoint指定-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config> <!--配置事务增强,事务切入后怎么办,如何处理(事务如何切入)-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
    <!--这个切入点切入的所有方法都是事务方法-->
    <tx:method name="*"/>
    <!--以get开始的所有方法,都认为是查询方法-->
    <tx:method name="get*" read-only="true"/>
    </tx:attributes>
    </tx:advice> </beans>
  • 第六步:resources文件下,mybatisConfig.xml 配置MyBatis配置文件

    • 配置一些对MyBatis的属性设置(如:settings、typeAliases等,properties属性已经在外部进行配置了,即springApplicationConfig.xml文件中)
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--MyBatis配置文件,配置一些MyBatis不好配置的设置-->
    <configuration>
    <settings>
    <!--开启驼峰命名映射-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!--可以返回自动生成主键-->
    <setting name="useGeneratedKeys" value="true"/>
    </settings>
    <!--
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?,
    typeAliases?, typeHandlers?,
    objectFactory?,objectWrapperFactory?,
    plugins?,
    environments?, databaseIdProvider?, mappers?
    -->
    <plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
    <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
    <property name="param1" value="value1"/>
    </plugin>
    </plugins> </configuration>
  • 第七步:resources文件下,mbg.xml 配置逆向生成属性

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
    PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
    <properties resource="dbconfig.properties"/>
    <!--
    targetRuntime="MyBatis3Simple":生成简单版的CRUD
    MyBatis3:豪华版 用于复杂的数据库CRUD操作
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 清除生成文件中的注释,该标签必须在生成文件之前定义 -->
    <commentGenerator>
    <property name="suppressAllComments" value="true"/>
    </commentGenerator> <!-- jdbcConnection:指定如何连接到目标数据库 -->
    <jdbcConnection driverClass="${mysql.driver}"
    connectionURL="${mysql.url}"
    userId="${mysql.username}"
    password="${mysql.password}">
    </jdbcConnection> <!--类型解析器-->
    <javaTypeResolver >
    <property name="forceBigDecimals" value="false" />
    </javaTypeResolver> <!-- javaModelGenerator:指定javaBean的生成策略(生成的位置)
    targetPackage="test.model":目标包名
    targetProject="\MBGTestProject\src":目标工程
    -->
    <javaModelGenerator targetPackage="com.atguigu.ssmcrud.bean"
    targetProject=".\src\main\java">
    <property name="enableSubPackages" value="true" />
    <property name="trimStrings" value="true" />
    </javaModelGenerator> <!-- sqlMapGenerator:sql语句与接口方法映射生成策略 xml配置文件 -->
    <!-- 指定sql映射文件生成的位置:生成到resources文件下的mapper文件夹中-->
    <sqlMapGenerator targetPackage="mapper"
    targetProject=".\src\main\resources">
    <property name="enableSubPackages" value="true" />
    </sqlMapGenerator> <!-- javaClientGenerator:指定mapper接口方法所在的位置 -->
    <!-- 指定dao接口生成的位置:mapper接口(业务行为接口) -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.ssmcrud.dao"
    targetProject=".\src\main\java">
    <property name="enableSubPackages" value="true" />
    </javaClientGenerator> <!-- table标签:指定每个表的生成策略 -->
    <!-- 指定要逆向分析数据库中的哪些表:根据表要创建javaBean -->
    <table tableName="tbl_emp" domainObjectName="Employee"></table>
    <table tableName="tbl_dept" domainObjectName="Department"></table> </context>
    </generatorConfiguration>

2.4 测试

2.4.1 数据库的创建

创建一个名为ssm_crud的数据库

  • 这个数据库中有tbl_dept、tbl_emp两张表
CREATE TABLE `tbl_emp` (
`emp_id` int(11) NOT NULL AUTO_INCREMENT,
`emp_name` varchar(255) DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `tbl_dept` (
`dept_id` int(11) NOT NULL AUTO_INCREMENT,
`dept_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 需要增加外键,即tbl_emp的d_id字段是tbl_dept的外键,从表tbl_dept的被引用字段dept_id是其主键

2.4.2 使用Spring单元测试对数据库进行操作

使用注解的方式导入Spring的配置文件,并且使用Spring单元测试(需添加依赖)

        <!-- Spring TestContext 用于注解导入Spring全局配置文件-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.18.RELEASE</version>
</dependency>

测试代码:

/*
* 推荐Spring项目使用Spring的单元测试,就可以自动注入我们需要的组件了
* 1. 导入SpringTest依赖,注意scope标签,要去掉test
* 2. @ContextConfiguration指定Spring配置文件的位置
* 3. autowired要使用的组件即可
* */ @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:springApplicationConfig.xml"})
public class MapperTest { /*
* ==========使用注解的方式===========
* 由于先加载了Spring配置文件,所以使用注解,自动注入,获取实例
* */
@Autowired
DepartmentMapper departmentMapper; @Autowired
EmployeeMapper employeeMapper; @Autowired
SqlSession sqlSession; /*
* 测试DepartmentMapper
* */
@Test
public void testCRUD(){
// // ===========传统方法============
// // 1. 创建Spring IOC容器
// ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:springApplicationConfig.xml");
// // 2. 从容器中获取mapper接口实例,因为在spring配置文件中,对mapper接口进行扫描并添加
// DepartmentMapper departmentMapper = ioc.getBean(DepartmentMapper.class); // 1. 新增部门信息,使用插入信息参数是可以选择的,所以选择 可选择参数插入方法,即insertSelective,因为id是自增的
departmentMapper.insertSelective(new Department(null, "开发部"));
departmentMapper.insertSelective(new Department(null, "测试部")); // 2. 生成员工数据,测试员工的插入
employeeMapper.insertSelective(new Employee(null, "Jerry", "M", "jerry@123.com", 1)); // 3. 批量插入多个员工:批量操作,需要在Spring全局配置文件中设置一个可以执行批量操作的sqlSession // for(){ // 如果直接使用自动注入的mapper对象,无论是循环多少次,都是建立了相应次数的sqlSession,并不是批量操作
// employeeMapper.insertSelective(new Employee());
// }
// 使用在spring配置文件中已经配置好的批量处理sqlSession
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 200; i++) {
String uid = UUID.randomUUID().toString().substring(0, 5) + i;
mapper.insertSelective(new Employee(null, uid, "W", uid + "@123.com", 1));
}
for (int i = 0; i < 200; i++) {
String uid = UUID.randomUUID().toString().substring(0, 5) + i;
mapper.insertSelective(new Employee(null, uid, "M", uid + "@qq.com", 2));
}
for (int i = 0; i < 200; i++) {
String uid = UUID.randomUUID().toString().substring(0, 5) + i;
mapper.insertSelective(new Employee(null, uid, "M", uid + "@gmail.com", 1));
} }
}

2.5 完善项目结构

  • bean:又entity,实体层,实现对数据库表的映射,往往是一个数据对象。
  • controller:控制层,实现对浏览器发来的请求进行相应处理。
  • dao:数据访问对象层,创建接口,定义对数据库进行CRUD的操作方法,再建立与mapper xml配置文件的映射。
  • service:业务逻辑层,实现业务的真正逻辑,编写业务逻辑的代码,可以实现切面操作等

3. 使用MBG逆向工程

1. 生成mapper及接口方法等文件

MyBatis Generator(MBG配置文件):使用MyBatis3

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="dbconfig.properties"/>
<!--
targetRuntime="MyBatis3Simple":生成简单版的CRUD
MyBatis3:豪华版 用于复杂的数据库CRUD操作
-->
<context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 清除生成文件中的注释,该标签必须在生成文件之前定义 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator> <!-- jdbcConnection:指定如何连接到目标数据库 -->
<jdbcConnection driverClass="${mysql.driver}"
connectionURL="${mysql.url}"
userId="${mysql.username}"
password="${mysql.password}">
</jdbcConnection> <!--类型解析器-->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver> <!-- javaModelGenerator:指定javaBean的生成策略(生成的位置)
targetPackage="test.model":目标包名
targetProject="\MBGTestProject\src":目标工程
-->
<javaModelGenerator targetPackage="com.atguigu.ssmcrud.bean"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator> <!-- sqlMapGenerator:sql语句与接口方法映射生成策略 xml配置文件 -->
<!-- 指定sql映射文件生成的位置:生成到resources文件下的mapper文件夹中-->
<sqlMapGenerator targetPackage="mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator> <!-- javaClientGenerator:指定mapper接口方法所在的位置 -->
<!-- 指定dao接口生成的位置:mapper接口(业务行为接口) -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.ssmcrud.dao"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator> <!-- table标签:指定每个表的生成策略 -->
<!-- 指定要逆向分析数据库中的哪些表:根据表要创建javaBean -->
<table tableName="tbl_emp" domainObjectName="Employee"></table>
<table tableName="tbl_dept" domainObjectName="Department"></table> </context>
</generatorConfiguration>

开启逆向工程:

    @Test
public void runMBG() throws XMLParserException, IOException, InvalidConfigurationException, SQLException, InterruptedException {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("src/main/resources/mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}

2. 增加含外键查询的功能

​ 主要是针对EmployeeMapper.xml文件,对SQL语句进行修改,以保证在数据库中查询员工数据的同时,通过外键将对应的部门信息查询出来。

增加了两个select查询语句、一个resultMap和一段sql

节选添加部分:

	........
<!-- 查询的结果的返回类型(员工信息+部门信息) -->
<resultMap id="WithDeptResultMap" type="com.atguigu.ssmcrud.bean.Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="gender" jdbcType="CHAR" property="gender" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="d_id" jdbcType="INTEGER" property="dId" />
<!--指定联合查询出的部门信息-->
<association property="department" javaType="com.atguigu.ssmcrud.bean.Department">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
......... <!-- sql语句中的查询参数 -->
<sql id="WithDept_Column_List">
e.emp_id, e.emp_name, e.gender, e.email, e.d_id, d.dept_id, d.dept_name
</sql> <!--
List<Employee> selectByExampleWithDept(EmployeeExample example);
查询职员信息(含部门信息)
-->
<select id="selectByExampleWithDept" resultMap="WithDeptResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="WithDept_Column_List" />
from tbl_emp e
left join tbl_dept d on e.d_id=d.dept_id
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select> <!--
Employee selectByPrimaryKeyWithDept(Integer empId);
按员工ID查询员工信息(含部门信息)
-->
<select id="selectByPrimaryKeyWithDept" resultMap="WithDeptResultMap">
select
<include refid="WithDept_Column_List" />
from tbl_emp e
left join tbl_dept d on e.'d_id'=d.'dept_id'
where emp_id = #{empId,jdbcType=INTEGER}
</select>
........

4. 查询功能

传统方式:只适用于客户端是浏览器,因为服务器会将整个页面数据回传给客户端

  1. 访问index.jsp页面
  2. index.jsp页面发送出查询员工列表请求
  3. EmployeeController来接受请求,查出员工数据
  4. 来到list.jsp页面进行展示
  5. pageHelper分页插件完成分页查询功能

URI:/emps

4.1 使用Ajax请求

Ajax特点:

  • 不刷新网页更新网页
  • 在页面加载后从服务器请求数据
  • 在页面加载后从服务器接收数据
  • 在后台向服务器发送数据

​ 为了满足不同客户端(如浏览器、Android等)向服务器发送请求,都能正确地显示页面,因为不可能返回一个页面数据给除浏览器外的其他客户端,所以服务器需要将数据以Json形式返回。提升了扩展性,与平台无关。

  1. index.jsp页面直接发送ajax请求进行员工分页数据的查询
  2. 服务器将查出的数据,以json字符串的形式返回给浏览器
  3. 浏览器收到js字符串,可以使用js对json进行解析,使用js通过dom增删改,改变页面
  4. 返回json。实现客户端的无关性

4.2 dao层

​ 定义了两个新的查询方法,这两个新方法已经与sql语句建立了映射关系

public interface EmployeeMapper {
long countByExample(EmployeeExample example); int deleteByExample(EmployeeExample example); int deleteByPrimaryKey(Integer empId); int insert(Employee record); int insertSelective(Employee record); List<Employee> selectByExample(EmployeeExample example); Employee selectByPrimaryKey(Integer empId); /*
* 定义了两个新的方法:
* 1. 按条件查询员工信息(带部门信息)
* 2. 按主键查询员工信息(带部门信息)
* */
List<Employee> selectByExampleWithDept(EmployeeExample example);
Employee selectByPrimaryKeyWithDept(Integer empId); int updateByExampleSelective(@Param("record") Employee record, @Param("example") EmployeeExample example); int updateByExample(@Param("record") Employee record, @Param("example") EmployeeExample example); int updateByPrimaryKeySelective(Employee record); int updateByPrimaryKey(Employee record);
}

4.3 service层

接口 EmployeeService.java

public interface EmployeeService {
public List<Employee> getAll(); Employee getEmp(Integer id);
}

在EmployeeServiceImpl.java文件中添加业务处理逻辑:

@Service
public class EmployeeServiceImpl implements EmployeeService{
@Autowired
EmployeeMapper employeeMapper; @Override
public List<Employee> getAll() {
return employeeMapper.selectByExampleWithDept(null); // 传入null值,即没有额外的查询的条件
} /*
* 按照员工的id查询员工
* */
@Override
public Employee getEmp(Integer id) {
Employee employee = employeeMapper.selectByPrimaryKey(id);
return employee;
}
}

4.4 controller层

@Controller
public class EmployeeController { @Autowired
EmployeeService employeeService;
/*
* 查询员工数据(分页查询)
* url: http://localhost:8080/ssm_crud/emps
* pn:分页查询的页码,这里使用RequestParam注解,如果不传pn的值,那么pn默认值为1
* */
@RequestMapping("/emps")
public String getEmps(@RequestParam(value = "pn", defaultValue = "1")Integer pn, Model model){
// 使用分页查询,引入PagerHelper分页插件
// 在查询之前只需要调用,并传入页码以及每页的数量
PageHelper.startPage(pn, 5);
// 在startPage后面紧跟查询,且这个查询就是一个分页查询,即查询返回的结果已经被分页了
List<Employee> empsList = employeeService.getAll();
// 当然也可以使用PageInfo对查询结果进行封装,以获取更多详细信息,包括查询出来的数据,传入连续显示的页数
PageInfo pageInfo = new PageInfo(empsList, 5); // 传入连续显示的页数,这里是5页,就能调用方法显示连续5页的页码
model.addAttribute("pageInfo", pageInfo); // 请求域中存放进行封装过的查询结果数据,用于在前端JSP页面进行显示 return "emps";
} @RequestMapping("/empsJson")
@ResponseBody
// 用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
public Msg getEmpsWithJson(@RequestParam(value = "pn", defaultValue = "1")Integer pn){ // 需要Jackson包支持
// 使用分页查询,引入PagerHelper分页插件
// 在查询之前只需要调用,并传入页码以及每页的数量
PageHelper.startPage(pn, 5);
// 在startPage后面紧跟查询,且这个查询就是一个分页查询,即查询返回的结果已经被分页了
List<Employee> empsList = employeeService.getAll();
// 当然也可以使用PageInfo对查询结果进行封装,以获取更多详细信息,包括查询出来的数据,传入连续显示的页数
PageInfo pageInfo = new PageInfo(empsList, 5); // 传入连续显示的页数,这里是5页,就能调用方法显示连续5页的页码 return Msg.success().add("pageInfo", pageInfo);
} // 查询指定id员工的信息
@RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)
@ResponseBody
public Msg getEmp(@PathVariable("id")Integer id) { // 指定id的值是从路径中取出
Employee emp = employeeService.getEmp(id);
return Msg.success().add("emp", emp);
}
}

4.5 效果图

5. 新增功能

  1. 在index.jsp页面点击“新增”
  2. 弹出新增对话框
  3. 去数据库查询部门列表,显示在对话框中
  4. 用户输入数据完成保存
    • Jquery前端校验
    • Ajax用户名重复校验
    • 重要数据加上后端校验(JSR303),唯一约束
  5. 完成保存
  • URI-restful风格

    • /emp/{id} GET 查询员工
    • /emp POST 保存员工
    • /emp/{id} PUT 修改员工
    • /emp/{id} DELETE 删除员工

5.1 dao层

public interface EmployeeMapper {
long countByExample(EmployeeExample example); int deleteByExample(EmployeeExample example); int deleteByPrimaryKey(Integer empId); int insert(Employee record); int insertSelective(Employee record); List<Employee> selectByExample(EmployeeExample example); Employee selectByPrimaryKey(Integer empId); /*
* 定义了两个新的方法:
* 1. 按条件查询员工信息(带部门信息)
* 2. 按主键查询员工信息(带部门信息)
* */
List<Employee> selectByExampleWithDept(EmployeeExample example); Employee selectByPrimaryKeyWithDept(Integer empId); int updateByExampleSelective(@Param("record") Employee record, @Param("example") EmployeeExample example); int updateByExample(@Param("record") Employee record, @Param("example") EmployeeExample example); int updateByPrimaryKeySelective(Employee record); int updateByPrimaryKey(Employee record);
}

5.2 service层

​ 增加两个方法,addEmp和checkUser

public interface EmployeeService {
public List<Employee> getAll(); void addEmp(Employee employee); boolean checkUser(String empName); Employee getEmp(Integer id);
}
@Service
public class EmployeeServiceImpl implements EmployeeService{
@Autowired
EmployeeMapper employeeMapper; @Override
public List<Employee> getAll() {
return employeeMapper.selectByExampleWithDept(null); // 传入null值,即没有额外的查询的条件
} @Override
public void addEmp(Employee employee) {
employeeMapper.insertSelective(employee); // insertSelective() 不需要插入id
} /*
* 检验用户名是否可用
* @return true:代表当前姓名可用;false:当前姓名不可用
* */
@Override
public boolean checkUser(String empName) {
// 创建我们的查询条件对象
EmployeeExample employeeExample = new EmployeeExample();
EmployeeExample.Criteria criteria = employeeExample.createCriteria(); // 创建拼接查询条件的对象
criteria.andEmpNameEqualTo(empName);
long count = employeeMapper.countByExample(employeeExample); // 进行查询,返回符合查询条件的记录数
return count == 0;
} /*
* 按照员工的id查询员工
* */
@Override
public Employee getEmp(Integer id) {
Employee employee = employeeMapper.selectByPrimaryKey(id);
return employee;
}
}

5.3 controller层

后端校验:

  • 使用JSR303校验,需要在pom.xml中导入Hibernate-Validator依赖
<!-- JSR303数据校验支持 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.1.Final</version>
</dependency>
  • 在EmployeeController.java文件中,saveEmp()方法中进行JSR303:
    /*
员工保存(支持JSR303校验,导入Hibernate-Validator)
使用restful风格的URI请求
- /emp/{id} GET 查询员工
- /emp POST 保存员工
- /emp/{id} PUT 修改员工
- /emp/{id} DELETE 删除员工
* */
@RequestMapping(value = "/emp", method = RequestMethod.POST)
@ResponseBody
// valid代表数据需要被校验,BindingResult 校验结果对象
public Msg saveEmp(@Valid Employee employee, BindingResult result){ // 因为页面提交来的数据与Employee对象的属性相同,会自动封装
if(result.hasErrors()){ // 校验失败
// 校验失败,返回失败,并在模态框中显示校验失败的错误信息
HashMap<String, Object> map = new HashMap<>();
List<FieldError> errors = result.getFieldErrors();
for (FieldError error : errors) {
System.out.println("错误的字段名:" + error.getField());
System.out.println("错误信息" + error.getDefaultMessage());
map.put(error.getField(), error.getDefaultMessage());
}
return Msg.fail().add("errorFields", map);
} else { // 校验通过,用户名可以使用
employeeService.addEmp(employee);
return Msg.success();
}
}

​ 校验前需要在Employee的bean对象中进行注解:

public class Employee {
private Integer empId; @Pattern(regexp = "(^[a-zA-Z0-9_-]{6,16}$)|(^[\\u2E80-\\u9FFF]{2,5})"
, message = "用户名必须是2-5位中文或者6-16位英文和数字的组合")
private String empName; private String gender; @Email // 使用@Email注解,JSR303会对这个属性进行邮箱格式校验
private String email; private Integer dId; ........ }
  • 在EmployeeController.java文件中,checkUser()方法中对传入的用户名进行检查,到数据库中检查是否该用户已经存在
    // 检查新增加的用户名是否可用
@RequestMapping("/checkuser")
@ResponseBody
public Msg checkuser(@RequestParam("empName")String empName){
// 先判断用户名是否是合法的用户名(后端校验)
String regx = "(^[a-zA-Z0-9_-]{6,16}$)|(^[\\u2E80-\\u9FFF]{2,5})";
if(!empName.matches(regx)) {
return Msg.fail().add("va_msg", "后端校验出用户名格式非法!");
}
boolean b = employeeService.checkUser(empName); // 后端校验
if (b){
return Msg.success();
} else {
return Msg.fail().add("va_msg", "后台服务器查询出数据库中已经有该用户名!");
}
}

​ employeeService.checkUser(empName); // 后端校验

    @Override
public boolean checkUser(String empName) {
// 创建我们的查询条件对象
EmployeeExample employeeExample = new EmployeeExample();
EmployeeExample.Criteria criteria = employeeExample.createCriteria(); // 创建拼接查询条件的对象
criteria.andEmpNameEqualTo(empName);
long count = employeeMapper.countByExample(employeeExample); // 进行查询,返回符合查询条件的记录数
return count == 0;
}

5.4 效果图

6. 修改功能

/emp/{id} PUT 修改员工

  1. 点击编辑
  2. 弹出用户修改的模态框(显示用户信息)
  3. 点击更新,完成用户修改

6.1 service层

public interface EmployeeService {
public List<Employee> getAll(); void addEmp(Employee employee); boolean checkUser(String empName); Employee getEmp(Integer id); void updateEmp(Employee employee);
}
@Service
public class EmployeeServiceImpl implements EmployeeService{
@Autowired
EmployeeMapper employeeMapper; @Override
public List<Employee> getAll() {
return employeeMapper.selectByExampleWithDept(null); // 传入null值,即没有额外的查询的条件
} @Override
public void addEmp(Employee employee) {
employeeMapper.insertSelective(employee); // insertSelective() 不需要插入id
} /*
* 检验用户名是否可用
* @return true:代表当前姓名可用;false:当前姓名不可用
* */
@Override
public boolean checkUser(String empName) {
// 创建我们的查询条件对象
EmployeeExample employeeExample = new EmployeeExample();
EmployeeExample.Criteria criteria = employeeExample.createCriteria(); // 创建拼接查询条件的对象
criteria.andEmpNameEqualTo(empName);
long count = employeeMapper.countByExample(employeeExample); // 进行查询,返回符合查询条件的记录数
return count == 0;
} /*
* 按照员工的id查询员工
* */
@Override
public Employee getEmp(Integer id) {
Employee employee = employeeMapper.selectByPrimaryKey(id);
return employee;
} /*
* 更新员工的信息
* */ @Override
public void updateEmp(Employee employee) {
employeeMapper.updateByPrimaryKeySelective(employee);
}
}

6.2 controller层

    /**
* 如果直接发送ajax=PUT形式的请求
* 封装的数据
* Employee
* [empId=1014, empName=null, gender=null, email=null, dId=null]
*
* 问题:
* 请求体中有数据;
* 但是Employee对象封装不上;
* update tbl_emp where emp_id = 1014;
*
* 原因:
* Tomcat:
* 1、将请求体中的数据,封装一个map。
* 2、request.getParameter("empName")就会从这个map中取值。
* 3、SpringMVC封装POJO对象的时候。
* 会把POJO中每个属性的值,request.getParamter("email");
* AJAX发送PUT请求引发的血案:
* PUT请求,请求体中的数据,request.getParameter("empName")拿不到
* Tomcat一看是PUT不会封装请求体中的数据为map,只有POST形式的请求才封装请求体为map
* org.apache.catalina.connector.Request--parseParameters() (3111);
*
* protected String parseBodyMethods = "POST";
* if( !getConnector().isParseBodyMethod(getMethod()) ) {
success = true;
return;
}
*
*
* 解决方案;
* 我们要能支持直接发送PUT之类的请求还要封装请求体中的数据
* 1、配置上HttpPutFormContentFilter;
* 2、他的作用;将请求体中的数据解析包装成一个map。
* 3、request被重新包装,request.getParameter()被重写,就会从自己封装的map中取数据
* 员工更新方法
* @param employee
* @return
*/
// 更新员工信息(注意是empId)
@RequestMapping(value = "/emp/{empId}", method = RequestMethod.PUT)
@ResponseBody
public Msg updateEmp(Employee employee){
System.out.println("将要更新的员工数据:" + employee);
employeeService.updateEmp(employee);
return Msg.success();
}

​ 由于ajax发送PUT请求会导致,Tomcat一看是PUT不会封装请求体中的数据为map,只有POST形式的请求才封装请求体为map。因此有两种解决方案:

  • 第一种方法:
    // 编辑模态框中:点击更新,更新员工信息
$("#emp_update_btn").click(function(){
//验证邮箱是否合法
//1、校验邮箱信息
var email = $("#email_update_input").val();
var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if(!regEmail.test(email)){
show_validate_msg("#email_update_input", "error", "邮箱格式不正确");
return false;
}else{
show_validate_msg("#email_update_input", "success", "");
}
//2、发送ajax请求保存更新的员工数据(验证成功后)
// 第一种方法:通过拦截器HiddenHttpMethodFilter将POST转换为PUT
$.ajax({
url:"${APP_PATH}/emp/"+$(this).attr("edit-id"),
type:"POST",
data:$("#empUpdateModal form").serialize()+"&_method=PUT",
success:function(result){
// alert(result.msg);
//1、关闭对话框
$("#empUpdateModal").modal("hide");
//2、回到本页面
to_page(currentPage);
}
});
});
  • 第二种方法:使用拦截器FormContentFilter
    // 编辑模态框中:点击更新,更新员工信息
$("#emp_update_btn").click(function(){
//验证邮箱是否合法
//1、校验邮箱信息
var email = $("#email_update_input").val();
var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if(!regEmail.test(email)){
show_validate_msg("#email_update_input", "error", "邮箱格式不正确");
return false;
}else{
show_validate_msg("#email_update_input", "success", "");
}
//2、发送ajax请求保存更新的员工数据(验证成功后)
// 第二种方法:使用拦截器FormContentFilter
$.ajax({
url:"${APP_PATH}/emp/"+$(this).attr("edit-id"),
type:"PUT",
data:$("#empUpdateModal form").serialize(),
success:function(result){
// alert(result.msg);
//1、关闭对话框
$("#empUpdateModal").modal("hide");
//2、回到本页面
to_page(currentPage);
}
});
});

6.3 效果图

7. 删除功能

/emp/{id} DELETE 删除员工

​ 实现单个及批量删除的方法

7.1 service层

public interface EmployeeService {
public List<Employee> getAll(); void addEmp(Employee employee); boolean checkUser(String empName); Employee getEmp(Integer id); void updateEmp(Employee employee); void deleteEmp(Integer empId); void deleteEmps(List<Integer> ids);
}
    /*
* 删除员工的信息
* */
@Override
public void deleteEmp(Integer empId) {
employeeMapper.deleteByPrimaryKey(empId);
} @Override
public void deleteEmps(List<Integer> ids) {
EmployeeExample example = new EmployeeExample();
EmployeeExample.Criteria criteria = example.createCriteria();
// delete from xxx where emp_id in(1,2,3,...)
criteria.andEmpIdIn(ids);
employeeMapper.deleteByExample(example);
}

7.2 controller层

    /*
* 删除员工信息(注意是empId)
* 单个批量二合一
* 批量删除:1-2-3
* 单个删除:1
* */
@RequestMapping(value = "/emp/{empIds}", method = RequestMethod.DELETE)
@ResponseBody
public Msg deleteEmp(@PathVariable("empIds")String empIds){ //PathVariable取请求路径中指定占位符的值
if(empIds.contains("-")) { // 批量删除
String[] splits = empIds.split("-");
ArrayList<Integer> del_ids = new ArrayList<>();
// 组装del_ids数组,需要批量删除员工数据的id集合
for (String split : splits) {
Integer id = Integer.parseInt(split);
del_ids.add(id);
}
employeeService.deleteEmps(del_ids);
} else { // 单个删除
Integer i = Integer.parseInt(empIds);
System.out.println("将要删除的员工数据:" + employeeService.getEmp(i));
employeeService.deleteEmp(i);
}
return Msg.success();
}

7.3 效果图

8. 总结

8.1 使用maven打包

  • 运行打包,web项目生成war包

  • 将生成的war包放到Tomcat目录下的webapp下,运行tomcat,会把项目在本地服务器上进行部署,就能进行访问了

​ 启动服务器,访问SSM_CRUD项目,完毕!

8.2 思路图

尚硅谷SSM-CRUD实战Demo的更多相关文章

  1. 2018年尚硅谷《全套Java、Android、HTML5前端视频》

    全套整合一个盘里:链接:https://pan.baidu.com/s/1nwnrWOp 密码:h4bw 如果分类里没有请下载下边那些小项教程链接 感谢尚硅谷提供的视频教程:http://www.at ...

  2. 尚硅谷《全套Java、Android、HTML5前端视频》

    尚硅谷<全套Java.Android.HTML5前端视频> (百万谷粉推荐:史上最牛.最适合自学的全套视频.资料及源码) [尚硅谷官网资料导航] 谷粒学院在线学习:http://www.g ...

  3. 尚硅谷全套课件整理:Java、前端、大数据、安卓、面试题

    目录 Java 尚硅谷 IT 精英计划 JavaSE 内部学习笔记.pdf 尚硅谷 Java 基础实战之银行项目.pdf 尚硅谷 Java 技术之 JDBC.pdf 尚硅谷 Java 技术之 Java ...

  4. 3、尚硅谷_SSM高级整合_使用ajax操作实现修改员工的功能

    当我们点击编辑案例的时候,我们要弹出一个修改联系人的模态对话框,在上面可以修改对应的联系人的信息 这里我们我们要编辑按钮添加点击事件弹出对话框 第一步:在页面中在新增一个编辑联系人的模态对话框 第二步 ...

  5. 2、尚硅谷_SSM高级整合_创建Maven项目.avi

    第一步我们新建立一个web工程 这里首先要勾选上enable的第一个复选框 这里要勾选上add maven support 我们在pom.xml中添加sevlet的依赖 创建java web项目之后, ...

  6. 3、尚硅谷_SSM高级整合_使用ajax操作实现删除的功能

    点击删除的时候,要删除联系人,这里同点击编辑按钮一样给删除按钮添加点击事件的时候不能使用 $(".delete_btn").click(function(){ }); 这种方式,因 ...

  7. 3、尚硅谷_SSM高级整合_使用ajax操作实现增加员工的功能

    20.尚硅谷_SSM高级整合_新增_创建员工新增的模态框.avi 1.接下来当我们点击增加按钮的时候会弹出一个员工信息的对话框 知识点1:当点击新增的时候会弹出一个bootstrap的一个模态对话框 ...

  8. 2、尚硅谷_SSM高级整合_使用ajax操作实现页面的查询功能

    16.尚硅谷_SSM高级整合_查询_返回分页的json数据.avi 在上一章节的操作中我们是将PageInfo对象存储在request域中,然后list页面解析request域中的对象实现信息的显示. ...

  9. Spring学习笔记 1. 尚硅谷_佟刚_Spring_HelloWorld

    1,准备工作 (1)安装spring插件 搜索https://spring.io/tools/sts/all就可以下载最新的版本 下载之后不用解压,使用Eclipse进行安装.在菜单栏最右面的Help ...

  10. 尚硅谷spring_boot课堂笔记

    尚硅谷spring_boot课堂笔记

随机推荐

  1. 【GitHub】本地代码上传

    本地代码上传GitHub 2019-11-18  20:03:45  by冲冲 1.注册GitHub https://github.com/ 2.安装Git工具 https://git-for-win ...

  2. html+css第六篇-定位

    relative相对定位/定位偏移量 position:relative; 相对定位 a.不影响元素本身的特性: b.不使元素脱离文档流: c.如果没有定位偏移量,对元素本身没有任何影响: 定位元素位 ...

  3. [NOIP2017 提高组] 逛公园

    考虑先做一个\(dp\),考虑正反建图,然后按0边拓扑,然后按1到这里的最小距离排序,然后扩展这个\(f_{i,j}\),即多了\(j\)的代价的方案数.

  4. HDU 7066 - NJU emulator(构造题)

    题面传送门 提供一种不同于官方题解.需要的操作次数比官方题解多(官方题解大概是 \(2\times 16\),我这大概是 \(3\times 16\)),但能通过此题的做法. 首先我们考虑一个暴力,我 ...

  5. PicGo + Gitee +Typora实现markdown图床

    目录 1. PicGo安装 2.Gitee配置 3.配置PicGo 3.Typora的设置 网上有一些很详细的教程,我这里只记录要点,其余部分按以下教程步骤来就行. 1. PicGo安装 国内下载可能 ...

  6. R 语言实战-Part 5-2笔记

    R 语言实战(第二版) part 5-2 技能拓展 ----------第21章创建包-------------------------- #包是一套函数.文档和数据的合集,以一种标准的格式保存 #1 ...

  7. Nginx pid文件找不到 nginx: [error] open() "/run/nginx/nginx.pid" failed (2: No such file or directory)

    Nginx pid文件找不到 nginx: [error] open() "/run/nginx/nginx.pid" failed (2: No such file or dir ...

  8. 码上来战!探索“智”感生活,HMS Core线上Codelabs挑战赛第4期开始!

    HMS Core线上Codelabs挑战赛第4期正式开始!我们向所有实践力超强.创新力满满的开发者发出邀请,用你的超级"码"力,解锁更多应用价值! 生活里,我们被手机"秒 ...

  9. day01互联网架构理论

  10. Scala【需求二:求各省市的各个指标】

    需求处理步骤 原始数据->json->过滤->列裁剪 需求二:求各省市的各个指标 原始数据 文本pmt.json,每一行都是一个json字符串.里面包含ip等信息 {"se ...