声明式事务(注解)
spring声明事务的方式,使用注解的方式
@Transactional
⚫ 名称:@Transactional
⚫ 类型:方法注解,类注解,接口注解
⚫ 位置:方法定义上方,类定义上方,接口定义上方
⚫ 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
⚫ 范例:
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {ArithmeticException.class, IOException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRES_NEW
)
tx:annotation-driven 标签用于开启事务的注解驱动
⚫ 名称:tx:annotation-driven
⚫ 类型:标签
⚫ 归属:beans标签
⚫ 作用:开启事务注解驱动,并指定对应的事务管理器
⚫ 范例:
<tx:annotation-driven transaction-manager="txManager"/>
声明式事务(纯注解驱动)
@EnableTransactionManagement 这个注解就是代替上面的标签
⚫ 名称:@EnableTransactionManagement
⚫ 类型:类注解
⚫ 位置:Spring注解配置类上方
⚫ 作用:开启注解驱动,等同XML格式中的注解驱动
⚫ 范例:
@Configuration
@ComponentScan("com.fs")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
代码演示(代码中注释为详细解释)
数据表account
构建maven项目
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fs</groupId>
<artifactId>day04_spring_AOP_Transaction_02</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- spring整合mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- aop切面包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.93.132:3306/test
jdbc.username=root
jdbc.password=root
编写配置类
SpringConfig
package com.fs.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;
//表示这个类是一个spring的配置类,并将其存入到ioc容器中
@Configuration
/*开启扫描注解的那些包
<!-- 启动spring扫描注解的包,只扫描spring的注解-->
<context:component-scan base-package="com.fs"/>
*/
@ComponentScan("com.fs")
//引入其他的配置类
@Import({JdbcConfig.class,MybatisConfig.class,TransactionManagerConfig.class})
//开启切面自动代理
@EnableAspectJAutoProxy
//开启声明式事务管理 相当于配置文件中的 <tx:annotation-driven transaction-manager="transactionManager"/>
@EnableTransactionManagement
public class SpringConfig {
}
JdbcConfig
package com.fs.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
/*
<!-- 引入properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
*/
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
//@Value("${Key}")
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/*
<!-- 整合druid,把DruidDataSource交给spring管理-->
<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>
*/
//将返回的DruidDataSource交给ioc管理
@Bean
public DruidDataSource getDruidDataSource(){
//创建DruidDataSource
DruidDataSource druidDataSource = new DruidDataSource();
//给属性赋值,这个是上面从配置文件中依耐注入给属性的
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
//返回DruidDataSource,然后交给ioc管理
return druidDataSource;
}
}
MybatisConfig
package com.fs.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig {
/*
<!-- 配置MyBatis的会话工厂类 mybatis.spring 下的SqlSessionFactoryBean
配置的sqlSessionFactory得到SqlSession,然后MyBatis从spring中拿到SqlSession.getMapper()去动态代理dao-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 给MyBatis配置链接池,依耐注入-->
<property name="dataSource" ref="dataSource"/>
<!-- 配置别名扫描的包,被扫描的包下的类起的别名就是类名首字母小写,用于mapper.xml文件中使用-->
<property name="typeAliasesPackage" value="com.fs.pojo"/>
</bean>
*/
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
//创建sql会话工厂类 //这里@Autowired也是从ioc拿DataSource
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//从ioc容器中取出jdbcConfig中配置的druidDatasource
sqlSessionFactoryBean.setDataSource(dataSource);
//配置起别名
sqlSessionFactoryBean.setTypeAliasesPackage("com.fs.pojo");
return sqlSessionFactoryBean;
}
/*
<!--配置MyBatis扫描dao的包,让MyBatis动态代理生成这个dao的实现类,并交给ioc管理
mybatis-spring这个包下MapperScannerConfigurer提供了spring于MyBatis的整合-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 告诉MyBatis我dao在哪里.然后MyBatis将这个dao实现,然后给spring管理-->
<property name="basePackage" value="com.fs.dao"/>
</bean>
*/
//创建MyBatis动态代理扫描类
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
//创建映扫描配置类
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//设置动态代理扫描的dao的包
mapperScannerConfigurer.setBasePackage("com.fs.dao");
//交给spring管理
return mapperScannerConfigurer;
}
}
TransactionManagerConfig
package com.fs.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/*
声明式事务配置类
*/
public class TransactionManagerConfig {
/*
<!-- 将事务管理器交给ioc管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
*/
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
//使用Spring的平台事务管理器,是一个接口,使用他的实现类构造器传递一个连接池
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
//传递一个连接池
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
实体类
Account
package com.fs.pojo;
import lombok.Data;
@Data
public class Account {
private Integer id;
private String name;
private Double money;
}
Dao
AccountDao
package com.fs.dao;
import com.fs.pojo.Account;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface AccountDao {
@Select("select * from account")
List<Account> findAll();//查询所有
//根据名字修改money
@Update("UPDATE account SET money = #{money} WHERE name = #{name}")
void transferMoney(Account account);
//根据名字查询账户信息
@Select("select * from account where name = #{name}")
Account findAccountByName(@Param("name") String name);
}
service
AccountService
package com.fs.service;
import com.fs.pojo.Account;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/*
在接口上配置@Transactional,那么他下面的实现类都会进行事务管理
这个注解就相当于配置文件中的这些配置
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.fs.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
如果不写属性,该注解的默认配置为
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {ArithmeticException.class, IOException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRES_NEW
)
*/
public interface AccountService {
List<Account> findAll();
Account findAccountByName(String name);
//对这个方法开启事务使用默认配置
@Transactional
void transferMoneyAtoB(String aName,String bName,Integer money);
}
AccountServiceImpl
package com.fs.service.impl;
import com.fs.dao.AccountDao;
import com.fs.pojo.Account;
import com.fs.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.List;
/*
<!-- 把业务类 AccountServiceImpl 交给ioc管理 -->
<bean id="accountServiceImpl" class="com.fs.service.impl.AccountServiceImpl">
<!-- 依耐注入dao,这个dao被MyBatis动态代理实现后被spring存放在ioc容器中-->
<property name="accountDao" ref="accountDao"/>
</bean>
*/
@Service
public class AccountServiceImpl implements AccountService {
//从ioc获取MyBatis动态代理的accountDao实现类
@Autowired
private AccountDao accountDao;
@Autowired
private DataSource dataSource;
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
@Override
public Account findAccountByName(String name) {
return accountDao.findAccountByName(name);
}
//转账的业务实现
@Override
public void transferMoneyAtoB(String aName, String bName, Integer money) {
//先查出两个人的数据
Account aAccount = accountDao.findAccountByName(aName);
Account bAccount = accountDao.findAccountByName(bName);
//然后a减钱,b加钱
aAccount.setMoney(aAccount.getMoney()-money);
bAccount.setMoney(bAccount.getMoney()+money);
//然后调用转账方法(sql语句为更新账户)
accountDao.transferMoney(aAccount);
//制作一个异常
// int i = 1/0;
accountDao.transferMoney(bAccount);
}
}
测试类
package com.fs.service.impl;
import com.fs.config.SpringConfig;
import com.fs.pojo.Account;
import com.fs.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceImplTest {
@Autowired
private AccountService accountService;
//测试查询所有方法
@Test
public void findAll() {
List<Account> all = accountService.findAll();
System.out.println(all);
}
//测试转账方法已经使用@Transactional绑定事务
@Test
public void transferMoneyAtoB() {
//测试转账方法
accountService.transferMoneyAtoB("小付","小花",100);
}
}
执行效果
业务层转账方法正常运行成功后数据表数据
由于我们测试代码中是小付向小花转账100元,代码执行成功后应该小付900,小花1100
业务层转账方法我们给制作一个异常1/0 的/ by zero异常,
运存测试转账方法控制台输出
数据库中表的数据
小付原本900,小花1100,我们进行了转账业务,但是制作了异常,事务会进行回滚,所以金额不会发生变化