目录
具体的看数据库中关于事务的知识点,这里做一个大概
1. 什么是事务?
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
2. 在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的
在java代码中写程序,控制事务,此时事务应该放在那里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句
3. 通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务 jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback(); mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback(); hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
4. 问题中事务的处理方式,有什么不足
总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法
5. 怎么解决不足 spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理 使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。
6. 处理事务,需要怎么做,做什么 spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了
接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。 mybatis访问数据库---spring创建好的是DataSourceTransactionManager hibernate访问数据库----spring创建的是HibernateTransactionManager
怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢? 声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用声明就可以了 例如,你要使用mybatis访问数据库,你应该在xml配置文件中
<bean id=“xxx" class="...DataSourceTransactionManager">
1. 说明方法需要的事务: 事务的隔离级别:有4个值。 DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。 ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。 ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。 ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 ➢ SERIALIZABLE:串行化。不存在并发问题。
1. 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚 * 单位是秒, 整数值, 默认是 -1. 2. 事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的, 7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的,记得前三个就行 * PROPAGATION_REQUIRED * PROPAGATION_REQUIRES_NEW * PROPAGATION_SUPPORTS * PROPAGATION_MANDATORY * PROPAGATION_NESTED * PROPAGATION_NEVER * PROPAGATION_NOT_SUPPORTED 3. 事务提交事务,回滚事务的时机 1. 当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit 2. 当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback 运行时异常的定义: RuntimeException 和他的子类都是运行时异常, 例如NullPointException , NumberFormatException 3. 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务 受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException
7. 总结spring的事务
spring的事务是一个统一模型
指定要使用的事务管理器实现类,使用
指定哪些类,哪些方法需要加入事务的功能
指定方法需要的隔离级别,传播行为,超时
你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为
购买商品,用户下单,向销售表中添加销售记录,从商品表中减少数据
创建数据表sale(销售表)和goods(商品表)
sale:id自增,方便后面测试
goods:id不自增,
注意,平时开发price用decimal类型
向goods表示适当的添加两天数据,用于测试
<?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.md</groupId> <artifactId>07-spring-trans</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--spring核心--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--spring事务用到的--> <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> <!--mybatis的--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <!--mybatis和spring集成的--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!--mysql驱动--> <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> <!--目的是把src/main/java目录中的xml文件包含到输出结果中,也就是输出到classes目录中--> <resources> <resource> <directory>src/main/java</directory><!--所在的目录--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build> </project>
package com.md.domain; /** * @author MD * @create 2020-08-11 9:20 */ public class Sale { private Integer id; private Integer gid; private Integer nums; public Sale() { } public Sale(Integer id, Integer gid, Integer nums) { this.id = id; this.gid = gid; this.nums = nums; } public void setId(Integer id) { this.id = id; } public void setGid(Integer gid) { this.gid = gid; } public void setNums(Integer nums) { this.nums = nums; } public Integer getId() { return id; } public Integer getGid() { return gid; } public Integer getNums() { return nums; } @Override public String toString() { return "SaleDao{" + "id=" + id + ", gid=" + gid + ", nums=" + nums + '}'; } } //----------------- package com.md.domain; /** * @author MD * @create 2020-08-11 9:21 */ public class Goods { private Integer id; private String name; private Integer amount; //实际开发中不用float private Float price; public Goods() { } public Goods(Integer id, String name, Integer amount, Float price) { this.id = id; this.name = name; this.amount = amount; this.price = price; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAmount() { return amount; } public void setAmount(Integer amount) { this.amount = amount; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } @Override public String toString() { return "Goods{" + "id=" + id + ", name='" + name + '\'' + ", amount=" + amount + ", price=" + price + '}'; } } //--------------------------------
package com.md.dao; import com.md.domain.Sale; /** * @author MD * @create 2020-08-11 9:24 */ public interface SaleDao { // 增加销售记录 int insertSale(Sale sale); } //---------------------- package com.md.dao; import com.md.domain.Goods; /** * @author MD * @create 2020-08-11 9:30 */ public interface GoodsDao { // 更新库存 int updateGoods(Goods goods); // 查询商品的信息,根据id Goods selectGoods(Integer id); }
SaleDao.xml
<?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"> <mapper namespace="com.md.dao.SaleDao"> <insert id="insertSale"> insert into sale(gid,nums) values(#{gid},#{nums}) </insert> </mapper>
GoodsDao.xml
<?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"> <mapper namespace="com.md.dao.GoodsDao"> <select id="selectGoods" resultType="com.md.domain.Goods"> select id , name , amount , price from goods where id=#{id} </select> <!-- --> <update id="updateGoods"> update goods set amount = amount - #{amount} where id=#{id} </update> </mapper>
定义service层可能抛出的异常类
package com.md.excep; /** * @author MD * @create 2020-08-11 9:49 */ // 自定义的运行时异常 public class NotEnoughException extends RuntimeException { public NotEnoughException() { super(); } public NotEnoughException(String message) { super(message); } }
package com.md.service; /** * @author MD * @create 2020-08-11 9:43 */ public interface BuyGoodsService { // 购买商品,goodsId:购买商品的编号,nums:购买的数量 void buy(Integer goodsId , Integer nums); } //----------------------------------- package com.md.service.impl; import com.md.dao.GoodsDao; import com.md.dao.SaleDao; import com.md.domain.Goods; import com.md.domain.Sale; import com.md.excep.NotEnoughException; import com.md.service.BuyGoodsService; /** * @author MD * @create 2020-08-11 9:45 */ public class BuyGoodsServiceImpl implements BuyGoodsService { private SaleDao saleDao; private GoodsDao goodsDao; public void setSaleDao(SaleDao saleDao) { this.saleDao = saleDao; } public void setGoodsDao(GoodsDao goodsDao) { this.goodsDao = goodsDao; } @Override public void buy(Integer goodsId, Integer nums) { System.out.println("=========buy方法开始==========="); // 记录销售记录,向sale表中添加数据 Sale sale = new Sale(); sale.setGid(goodsId); sale.setNums(nums); saleDao.insertSale(sale); // 先查询该商品 Goods goods = goodsDao.selectGoods(goodsId); if (goods == null){ // 商品不存在 throw new NullPointerException("编号:"+goodsId+" 的商品不存在"); }else if (goods.getAmount() < nums){ // 商品库存不足 throw new NotEnoughException("编号:"+goodsId+" 的商品不足,只能购买: "+goods.getAmount()+" 个"); } // 更新库存 Goods buyGoods = new Goods(); buyGoods.setId(goodsId); buyGoods.setAmount(nums); goodsDao.updateGoods(buyGoods); System.out.println("=========buy方法结束==========="); } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--settings:控制mybatis全局行为--> <settings> <!--设置mybatis输出日志--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--设置别名--> <typeAliases> <!--name:实体类所在的包名--> <package name="com.md.domain"/> </typeAliases> <!-- sql映射文件的位置 --> <mappers> <!--name是包名,这个包中所有mapper.xml一次加载--> <package name="com.md.dao"/> </mappers> </configuration>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 把数据库的配置信息写在一个独立的文件中,编译修改数据库的配置内容 让spring知道jdbc.properties文件的位置 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!--声明数据源DataSource,作用是连接数据库--> <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!--set注入提供连接数据库信息--> <!--<property name="url" value="jdbc:mysql://localhost:3306/ssm" />--> <!--<property name="username" value="root" />--> <!--<property name="password" value="123456" />--> <!--<property name="maxActive" value="20" />--> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}" /> </bean> <!--SqlSessionFactory--> <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory--> <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--set注入,把数据库连接池付给dataSource属性--> <property name="dataSource" ref="myDataSource"/> <!--mybatis主配置文件的位置 configLocation属性是Resource类型,读取配置文件 它的赋值使用的是value , 指定文件的路径,使用的是classpath:表示文件的位置 --> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!--创建 dao对象 使用SqlSession的getMapper(StudentDao.class) MapperScannerConfigurer在内部调用getMapper()生成每个dao接口的代理对象 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定的是SqlSessionFactory对象的id--> <property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/> <!--指定包名,包名是dao接口所在的包名 MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行 一次getMapper()方法,得到每个接口的dao对象 创建好的dao对象放入到spring的容器中 dao默认对象的名称:是接口名字的首字母小写 --> <property name="basePackage" value="com.md.dao"/> <!--多个包--> <!--<property name="basePackage" value="com.md.dao,com.md.dao2"/>--> </bean> <!--上面的这个是一个模板,只有最后dao对象的这个包名的value的值是根据自己创建写的--> <!--下面的就是自己定义的service--> <!--声明service--> <bean id="buyGoodsService" class="com.md.service.impl.BuyGoodsServiceImpl"> <!--就是上面通过创建的dao对象,在service接口的实现类中使用--> <property name="saleDao" ref="saleDao"/> <property name="goodsDao" ref="goodsDao"/> </bean> </beans>
@Test public void test01(){ String config = "applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); // 从容器中获取service,你声明service时候的id BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService"); buyGoodsService.buy(1001,101); }
此时程序一切正常
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理
主要记住前三个就行了
@Transactional 的所有可选属性如下所示:
需要注意的是,
@Transactional 若用在方法上,只能用于 public 方法上
对于其他非 public方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤:
把上面写好的程序重新复制一份,内容不变
还是在Spring的配置文件中加入
<!--1. 声明事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 连接数据库,指定数据源--> <property name="dataSource" ref="myDataSource"/> </bean>
<!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象 --> <!--注意:选择结尾是tx的driven--> <!--transaction-manager:事务管理器对象的id--> <tx:annotation-driven transaction-manager="transactionManager"/>
<?xml version="1.0" encoding="UTF-8"?> <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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:property-placeholder location="classpath:jdbc.properties"/> <!--声明数据源DataSource,作用是连接数据库--> <bean id="myDataSource" 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="maxActive" value="${jdbc.maxActive}" /> </bean> <!--SqlSessionFactory--> <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!--创建 dao对象--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/> <property name="basePackage" value="com.md.dao"/> </bean> <!--声明service--> <bean id="buyGoodsService" class="com.md.service.impl.BuyGoodsServiceImpl"> <!--就是上面通过创建的dao对象--> <property name="saleDao" ref="saleDao"/> <property name="goodsDao" ref="goodsDao"/> </bean> <!-- 使用spring的事务处理 --> <!--1. 声明事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 连接数据库,指定数据源--> <property name="dataSource" ref="myDataSource"/> </bean> <!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象 --> <!--注意:选择结尾是tx的driven--> <!--transaction-manager:事务管理器对象的id--> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 3. 在需要事务方法上加注解 --> </beans>
在方法上面加@Transactional
这里也就是在service接口的实现类里的方法上,全部代码如下:
package com.md.service.impl; import com.md.dao.GoodsDao; import com.md.dao.SaleDao; import com.md.domain.Goods; import com.md.domain.Sale; import com.md.excep.NotEnoughException; import com.md.service.BuyGoodsService; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * @author MD * @create 2020-08-11 9:45 */ public class BuyGoodsServiceImpl implements BuyGoodsService { private SaleDao saleDao; private GoodsDao goodsDao; public void setSaleDao(SaleDao saleDao) { this.saleDao = saleDao; } public void setGoodsDao(GoodsDao goodsDao) { this.goodsDao = goodsDao; } /** *rollbackFor:表示发生指定的异常一定回滚 * */ // @Transactional( // propagation = Propagation.REQUIRED, // isolation = Isolation.DEFAULT, // readOnly = false, // rollbackFor = { // NullPointerException.class,NotEnoughException.class // } // ) // 都使用默认值也是可以的,默认的传播行为是REQUIRED,默认的隔离级别是DEFAULT // 默认抛出运行时异常,回滚事务 @Transactional @Override public void buy(Integer goodsId, Integer nums) { System.out.println("=========buy方法开始==========="); // 记录销售记录,向sale表中添加数据 Sale sale = new Sale(); sale.setGid(goodsId); sale.setNums(nums); saleDao.insertSale(sale); // 先查询该商品 Goods goods = goodsDao.selectGoods(goodsId); if (goods == null){ // 商品不存在 throw new NullPointerException("编号:"+goodsId+" 的商品不存在"); }else if (goods.getAmount() < nums){ // 商品库存不足 throw new NotEnoughException("编号:"+goodsId+" 的商品不足,只能购买: "+goods.getAmount()+" 个"); } // 更新库存 Goods buyGoods = new Goods(); buyGoods.setId(goodsId); buyGoods.setAmount(nums); goodsDao.updateGoods(buyGoods); System.out.println("=========buy方法结束==========="); } }
@Test public void test01(){ String config = "applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); // 从容器中获取service,你声明service时候的id BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService"); // jdk动态代理对象com.sun.proxy.$Proxy16 //System.out.println(buyGoodsService.getClass().getName()); buyGoodsService.buy(1003,100); }
假设此时的1003号商品不存在,即使上面已经向sale表中添加数据了,但是由于还没有执行到更新库存方法的时候出现了异常,此时由于添加了事务,这个时候就会回滚,你查看sale的数据表没有数据,但是你之后买一个存在的商品,发现id号已经不是连续的了,如图所示:
由于sale的id设置的自增,就是因为回滚的原因,id不连续
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。 使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可
还是使用上面举例的程序,复制一份
还是直接把完整的pom.xml放这,主要就是添加了一个aspectj的
<?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.md</groupId> <artifactId>09-spring-trans-aspectj</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--aspectj依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--spring核心--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--spring事务用到的--> <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> <!--mybatis的--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <!--mybatis和spring集成的--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!--mysql驱动--> <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> <!--目的是把src/main/java目录中的xml文件包含到输出结果中,也就是输出到classes目录中--> <resources> <resource> <directory>src/main/java</directory><!--所在的目录--> <includes><!--包括目录下的.properties,.xml 文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build> </project>
<!--1. 声明事务管理器对象--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource"/> </bean>
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。 例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务
<!--2. 声明业务方法它的事务属性 id:自定义名称,表示<tx:advice>和</tx:advice>之间的配置内容 transaction-manager:事务管理器对象的id --> <!--注意:advice选择tx结尾的--> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <!-- tx:attributes :配置事务的属性--> <tx:attributes> <!-- tx:method:给具体的方法配置事务属性,可以有多个,分别给不同的方法设置事务 name:方法名称 1. 完整的方法名称,不带包和类 2. 方法可以使用通配符,*表示任意字符 propagation:传播行为 isolation:隔离级别 rollback-for:你指定的异常类名,全限定类名,发生异常一定回滚 --> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException , com.md.excep.NotEnoughException"/> <!--当方法多的时候,使用通配符--> <!--表示add开头的方法,其他都是默认--> <tx:method name="add*" /> <tx:method name="modify*"/> </tx:attributes> </tx:advice>
指定将配置好的事务通知,织入给谁
<!--3. 配置aop--> <aop:config> <!--配置切入点表达式:指定那些包中的类,要使用事务 id:切入点表达式名称,唯一 expression:切入点表达式,指定那些类要使用事务,aspectj会创建代理对象 这里写的表示所有service类中的所有方法 --> <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/> <!--配置增强器:关联advice和pointcut advice-ref:通知,上面tx:advice里面的配置 pointcut-ref:切入点表达式的id --> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/> </aop:config>
<?xml version="1.0" encoding="UTF-8"?> <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: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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 把数据库的配置信息写在一个独立的文件中,编译修改数据库的配置内容 让spring知道jdbc.properties文件的位置 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!--声明数据源DataSource,作用是连接数据库--> <bean id="myDataSource" 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="maxActive" value="${jdbc.maxActive}" /> </bean> <!--SqlSessionFactory--> <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!--创建 dao对象--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="SqlSessionFactory"/> <property name="basePackage" value="com.md.dao"/> </bean> <!--下面的就是自己定义的service--> <!--声明service--> <bean id="buyGoodsService" class="com.md.service.impl.BuyGoodsServiceImpl"> <!--就是上面通过创建的dao对象--> <property name="saleDao" ref="saleDao"/> <property name="goodsDao" ref="goodsDao"/> </bean> <!-- 声明式事务处理:和源代码完全分离 --> <!--1. 声明事务管理器对象--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource"/> </bean> <!--2. 声明业务方法它的事务属性 id:自定义名称,表示<tx:advice>和</tx:advice>之间的配置内容 transaction-manager:事务管理器对象的id --> <!--注意:advice选择tx结尾的--> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <!-- tx:attributes :配置事务的属性--> <tx:attributes> <!-- tx:method:给具体的方法配置事务属性,可以有多个,分别给不同的方法设置事务 name:方法名称 1. 完整的方法名称,不带包和类 2. 方法可以使用通配符,*表示任意字符 propagation:传播行为 isolation:隔离级别 rollback-for:你指定的异常类名,全限定类名,发生异常一定回滚 --> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException , com.md.excep.NotEnoughException"/> <!--当方法多的时候,使用通配符--> <!--表示add开头的方法,其他都是默认--> <tx:method name="add*" /> <tx:method name="modify*"/> </tx:attributes> </tx:advice> <!--3. 配置aop--> <aop:config> <!--配置切入点表达式:指定那些包中的类,要使用事务 id:切入点表达式名称,唯一 expression:切入点表达式,指定那些类要使用事务,aspectj会创建代理对象 这里写的表示所有service类中的所有方法 --> <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/> <!--配置增强器:关联advice和pointcut advice-ref:通知,上面tx:advice里面的配置 pointcut-ref:切入点表达式的id --> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/> </aop:config> </beans>
@Test public void test01(){ String config = "applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); // 从容器中获取service,你声明service时候的id BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService"); buyGoodsService.buy(1001,10); }
适合中小项目使用的注解方案
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。 @Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。 可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等
使用@Transactional的步骤:
需要声明事务管理器对象
开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务
spring给业务方法加入事务:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){ 开启事务,spring给你开启 try{ buy(1001,10); spring的事务管理器.commit(); }catch(Exception e){ spring的事务管理器.rollback(); } }
适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务
这种方式业务方法和事务配置完全分离
实现步骤: 都是在xml配置文件中实现
要使用的是aspectj框架,需要加入依赖
声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
配置aop:指定哪些哪类要创建代理
原文链接:https://www.cnblogs.com/mengd/p/13499264.html