事务原本是数据库中的概念,在Dao层。但一般情况下,需要将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。
在Spring中通常可以通过以下两种方式来实现对事务的管理:
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager接口有两个常用的实现类:
事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A事务中的方法getUser()调用 B 事务中的方法getName(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。事务传播行为常量都是以PROPAGATION_ 开头,形如 PROPAGATION_XXX。
常用的有三种:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring默认的事务传播行为。
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
模拟购买商品、商品表商品数量减少、销售记录表增加记录
//商品 public class Goods { private int id; private String name; private int amount; private float price; //getter setter toString().... } //销售记录 public class Sales { private int id; private int gid; private int nums; //getter setter toString().... }
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.9</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory><!--所在的目录--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
public interface GoodsDao { Goods selectGoods(int id); int updateGoods(Goods goods); }
public interface SalesDao { int insertSales(Sales sales); }
<!-- GoodsDao对应的mapper文件 --> <mapper namespace="com.rg.dao.GoodsDao"> <select id="selectGoods" resultType="Goods" parameterType="int"> select * from goods where id=#{id} </select> <update id="updateGoods" parameterType="Goods"> update goods set amount = amount - #{amount} where id=#{id} </update> </mapper>
<!-- SalesDao对应的mapper文件 --> <mapper namespace="com.rg.dao.SalesDao"> <insert id="insertSales" parameterType="Sales"> insert into sales(gid,nums) values(#{gid},#{nums}) </insert> </mapper>
public class NotEnoughException extends RuntimeException { public NotEnoughException() { super(); } public NotEnoughException(String message) { super(message); } }
public interface BuyGoodsService { void buy(int goodId, int nums); }
public class BuyGoodsServiceImpl implements BuyGoodsService { private GoodsDao goodsDao; private SalesDao salesDao; public void setGoodsDao(GoodsDao goodsDao) { this.goodsDao = goodsDao; } public void setSalesDao(SalesDao salesDao) { this.salesDao = salesDao; } /** *购买商品 * @param goodId 商品id * @param nums 购买数量 */ @Override public void buy(int goodId, int nums) { Sales sales = new Sales(); sales.setGid(goodId); sales.setNums(nums); salesDao.insertSales(sales); Goods goods = goodsDao.selectGoods(goodId); if (goods == null) { throw new NullPointerException("无此商品"); } if (goods.getAmount() < nums) { throw new NotEnoughException("商品数量不足"); } Goods newGoods = new Goods(); newGoods.setId(goodId); newGoods.setAmount(nums); goodsDao.updateGoods(newGoods); } }
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="com.rg.dao"/> </bean> <bean id="buyService" class="com.rg.service.impl.BuyGoodsServiceImpl"> <property name="goodsDao" ref="goodsDao"/> <property name="salesDao" ref="salesDao"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
@Test public void test01() { String config = "applicationContext.xml"; ApplicationContext apx = new ClassPathXmlApplicationContext(config); BuyGoodsService buyGoodsService = (BuyGoodsService) apx.getBean("buyService"); buyGoodsService.buy(1, 120); }
这个时候如果执行程序、数据库中商品表的的商品数量不会变、但是销售记录表会增加一条记录。下边给方法添加事务。
通过@Transactional注解方式,可将事务织入到相应 public方法中,实现事务管理。
@Transactional的所有可选属性如下所示:
<!-- 声明事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource"/> </bean> <!-- tx:annotation-driven:开启注解驱动 transaction-manager:事务管理器 bean 的 id --> <tx:annotation-driven transaction-manager="transactionManager"/>
public class BuyGoodsServiceImpl implements BuyGoodsService { private GoodsDao goodsDao; private SalesDao salesDao; public void setGoodsDao(GoodsDao goodsDao) { this.goodsDao = goodsDao; } public void setSalesDao(SalesDao salesDao) { this.salesDao = salesDao; } /** *购买商品 * @param goodId 商品id * @param nums 购买数量 */ @Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = { NotEnoughException.class, NullPointerException.class } ) @Override public void buy(int goodId, int nums) { Sales sales = new Sales(); sales.setGid(goodId); sales.setNums(nums); salesDao.insertSales(sales); Goods goods = goodsDao.selectGoods(goodId); if (goods == null) { throw new NullPointerException("无此商品"); } if (goods.getAmount() < nums) { throw new NotEnoughException("商品数量不足"); } Goods newGoods = new Goods(); newGoods.setId(goodId); newGoods.setAmount(nums); goodsDao.updateGoods(newGoods); } }
这样、在方法上边使用@Transactional后、该方法就有了事务、如果发生的异常在rollbackFor的列表中、就会回滚、从而保证数据的完整性。另外、注解里边的属性值用的都是默认的,所以只写一个@Transactional也可以、在日常开发中、这种方式也是比较常用的方式。
@Transactional @Override public void buy(int goodId, int nums) { //....... }