SSM整合教程

一.创建一个web项目

二.项目的各种配置

web.xml文件配置

  • 配置dispatcherServlet前端控制器servlet
  • 配置characterEncodingFilter字符集过滤器filter
  • 配置HiddenHttpMethodFilterrest风格请求过滤器,可以处理rest风格的http请求
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!--ContextLoaderListener配置监听器,用于加载配置bean的ioc容器
        在启动项目的时候主动加载配置bean的ioc容器
     -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--配置字符编码过滤器-->
  <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>
    <init-param>
      <!--指定该过滤器一定会请求的编码设置为指定的编码utf8-->
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <!--指定该过滤器一定会响应的编码设置为指定的编码utf8-->
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!--配置HiddenHttpMethodFilter,可以处理rest风格的http请求-->
  <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>
  <!--配置前端控制器-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

SpringMVC.xml文件配置

  • 配置InternalResourceViewResolver默认视图解析器
  • 配置默认请求处理配置,数据校验配置与国际化配置
  • 配置要扫描的包,只扫描Controller
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--配置要扫描的包,只扫描控制器-->
    <context:component-scan base-package="com.yangyi.furn" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--配置默认视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
        <!--配置视图的前缀和后缀-->
        <property name="prefix" value="/pages/"></property>
        <property name="suffix" value=".jsp"></property>
        <!--配置优先级-->
        <property name="order" value="10"></property>
    </bean>

    <!--加入两个springmvc的常规配置-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--将springmvc不能处理的请求交给tomcat-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
</beans>

Spring.xml文件配置

  • 配置要扫描的包,不扫描Controller
  • 配置数据源
  • 指定mybatis的核心配置文件
  • 配置扫描器,将mybatis接口的实现加入到ioc容器中
  • 配置事务管理器
  • 开启事务,注解的方式或者xml的方式
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       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">

    <!--配置要扫描的包,不能扫描Controller-->
    <context:component-scan base-package="com.yangyi.furn">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置druid数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!--配置spring与mybatis的整合,引入mybatis到spring的适配包-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--指定mybatis的核心配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--指定数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--指定mapper.xml文件位置-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--配置扫描器,将mybatis的实现接口加入ioc容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer">
        <!--指定扫描接口的包-->
        <property name="basePackage" value="com.yangyi.furn.dao"/>
    </bean>

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

    <!--配置基于注解的事务管理-->
    <!--<tx:annotation-driven transaction-manager="transactionManager"/>-->

    <!--配置基于xml的事务管理-->
    <aop:config>
        <!--配置事务管理的切入表达式-->
        <aop:pointcut id="txPointcut" expression="execution(* com.yangyi.furn.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
    <!--配置增强事务,指定事务的切入-->
    <tx:advice id="txAdvice">
       <tx:attributes>
           <!--配置所有的方法都开启事务-->
           <tx:method name="*"/>
           <!--以get开始的方法,我们认为是只读的,进行优化-->
           <tx:method name="get*" read-only="true"/>
       </tx:attributes>
    </tx:advice>

</beans>

配置jdbc数据源配置文件

Mybatis.xml文件配置

  • 配置环境
  • 配置全局设置,日志输出,缓存,懒加载
  • 配置数据源
  • 配置接口映射文件

Mybatis逆向工程

  • 逆向工程的配置文件
<?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>
    <context id="DB2Tables" targetRuntime="Mybatis3">
        <commentGenerator>
            <!--指定生产的bean没有注释-->
            <property name="suppressDate" value="true" />
            <!--<property name="suppressAllComments" value="true" />-->
        </commentGenerator>
        <!--配置数据库连接信息-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://127.0.0.1:3306/furn_ssm?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8" userId="root" password="hsp"></jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!--指定javabean生成的位置-->
        <javaModelGenerator targetPackage="com.yangyi.furn.bean" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!--指定sql映射文件生成的位置-->
        <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resource">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--指定mapper接口生成的位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.yangyi.furn.dao" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!--指定逆向生成的表和生成策略-->
        <table tableName="furniture" domainObjectName="Furniture"/>
    </context>

</generatorConfiguration>
  • 测试
package com.yangyi.furn.test;

import org.junit.Test;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @Projectname: furn-ssm
 * @Filename: MBGTest
 * @Author: 杨逸
 * @Data:2023/9/22 15:47
 * @Description: 测试mybatis逆向工程
 */
public class MBGTest {
    @Test
    public void mbgTest() throws XMLParserException, IOException, InvalidConfigurationException, SQLException, InterruptedException {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        //这里配置文件修改为自己的
        String resource = "mbg.xml";
        File configFile = new File(resource);
        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);
    }
}

使用VUE-CLI搭建前端工程,使用vue3

  • 因为是前后端分离的项目所有,单独创建前端项目

  • 安装node.js

  • 使用npm安装vue的cli脚手架npm install -g @vue/cli全局安装

  • 创建vue项目vue create ssm-vue

  • 修改vue项目的服务端端口,在vue-config.js中配置

module.exports = {
  devServer:{
    port:10000  //vue项目的端口
  }
}
  • 安装element-puls插件,npm install element-plus --save

  • 去掉默认页面和模板多余的代码,准备写自己需要的代码

  • 视图是用于展示的,组件构成视图

  • 在前端项目中引入element-plus

//引入element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
  • 引入中文,支持国际化
//国际化,显示中文
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
  • 安装axiso,用于发送ajax请求,npm i axios -S
//引入axios
import axios from 'axios'

//创建axios实例
const request = axios.create({
    timeout:5000
})

//请求拦截器
request.interceptors.request.use(config=>{
    config.headers["contentType"] = "application/json";
    return config;
},error=>{
    return Promise.reject(error);
})

//响应拦截器,对返回的数据进行统一处理

request.interceptors.response.use(response=>{
    let res = response.data;
    //如果是文件就直接返回
    if(response.config.responseType === 'blob')return res;
    //如果是字符串就转换为json对象
    if(typeof res === 'string'){
        res = res?JSON.parse(res):res;
    }
    return res;
})


//导出
export default request;
  • 设置代理,解决跨域问题
proxy:{//解决跨域问题,设置代理
      '/api':{  //代理的名称
        target:'http://127.0.0.1:8080/',  //代理的目标地址
        changeOrigin:true,  //设置是否允许跨域
        pathRewrite:{
          '/api':''  //选择忽略拦截器里的单词
        }
      }
    }

mybatis分页插件

  • 引入插件
   <!--引入mybatis pageHelper分页插件-->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.3.2</version>
    </dependency>
  • 配置分页拦截器
 <!--配置分页拦截器-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--启用合理化分页-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
  • 进行分页查询
/**
     * 分页请求接口,带条件的分页查询
     * @param pageNum 分页的号码,默认为1
     * @param pageSize 分页的大小,默认为5
     * @param search 查询的条件,默认为空
     * @return
     */
    @GetMapping(value = "/furn/listFurnitureByConditionPage")
    @ResponseBody
    //通过@RequestParam(defaultValue = "1")设置默认的分页编号
    public Msg listFurnitureByConditionPage(@RequestParam(defaultValue = "1") Integer pageNum,
                                   @RequestParam(defaultValue = "5") Integer pageSize,
                                   @RequestParam(defaultValue = "") String search){
        //设置分页参数
        PageHelper.startPage(pageNum,pageSize);
        //调用getAllFurniture(),因为引入了分页插件,底层会进行物理分页,而不是逻辑分页
        List<Furniture> furnitureList = furnitureServiceImpl.getAllFurnitureByName(search);
        //将查询到的分页信息封装到PageInfo对象中
        PageInfo<Furniture> pageInfo = new PageInfo<>(furnitureList, pageSize);
        HashMap<String, Object> map = new HashMap<>();
        map.put("pageInfo",pageInfo);
        Msg msg = Msg.success();
        msg.setData(map);
        return msg;
    }
  • 前端分页控件
  • <el-pagination/>表示分页控件
  • 通过v-model:page-size="pageSize":属性设置分页的大小
  • 通过v-model:current-page="currentPage":属性设置当前显示分页
  • 通过:total="total:属性设置总数据量
  • 通过@size-change="handleSizeChange":属性设置分页大小改变时发生的事件
  • 通过@current-change="handleCurrentChange"":属性设置当前页改变时发生的事件
<template>
  <div>
   //显示表单
    <el-table :data="tableData" stripe style="width: 90%;height: 400px" default="scope">
      <el-table-column  fixed prop="id" sortable  label="ID"  />
      <el-table-column prop="name" label="姓名"  />
      <el-table-column prop="mark" label="品牌" />
      <el-table-column prop="price" label="价格" />
      <el-table-column prop="sales" label="销量" />
      <el-table-column prop="stock" label="库存" />
      <el-table-column prop="operate" label="操作" fixed="right" >
        <template #default="scope">
          <el-popconfirm title="确定删除这个家具?" @confirm="handleDelete(scope.row.id)" @cancel="this.$message.info('已取消删除')">
            <template #reference>
              <el-button link type="danger">删除</el-button>
            </template>
          </el-popconfirm>
          <el-button link type="primary" @click="edit(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>

//分页控件
  <div style="height: 50px;width: 40%">
    <el-pagination
        v-model:page-size="pageSize" //分页的大小
        :page-sizes="[5,10]" //分页大小的选择
        v-model:current-page="currentPage" //当显示的页数
        layout="prev,sizes, pager, next, jumper, ->, total" //控件的显示风格
        :total="total" //总共数据条数
        @size-change="handleSizeChange" //分页大小改变触发的事件
        @current-change="handleCurrentChange"/> //当前显示页改变发生的事件
  </div>

  </div>
</template>

<script>

import axios from "@/utils/request";

export default {
  name: 'HomeView',
  components: {
  },
  data(){
    return{
      tableData:[],
      total:100, // 默认数据量
      pageSize:5, //默认页面显示数据大小
      currentPage:1, //默认显示的页面
    }
  },
  methods:{
    handleSizeChange(){
        //发生改变重新请求数据
      this.list();
    },
    handleCurrentChange(){
         //发生改变重新请求数据
      this.list();
    },
    list(){
      axios.get("/api/furniture/list/page",{
        params:{
          pageSize:this.pageSize,
          pageNum:this.currentPage
        }
      }).then(res => {
        this.tableData = res.data.records;
        this.total = res.data.total;
      }).catch(error=>{
        console.log(error);
        console.log('查询家具列表失败')
      });
    }
  },
  created() {
    this.list();
  }
}
</script>


后端数据校验

  • 使用hibernate-validator
  • 在属性上使用校验注解标注需要校验的属性,以及校验的类型
package com.yangyi.springboot.bean;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;

/**
 * @Projectname: springboot_furn
 * @Filename: Furnitrue
 * @Author: 杨逸
 * @Data:2023/10/3 10:20
 * @Description: 实体类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Furniture {

    /**
     * 使用@TableId(type = IdType.AUTO)注解标识一个属性为表的主键,
     * 通过type属性设置主键的类型为自动增长类型
     *
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    @NotEmpty(message = "商品名称不能为空")
    private String name;

    @NotEmpty(message = "商品品牌不能为空")
    private String mark;

    @NotNull(message = "商品价格不能为空")
    @Range(min = 0, message = "商品价格不能小于0")
    private BigDecimal price;

    @NotNull(message = "商品销量不能为空")
    @Range(min = 0, message = "商品销量不能小于0")
    private Integer sales;

    @NotNull(message = "商品库存不能为空")
    @Range(min = 0, message = "商品库存不能小于0")
    private Integer stock;

    private String imgPath = "./assert/deafult.jpg";
}

  • 在需要校验的参数前加上@Validated注解进行校验
  • 在方法形参上,加上一个Errors类型的参数,接受校验失败的信息
  /**
     *添加家具
     * 因为前端是发送Json格式的数据,所有需要使用@RequestBody注解才能将json格式的数据封装为javabean
     * 如果是使用表单提交的数据,则不需要使用@RequestBody注解
     * @param furniture
     * @return
     */
    @PostMapping("/furniture/save")
    public Result save(@Validated @RequestBody Furniture furniture, Errors errors){
        HashMap<String, Object> map = new HashMap<>();
        for (FieldError fieldError : errors.getFieldErrors()) {
            log.error("字段错误,字段={},错误信息={}",fieldError.getField(),fieldError.getDefaultMessage());
            map.put(fieldError.getField(),fieldError.getDefaultMessage());
        }
        if(map.isEmpty()){
            log.info("添加家具={}",furniture);
            furnitureService.save(furniture);
            log.info("添加家具成功={}",furniture);
            return Result.success();
        }else{
            Result result = Result.error(450, "添加家具失败,数据格式不对");
            result.setData(map);
            return result;
        }