SSM框架基础

Spring

Spring入门

Spring简介

  • Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IoC(Inverse Of Control:控制反转)和**AOP**(Aspect Oriented Programming)为内核
  • Spring提供了展现层SpringMVC持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架

Spring发展历程

  • 1997年,IBM提出EJB思想
  • 1998年,SUN制定开发标准规范EJB1.0
  • 1999年,EJB1.1发布
  • 2001年,EJB2.0发布
  • 2003年,EJB2.1发布
  • 2006年,EJB3.0发布
  • 2017年9月发布了Spring的最新版本Spring5.0通用版

Spring的优势

  • 方便解耦,简化开发
    • 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合,用户也不必再为单例模式类、属性文件解析等这些底层需求编写代码,而更专注于上层的应用
  • AOP编程的支持
    • 通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP实现
  • 声明式事务的支持
    • 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率
  • 方便程序的测试
    • 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
  • 方便集成各种优秀框架
    • Spring对各种优秀的框架(Structs、Hibernate、HEssian、Quartz等)的支持
  • 降低Java EE API的使用难度
    • Spring对Java EE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,降低了API的使用难度
  • Java源码的经典学习典范
    • Spring的源代码设计精妙、结构清晰、匠心独到,处处体现着大师对Java设计模式的灵活运用以及对Java技术的高深造诣。

Spring的体系结构

image-20210817142426719

Spring快速入门

Spring程序开发步骤

image-20210817143126587
  1. 导入Spring开发的基本包坐标:spring-context

    image-20210817145031674
  2. 编写dao层接口(UserDao)和实现类(UserDaoImpl)

    UserDaoimage-20210817145149356 UserDaoImplimage-20210817145209302

  3. 创建Spring核心配置文件

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDao" class="com.taoyyz.dao.impl.UserDaoImpl"/>
    </beans>
  4. 在Spring配置文件中配置UserDaoImpl

    1
    <bean id="userDao" class="com.taoyyz.dao.impl.UserDaoImpl"></bean>
  5. 使用Spring的API获得Bean的实例(而非new UserDaoImpl()

    image-20210817145136182

Spring配置文件

Bean标签基本配置

  • 概述:
    • 用于配置对象交由Spring来创建
    • 默认情况下它调用的是类中的无参构造方法,如果没有无参构造方法则不能创建成功
  • 基本属性:
    • id:Bean实例在Spring容器内的唯一标识
    • class:Bean的全限定名称

Bean标签范围配置

  • scope属性:对象的作用范围,取值如下:
image-20210817145915770
  • scope属性控制下的Bean的创建时机:

    image-20210817151121696

Bean的生命周期配置

  • init-method:为此bean指定一个初始化时的方法
  • destroy-method:为此bean指定一个销毁时的方法
    • 值得注意的是,在单元测试时的Spring要正常关闭,需要调用ClassPathXmlApplicationContext对象的close()方法才能正常关闭Spring,以及调用bean的销毁方法

Bean实例化的三种方式

  • 无参构造方法实例化

    • 定义一个Bean(默认有无参构造)

    • 在配置文件中配置bean的属性:

      1
      <bean id="userDao" class="com.taoyyz.dao.impl.UserDaoImpl"></bean>
  • 工厂静态方法实例化

    • 定义一个工厂类,在静态方法里生产Bean: image-20210817152736007

    • 在配置文件中配置bean的属性:

      1
      2
      <bean id="userDao" class="com.taoyyz.factory.StaticFactory" 
      factory-method="getUserDao"></bean>
  • 工厂实例方法实例化

    • 定义一个工厂类,在实例方法里生产Bean: image-20210817153348508

    • 在配置文件中配置bean的属性:

      1
      2
      3
      4
      <!--先加载工厂的实例Bean-->
      <bean id="factory" class="com.taoyyz.factory.DynamicFactory"></bean>
      <!--再从工厂的实例里调方法-->
      <bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>

Bean的依赖注入DI

概念

  • 依赖注入(Dependency Injection):它是Spring框架核心IoC的具体实现

注入的方式:

  • 构造方法注入

    1
    2
    3
    4
    <bean id="userService" class="com.taoyyz.service.impl.UserServiceImpl">
    <!--name是注入到哪个成员属性,ref是引用哪个bean-->
    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
    </bean>
  • set方法注入:切割setXxx()方法,取Xxx部分首字母变小写作为set到的成员属性名

    1
    2
    3
    4
    5
    6
    7
    <!--配置被依赖的bean-->
    <bean id="userDao" class="com.taoyyz.dao.impl.UserDaoImpl"></bean>
    <!--下面的UserService的bean依赖于上述userDao的bean-->
    <bean id="userService" class="com.taoyyz.service.impl.UserServiceImpl">
    <!--ref引用userDao这个bean,注入到userServiceImpl的name指定的成员属性里-->
    <property name="userDao" ref="userDao"></property>
    </bean>
  • 简化set方法的p命名空间注入:

    • 引入p命名空间的xmlns约束:

      1
      xmlns:p="http://www.springframework.org/schema/p"
    • 在需要注入的bean上利用p标签注入引用:

      1
      2
      3
      4
      <!--配置被依赖的bean-->
      <bean id="userDao" class="com.taoyyz.dao.impl.UserDaoImpl"></bean>
      <!--下面的UserService的bean依赖于上述userDao的bean-->
      <bean id="userService" class="com.taoyyz.service.impl.UserServiceImpl" p:userDao-ref="userDao"></bean>

注入的数据类型

  • 上述注入方式都是注入的Bean。除了对象的引用可以注入外,普通数据类型、集合等都可以在容器中注入

  • 注入数据的三种数据类型

    • 普通数据类型

      1
      2
      3
      4
      5
      <bean id="userDao" class="com.taoyyz.dao.impl.UserDaoImpl">
      <!--name为要注入的成员属性名,value为此成员属性的字面值-->
      <property name="username" value="taoyyz"></property>
      <property name="age" value="18"></property>
      </bean>
    • 引用数据类型

    • 集合数据类型

      • List集合
      1
      2
      3
      4
      5
      6
      7
      <property name="strList">
      <list>
      <value>aaa</value>
      <value>bbb</value>
      <value>ccc</value>
      </list>
      </property>
      • Map集合
      1
      2
      3
      4
      5
      6
      7
      8
      <property name="userMap">
      <map>
      <!--由于map是由一个一个entry组成,每个entry包含key和value-->
      <!--这里key是字符串String直接给字面值,但是value是User对象需要给引用-->
      <entry key="tao" value-ref="user1"></entry>
      <entry key="胖" value-ref="user2"></entry>
      </map>
      </property>
      • Properties集合
      1
      2
      3
      4
      5
      6
      <property name="properties">
      <props>
      <prop key="prop1">第一个prop</prop>
      <prop key="prop2">第二个prop</prop>
      </props>
      </property>

配置文件的引入

  • 将部分配置拆解到其他配置文件中,在Spring主配置文件通过import标签引入即可

    1
    <import resource="要引入的其他Spring配置文件名"></import>

Spring配置的重点回顾

image-20210817170600266

Spring相关API

image-20210817174502199

ApplicationContext的实现类:

  • ClassPathXmlApplicationContext

    • 从类的根路径下加载配置文件推荐使用这种
  • FileSystemXmlApplicationContext

    • 从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
  • AnnotationConfigApplicationContext

    • 使用注解配置容器对象时,使用此类来创建Spring容器,用来读取注解

      image-20210817175414157

getBean()方法

  • 参数为bean的id的字符串,此时总能获取此id对应的Object类型的bean实例

    1
    2
    3
    4
    public Object getBean(String name) throws BeansException {
    this.assertBeanFactoryActive();
    return this.getBeanFactory().getBean(name);
    }
  • 参数为T类型的Class对象,从容器中匹配T类型的bean实例并返回,如果有多个此类型的bean时会报错

    1
    2
    3
    4
    public <T> T getBean(Class<T> requiredType) throws BeansException {
    this.assertBeanFactoryActive();
    return this.getBeanFactory().getBean(requiredType);
    }

Spring配置数据源

数据源(连接池)的作用

  • 数据源(连接池)是提高程序性能出现的
  • 实例化数据源,初始化部分连接资源
  • 使用连接资源时从数据源中获取
  • 使用完毕后归还连接资源给数据源

常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等

数据源的使用步骤

  • 导入数据源的坐标和数据库驱动坐标
  • 创建数据源对象
  • 设置连接数据源的基本连接信息(驱动、数据库URL、用户名、密码)
  • 使用数据源获取连接资源和归还连接资源

数据源的手动创建

  • 创建数据源对象
  • 设置数据源参数
  • 获取连接
  • 归还连接

Spring配置数据源的Bean

  • 在Spring配置文件中装载数据源为bean,class为此数据源的全类名

  • 配置此bean的参数,也就是数据源需要的一些参数(驱动、数据库URL、用户名、密码)

    1
    2
    3
    4
    5
    6
    7
    <!--装载ComboPooledDataSource对象为bean-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql:///db1"/>
    <property name="user" value="root"/>
    <property name="password" value="199988"/>
    </bean>
  • 即可通过Spring的Context上下文去getBean()得到此bean(数据源对象),执行后续操作

Spring加载properties文件

  • 在applicationContext.xml配置文件中加载jdbc.properties文件:

    1. 引入context命名空间和约束路径

      • 命名空间:xmlns:context="http://www.springframework.org/schema/context"

      • 约束路径:为xsi:schemaLocation添加以下两行约束(注意要保证两个同类型约束挨着):

        1
        2
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
    2. 利用context命名空间下的property-placeholder标签加载properties配置文件

    3. 使用SpEL表达式取值

      1
      2
      3
      4
      5
      6
      7
      8
      <!--加载外部properties文件,在location中指定位置-->
      <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="driverClass" value="${jdbc.driver}"/>
      <property name="jdbcUrl" value="${jdbc.url}"/>
      <property name="user" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
      </bean>

Spring注解开发

  • Spring是一种轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替XML配置文件可以简化配置,提高开发效率

Spring原始注解

  • 原始注解主要是替代<bean>标签的配置

    原始注解

    image-20210817200449058
  • 使用方式:

    • 用@Component代替所有的bean标签的class属性,此注解打在对应的类名上

      UserDao(实现类)的bean: image-20210817202516725

      UserService(实现类)的bean: image-20210817202617838

    • 用@Autowired代替所有的property标签内的ref属性,此注解打在对应的成员属性上

      image-20210817202904688
    • 在Spring的核心配置文件中指定context命名空间的component-scan标签的base-package属性:

      1
      2
      3
      4
      <!--用注解方式进行装配Bean需要配置组件扫描-->
      <context:component-scan base-package="com.taoyyz">
      <!--base-package属性是指定从哪个包(及其子包)扫描带注解的组件。-->
      </context:component-scan>
    • 此时调用getBean(String name)只需要传入需要获取哪个Bean即可(也就是被打上了@Component注解的类),如果@Component没有传入参数,则默认Bean名称是类名首字母转小写

说明

  • 注解方式不同于XML,可以不写set方法,会自动给打上注解的成员属性赋值
  • @Controller、@Service、@Repository只是语义化的@Component注解
    • @Repository可以捕获特定于平台的异常,并将它们作为Spring统一未检查异常的一部分重新抛出
  • @Autowired可以不搭配@Qualifier,此时会按照数据类型从Spring容器进行匹配(例如匹配UserDao类型),如果匹配不到一个bean会报错,此时可以指定@Autowired的required=false
  • @Qualifier要结合@Autowired使用,是按照名称Beanid)从Spring容器中进行匹配
  • @Resource相当于@Autowired + @Qualifier,此时按照名称匹配要指定@Resource注解的name属性值
    • @Resource在Spring4.2之前不支持@Primary:但是在4.2之后,@Resource已经全面支持了@Primary以及提供了对@Lazy的支持

Autowired补充,解决byType不唯一的NoUniqueBeanDefinitionException异常

  • 向容器注入Bean的时候加上@Primary注解
  • 使用@Qualifier指定beanName
  • 使得需要注入的对象名,和容器里的beanName一致也可以确定唯一的bean

Spring新注解

  • 使用原始注解不能全部替代XML配置文件,例如

    • 非自定义的Bean的配置:<bean>
    • 加载properties文件的配置:<context:property-placeholder>
    • 组建扫描的配置:<context:component-scan>
    • 引入其他文件:<import>

    新注解

image-20210817212227550
  • 使用方式:
    • 用一个类替代XML配置文件,为这个类打上@Configuration注解,表示这是一个Spring核心配置类
    • 在打上@Configuration的核心配置类上再打上@ComponentScan(“包路径”),指定组建扫描的包
    • 如果想要引入其他希望产生Bean的配置类,需要在打上@Configuration的核心配置类上再打上@Import(其他配置类的Class<?>[])即可使引入的类的Bean被Spring管理
    • 用@PropertySource(“文件路径”)代替context命名空间的location属性,表示读取properties配置文件
      • 在@PropertySource读取到properties配置文件后,可以利用@Value(“${SpEL表达式}“)取出properties配置文件中的key赋值给被打上此@Value注解的成员属性
    • 用@Bean代替<bean>标签,此注解打在方法上。此方法的方法名会作为bean的id属性,返回值就是bean的class属性,也可以指定生成的bean的id,即@Bean(“生成的bean的名称”)

Spring集成Junit

  • 原始的Junit测试Spring存在的问题:

    • 每个测试类都需要先获取上下文,然后调用getBean()

    image-20210817220929954

  • 解决思路:

    • 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
    • 将需要进行测试的Bean直接在测试类中进行注入
  • 集成步骤:

    1. 导入Spring集成Junit的坐标

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.0.5.RELEASE</version>
      </dependency>
      • 踩坑:Maven中spring-test依赖包版本要和spring-core版本一致,否则会导致以下报错:

        java.lang.IllegalStateException: Could not load TestContextBootstrapper

    2. 使用@Runwith注解替换原来的运行期

      • @Runwith(Class<? extends Runner>)打在测试类上,参数为一个Runner及子类的Class对象

        例如:给测试类打上@RunWith(SpringJUnit4ClassRunner.class)的注解

    3. 使用@ContextConfiguration指定配置文件或配置类

      • 指定XML配置文件:@ContextConfiguration("classpath:applicationContext.xml")
      • 指定配置类:@ContextConfiguration(classes = SpringConfiguration.class)
    4. 使用@Autowired注入需要测试的对象

      • 相当于从Spring容器中取出值给成员属性赋值
    5. 创建测试方法进行测试

Spring集成WEB环境

ApplicationContext应用上下文获取方式

  • 如果在doGet()doPost()方法中每次都通过new ClassPathXmlApplicationContext()去加载应用上下文很麻烦
  • 所以在WEB项目中,可以通过ServletContextListener监听WEB应用的启动,在启动时就加载Spring的配置文件,创建应用上下文ApplicationContext对象,并将其存储到最大的ServletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了
  • 也可以通过在web.xml中设置一个<context-param>标签,存入param-name与对应的param-value获取xml文件名

Spring提供获取应用上下文的工具

  • Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具类WebApplicationContextUtils供使用者获取应用上下文对象

  • 使用方式:

    • 在web.xml中配置ContextLoaderListener监听器(需要导入spring-web坐标)

      1
      2
      3
      4
      5
      6
        <!--配置监听器-->
      <listener>
      <listener-class>
      org.springframework.web.context.ContextLoaderListener
      </listener-class>
      </listener>
    • 使用WebApplicationContextUtilsgetWebApplicationContext()方法即可获得应用上下文对象

      1
      2
      ApplicationContext app = WebApplicationContextUtils
      .getWebApplicationContext(request.getServletContext());

    踩坑:出现以下错误:

    1
    java.lang.ClassNotFoundException:org.springframework.web.context.ContextLoaderListener
    1. 考虑web.xml的版本过高,Tomcat8.5环境下修改web.xml的4.0版本为低版本,或换用Tomcat9
    2. 考虑修改IDEA的项目结构:设置工件 image-20210818010245791

SpringMVC

SpringMVC概述

  • SpringMVC是一种基于Java的实现MVC设计模式的请求驱动类型的轻量级WEB框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow中
  • SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring3.0发布,全面超越Struts2,成为最优秀的MVC框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无需实现任何接口,同时还支持RESTful编程风格的请求

SpringMVC快速入门

  • 客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转

image-20210818132536592

  • 开发步骤:

    1. 导入SpringMVC相关坐标

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.0.5.RELEASE</version>
      </dependency>
    2. 配置SpringMVC核心控制器DispatcherServlet

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <!--配置一个SpringMVC的前端控制器-->
      <servlet>
      <servlet-name>DispatcherServlet</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.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>
    3. 创建Controller类和视图页面

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Controller
      public class UserController {
      @RequestMapping("taoyyz.do")
      public String method1() {
      System.out.println("Controller method1执行啦");
      return "success.jsp"; //返回一个JSP视图给浏览器
      }

      @RequestMapping("/hhh") //返回值为void会默认返回名为hhh.jsp的视图
      public void t() {
      System.out.println("hhh来了");
      }
      }
    4. 使用注解配置Controller类中业务方法的映射地址:即@RequestMapping

    5. 配置SpringMVC核心配置文件:spring-mvc.xml

      1
      2
      3
      4
      <!--启动组件扫描-->
      <context:component-scan base-package="com.taoyyz.controller"/>
      <!--启动允许注解驱动-->
      <mvc:annotation-driven/>
    6. 客户端发请求测试

SpringMVC组件解析

SpringMVC的执行流程

image-20210818135620250

  1. 用户发送请求到前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器找到具体的处理器(可以根据XML配置或注解进行查找),生成处理器对象及处理器拦截器(如果有)一并返回给DispathcerServlet
  4. DispatcherServlet调用HandlerAdapter处理器适配器
  5. HadlerAdapter适配器经过适配调用具体的处理器(也就是后端控制器Controller)
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器
  9. ViewResolver解析后返回具体View
  10. DispatcherServlet根据View进行渲染视图(将模型数据填充至视图中),然后响应给用户

SpringMVC注解配置解析

  • @RequestMapping

    • 作用:用于建立请求的URL和处理请求的方法之间的对应关系

    • 位置:

      • 类上:请求URL的第一级访问目录,此处不写的话默认为应用上下文根目录
      • 方法上:请求URL的第二级访问目录,与类上的第一级目录一起组成虚拟访问路径
    • 属性:

      • value:用于指定请求的URL,它和path属性的作用是一样的

      • method:用于指定请求的方式,取值为RequestMethod的枚举

      • params:用于指定限制请求参数的条件,支持简单的表达式,要求请求参数的key和value必须和配置的一模一样

        例如:params = {"accountName"} 表示请求参数必须含有accountName

        params = {"money!=100"} 表示请求参数中的money不能为100

SpringMVC的XML配置解析

  • 视图解析器:

    • SpringMVC组件的默认配置:DispatcherServlet.properties位于org.springframework.web.servlet
    • 此配置指定了诸如HandlerMappingHandlerAdapterViewResolver视图解析器等组件
  • 在SpringMVC的XML中配置自定义ViewResolver的Bean可以为此Bean的成员属性赋值,例如设置前/后缀:

    1
    2
    3
    4
    5
    <!--配置解析器的前缀和后缀-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/jsp/"/>
    <property name="suffix" value=".jsp"/>
    </bean>

    前/后缀属性位于InternalResourceViewResolver的父类中: image-20210818151741663

    UrlBasedViewResolver还包含了重定向和转发前缀: image-20210818151952895

知识要点:

image-20210818152241456 image-20210818152254065

SpringMVC数据响应的方式

页面跳转

  • 直接返回字符串

    image-20210818154946053

    • 如果@RequestMapping映射的方法返回void,那么会跳转到此方法的映射名作为视图的页面上,如果不希望跳转,可以使返回值类型为ModelAndView对象,并且return null就不会产生跳转
  • 通过ModelAndView对象返回

    1
    2
    3
    4
    5
    6
    @RequestMapping("/method3")
    public ModelAndView method3(ModelAndView modelAndView) { //SpringMVC会分配此对象
    modelAndView.setViewName("success"); //设置视图名称,也就是要跳转到的页面
    modelAndView.addObject("username", "method3的"); //设置模型的内容类似Attribute
    return modelAndView; //返回此ModelAndView
    }
    • 也可以在映射的方法内部手动new一个ModelAndView对象,设置内容

      1
      2
      3
      4
      5
      6
      7
      @RequestMapping("/method2")
      public ModelAndView method2() {
      ModelAndView modelAndView = new ModelAndView(); //手动new一个模型视图对象
      modelAndView.setViewName("success"); //设置视图名称,也就是要跳转到的页面
      modelAndView.addObject("username", "tao"); //设置模型的内容类似于Attribute
      return modelAndView; //返回此ModelAndView
      }
    • 还可以只利用Model对象传递参数,方法返回值的字符串作为View

      1
      2
      3
      4
      @RequestMapping("/method4")
      public String method4(Model model) { //参数只需要Model
      model.addAttribute("username", "method4的"); //设置模型的内容
      return "success"; //这里返回的字符串作为View

回写数据

  • 直接返回字符串

    • 在Servlet中:Response对象可以使用getWriter()getOutputStream()回写数据

    • 在SpringMVC中,可以注入response对象回写数据,此时不需要进行视图跳转,所以返回值为void

      1
      2
      3
      4
      5
      @RequestMapping("/method6")
      public void method6(HttpServletResponse response) throws IOException {
      response.setContentType("text/html;charset=utf-8"); //记得设置响应头防乱码
      response.getWriter().write("来自response的你好");
      }
    • 建议使用@ResponseBody告诉SpringMVC框架不要进行视图跳转,而是当成响应体返回

      1
      2
      3
      4
      5
      @RequestMapping(value = "/method7",produces = "text/html;charset=utf-8")
      @ResponseBody
      public String method7() {
      return "来自SpringMVC的回写的数据";
      }

      @ResponseBody的响应编码默认ISO-8859-1会导致乱码,在请求映射中使用produces属性指定

超级踩坑:IDEA对Maven项目依赖jar包的打包问题:引入了jackson依赖后,启动项目总是报错启动失败

原因:IDEA在对工件进行打包时可能会漏掉一些jar包,在项目结构的工件设置里手动添加到WEB-INF/lib

image-20210818172612181

  • 返回对象或集合

    1. 可以通过jackson的ObjectMapper对象的writeValueAsString()方法转换为JSON字符串

    2. 可以通过配置SpringMVC的处理器适配器RequestMappingHandlerAdapter来利用jackson的转换器转换:配置一个适配器RequestMappingHandlerAdapter的Bean,然后指定messageConverters

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!--配置请求映射处理器适配器的消息转换器为Jackson-->
      <bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation
      .RequestMappingHandlerAdapter">
      <property name="messageConverters">
      <list>
      <bean class="org.springframework.http.converter.json
      .MappingJackson2HttpMessageConverter"/>
      </list>
      </property>
      </bean>

    究极方法:使用<mvc:annotation-driven/>注解驱动,搭配@ResponseBody返回的类型就为所欲为

    image-20210818181133229

SpringMVC获得请求数据

获得请求的参数

1.基本类型参数

  • Controller中业务方法的参数要与请求参数中的参数名一致,参数值会自动映射匹配

    例如:http://localhost/req?username=tom&age=20请求,可以被Controller中的此方法匹配:

    1
    2
    3
    4
    5
    6
    7
    8
    @RequestMapping("/req")
    @ResponseBody
    public void method12(String username,int age,Float f) {
    System.out.println("method12执行了");
    System.out.println("username = " + username); //tom
    System.out.println("age = " + age); //20
    System.out.println("f = " + f); //null
    }

    注意:

    • 如果请求的参数没有匹配映射方法的参数,Java中的引用数据类型会被赋值为null,基本数据类型会报错
    • 如果对String类型传递了多个参数,它们赋值给String时,会以逗号隔开,而其他类型会以初次赋值为准

2.POJO类型参数

  • Controller中的业务方法的POJO参数的属性名要与请求参数中的参数名一致,参数值会自动映射匹配

    例如:http://localhost/req?username=tom&age=20请求,可以被Controller中的此方法匹配:

    1
    2
    3
    4
    5
    6
    @RequestMapping(value = "/method13")
    @ResponseBody
    public void method13(User user) { //User对象含有username和age两个属性
    System.out.println("method13执行了");
    System.out.println("user = " + user); //User{username='tom', age=20}
    }

3.数组类型参数

  • Controller中的业务方法的数组名称与请求参数的参数名一致,参数值会自动映射匹配

    例如:http://localhost/req?hobby=唱歌&hobby=跳舞请求,可以被Controller中的此方法匹配:

    1
    2
    3
    4
    5
    6
    @RequestMapping(value = "/method14")
    @ResponseBody
    public void method14(String[] hobby) {
    System.out.println("method14执行了");
    System.out.println("hobbies = " + Arrays.toString(hobby)); //[唱歌, 跳舞]
    }

4.集合类型参数

方式1:使用VO对象包装集合

将集合参数包装到一个POJO中,这个POJO含有一个集合对象的成员属性,此时的POJO被称为VO对象

例如:定义一个UserVO对象,含有一个成员属性:List<User>,用Controller以下方法匹配映射:

1
2
3
4
5
6
@RequestMapping(value = "/method15")
@ResponseBody
public void method15(UserVO userVO) { //对象集合需要包装到VO(Value/View Object)对象中
System.out.println("method15执行了");
System.out.println("userVO = " + userVO);
}

通过POST请求提交方便编写集合的名称及属性:

1
2
3
4
5
6
7
<form action="${pageContext.request.contextPath}/user/method15" method="post">
<input type="text" name="userList[0].username"> <%--第1个User对象的username--%>
<input type="text" name="userList[0].age"> <%--第1个User对象的age--%>
<input type="text" name="userList[1].username"> <%--第2个User对象的username--%>
<input type="text" name="userList[1].age"> <%--第2个User对象的age--%>
<input type="submit" value="提交">
</form>
方式2:使用JSON格式利用@RequestBody注解直接用集合接收

当使用AJAX提交时,可以指定contentTypeJSON形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用VO对象进行包装

例如:利用AJAX提交一段JSON数据:

1
2
3
4
5
6
7
8
9
var userList = [];
userList.push({username: '陶俊杰', age: 22})
userList.push({username: '胖', age: 23})
$.ajax({
type: 'POST',
url: '${pageContext.servletContext.contextPath}/user/method16',
data: JSON.stringify(userList),
contentType: 'application/json;charset=utf-8'
})

提交的userList集合可以被打上@RequestBody注解的集合对象直接接收:

1
2
3
4
5
6
7
@RequestMapping(value = "/method16")
@ResponseBody
public void method16(@RequestBody List<User> userList) {
System.out.println("method15执行了");
System.out.println("userList = " + userList);
//[User{name='陶俊杰', age=22}, User{name='胖', age=23}]
}

注意:引用JQuery的JS文件可能导致404 not found,因为SpringMVC配置文件中默认用/匹配所有的请求映射,而此时没有配置其他的Servlet,导致此请求交给了DispatcherServlet处理,在DispatcherServlet中JS文件这种静态资源没有找到对应映射路径,所以就匹配不到结果,导致资源找不到

解决方法

  • 利用SpringMVC配置文件指定允许的静态资源:

    1
    <mvc:resources mapping="/js/**" location="classpath:/js/"/>

    表示请求映射为/js/下的任意文件都可以从location属性中指定的/js/中找到

  • 利用SpringMVC配置文件指定默认处理器:

    1
    <mvc:default-servlet-handler/>

    此时如果没有任何Servlet映射此资源,那么就会交给默认的(Tomcat服务器)找资源

请求数据乱码问题

  • 问题:当使用POST请求时,提交到SpringMVC的数据可能会乱码

  • 解决方法:需要在web.xml配置一个CharacterEncodingFilter过滤器来进行编码的过滤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--配置编码过滤器-->
    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name> <!--设置此过滤器的encoding编码成员属性-->
    <param-value>UTF-8</param-value> <!--设置编码为UTF-8-->
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> <!--直接过滤所有请求-->
    </filter-mapping>

    类似于Servlet中request对象的setChracterEncoding()

注解进行参数绑定

  • 当请求的参数名称与映射方法中的参数名不一致时,利用@RequestParam注解来绑定到方法中的参数

    例如:http://localhost/req?name=tom,需要把参数name绑定到映射方法的username上:

    1
    2
    3
    4
    5
    6
    @RequestMapping(value = "/method17")
    @ResponseBody
    public void method17(@RequestParam("name") String username) {
    System.out.println("method17执行了");
    System.out.println("username = " + username);
    }

    此@RequestParam注解有以下参数可以使用:

    • value:需要用于匹配的可能的参数名
    • required:在指定的请求参数是否是必须的,默认为true,即请求如果没有传递此参数会报错
    • defaultValue:当没有指定请求参数时,使用的默认值

Restful风格的参数

  • Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁、更有层次、更易于实现缓存机制等等

  • Restful风格的请求是使用”url+请求方式“表示一次请求目的,HTTP协议里面四个表示操作方式的动词如下:

    • GET:用于获取资源
    • POST:用于新建资源
    • PUT:用于更新资源
    • DELETE:用于删除资源
  • 举个例子:

    • /user/1 GET :得到id为1的user
    • /user/1 DELETE :删除id为1的user
    • /user/1 PUT :更新id为1的user
    • /user POST :新增user
  • 用法:

    • 修改方法的注解指定映射为:@RequestMapping("/映射路径/{占位符}")

    • 访问映射路径时直接带上参数即可自动匹配给占位符

    • 在方法参数里使用@PathVariable("占位符")取出请求的参数,被此注解修饰的映射方法参数会获得占位符的值

      1
      2
      3
      4
      5
      6
      @RequestMapping(value = "/method18/{name}")
      @ResponseBody
      public void method18(@PathVariable("name") String username) {
      System.out.println("method18执行了");
      System.out.println("username = " + username);
      }

获得Servlet相关API

  • SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象有:

    • HttpServletRequest
    • HttpServletResponse
    • HttpSession
    1
    2
    3
    4
    5
    6
    7
    8
    @RequestMapping(value = "/method20")
    @ResponseBody
    public void method20(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
    System.out.println("request = " + request);
    System.out.println("response = " + response);
    System.out.println("session = " + session); //这俩都一样
    System.out.println("request.getSession() = " + request.getSession());//就是上面
    }

获取请求头

  • 使用@RequestHeader可以获得请求头信息,相当于Servlet中request.getHeader(name),含有以下属性

    • value:请求头的名称
    • required:是否必须携带此请求头
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RequestMapping(value = "/method21")
    @ResponseBody
    public void method21(@RequestHeader(value = "User-Agent") String user_agent,
    @RequestHeader("Accept") String accept,
    @RequestHeader("host") String host) {
    System.out.println("user_agent = " + user_agent);
    System.out.println("accept = " + accept);
    System.out.println("host = " + host);
    }
  • 使用@CookieValue获取指定Cookie的值,含有以下属性:

    • value:指定cookie的名称
    • required:是否必须携带此cookie
    1
    2
    3
    4
    5
    6
    7
    @RequestMapping(value = "/method22")
    @ResponseBody
    public void method22(@CookieValue("JSESSIONID") String JSessionID,
    @CookieValue(value = "IDEA",required = false) String idea) {
    System.out.println("JSessionID = " + JSessionID); //JSESSIONID的值
    System.out.println("idea = " + idea); //没有此cookie,为null
    }

自定义类型转换器

  • SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串可以自动转为int类型等
  • 但不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如日期类型就需要自定义转换器

自定义类型转换器的步骤

  1. 自定义类型转换器,实现Converter接口并重写convert(String date)方法,解析String为Date并返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class DateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String date) {
    //在这里把String类型的字符串转换成Date对象然后返回
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    try {
    return sdf.parse(date);
    } catch (ParseException e) {
    e.printStackTrace();
    }
    return null;
    }
    }
  2. 在SpringMVC配置文件中声明转换器

    1
    2
    3
    4
    5
    6
    7
    8
    <!--配置类型转换器-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters"> <!--给这个converters成员属性赋值注入-->
    <set>
    <bean class="com.taoyyz.converter.DateConverter"/> <!--自定义的转换器-->
    </set>
    </property>
    </bean>
  3. <annotation-driven>中引用转换器

    1
    <mvc:annotation-driven conversion-service="conversionService"/>

案例:文件上传

  • 前端:

    • 在form表单内以POST方式提交数据
    • 提交的数据类型是:<input type="file" name="filename">时,需要把form表单的enctype属性设置为"multipart/form-data"
  • 后端:

    • 当form表单的enctype为多部分表单时,request.getParameter()将失效

      • enctype"application/x-www-form-urlencodedmultipart"时,form表单提交的正文内容格式为:key=value&key=value&key=value

      • enctype"multipart/form-data"时,form表单提交的正文内容就会变成多部分形式,例如:

        image-20210819162522038

    • 步骤:

      1. 导入fileupload和io坐标

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
        </dependency>
        <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
        </dependency>
      2. 配置文件上传解析器

        1
        2
        3
        4
        5
        6
        7
        8
        9
        <!--配置文件上传解析器-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--上传文件的总大小-->
        <property name="maxUploadSize" value="5242800"/>
        <!--上传的单个文件的大小-->
        <property name="maxUploadSizePerFile" value="5242800"/>
        <!--上传文件的编码类型-->
        <property name="defaultEncoding" value="UTF-8"/>
        </bean>
      3. 编写文件上传代码

        1
        2
        3
        4
        5
        6
        7
        8
        @RequestMapping(value = "/upload")
        @ResponseBody
        public void upload(String name,MultipartFile filename) throws IOException {
        System.out.println("name = " + name);
        String originalFilename = filename.getOriginalFilename(); //获得文件名
        System.out.println("文件名 = " + originalFilename);
        filename.transferTo(new File("E:"+originalFilename)); //保存到磁盘
        }
      4. 前端通过form表单提交数据

        1
        2
        3
        4
        5
        <form action="${pageContext.servletContext.contextPath}/user/upload" method="post" enctype="multipart/form-data">
        名称: <input type="text" name="name">
        文件: <input type="file" name="filename">
        <input type="submit" value="提交">
        </form>
    • 多文件上传

      • 方式1:后端定义多个不同名的MultipartFile对象接收前端发送对应name的文件,然后分别处理

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        @RequestMapping(value = "/uploads")
        @ResponseBody
        public void uploads(String name,
        MultipartFile filename1,
        MultipartFile filename2) throws IOException {
        System.out.println("name = " + name);
        String originalFilename1 = filename1.getOriginalFilename(); //文件名
        String originalFilename2 = filename2.getOriginalFilename(); //文件名
        uploadFile1.transferTo(new File("E:" + originalFilename1));
        uploadFile2.transferTo(new File("E:" + originalFilename2));
        }
      • 方式2:后端用一个MultipartFile[]数组接收前端发送的同名name文件,当做数组处理

        1
        2
        3
        4
        5
        6
        7
        8
        @RequestMapping(value = "/uploadArr")
        @ResponseBody
        public void uploads(String name,MultipartFile[] files) throws IOException {
        System.out.println("name = " + name);
        for (MultipartFile file : files) {
        file.transferTo(new File("E:" + file.getOriginalFilename()));
        }
        }

Spring JdbcTemplate

概述

Spring JdbcTemplate是Spring框架中提供的一个对象,是对原始JDBC API对象的简单封装,Spring框架为我们提供了很多操作的模板类。例如:操作关系型数据库的JdbcTemplate和HibernateTemplate,操作NoSQL数据库的RedisTemplate,操作消息队列的JmsTemplate等

JdbcTemplate开发步骤

  1. 导入spring-jdbc和spring-tx(事务)坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.0.5.RELEASE</version>
    </dependency>
  2. 创建数据库表和实体

  3. 创建JdbcTemplate对象

    • 可以把JdbcTemplate对象和DataSource对象配置为Bean,即可直接从Spring容器拿到对象
  4. 执行数据库操作

    • 更新/删除:int update(String sql,Object... args)
    • 查询:
      • 查询单个对象:T queryForObject(String sql,RowMapper<T> rowMapper,Object... args)
      • 查询多个对象的列表:<T> query(String sql,RowMapper<T> rowMapper,Object... args)
      • 聚合函数查询:T queryForObject(String sql,Class<T> requiredType)

案例:用户管理系统

角色列表的展示

  • 步骤:
    1. 点击角色管理菜单,发送请求到服务器端(修改角色管理菜单栏的URL地址)
    2. 创建RoleController和showList()方法
    3. 创建RoleService和showList()方法
    4. 创建RoleDao和findAll()方法
    5. 使用JdbcTemplate完成查询操作
    6. 将查询数据存储到Model中
    7. 转发到role-list.jsp页面进行展示
  • 踩坑踩大坑!!!报错CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
    • 原因:竟然是新引入的Maven依赖:mysql-connector没有被IDEA打包进项目!
    • 解决方法:在IDEA的项目结构选项里面把漏掉的jar包添加到WEB-INF/lib

角色添加

  • 步骤:

    1. 点击角色列表的新建按钮跳转到角色添加页面
    2. 输入角色信息,点击保存按钮,表单数据提交到服务器
    3. 编写RoleController的save()方法
    4. 编写RoleService的save()方法
    5. 编写RoleDao的insert()方法
    6. 使用JdbcTemplate保存Role数据到sys_role表
    7. 跳转会角色列表页面
  • 可能的问题:乱码问题

    • 原因:添加的表单form是POST方式,对于tomcat来说会乱码

    • 解决方法:配置一个过滤器把编码方式设置为UTF-8

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <!--配置过滤器,解决乱码问题-->
      <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>
      </filter>
      <filter-mapping>
      <filter-name>characterEncodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
      </filter-mapping>

用户列表的展示

用户添加

用户删除

  • 注意要同时处理两张表:用户表、用户与角色的对应关系表,应该先处理关系表再删主表,因为有外键约束

SpringMVC拦截器

拦截器(interceptor)的作用

  • SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理后处理
  • 将拦截器按照一定的顺序连接成一条链,这条链被称为拦截器链(interceptor chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器会按照其之前定义的顺序被调用。拦截器也是AOP思想的具体实现

拦截器和过滤器的区别

image-20210821164156819

image-20211222211806443

拦截器快速入门

  • 步骤

    1. 创建拦截器实现类,实现HandlerInterceptor接口

      • 重写boolean preHandle():返回true时代表放行,返回false拦截在这里
      • 重写void postHandle():这里可以再次修改ModelAndView对象
      • 重写void afterCompletion:这里可以用于处理Exception异常
    2. 配置拦截器

      • 在web.xml中配置(多个)拦截器

        1
        2
        3
        4
        5
        6
        7
        8
        9
        <!--配置拦截器-->
        <mvc:interceptors>
        <mvc:interceptor>
        <!--拦截哪些资源?-->
        <mvc:mapping path="/**"/>
        <!--拦截器实现类的全类名-->
        <bean class="com.interceptor.MyInterceptor1"/>
        </mvc:interceptor>
        </mvc:interceptors>
      • 多个在web.xml中配置的拦截器会按照配置的先后顺序执行

    3. 测试拦截器效果

    image-20210821190329982

案例:用户登录权限控制

  • 需求:用户没有登录的情况下,不能对后台菜单进行访问操作,点击菜单跳转到登录页面,只有用户登录成功后才可以进行后台功能的操作

  • 方案:编写一个拦截器类,实现HandlerInterceptor接口,重写preHandle()方法

    • 类似于过滤器,在这个拦截器的preHandle()方法中检测Session中是否含有已经登录的user对象

      • 如果有,就return true放行
      • 如果没有,就重定向到登录页面并return false
    • 在springMVC配置文件中配置拦截器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!--配置拦截器-->
      <mvc:interceptors>
      <mvc:interceptor>
      <!--配置需要拦截的资源-->
      <mvc:mapping path="/**"/>
      <!--配置排除拦截的资源-->
      <mvc:exclude-mapping path="/user/login"/>
      <bean class="com.taoyyz.interceptor.AccessControlInterceptor"/>
      </mvc:interceptor>
      </mvc:interceptors>

      要注意排除掉登录的访问拦截

SpringMVC异常处理机制

SpringMVC异常处理思路

  • 系统中的异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。
  • 系统的Dao、Service、Controller出现都通过throws Exception往上层抛出,最后由SpringMVC前端控制器交由异常处理器(HandlerExceptionResolver)进行异常处理

SpringMVC异常处理的两种方式

  • 使用SpringMVC提供的简单异常处理器:SimpleMappingExceptionResolver
  • 实现Spring的异常处理接口:HandlerExceptionResolver自定义自己的异常处理

简单异常处理器

  • SpringMVC已经定义好了该类型的转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--配置简单异常映射处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!--当exceptionMappings匹配不到时匹配此默认-->
    <property name="defaultErrorView" value="error"/>
    <property name="exceptionMappings">
    <map>
    <!--匹配Exception异常-->
    <entry key="java.lang.Exception" value="genericExp"/>
    <!--匹配类型转换异常-->
    <entry key="java.lang.ClassCastException" value="classCastExp"/>
    </map>
    </property>
    </bean>

自定义异常处理

  • 步骤

    1. 创建异常处理器实现类,实现HandlerExceptionResolver接口和其中的resolveException()方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) {
      ModelAndView modelAndView = new ModelAndView();
      if (e instanceof ClassCastException) {
      modelAndView.addObject("msg", "类型转换异常惹");
      } else if (e instanceof NullPointerException) {
      modelAndView.addObject("msg", "空指针啦");
      } else {
      modelAndView.addObject("msg", "出现异常惹");
      }
      modelAndView.setViewName("error"); //统一跳转到error.jsp页面
      return modelAndView;
      }
    2. 配置异常处理器

      1
      2
      <!--配置自定义异常处理器-->
      <bean class="com.taoyyz.resolver.MyExceptionResolver"/>
    3. 编写异常页面

      1
      2
      3
      <body>
      <h1>出错惹,错误原因:${msg}</h1>
      </body>
    4. 测试异常的跳转

Spring的AOP

Spring的AOP简介

  • AOP是 Aspect Oriented Programming 的缩写,意为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

AOP的作用及优势

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
  • 优势:减少重复代码,提高开发效率,并且便于维护

AOP的底层实现

  • AOP的底层是通过Spring提供的动态代理技术实现的,在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,完成功能的增强

AOP的动态代理技术

  • JDK代理:基于接口的动态代理技术
  • cglib代理:基于父类的动态代理技术
image-20210821220350274

AOP的相关概念

  • Target:代理的目标对象
  • Proxy:一个类被AOP增强后,就产生了一个结果的代理类
  • Joinpoint:连接点,指那些被拦截到的点(方法)。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点
  • Pointcuit:切入点,指的是要对哪些连接点进行拦截的定义
  • Advice:通知/增强,指的是拦截到连接点之后要做的事情(增强)
  • Aspect:切面,是切入点和通知的结合
  • Weaving:织入,把增强应用到目标对象来创建新的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入

AOP开发明确的事项

  • 需要编写的内容:
    • 编写核心业务代码(目标类的目标方法)
    • 编写切面类,切面类中有通知(增强功能方法)
    • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
  • AOP技术实现的内容
    • Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,就使用代理机制,动态的创建目标对象的代理对象,根据通知的类型,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
  • AOP底层使用哪种代理方式
    • Spring框架会根据目标类是否实现了接口来判断采用哪种动态代理的方式

AOP快速入门

基于XML的AOP开发

步骤:
  1. 导入AOP相关坐标

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.12</version>
    </dependency>
  2. 创建目标接口和目标类( 内部有切点)

  3. 创建切面类(内部有增强方法)

  4. 将目标类和切面类的对象创建权交给Spring,即配置Bean

    1
    2
    3
    4
    <!--配置目标对象的bean-->
    <bean id="target" class="com.taoyyz.aop.Target"/>
    <!--配置切面对象的bean-->
    <bean id="myAspect" class="com.taoyyz.aop.MyAspect"/>
  5. 在applicationContext.xml中配置织入关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!--配置织入,告诉Spring框架哪些方法(切入点)需要被增强(前置、后置等)需要AOP的命名空间-->
    <aop:config>
    <!--声明配置切面-->
    <aop:aspect ref="myAspect">
    <!--配置此切面的后置增强,切面 = 切点 + 通知,method是切面中的方法,pointcut是需要切入到哪里的切点表达式-->
    <aop:before method="before"
    pointcut="execution(public void com.taoyyz.aop
    .TargetInterface.save())"/>
    </aop:aspect>
    </aop:config>
  6. 测试代码

    调用被增强之后的Target对象的save()方法,此save()方法已经被织入一个前置增强

切点表达式

  • 语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

    • 访问修饰符可以省略不写
    • 返回值类型、包名、类名、方法名可以使用*星号代表任意
    • 包名与类名之间可以用一个点.代表当前包下的类,两个点..代表当前包及子包下的类
    • 参数列表可以使用两个点表示任意个数,任意类型的参数列表
  • 例如: 以下被切点表达式匹配的情况

    1
    2
    3
    4
    5
    6
    7
    8
    //匹配指定包下的指定类的无返回值的method()方法
    execution(public void com.taoyyz.aop.Target.method())
    //匹配指定包下的指定类的无返回值的任意参数的任意方法
    execution(void com.taoyyz.aop.Target.*(..))
    //匹配指定包及子包下的任意类的任意方法的任意参数的任意返回值的方法
    execution(* com.taoyyz.aop.*.*(..))
    //匹配任意包及其子包下任意类的任意方法的任意参数的任意返回值的方法
    execution(* *..*.*(..))

通知的类型

对于一个通知的配置语法:

1
<aop:通知类型 method="方法名" pointcut="execution(public void com.taoyyz.*.*(..))"/>

可选的通知类型有:

image-20210822154545596

切点表达式的抽取

  • 在多个增强的切点表达式相同时,可以将这些切点表达式抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取之后的切点表达式

    1
    2
    3
    4
    5
    6
    <!--抽取pointcut切点表达式-->
    <aop:pointcut id="myPointCut" expression="execution(void com.taoyyz..*.*(..)))"/>
    <!--环绕增强,引用切点表达式-->
    <aop:around method="around" pointcut-ref="myPointCut"/>
    <!--异常抛出增强,引用切点表达式-->
    <aop:after-throwing method="afterThrowing" pointcut-ref="myPointCut"/>

基于注解的AOP开发

步骤:

  1. 创建目标接口和目标类(内部有切点)

  2. 创建切面类(内部有增强方法)

  3. 将目标类和切面类的对象创建权交给Spring

    • 指定目标类的@Component
    • 指定切面类的@Component
  4. 在切面类中使用注解配置织入关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component("myAspect") //指定此类是一个Bean
    @Aspect //指定此类为切面类
    public class MyAspect {
    //配置前置增强
    @Before("execution(void com.taoyyz.annoAop..*.*(..))")
    // @Before("bean(target)")
    void before() {
    System.out.println("前置增强");
    }
    }
  5. 在配置文件中开启组件扫描和AOP的自动代理

    1
    2
    3
    4
    <!--开启组件扫描-->
    <context:component-scan base-package="com.taoyyz"/>
    <!--开启AOP自动代理-->
    <aop:aspectj-autoproxy/>
  6. 测试

通知的类型

对于注解配置通知的语法:

@通知注解("切点表达式"),也可以用@通知注解("bean(Bean的id)")

可选的通知类型有:

image-20210822165932936

切点表达式的抽取
  1. 定义一个任意的方法用于注解抽取切点表达式,切点表达式的名称就是方法名

    1
    2
    @Pointcut("execution(* com.taoyyz..*.*(..))")
    public void myExp() { }
  2. 引用此切点表达式,以下4中方式效果相同

    1
    2
    3
    4
    5
    6
    7
    8
    //配置前置增强
    //@Before("execution(* com.taoyyz..*.*(..))")
    //@Before("bean(target)") //切入到id为target的bean中
    //@Before("myExp()") //引入切点表达式的抽取
    @Before("MyAspect.myExp()") //同上
    void before() {
    System.out.println("前置增强");
    }

Spring的事务控制

编程式事务控制相关对象

PlatformTransactionManager

  • 此接口是Spring的事务管理器,它提供了常用的操作事务的方法:

    image-20210822173527109

  • PlatformTransactionManager是接口类型,不同的Dao层技术有不同的实现类,例如Dao层技术是jdbc或mybatis时,实现类为org.springframework.jdbc.datasource.DataSourceTransactionManager

TransactionDefinition

  • 此接口是事务的定义信息对象,含有如下方法:

    image-20210822174251488
  • 事务的隔离级别

    • ISO_DEFAULT
    • ISO_READ_UNCOMMITTED
    • ISO_READ_COMMITTED
    • ISO_REPEATABLE_READ
    • ISO_SERIALIZABLE
  • 事务的传播行为

    • REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务,则加入这个事务(默认)

      例如:A调用B,B会看A有没有事务,如果A没有就新建,否则A有就加入到A的事务

    • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)

      例如:A调用B,B会看A有没有事务,如果A有事务就支持A的事务,否则A没有就以非事务方式执行

    • MANDATORY:使用当前的事务,如果当前没有事务就抛出异常

      例如:A调用B,B会看A有没有事务,如果A有事务就支持当前事务,否则A没有就抛出异常

    • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起

    • NEVER:以非事务方式执行,如果当前存在事务就抛出异常

    • NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行REQUIRED操作

    • 超时时间:默认为-1没有超时限制。如果有,则以秒为单位设置

    • 是否只读:建议查询时设置为只读

TransactionStatus

  • 此接口提供了事务的运行状态,方法如下:

    image-20210822175929810

基于XML的声明式事务控制

概念:

Spring的声明式事务控制就是采用声明的方式来处理事务。声明指的是在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务

作用:

  • 声明式事务管理不侵入开发的组件,具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
  • 在不需要事务管理的时候,只需要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译
  • Spring声明式事务控制底层就是AOP

声明式事务控制的实现

  • 明确事项:

    • 谁是切点?需要被事务控制的业务方法

      1
      2
      3
      4
      <!--切点:目标对象内部需要增强的方法(被事务控制的方法)-->
      <bean id="accountService" class="com.taoyyz.service.impl.AccountServiceImpl">
      <property name="accountDao" ref="accountDaoImpl"/>
      </bean>
    • 谁是通知?事务控制

      1
      2
      3
      4
      5
      6
      7
      8
      <!--通知:事务的增强-->
      <tx:advice id="txAdvice" transaction-manager="transactionManager">
      <!--设置事务的属性信息,可以对切入的多个method分别配置-->
      <tx:attributes>
      <!--name为切入到的方法,*表示任意方法-->
      <tx:method name="*"/>
      </tx:attributes>
      </tx:advice>
    • 配置切面?

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!--配置一个平台事务管理器,需要一个数据源对象以供操作数据库-->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource
      .DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
      </bean>
      <!--配置织入:结合通知和切点-->
      <aop:config>
      <!--把通知txAdvice切入到accountService这个切入点-->
      <aop:advisor advice-ref="txAdvice" pointcut="bean(accountService)"/>
      </aop:config>
  • 配置的参数

    image-20210823030556659

基于注解的声明式事务控制

  • 谁是切点?需要被事务控制的业务方法,需要把切入点所在的类声明为Bean

  • 谁是通知?为切入点加上@Transactional注解,此注解可以有参数,例如只读、隔离级别、超时时间等

    • 如果@Transactional注解打在类上,则此类所有的方法都被此事务管理
    • 如果@Transactional注解打在方法上,则对此方法生效
  • 配置切面?织入切面和切点:在XML中配置事务管理器:

    1
    2
    3
    4
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
    </bean>
  • 引入事务的注解驱动:

    1
    2
    <!--引入事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
  • 在多个方法间使用事务可能出现的问题参考我的面试总结的md文件,里面提到了A方法调用B方法的事务问题

MyBatis

  • 原始JDBC开发存在的问题:

    • 数据库频繁创建连接、释放造成系统资源的浪费从而影响系统性能
    • SQL语句在代码中硬编码,造成代码不易维护,实际应用SQL变化的可能较大,SQL变动需要改动Java代码
    • 查询操作时,需要手动将结果集中的数据封装到实体中。插入操作时需要手动将实体的数据设置到SQL语句中
  • 解决方案:

    • 使用数据库连接池初始化连接资源
    • 将SQL语句抽取到XML配置文件中
    • 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

概念

  • MyBatis是一个优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注SQL语句本身,而不需要花费精力去加载驱动、创建连接、创建statement等繁杂的过程
  • MyBatis通过XML或注解方式将要执行的各种statement配置起来,并通过Java对象和statement中SQL的动态参数进行映射生成最终执行的SQL语句
  • 最后MyBatis框架执行SQL并将结果映射为Java对象并返回,采用ORM思想解决了实体和数据库映射的问题,对JDBC进行了封装,屏蔽了JDBC API底层访问细节,使我们不再与JDBC API打交道就可以完成数据库的操作

MyBatis快速入门

  1. 添加MyBatis坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dependencies>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
    </dependency>
    </dependencies>
  2. 创建数据表

  3. 编写实体类

  4. 编写XML映射文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="userMapper">
    <select id="findAll" resultType="com.taoyyz.domain.User">
    select * from user
    </select>
    </mapper>
  5. 编写MyBatis核心配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?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">
    <configuration>
    <!--数据源环境-->
    <environments default="mysql">
    <!--配置一个环境-->
    <environment id="mysql">
    <transactionManager type="JDBC"/>
    <!--配置数据源为POOLED池-->
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///db3"/>
    <property name="username" value="root"/>
    <property name="password" value="199988"/>
    </dataSource>
    </environment>
    </environments>
    <!--加载映射文件-->
    <mappers>
    <mapper resource="com/taoyyz/mapper/UserMapper.xml"/>
    </mappers>
    </configuration>
  6. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //1.读取MyBatis核心配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //2.利用工厂类建造者对象建造一个工厂类对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    //3.利用工厂对象生产一个会话对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //4.利用对话对象执行SQL语句,参数就是mapper的命名空间和id
    List<User> userList = sqlSession.selectList("userMapper.findAll");
    //5.打印数据
    System.out.println(userList);
    //6.释放资源
    sqlSession.close();
    is.close();

踩坑!!数据库字段名含有下划线时,无法赋值给Java类中的成员属性(转驼峰)

  • 解决方法:

    • 在Java成员属性命名时,考虑数据库字段名下划线转驼峰,也就是字段dept_id赋值时匹配的是deptId

    • 关掉MyBatis的转驼峰,默认为false

      1
      2
      <!--关掉驼峰转换-->
      <setting name="mapUnderscoreToCamelCase" value="false"/>

MyBatis的CRUD

插入数据

  1. 在映射文件中编写SQL语句

    1
    2
    3
    4
    <!--配置此insert语句的id和参数类型-->
    <insert id="insert" parameterType="com.taoyyz.domain.User">
    insert into user values (#{id},#{username},#{password})
    </insert>
    • 使用parameterType属性指定参数的类型的全类名,这里是User类型

    • 使用#{实体参数的属性名}取出User类型的成员属性值作为占位符

    • 主键自增:需要对插入的数据实行主键自增,可以在insert标签利用以下属性(有顺序要求)

      1
      <insert id="save" parameterType="emp" keyProperty="id" useGeneratedKeys="true">
  2. 调用SqlSession对象的int insert(String s,Object o)方法

    • 参数1:要执行的映射文件中的SQL语句的命名空间和id,例如userMapper.insert
    • 参数2:SQL语句插入所需要的参数对象,例如User对象
  3. 调用SqlSession对象的commit()方法提交事务

  4. 释放连接:关闭SqlSession与输入流

修改数据

  1. 在映射文件中编写SQL语句

    1
    2
    3
    4
    <!--配置此update语句的id和参数类型-->
    <update id="update" parameterType="com.taoyyz.domain.User">
    update user set username = #{username},password = #{password} where id = #{id}
    </update>
    • 使用parameterType属性指定参数的类型的全类名,这里是User类型
    • 使用#{实体参数的属性名}取出User类型的成员属性值作为占位符
  2. 调用SqlSession对象的int update(String s,Object o)方法

    • 参数1:要执行的映射文件中的SQL语句的命名空间和id,例如userMapper.update
    • 参数2:SQL语句修改所需要的参数对象,例如User对象
  3. 调用SqlSession对象的commit()方法提交事务

  4. 释放连接:关闭SqlSession与输入流

删除数据

  1. 在映射文件中编写SQL语句

    1
    2
    3
    4
    <!--配置此delete语句的id和参数类型-->
    <delete id="delete" parameterType="java.lang.Integer">
    delete from user where id = #{单个参数名随便写}
    </delete>
    • 使用parameterType属性指定参数的类型的全类名,这里是Integer类型
    • 使用#{参数的属性名}取出参数类型的值,这里是单个值随意
  2. 调用SqlSession对象的int delete(String s,Object o)方法

    • 参数1:要执行的映射文件中的SQL语句的命名空间和id,例如userMapper.delete
    • 参数2:SQL语句修改所需要的参数对象,例如一个Integer对象
  3. 调用SqlSession对象的commit()方法提交事务

  4. 释放连接:关闭SqlSession与输入流

MyBatis核心配置文件

值得注意的是,configuration标签中的子标签是有顺序要求的,例如typeAliases必须位于properties后面

  • configuration标签的配置
    • properties属性
    • settings设置
    • typeAliases类型别名
    • typeHandlers类型处理器
    • objectFactory对象工厂
    • plugins插件
    • environments环境配置,支持配置多个环境,可以通过default属性指定默认的环境名称
      • environment配置某个环境,通过id属性区分
        • transactionManager事务管理器,type属性设置为JDBC
        • dataSource数据源,type属性设置为POOLED连接池
          • driver、url、username、password等属性
    • databaseIdProvider数据库厂商标识
    • mappers映射器,加载Mapper.xml文件

environments标签

  • 对于事务管理器transactionManager,它的type属性有两种:
    • JDBC:这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域
    • MANAGED:这个配置几乎没做什么,它从来不提交或回滚一个连接,而是让容器来管理事务的整个声明周期(比如JEE应用服务器的上下文)。默认情况下它不会关闭连接,然而一些容器并不希望这样,因此需要将closeConnection属性设置为false来组织它默认的关闭行为
  • 对于数据源dataSource,它的type属性有三种:
    • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接
    • POLLED:这个数据源的实现利用的概念将JDBC连接对象组织起来
    • JNDI:这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置在一个JNDI上下文的引用

mapper标签

  • mapper标签用于加载映射,加载方式有以下几种:

    • 使用相对resources资源目录的类路径引用,例如:

      1
      <mapper resource="com/taoyyz/mapper/UserMapper.xml"/>
    • 使用完全限定资源定位符(URL),例如

      1
      <mapper url="file:///var/mappers/UserMapper.xml"/>
    • 使用映射器接口实现类的全类名,例如:

      1
      <mapper class="org.mybatis.builder.UserMapper.xml"/>
    • 将包内的映射器接口实现全部注册为映射器(类似于扫描包),例如:

      1
      <package name="org.mybatis.builder"/>

properties标签

  • properties标签用于加载properties配置文件

    1
    2
    <!--加载properties配置文件-->
    <properties resource="jdbc.properties"/>
  • 使用SpEL表达式即可取出配置文件中的key

typeAliases标签

  • typeAliases标签用于为Java类型的全限定名设置一个别名

    • 定义全类名的别名:com.taoyyz.domain.User的别名为user
    1
    2
    3
    4
    <!--定义别名-->
    <typeAliases>
    <typeAlias type="com.taoyyz.domain.User" alias="user"/>
    </typeAliases>
  • MyBatis框架已经为我们设置好了一些类型的别名

    image-20210823172634343
  • Mybatis会自动扫描指定包下面的JavaBean,并且默认设置一个别名,默认的名字为: JavaBean 的首字母小写的非限定类名来作为它的别名(其实别名是不去分大小写的)。也可在javabean 加上注解@Alias 来自定义别名, 例如: **@Alias(user)**,也可以在配置文件中用 <typeAliases> 的子元素 <package> 来让Mybatis自动扫描

    1
    2
    3
    <typeAliases>
    <package name="com.domain"/>
    </typeAliases>

MyBatis相关API

SqlSessionFactoryBuilder

  • SqlSession的工厂建造器,build()方法用于构造一个工厂对象

    • SqlSessionFactory build(InputStream inputStream)
      • 此方法传入一个InputStream输入流对象用于指示MyBatis核心配置文件的路径
      • 会返回一个SqlSessionFactory工厂对象
      • 建议使用org.apache.ibatis.io Resources类的getResourceAsStream(String resource)方法传入一个类加载路径下的文件名,获得此文件名的InputStream输入流
  • 用法:

    1
    2
    3
    4
    //1.读取MyBatis核心配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //2.利用工厂类建造者对象建造一个工厂类对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

SqlSessionFactory

  • SqlSession的工厂类,用于创建SqlSession的实例,常用方法:
    • SqlSession openSession():默认开启一个事务,但事务不会自动提交,需要手动commit()
    • SqlSession openSession(boolean autoCommit):参数可以指定是否自动提交

SqlSession

  • 会话对象,用于操作数据库、执行SQL、提交或回滚事务、获取映射器实例等
  • 执行SQL语句的方法主要有:
    • T selectOne(String statement,Object parameter)
    • List<E> selectList(String statement,Object parameter)
    • int insert(String statement,Object parameter)
    • int update(String statement,Object parameter)
    • int delete(String statement,Object parameter)
  • 操作事务的主要方法有:
    • void commit()
    • void rollback()

MyBatis的Dao层实现

传统方式

编写Dao层接口,在方法里利用MyBatis的SqlSession对象查询数据库并返回结果

1
2
3
4
5
6
7
8
9
10
public class UserDaoImpl implements UserDao {
@Override
public List<User> findAll() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> userList = sqlSession.selectList("userMapper.findAll");
return userList;
}
}

代理开发方式

  • 采用MyBatis的代理开发方式实现Mapper/Dao层开发是主流方式
  • Mapper/Dao层接口开发方法只需要编写Mapper接口(Dao接口),由MyBatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Mapper/Dao层接口实现类的方法
  • Mapper接口开发需要遵循以下规范:
    • Mapper.xml文件中的**mapper标签的namespace要与Mapper/Dao层接口的全类名相同**
    • Mapper/Dao层接口的方法名和Mapper.xml中定义的每个操作(例如select)标签的**id相同**
    • Mapper/Dao层接口方法的参数类型和Mapper.xml中定义的每个SQL的parameterType的类型相同
    • Mapper/Dao层接口方法的返回值类型和Mapper.xml中定义的每个SQL的resultType的类型相同

MyBatis映射文件高级

动态SQL

  • if标签配合where标签:可以根据条件决定是否拼接SQL,如果if写在where中,相当于where 1 = 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <select id="findByCondition" resultType="user" pa
    select * from user
    <where>
    <if test="id!=0 and id!=null">
    and id = #{id}
    </if>
    <if test="username!=null">
    and username = #{username}
    </if>
    <if test="password!=null">
    and password = #{password}
    </if>
    </where>
    </select>
  • foreach标签,用于遍历参数,collection属性可以为listarray

    1
    2
    3
    4
    5
    6
    7
    8
    <select id="findByIds" resultType="user" parameterType="list">
    select * from user
    <where>
    <foreach collection="list" open="id in(" item="id" separator="," close=")">
    #{id}
    </foreach>
    </where>
    </select>

抽取

把重复的SQL语句抽取到mapper标签内的sql标签内:

1
2
3
4
5
<sql id="selectUser">select * from user</sql>
<select id="findByCondition" resultType="user" parameterType="user">
<!--引用抽取的sql即可-->
<include refid="selectUser"/>
</select>

MyBatis核心配置文件高级

typeHandlers标签

  • MyBatis在预处理语句(PreparedStatement)中设置参数或从结果集取出数据时,都会用类型处理器将获取的值以合适的方式转化为Java的数据类型,以下是一些默认的类型处理器:

image-20210823211628684

  • 可以重写类型处理器或者创建类型处理器来处理不支持的或非标准的类型。

    • 具体做法为:实现org.apache.ibatis.type.TypeHandler接口,或继承一个很便利的类org.apache.ibatis.type.BaseTypeHandler,然后可以选择将它映射到一个JDBC类型。
    • 例如需求:一个Java中的Date数据类型,将它存储到数据库时存为一个毫秒值,取出来时转换成Java的Date类型,也就是在Java的Date类型与数据库的varchar毫秒值之间转换
  • 开发步骤:

    1. 定义转换类,继承BaseTypeHandler<T>

    2. 重写其中的方法,其中

      • setNonNullParameter为Java程序设置到数据库的回调方法
      • getNullableResult为查询时MySQL字符串类型转换为Java的Type类型的方法
    3. 在MyBatis核心配置文件中注册此转换类

      1
      2
      3
      4
      <!--自定义类型处理器-->
      <typeHandlers>
      <typeHandler handler="com.handler.DateTypeHandler"/>
      </typeHandlers>
    4. 测试

plugins标签

  • 概念

    • MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
  • 开发步骤:

    1. 导入通用PageHelper的坐标

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>4.2.1</version>
      </dependency>
      <dependency>
      <groupId>com.github.jsqlparser</groupId>
      <artifactId>jsqlparser</artifactId>
      <version>0.9.5</version>
      </dependency>
    2. 在MyBatis核心配置文件中配置PageHelper插件

      1
      2
      3
      4
      5
      6
      7
      <!--配置分页插件-->
      <plugins>
      <plugin interceptor="com.github.pagehelper.PageHelper">
      <!--配置方言-->
      <property name="dialect" value="mysql"/>
      </plugin>
      </plugins>
    3. 测试:利用PageHelper类的静态方法操作分页

      1
      PageHelper.startPage(2,3); //页码为第2页,分页大小为显示3条
  • 获得分页相关参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //获得分页相关参数,利用一个PageInfo对象,参数为当前结果集
    PageInfo<User> pageInfo = new PageInfo<>(userList);
    System.out.println("当前页为 " + pageInfo.getPageNum());
    System.out.println("显示条数 " + pageInfo.getPageSize());
    System.out.println("总条数 " + pageInfo.getTotal());
    System.out.println("总分页数 " + pageInfo.getPages());
    System.out.println("上一页的页码 " + pageInfo.getPrePage());
    System.out.println("下一页的页码 " + pageInfo.getNextPage());
    System.out.println("是否第一页 " + pageInfo.isIsFirstPage());
    System.out.println("是否最后一页 " + pageInfo.isIsLastPage());

MyBatis多表操作

  • 一对一:用<resultMap>配置
  • 一对多:用<resultMap> + <collection>配置
  • 多对多:用<resultMap> + <collection>配置

一对一

Emp类中存在一个成员属性:Dept对象,表示每一个Emp员工所在的Dept部门,一个员工对应属于一个部门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<mapper namespace="com.taoyyz.dao.EmpDao">
<!--配置一个结果Map集合,类型是Emp全类名,这里用别名emp-->
<resultMap id="empMap" type="emp">
<!--配置主键,column是数据库查询出的字段名,property是实体类属性名-->
<id column="empid" property="id"/>
<!--其他属性用result,column是查询的数据库结果名,property是实体类属性名-->
<result column="empname" property="name"/>
<result column="empdept_id" property="dept_id"/>
<!--<result column="deptdept_id" property="dept.dept_id"/>-->
<!--<result column="deptname" property="dept.name"/>-->
<!--<result column="deptloc" property="dept.loc"/>-->
<!--另一种方式,property是Emp中的属性名称dept,javaType是Emp中的属性类型-->
<association property="dept" javaType="dept">
<id column="ddept_id" property="dept_id"/>
<result column="dname" property="name"/>
<result column="dloc" property="loc"/>
</association>
</resultMap>
<select id="findAll" resultMap="empMap">
select emp.id as empid
, emp.name as empname
, emp.dept_id as empdept_id
, dept.dept_id as deptdept_id
, dept.name as deptname
, dept.loc as deptloc
from emp,
dept
where emp.dept_id = dept.dept_id
</select>
</mapper>

一对多

Dept类中存在一个成员属性:List<Emp>集合,表示一个Dept部门下可能存在多个Emp员工

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<mapper namespace="com.taoyyz.dao.DeptDao">
<!--配置一个结果Map集合,类型是Dept全类名,这里用别名dept-->
<resultMap id="deptMap" type="dept">
<id column="ddept_id" property="dept_id"/>
<result column="dname" property="name"/>
<result column="dloc" property="loc"/>
<!--配置集合信息,property是Dept中集合的名称,ofType是Dept中集合装的数据类型-->
<collection property="empList" ofType="emp">
<id column="eid" property="id"/>
<result column="ename" property="name"/>
<result column="edept_id" property="dept_id"/>
</collection>
</resultMap>
<select id="findAll" resultMap="deptMap">
SELECT dept.dept_id as ddept_id,
dept.name as dname,
dept.loc as dloc,
emp.id as eid,
emp.name as ename,
emp.dept_id as edept_id
FROM emp,
dept
WHERE emp.dept_id = dept.dept_id
</select>
</mapper>

多对多

Emp类中存在一个成员属性:List<Role>集合,表示每个Emp员工可能有多个角色,关系由一个中间表维护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<mapper namespace="com.taoyyz.dao.EmpDao">
<resultMap id="empRoleMap" type="emp">
<id column="eid" property="id"/>
<result column="name" property="name"/>
<result column="dept_id" property="dept_id"/>
<collection property="roleList" ofType="role">
<id column="roleid" property="id"/>
<id column="roleName" property="roleName"/>
<id column="roleDesc" property="roleDesc"/>
</collection>
</resultMap>
<select id="findAllWithRole" resultMap="empRoleMap">
SELECT *, emp.id as eid
FROM emp,
role,
emp_role
where emp.id = emp_role.empid
AND role.id = emp_role.roleid
</select>
</mapper>

MyBatis注解开发

MyBatis注解实现简单的CRUD

  • 在Dao/Mapper接口的方法上打上相应的注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public interface UserDao {
    @Select("select * from user")
    List<User> findAll() throws IOException;

    @Insert("insert into user values (#{id}, #{username},#{password},#{birthday})")
    void insert(User user);

    @Update("update user set username = #{username},
    password = #{password} where id = #{id}")
    void update(User user);

    @Delete("delete from user where id = #{单个参数名随便写}")
    void delete(Integer id);

    @Select("select * from user where id = #{单个参数名随便写}")
    User findUserById(Integer id);
    }
  • 在MyBatis核心配置文件中开启注解接口扫描的包

    1
    2
    3
    4
    5
    <!--从Dao层的包加载映射-->
    <mappers>
    <!--加载映射关系的注解的包-->
    <package name="com.taoyyz.dao"/>
    </mappers>

MyBatis注解实现多表复杂映射

image-20210824180725774

image-20210824180818367

一对一

  • 数据库字段与Java成员属性名的对应关系:

    • @Result(column = "did", property = "dept_id"):把查询结果字段dept_id映射赋值给成员属性名

      • 可以使用多个@Result来映射多个字段与成员属性名
        • 区分多个表的成员属性在property属性用成员属性.该成员自身的属性赋值
      • 也可以用@Results({@Result(column = "did", property = "dept_id")...})
    • ```java
      @Select(“select * from emp”)
      @Result(column = “dept_id”, property = “dept_id”)
      @Result(

          //javaType要封装到Emp的成员属性的类,property是要封装到的Emp的成员属性名
          javaType = Dept.class, property = "dept",
          //column是根据哪个字段查询Dept表的数据,然后用此dept_id去select查Dept表
          column = "dept_id",one = @One(select = "com.taoyyz.dao.DeptDao
                                        .findById")
      

      )
      List findAll();

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      * 这样相当于先查询`@Select("select * from emp")`,然后用查询的结果中包含的`dept_id`去再次调用@One注解传入一个打上了@Select注解的select语句的`全类名.方法名`即可二次封装

      #### 一对多

      * 也可以采用先`@Select(select * from dept)`查出一张表的数据,然后在@Result里封装映射结果到成员属性里

      * 封装的成员属性是一个集合时,也就是一对多查询,此时@Result就要指定`javaType`和`many`

      ```java
      @Select("select * from dept")
      @Results({
      @Result(id = true, column = "dept_id", property = "dept_id"),
      @Result(column = "name", property = "name"),
      @Result(column = "loc", property = "loc"),
      @Result(
      column = "dept_id", property = "empList",
      javaType = List.class, many = @Many(select = "com.taoyyz.dao.EmpDao.findByDeptId")
      )
      })
      List<Dept> findAllWithEmp();
  • 关键就是javaType此时是List集合的class,并且由于结果是多个,所以用many再查其他表中的记录

多对多

  • 关键是第一次查询的emp.id要作为中间表的条件,找到对应的role.id,再根据role.id查询role表中的id

    1
    2
    3
    4
    5
    6
    7
    @Select("select *,emp.id as empid from emp")
    @Result(column = "empid",property = "id")
    @Result(
    javaType = List.class,property = "roleList",
    column = "empid",many = @Many(select = "com.taoyyz.dao.RoleDao.findById")
    )
    List<Emp> findAllWithRole();
    1
    2
    @Select("SELECT * FROM emp_role,role where emp_role.roleid = role.id and emp_role.empid = #{empid}")
    List<Role> findById(Integer empid);

MyBatis-Plus

概述

简介

MyBatis-Plus(简称MP)是一款MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

框架结构

image-20210918162712849

快速开始及3种用法

  1. 创建Maven工程

  2. 导入相关坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <dependencies>
    <!-- mybatis-plus插件依赖 -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.1.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.3</version>
    </dependency>
    <!--简化bean代码的工具包-->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    <version>1.18.20</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.4</version>
    </dependency>
    </dependencies>

用法1:MyBatis + MP

  1. 将UserMapper继承BaseMapper<T>

    1
    public interface UserMapper extends BaseMapper<User>

    此时UserMapper获得了BaseMapper中的方法: image-20210918171312644

  2. 利用MybatisSqlSessionFactoryBuilder构造SqlSessionFactory,然后用此工厂产生SqlSession

  3. 调用mapper对象继承自BaseMapper的方法,例如List<T> selectList(Wrapper<T> queryWrapper);

    • 此处的Wrapper是条件构造器,如果没有条件,直接传入null即可
    • MyBatisPlus默认以实体类名首字母小写作为表名,如果需要自己指定,在类上打上@TableName("tb_user")注解指定表名
    • MyBatisPlus默认开启转驼峰

用法2:Spring + MyBatis + MP

  1. 引入相关依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- mybatis-plus插件依赖 -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.1.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
    </dependency>
    <!-- 连接池 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.3</version>
    </dependency>
  2. 配置spring的核心配置文件

    • 配置数据源dataSource的bean
    • 配置sqlSessionFactory的bean为MybatisSqlSessionFactoryBean的类,指定dataSourceproperty
    • 配置mapper包扫描的bean为MapperScannerConfigurer的类,指定basePackageproperty
  3. 直接在测试类中利用@Autowired注入对应mapper的bean即可使用

用法3:SpringBoot + MyBatis + MP

  1. 引入起步依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.1.1</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    </dependency>
  2. 为mapper接口打上@Mapper注解

  3. 直接利用@Autowired注入mapper层接口的bean即可

    :SpringBoot使用log4j进行日志显示配置springboot核心配置文件:

    1
    2
    3
    4
    5
    logging:
    level:
    com:
    taoyyz:
    mybatisplusspringboot: debug

通用CRUD

插入

利用mapper层接口的int insert(T entity)插入数据,返回值是受影响的行数

  • 获取主键自增后的结果,只需要在实体类的主键属性打上@TableId(type = IdType.AUTO)注解即可把自增主键回写到实体对象
  • @TableFiled注解:用于指定字段的一些属性,通常有2种用法:
    • 对象中的属性名和字段名不一致的问题(非驼峰)
      • 利用@TableField("数据库端字段名")来匹配字段名到属性上
    • 对象中的属性名在表中不存在的问题
      • 利用@TableField(exist = false)来声明此属性不存在于数据库中
    • 查询时忽略此属性
      • 利用@TableField(select = false)不查询此字段
  • @TableName注解:用于指定实际数据库的表名

更新

1.根据id更新

  • 利用int updateById(T entity)更新此实体对象id对应的数据库内容

2.根据条件更新

  • 利用int update(T entity,Wrapper<T> updateWrapper)更新满足updateWrapper条件的值为此实体对象的值

    Wrapper对象可以为:

    • QueryWrapper:满足此查询条件构造器的数据会被更新为entity的属性
    • UpdateWrapper:满足此更新条件构造器的数据可以直接利用此构造器的set(R column, Object val)方法赋值,此时entity传入null也可以,在set()方法中指定更新的具体字段的值优先级更高

删除

1.根据id删除

  • 利用int deleteById(Serializable id)传入主键删除

2.根据id批量删除

  • 利用int deleteBatchIds(Collection<? extends Serializable> idList)传入需要删除的id的集合

3.根据字段键值的Map删除

  • 利用int deleteByMap(Map<String, Object> columnMap)删除满足key对应的字段的值为value的数据

4.根据条件删除

  • 利用int delete(Wrapper<T> wrapper)删除满足条件选择器的数据

查询

1.根据id查询

  • 利用T selectById(Serializable id)查询此id对应的实体对象

2.根据id批量查询

  • 利用List<T> selectBatchIds(Collection<? extends Serializable> idList)传入需要查询的id的集合

3.根据字段值的Map查询

  • 利用List<T> selectByMap(Map<String, Object> columnMap)查询满足key对应字段的值为value的数据

4.根据条件查询一条数据

  • 利用T selectOne(Wrapper<T> queryWrapper)根据条件查询一条记录,返回封装的实体对象。不止一条结果会异常

5.根据条件查询条数

  • 利用Integer selectCount(Wrapper<T> queryWrapper)根据条件查询记录的数量

5.根据条件批量查询

  • 利用List<T> selectList(Wrapper<T> queryWrapper)根据条件查询记录的集合

6.分页查询

  • 利用IPage<T> selectPage(IPage<T> page,Wrapper<T> queryWrapper)
    • 需要配置一个拦截器Bean:可以通过@Configuration所在的配置类产生PaginationInterceptor的Bean。(新版MyBatis-Plus推荐产生MyBatisPlusInterceptor的Bean并且添加一个PaginationInnerInterceptor作为InnerInterceptor)
    • 调用查询方法,传入的参数为IPage接口的实现类对象以及条件查询包装器。如list(page,wrapper)
    • 返回一个IPage分页对象
    • 通过IPage分页对象的方法可以得到总页码等数据,以及getRecords()方法得到查询的结果集合

MyBatis-Plus配置文件

基本配置

configLocation:指定mybatis核心配置文件的位置

  • SpringBoot:

    1
    2
    mybatis-plus:
    config-location: classpath:mybatis-config.xml #指定mybatis核心配置文件的位置
  • Spring:

    1
    2
    3
    4
    <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

mapperLocations:配置XML映射文件的位置

  • SpringBoot:

    1
    2
    mybatis-plus:
    mapper-locations: classpath*:mapper/*.xml #指定映射文件的位置,这里classpath*是加载所有文件
  • Spring:

    1
    2
    3
    4
    <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

type-aliases-package:配置别名扫描包

  • SpringBoot:

    1
    2
    mybatis-plus:
    type-aliases-package: com.taoyyz.mybatisplusspringboot.domain #设置别名扫描包,包下的类会被设置别名
  • Spring:

    1
    2
    3
    4
    <bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="com.taoyyz.mybatisplusspringboot.domain"/>
    </bean>

table-prefix:配置表名前缀

  • SpringBoot

    1
    2
    3
    4
    mybatis-plus:
    global-config:
    db-config:
    table-prefix: tb_ #设置表前缀,例如实体类Book对应的表会被认为是tb_book

进阶配置

mapUnderscoreToCamelCase:驼峰映射

  • 类型:boolean

  • MyBatis默认:false

  • MyBatisPlus默认:true

  • 用法:配置SpringBoot核心配置文件:

    1
    2
    3
    mybatis-plus:
    configuration:
    map-underscore-to-camel-case: true # 驼峰映射

    注意:在SpringBoot核心配置文件中不能指定configLocation,否则会冲突,建议配置MyBatis核心配置文件

cacheEnabled:设置全局映射器的缓存

  • 类型:boolean

  • 默认值:true

  • 用法:配置SpringBoot核心配置文件:

    1
    2
    3
    mybatis-plus:
    configuration:
    cache-enabled: true # 配置全局缓存

SSM整合

原始方式整合

  1. 创建数据库和表

image-20210825133345997 image-20210825133420192

  1. 创建Maven工程
  2. 导入Maven依赖项的坐标在pom.xml中
  3. 编写数据库表的实体类domain
  4. 编写操作数据库的Dao/Mapper层接口
  5. 编写操作业务逻辑的Service层接口及实现
  6. 编写Controller层
  7. 编写前端页面
  8. 测试

Spring整合MyBatis

  • 整合思路:

    image-20210825153818996
  • 将SqlSessionFactory配置到Spring容器中作为Bean,配置Spring核心配置文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!--加载properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--配置SqlSessionFactory,需要引入mybatis-spring的1.3.1版本maven依赖-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--设置数据源为bean的引用-->
    <property name="dataSource" ref="dataSource"/>
    <!--加载MyBatis核心配置文件-->
    <property name="configLocation" value="classpath:mybatis-config-spring.xml"/>
    </bean>
    <!--扫描mapper所在的包,相当于MyBatis为我们创建实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.taoyyz.mapper"/>
    </bean>
  • 踩坑1:MyBatis操作找不到Dao/Mapper层接口中的方法

    • 例如:com.taoyyz.dao.UserMapper.findAll找不到
    • 原因:resources目录下的Mapper.xml文件目录层级要和java下的Dao/Mapper接口层级相同
    • 解决方法:
      • 检查Mapper.xml的namespace命名空间属性是否跟Dao/Mapper接口全类名对应
      • 检查Mapper.xml的标签id属性是否跟Dao/Mapper接口中的方法名对应
      • 修改目录层级,使得他们被打包后可以被输出到同一目录 image-20210825164447435
  • 踩坑2:java.lang.NoClassDefFoundError: org/springframework/jdbc/datasource/TransactionAwareDataSourceProxy

    • 原因:spring-jdbc的jar包依赖没有导入: image-20210825160947082
  • 踩坑3:Method com/mchange/v2/c3p0/impl/NewProxyResultSet.isClosed()Z is abstract

    • 原因:c3p0版本太老,需要换到0.9.2之后的版本
    • 解决方法:把maven依赖的c3p0版本从0.9.1.2换到0.9.5.2
    • 注意:可能会从仓库找不到0.9.5.2版本,需要自己去Maven仓库下载jar然后手动执行mvn install安装
      • 在利用mvn install手动安装jar包时需要保证当前目录下有pom.xml文件且文件内部不能有错误
  • 踩坑4:Failed to introspect Class [com.mchange.v2.c3p0.ComboPooledDataSource] from ClassLoader [ParallelWeb

    • 原因:换到c3p0的0.9.2以上版本,内部不再集成mchange-commons-java的jar包依赖
    • 解决方法:手动引入Maven依赖:mchange-commons-java0.2.20版本
  • 配置声明式事务控制

    • 引入txaop约束

    • <!--声明式事务控制-->
      <!--平台事务管理器-->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      <!--配置事务增强-->
      <tx:advice id="txAdvice">
          <tx:attributes>
              <tx:method name="*"/>
          </tx:attributes>
      </tx:advice>
      <!--配置事务AOP织入-->
      <aop:config>
          <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.taoyyz.service.impl..*.*(..))"/>
      </aop:config>