栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SSM学习笔记1-Spring

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SSM学习笔记1-Spring

Spring学习笔记 Spring简单入门 一、概念

spring是分层的java SE/EE应用full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核。

二、优势
  1. 提供IoC容器,解耦;
  2. AOP编程的支持
  3. 声明式事务的支持
  4. 方便测试
  5. 方便集成各种框架
  6. 降低javaEE API的使用难度(封装)
  7. java源码可供学习
三、开发步骤
  1. 导入Spring开发的基本坐标
  2. 编写Dao接口和实现类
  3. 创建Spring核心配置文件
  4. 在Spring配置文件中配置UserDaoImpl
  5. 使用Spring的API获得Bean实例(getBean参数为配置的id)

各步骤示例如下:

Ioc入门案例

1.导入Spring坐标

dependency>
    org.springframework
    spring-context
    5.2.10.RELEASE

2.定义Spring管理的类(接口)

package com.itheima.service;

public interface BookService {
    public void save();
}
public class BookServiceImpl implements BookService {

    private BookDao bookDao = new BookDaoImpl();
    @Override
    public void save() {
        bookDao.save();
    }
}

3创建Spring配置文件,配置对应类作为Spring管理的bean






    


bean定义时id属性不能重复
4.初始化Ioc容器,通过容器获取bean

public class App2 {
    public static void main(String[] args) {
        //3.获取Ioc容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //4.获取bean
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();

    }
}
DI入门案例

1.删除使用new的方式创建对象的代码

2.提供依赖对象对应的setter方法

public class BookServiceImpl implements BookService {

    //5.删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }
    //6.提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

3.配置service与dao之间的关系






    

    




        
    

bean相关 bean基础配置

bean实例化
  • bean本质上就是对象,创建bean使用构造方法完成
一、 构造方法(常用)

提供可访问的构造方法

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDao.....");
    }
}

配置


无参构造方法如果不存在,将抛出异常BeanCreationException

二、 静态工厂(了解)

静态工厂

public class OrderDaoFactory{
	public static OrderDao getOrderDao(){
	return new OrderDaoImpl();
	}
}

配置


三、 实例工厂(了解)

实例工厂

public class UserDaoFactory{
	public UserDao getUserDao(){
	return new UserDaoImpl();
	}
}

配置




四、使用FactoryBean实例化(实用)

FactoryBean(实用):
(1)FactoryBean
implements FactoryBean
实现getObject()和getObjectType(还有isSingleton()可以修改是否单例)
(2)配置
class = “com. itheima.factory.UserDaoFactoryBean”

bean生命周期

bean生命周期:bean从创建到销毁的整体过程

生命周期控制:

方法一:

提供生命周期控制方法

public class BookDaoImpl implements BookDao{
	public void save(){
        
    }
    public void init(){
        
    }
    public void destroy(){
        
    }
}

配置生命周期s控制方法


方法二:实现 InitializingBean,DisposableBean接口(了解)

public class BookServiceImpl implements BookService , InittializingBean, DisposableBean {
	public void save(){
        
    }
    public void afterPropertiesSet() throws Exception(){
        
    }
    public void destroy throws Exception(){
        
    }
}
生命周期:

1.初始化容器
(1)创建对象,内存分配
(2)执行构造方法
(3)执行属性注入——setBookDao()
(4)执行bean初始化方法——afterPropertiesSet()
2.使用bean
执行业务操作
3.销毁容器
执行bean销毁方法——destroy()

依赖注入方式 1.setter注入

​ (1)简单类型,如int

(2)引用类型,如String

2.构造器注入

​ (1)简单类型

​ (2)引用类型

依赖注入方式选择
  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现

  2. 可选依赖使用setter注入进行,灵活性强

  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨

  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注

  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入

  6. 自己开发的模块推荐使用setter注入

自动装配

1.定义:IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中

2.方式

  • 按类型(常用)

  • 按名称

  • 按构造方法

  • 不启用自动装配

3.实现

(1)service的bean里去掉ref=“dao”

(2)service保留setter方法

​ 自动配置也需要入口获取资源再注入
(3)service的bean里配置

​ 方式一:按类型——autowire=“byType”
​ 注1:dao的bean不能去
​ 注2:可用的dao的bean必须唯一
​ 方式二:按名称——autowire=“byName”
​ 注:可以解决不唯一的问题(按setter方法名去找依赖的bean id)



4.特征

自动装配用于引用类型依赖注入,不能对简单类型进行操作
使用按类型装配时( byType )必须保障容器中相同类型的bean唯一,推荐使用
使用按名称装配时( byName )必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用自动装配优先级低于setter注入与构造器

集合注入

数组

List

Set

Map

Properties

1.数组

bean id="bookDao" >
    
        
            100
            200
            300
        
    

2.list

    
        
            itheima
            itcast/value>
            boxuegu
        
    

3.Set

    
        
            itheima
            itcast/value>
            boxuegu
        
    

4.Map

    
        
            
            
            
        
    

5.Properties

    
        
            china
            henan
            kaifeng
        
    
案例:数据源对象管理(Spring管理第三方资源) 一、Druid连接对象

1.pom.xml

​ 1.引入依赖Druid
​ 2.刷新maven

2.applicationContext.xml

管理DruidDataSource对象


    
        
        
        
        
    
二、C3p0连接对象

1.pom.xml

​ 1.搜索maven repository
​ 2.引入依赖C3p0、mysql
​ 2.刷新maven

2.applicationContext.xml

管理DataSource对象


    
    
    
    

Spring加载properties文件





    

    
        
        
        
        
    

问题:
	系统环境变量有个userame(优先级更高)
	如果properties里写 username=666
	那么不会正常注入,而是会注入系统环境变量设置的值
解决:关闭系统属性
	
问题:多个配置文件——jdbc.properties、jdbc2.properties
解决1:逗号分隔
	
解决2:通配符(最规范)
	
仅读取当前工程目录:location="classpath:*.properties"
可读取依赖的jar包:location="classpath*:*.properties"
容器 创建容器

1.加载类路径下的配置文件(常用)
AppLicationContext ctx = new ClassPathXmLApplicationContext(“applicationContext.xmL”);
2.从文件系统下加载
AppLicationContext ctx = new FileSystemXmApplicationContext(“D:workspacespring…(绝对路径)”)

获取bean

1.使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean(“bookDao”);
2.使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean(“bookDao”, BookDao.class);
3.使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);

容器类层次结构

Ctrl+F12 查看类中各方法

Ctrl+H 查看接口继承

BeanFactory

是所有容器类的顶层接口

虽然BeanFactory也可以作为容器
但是ApplicationContext提供的功能要比BeanFactory多

1.核心区别:加载bean的时机不一样:
    ApplicationContext	立即加载bean
    BeanFactory			延迟加载bean

2.现象:在只加载配置文件后运行,看是否执行构造方法

3.ApplicationContext实现延迟加载
	
核心容器总结

bean相关

依赖注入相关

Spring注解开发 注解开发定义bean

使用@Component定义bean

@Component("bookDao")
public class BookDaoImpl implements BookDao {

}

核心配置文件中通过组件扫描加载bean


注解说明
@Component使用在类上用于实例化Bean
@Controller使用在web层类上用于实例化Bean
@Service使用在service层类上用于实例化Bean
@Repository使用在dao层类上用于实例化Bean
纯注解开发

Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道

Java类代替Spring核心配置文件


改为

@Configuration
@ComponentScan("com.itheima")
public class SpringConfig {
}

@Configuration注解用于设定当前类为配置类

@ComponentScan(“com.itheima”)注解用汉语设定扫描路径,此注解只能添加一次,多个数据用数组格式

@ComponentScan({"com.itheima.service","com.itheima.dao"})

读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
注解开发作用范围与生命周期管理

使用@Scope标注Bean的范围:

@Repository
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}

作用范围与生命周期管理

注解说明
@Scope标注Bean的作用范围
@PostConstruct使用在方法上标注该方法是Bean的初始化方法
@PreDestroy使用在方法上标注该方法是Bean的销毁方法
注解开发依赖注入
注解说明
@Autowired使用在字段上用于根据类型依赖注入
@Qualifier结合@Autowired一起使用用于根据名称进行依赖注入
@Resource相当于@Autowired+@Qualifier,按照名称进行注入
@Value注入普通属性

使用@Autowired注解开启自动装配模式(按类型)

注意:无需提供setter方法

使用@Qualifier注解开启指定名称装配bean

注意:@Qualifier注解无法单独使用,必须配合@Autowired

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    @Qualifier("bookDao")
    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service save...");
        bookDao.save();
    }
}

使用@Value注解注入简单类型

@Value("${jdbc.driver}")

使用@PropertySource注解加载properties文件(不允许用通配符*)

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}
注解说明
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan用于指定 Spring 在初始化容器时要扫描的包。作用和在 Spring 的 xml 配置文件中的一样
@Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource用于加载.properties 文件中的配置
为第三方bean配置资源

核心配置类

@Configuration		//标志该类是Spring的核心配置类

@ComponentScan("com.itheima")   //代替组件扫描:
//
@Import({DataSourceConfiguration.class})		//把子配置类引入主配置类 代替了引入其他文件:
public class SpringCofiguration {

}

子配置类

@PropertySource("classpath:jdbc.properties")	//加载jdbc配置文件
public class DataSourceConfiguration {

    @Value("${jdbc.driver}")	// 用SpEl避免写死了而导致耦合
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")  //Spring会将当前方法的返回值以指定名称(dataSource)存储到Spring容器中
    public DataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }

}

注解开发总结

Spring整合Mybatis

分析

整合mybatis

Spring整合Juit

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

解决上述问题的思路:

  • 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它

  • 将需要进行测试Bean直接在测试类中进行注入

Spring集成Junit步骤:

① 导入spring集成Junit的坐标

 
 
    org.springframework 
    spring-test 
    5.0.2.RELEASE
 
 
    junit
    junit
    4.12 
    test


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

@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}

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

@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}

④ 使用@Autowired注入需要测试的对象

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})

public class SpringJunitTest {
	@Autowired
	private UserService userService;
}

⑤ 创建测试方法进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
	@Autowired
	private UserService userService;
	@Test
	public void testUserService(){
		userService.save();
	} 
}

Spring集成Junit步骤

① 导入spring集成Junit的坐标

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

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

④ 使用@Autowired注入需要测试的对象

⑤ 创建测试方法进行测试

AOP AOP基本介绍

AOP(Aspect Oriented Programming:面向切面编程),一种编程范式,指导开发者如何组织程序结构

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

  • 优势:减少重复代码,提高开发效率,并且便于维护

  • Spring理念:无侵入式

AOP核心概念

  • 连接点:程序执行过程中的任意位置
    • 在SpringAOP中,理解为方法的执行
  • 切入点:匹配连接点的式子
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:
      • 匹配多个方法:
  • 通知:在切入点处执行的操作,也就是共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面:描述通知与切入点的对应关系
AOP快速入门(基于注解)

①导入 AOP 相关坐标

②定义dao接口与实现类

③定义通知类

④定义切入点

⑤绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

⑥定义通知类受Spring容器管理,并定义当前类为切面类

⑦开启Spring对AOP注解驱动支持

具体如下:

①导入 AOP 相关坐标



  org.springframework
  spring-context
  5.0.5.RELEASE



  org.aspectj
  aspectjweaver
  1.8.13

②定义dao接口与实现类

public interface BookDao {
    public void save();
    public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save...");
    }

    public void update() {
        System.out.println("book dao update...");
    }
}

③定义通知类

public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

④定义切入点

public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

}

⑤绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

⑥定义通知类受Spring容器管理,并定义当前类为切面类

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

⑦开启Spring对AOP注解驱动支持

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
AOP工作流程

SpringAOP本质:代理模式

AOP切入点表达式

AOP通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知,它们分别是:

  • 前置通知 (@Before) 。

    在切入点前

  • 后置通知 (@After)。

    在切入点后

  • 环绕通知 (@Around) :(优先级最高)

    在切入点首和尾

@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        pjp.proceed();
        System.out.println("around after advice");
        return ret
    }
  • 返回通知 (@AfterReturning) 。(了解即可)

    正常执行完毕后(没有抛异常)的时候执行

  • 异常通知 (@AfterThrowing) 。(了解即可)

    当前通知方法在切入点方法运行抛出异常后执行

案例:测量业务层接口万次执行效率

AOP通知获取数据

Spring事务 Spring事务简介 相关概念介绍
  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败

数据层有事务我们可以理解,为什么业务层也需要处理事务呢?

举个简单的例子,

  • 转账业务会有两次数据层的调用,一次是加钱一次是减钱
  • 把事务放在数据层,加钱和减钱就有两个事务
  • 没办法保证加钱和减钱同时成功或者同时失败
  • 这个时候就需要将事务放在业务层进行处理。
转账案例-需求分析

接下来通过一个案例来学习下Spring是如何来管理事务的。

先来分析下需求:

需求: 实现任意两个账户间转账操作

需求微缩: A账户减钱,B账户加钱

为了实现上述的业务需求,我们可以按照下面步骤来实现下:
①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)

②:业务层提供转账操作(transfer),调用减钱与加钱的操作

③:提供2个账号和操作金额执行转账操作

④:基于Spring整合MyBatis环境搭建上述操作

转账案例-环境搭建 步骤1:准备数据库表

之前我们在整合Mybatis的时候已经创建了这个表,可以直接使用

create database spring_db character set utf8;
use spring_db;
create table tbl_account(
    id int primary key auto_increment,
    name varchar(35),
    money double
);
insert into tbl_account values(1,'Tom',1000);
insert into tbl_account values(2,'Jerry',1000);
步骤2:创建项目导入jar包

项目的pom.xml添加相关依赖


    
      org.springframework
      spring-context
      5.2.10.RELEASE
    
    
      com.alibaba
      druid
      1.1.16
    

    
      org.mybatis
      mybatis
      3.5.6
    

    
      mysql
      mysql-connector-java
      5.1.47
    

    
      org.springframework
      spring-jdbc
      5.2.10.RELEASE
    

    
      org.mybatis
      mybatis-spring
      1.3.0
    

    
      junit
      junit
      4.12
      test
    

    
      org.springframework
      spring-test
      5.2.10.RELEASE
    

  
步骤3:根据表创建模型类
public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;
	//setter...getter...toString...方法略    
}
步骤4:创建Dao接口
public interface AccountDao {

    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}
步骤5:创建Service接口和实现类
public interface AccountService {
    
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        accountDao.inMoney(in,money);
    }

}
步骤6:添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
步骤7:创建JdbcConfig配置类
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}
步骤8:创建MybatisConfig配置类
public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
步骤9:创建SpringConfig配置类
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

步骤10:编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",100D);
    }

}
事务管理

上述环境,运行单元测试类,会执行转账操作,Tom的账户会减少100,Jerry的账户会加100。

这是正常情况下的运行结果,但是如果在转账的过程中出现了异常,如:

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }

}

这个时候就模拟了转账过程中出现异常的情况,正确的操作应该是转账出问题了,Tom应该还是900,Jerry应该还是1100,但是真正运行后会发现,并没有像我们想象的那样,Tom账户为800而Jerry还是1100,100块钱凭空消息了,银行乐疯了。如果把转账换个顺序,银行就该哭了。

不管哪种情况,都是不允许出现的,对刚才的结果我们做一个分析:

①:程序正常执行时,账户金额A减B加,没有问题

②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

当程序出问题后,我们需要让事务进行回滚,而且这个事务应该是加在业务层上,而Spring的事务管理就是用来解决这类问题的。

Spring事务管理具体的实现步骤为:

步骤1:在需要被事务管理的方法上添加注解@Transactional
public interface AccountService {
    
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
	@Transactional
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }

}

注意:

@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上
步骤2:在JdbcConfig类中配置事务管理器
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

**注意:**事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用DataSourceTransactionManager

步骤3:开启事务注解@EnableTransactionManagement

在SpringConfig的配置类中开启

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

步骤4:运行测试类

会发现在转换的业务出现错误后,事务就可以控制回顾,保证数据的正确性。

知识点1:@EnableTransactionManagement
名称@EnableTransactionManagement
类型配置类注解
位置配置类定义上方
作用设置当前Spring环境中开启注解式事务支持
知识点2:@Transactional
名称@Transactional
类型接口注解 类注解 方法注解
位置业务层接口上方 业务层实现类上方 业务方法上方
作用为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)
Spring事务角色

这节中我们重点要理解两个概念,分别是事务管理员和事务协调员。

通过上面例子的分析,我们就可以得到如下概念:

  • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
  • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
事务配置

上面这些属性都可以在@Transactional注解的参数上进行设置。

  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。

  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。

  • rollbackFor:当出现指定异常进行事务回滚

  • noRollbackFor:当出现指定异常不进行事务回滚

    • 思考:出现异常事务会自动回滚,这个是我们之前就已经知道的

    • noRollbackFor是设定对于指定的异常不回滚,这个好理解

    • rollbackFor是指定回滚异常,对于异常事务不应该都回滚么,为什么还要指定?

      • 这块需要更正一个知识点,并不是所有的异常都会回滚事务,比如下面的代码就不会回滚

        public interface AccountService {
            
            //配置当前接口方法具有事务
            public void transfer(String out,String in ,Double money) throws IOException;
        }
        
        @Service
        public class AccountServiceImpl implements AccountService {
        
            @Autowired
            private AccountDao accountDao;
        	@Transactional
            public void transfer(String out,String in ,Double money) throws IOException{
                accountDao.outMoney(out,money);
                //int i = 1/0; //这个异常事务会回滚
                if(true){
                    throw new IOException(); //这个异常事务就不会回滚
                }
                accountDao.inMoney(in,money);
            }
        
        }
        
  • 出现这个问题的原因是,Spring的事务只会对Error异常和RuntimeException异常及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚

    • 此时就可以使用rollbackFor属性来设置出现IOException异常不回滚

      @Service
      public class AccountServiceImpl implements AccountService {
      
          @Autowired
          private AccountDao accountDao;
      	 @Transactional(rollbackFor = {IOException.class})
          public void transfer(String out,String in ,Double money) throws IOException{
              accountDao.outMoney(out,money);
              //int i = 1/0; //这个异常事务会回滚
              if(true){
                  throw new IOException(); //这个异常事务就不会回滚
              }
              accountDao.inMoney(in,money);
          }
      
      }
      
  • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串

  • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串

  • isolation设置事务的隔离级别

    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化
要实现案例不管是否转账成功都添加日志

事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

具体如何解决,就需要用到之前我们没有说的propagation属性。

修改logService改变事务的传播行为

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	//propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

运行后,就能实现我们想要的结果,不管转账是否成功,都会记录日志。

事务传播行为可选参数

            throw new IOException(); //这个异常事务就不会回滚
          }
          accountDao.inMoney(in,money);
      }
  
  }
  ```
  • 出现这个问题的原因是,Spring的事务只会对Error异常和RuntimeException异常及其子类进行事务回顾,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚

    • 此时就可以使用rollbackFor属性来设置出现IOException异常不回滚

      @Service
      public class AccountServiceImpl implements AccountService {
      
          @Autowired
          private AccountDao accountDao;
      	 @Transactional(rollbackFor = {IOException.class})
          public void transfer(String out,String in ,Double money) throws IOException{
              accountDao.outMoney(out,money);
              //int i = 1/0; //这个异常事务会回滚
              if(true){
                  throw new IOException(); //这个异常事务就不会回滚
              }
              accountDao.inMoney(in,money);
          }
      
      }
      
  • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串

  • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串

  • isolation设置事务的隔离级别

    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化
要实现案例不管是否转账成功都添加日志

事务传播行为:事务协调员对事务管理员所携带事务的处理态度。

具体如何解决,就需要用到之前我们没有说的propagation属性。

修改logService改变事务的传播行为

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	//propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

运行后,就能实现我们想要的结果,不管转账是否成功,都会记录日志。

事务传播行为可选参数

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/880831.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号