本节内容:
- Spring整合JDBC
- Spring中的AOP管理事务
一、Spring整合JDBC
Spring框架永远是一个容器,Spring整合JDBC其实就是Spring提供了一个对象,这个对象封装了JDBC技术,它可以操作数据库,这个对象可以放入Spring容器,交给Spring容器来管理。所以我们主要是要学习这个对象:JDBCTemplate。这个对象和DBUtils中的QueryRunner非常相似。
1. 导包
4+2+2(测试需要的包spring-test,新版本测试时还需要spring-aop包,junit4类库)+(操作数据库包:JDBC驱动包mysql-connector-java-5.1.7-bin.jar + c3p0连接池:com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar)+ spring-jdbc(JDBCTemplate在这个包里)+ spring-tx(不管你用不用事务,操作数据库都要导入这个包)
2. 准备数据库
我这里使用Navicat创建一个数据库 ,在库里创建一张表。
3. 写代码测试(先自己写代码new JdbcTemplate)
创建一个包com.wisedu.jdbctemplate,在里面创建一个类Demo.java,使用JdbcTemplate:
package com.wisedu.jdbctemplate; import java.beans.PropertyVetoException; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.mchange.v2.c3p0.ComboPooledDataSource; import com.wisedu.bean.User; //演示JDBC模板 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo { @Resource(name="userDao") private UserDao ud; @Test public void fun1() throws Exception{ //0 准备连接池 ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql:///spring"); dataSource.setUser("root"); dataSource.setPassword("123456"); //1 创建JDBC模板对象,和使用QueryRunner一样,空参或者传一个连接池进去 JdbcTemplate jt = new JdbcTemplate(); jt.setDataSource(dataSource); //2 书写sql,并执行 String sql = "insert into t_user values(null,‘rose‘) "; jt.update(sql); } }
4. 使用Spring容器管理JdbcTemplate对象
上面的代码中没有用到Spring容器,对象都是我们手动new出来。
UserDao.java
package com.wisedu.jdbctemplate; import java.util.List; import com.wisedu.bean.User; public interface UserDao { //增 void save(User u); //删 void delete(Integer id); //改 void update(User u); //查 User getById(Integer id); //查 int getTotalCount(); //查 List<User> getAll(); }
UserDaoImpl.java
package com.wisedu.jdbctemplate; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import com.wisedu.bean.User; //使用JDBC模板实现增删改查 public class UserDaoImpl implements UserDao { private JdbcTemplate jt; @Override public void save(User u) { String sql = "insert into t_user values(null,?) "; jt.update(sql, u.getName()); } @Override public void delete(Integer id) { String sql = "delete from t_user where id = ? "; jt.update(sql, id); } @Override public void update(User u) { String sql = "update t_user set name = ? where id=? "; jt.update(sql, u.getName(), u.getId()); } @Override public User getById(Integer id) { String sql = "select * from t_user where id = ? "; return jt.queryForObject(sql, new RowMapper<User>() { @Override public User mapRow(ResultSet resultSet, int i) throws SQLException { //Spring会帮忙遍历ResultSet,不用判断,能进入这个方法,一定有值 User u = new User(); u.setId(resultSet.getInt("id")); u.setName(resultSet.getString("name")); return u; } }, id); } @Override public int getTotalCount() { String sql = "select count(*) from t_user "; Integer count = jt.queryForObject(sql, Integer.class); return count; } @Override public List<User> getAll() { String sql = "select * from t_user "; List<User> list = jt.query(sql, new RowMapper<User>() { @Override public User mapRow(ResultSet resultSet, int i) throws SQLException { User u = new User(); u.setId(resultSet.getInt("id")); u.setName(resultSet.getString("name")); return u; } }); return list; } public void setJt(JdbcTemplate jt) { this.jt = jt; } }
写完代码之后,我们需要把 UserDaoImpl 配置到Spring容器中,交由Spring容器来管理。这个UserDaoImpl对象依赖 JdbcTemplate 对象,而 JdbcTemplate 对象依赖连接池,将连接池注入到 JdbcTemplate 中。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd "> <!-- 1.将连接池放入spring容器. 这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 --> <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="jdbcUrl" value="jdbc:mysql:///spring" ></property> <property name="driverClass" value="com.mysql.jdbc.Driver" ></property> <property name="user" value="root" ></property> <property name="password" value="123456" ></property> </bean> <!-- 2.将JDBCTemplate放入spring容器 --> <bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" > <property name="dataSource" ref="dataSource" ></property> <!--set方式注入连接池--> </bean> <!-- 3.将UserDao放入spring容器 --> <bean name="userDao" class="com.wisedu.jdbctemplate.UserDaoImpl" > <property name="jt" ref="jdbcTemplate" ></property> </bean> </beans>
编写测试代码:
Demo.java
package com.wisedu.jdbctemplate; import java.beans.PropertyVetoException; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.mchange.v2.c3p0.ComboPooledDataSource; import com.wisedu.bean.User; //演示JDBC模板 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo { @Resource(name="userDao") private UserDao ud; @Test public void fun1() throws Exception{ //0 准备连接池 ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql:///spring"); dataSource.setUser("root"); dataSource.setPassword("123456"); //1 创建JDBC模板对象,和使用QueryRunner一样,空参或者传一个连接池进去 JdbcTemplate jt = new JdbcTemplate(); jt.setDataSource(dataSource); //2 书写sql,并执行 String sql = "insert into t_user values(null,‘rose‘) "; jt.update(sql); } @Test public void fun2() throws Exception{ User u = new User(); u.setName("tom"); ud.save(u); } @Test public void fun3() throws Exception{ User u = new User(); u.setId(2); u.setName("jack"); ud.update(u); } @Test public void fun4() throws Exception{ ud.delete(2); } @Test public void fun5() throws Exception{ System.out.println(ud.getTotalCount()); } @Test public void fun6() throws Exception{ System.out.println(ud.getById(1)); } @Test public void fun7() throws Exception{ System.out.println(ud.getAll()); } }
5. 扩展知识:JdbcDaoSupport
这个类可以根据连接池创建JdbcTemplate对象。所以在配置文件中需要将连接池注入到UserDaoImpl中,就不需要配置JdbcTemplate了。
修改后的UserDaoImpl.java
public class UserDaoImpl extends JdbcDaoSupport implements UserDao { //这个父类JdbcDaoSupport会根据连接池帮你创建JdbcTemplate对象,这样就不需要在配置文件中配置JdbcTemplate // 子类在使用时只需要调用父类的getJdbcTemplate()方法获取JdbcTemplate对象 //public class UserDaoImpl implements UserDao { //private JdbcTemplate jt; @Override public void save(User u) { String sql = "insert into t_user values(null,?) "; //jt.update(sql, u.getName()); super.getJdbcTemplate().update(sql, u.getName()); } @Override public void delete(Integer id) { String sql = "delete from t_user where id = ? "; //jt.update(sql, id); super.getJdbcTemplate().update(sql,id); } @Override public void update(User u) { String sql = "update t_user set name = ? where id=? "; //jt.update(sql, u.getName(), u.getId()); super.getJdbcTemplate().update(sql, u.getName(),u.getId()); } @Override public User getById(Integer id) { String sql = "select * from t_user where id = ? "; /*return jt.queryForObject(sql, new RowMapper<User>() { @Override public User mapRow(ResultSet resultSet, int i) throws SQLException { //Spring会帮忙遍历ResultSet,不用判断,能进入这个方法,一定有值 User u = new User(); u.setId(resultSet.getInt("id")); u.setName(resultSet.getString("name")); return u; } }, id);*/ return super.getJdbcTemplate().queryForObject(sql,new RowMapper<User>(){ @Override public User mapRow(ResultSet rs, int arg1) throws SQLException { User u = new User(); u.setId(rs.getInt("id")); u.setName(rs.getString("name")); return u; }}, id); } @Override public int getTotalCount() { String sql = "select count(*) from t_user "; //Integer count = jt.queryForObject(sql, Integer.class); Integer count = super.getJdbcTemplate().queryForObject(sql, Integer.class); return count; } @Override public List<User> getAll() { String sql = "select * from t_user "; /*List<User> list = jt.query(sql, new RowMapper<User>() { @Override public User mapRow(ResultSet resultSet, int i) throws SQLException { User u = new User(); u.setId(resultSet.getInt("id")); u.setName(resultSet.getString("name")); return u; } });*/ List<User> list = super.getJdbcTemplate().query(sql, new RowMapper<User>(){ @Override public User mapRow(ResultSet rs, int arg1) throws SQLException { User u = new User(); u.setId(rs.getInt("id")); u.setName(rs.getString("name")); return u; } }); return list; } // public void setJt(JdbcTemplate jt) { // this.jt = jt; // } }
修改后的applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd "> <!-- 1.将连接池放入spring容器. 这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 --> <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="jdbcUrl" value="jdbc:mysql:///spring" ></property> <property name="driverClass" value="com.mysql.jdbc.Driver" ></property> <property name="user" value="root" ></property> <property name="password" value="123456" ></property> </bean> <!-- 2.将JDBCTemplate放入spring容器 --> <!--<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >--> <!--<property name="dataSource" ref="dataSource" ></property> <!–set方式注入连接池–>--> <!--</bean>--> <!-- 3.将UserDao放入spring容器 --> <bean name="userDao" class="com.wisedu.jdbctemplate.UserDaoImpl" > <!--<property name="jt" ref="jdbcTemplate" ></property>--> <property name="dataSource" ref="dataSource" ></property> </bean> </beans>
测试代码内容不变,可以再次测试下。
6. db.properties
在实际开发中,数据库连接信息可能会变,每次都要打开Spring的配置文件applicationContext.xml文件进行修改,所以把数据库的信息挪到外面的一个文件中,比如在src下新建一个文件db.properties
jdbc.jdbcUrl=jdbc:mysql:///spring jdbc.driverClass=com.mysql.jdbc.Driver jdbc.user=root jdbc.password=123456
【注意】:properties里的key建议加个前缀,防止某个key与Spring中的某些关键词重复,那么这个key就读不出来了。尤其user这个键。
那么applicationContext.xml文件关于连接池的配置修改如下:
<!-- 指定spring读取db.properties配置 property-placeholder用来指定读取properties配置文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 1.将连接池放入spring容器. 这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 --> <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property> <property name="driverClass" value="${jdbc.driverClass}" ></property> <property name="user" value="${jdbc.user}" ></property> <property name="password" value="${jdbc.password}" ></property> </bean>
二、Spring中的AOP管理事务
1. 事务回顾
(1)什么是事务
事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
(2)事务特性(ACID)
- 原子性:强调事务的不可分割。
- 一致性:事务的执行的前后数据的完整性保持一致。
- 隔离性:一个事务执行的过程中,不应该受到其他事务的干扰
- 持久性:事务一旦结束,数据就持久到数据库
(3)如果不考虑隔离性引发安全性问题(并发问题)
- 脏读:一个事务读到了另一个事务的未提交的数据
- 不可重复读:一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
- 幻读:一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.
(4)解决读问题:设置事务隔离级别(解决并发问题)
- 读未提交:脏读,不可重复读,虚读都有可能发生
- 读已提交:避免脏读。但是不可重复读和虚读有可能发生
- 可重复读:避免脏读和不可重复读.但是虚读有可能发生.
- 串行化:避免以上所有读问题.
Mysql 默认:可重复读
Oracle 默认:读已提交
2. Spring中事务
Spring封装了事务管理代码,无非就是打开事务的代码,提交事务的代码以及回滚事务的代码。因为使用不同平台(JDBC、Hibernate、Mybatis),操作事务的代码各不相同,所以Spring提供了一个接口PlatformTransactionManager,平台事务管理器。这个接口中声明了事务操作的方法,针对不同的平台,Spring提供不同的实现类。比如针对JDBC平台,提供的实现类是DataSourceTransactionManager,针对Hibernate平台提供的实现类是HibernateTransactionManager。
【注意】:在Spring中玩事务管理,最核心的对象就是 TransactionManager 对象。
3. Spring管理事务的属性介绍
事务封装好了,可以通过属性来配置事务。
- 事务的隔离级别 isolation
- 事务是否只读 read-only
- 事务的传播行为 propagation
PROPAGION_XXX:事务的传播行为。
保证同一个事务中:
- PROPAGATION_REQUIRED 支持当前事务,如果不存在,就新建一个(默认) --99.999的情况都是用这种
- PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
- PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
保证没有在同一个事务中:
- PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
- PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
- PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
- PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
4. Spring事务环境准备
业务环境:转账
表:
表中数据:
准备Dao和Service层:
AccountDao.java
package com.wisedu.dao; /** * Created by jkzhao on 12/20/17. */ public interface AccountDao { //加钱 void increaseMoney(Integer id, Double money); //减钱 void decreaseMoney(Integer id, Double money); }
AccountDaoImpl.java
package com.wisedu.dao.impl; import com.wisedu.dao.AccountDao; import org.springframework.jdbc.core.support.JdbcDaoSupport; /** * Created by jkzhao on 12/20/17. */ public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void increaseMoney(Integer id, Double money) { getJdbcTemplate().update("update t_account set money = money+? where id = ?",money, id); } @Override public void decreaseMoney(Integer id, Double money) { getJdbcTemplate().update("update t_account set money = money-? where id = ?",money, id); } }
AccountService.java
package com.wisedu.service; /** * Created by jkzhao on 12/20/17. */ public interface AccountService { //转账 void transfer(Integer from, Integer to, Double money); }
AccountServiceImpl.java
ackage com.wisedu.service.impl; import com.wisedu.dao.AccountDao; import com.wisedu.service.AccountService; /** * Created by jkzhao on 12/20/17. */ public class AccountServiceImpl implements AccountService { private AccountDao ad; @Override public void transfer(Integer from, Integer to, Double money) { //减钱 ad.decreaseMoney(from, money); //加钱 ad.increaseMoney(to, money); } public void setAd(AccountDao ad) { this.ad = ad; } }
配置文件applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd "> <!-- 指定spring读取db.properties配置 property-placeholder用来指定读取properties配置文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 1.将连接池放入spring容器. 这个连接池com.mchange.v2.c3p0.ComboPooledDataSource以前是我们自己手动new出来,现在是Spring帮我们new出来 --> <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property> <property name="driverClass" value="${jdbc.driverClass}" ></property> <property name="user" value="${jdbc.user}" ></property> <property name="password" value="${jdbc.password}" ></property> </bean> <!-- 2.将AccountDao放入spring容器 --> <bean name="accountDao" class="com.wisedu.dao.impl.AccountDaoImpl" > <property name="dataSource" ref="dataSource" ></property> </bean> <!-- 3.将AccountDao放入spring容器 --> <bean name="accountService" class="com.wisedu.service.impl.AccountServiceImpl" > <property name="ad" ref="accountDao" ></property> </bean> </beans>
编写测试代码,建一个包com.wisedu.tx,编写测试文件Demo.java
package com.wisedu.tx; import com.wisedu.service.AccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; /** * Created by jkzhao on 12/20/17. */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo { @Resource(name = "accountService") private AccountService as; @Test public void fun1(){ as.transfer(1, 2, 100d); //100d,因为该字段是double类型的 } }
执行方法fun1,查看数据库。
【注意】:现在上面的代码中还没有添加事务。如果在转账的过程中出现了异常,比如修改AccountServiceImpl.java的部分代码:
public class AccountServiceImpl implements AccountService { private AccountDao ad; @Override public void transfer(Integer from, Integer to, Double money) { //减钱 ad.decreaseMoney(from, money); int i = 1/0; //加钱 ad.increaseMoney(to, money); } ... }
再次执行fun1方法,查看数据库。发现张三的钱少了,但是李四的钱并没有加上来。所以需要给这个Service加上事务。
5. Spring管理事务方式(三种)
- 编码式 --了解
- xml配置式(属于aop) --重要
- 注解配置(属于aop) --重要
(1)编码式
在代码中管理事务,如果有10个方法用到事务,得在10个地方添加事务代码。
将核心事务管理器配置到Spring容器。在上面的applicationContext.xml文件中加入如下代码:
<!-- 将核心事务管理器配置到Spring容器,封装了所有事务操作 事务依赖DataSource --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 编码式事务 操作事务需要使用一个事务模板对象。把调用事务的操作封装到TransactionTemplate对象中,它依赖核心事务管理器。 这个TransactionTemplate只是帮你去调事务处理的方法,但是方法是写在和事务管理器中的 --> <bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean>
修改AccountServiceImpl.java的代码
package com.wisedu.service.impl; import com.wisedu.dao.AccountDao; import com.wisedu.service.AccountService; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; /** * Created by jkzhao on 12/20/17. */ public class AccountServiceImpl implements AccountService { private AccountDao ad; private TransactionTemplate tt; @Override public void transfer(final Integer from, final Integer to, final Double money) { tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { //减钱 ad.decreaseMoney(from, money); int i = 1/0; //加钱 ad.increaseMoney(to, money); } }); } public void setAd(AccountDao ad) { this.ad = ad; } public void setTt(TransactionTemplate tt) { this.tt = tt; } }
在applicationContext.xml中的accountService这个bean里注入属性tt:
<bean name="accountService" class="com.wisedu.service.impl.AccountServiceImpl" > <property name="ad" ref="accountDao" ></property> <property name="tt" ref="transactionTemplate"></property> </bean>
再次执行方法fun1,查看数据库结果。很显然,当遇到异常时,事务进行了回滚。
但是很显然这种不合理,如果Service中有多个方法要用到事务,那么tt.excute()在每个方法里都得调用。
(2)xml配置aop事务
Spring已经写好了一个事务的通知,如果Spring没有写这个通知,我们自己来实现的话,得用环绕通知。既然已经帮我们实现了通知,我们只需要将这个通知织入到目标对象上(在本案例中,目标对象是那个service),我们只需要配置就可以了。
a. 首先要导包:4 + 2 + aop + aspect + aop联盟包(com.springsource.org.aopalliance-1.0.0.jar)+ weaving织入包(com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar)
b. 导入新的命名空间aop和tx:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> ...
命名空间在本案例中的作用:
- beans:最基本
- context:注解和读取properties配置文件
- aop:配置AOP(配置将事务通知织入目标对象)
- tx:配置事务通知
c. 配置通知和织入
<!-- 将核心事务管理器配置到Spring容器,封装了所有事务操作 事务依赖DataSource --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 方式一:编码式事务 操作事务需要使用一个事务模板对象。把调用事务的操作封装到TransactionTemplate对象中,它依赖核心事务管理器。 这个TransactionTemplate只是帮你去调事务处理的方法,但是方法是写在和事务管理器中的 --> <!--<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">--> <!--<property name="transactionManager" ref="transactionManager"></property>--> <!--</bean>--> <!-- 方式二:xml方式管理事务 --> <!-- 配置事务通知 事务通知是Spring已经写好了。我们只需要配置其事务管理器--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- <tx:attributes> 来指定属性--> <!-- 以方法为粒度配置事务属性,有3个属性(isolation、propagation:传播行为、read-only)可以配置。每个属性有多个值可以选择 --> <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <!--service里的这个transfer方法里面是要修改数据库的,所以read-only这个属性的值千万别配置true--> <!-- 企业当中开发以通配符来完成批量配置 两套增删改查 --> <tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/> <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/> <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/> </tx:attributes> </tx:advice> <!-- 配置将事务通知织入目标对象 --> <aop:config> <!-- 配置切点表达式 --> <aop:pointcut id="txPc" expression="execution(* com.wisedu.service.impl.*ServiceImpl.*(..) )" /> <!-- 配置切面 advice-ref:通知的名称 pointcut-ref:切点名称 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc"/> <!-- advisor:切面=通知+切入点 --> </aop:config>
将方式一的配置全部注释掉,执行Demo.java中的测试方法fun1,测试下无异常和有异常两种情况,查看数据库结果。
(3)注解配置aop事务
导包和导入新的约束和上面的一样。
开启注解管理事务:
<!-- 将核心事务管理器配置到Spring容器,封装了所有事务操作 事务依赖DataSource --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 方式三:开启使用注解管理aop事务 --> <tx:annotation-driven transaction-manager="transactionManager" />
使用注解:
public class AccountServiceImpl implements AccountService { private AccountDao ad; private TransactionTemplate tt; @Override @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, readOnly = false) public void transfer(Integer from, Integer to, Double money) { //减钱 ad.decreaseMoney(from, money); //int i = 1/0; //加钱 ad.increaseMoney(to, money); } ...
将方式一和方式二的配置全部注释掉,执行Demo.java中的测试方法fun1,测试下无异常和有异常两种情况,查看数据库结果。
但是注解这种方式,每个方法上都要加注解。我们可以把这个注解加到类上。
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, readOnly = false) public class AccountServiceImpl implements AccountService { private AccountDao ad; private TransactionTemplate tt; @Override public void transfer(Integer from, Integer to, Double money) { //减钱 ad.decreaseMoney(from, money); int i = 1/0; //加钱 ad.increaseMoney(to, money); } ...
这样类中的全部方法都会使用这个注解,如果某个方法需要使用注解时的属性值不一样,可以在方法上单独写一个注解。