事务:事务指数据库中多个操作合并在一起形成的操作序列。
3个API:
为接口,最终要操作的是他的实现类,它的实现类非常多:
此接口定义了事务的基本操作
TransactionStatus getTransaction(TransactionDefinition definition)
// 参数为事务定义对象,返回的是事务的状态
void commit(TransactionStatus status)
void rollback(TransactionStatus status)
TransactionDefinition,此接口定义了事务的基本信息
获取事务定义名称: String getName()
获取事务的读取属性: boolean isReadOnly()
获取事务隔离级别: int getIsolationLevel()
值有:int ISOLATION_DEFAULT=-1, int ISOLATION_READ_UNCOMMITTED=1…
获取事务超时时间: int getTimeout() 默认:int TIMEOUT_DEFAULT=-1 (永不超时)
获取事务传播行为特征: int getPropagationBehavior()
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的操作状态
1、获取事务是否处于新开启事务状态
boolean isNewTransaction()
2、获取事务是否处于已完成状态
boolean isCompleted()
3、获取事务是否处于回滚状态
boolean isRollbackOnly()
4、刷新事务状态
void flush()
5、获取事务是否具有回滚存储点
boolean hasSavepoint()
6、设置事务处于回滚状态
void setRollbackOnly()
ApplictionContext.xml 业务层需要注入dataSource
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}" />
bean>
<bean id="accountService" class="com.lvmanba.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" >
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.lvmanba.domain" />
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lvmanba.dao"/>
bean>
<bean id="txAdvice" class="com.lvmanba.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="transactionManager" pointcut-ref="pt" />
aop:aspect>
aop:config>
AccountServiceImpl
public void transfer(String outName, String inName, Double money) {
//原始代码: 如果在没有出现异常的情况下,可以正常运行,如果出现异常,将会导致金额出错
/*accountDao.inMoney(outName,money);
accountDao.outMoney(inName,money);*/
//代码改进,使用事务
//1,开启事务(使用接口)为事务设置与数据层相同的数据源
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource); //使用构造方法进入数据源,或者可以使用下面方法.
/*
DataSourceTransactionManager ptm = new DataSourceTransactionManager();
ptm.setDataSource(dataSource);
*/
//事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
//2、事务的状态
TransactionStatus ts = ptm.getTransaction(td); //保持状态一致
accountDao.inMoney(outName,money);
int sum = 1/0;
accountDao.outMoney(inName,money);
//提交事务
ptm.commit(ts); //参数为事务状态
}
如果使用多个事务,将会产生多重复的代码,这时候就应该想到AOP,将业务层的事务处理功能抽出来制作成AOP通知,利用环绕通知运行期动态织入。
使用AOP控制事务
1、在pom.xml导入依赖
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.8.RC1version>
<scope>runtimescope>
dependency>
2、通知类 com.lvmanba.aop.TxAdvice.java
//AOP around 控制事务
public class TxAdvice {
//注入dataSource
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object transactionManager(ProceedingJoinPoint jpj) throws Throwable {
//创建事务管理器
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
//定义事务对象
TransactionDefinition td = new DefaultTransactionDefinition();
//获取事务状态
TransactionStatus ts = ptm.getTransaction(td); //需要事务定义对象
Object proceed = jpj.proceed(jpj.getArgs()); //原始方法是有参数的,这里写传递参数,如果不写,也会自动传参
//提交事务
ptm.commit(ts); //需要事务状态参数
return proceed;
}
}
3、配置AOP
//加入声明
<beans
...
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="...
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
!-- 设置AOP-->
<bean id="txAdvice" class="com.lvmanba.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="transactionManager" pointcut-ref="pt" />
aop:aspect>
aop:config>
beans>
目的是将通知类放入XML文件中。
1、导入tx命名空间
2、使用tx配置事务
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false"/>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true"/>
<tx:method name="transfer" read-only="false" />
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.lvmanba.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
aop:config>
3、可以删除com.lvmanba.aop.TxAdvice.java
tx标签说明
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
tx:attributes>
tx:advice>
目标是把下列代码替换掉
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method
name="transfer"
read-only="false"
timeout="-1"
isolation="DEFAULT"
no-rollback-for=""
rollback-for=""
propagation="REQUIRED"
/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.lvmanba.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
aop:config>
1、在需要的事务的方法上面使用@Transactional
@Transactional 替换下面内容
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.lvmanba.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
aop:config>
2、开启注解驱动
<tx:annotation-driven transaction-manager="txManager"/>
替换下列内容
<tx:advice id="txAdvice" transaction-manager="txManager"> <!--相当于<bean id="txAdvice">,需要一个事务管理器...-->
<tx:attributes>
<!--这里必须重写进行限制,否则事务不起作用-->
<tx:method
name="transfer"
read-only="false"
timeout="-1"
isolation="DEFAULT"
no-rollback-for=""
rollback-for=""
propagation="REQUIRED"
/>
</tx:attributes>
</tx:advice>
替换后的要使用事务的方法代码如下:
@Transactional(
readOnly = true,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {},//java.lang.ArithmeticException.class, IOException.class
noRollbackFor = {},
propagation = Propagation.REQUIRED
)
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName,money);
//int a = 1/0;
accountDao.outMoney(inName,money);
}
现在是在业务层方法上,应该配置在接口上,这样实现的时候直接使用
public interface AccountService {
/*
@param outName 出账用户名
@param inName 入账用户名
@param money 转出金额
* */
@Transactional(
readOnly = true,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {},//java.lang.ArithmeticException.class, IOException.class
noRollbackFor = {},
propagation = Propagation.REQUIRED
)
public void transfer(String outName,String inName,Double money);
}
我们放在接口里面的方法,也可以直接放在接口上面,这样下面的方法都具有了事务。
1、替换Mybatis映射配置文件
原始:dao.AccountDao.java
public interface AccountDao {
void inMoney(@Param("name") String name, @Param("money") Double money);
void outMoney(@Param("name") String name,@Param("money")Double money);
}
对应的Mybatis 映射配置文件
update account set money=money + #{ money } where name = #{ name }
update account set money = money - #{ money } where name = #{ name }
去掉映射配置文件,将sql加入到:dao.AccountDao.java,后为:
public interface AccountDao {
@Update("update account set money=money + #{ money } where name = #{ name }")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update account set money=money - #{ money } where name = #{ name }")
void outMoney(@Param("name") String name,@Param("money")Double money);
}
Spring集成了很多模板,大大提高了书写效率,常见的模板对象有以下几种。
public class JDBCConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
//注册JdbcTempate模板对象bean
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
//注入模板对象
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void save(Account account) {
String sql="insert into account(name,money)values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
@Override
public void delete(Integer id) {
String sql="delete from account where id=?";
jdbcTemplate.update(sql,id);
}
@Override
public void update(Account account) {
String sql="update account set name=?,money=? where id=?";
jdbcTemplate.update(sql,account.getName(),account.getMoney(),account.getId());
}
@Override
public String findNameById(Integer id) {
String sql="select name from account where id=?";
//单个字段查询可以使用专用的查询方法,必须制定查询出数据的类型,例如name为String类型
return jdbcTemplate.queryForObject(sql,String.class,id);
}
@Override
public Account findById(Integer id) {
String sql="select * from account where id=?";
//支持自定义行映射解析器
RowMapper<Account> rm = (rs, rowNum) -> {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
};
return jdbcTemplate.queryForObject(sql,rm,id);
}
@Override
public List<Account> findAll() {
String sql="select * from account";
//使用spring自带的行映射解析器,要求必须是标准封装
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<Account>(Account.class));
}
@Override
public List<Account> findAll(int pageNum, int preNum) {
String sql="select * from account limit ?,?";
//分页数据通过查询参数赋值
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<Account>(Account.class),(pageNum-1)*preNum,preNum);
}
@Override
public Long getCount() {
String sql="select count(id) from account";
//单个字段查询可以使用专用的查询方法,必须制定查询出的数据类型,例如数据总量为Long类型。
return jdbcTemplate.queryForObject(sql,Long.class);
}
}