0%

Spring春之初性本善

环境搭建

使用MyBatis操作数据库

准备好两个对象:

账户(Account):id,name,money(账户余额)

图书(Book):id,bookName,price,stock(库存)

几个方法:

1
//账户余额减少
2
void spendMoney(@Param("accountName") String accountName, @Param("qian")int qian);
3
//通过书名找到书本对象
4
Book getBookByName(String bookName);
5
//更新书本库存
6
void updateBook(Book book);
7
8
//业务层(两个参数,账户名+书名)
9
public void checkout(String accountName,String bookName){
10
	Book abook=bookMapper.getBookByName(bookName);
11
	//得到要买的书
12
	bookMapper.updateBook(abook);
13
	//书本减少库存
14
	accountMapper.spendMoney(accountName,abook.getPrice());
15
	//账户余额减少
16
}

注意:MyBatis使用多参数操作数据库是,要在参数前加上@Param注解,例如这个花钱方法

1
void spendMoney(@Param("accountName") String accountName, @Param("qian")int qian);

测试顺利,环境搭建完成

案列分析

这个过程分了两个操作完成,减少库存和减少余额,但是如果中间发生了一些差错时,库存减少了,余额确没减少,这样商家就亏了,所以要把它们弄成一个整体,引入事务控制。

声明式事务

事务概念

一组逻辑紧密关联的数据库操作合成的整体。

比如买书时,账户扣钱和书本减少库存,转账时一方扣钱另一方加钱。两者必须是一起的。

四个关键属性

原子性(不可再分,要么都执行,要么都不执行),

一致性(所有数据都处于满足业务规则的一致性状态,只能由一个一致的状态到另一个一致的状态,一旦某个操作失败就回滚到没执行前的状态),

隔离性(多个事务并发执行不会互相干扰),

持久性(对数据的修改会永久保存)。

原生编程式事务

Spring事务控制

只需要告诉Spring哪个方法是事务方法,Spring就会自动进行事务控制。

根据框架不同使用不同的管理器,MyBatis使用DataSourceTransactionManager

在这之前,由于这是基于切面的,所以要导入切面相关依赖。

配置事务管理器

让它控制住数据源

1
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
2
    <property name="dataSource" ref="dataSource"></property>
3
</bean>

开启基于注解的事务控制模式

导入tx名称空间,在这里又学到了

1
xmlns:tx="http://www.springframework.org/schema/tx"
2
<!--加了这一句后,还要在xsi:schemaLocation加上同一句和/spring-xxx.xsd-->
3
http://www.springframework.org/schema/tx
4
http://www.springframework.org/schema/tx/spring-tx.xsd

这里的属性与刚刚的id对应

1
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

给事务方法加注解

用@Transactional注解

测试

先弄一个产生异常的语句加载两个数据库操作中间。

1
public void checkout(String accountName,String bookName){
2
    Book abook=bookMapper.getBookByName(bookName);
3
    //得到要买的书
4
    bookMapper.updateBook(abook);
5
    //书本减少库存
6
    int x=5/0;
7
    //造成异常
8
    accountMapper.spendMoney(accountName,abook.getPrice());
9
    //账户余额减少
10
    System.out.println("结账完成");
11
}

没加注解时,运行抛出异常,发现库存减少了,账户余额并没有减少

加了注解后,同样抛出异常,但是库存也没减少,余额也没减少,很成功。

细节

注解属性解析

readOnly(boolean):设置事务为只读事务,可以进行事务优化,如果操作都是只读数据库,调成true可以省去一些操作,比如加锁,加快查询速度。默认false

1
@Transactional(readOnly=true)//该事务只能对数据库进行读取操作

timeout(int):超时自动终止并回滚,以秒为单位

1
@Transactional(timeout=3)//如果3秒内不能执行完就GG

运行时异常默认都回滚,比如数学运算异常(数字除以0),编译时异常默认不会滚,比如IO异常,读取文件不到。如果在完成数据库操作后发生编译时异常,数据操作已经执行了。

noRollbackFor(Class[]):指定哪些异常事务可以不回滚,可以让原本回滚的异常不再回滚。

1
@Transactional(noRollbackFor={NullPointerExceptioin.class,xxx.class})

noRollbackForClassName(String[]):上述异常的String类型全类名

1
@Transactional(noRollbackForClassName={"java.lang.xxxException","xxx"})

rollbackFor(Class[]):指定哪些异常需要回滚,让不回滚的异常回滚,用法与上面相反

rollbackForClassName(String[]):上述异常的String类型全类名

isolation(Isolation):调整事务隔离级别

几个概念:

脏读:事务A将20改为30,事务B读取到30,事务A回滚,此时30这个数据就是脏数据

不可重复读:事务A读取数据20,事务B修改为30,事务A再读取到了30,两次不一致

幻读:事务A读取到信息10条,事务B插入了10条,事务A再读成了20条,信息多了

隔离级别:不同级别应对不同现象

读未提交:允许事务B读取事务A未提交的数据,—以上都可能产生

读以提交:要求事务B只能读取事务A以提交的数据,—避免脏读,但产生另外两种

可重复读:在读取数据期间,不可修改数据,—避免不可重复读和脏读(实际上MySql这个级别能避免幻读)

串行化:变成单线程,啥都能避免,—但没啥用了,事务排队一万年

propagation(Propagation):指定事务传播行为,事务传播+事务行为

如果多个事务嵌套运行,子事务是否和父事务共用一个事务。比如:ta,tb,tc都是事务

1
Service{
2
    tb(){}
3
    tc(){}
4
}
5
Test{
6
    //注入Service s
7
    ta(){
8
        s.tb(){}
9
        s.tc(){}
10
    }
11
}

当tc回滚时,tb,ta要不要回滚。怎么控制就靠传播行为控制

REQUIRED在当前事务里执行自己,如果当前没有,则开新事务。REQUIRED_NEW必定会挂起当前事务,开新事务执行自己。

不管后面怎么异常,之前已经执行的带有REQUIRED_NEW不会回滚,而REQUIRED会受到影响。当REQUIRED_NEW中的异常没有捕获被抛出时,会影响到父事务。

当属性为REQUIRED时,其他属性都跟父事务一样,在子事务上调整不起作用。

REQUIRED:将之前事务的connection传递给这个方法使用

REQUIRED_NEW:创建新的connection

本类方法的嵌套调用,就会看作一个事务,比如C事务调用了本类的A,B事务

1
Service{
2
    a(){}//A事务
3
    b(){}//B事务
4
    c(){//C事务
5
        a();
6
        b();
7
    }
8
}

基于XML事务控制

配置事务管理器一样,导入名称空间一样。

告诉Spring哪些是事务方法

让事务切面按照定义的切入点表达式去切入事务方法

1
<aop:config>
2
	<aop:pointcut expression="execution(xxx)" id="point"/>
3
    <!--用表达式指定要切入的方法-->
4
    <aop:advisor advice-ref="myAdvice" pointcut-ref="point"/>
5
    <!--指定事务管理器-->
6
</aop:config>
7
<!--配置事务管理器-->
8
<tx:advice id="myAdvice" transaction-manager="transactionManager">
9
	<tx:attributes>
10
        <!--指明哪些方法是事务方法-->
11
        <tx:method name="test" timeout="3"></tx:method>
12
        <!--具体哪个方法,所有的参数都可以配置,这里以timeout为例-->
13
        <tx:method name="*"></tx:method>
14
        <!--所有方法-->
15
        <tx:method name="get*" read-only="true"></tx:method>
16
        <!--get开头的方法-->
17
    </tx:attributes>
18
</tx:advice>

总结

头发-1