spring与mybatis整合配置可参考上篇Spring系列(七)、Spring与MyBatis框架整合
在spring中事务是自动提交的,但是我们在操作数据的时候,总有些业务流程需要事务控制。
在项目中,业务层(Service层)既是处理业务的地方,业务层编写又是管理数据库事务的地方,要对事务进行测试,首先创建业务层,并在业务层编写添加用户取款、存款和转账操作的代码后,有意的咱取款处添加一行异常代码(如:int i = 1/0;)或者在数据库中添加一个账户余额最小不低于1元的约束条件,来模拟现实中的意外情况,最后编码测试方法,这样程序在执行到错误代码时就会出现异常,在没有事务管理的情况下,即使出现了取款(转出)异常,存款方依然成功存入了(转入)转账金额,这样银行就亏了;如果添加了事务管理机制,并且事务管理的配置正确,那么在执行上述操作时,转账双方,只要有任意一方在执行业务的过程中出现了异常,事务就会发生回滚,保证事务同时成功或失败!
分析:
提示:
对哪些方法,采取什么样的事务策略;
配置步骤:
propagation有以下属性:
参数 | 作用 |
---|---|
REQUIERD | 如果当前没有事务,就新建一个事务;若果已存在一个事务,加入到这个事务中,这是最常见的选择 |
SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方法执行 |
MANDATORY | 使用当前事务,如果没有当前事务,就抛出异常 |
REQUIERD_NEW | 新建事务,如果当前存在事务,就把当前事务挂起 |
NOT_SOPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
NEVER | 以非事务方式执行操作,如果当前事务存在则抛出异常 |
NESTED | 则在嵌套事务内执行;如果当前没有事务,则执行与REQUIRED类似的事务 |
-- Create table
create table ACCOUNT
(
accountid VARCHAR2(20),
accountname VARCHAR2(20),
accountmoney NUMBER(11)
)
-- 添加约束(账户金额不能低于1元)
alter table ACCOUNT add constraint CK_ACCOUNT_MONEY check (accountmoney>=1);
--添加两条数据
insert into account values('1001','张三',100);
insert into account values('1002','李四',1);
package com.pojo;
import java.io.Serializable;
/**
* @author 一宿君(CSDN : qq_52596258)
* @date 2021-07-20 10:18:37
*/
public class Account implements Serializable {
private String accountid;
private String accountname;
private float accountmoney;
public String getAccountid() {
return accountid;
}
public void setAccountid(String accountid) {
this.accountid = accountid;
}
public String getAccountname() {
return accountname;
}
public void setAccountname(String accountname) {
this.accountname = accountname;
}
public float getAccountmoney() {
return accountmoney;
}
public void setAccountmoney(float accountmoney) {
this.accountmoney = accountmoney;
}
}
package com.dao;
import org.apache.ibatis.annotations.Param;
import javax.ws.rs.PATCH;
/**
* @author 一宿君(CSDN : qq_52596258)
* @date 2021-07-20 10:20:04
*/
public interface AccountDaoMapper {
/**
* 取款
* @param accountid 取款账户id
* @param money 取款金额
* @return
*/
public int takeMoney(@Param("accountid") String accountid, @Param("money") float money);
/**
* 存款
* @param accountid 存款账户id
* @param money 存款金额
* @return
*/
public int saveMoney(@Param("accountid") String accountid,@Param("money")float money);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace是命名空间,要与对应的接口的路径保持一致(如果没有创建该接口,MyBatis在内部编译时会自动创建)-->
<mapper namespace="com.dao.AccountDaoMapper">
<!--取款-->
<update id="takeMoney">
update account set accountmoney = accountmoney - #{money} where accountid = #{accountid}
</update>
<!--存款-->
<update id="saveMoney">
update account set accountmoney = accountmoney + #{money} where accountid = #{accountid}
</update>
</mapper>
package com.service;
/**
* @author 一宿君(CSDN : qq_52596258)
* @date 2021-07-20 10:30:13
*/
public interface AccountService {
/**
* 转账
* @param accountid1 取款id
* @param accountid2 存款id
* @param money 转账金额
*/
public int transMoney(String accountid1,String accountid2,float money);
}
package com.service.impl;
import com.dao.AccountDaoMapper;
import com.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.ejb.TransactionManagement;
/**
* @author 一宿君(CSDN : qq_52596258)
* @date 2021-07-20 10:32:59
*/
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDaoMapper accountDaoMapper;
/**
* 取款
* @param accountid1 取款id
* @param accountid2 存款id
* @param money 转账金额
*/
@Override
public int transMoney(String accountid1, String accountid2, float money) {
//如果先取款
int r1 = accountDaoMapper.takeMoney(accountid1,money);
//后存款
int r2 = accountDaoMapper.saveMoney(accountid2,money);
return 1;
}
}
package com.test;
import com.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 一宿君(CSDN : qq_52596258)
* @date 2021-07-20 10:47:10
*/
public class TestTransactionManager {
public static void main(String[] args) {
ApplicationContext ap = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService)ap.getBean("accountServiceImpl");
String accountid1 = "1001";//张三账户id
String accountid2 = "1002";//李四账户id
float money = 100;//转账金额
int r = accountService.transMoney(accountid1,accountid2,money);
System.out.println();
}
}
执行结果:
上述没有开启事务管理,转账失败了,是因为违反了accountmoney键的约束,不能低于1元,因为张三要转出100元,他的账户中只有100元,所以转出后零,就违反了约束条件,所以转账失败!
要是出现上述情况,张三能高兴死!银行能亏死!所以我们要引入事务机制,要保证事务能够同时成功或同时失败!
<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"
xmlns:bean="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<context:property-placeholder location="database.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="filters" value="${jdbc.filters}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="maxWait" value="${jdbc.maxWait}"/>
<property name="minIdle" value="${jdbc.minIdle}"/>
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
<property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
<property name="testOnReturn" value="${jdbc.testOnReturn}"/>
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="mybatis-conf.xml"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"/>
bean>
<context:component-scan base-package="com.service"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="trans*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="SUPPORTS"/>
<tx:method name="find*" propagation="SUPPORTS"/>
<tx:method name="search*" propagation="SUPPORTS"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pointcutServiceAdvice" expression="execution(* com.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcutServiceAdvice"/>
aop:config>
beans>
业务逻辑层实现类AccountServiceImpl
package com.service.impl;
import com.dao.AccountDaoMapper;
import com.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.ejb.TransactionManagement;
/**
* @author 一宿君(CSDN : qq_52596258)
* @date 2021-07-20 10:32:59
*/
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDaoMapper accountDaoMapper;
/**
* 取款
* @param accountid1 取款id
* @param accountid2 存款id
* @param money 转账金额
*/
@Override
/*@Transactional(propagation = Propagation.REQUIRED)*/
public int transMoney(String accountid1, String accountid2, float money) {
//如果先存款
int r2 = accountDaoMapper.saveMoney(accountid2,money);
//后取款
int r1 = accountDaoMapper.takeMoney(accountid1,money);
return 1;
}
}
效果和上述在xml文件中配置的事务管理机制是一样的,在以后的开发中,注解是使用最广泛的,暂时理解这么多,妥了!!!