spring详解

spring是于2003年兴起的一款轻量级的,非侵入式的IOC和AOP的一站式的java开发框架,为简化企业级应用开发而生。
轻量级的 :指的是spring核心功能的jar包不大。
非侵入式的 :业务代码不需要继承或实现spring中任何的类或接口
IOC :控制反转(Inverse of Control),以前项目都是在哪儿用到对象,在哪儿new,把生成对象的权利反转给spring框架 (让spring把对象管理起来,在哪用在哪注入)
AOP :面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
一站式框架

  • 提供核心功能,主要是IOC,创建管理对象;
  • 提供面向切面编程,增强程序扩展;
  • 对数据访问层进行了封装(重点在于事务管理)
  • 对web层进行封装,使得请求更加便捷

Spring Hello World搭建

1.Maven 导入 spring 核心基础 jar

xml 复制代码
<!-- spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

2.编写一个Admin实体类

java 复制代码
package com.ffyc.ssm.model;

public class Admin {
    private Integer id;
    private String account;

    public Admin() {
        System.out.println("admin无参构造");
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}

3.编写 spring 配置文件,在resources/spring.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" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置需要让spring进行管理的类-->
    <bean id="admin" class="com.ffyc.ssm.model.Admin"></bean>
</beans>

4.测试spring

读取spring配置文件,并对文件中配置的对象进行创建

java 复制代码
package com.ffyc.ssm.test;

public class Test1 {
    public static void main(String[] args) {
        /*以前使用对象方式,在哪儿用,在哪儿new
        new Admin();
        spring思想是,由框架统一对项目中的类进行管理(创建对象,后期增强一些功能),在需要的地方注入即可*/
        //Map(admin,new Admin)
        //ClassPathXmlApplicationContext就是spring中的实际实现者
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        //Object admin=app.getBean("admin");//不知道返回的类型,从Map中拿出new Admin对象
        Admin admin=app.getBean("admin",Admin.class);//明确类型
        System.out.println(admin);
    }
}

IOC(控制反转)

正控:若要使用某个对象,需要自己去负责对象的创建
反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程 ,也就是把创建对象的控制权反转给了 Spring 框架

**底层实现方式:**解析 xml/扫描注解标签 + 工厂模式 + 反射机制

Spring Bean 管理

1、基于 xml 配置方式

配置需要让spring进行管理的类

id="唯一的标识"

class="让spring管理的类名"

bean对象?由spring框架创建并管理的对象

  • Scope="singleton 默认值",单例的,在整个应用程序中只创建一个对象,在spring框架启动时就创建好了
  • scope="prototype",原型的,每次获取时创建一个对象,可以创建多个
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" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置需要让spring进行管理的类-->
    <bean id="admin" class="com.ffyc.ssm.model.Admin" scope="prototype"></bean>
</beans>
java 复制代码
public class Test1 {
    public static void main(String[] args) {
        //读取spring配置文件,并对文件中配置的对象进行创建
        //ClassPathXmlApplicationContext就是spring中的实际实现者
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        Admin admin1=app.getBean("admin",Admin.class);
        System.out.println(admin);
        System.out.println(admin1);
    }
}
/*
admin无参构造
admin无参构造
com.ffyc.ssm.model.Admin@685cb137
com.ffyc.ssm.model.Admin@6a41eaa2
*/

spring框架创建对象(控制反转)时,为对象的属性进行赋值操作(这个赋值操作称为依赖注入

1.属性赋值 getXXX、setXXX方法

xml 复制代码
<bean id="admin" class="com.ffyc.ssm.model.Admin" scope="prototype">
    <property name="id" value="10"></property>
    <property name="account" value="张三"></property>
</bean>
java 复制代码
public class Test1 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        System.out.println(admin);
    }
}
/*
admin无参构造
Admin{id=10, account='张三'}
*/

2.构造方法赋值

xml 复制代码
<bean id="admin" class="com.ffyc.ssm.model.Admin" scope="prototype">
    <constructor-arg name="id" value="1"></constructor-arg>
    <constructor-arg name="account" value="1"></constructor-arg>
</bean>
java 复制代码
public class Test1 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        System.out.println(admin);
    }
}
//Admin{id=1, account='1'}

在一个方法中注入另一个方法

xml 复制代码
<bean id="admin" class="com.ffyc.ssm.model.Admin" scope="prototype">
    <constructor-arg name="id" value="1"></constructor-arg>
    <constructor-arg name="account" value="1"></constructor-arg>
</bean>
<bean id="adminDao" class="com.ffyc.ssm.dao.AdminDao"></bean>
<bean id="adminService" class="com.ffyc.ssm.service.AdminService">
    <property name="adminDao" ref="adminDao"></property> <!--在adminService中注入adminDao-->
</bean>
java 复制代码
public class Test2 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        AdminService adminService=app.getBean("adminService",AdminService.class);
        adminService.save(admin);
    }
}

2、注解方式实现(建议)

1、注解需要的jar包

注解功能封装在AOP包中,导入 Spring aop jar 包即可

xml 复制代码
<!-- spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

2、开启注解扫描

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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.ffyc.ssm"> </context:component-scan>
</beans>

3、注解创建对象
有注解标签才是spring管理的对象,并创建对象

1.@Component(value = "admin") 一般用于模型类,等同于

2.@Scope(value="prototype") 原型(多次)
@Scope(value="singleton") 单例(一次)

java 复制代码
@Component(value = "admin")
@Scope(value = "singleton")

3.@Repository(value = "adminDao"),用于dao层

java 复制代码
@Repository(value = "adminDao")
public class AdminDao {
    public void saveAdmin(Admin admin){
        System.out.println("保存管理员"+admin);
    }
}

4.@Service(value = "adminService")

5.@Autowired ,spring框架提供的一种自动注入 的注解标签,可以写在字段和 setter 方法上。如果写在字段上,那么就不需要再写 setter 方法 。默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。

有两种方式去查找对象:

  • byType,去spring容器中根据当前类型搜索,@Autowired
  • byName,通过名称查找,需要结合 @Qualifier(value="adminDao") ,value值和@Repository(value = "adminDao")里面的value对应
    6.@Resource,是jdk中提供的一个注解标签
  • byType,去spring器中根据当前类型搜索,@Resource
  • byName,通过名称查找,@Resource(name = "adminDao")
    Autowired(required=true),注入时,对象值不能为空,为空报错
java 复制代码
@Service(value = "adminService")
public class AdminService {
    /*@Autowired
     @Qualifier(value = "adminDao")
    AdminDao adminDao;//依赖注入*/
    
    //@Resource
    @Resource(name = "adminDao")
    AdminDao adminDao;

    public void save(Admin admin){
        adminDao.saveAdmin(admin);
    }
}
java 复制代码
public class Test3 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        AdminService adminService=app.getBean("adminService",AdminService.class);
        adminService.save(admin);
    }
}

3、注解与 XML 的对比

注解优点: 方便、直观、高效(代码少,没有配置文件的书写那么复杂)。

**注解缺点:**以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的 (通过maven把项目重新编译compile一下)。
xml 优点是: 配置和代码是分离的,在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

**xml 缺点是:**编写麻烦,效率低,大型项目过于复杂。

Spring JDBC

spring框架中的JDBC功能

  1. jdbc封装: JdbcTemplate(了解即可 后面常用mybatis),事务管理(重点)
  2. ioc实际的应用
    Spring 是个一站式框架:Spring 自身也提供了控制层的 SpringMVC 和 持久层的 Spring JdbcTemplate。
    1.下载 Spring JdbcTemplate 的 jar 包
    导入jdbc模块依赖
xml 复制代码
<!-- spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

导入mysql

xml 复制代码
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>

导入阿里巴巴提供的数据源管理组件,数据源组件封装了连接数据库,还有数据库连接池功能

druid(德鲁伊),常用的数据库链接池组件:dbcp、c3p0

xml 复制代码
<!-- 阿里的数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>

2.config.properties

xml 复制代码
classDriverName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/ssmdb?serverTimezone=Asia/Shanghai
uname=root
pwd=123456

3.db.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置jdbc功能-->
    <!--负责加载config.properties文件-->
    <context:property-placeholder location="classpath:config.properties"></context:property-placeholder>
    <!--spring统一管理数据库链接对象,创建对象并赋值-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${classDriverName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${uname}"></property>
        <property name="password" value="${pwd}"></property>
        <property name="initialSize" value="5"></property>
        <property name="maxActive" value="10"></property>
    </bean>
    <!--创建spring框架封装的jdbc功能的实现类-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>  <!--注入数据源对象-->
    </bean>
    <!--以后引入mybatis,需要数据源对象,直接注入即可-->
</beans>

4.spring.xml注解db.xml

xml 复制代码
<import resource="classpath:db.xml"></import> <!--指的是target/classes下的-->

增删改用update

java 复制代码
@Repository(value = "adminDao")
public class AdminDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
    
   public void saveAdmin(Admin admin){
        //jdbcTemplate.update("insert into admin(account,password)value(?,?)","aaa","111");
        jdbcTemplate.update("delete from admin where id =?","5");
    }
}

查询,返回单个结果

java 复制代码
public void saveAdmin(Admin admin){
    Admin admin2=jdbcTemplate.queryForObject("select * from admin where id=?",
            new RowMapper<Admin>() {
                @Override
                public Admin mapRow(ResultSet resultSet, int i) throws SQLException {
                    Admin admin1=new Admin();
                    admin1.setId(resultSet.getInt("id"));
                    admin1.setAccount(resultSet.getString("account"));
                    return admin1;
                }
            },1);
    System.out.println(admin2);
}

查询,返回多个结果

java 复制代码
public void saveAdmin(Admin admin){
    List<Admin> adminList=jdbcTemplate.query("select * from admin",
            new RowMapper<Admin>() {
                @Override
                public Admin mapRow(ResultSet resultSet, int i) throws SQLException {
                    Admin admin1=new Admin();
                    admin1.setId(resultSet.getInt("id"));
                    admin1.setAccount(resultSet.getString("account"));
                    return admin1;
                }
            });
    System.out.println(adminList);
}

AOP

不使用 AOP 的开发方式

案例:

先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要写两个非业务方法。非业务方法也称为交叉业务逻辑:

  • doTransaction():用于事务处理
  • doLog():用于日志处理
    然后,再使接口方法调用它们。接口方法也称为主业务逻辑.

    在方法中想要实现额外功能时,需要在业务代码中显示调用.

    即使抽取一个工具类,将这些方法封装起来,依然还是需要显示调用.

AOP概述

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,AOP 可以**对业务逻辑和非业务逻辑进行隔离,从而使得各部分之间的耦合度降低,提高程序的可重用性,**同时提高了开发的效率。将程序中的一些非业务代码进行提取,在不需要修改原来代码的情况下,为程序添加额外的功能

AOP思想不是spring框架特有的,只是spring框架引入使用了这一思想。
面向切面编程的好处就是: 减少重复,专注业务;

注意:面向切面编程只是面向对象编程的一种补充。
核心原理: 使用动态代理的方式在执行方法前后或者出现异常的时候做加入相关的逻辑.
非业务代码使用案例:

  • 事务处理:开启事务,关闭事务,出现异常后回滚事务
  • 验证权限:在执行方法前,判断是否具有权限
  • 打印日志:在执行前进行日志处
  • 统一异常处理

是怎么做到的?底层实现:使用的是动态代理模式 。是通过一个代理对象来实现对非业务代码进行调用的。告诉代理对象,调用哪个方法时,让代理对象去帮助我们调用哪个方法

代理对象(4s 中介 手机店)

AOP的基本概念

**连接点(Joinpoint):**类中可以被增强的方法

**切入点(pointcut):**类中有很多方法可以被增强,但实际中只有 add 和 update被增了,那么 add 和 update 方法就被称为切入点(类中实际被增强的方法)
通知(Advice): 指提取的非业务的功能。通知分为:前置通知、后置通知、异常通知、最终通知、环绕通知

**切面(Aspect):**把通知添加到切入点的整个过程
目标(Target): 代理的目标对象(连接点,切入点所在类AdminDao)
代理(Proxy): 向目标对象应用通知时创建的代理对象

springAOP实现

有两种实现方式:1.xml配置方式 2.注解方式

1、xml配置方式

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。

AspectJ 是一个基于 Java 语言的 AOP 框架,它提供了强大的 AOP 功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,spring中引入了一个AspectJ的aop框架.

下载 AOP 相关 jar

xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>
xml 复制代码
<!--spring.xml中-->
<import resource="classpath:aopDemo.xml"></import>

基于 aspectj 的 xml 配置方式实现。

先把类交给spring管理,这样spring生成的代理对象才可以调用

xml 复制代码
<!--aopDemo.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--把非业务代码(通知) 交给spring框架管理,以后要用到这个类的方法,就把myutil注入-->
    <bean id="myutil" class="com.ffyc.ssm.util.MyUtil"></bean>

    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="saveAdmin" expression="execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))"/>
        <!--将通知与切入点进行配置,生成的代理对象就知道如何调用-->
        <aop:aspect ref="myutil">
            <!--前置通知:先输出通知在执行方法-->
            <aop:before method="printLog" pointcut-ref="saveAdmin"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
java 复制代码
package com.ffyc.ssm.util;

   /* 提取的非业务的功能称为通知
    通知又分为5种: 前置通知、后置通知、异常通知、最终通知、环绕通知*/
public class MyUtil {
    public void printLog(){
        System.out.println("打印日志");
    }
    public void commit(){
        System.out.println("提取事务");
    }
    public void exceptionAdvice(Throwable e){
        System.out.println("异常通知"+e.getMessage());
    }
}
java 复制代码
package com.ffyc.ssm.dao;

@Repository(value = "adminDao")
//目标类
public class AdminDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    /*连接点:类中可以被增强的方法,称为连接点
    切入点: 类中实际被增强的方法,横切面切入的方法*/

    public void saveAdmin(Admin admin){
        System.out.println("保存管理员");
    }

    public void updateAdmin(Admin admin){
        System.out.println("修改管理员");
    }
}
java 复制代码
public class Test3 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        AdminService adminService=app.getBean("adminService",AdminService.class);
        adminService.save(admin);
    }
}

通知又分为5种:

前置通知:业务方法执行前调用

后置通知(after-returning):业务方法执行后调用,当方法出现异常不执行

异常通知 (after-throwing):业务方法出现异常时调用

最终通知(after):业务方法执行后调用,当方法出现异常也会执行
环绕通知,环绕通知包含前四种通知

java 复制代码
package com.ffyc.ssm.util;

public class MyUtil {
    /*ProceedingJoinPoint需要调用的方法*/
    public void aroundAdvice(ProceedingJoinPoint point){
        try {
            System.out.println("前置通知");
            point.proceed();//调用我们自己的业务方法
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("异常通知"+throwable.getMessage());
        }
        System.out.println("最终通知");
    }
}
xml 复制代码
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="saveAdmin" expression="execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))"/>
    <!--将通知与切入点进行配置,生成的代理对象就知道如何调用-->
    <aop:aspect ref="myutil">
        <aop:around method="aroundAdvice" pointcut-ref="saveAdmin"></aop:around>
    </aop:aspect>
</aop:config>
2、注解方式的实现

1.开启自动代理

xml 复制代码
<!--spring.xml中开启自动代理-->
<aop:aspectj-autoproxy />

2.@Component //让spring管理生成对象

@Aspect //表明装有通知的类/切面

java 复制代码
package com.ffyc.ssm.util;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component //让spring管理生成对象
@Aspect  //表明装有通知的类/切面
public class MyUtil {
    //@Before("execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))")
    //@AfterReturning("execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))")
    //@After("execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))")
    public void printLog(){
        System.out.println("打印日志");
    }
    public void commit(){
        System.out.println("提取事务");
    }
    //@AfterThrowing(value = "execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))",throwing = "e")
    public void exceptionAdvice(Throwable e){
        System.out.println("异常通知"+e.getMessage());
    }
    
    /*ProceedingJoinPoint需要调用的方法*/
    @Around("execution(* com.ffyc.ssm.dao.AdminDao.saveAdmin(..))")
    public void aroundAdvice(ProceedingJoinPoint point){
        try {
            System.out.println("前置通知");
            point.proceed();//调用我们自己的业务方法
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("异常通知"+throwable.getMessage());
        }
        System.out.println("最终通知");
    }
}

spring事务管理

jdbc自动事务提交,mybatis里面,事务默认不自动提交,需要我们在程序中手动提交 sqlsession.commit();spring框架把提交事务的功能帮助我们管理起来了,封装好了。

事物可以看做是由对数据库若干操作组成的一个单元。案例:转账

关系型数据库事务基本特征:

1.原子性:保障一个事务中的多条sql,要么都执行,要么都不执行

2.隔离性

3.持久性

4.一致性

我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常, 异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。

事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态,这些操作要么都完成,要么都取消 ,从而保证数据满足一致性的要求

Spring 中的事务管理分两种:

1.编程式事务管理:在代码中写,实际开发中使用的少。这种方式需要注入一个事务管理对象TransactionTemplate,然后在我们代码中需要提交事务或回滚事务时自己写代码实现。

java 复制代码
public class AdminDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionTemplate transactionTemplate;
    
    public void saveAdmin(Admin admin){
        System.out.println("保存管理员");
        transactionTemplate.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into admin(account,password)value(?,?)","aa","aa");
                int i=10/0;
                jdbcTemplate.update("insert into admin(account,password)value(?,?)","bb","bb");
                return null;
            }
        });
    }
}

2.声明式事务管理:使用注解标签标实现,底层实现原理就是AOP的思想,本质是对方法前后进行拦截 ,是方法级别的,在执行某个方法时,为方法添加额外的事务管理功能

Spring 声明式事物管理方式有两种:

  • 基于 xml 配置
  • 基于注解实现
    Spring 针对不同的 dao 框架,提供了不同的实现类,Jdbc,mybatis事物管理实现类是 DataSourceTransactionManager.
    (1)jar包
xml 复制代码
<!-- spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

(2)配置事物管理器、注解方式

xml 复制代码
<!-- 配置spring事务管理类, 并注入数据源。db.xml -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--开启注解事务管理。db.xml-->
<tx:annotation-driven transaction-manager="transactionManager"/>

(3)@Transactional 注解标签

java 复制代码
public class AdminDao {
@Autowired
JdbcTemplate jdbcTemplate;

@Transactional
public void saveAdmin(Admin admin){
    System.out.println("保存管理员");
    jdbcTemplate.update("insert into admin(account,password)value(?,?)","aa","aa");
    int i=10/0;
    jdbcTemplate.update("insert into admin(account,password)value(?,?)","bb","bb");
}

@Transactional标签的用法:

  • 一般把事务管理的注解标签在service的方法中来进行控制。因为dao层是接口,一个接口对应一个sql,在一个业务逻辑中,可能要执行多个dao层的sql,需要把这多个sql,放在同一个事务管理中进行
  • @Transactional可以添加在service层中上,类中所有的方法都会添加事务管理功能
  • @Transactional如果只添加在某个方法 上,那么表示此方法在事务管理中进行
    案例:转钱
    这个案例中如果service层没有@Transactional,而dao有@Transactional,可能会出现问题。加减是独立的的事务,不是同一个事务
java 复制代码
package com.ffyc.ssm.service;

@Service(value = "adminService")
public class AdminService {
//对于service层来讲,转账是一个操作
@Transactional
    public void zhuanzhang(){
        int id1=1;
        int id2=7;
        int money=500;
        //调用不同的层的方法
        adminDao.jian(id1,money);
        System.out.println(10/0);
        adminDao.jia(id2,money);
    }
}
java 复制代码
package com.ffyc.ssm.dao;

@Repository(value = "adminDao")
//目标类
public class AdminDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
    //减钱
    public void jian(int id1,int m){
        jdbcTemplate.update("update admin set money=money-"+m+" where id=?",id1);
    }
    //加钱
    public void jia(int id2,int m){
        jdbcTemplate.update("update admin set money=money+"+m+" where id=?",id2);
    }
}
java 复制代码
package com.ffyc.ssm.test;

public class Test3 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        AdminService adminService=app.getBean("adminService",AdminService.class);
        adminService.zhuanzhang();
    }
}

@Transactional 声明式注解事务管理,在以下情况会失效:(重点)

1.修饰非public的方法,底层权限只针对public修饰的方法

2.方法中的异常被catch捕获处理了

java 复制代码
@Transactional
public void zhuanzhang(){
    int id1=1;
    int id2=7;
    int money=500;
    //调用不同的层的方法
    adminDao.jian(id1,money);
    try{
        System.out.println(10/0);
    }catch (Exception e){
        System.out.println("算术异常");
    }
    adminDao.jia(id2,money);
}
//方法中出现异常,但是却正常转账了,是不正常的

3.出现编译期异常,事务不生效

默认情况下,事务只对运行期异常进行生效@Transactional(rollbackFor = RuntimeException.class)

我们可以把其修改为**@Transactional(rollbackFor = Exception.class),这样就可以处理任意的异常**

java 复制代码
@Transactional(rollbackFor = Exception.class)//出现编译期异常,事务也会生效
public void zhuanzhang() throws UnsupportedEncodingException {
    int id1=1;
    int id2=7;
    int money=500;
    adminDao.jian(id1,money);
    "abc".getBytes("utf--8");
    adminDao.jia(id2,money);
}

4.@Transactional事务传播行为设置错误

5.数据库引擎不支持事务。数据库引擎是mysql底层具体的一种数据处理实现的机制

常用的两个引擎:innodb(支持事务功能),myisam(不支持事务)

6.同一个类中,使用非代理对象调用一个有事务的方法

在一个非事务方法中使用this(原始的对象==自己new出来的对象)

java 复制代码
public class AdminService {
   
    //this表示自己没有被spring进行任何增强的 最原始的new出来的对象
    //如果是spring生成的对象,spring会根据这个方法上所添加的注解标签来做一些额外的增强,会返回一个代理对象
    @Transactional(rollbackFor = Exception.class)//加上这个就好了
    public void test() throws UnsupportedEncodingException {
        this.zhuanzhang();
    }

	@Transactional(rollbackFor = Exception.class)
    public void zhuanzhang() throws UnsupportedEncodingException {
        int id1=1;
        int id2=7;
        int money=500;
        //调用不同的层的方法
        adminDao.jian(id1,money);
        "abc".getBytes("utf--8");
        adminDao.jia(id2,money);
    }
}
java 复制代码
public class Test3 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        AdminService adminService=app.getBean("adminService",AdminService.class);
        adminService.test();
    }
}

事务传播行为

事务功能本质上属于mysql数据库

spring事务管理:指的是spring框架可以帮助我们进行事务开启、提交、回滚
什么叫事务传播行为?

即然是传播,那么至少有两个方法才可以发生传播,单体不存在传播这个行为。

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。事务传播行为是 Spring 框架独有的事务增强特性,不属于事务实际提供者mysql.

例如:A事务方法调用B事务方法时,B事务是一个独立的事务呢(独立的,表示B出现了问题,不影响A)?还是B事务合并到A事务中呢(B影响A,A影响B)?这就是由B的事务传播行为决定的

要么多个事务嵌套在一个中,要么多个事务相互独立运行。

Spring 定义了七种传播行为:

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果A有事务, B加入到A事务中;如果A没有事务,B就新建一个事务,与A没有关系。(常见)
PROPAGATION_REQUIRES_NEW 无论A是否有事务,B都会创建新的事务(独立的)
PROPAGATION_SUPPORTS 支持A事务;如果A没有事务,B就以非事务方式执行
PROPAGATION_NEVER B以非事务方式执行;如果A存在事务,则抛出异常。
PROPAGATION_MANDATORY 使用A事务,如果A没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务, 则执行与 PROPAGATION_REQUIRED 类似的操作。

注意:事务传播行为不能是同一个类中的方法相互调用,必须是一个类中的方法调用另一个类中的方法

案例:保存管理员时,需要向日志表里插入一条记录

1、PROPAGATION_REQUIRED

A事务方法调用B方法,传播行为REQUIRED。如果A方法有事务,B加入到A方法事务中,任意一方出现异常都会回滚;A方法没有事务,B方法会自己创建一个事务,与A没有关系。

AdminService中save的调用LogService中的saveLog方法,两个方法都有事务,调用saveLog时就加入到save中(合二为一),如果出现异常两者SQL都不会执行

java 复制代码
@Service(value = "adminService")
public class AdminService {
    @Autowired
    AdminDao adminDao;

    @Autowired
    LogService logService;//AdminService调用LogService
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Admin admin){
        adminDao.saveAdmin(admin);
        logService.saveLog();//save调用saveLog方法
        System.out.println(10/0);
    }
}
java 复制代码
@Repository(value = "adminDao")
public class AdminDao {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public void saveAdmin(Admin admin){
        System.out.println("保存管理员");
        jdbcTemplate.update("insert into admin(account,password)value(?,?)","ff","ff");
    }
}
java 复制代码
@Service(value = "logService")
public class LogService {
    @Autowired
    LogDao logDao;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveLog(){
        logDao.saveLog();
    }
}
java 复制代码
@Repository(value = "logDao")
public class LogDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    public void saveLog(){
        jdbcTemplate.update("insert into log(oper_time)value(now())");
    }
}
java 复制代码
public class Test3 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        ClassPathXmlApplicationContext app=new ClassPathXmlApplicationContext("spring.xml");
        Admin admin=app.getBean("admin",Admin.class);
        AdminService adminService=app.getBean("adminService",AdminService.class);
        adminService.save(admin);
    }
}

save没有事务,saveLog有事务,此时saveLog会新建一个事务,与save没有关系。保存管理员正常执行。结果:保存管理员也可以打印日志

java 复制代码
@Service(value = "adminService")
public class AdminService {
    @Autowired
    AdminDao adminDao;

    @Autowired
    LogService logService;//AdminService调用LogService
    
    public void save(Admin admin){
        adminDao.saveAdmin(admin);//正常执行
        logService.saveLog();//save调用saveLog方法,新建一个事务
        System.out.println(10/0);
    }
}
java 复制代码
@Service(value = "logService")
public class LogService {
    @Autowired
    LogDao logDao;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveLog(){
        logDao.saveLog();
    }
}

save没有事务,saveLog有事务,此时saveLog会新建一个事务,异常在saveLog中,保存管理员正常执行,而保存日志有异常而不会执行

java 复制代码
@Service(value = "adminService")
public class AdminService {
    @Autowired
    AdminDao adminDao;

    @Autowired
    LogService logService;//AdminService调用LogService
    
    public void save(Admin admin){
        adminDao.saveAdmin(admin);
        logService.saveLog();//save调用saveLog方法
    }
}
java 复制代码
@Service(value = "logService")
public class LogService {
    @Autowired
    LogDao logDao;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveLog(){
        logDao.saveLog();
        System.out.println(10/0);
    }
}

2、PROPAGATION_REQUIRES_NEW

无论A是否有事务,B都会创建新的事务(独立的)

两者是独立的事务,异常在save中,保存管理员不执行,保存日志执行

java 复制代码
    @Autowired
    AdminDao adminDao;

    @Autowired
    LogService logService;//AdminService调用LogService

    public void save(Admin admin){
        adminDao.saveAdmin(admin);
        logService.saveLog();
        System.out.println(10/0);
    }
}
java 复制代码
@Service(value = "logService")
public class LogService {
    @Autowired
    LogDao logDao;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(){
        logDao.saveLog();
    }
}

3、PROPAGATION_NEVER

B以非事务方式执行;如果A存在事务,则抛出异常。

saveLog在非事务的方法中执行,如果save中有事务,则会抛出异常

java 复制代码
public class AdminService {
    @Autowired
    AdminDao adminDao;

    @Autowired
    LogService logService;//AdminService调用LogService
	@Transactional
    public void save(Admin admin){
        adminDao.saveAdmin(admin);
        logService.saveLog();
    }
}
java 复制代码
@Service(value = "logService")
public class LogService {
    @Autowired
    LogDao logDao;

    @Transactional(propagation = Propagation.NEVER)
    public void saveLog(){
        logDao.saveLog();
    }
}
相关推荐
一颗知足的心7 分钟前
Go语言之路————指针、结构体、方法
开发语言·后端·golang
北执南念15 分钟前
项目代码生成工具
java
中国lanwp21 分钟前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
苹果酱05671 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
Java致死1 小时前
单例设计模式
java·单例模式·设计模式
胡子发芽1 小时前
请详细解释Java中的线程池(ThreadPoolExecutor)的工作原理,并说明如何自定义线程池的拒绝策略
java
沫夕残雪1 小时前
Tomcat的安装与配置
java·tomcat
Rabbb2 小时前
C# JSON属性排序、比较 Newtonsoft.Json
后端
蓝易云2 小时前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
胡子发芽2 小时前
请解释Java中的NIO(New I/O)与传统I/O的区别,并说明NIO中的关键组件及其作用
java