数据事务是企业应用关心的核心内容。在事务的实操之前先说明一些数据库的相关概念。
一.数据库事务的ACID特性
数据库事务正确实行的4个基础要素是原子性atomit,一致性Consistency,隔离性Isolation,和持久性Durability。
原子性:事务中的所有操作要么成功、要么失败,不能处于中间状。
一致性: 一旦一个事务完成,将来的所有事务都必须基于这个完成后的状态。
隔离性:未完成的事务之间不会互相影响。
持久性:在事务完成以后,该事务对数据库的修改会持久保存在数据库之中,不会被回滚。
二.事务的隔离级别
按照SQL的标准规范,数据库的隔离级别分为4层:脏读dirty-read,读/写提交read-commit,可重复度repeatable-read,序列化serializable。
其中脏读是最低的隔离级别,其含义是允许一个事务去读取另一个事务未提交的数据。为了克服脏读,SQL标准提出了第二个隔离级别,读写提交。读写提交顾名思义就是一个事务只能读取另外一个事务已经提交了的数据。但是还存在一个问题,就是不可重复读。为了克服不可重复读的问题,SQL标准提出了第三个隔离级别,可重复读。在可重复读中,是针对数据库的同一条记录而言的,但对于数据库的很多场景,是需要对多条记录进行读写的,这个时候就会出现幻读,所以SQL标准提出了第四个隔离级别,序列化。它是最高的隔离级别,让SQL按照顺序进行读写,但同时也是并发性最差的隔离级别。在大多数场景中,使用读写提交,可以有利于提高并发,并且抑制脏读。关于更详细的解释,可以看 点击打开链接
MySql中支持4类隔离级别,默认的隔离级别是可重复读。而Oracle中只有读写提交和序列化,默认的隔离级别是续写提交。
三.事务的传播行为
传播行为是指方法之间的调用事务策略的问题。
在Spring中有这7种传播行为。
1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
四.使用事务
传播行为 PROPAGATION_REQUIRES_NEW 隔离级别为读写提交来说明Spring中的事务,同时从中也可以看出Spring是如何整合MyBatis的,以及MyBatis是如何使用的。Spring中使用事务有很多种方式,有编程式事务和声明式事务,编程式用的比较少。声明式中有XML和注解方式。一般用的比较多的是@Transactionl注解。它注解在对应的方法上,底层原理上由Spring AOP来完成事务的流程。如果注解的方法没有发生异常,则提交事务。如果发生异常则回滚事务(也可以通过配置信息仍允许提交事务)。这里以为@Transactionl注解为例子演示事务。
1.构建Pojo类:
package pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Role {
private Long id;
private String roleName;
private String note;
public Role(){
this.roleName = "roleName_test_database_trasaction_1";
this.note = "note_test_database_trasaction_1";
}
//setter getter
}
2.两个Service类接口:
public interface RoleService {
int insertRole(Role role);
}
public interface RoleListService {
int insertRoleList(List roleList);
}
3.两个Service类接口的实现类,尽量采用接口+实现的方式来写业务类,这样可以将定义和实现分开。同时面向接口编程也方便后期的拓展。
@Component
public class RoleServiceImpl implements RoleService {
@Autowired
RoleMapper roleMapper;
@Override
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRES_NEW)
public int insertRole(Role role) {
// TODO Auto-generated method stub
return roleMapper.insertRole(role);
}
}
@Component
public class RoleListServiceImpl implements RoleListService {
static Logger log = Logger.getLogger(RoleListServiceImpl.class);
@Autowired
RoleService roleService;
@Override
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED)
public int insertRoleList(List roleList) {
// TODO Auto-generated method stub
int count = 0;
for(Role role : roleList) {
try {
count += roleService.insertRole(role);
}catch(Exception e) {
log.info(e);
}
}
return count;
}
}
4.MyBatis中的mapper映射器和对应的xml。RoleMapper的@Repository是为了告诉Spring这个是一个数据库访问层,也就是MyBatis中的mapper,在后面的配置文件中会对此进行组件扫描,这样就可以直接通过得到Bean的方式得到mapper了。
@Repository
public interface RoleMapper {
int insertRole(Role role);
}
insert into t_role(role_name,note) values(#{roleName},#{note});
5.配置文件
5.1 log4j配置文件
log4j.rootLogger=DEBUG , stdout
log4j.logger.org.mybatis=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
5.2 MyBatis配置文件
5.3 Spring配置文件,Spring中事务通过TransactionManager事务管理器来进行管理。这里在配置文件中配置即可。同时最后添加使用注解定义事务。
6.MySql数据库名数据表需要自己构建一下。同时数据库的名字需要在配置文件中进行修改。
表名为t_role。字段为 id, role_name,note。
7.测试类
public class TestDataBase {
static Logger log = Logger.getLogger(TestDataBase.class);
public static void main(String[] args) {
// TODO Auto-generated method stub
testSpringMyBatisWithTransaction();
}
private static void testSpringMyBatisWithTransaction() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config-database.xml");
RoleListService roleService = context.getBean(RoleListService.class);
List roleList = new ArrayList<>();
roleList.add(new Role());
roleList.add(new Role());
roleService.insertRoleList(roleList);
context.close();
}
}
8.测试结果中截取一部分来看。可以看到总共创建了三个事务。
DEBUG 2018-05-31 20:27:39,162 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] for JDBC transaction
DEBUG 2018-05-31 20:27:39,166 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to 2
DEBUG 2018-05-31 20:27:39,167 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to manual commit
DEBUG 2018-05-31 20:27:39,168 org.springframework.transaction.support.AbstractPlatformTransactionManager: Suspending current transaction, creating new transaction with name [service.RoleServiceImpl.insertRole]
Thu May 31 20:27:39 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
DEBUG 2018-05-31 20:27:39,173 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] for JDBC transaction
DEBUG 2018-05-31 20:27:39,174 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to 2
DEBUG 2018-05-31 20:27:39,175 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to manual commit
DEBUG 2018-05-31 20:27:39,180 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Creating a new SqlSession
DEBUG 2018-05-31 20:27:39,185 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1b1cfb87]
DEBUG 2018-05-31 20:27:39,201 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] will be managed by Spring
DEBUG 2018-05-31 20:27:39,205 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: ==> Preparing: insert into t_role(role_name,note) values(?,?);
DEBUG 2018-05-31 20:27:39,232 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: ==> Parameters: roleName_test_database_trasaction_1(String), note_test_database_trasaction_1(String)
DEBUG 2018-05-31 20:27:39,312 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: <== Updates: 1
DEBUG 2018-05-31 20:27:39,316 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1b1cfb87]
DEBUG 2018-05-31 20:27:39,317 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1b1cfb87]
DEBUG 2018-05-31 20:27:39,317 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1b1cfb87]
DEBUG 2018-05-31 20:27:39,317 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1b1cfb87]
DEBUG 2018-05-31 20:27:39,318 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit
DEBUG 2018-05-31 20:27:39,318 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J]
DEBUG 2018-05-31 20:27:39,355 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to 4
DEBUG 2018-05-31 20:27:39,356 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] after transaction
DEBUG 2018-05-31 20:27:39,356 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG 2018-05-31 20:27:39,357 org.springframework.transaction.support.AbstractPlatformTransactionManager: Resuming suspended transaction after completion of inner transaction
DEBUG 2018-05-31 20:27:39,357 org.springframework.transaction.support.AbstractPlatformTransactionManager: Suspending current transaction, creating new transaction with name [service.RoleServiceImpl.insertRole]
DEBUG 2018-05-31 20:27:39,358 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] for JDBC transaction
DEBUG 2018-05-31 20:27:39,358 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to 2
DEBUG 2018-05-31 20:27:39,359 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to manual commit
DEBUG 2018-05-31 20:27:39,360 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Creating a new SqlSession
DEBUG 2018-05-31 20:27:39,360 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f58853c]
DEBUG 2018-05-31 20:27:39,361 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] will be managed by Spring
DEBUG 2018-05-31 20:27:39,361 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: ==> Preparing: insert into t_role(role_name,note) values(?,?);
DEBUG 2018-05-31 20:27:39,361 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: ==> Parameters: roleName_test_database_trasaction_1(String), note_test_database_trasaction_1(String)
DEBUG 2018-05-31 20:27:39,382 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: <== Updates: 1
DEBUG 2018-05-31 20:27:39,383 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f58853c]
DEBUG 2018-05-31 20:27:39,383 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f58853c]
DEBUG 2018-05-31 20:27:39,383 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f58853c]
DEBUG 2018-05-31 20:27:39,384 org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f58853c]
DEBUG 2018-05-31 20:27:39,384 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit
DEBUG 2018-05-31 20:27:39,385 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J]
DEBUG 2018-05-31 20:27:39,441 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to 4
DEBUG 2018-05-31 20:27:39,443 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] after transaction
DEBUG 2018-05-31 20:27:39,443 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG 2018-05-31 20:27:39,443 org.springframework.transaction.support.AbstractPlatformTransactionManager: Resuming suspended transaction after completion of inner transaction
DEBUG 2018-05-31 20:27:39,443 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit
DEBUG 2018-05-31 20:27:39,444 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J]
DEBUG 2018-05-31 20:27:39,445 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] to 4
DEBUG 2018-05-31 20:27:39,446 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter5?serverTimezone=UTC, UserName=root@localhost, MySQL Connector/J] after transaction
DEBUG 2018-05-31 20:27:39,446 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
MySql默认的事务级别为4也就是可重复读。在创建事务的时候变成了2也就是读写提交,事务结束的时候重置回了4。TransactionDefinition源码里是定义的
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads can occur.
* This level allows a row changed by one transaction to be read
* by another transaction before any changes in that row have been
* committed (a "dirty read"). If any of the changes are rolled back,
* the second transaction will have retrieved an invalid row.
*/
int TRANSACTION_READ_UNCOMMITTED = 1;
/**
* A constant indicating that
* dirty reads are prevented; non-repeatable reads and phantom
* reads can occur. This level only prohibits a transaction
* from reading a row with uncommitted changes in it.
*/
int TRANSACTION_READ_COMMITTED = 2;
/**
* A constant indicating that
* dirty reads and non-repeatable reads are prevented; phantom
* reads can occur. This level prohibits a transaction from
* reading a row with uncommitted changes in it, and it also
* prohibits the situation where one transaction reads a row,
* a second transaction alters the row, and the first transaction
* rereads the row, getting different values the second time
* (a "non-repeatable read").
*/
int TRANSACTION_REPEATABLE_READ = 4;
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads are prevented.
* This level includes the prohibitions in
* TRANSACTION_REPEATABLE_READ
and further prohibits the
* situation where one transaction reads all rows that satisfy
* a WHERE
condition, a second transaction inserts a row that
* satisfies that WHERE
condition, and the first transaction
* rereads for the same condition, retrieving the additional
* "phantom" row in the second read.
*/
int TRANSACTION_SERIALIZABLE = 8;