1.Spring(一个轻量级的控制反转IOC和面向切面变成AOP的框架)
1.1优点
- 免费开源的框架
- 轻量级的非入侵式的框架
- IOC,AOP
- 支持事务的处理,对框架整合的支持
1.2拓展
- spring boot
- 一个快速开发的脚手架
- 基于它可以快速的开发单个微服务
- 约定大于配置
- spring cloud
- 它是基于spring boot实现的3
1.3核心概念
-
IOC控制反转:代码耦合度偏高的话,使用对象时再程序中不主动使用new产生对象,转换为由外部提供对象。(即对象的创建控制权由程序转移到外部,这种思想叫做控制反转)(目标:充分 解耦)
-
spring提供容器成为IOC容器,提供对象。(将对象放进ioc容器里,管理对象创建和初始化过程)
-
IOC容器负责对象的创建初始化等一系列工作,这个对象在IOC中统称为==Bean==
-
-
DI依赖注入
- 在IOC中建立bean和bean之间的依赖关系的整个过程叫做DI依赖注入
2.IOC
2.1入门案例
- 导入Spring坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-content</artifactId>
<version>5.2.10</version>
</dependency>
- 定义Spring管理的类(接口)
- 创建spring’‘配置文件,配置对应类作为Spring管理的bean
<bean id="bookservice" class="com.itheima.service.impl.BookServiceImpl"></bean>
id不能重复
- 初始化IOC容器,通过容器获取bean
//加载配置文件得到上下文对象,也就是容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取资源bean
BookService bookService = (BookService) ctx.getBean("bookService")
bookService.save();
3.DI
3.1入门案例
- 基于IOC管理bean
- Service中使用的new形式创建的对象不在保留
- 为service中的对象进入到service中提供方法
- 用配置描述service与对象之间的关系
//配置service与dao对象的关系
<bean id="bookservice" class="com.itheima.service.impl.BookServiceImpl">
//property表示当前bean的属性
//name属性表示配置哪一个具体的属性
//ref属性表示参照哪一个bean
<property name="bookDao" ref="bookDao"/>
</bean>
4. Bean
4.1别名配置:
- 定义bean的别名,可以定义多个,使用逗号,分号,空格分隔。
4.2 bean作用范围
<bean id="bookservice" name="Dao" class="com.itheima.service.impl.BookServiceImpl" scope="prototype"> //默认scope="singleton"单例(是同一个对象) prototype是多例(不是同一个对象)
- 适合交给容器进行管理的bean:变现层对象,业务层对象,数据层对象,工具对象
- 不适合交给容器的bean:封装实体的域对象
4.3 bean实例化
- bean本质上就是对象,创建bean使用构造方法完成
4.3.1 构造方法
- 提供可访问的构造方法
public class BookDaoImpl implements BookDao{
public BookDaoImpl(){
System.out.println("123");
}
public void save(){
System.out.println("321");
}
}
- 配置
<bean
id="bookDao"
class="com.itheima.dao.impl.BookDaoImpl"
/>
- 无参构造方法如果不存在,将抛出异常BeanCreationException
4.3.2 使用静态工厂实例化bean
- 静态工厂
public class OrderDaoFactory{
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
- 配置
<bean
id="orderDao"
factory-method="getOrderDao"
class="com.itheima,factory.OrderDaoFactory"
/>
4.3.3 使用实例工厂实例化bean
- 配置
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>//先把实例造出来。配合使用,没有实际意义
<bean id="UserDao" factory-method="getUserDao" factory-bean="UserFactory"/>
//factory-bean指向工厂实例,factory-method方法名不固定每次需要配置!!可以优化
4.3.4 使用factoryBean实例化(属于实例工厂的一种)
public class UserDaoFactoryBean implements FactoryBean<UserDao>{
public UserDao getObject() throws Exception{
return new UserDaoImpl;
}
public class<?> getObjectType(){
return UserDao.class;
}
//isSingleton()函数是设置单例或者非单例的,就是对象地址是不是一样的
public boolean isSingleton(){
return true;
}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
4.4Bean生命周期
- 生命周期:从创建到消亡的完整过程
- bean生命周期:bean从创建到销毁的整个过程
- bean生命周期控制:在bean创建后到销毁前做的一些事情
<bean id="bookDao" class="com.itheima.factory.BookServiceImpl" init-method="init" destory-method="destory"/>
4.4.1生命周期控制:在关虚拟机之前先关闭容器(利用钩子)
public class BookDaoImpl implements BookDao {
public void save(){
System.out.println("book dao save ...");
}
public void init(){
System.out.println("book init ...");
}
public void destory(){
System.out.println("book destory ...");
}
}
//加载配置文件得到上下文对象,也就是容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取资源bean
BookService bookService = (BookService) ctx.getBean("bookService")
bookService.save();
ctx.registerShutdownHook();//关闭钩子(任何时候都可以)
4.4.2使用接口控制生命周期
实现InitializingBean,DisposableBean接口
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
public void save(){
System.out.println("book service save ...");
}
public void afterPropertiesSet() throws Exception{
System.out.println("afterPropertiesset");
}
public void destroy() throws Exception {
System.out.println("destroy");
}
}
5.依赖注入
传递数据的方式:
- 普通方法(set方法):提供一个set方法,用ref引用bean
- 构造方法
依赖注入的数据分为两种类型:
- 引用类型
- 简单类型(基本数据类型和String)
依赖注入方式:
- setter注入
- 简单类型
- 引用类型
- 构造方法注入
- 简单类型
- 引用类型
两种依赖方式的选择:
- 强制依赖使用构造器注入,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入,灵活性强
- Spring框架倡导使用构造器,第三方框架内部大多数使用构造器注入的形式进行数据初始化,相对严谨
- 实际开发中,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
5.1 setter注入
需要构建一个set构造方法
public class BookDaoImpl implements BookDao{
private String databaseName;
public setdatabaseName(String databaseName){
this.databaseName=databaseName
}
public void save(){
System.out.println("123");
}
}
value参数
//setter注入
<bean id="bookDao" class="com.itheima.service.impl.BookDaoImpl">
//property表示当前bean的属性
//name属性表示配置哪一个具体的属性
//value属性表示注入的值
<property name="databaseName" value="mysql"/> //数据类型
<property name="bookDao" ref="bookDao"/> //引用类型
</bean>
5.2构造方法注入
- 在bean中引用类型属性并提供可访问的构造方法
public class BookserviceImpl impletments BookService{
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao){
this.bookDao=bookDao;
}
}
- constructor-arg参数:配置中使用这个constructor-arg标签ref属性注入引用类型对象
public class BookDaoImpl implements BookDao{
private String databaseName;
private int connectionNum;
public BookDaoImpl(String databaseName,int connectionNum){
this.databaseName=databaseName;
this.connectionNum=connectionNum;
}
public void save(){
System.out.println("123");
}
}
//构造器注入
//property表示当前bean的属性
//name是形参的名字!!!!!!!!!!!(跟着传给函数中的形参走)
<bean id="bookDao" class="com.itheima.service.impl.bookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="10"/>
//解决形参变化以及参数类型重复的问题,不耦合了。index表示上面变量名的顺序
//<constructor-arg index="0" value="mysql"/>
//<constructor-arg index="1" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.service.impl.userDaoImpl">
<bean id="bookService" class="com.itheima.service.impl.bookServiceImpl">
<constructor-arg name="bookDao1" ref="bookDao"/> //name是形参的名字!!!!!!!!!!!
<constructor-arg name="userDao1" ref="userDao"/>
</bean>
5.3依赖自动注入装配
- ioc容器根据bean所依赖的资源在容器中自动查找并注入到bean过程中
- 自动装配方式:(需要set提供方法setBookDao!!!!!!!)
- 按类型(常用):必须保障容器中相同类型的bean唯一
- 按名称:必须保障容器中具有指定名称的bean,因变量名和配置耦合。
- 按构造方法
- 不启用自动装配
//autowire自动装配
<bean id="bookService" class="com.itheima.service.impl.bookServiceImpl" autowire="byType" />
5.4集合注入
数组,list,map,set,properties
//setter注入
<bean id="bookDao" class="com.itheima.service.impl.BookDaoImpl">
<property name="arrayname">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<property name="listname">
<list>
<value>100</value>
<value>200</value>
<value>300</value>
</list>
</property>
<property name="setname">
<set>
<value>a</value>
<value>b</value>
<value>c</value>
</set>
</property>
<property name="mapname">
<map>
<entry key="country" value="china"/>
<entry key="province" value="jiangsu"/>
<entry key="city" value="yangzhou"/>
</map>
</property>
<property name="propertiesname">
<props>
<prop key="country">china</prop>
<prop key="province">jiangsu</prop>
<prop key="city">yangzhou</prop>
</props>
</property>
</bean>
5.5 properties加载
- 开启context命名空间
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
- 使用context命名空间,加载指定properties文件
<context:property-placeholder location="classpath*:*.properties"/> //加载类路径或jar包下的所有properties文件
- 使用${}读取加载的属性值
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
</bean>
6. 容器
从文件系统下加载配置文件:
FileSystemApplicationContext(”绝对路径“)
//之前的方法
//ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//BookService bookService = (BookService) ctx.getBean("bookService")
//bookService.save();
//文件系统下加载配置文件
ApplicationContext ctx = new FileSystemApplicationContext("D:\\workspace\\applicationContext.xml");//绝对路径!!
BookService bookService = ctx.getBean("bookService",bookService.class)
BookService bookService = ctx.getBean(bookService.class)//按类型找,容器中只能有一个bean
bookService.save();
BeanFactory是所有容器类的顶层接口
BeanFactory创建完毕后,所有的bean均为延迟加载
7. 注解开发
==需要在xml文件中加入context:annotation-config ,这是用来开启注解的支持,如果不加上注解就无效。
<?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
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
7.1 Component
- 使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao{
}
@Component //与上面的区别是没有起一个名称Id,后面ctx.getBean()的时候只能传递类参数,而不能传递名称
public class BookServiceImpl implements BookService{
}
- 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.itheima"/>
Spring提供@Component三个衍生注解:
1.@Controller:用于表现层bean定义
2.@Service:用于业务层bean定义
3.@Repository:用于数据层bean定义
@Controller
@Service
@Repository
@Component("bookDao")
@Service("bookDao")
@Repository("bookDao")
public class BookServiceImpl implements BookService{
}
7.2 纯注解开发
7.2.1 Configuration
@Configuration:用于设定当前类为配置类
==@ComponentScan(“com.itheima”):==用于设定扫描路径,此注解只能添加一次。可以在新建的类离里替代<context:component-scan base-package=“com.itheima”/>
@Configuration
@ComponentScan("com.itheima")
public class SpringConfig{
}
- 加载配置类初始化容器:
//原来的写法ApplicationContext ctx = new ClassPathXmlApplicaionContext(SpringConfig.class)
ApplicationContext ctx = new AnnotationConfigApplicaionContext(SpringConfig.class)
7.2.2 注解Bean生命周期
@Repository
@Scope("singleton")//单例
public class BookDaoImpl implements BookDao {
public void save(){
System.out.println("book dao save ...");
}
@PostConstruct//构造方法后运行的方法
public void init(){
System.out.println("book init ...");
}
@PreDestory//彻底销毁前构造的方法
public void destory(){
System.out.println("book destory ...");
}
}
7.2.3 自动装配
@Autowired //自动装配
@Qualifier(“bookDao2”):指定加载的bean的名称,并且qualifier依赖Autowired
@PropertySource(“jdbc.properties”)//写在配置文件类里的。加载properties文件
@Service
public class BookServiceImpl implements BookService{
@Autowired//自动装配
@Qualifier("bookDao2")//指定加载的bean的名称,并且qualifier依赖Autowired
@Value("itheima")//注入值类型
@Value(${jdbc.name})//注入值类型
private String Name;
private BookDao bookDao;
public void save(){
System.out.println("book service save ...");
bookDao.save();
}
}
7.2.4 第三方bean管理
@Bean:表示当前方法的返回值式一个bean。
第一种方式:导入式
//不需要@Configuration!!!!!!
public class Jdbcconfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();//相关配置
return ds;
}
}
//使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
@Configuration
@Import(JdbcConfig.class)
public class Springconfig {
}
第二种方式:扫描式(不推荐)
@Configuration
public class Jdbcconfig {
@Bean
public DataSource datasource(){
DruidDataSource ds =new DruidDataSource();//相关配置
return ds;
}
}
//使用@componentscan注解扫描配置类所在的包,加载对应的配置类信息
@Configuration
@ComponentScan({"com.itheima.config","com.itheima.service","com.itheima.dao"})
public class Springconfig {
}
7.2.5 第三方bean依赖注入
- 简单类型:
public class Jdbcconfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("password")
private String pässword;
@Bean
public DataSource datasource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverclassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 引用类型:
@Bean
public DataSource dataSource(BookService bookService){
System.out.println(bookService);
DruidDataSource ds=new DruidDataSource();//属性设置
return ds;
}
引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
7.3 xml配置和注解的比较
功能 | xml配置 | 注解 |
---|---|---|
定义bean | bean标签 id属性 class 属性 | @Component @Controller @Service @Repository @ComponentScan |
设置依赖注入 | setter注入 构造器注入 自动装配 | @Autowired @Qualifier @Value |
配置第三方bean | bean标签 静态工厂 实例工厂 FactoryBean | @Bean |
作用范围 | scope属性 | @Scope |
生命周期 | 标准接口 init-method destroy-method | @PostConstructor @PreDestroy |
8. Spring整合MyBatis(将mybatis的xml配置文件整合)
- SqlSessionFactoryBean
- MapperScannerConfigurer
public class Mybatisconfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb=new SqlSessionFactoryBean();
/*
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases)
*/
ssfb.setTypeAliasesPackage("com.itheima.domain");
/*
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
*/
ssfb.setDataSource(dataSource);
return ssfb;
}
/*
<mappers>
<package name="com.itheima.dao"></package>
</mappers>
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc=new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.ao");
return msc;
}
}
9. Spring整合Junit
@RunWith:设定专用的类运行器
@ContextConfiguration:指定spring上下文运行的配置类
@RunWith(springJUnit4classRunner.class)
@Contextconfiguration(classes =SpringConfig.class)
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
public void testSave(){
bookService.save();
}
}
10. AOP:面向切面编程
10.1 定义
- 作用:在不惊动原始设计的基础上为其进行功能增强
- Spring理念,无入侵式
- 连接点:在AOP中理解为方法的执行
- 切入点:一个切入点描述一个具体方法,也可以匹配多个方法。
- 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法
- 通知(共性功能):功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面:描述通知与切入点的对应关系
10.2 入门案例
-
思路分析
- 导入坐标(pom.xml)
- 制作连接点方法(原始操作,Dao接口与实现类)
- 制作共性功能(通知类喝通知)
- 定义切入点(@Pointcut(“execution(void com.itheima.dao.BookDao.update())”))
- 绑定切入点与通知关系(切面)
@Aspect:把MyAdvice的bean当作一个AOP来处理
@EnableAspectJAutoProxy:这句话启动了通知类里的@Aspect。告诉Spring用到注解开发的AOP
@Before(“pt()”):绑定切入点和通知
@Component
@Aspect//把MyAdvice的bean当作一个AOP来处理。启动了下列的@Pointcut @Before
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Before("pt()")//绑定切入点和通知
public void method(){
System.out.println(System.currentTimeMillis());
}
}
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy//这句话启动了通知类里的@Aspect。告诉Spring用到注解开发的AOP
public class SpringConfig{
}
10.3 AOP工作流程
目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象式无法直接完成最终工作的
代理:目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean式代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
10.4 切入点表达式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强的方法的描述方式
- 切入点表达式标准规则:动作关键字(访问修饰符 返回值 包名.类/接口.方法名(参数)异常名)
execution(public User com.itheima.service.UserService.findById(int))
-
可以使用通配符描述切入点,快速描述
-
*:单个独立的符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现。必有一个
execution(public * com.itheima.&.UserService.find*(*)) //匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
-
…:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写。任意
execution(public User com..UserService.findById(..)) //匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
-
10.5 AOP通知类型
- 前置通知
@Before("pt()")
public void method(){System.out.println(1);}
//输出为:
// 1
// pt()
- 后置通知
@after("pt()")
public void method(){System.out.println(1);}
//输出为:
// pt()
// 1
-
环绕通知
注意事项:
- 环绕必须依赖形参:ProceedingJoinPoint才能实现对原始方法的调用
- 如果没有使用ProceedingJoinPoint对原始方法的调用将跳过原始发放的执行
- 对原始方法的调用可以不接受返回值,通知方法设置成void即可,如果接受返回值必须设定为Object类型返回值
- 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
@Around("pt()")
public Object method(ProceedingJoinPoint pjp) throw Throwable{
System.out.println("before");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("after");
return ret
}
//输出为:
// before
// pt()的返回值
// after
- 返回后通知
@AfterReturning
- 抛出异常后通知
@AfterThrowing
10.6 AOP通知获取数据
JointPoint参数
ProceedJointPoint是JoinPoint的子类
- AOp通知获取参数数据
@Before("pt()")
public void before(JoinPoint jp){
object[] args = jp.getArgs();
System.out.println(Arrays.tostring(args));
}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object[] args =pjp.getArgs();
System.out.println(Arrays.tostring(args));
Object ret = pjp.proceed();
return ret;
}
/*
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object[] args =pjp.getArgs(); //假设args[0]是100
System.out.println(Arrays.tostring(args));
args[0] = args[0]+566;
Object ret = pjp.proceed(args[0]);
return ret;
}
返回值args[0]是100+566-->666
- AOP通知获取返回值参数
//抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接受对应的异常对象
@AfterReturning(value ="pt()",returning ="ret")
public void afterReturning(string ret){
System.out.println("afterReturning advice ..."+ret);
}
//环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pip) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
11 事务
11.1 事务介绍
-
主要流程
- @Transactional:在业务接口(也可以在接口类上)上添加Spring事务管理(通常不会加到业务层实现类中)
public interface PlatformTransactionManager(){ @Transactional public void transfer(String out,String in,Double money) }
- 设置事务管理器
//DataSourceTransactionManager Spring提供的接口 @Bean public class DataSourceTransactionManager(Datasource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
- 开启注解式事务驱动
@Configuration @ComponentScan("com.itheima") @PropertySource("jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableTransactionManagement//告诉配置文件使用注解事务 public class SpringConfig{ }
-
事务作用:在数据层保障一系列的数据库操作同成功同失败
-
Spring事务作用:在数据或业务层保障一系列的数据库操作同成功同失败
11.2 事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以式业务层的方法
11.3 事务配置
@Transactional(readOnly = true,timeout=-1)//只读,永不超时
@Transactional(rollbackFor = {IOException.class})//遇到IOException异常就回滚(有的事务不一定会回滚)
- 事务传播行为:事务协调员对事务管理员所携带事物的处理态度
@Transactional(propagation = Propagation.REQUIRES_NEW)//不管转账有没有成功,都会执行日志操作。事务管理员开启事务T,事务协调员新建事务T2