大家不懂可以留言我,虽然我也很菜
事务的基本知识,我专门有一篇博客写了: 事务基础
spring事务管理
spring为我们提供给了事务管理,我们就不需要手动去开启事务,关闭事务了,我们只需要配置一下就可以了,它就会帮我们把事务放在service层去.
spring提供了事务jar包:
spring-tx-5.0.2-RELEASE.jar
,里面大部分都是接口,其中有几个接口我们需要注意一下子.
SavepointManager: 保存点管理器,比如说,我这个事务里面包括四个操作,前两个如果对了就一定要提交,后两个无所谓,那么我们就可以在前两个操作后设置一个保存点savepoint对象,然后当第三个操作出错了之后,回滚到保存点对象的位置,前面的提交,这样就可以保证前两个操作一定提交了.
**PlatformTransactionManager:**平台事务管理器, spring要管理事务,必须使用事务管理器,进行事务配置时,必须配置事务管理器.
**TransactionStatus:**事务状态,spring用于记录当前事务运行状态,例如: 是否有保存点,事务是否完成等,spring根据状态进行相应操作.
**TransactionDefinition:**事务详情(包括事务定义,事务属性等),spring用于确定事务具体详情,例如: 隔离级别,是否只读,超时时间等,进行事务配置时,**必须配置详情,**spring会将配置项封装到对象实例中.
jdbc管理数据库通过spring管理事务
导入包: jdbc开发数据库的话,要导入jdbc对应的包(包里面放的是spring事务中接口的实现类):
spring-jdbc-5.0.2.RELEASE.jar,
以及spring事务管理的jar包.通过spring-jdbc包下面的PlatformTransactionManager接口的实现类DataSourceTransactionManager作为事务管理器来实现事务管理
TransactionDefinition接口中的成员变量,隔离的变量在开头链接的博客里面有,这里只看传播行为的几个变量
传播行为用于解决在两个业务中如何共享事务.
PROPAGATION_REQUIRED(默认值),required表示必须,即支持当前事务,A如果有事务,B将使用该事务,如果A没有事务,B将创建一个新的事务.
PROPAGATION_SUPPORTS,support表示支持,即支持当前事务,A如果有事务,B将使用该事务,如果A没有事务,B将以非事务执行.
PROPAGATION_MANDATORY,mandatory表示强制,即支持当前事务,A如果有事务,B将使用该事务,如果A没有事务,B将抛出异常.
PROPAGATION_REQUIRES_NEW,requires_new表示必须新的,A如果有事务,将A的事务挂起,B创建一个新的事务执行,如果A没有事务,B创建一个新的事务执行.
PROPAGATION_NOT_SUPPORTED,not_supported表示不支持,A如果有事务,将A的事务挂起,B以非事务执行,如果A没有事务,B以非事务执行.
PROPAGATION_NEVER,never表示从不,A如果有事务,B将抛出异常,A如果没有事务,B将以非事务执行.
PROPAGATION_NESTED,nested表示嵌套,A和B底层采用保存点机制,形成嵌套事务.
转账案例_手动管理事务(了解就可以了 )
实现过程
- 创建数据库表并插入两条数据
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(15),
money INT
);
INSERT INTO account(username, money) VALUES("user1", 1000);
INSERT INTO acCOUNT(username, money) VALUES("user2", 1000);
导入jar包:创建的spring项目的话,大部分jar包它都有,我们只需要导入c3p0连接池的jar包和数据库驱动的jar包就可以了,链接如下: jar包
Dao层
public class AccountDao extends JdbcDaoSupport {
//进账
public void in(int money, String inner) {
String sql = "update account set money = money + ? where username = ?";
super.getJdbcTemplate().update(sql,money, inner);
System.out.println("已成功向"+ inner + "存钱" + money + "元");
}
//出账
public void out(int money, String outer) {
String sql = "update account set money = money - ? where username = ?";
super.getJdbcTemplate().update(sql,money, outer);
System.out.println("已成功从"+ outer + "取钱" + money + "元");
}
}
- service层
public class AccountService {
//生成dao层对象,方便等会调用方法
private AccountDao accountDao;
//提供set方法,等会通过spring注入AccountDao对象
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//spring配置事务模板
private TransactionTemplate transactionTemplate;
//提供set方法,由spring注入
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
//转账方法
public void transfer(String inner, String outer, int money) {
//执行事务,出错之后就会自动回滚
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//进账
accountDao.in(money, inner);
int i = 10 / 0;
//出账
accountDao.out(money, outer);
}
});
}
}
- 测试类
public class UnitClass {
@Test
public void test() {
//获取容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans1.xml");
//获取service层对象
AccountService service = (AccountService) context.getBean("accountService");
//调用方法
service.transfer("user1", "user2", 500);
}
}
- 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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--让spring去读我们的连接数据库的配置文件-->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过看源码知道,它需要注入一个数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置一个事务模板对象-->
<bean id="template" class="org.springframework.transaction.support.TransactionTemplate">
<!--通过看源码知道,我们还需要配置一个事务管理器-->
<property name="transactionManager" ref="txManager"></property>
</bean>
<!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
<bean id="dao" class="transaction_example1.dao.AccountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--给service层注入dao层对象-->
<bean id="accountService" class="transaction_example1.service.AccountService">
<property name="accountDao" ref="dao"></property>
<!--配置事务管理模板-->
<property name="transactionTemplate" ref="template"></property>
</bean>
</beans>
- properties配置文件
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/spring_day04
user=root
password=root
转账案例_spring用工厂bean生成代理半自动管理事务(了解)
注意:
这里的动态代理就是JDK自带的那种,必须针对接口,关于动态代理,参考这篇博客: 动态代理.
xml中属性详情必须配,不能少
一个很恼火的错误:**com.sun.proxy.$Proxy0 cannot be cast to **,这就是你在容器中获取service代理对象或者自己创建代理对象的时候,生成的对象不是接口类型,而是对象类型导致的,例子如下:
有一个接口User,它的实现类是UserImpl,现在要生成代理对象
1. UserImpl u1 = (UserImpl)Proxy.newProxyInstance(参数省略...);
这个就会报错--`com.sun.proxy.$Proxy0 cannot be cast to`
2. User u2 = (User)Proxy.newProxyInstance(参数省略...);
这个写法才是正确的,因为jdk的动态代理是针对接口的,记住记住!!!!
spring提供了管理事务的代理工厂bean,叫
TransactionProxyFactoryBean
,我们通过代理工厂的话就不用配置事务模板了,代码相对于纯手动管理要简单一些哈哈哈实现过程
- service类
/**
* 在service层中调用dao层的方法
*/
public class AccountService implements Account{
//生成dao层对象,方便等会调用方法
private AccountDao accountDao;
//提供set方法,等会通过spring注入AccountDao对象
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//转账方法
public void transfer(String inner, String outer, int money) {
//进账
accountDao.in(money, inner);
int i = 10 / 0;
//出账
accountDao.out(money, outer);
}
}
- 测试类
public class UnitClass {
@Test
public void test() {
//获取容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans2.xml");
//获取service层的代理对象,注意这里的Account必须是接口啊,不能是其实现类
Account service = (Account) context.getBean("proxyService");
//调用方法
service.transfer("user1", "user2", 500);
}
}
- 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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--让spring去读我们的连接数据库的配置文件-->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过看源码知道,它需要注入一个数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
<bean id="dao" class="transaction_example2.dao.AccountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置service,给service层注入dao层对象-->
<bean id="accountService" class="transaction_example2.service.AccountService">
<property name="accountDao" ref="dao"></property>
</bean>
<!--配置一个工厂代理-->
<bean id="proxyService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--配置接口-->
<property name="proxyInterfaces" value="transaction_example2.service.Account"></property>
<!--目标对象-->
<property name="target" ref="accountService"></property>
<!--切面对象不用写,spring帮我们做了-->
<!--配置事务管理器-->
<property name="transactionManager" ref="txManager"></property>
<!--配置事务属性,也就是事务详情
key:哪些方法需要使用事务,写方法名
value: 写事务的详情控制
格式: propagation(传播行为),isolation(隔离级别),readonly(是否只读)
-Exception(异常回滚),+Exception(异常提交)
前两个必须写,后面的可以不写
当设置了只读之后,就说明这个代理对象只能读,不能写,所以对数据库的修改会失败.
设置了+Exception之后,表示就算有异常,异常前面的语句照样提交,类似于保存点的功能
-->
<property name="transactionAttributes">
<props>
<!--有多个方法就写多个prop标签就好了-->
<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+Exception</prop>
</props>
</property>
</bean>
</beans>
spring基于AOP的事务管理(这个要掌握)
手动管理事务我们每次有新的业务方法都还要加到事务中去,还要自己手动生成对象调用,这就很麻烦,而半自动代理,还是要配置什么隔离级别一堆,还是麻烦,所以我们决定用AOP切面改进.
注意
如果我们service层中的类实现了接口,比如上面的
AccountService
,那么在容器内获取该对象的时候,就必须是接口类型的,因为这个就是通过代理对象完成的,只是不需要我们配置代理对象而已.如果service层中的类没有实现接口,那么在在容器内获取该对象的时候,就直接写类类型就可以,不需要是接口类型,我的例子里实现了接口,所以只能是接口类型.
使用案例及步骤(相对于上面一个半自动管理来说,这里只修改xml文件):
- 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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--让spring去读我们的连接数据库的配置文件-->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过看源码知道,它需要注入一个数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
<bean id="dao" class="transaction_example3.dao.AccountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置service,给service层注入dao层对象-->
<bean id="accountService" class="transaction_example3.service.AccountService">
<property name="accountDao" ref="dao"></property>
</bean>
<!--1. 配置作为通知使用的事务管理器,使用前要在头部加上标签的引用,就是xmlns那些-->
<tx:advice transaction-manager="txManager" id="myAdvice">
<!--配置事务详情:传播行为,隔离级别等,使用aop配置时都可以省略,自动使用默认值-->
<tx:attributes>
<!--方法就是我们要被事务控制的方法,我们这里是转账方法-->
<tx:method name="transfer"/>
</tx:attributes>
</tx:advice>
<!--2. 使用spring的aop标签来配置切面
这里切面中的通知就是事务,是由spring的事务管理器管理的
切入点就还是转账方法
在这里区分一下切入点和连接点,一个类中的所有方法都是连接点
但是只有要被通知进行功能增强的方法才叫切入点-->
<aop:config>
<!--配置一个切入点-->
<aop:pointcut id="myPoint" expression="execution(* transaction_example3.service.*.transfer(..))"></aop:pointcut>
<!--3. 将事务与切入点关联-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPoint"></aop:advisor>
</aop:config>
</beans>
spring基于注解的事务管理(这个也要掌握)
不过事务管理的话一般还是写在xml里比较好,因为要是service很多的话,也懒得写,在xml里还一目了然些,不过没关系啦,看自己习惯,咋都行.
在xml中配置使用注解进行事务管理的时候这里有个大坑:
- 配置如下,其中特别注意配置事务管理器的id,这个id只能写transactionManager,不能写其他的,不能写其他的,写其他的就会报错: NoSuchBeanDefinitionException: No bean named ‘transactionManager’ available
<!--1. 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过看源码知道,它需要注入一个数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2. 配置事务的注解驱动,告诉虚拟机由注解来完成事务操作-->
<tx:annotation-driven transaction-manager="transactionManager"/>
实现步骤(没写的就说明和上面的一致,这里只有service和xml变化了)
- service代码
/**
* 在service层中调用dao层的方法
* 注解写在类名上,表示该类所有方法都会被事务控制
* 如果想事务控制只对某一个方法生效,将注解写在对应方法名上就行
*/
public class AccountService{
//生成dao层对象,方便等会调用方法
private AccountDao accountDao;
//提供set方法,等会通过spring注入AccountDao对象
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//转账方法,注解也可以写成@Transactional
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void transfer(String inner, String outer, int money) {
//进账
accountDao.in(money, inner);
//int i = 10 / 0;
//出账
accountDao.out(money, outer);
}
}
- 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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--让spring去读我们的连接数据库的配置文件-->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--配置数据源对象,因为jdbctemplate必须要用到数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<tx:annotation-driven></tx:annotation-driven>
<!--创建dao层对象,同时给其注入数据源,因为jdbctemplate的创建需要数据源-->
<bean id="dao" class="transaction_example4.dao.AccountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置service,给service层注入dao层对象-->
<bean id="accountService" class="transaction_example4.service.AccountService">
<property name="accountDao" ref="dao"></property>
</bean>
<!--1. 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--通过看源码知道,它需要注入一个数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2. 配置事务的注解驱动,告诉虚拟机由注解来完成事务操作-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>