事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
(1)原子性(Atomicity)
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
(3)隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
(4)持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
事务几种实现方式 (编程式事务管理、编程式事务管理)
(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(2)基于 TransactionProxyFactoryBean的声明式事务管理
(3)基于 @Transactional 的声明式事务管理
(4)基于Aspectj AOP配置事务
注:此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考。
举例说明事务不同实现
以账户转钱为例子说明。
新建持久层、业务层和客户层,并使用connectionutils实现工具类,只调用一个线程连接去实现数据库与线程的绑定减少异常导致数据处理异常。
业务层作为核心。
public class AccountServiceImpl implements AccountService {
//@Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.deleteAccount(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2、根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3、转出账户减钱
source.setMoney(source.getMoney()-money);
//4、转入账户加钱
target.setMoney(target.getMoney()+money);
//5、更新转出账户
accountDao.updateAccount(source);
//int i = 1/0;
//6、更新转入账户
accountDao.updateAccount(target);
}
}
持久层实现具体的数据操作。
public class AccountDaoImpl implements AccountDao {
//@Autowired
private QueryRunner runner;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Override
public List<Account> findAllAccount() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if (accounts == null || accounts.size() == 0){
return null;
}
if (accounts.size()>1){
throw new RuntimeException("结果不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
工具类实现线程的绑定和解绑。
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
//获取当前线程上的连接
public Connection getThreadConnection(){
Connection conn = tl.get();
try{
if (conn == null){
//获取数据源的连接
conn = dataSource.getConnection();
tl.set(conn);
}
return conn;
}
catch (Exception e){
throw new RuntimeException(e);
}
}
//把连接和线程解绑
public void removeConnection(){
tl.remove();
}
}
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils){this.connectionUtils = connectionUtils;}
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e) {
e.printStackTrace();
}
}
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}
此处使用基于xml的依赖配置和实现aop
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
<bean id="beanFactory" class="demo.Factory.BeanFactory">
<!– 注入service –>
<property name="accountService" ref="accountService"></property>
<!– 注入事务管理器 –>
<property name="txManager" ref="txManager"></property>
</bean>-->
<bean id="accountService" class="demo.service.Impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="demo.dao.Impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="connectionUtils" class="demo.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="demo.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 配置aop-->
<aop:config>
<!--配置通用切入点表达式 -->
<aop:pointcut id="pt" expression="execution(* demo.service.Impl.*.*(..))"/>
<!--配置切面 -->
<aop:aspect id="txAdvice" ref="txManager">
<aop:before method="beginTransaction" pointcut-ref="pt"></aop:before>
<aop:after-returning method="commit" pointcut-ref="pt"></aop:after-returning>
<aop:after-throwing method="rollback" pointcut-ref="pt"></aop:after-throwing>
<aop:after method="release" pointcut-ref="pt"></aop:after>
</aop:aspect>
</aop:config>
最后写个测试类进行测试。