数据库和事务
1.mybatis的配置内容结构图
Mybatis是一个基于SqlSessionFactory构建的框架,他的唯一目的就是生成SqlSession对象。虽然与boot结合之后这个对象也被擦除了(SqlSession是功能性代码理应被擦除,这样我们开发过程中只关注业务代码)。SqlSessionFactory作用是单一的,在Mybatis生命周期中理应只有一个SqlSessionFactory对象,使用单例设计模式来实现。构建SqlSession是由配置类Configuration来实现的,对于boot而言可以直接从application.properties来配置相关内容。Configuration可以配置的内容如下
具体的配置看官网
2.事物处理
2.1. spring 声明式事物的使用
spring声明式事物的约定
对于事务,需要通过标注告诉 Spring在什么地方启用数据库事务功能。对于声明式事务,是使用@Transactional进行标注的。这个注解可以标注在类或者方法上,当它标注在类上时,代表这个类所有公共(public)非静态的方法都将启用事务功能。在@ Transactional中,还允许配置许多的属性,如事务的隔离级别和传播行为,这是本章的核心内容;又如异常类型,从而确定方法发生什么异常下回滚事务或者发生什么异常下不回滚事务等。这些配置内容,是在 Spring loc容器在加载时就会将这些配置信息解析出来,然后把这些信息存到事务定义器( TransactionDefinition接口的实现类) 里,并且记录哪些类或者方法需要启动事务功能,采取什么策略去执行事务。这个过程中,我们所需要做的只是给需要事务的类或者方法标注@Transactional和配置其属性而已,并不是很复杂有了@Transactional的配置, Spring就会知道在哪里启动事务机制,其约定流程如图6-2所示。
在上图中只有执行方法逻辑才是我们自己编写的,其余的流程只要你加上@Transactional,spring都会使用AOP原理帮我们弄好。
值得注意的是@Transactional最好加在实现类(ServiceImpl)上而不是其接口(Service)上,因为如果你加在接口上,那么使用JDK代理才能生效,而springboot默认的代理方式是CGLIB。
事务的开启,提交,回滚等都是有事务管理器实现的,spring的事务管理器的继承关系如下图所示:
PlatformTransactionManager源码如下
public interface PlatformTransactionManager extends TransactionManager {
//获取事务的配置
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
//提交事务
void commit(TransactionStatus status) throws TransactionException;
//回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
事务的配置是由@Transactional的配置项注入到TransactionDefinition对象里面的。
测试:
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,timeout = 1)
public int insertUser(User user) throws RuntimeException {
userMapper.insertUser(user);
throw new RuntimeException("手动生成异常");
}
@Test
@Transactional
void contextLoads() throws InterruptedException {
User user = new User();
user.setUserName("yogurt");
user.setNote("student");
service.insertUser(user);
}
断点调试如下
测试之后数据依然不会插入数据库
2.2. 事务的隔离级别
事务的隔离级别越高,出现的问题越少,同时系统的并发性越弱。所以在合适的场景选择合适的隔离级别很重要。
隔离级别分为:未提交读,读写提交,可重复读,串行化,这几种级别分别解决如下问题
一般把隔离级别设置为读写提交。MySql的默认隔离级别是可重复读,可以在@Transactional注解的配置项中修改,但是一个个修改也很麻烦,也可在配置文件中指定隔离级别
spring:
datasource:
hikari:
transaction-isolation: 2
tomcat:
default-transaction-isolation: 2
#ISOLATION_READ_UNCOMMITTED = 1
#ISOLATION_READ_COMMITTED = 2
#ISOLATION_REPEATABLE_READ = 4
#ISOLATION_SERIALIZABLE = 8
2.3. 事务的传播行为
传播行为是方法之间调用事务才去的策略问题。默认情况下我们会认为事务要么一起成功,要么一起失败,但是假设处理一个批量程序,它很多交易都成功了,只有一两个交易没有成功,我们不可能因为这一两个交易没有成功而放弃其他交易的提交。这时候就考虑到事务的传播行为了。我们只回滚那些出现异常的交易,那些完成的交易就正常提交,事情就解决了。
上图就是事务的一个传播行为,批量事务是当前事务,而批量事务执行单个交易的时候就开启了一个独立事务这就是事务的传播行为。这只是传播行为的一种,选择传播行为完全取决于业务需求
七种传播行为
其中三种加粗的是常用的,分别是REQUIRED
、 REQUIRES_NEW
、 NESTED
测试上述三种传播行为
准备批量事务,userService.insertUser上的Transactional默认就是REQUIRED
的
@Override
@Transactional
public int insertUserList(List<User> userList) {
int result = 0;
for (User user : userList) {
result += userService.insertUser(user);
}
return result;
}
测试
@RequestMapping("/insertUsers")
@ResponseBody
public int insertUsers() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
users.add(new User("iandf"+i,"student"+i));
}
return userBatchService.insertUserList(users);
}
结果如下
显然批量处理里面的事务都沿用了当前事务
测试
REQUIRES_NEW
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public int insertUser2(User user) throws RuntimeException {
return userMapper.insertUser(user);
}
当事务传播级别为REQUIRES_NEW
时,会为每个交易生成一个transaction,这样只会回滚出现异常的交易,而之前的交易可以提交到数据库。
测试
NESTED
@Override
@Transactional(propagation = Propagation.NESTED,isolation = Isolation.READ_COMMITTED)
public int insertUser(User user) throws RuntimeException {
return userMapper.insertUser(user);
}
他会为每一个交易都开启一个新事物,并且如果后面的代码出现异常只回滚到这个标志位的数据状态,这个标志位在数据库中被称之为保存点(savepoint)
NOTE:
NESTED传播行为和 REQUIRES NEW还是有区别的。 NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而 REQUIRES NEW则可以拥有自己独立的隔离级别和锁等特性,这是在应用中需要注意的地方。