Spring 核心技术总结:IOC、AOP、事务管理与 MyBatis 整合实战

Spring 核心技术总结:IOC、AOP、事务管理与 MyBatis 整合实战

一、Spring 概述

1.1 Spring 介绍

Spring 是轻量级 Java EE 应用开源框架(全栈式开发框架,官网: http://spring.io ),由 Rod Johnson 创建,旨在解决企业级编程开发的复杂性。

1.2 Spring 的优点

  1. IOC:解决传统 Web 开发中硬编码所造成的程序耦合(控制反转)
  2. AOP:在运行期间不修改源代码对程序进行增强(切面编程)
  3. 粘合剂:除自身功能外,还可以整合其他技术和框架

1.3 Spring 的体系结构

Spring 框架按功能分为五大模块:

模块分类核心功能
Core Container(核心容器)提供 IOC 和依赖注入特性,是框架最基础部分
AOP、Aspects、Instrumentation(检测)、Messaging(消息处理)提供面向切面编程实现、检测、消息处理等
Data Access/Integration(数据访问与集成)简化持久层操作
Web提供 Spring MVC 框架及与 Servlet、WebSocket 的集成
Test方便程序测试

1.4 Spring 的发展历程

  • 1997 年 IBM 提出了 EJB 的思想
  • 1998 年,SUN 制定开发标准规范 EJB1.0
  • 1999 年,EJB1.1 发布
  • 2001 年,EJB2.0 发布
  • 2003 年,EJB2.1 发布Rod Johnson(spring 之父)
    • Expert One-to-One J2EE Design and Development(2002):阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
    • Expert One-to-One J2EE Development without EJB(2004):阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
  • 2006 年,EJB3.0 发布
  • 2017 年 9 月发布了 Spring 的最新版本 Spring5.0 通用版
  • ......

二、Spring IOC(重点)

2.1 程序的耦合

  • 耦合:对象之间的依赖关系,耦合越高,维护成本越高
  • 传统开发耦合案例:没有引入 IOC 容器时系统的 Web 层、业务层、持久层存在耦合
/** * 持久层实现类 */ public class UserDaoImpl implements UserDao { @Override public void addUser(){ System.out.println("insert into tb_user......"); } } 
/** * 业务层实现类 */ public class UserServiceImpl implements UserService { //硬编码:此处有依赖关系 private UserDao userDao = new UserDaoImpl(); public void addUser(){ userDao.addUser(); } } 
/** * 模拟表现层 */ public class Client { public static void main(String[] args) { //硬编码:此处有依赖关系 UserService userService = new UserServiceImpl(); userService.addUser(); } } 
  • 问题分析:service 层依赖 dao 层实现类,若修改 dao 实现类或缺失 dao 实现类,编译失败

    2.2 工厂模式的 IOC 解决程序耦合

    2.2.1 什么是 IOC

    • IOC (Inverse of Control) 即控制反转:控制是控制对象的创建;正转是自己创建依赖对象;反转是由IOC 工厂来创建依赖对象;
    • IOC (工厂模式) 解耦 步骤
      1. 把所有dao 和 service 对象使用配置文件配置起来
      2. 当服务器启动时读取配置文件
      3. 通过反射创建对象保存在容器(Map)中
      4. 使用时,直接从工厂拿

    IOC 方式:被动从 IOC 工厂获取依赖对象。

    这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。

    传统方式:主动 new 创建依赖对象。

    2.2.2 工厂模式的 IOC 解耦

    • 案例一
    /** * bean工厂 */ public class BeanFactory_v1 { /** * 获得UserServiceImpl对象 * @return */ public static UserService getUserService(){ return new UserServiceImpl(); } /** * 获得UserDaoImpl对象 * @return */ public static UserDao getUserDao(){ return new UserDaoImpl(); } } 

    问题:我们在开发中会有很多个 service 和 dao,此时工厂类就要添加无数个方法。

    • 案例二

    配置文件(bean.properties):

    properties

    #1、配置要使用的dao和service UserDao=com.hg.dao.UserDaoImpl UserService=com.hg.service.UserServiceImpl 

    工厂类:

    /** * bean工厂 */ public class BeanFactory_v2 { private static Properties prop = new Properties(); /** * 根据全类名获取bean对象 * @param beanName * @return * @throws ClassNotFoundException */ public static Object getBean(String beanName) { try { //不能使用:web工程发布后没有src目录 //InputStream is = new FileInputStream("src/bean.properties"); InputStream is = BeanFactory_v2.class.getClassLoader() .getResourceAsStream("bean.properties"); prop.load(is); return Class.forName(prop.getProperty(beanName)).newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { System.out.println(prop.get("UserService")); System.out.println(getBean("UserService")); } } 

    业务层实现类:

    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao = (UserDao) BeanFactory.getBean("UserDao"); public void addUser(){ userDao.addUser(); } } 

    测试类:

    运行

    /** * 模拟表现层 */ public class Client { public static void main(String[] args) { //直接引用接口实现类 for (int i = 0; i < 5; i++) { UserService userService = (UserService)BeanFactory_v2.getBean("UserService"); System.out.println(userService); } } } 

    问题

    1. 每次都会创建新的对象
    2. 程序运行时才创建对象 (读取配置文件)
    • 案例三
    package com.hg.factory; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; /** * bean工厂 */ public class BeanFactory_v3 { //定义一个容器,用于存放对象 private static Map<String, Object> beans = new HashMap<>(); /** * 加载配置文件 */ static { try { //2、读取配置文件 //不能使用:web工程发布后没有src目录 //InputStream is = new FileInputStream("src/bean.properties"); InputStream is = BeanFactory_v3.class.getClassLoader() .getResourceAsStream("bean.properties"); //3、通过反射创建对象,把对象存到容器中 Properties prop = new Properties(); prop.load(is); Set<Map.Entry<Object, Object>> entrySet = prop.entrySet(); for (Map.Entry<Object, Object> entry : entrySet) { String key = entry.getKey().toString(); String beanName = entry.getValue().toString(); Object value = Class.forName(beanName).newInstance(); beans.put(key, value); } } catch (Exception e) { e.printStackTrace(); } } /** * 4、在使用的时候,直接从工厂拿 * @param beanName * @return */ public static Object getBean(String beanName) { try { return beans.get(beanName); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { System.out.println(getBean("UserService")); } } 

    2.3 Spring 的 IOC 解决程序耦合

    2.3.1 创建工程

    2.3.1.1 pom.xml

    xml

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hg</groupId> <artifactId>Spring_IOC_Xml</artifactId> <version>1.0-SNAPSHOT</version> <properties> <!-- 项目源码及编译输出的编码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- 项目编译JDK版本 --> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!-- Spring常用依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> </dependencies> </project> 
    注意:Jar 包彼此存在依赖,只需引入最外层 Jar 即可由 Maven 自动将相关依赖 Jar 引入到项目中。
    Spring 常用功能的 Jar 包依赖关系

    核心容器模块说明

    • spring-beans + spring-core:提供 IOC/DI,BeanFactory(延迟加载)
    • spring-context:扩展 BeanFactory,ApplicationContext(立即加载)
    • spring-expression:Spring 表达式语言
    2.3.1.2 dao
    /** * 持久层实现类 */ public class UserDaoImpl implements UserDao { @Override public void addUser(){ System.out.println("insert into tb_user......"); } } 
    2.3.1.3 service
    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { //此处有依赖关系 private UserDao userDao = new UserDaoImpl(); @Override public void addUser(){ userDao.addUser(); } } 

    2.3.2 IOC

    2.3.2.1 applicationContext.xml

    xml

    <?xml version="1.0" encoding="UTF-8"?> <!--1、注意:要导入schema约束--> <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"> <!-- 2、<bean>标签作用:通过反射创建对象并交给spring的ioc容器去管理 id:给对象在容器中提供一个唯一标识。用于获取对象 class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数 --> <bean></bean> <bean></bean> </beans>
    注意:命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml
    2.3.2.2 测试

    运行

    /** * 模拟表现层 */ public class Client { public static void main(String[] args) { //1.使用ApplicationContext接口,就是在获取spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.根据bean的id获取对象 UserDao userDao = (UserDao) ac.getBean("userDao"); System.out.println(userDao); UserService userService = (UserService) ac.getBean("userService"); System.out.println(userService); userService.addUser(); } } 
    • 问题:service 层仍然耦合

    2.3.3 DI(构造,set,自动注入)

    概述:DI(Dependency Injection)依赖注入:将依赖对象(userDao)从容器中拿出来赋值给调用者(userService)

    2.3.3.1 构造函数注入

    使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。具体代码如下:

    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; private String name; private Integer age; public UserServiceImpl(UserDao userDao, String name, Integer age) { this.userDao = userDao; this.name = name; this.age = age; } public void addUser(){ System.out.println(name+","+age); userDao.addUser(); } } 

    xml

    ​ <?xml version="1.0" encoding="UTF-8"?> <!--1、注意:要导入schema约束--> <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"> <!--2、把对象交给spring来创建--> <bean></bean> <bean> <!-- 要求:类中需要提供一个对应参数列表的构造函数。 标签:constructor-arg ==给谁赋值:== index:指定参数在构造函数参数列表的索引位置 name:指定参数在构造函数中的名称 ==赋什么值:== value:它能赋的值是基本数据类型和String类型 ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean --> <constructor-arg name="userDao" ref="userDao"></constructor-arg> <constructor-arg name="name" value="张三"></constructor-arg> <constructor-arg index="2" value="18"></constructor-arg> </bean> </beans> ​
    2.3.3.2 set 方法注入

    在类中提供需要注入成员的 set 方法。具体代码如下:

    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; private String name; private Integer age; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void addUser(){ System.out.println(name+","+age); userDao.addUser(); } } 

    xml

    <?xml version="1.0" encoding="UTF-8"?> <!--1、注意:要导入schema约束--> <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"> <!--2、把对象交给spring来创建--> <bean></bean> <bean> <!-- 要求:必须提供set方法 标签:property ==给谁赋值:== name:找的是类中set方法后面的部分 ==赋什么值:== value:它能赋的值是基本数据类型和String类型 ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean --> <property name="userDao" ref="userDao"></property> <property name="name" value="张三"></property> <property name="age" value="18"></property> </bean> </beans> 
    2.3.3.3 自动注入

    不用在配置中 指定为哪个属性赋值,由 spring 自动根据某个 "原则" ,在工厂中查找一个 bean 并为属性注入值。具体代码如下:

    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void addUser(){ userDao.addUser(); } } 

    xml

    <?xml version="1.0" encoding="UTF-8"?> <!--1、注意:要导入schema约束--> <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"> <!--2、把对象交给spring来创建--> <bean></bean> <!--autowire="byType":按照类型自动注入值--> <bean autowire="byType"> </bean> </beans> 
    2.3.3.4 注入集合类型的属性

    给类中的集合传值,它用的也是 set 方法注入,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map。具体代码如下:

    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setMyStrs(String[] myStrs) { this.myStrs = myStrs; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void addUser(){ System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); userDao.addUser(); } } 

    xml

    <?xml version="1.0" encoding="UTF-8"?> <!--1、注意:要导入schema约束--> <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"> <!--2、把对象交给spring来创建--> <bean></bean> <bean> <property name="userDao" ref="userDao"></property> <!-- 给mySet集合注入数据 --> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <!-- 注入array数组数据 --> <property name="myArray"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <!-- 注入list集合数据 --> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <!-- 注入Map数据 --> <property name="myMap"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB" value="bbb"></entry> </map> </property> </bean> </beans> 

    2.4 Spring 中的工厂类

    2.4.1 ApplicationContext

      • ClassPathXmlApplicationContext:加载类路径下 Spring 的配置文件
      • FileSystemXmlApplicationContext:加载本地磁盘下 Spring 的配置文件

    ApplicationContext 的实现类,如下图:

    2.4.2 BeanFactory

    • 区别
      • BeanFactory:顶级接口, 在 getBean 的时候才会创建对象。
      • ApplicationContext:子接口, 读取配置文件就会创建对象。

    spring 中工厂的类结构图

    /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl() { System.out.println("UserServiceImpl对象创建了..."); } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void addUser(){ userDao.addUser(); } } 
    /** * 模拟表现层 */ public class Client { public static void main(String[] args) { new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println("Spring IOC容器创建好了"); } } 
      /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl() { System.out.println("UserServiceImpl对象创建了..."); } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void addUser(){ userDao.addUser(); } } 
      /** * 模拟表现层 */ public class Client { public static void main(String[] args) { new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); System.out.println("Spring IOC容器创建好了"); } } 

      2.5 bean 的作用域范围

      2.5.1 概述

      • 在 Spring 中,bean 作用域用于确定 bean 实例应该从哪种类型的 Spring 容器中返回给调用者。

      2.5.2 五种作用域

      • 目前 Spring Bean 的作用域或者说范围主要有五种:
      作用域说明
      singleton默认值,Bean 以单例方式存在 spring IoC 容器
      prototype多例,每次从容器中调用 Bean 时都返回一个新的实例,相当于执行 newInstance ()
      requestWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
      sessionWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
      applicationWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 ServletContext 域中
      • 单例、多例的使用场景:
        • 单例:Service、DAO、SqlSessionFactory(所有工厂)
        • 多例:Connection、SqlSession

      可以通过 <bean> 标签的scope 属性控制 bean 的作用范围,其配置方式如下所示:xml

      <bean scope="singleton"/> 

      2.6 bean 的生命周期

      2.6.1 单例 bean

      • 案例

      xml

      <bean scope="singleton" init-method="init" destroy-method="destroy"> 

      java

      /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl() { System.out.println("调用构造方法创建bean..."); } public void setUserDao(UserDao userDao) { System.out.println("调用set方法注入值..."); this.userDao = userDao; } public void init(){ System.out.println("调用init方法初始化bean..."); } public void destroy(){ System.out.println("调用destroy方法销毁bean..."); } public void addUser(){ userDao.addUser(); } } 
      /** * 模拟表现层 */ public class Client { public static void main(String[] args) { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //关闭容器 ac.close(); } } 

      生命周期

      [容器启动]---> 构造方法 (实例化)--->set 方法 (注入)--->init 方法 (初始化)--->[容器关闭]--->destroy 方法 (销毁)

      2.6.2 多例 bean

      • 案例

      xml

      <bean scope="prototype" init-method="init" destroy-method="destroy"> 

      java

      /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl() { System.out.println("调用构造方法创建bean..."); } public void setUserDao(UserDao userDao) { System.out.println("调用set方法注入值..."); this.userDao = userDao; } public void init(){ System.out.println("调用init方法初始化bean..."); } public void destroy(){ System.out.println("调用destroy方法销毁bean..."); } public void addUser(){ userDao.addUser(); } } 

      java

      /** * 模拟表现层 */ public class Client { public static void main(String[] args) { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //使用对象 ac.getBean("userService"); } } 

      生命周期

      [使用对象]----> 构造方法 (实例化)--->set 方法 (注入)--->init 方法 (初始化)--->[JVM 垃圾回收]--->destroy 方法 (销毁)

      三、基于注解的 IOC 配置

      学习基于注解的 IOC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。

      3.1 创建工程

      3.1.1 pom.xml

      xml

      <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hg</groupId> <artifactId>Spring_IOC_Annotation</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- Spring常用依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> </dependencies> </project> 

      3.1.2 dao

      java

      /** * 持久层实现类 */ public class UserDaoImpl implements UserDao { @Override public void addUser(){ System.out.println("insert into tb_user......"); } } 

      3.1.3 service

      java

      /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; @Override public void addUser(){ userDao.addUser(); } } 

      3.2 IOC

      3.2.1 applicationContext.xml

      xml

      <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 告知spring框架,在读取配置文件,创建容器时扫描包,依据注解创建对象,并存入容器中 --> <context:component-scan base-package="com.hg"></context:component-scan> </beans> 

      3.2.2 dao

      java

      @Repository public class UserDaoImpl implements UserDao { ... ... } 

      3.2.3 service

      java

      @Service public class UserServiceImpl implements UserService { ... ... } 

      3.3 DI

      3.3.1 service

      java

      @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public void addUser() { userDao.addUser(); } } 

      3.3.2 测试

      java

      /** * 模拟表现层 */ public class Client { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = ac.getBean("userServiceImpl",UserService.class); userService.addUser(); } } 

      3.4 常用注解

      以下四个注解的作用及属性都是一模一样的,都是针对一个的衍生注解只不过是提供了更加明确的语义化。

      3.4.1 用于创建对象的(IOC)

      3.4.1.1 @Controller

                用于web/controller/servlet类

      3.4.1.2 @Service

      案例(service类)

      //@Service("userService")声明bean,且id="userService" @Service//声明bean,且id="userServiceImpl" public class UserServiceImpl implements UserService { ... } 
      3.4.1.3 @Repository

      案例(dao类)

      //@Repository("userDao")声明bean,且id="userDao" @Repository//声明bean,且id="userDaoImpl" public class UserDaoImpl implements UserDao { @Override public void addUser(){ System.out.println("insert into tb_user......"); } } 
      3.4.1.4 @Component
      • 作用:把资源交给 spring 来管理,相当于:<bean>;通用。
      • 属性:value:指定 bean 的 id;如果不指定 value 属性,默认 bean 的 id 是当前类的类名,首字母小写;
      3.4.1.5 @Scope
      • 作用:指定 bean 的作用域范围。
      • 属性:value:指定范围的值,singleton prototype request session。

      3.4.2 用于属性注入的(DI)

      以下注解的作用相当于:<property>

      3.4.2.1 @Autowired
      • 作用:自动按类型注入。set 方法可以省略

      案例

      @Service public class UserServiceImpl implements UserService { @Autowired //注入类型为UserDAO的bean private UserDao userDao; public void addUser(){ userDao.addUser(); } } 
      3.4.2.2 @Resource
      • 作用:自动按名字注入。set 方法可以省略
      • 属性:name:指定 bean 的 id。

      案例

      @Service public class UserServiceImpl implements UserService { @Resource(name="userDaoImpl")//注入id=“userDaoImpl”的bean private UserDao userDao; public void addUser(){ userDao.addUser(); } } 
      3.4.2.3 @Value
      • 作用:注入基本数据类型和 String 类型数据的
      • 属性:value:用于指定值
      • 案例二

      注入属性值

      @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Value("${name}")//注入String private String name; @Value("${age}")//注入Integer private Integer age; public void addUser() { System.out.println(name+","+age); userDao.addUser(); } } 

      加载配置文件xml

      <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!--加载config.properties--> <context:property-placeholder location="config.properties"/> <context:component-scan base-package="com.hg"></context:component-scan> </beans> 

      创建 config.propertiesproperties

      name=张三 age=18 

      案例一

      @Service public class UserServiceImpl implements UserService { @Resource(name="userDaoImpl") //注入id=“userDaoImpl”的bean private UserDao userDao; @Value("张三")//注入String private String name; @Value("18")//注入Integer private Integer age; public void addUser(){ System.out.println(name+","+age); userDao.addUser(); } } 

      四、Spring AOP(重点)

      4.1 为什么要学习 AOP?

      • 问题:输出日志的逻辑无法复用

      案例:有一个接口 Service 有一个 addUser 方法,在调用 addUser (被调用时打印调用前的时间与调用后的时间),其实现为:java

      @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public void addUser(){ System.out.println("方法开始时间:"+new Date()); userDao.addUser(); System.out.println("方法结束时间:"+new Date()); } } 

      4.2 AOP 概述

      AOP:全称是Aspect Oriented Programming,即:面向切面编程。

      • AOP 本质:把程序重复代码抽取出来,需要执行时,使用动态代理,不改源码,对程序增强:权限校验,日志记录,性能监控,事务控制.
      • 解决的核心问题
        • 案例中直接在 addUser() 方法中写日志逻辑,导致日志代码与业务代码耦合,且无法在其他方法 / 类中复用;
        • AOP 可将 "打印方法执行时间" 的逻辑抽取为独立模块,按需切入到任意方法的执行前后,实现一次编写、多处复用。

      4.3 代理(Proxy)模式

      代理模式详解:静态代理、JDK 动态代理与 CGLib 动态代理-ZEEKLOG博客https://blog.ZEEKLOG.net/2403_89058622/article/details/158661084?spm=1001.2014.3001.5501

      4.4 AOP核心术语

      术语定义
      连接点(joinpoint)被拦截到的点,Spring 中仅支持方法类型的连接点,即被拦截的方法
      切入点(pointcut)对哪些连接点进行拦截的定义(要增强的方法)
      通知(advice)拦截到连接点后执行的代码,分为前置、后置、异常、最终、环绕通知五类
      切面(aspect)切入点和通知的结合,把增强应用到切点上
      引介(introduction)特殊通知,运行期为类动态添加方法 / 字段(无需修改代码)
      目标对象(Target)要代理 / 增强的目标类
      织入(weave)将通知(advice)应用到目标对象(target)的过程
      代理(Proxy)类被 AOP 增强后生成的代理类

      4.5 基于 XML 的 AOP 配置

      1. 工程搭建

      (1)pom.xml 依赖配置

      xml

      <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hg</groupId> <artifactId>Spring_AOP_Xml</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.8.RELEASE</version> </dependency> </dependencies> </project> 
      (2)核心业务代码
      • DAO 层
      /** * 持久层实现类 */ public class UserDaoImpl implements UserDao { @Override public void addUser(){ System.out.println("insert into tb_user......"); } } 
      • Service 层
      /** * 业务层实现类 */ public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao=userDao; } @Override public void addUser(){ userDao.addUser(); } } 
      (3)Spring 核心配置文件(applicationContext.xml)

      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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean></bean> <bean> <property name="userDao" ref="userDao"></property> </bean> </beans> 
      (4)测试类(web层 )

      运行

      /** * 模拟表现层 */ public class Client { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //使用对象 UserService userService = ac.getBean("userService",UserService.class); System.out.println(userService.getClass()); userService.addUser(); } } 

      2. 增强类编写

      package com.hg.advice; import org.aspectj.lang.ProceedingJoinPoint; import java.util.Date; public class MyLogAdvice { //前置通知 public void before(){ System.out.println("前置通知"); } //后置通知【try】 public void afterReturning(){ System.out.println("后置通知"); } //异常通知【catch】 public void afterThrowing(){ System.out.println("异常通知"); } //最终通知【finally】 public void after(){ System.out.println("最终通知"); } //环绕通知 public void around(ProceedingJoinPoint joinPoint){ try { System.out.println("方法执行前的环绕通知"); joinPoint.proceed(); System.out.println("方法执行后的环绕通知"); } catch (Throwable throwable) { throwable.printStackTrace(); } } } 

      3. 配置增强、切点、切面

      xml

      <!--配置增强类--> <bean></bean> <!--AOP核心配置:切点 + 切面--> <aop:config> <!--切点定义:表达式语法 execution([修饰符] 返回值类型 包名.类名.方法名(参数))--> <aop:pointcut expression="execution(* com.hg.service.*.*(..))"/> <!--切面:切点 + 通知的绑定--> <aop:aspect ref="myLogger"> <!--前置通知:切入点方法执行前执行--> <aop:before method="before" pointcut-ref="pointcut"/> <!--后置通知(try块):与异常通知互斥--> <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/> <!--异常通知(catch块):与后置通知互斥--> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/> <!--最终通知(finally块):无论是否异常都会执行--> <aop:after method="after" pointcut-ref="pointcut"/> <!--环绕通知:覆盖方法执行全流程--> <aop:around method="around" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> 

      4. 测试说明

      • Service 不实现接口时:生成 CGLIB 动态代理

      Service 实现接口时:生成 JDK 动态代理

      4.6 基于注解的 AOP 配置

      1. 工程搭建

      (1)pom.xml 依赖(同 XML 配置)
      (2)核心业务代码(添加注解)
      • DAO 层
      @Repository public class UserDaoImpl implements UserDao { @Override public void addUser(){ System.out.println("insert into tb_user......"); } } 
      • Service 层
      @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public void addUser() { userDao.addUser(); } } 
      (3)Spring 配置文件
      • applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--组件扫描--> <context:component-scan base-package="com.hg"></context:component-scan> <!--开启注解AOP支持--> <aop:aspectj-autoproxy/> </beans> 
      (4)测试类

      运行

      /** * 模拟表现层 */ public class Client { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //使用对象 UserService userService = ac.getBean("userServiceImpl",UserService.class); userService.addUser(); } } 

      2. 注解式 AOP 实现

      @Component @Aspect //声明为切面类 public class MyLogger { //前置通知 + 切入点表达式 @Before("execution(* com.hg.service.*.*(..))") public void before(){ System.out.println("方法开始时间:"+new Date()); } //最终通知 + 切入点表达式 @After("execution(* com.hg.service.*.*(..))") public void after(){ System.out.println("方法结束时间:"+new Date()); } } 

      3. AOP 常用注解说明

      注解作用
      @Aspect声明当前类为切面类
      @Before配置前置通知,指定切入点表达式
      @AfterReturning配置后置(try)通知,指定切入点表达式
      @AfterThrowing配置异常(catch)通知,指定切入点表达式
      @After配置最终(finally)通知,指定切入点表达式
      @Around配置环绕通知,指定切入点表达式

      五、Spring 整合 MyBatis

      5.1、工程搭建

      5.1.1 pom.xml 依赖配置

      xml

      <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hg</groupId> <artifactId>Spring_MyBatis</artifactId> <version>1.0-SNAPSHOT</version> <properties> <!-- 项目源码及编译输出的编码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- 项目编译JDK版本 --> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!-- Spring常用依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- MySql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- Druid连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.0</version> </dependency> <!-- MyBatis核心依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <!-- MyBatis整合Spring依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency> <!-- 日志依赖 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.26</version> </dependency> <!-- 测试依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> <build> <resources> <!-- 加载java目录下的xml文件 --> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <!-- 加载resources目录下的所有文件 --> <resource> <directory>src/main/resources</directory> </resource> </resources> </build> </project> 

      5.1.2 日志配置(log4j.properties)

      properties

      # Global logging configuration log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n 

      5.1.3 Spring 核心配置文件(初始版 applicationContext.xml)

      xml

      <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 组件扫描 --> <context:component-scan base-package="com.hg"></context:component-scan> </beans> 

      5.2、配置数据源

      5.2.1 数据库配置文件(db.properties)

      properties

      jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8 jdbc.username=root jdbc.password=1111 

      5.2.2 配置 Druid 数据源(更新 applicationContext.xml)

      xml

      <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 加载数据库配置文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置Druid数据源 --> <bean destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 组件扫描 --> <context:component-scan base-package="com.hg"></context:component-scan> </beans> 

      5.3、整合 MyBatis(完善 applicationContext.xml)

      xml

      <!-- 配置MyBatis的SqlSessionFactory --> <bean> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"></property> <!-- 配置实体类别名包 --> <property name="typeAliasesPackage" value="com.hg.pojo"></property> </bean> <!-- Mapper接口扫描器:自动生成Mapper代理类并交给Spring容器管理 --> <bean> <!-- 指定Mapper接口所在包 --> <property name="basePackage" value="com.hg.mapper"></property> <!-- 指定SqlSessionFactory名称 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> 

      5.4、功能实现与测试

      5.4.1 数据库表创建

      sql

      CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `money` float DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8; 

      5.4.2 实体类(pojo/User.java)

      java

      package com.hg.pojo; public class User { private Integer id; private String name; private Float money; public User(String name, Float money) { this.name = name; this.money = money; } public User() { } // Getter & Setter public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getMoney() { return money; } public void setMoney(Float money) { this.money = money; } } 

      5.4.3 Mapper 层

      (1)接口(mapper/UserMapper.java)

      java

      package com.hg.mapper; import com.hg.pojo.User; import org.apache.ibatis.annotations.Insert; public interface UserMapper { /** * 添加用户 * @param user 用户对象 */ public void addUser(User user); } 
      (2)映射文件(mapper/UserMapper.xml)

      xml

      <?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="com.hg.mapper.UserMapper"> <insert parameterType="User"> insert into t_user(name,money) values(#{name},#{money}) </insert> </mapper> 

      5.4.4 Service 层

      java

      package com.hg.service; import com.hg.mapper.UserMapper; import com.hg.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void addUser(User user) { userMapper.addUser(user); } } 

      5.4.5 测试类(test/ServiceTest.java)

      java

      运行

      package com.hg.test; import com.hg.pojo.User; import com.hg.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")//加载Spring配置文件 public class ServiceTest { @Autowired private UserService userService; @Test public void testAdd(){ userService.addUser(new User("张三丰",4000F)); userService.addUser(new User("宋远桥",2000F)); } } 

      六、 事务控制(重点)

      6.1. 事务介绍

      6.1.1. 什么是事务?

      当需要一次执行多条 SQL 语句时,可使用事务。通俗来讲:若这几条 SQL 语句全部执行成功,则对数据库进行一次更新;若有一条 SQL 语句执行失败,则这几条 SQL 语句全部不执行,此时需用到事务。

      《无间道》:去不了终点,回到原点

      回顾数据库事务的四大特性 ACID:

      特性简称通俗解释
      原子性A事务不可分割,要么全执行,要么全不执行
      一致性C事务执行前后,数据总状态不变
      隔离性I事务之间相互隔离,互不干扰
      持久性D事务一旦提交不可再回滚

      6.1.2. 数据库本身控制事务

      mysql

      start transaction; //1.本地数据库操作:张三减少金额 //2.本地数据库操作:李四增加金额 rollback; 或 commit; 

      6.1.3.jdbc 中使用事务

      1. 获取对数据库的连接

      2. 设置事务不自动提交(默认情况是自动提交的)

      java

      conn.setAutoCommit(false); //其中conn是第一步获取的数据库连接对象。 

      3. 把想要一次性提交的几个 sql 语句用事务进行提交

      try{ Statement stmt = null; stmt =conn.createStatement(); stmt.executeUpdate(sql1); int a=6/0; stmt.executeUpdate(Sql2); . . . conn.commit(); //使用commit提交事务 } 

      4. 捕获异常,进行数据的回滚(回滚一般写在 catch 块中)

      catch(Exception e) { ... conn.rollback(); } 

      6.2. 转账案例

      6.2.1. 拷贝上一章代码

      6.2.2. 添加转账业务

      6.2.2.1.mapper

      xml

      <?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="com.hg.mapper.UserMapper"> ... ... <!--转账--> <update> update t_user set money=money-#{money} where name=#{source} </update> <update> update t_user set money=money+#{money} where name=#{target} </update> </mapper> 

      java

      public interface UserMapper { ... ... /**扣钱*/ void updateUserOfSub(@Param("source") String source, @Param("money") Float money); /*加钱*/ void updateUserOfAdd(@Param("target") String target, @Param("money") Float money); } 
      6.2.2.2.service
      @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; /** * 转账 * @param source * @param target * @param money */ @Override public void updateUser(String source, String target, Float money) { userMapper.updateUserOfSub(source, money); int a = 6/0; userMapper.updateUserOfAdd(target, money); } } 
      6.2.2.3. 测试

      运行

      @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")//加载配置文件 public class ServiceTest { @Autowired private UserService userService; /** * 转账业务 */ @Test public void testUpdate(){ userService.updateUser("张三丰","宋远桥",1F); } } 
      • 1.此时观察数据表里面的变化情况:
      • 转账看似成功,但存在业务问题:若业务层实现类中任一环节出错,都会导致数据异常。
      • 2.先将数据恢复到转账前:
      • 故意模拟转账业务出现问题:

      再次测试:

      业务执行出错,但数据出现异常:

      原因:不满足事务的一致性(减钱的操作已提交,加钱的操作未执行 / 未提交)。

      6.3.Spring 中事务控制的 API 介绍

      • 说明:
        • JavaEE 体系分层开发,事务处理位于业务层,Spring 提供了面向业务层的事务处理解决方案。
        • Spring 框架提供了一组事务控制接口,核心接口在spring-tx.RELEASE.jar中。
        • Spring 事务控制基于 AOP 实现,支持编程式和配置式两种方式,重点学习配置式实现。

      6.3.1.PlatformTransactionManager

      • 此接口是 Spring 的事务管理器,提供了事务操作的核心方法,源代码如下:
      public interface PlatformTransactionManager { //开启事务 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; //提交事务 void commit(TransactionStatus status) throws TransactionException; //回滚事务 void rollback(TransactionStatus status) throws TransactionException; } 
      • 不同 ORM 框架对应的实现类(真正管理事务的对象):
        • DataSourceTransactionManager:适用于 Spring JDBC 或 MyBatis/iBatis 持久化场景
        • HibernateTransactionManager:适用于 Hibernate 持久化场景

      6.3.2.TransactionDefinition

      • TransactionDefinition 接口定义了事务属性相关方法,源代码如下:
      public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; int TIMEOUT_DEFAULT = -1; //传播行为 int getPropagationBehavior(); //隔离级别 int getIsolationLevel(); //超时时间 int getTimeout(); //是否只读 boolean isReadOnly(); } 

      TransactionDefinition 接口定义的事务规则包括:事务隔离级别、事务传播行为、事务超时、事务的只读、回滚规则,Spring 提供默认实现类DefaultTransactionDefinition(适用于大多数场景),也可自定义实现接口。

      6.3.2.1. 事务隔离级别

      Spring 事务隔离级别(由低到高):

      隔离级别说明
      ISOLATION_DEFAULT默认级别,使用数据库自身的事务隔离级别
      ISOLATION_READ_UNCOMMITTED最低级别,会产生脏读、不可重复读和幻像读
      ISOLATION_READ_COMMITTED避免脏读,可能出现不可重复读和幻像读(Oracle 默认级别)
      ISOLATION_REPEATABLE_READ防止脏读、不可重复读,可能出现幻像读(MySQL 默认级别)
      ISOLATION_SERIALIZABLE最高级别,事务顺序执行,避免所有并发问题(性能开销最大)

      事务并发时的安全问题:

      问题描述对应解决的隔离级别
      脏读一个事务读取到另一个事务还未提交的数据read-commited
      不可重复读一个事务内多次读取一行数据的内容,结果不一致repeatable-read
      幻读一个事务内多次读取一张表中的内容,结果不一致serialized-read
      6.3.2.2. 事务的传播行为
      • 定义:当一个事务方法被另一个事务方法调用时,该事务方法的执行策略(是否新建事务、是否加入已有事务等)。

      Spring 定义的七种传播行为:

      事务传播行为类型说明
      PROPAGATION_REQUIRED最常用:当前无事务则新建一个,有事务则加入
      PROPAGATION_SUPPORTS支持当前事务,无事务则以非事务方式执行
      PROPAGATION_MANDATORY必须在事务中执行,无事务则抛异常
      PROPAGATION_REQUIRES_NEW新建事务,若当前有事务则挂起原事务
      PROPAGATION_NOT_SUPPORTED以非事务方式执行,若当前有事务则挂起原事务
      PROPAGATION_NEVER以非事务方式执行,若当前有事务则抛异常
      PROPAGATION_NESTED有事务则在嵌套事务内执行,无事务则同 REQUIRED
      6.3.2.3. 事务超时
      • timeout:当前事务所需数据被其他事务占用时的等待时间
        • 正数(如 100):自定义等待时间(秒)
        • -1:使用数据库默认等待时间(推荐)
      6.3.2.4. 读写性
      • readonly:事务的读写属性
        • true:只读,提升查询效率(适合查询操作)
        • false:可读可写(适合增删改操作)
      6.3.2.5. 回滚规则
      • rollbackOn:自定义回滚规则(可省略,默认rollbackOn="Exception"
        • 抛出RuntimeException:自动回滚
        • 抛出CheckException:不自动回滚

      TransactionAttribute(继承 TransactionDefinition):默认实现类DefaultTransactionAttribute,指定:RuntimeException(非检查异常)触发事务回滚,CheckedException(检查异常)不触发自动回滚。

      6.3.3.TransactionStatus

      • PlatformTransactionManager.getTransaction (…) 返回该接口对象,代表新的 / 已存在的事务,源代码如下:
      public interface TransactionStatus{ boolean isNewTransaction(); // 是否是新事务 void setRollbackOnly(); // 设置事务仅回滚 boolean isRollbackOnly(); // 事务是否标记为仅回滚 } 

      6.4. 改造转账案例

      6.4.1.applicationContext.xml

      xml

      <!--配置事务管理器--> <bean> <property name="dataSource" ref="dataSource"/> </bean> <!--配置事务属性--> <bean> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/> <property name="readOnly" value="false"></property> </bean> 

      6.4.2.service

      java

      @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private TransactionDefinition txDefinition; @Autowired private PlatformTransactionManager txManager; /** * 转账 * @param source 转出人 * @param target 转入人 * @param money 金额 */ @Override public void updateUser(String source, String target, Float money) { // 获取事务状态 TransactionStatus txStatus = txManager.getTransaction(txDefinition); try { userMapper.updateUserOfSub(source, money); int a = 6/0; // 模拟异常 userMapper.updateUserOfAdd(target, money); // 提交事务 txManager.commit(txStatus); }catch (Exception e){ // 回滚事务 txManager.rollback(txStatus); e.printStackTrace(); } } } 

      6.4.3. 测试

      java

      @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")//加载配置文件 public class ServiceTest { @Autowired private UserService userService; /** * 转账业务测试 */ @Test public void testUpdate(){ userService.updateUser("张三丰","宋远桥",1F); } } 
      • 事务回滚效果:
      • 正常执行效果:
      注:当前实现虽完成了事务控制,但代码耦合度高、冗余,可通过动态代理(AOP 配置式事务)简化代码。

      6.5 动态代理控制事务

      6.5.1 工厂类创建代理对象

      我们创建一个工厂,专门用来给 Service 创建代理对象,如下:

      java

      package com.hg.factory; import com.hg.service.UserService; import com.hg.service.UserServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * bean工厂 */ @Component public class BeanFactory { @Autowired private UserService userService; @Autowired private TransactionDefinition txDefinition; @Autowired private PlatformTransactionManager txManager; /** * 获得UserServiceImpl代理对象 * @return */ public UserService getUserService() { return (UserService) Proxy.newProxyInstance( userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //开启事务 TransactionStatus txStatus = txManager.getTransaction(txDefinition); try { method.invoke(userService, args); //提交事务 txManager.commit(txStatus); } catch (Exception e) { //回滚事务 txManager.rollback(txStatus); e.printStackTrace(); } return null; } }); } } 

      6.5.2 applicationContext.xml 配置代理

      xml

      <!--配置service代理对象--> <bean factory-bean="beanFactory" factory-method="getUserService"></bean> 

      6.5.3 Service 实现类

      @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; /** * 转账 * @param source * @param target * @param money */ @Override public void updateUser(String source, String target, Float money) { userMapper.updateUserOfSub(source, money); int a = 6/0; userMapper.updateUserOfAdd(target, money); } } 

      6.5.4 测试类

      @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class ServiceTest { @Autowired @Qualifier("proxyService")//注入代理对象 private UserService userService; @Test public void testUpdate(){ userService.updateUser("张三丰","宋远桥",1F); } } 
      • 事务回滚效果:

      6.6 Spring AOP 控制事务

      6.6.1 导入 schema 约束

      xml

      <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> </beans> 

      6.6.2 配置事务增强

      xml

      <!-- 1、增强 --> <tx:advice transaction-manager="transactionManager"> <!--事务属性--> <tx:attributes> <!-- 指定方法名称:是业务核心方法 read-only:是否是只读事务。默认false,不只读。 isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 propagation:指定事务的传播行为。 timeout:指定超时时间。默认值为:-1。永不超时。 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。 省略时任何异常都回滚。 --> <tx:method name="*" read-only="false" propagation="REQUIRED"/> <tx:method name="select*" read-only="true" propagation="SUPPORTS"/> <tx:method name="get*" read-only="true" propagation="SUPPORTS"/> </tx:attributes> </tx:advice> 

      6.6.3 配置切点

      xml

      <aop:config> <!--2、切点--> <aop:pointcut expression="execution(* com.hg.service.*.*(..))"/> </aop:config> 

      6.6.4 配置切面

      xml

      <aop:config> <!--3、切面--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor> </aop:config> 

      6.6.5 删除工厂类

      删除之前编写的 BeanFactory 工厂类

      6.6.6 测试

      @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")//加载配置文件 public class ServiceTest { @Autowired private UserService userService; /** * 转账业务 */ @Test public void testUpdate(){ userService.updateUser("张三丰","宋远桥",1F); } } 
      • 事务回滚:

      6.7 事务失效的场景?【面试题】

      • 问题一:如果我们把方法名称改了,那么事务还会回滚吗?

      问题二:如果我们在service把异常捕捉了,那么事务还会回滚吗?

      修改 service:

      修改 applicationContext.xml:


      七、 基于注解的事务控制

      7.1 拷贝上一章代码

      7.2 applicationContext.xml 开启注解事务

      xml

      <!-- 开启spring对注解事务的支持,并指定对应的事务管理器 --> <tx:annotation-driven transaction-manager="transactionManager"/> 

      7.3 Service 层使用事务注解

      @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; /** * 转账 * @param source * @param target * @param money */ @Override @Transactional(readOnly=false,propagation=Propagation.REQUIRED) public void updateUser(String source, String target, Float money) { userMapper.updateUserOfSub(source, money); int a = 6/0; userMapper.updateUserOfAdd(target, money); } } 

      7.4 测试

      @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml")//加载配置文件 public class ServiceTest { @Autowired private UserService userService; /** * 转账业务 */ @Test public void testUpdate(){ userService.updateUser("张三丰","宋远桥",1F); } } 
      • 事务回滚:

      Read more

      Flutter 组件 oxy 适配鸿蒙 HarmonyOS 实战:响应式原子化状态管理,构建高性能局部刷新与副作用治理架构

      Flutter 组件 oxy 适配鸿蒙 HarmonyOS 实战:响应式原子化状态管理,构建高性能局部刷新与副作用治理架构

      欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 oxy 适配鸿蒙 HarmonyOS 实战:响应式原子化状态管理,构建高性能局部刷新与副作用治理架构 前言 在鸿蒙(OpenHarmony)生态迈向极致流畅交互、涉及大量复杂实时仪表盘及超长列表渲染的背景下,如何实现状态的高效分发与局部更新,已成为决定应用“视口丝滑度”的核心架构命题。在鸿蒙设备这类强调 AOT 极致优化与 VSync 垂直同步波动的环境下,如果应用依然依赖全局的 setState 或过于沉重的树级状态注入(如传统的 Provider 模式),由于由于底层 OID 监听与 Widget 树重建带来的 CPU 抖动,极易由于由于“无效重绘”导致页面滚动时的微小掉帧。 我们需要一种能够实现原子化追踪、具备自动依赖收集且不依赖 Widget 树继承关系的极轻量响应方案。 oxy 为 Flutter

      By Ne0inhk
      【MySQL数据库基础】(五)MySQL 数据类型深度解析:选对类型 = 性能拉满!

      【MySQL数据库基础】(五)MySQL 数据类型深度解析:选对类型 = 性能拉满!

      前言         在 MySQL 表结构设计中,数据类型的选择是最核心也最容易踩坑的环节。很多开发者随手给字段设为int、varchar(255),看似省事,实则会导致磁盘空间浪费、查询效率低下,甚至出现数据溢出、精度丢失的问题。         选对数据类型的本质,是用最小的存储空间存储符合业务需求的数据,这不仅能节省服务器资源,还能提升索引和查询的效率。本文将从 MySQL 的四大核心数据类型(数值、字符串、日期时间、枚举集合)出发,结合实战案例讲透每种类型的用法、边界、坑点,还有不同场景下的选择技巧,让你从根源上做好表结构设计!下面就让我们正式开始吧! 一、数据类型总览:四大类覆盖所有业务场景         MySQL 提供了丰富的数据类型,按用途可分为数值类型、字符串类型、日期时间类型和特殊字符串类型(ENUM/SET),不同类型对应不同的存储规则和业务场景,核心设计原则是按需选择,宁小勿大。         先看一张核心数据类型分类表,快速建立整体认知: 分类核心类型适用场景数值类型TINYINT/INT/BIGINT/FLOAT/

      By Ne0inhk
      Spring Boot 视图层与模板引擎

      Spring Boot 视图层与模板引擎

      Spring Boot 视图层与模板引擎 19.1 学习目标与重点提示 学习目标:掌握Spring Boot视图层与模板引擎的核心概念与使用方法,包括Spring Boot视图层的基本方法、Spring Boot与Thymeleaf的集成、Spring Boot与Freemarker的集成、Spring Boot与Velocity的集成、Spring Boot的静态资源管理、Spring Boot的实际应用场景,学会在实际开发中处理视图层问题。 重点:Spring Boot视图层的基本方法、Spring Boot与Thymeleaf的集成、Spring Boot与Freemarker的集成、Spring Boot与Velocity的集成、Spring Boot的静态资源管理、Spring Boot的实际应用场景。 19.2 Spring Boot视图层概述 Spring Boot视图层是指使用Spring Boot进行Web应用开发的方法。 19.2.1 视图层的定义 定义:视图层是指使用Spring Boot进行Web应用开发的方法。 作用:

      By Ne0inhk
      RUST异步并发安全与内存管理的最佳实践

      RUST异步并发安全与内存管理的最佳实践

      RUST异步并发安全与内存管理的最佳实践 一、引言 异步并发编程在提高系统性能和响应时间的同时,也带来了并发安全和内存管理的挑战。Rust语言以其独特的所有权、借用和生命周期系统,为解决这些问题提供了强大的工具。本章将深入探讨异步并发安全与内存管理的核心概念、常见问题及解决方案,并通过实战项目优化演示这些方法的应用。 二、异步并发安全的基础概念 2.1 所有权、借用与生命周期 Rust的所有权系统是其并发安全的基础。每个值都有唯一的所有者,当所有者离开作用域时,值会被自动释放。借用分为可变借用和不可变借用,同一时间只能有一个可变借用或多个不可变借用,从而避免数据竞争。生命周期则确保引用在所有者有效的时间内使用。 fnmain(){letmut s =String::from("hello");// s是所有者let r1 =&s;// 不可变借用let r2 =&s;// 不可变借用(允许)// let r3 = &mut s; // 可变借用(禁止,

      By Ne0inhk