Spring整合JDBC以及AOP管理事务

时间:2021-01-14 21:35:24

本节内容:

  • 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创建一个数据库 ,在库里创建一张表。

Spring整合JDBC以及AOP管理事务

Spring整合JDBC以及AOP管理事务

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

Spring整合JDBC以及AOP管理事务

PROPAGION_XXX:事务的传播行为。

保证同一个事务中:

  • PROPAGATION_REQUIRED 支持当前事务,如果不存在,就新建一个(默认)  --99.999的情况都是用这种
  • PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
  • PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常

保证没有在同一个事务中:

  • PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
  • PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
  • PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
  • PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

4. Spring事务环境准备

业务环境:转账

表:

Spring整合JDBC以及AOP管理事务

表中数据:

Spring整合JDBC以及AOP管理事务

准备Dao和Service层:

Spring整合JDBC以及AOP管理事务

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,查看数据库。

Spring整合JDBC以及AOP管理事务

【注意】:现在上面的代码中还没有添加事务。如果在转账的过程中出现了异常,比如修改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加上事务。

Spring整合JDBC以及AOP管理事务

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,查看数据库结果。很显然,当遇到异常时,事务进行了回滚。

Spring整合JDBC以及AOP管理事务

但是很显然这种不合理,如果Service中有多个方法要用到事务,那么tt.excute()在每个方法里都得调用。

(2)xml配置aop事务

Spring整合JDBC以及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); }
...

这样类中的全部方法都会使用这个注解,如果某个方法需要使用注解时的属性值不一样,可以在方法上单独写一个注解。