在写之前先提醒一下,大部分代码还是沿用上一篇的,在其基础上更改。若没有案例代码的,可以去我文章的上一篇中查找。但是我在此需要对代码进行一部分的修改:①获取连接的方式②新增事务类
连接工具类ConnectionUitls
package com.utils;
import javax.sql.DataSource;
import java.sql.Connection;
//连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
//获取当前线程上的连接
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取
Connection connection = threadLocal.get();
//2.当前线程上是否有连接
try{
if(connection == null) {
//3.从数据源中获取连接,并且存入ThreadLocal中
connection = dataSource.getConnection();
//4.将连接存入threadLocal
threadLocal.set(connection);
}
//5.返回当前线程上的连接
return connection;
}catch (Exception e){
throw new RuntimeException(e);
}
}
//把连接和当前线程解绑
public void removeConnection(){
threadLocal.remove();
}
}
事务类TransactionManager
package com.itheima.utils;
//和事务管理相关的工具类
//开启事务、提交事务、回滚事务和释放连接
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//开启事务
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
//提交事务
public void commitTransaction(){
try{
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
//回滚事务
public void rollbackTransaction(){
try{
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
//释放连接
public void release(){
try{
//将连接放回连接池中
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
现在有了事务类的这个需要增强的方法,我们还得对AccountDaoImpl进行获取连接的修改
package com.dao.impl;
import com.dao.IAccountDao;
import com.domain.Account;
import com.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.util.List;
//账户的持久层实现类
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public List<Account> findAllAccount() {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account", new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer id) {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?", new BeanHandler<Account>(Account.class) , id);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"insert into account(name , money) values (? , ?)" , account.getName() , account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name = ? , money = ? where id = ?" , account.getName() , account.getMoney() , account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer id) {
try {
runner.update(connectionUtils.getThreadConnection(),"delete from account where id = ?" , id);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountByName(String accountName) {
try {
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ", new BeanListHandler<Account>(Account.class) , accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不为1,数据有问题");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
最后在Bean.xml配置中添加新建的ConnectionUtils对象以及TransactionManager对象
<!--配置ConnectionUtils-->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器TransactionManager-->
<bean id="transactionManager" class="com.itheima.utils.TransactionManager">
<!--注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
事务管理器TransactionManager已经创建出来了,那需要什么配置去通知Spring我已经开启事务管理这个增强方法呢?
<aop:config></aop:config>
此元素标签代表这里是AOP配置,所有的AOP配置都写在里面。
<aop:aspect></aop:aspect>
此元素标签代表这里是切面,属性id代表的是此切面的唯一标识,属性ref代表的是切面的Bean对象配置引用
<aop:pointcut></aop:pointcut>
此元素标签代表切面表达式,属性id代表的是此切面表达式的唯一标识,属性expression里代表的是切面表达式
<aop:before></aop:before>
此元素标签代表的是前置通知。属性method代表的是在增强类中要使用到的方法的方法名,pointcut-ref代表的是引用<aop:pointcut>标签中定义的切面表达式
<aop:after-returning></aop:after-returning>
此元素标签代表的是后置通知。属性method代表的是在增强类中要使用到的方法的方法名,pointcut-ref代表的是引用<aop:pointcut>标签中定义的切面表达式
<aop:after-throwing></aop:after-throwing>
此元素标签代表的是异常通知。属性method代表的是在增强类中要使用到的方法的方法名,pointcut-ref代表的是引用<aop:pointcut>标签中定义的切面表达式
<aop:after></aop:after>
此元素标签代表的是最终通知。属性method代表的是在增强类中要使用到的方法的方法名,pointcut-ref代表的是引用<aop:pointcut>标签中定义的切面表达式
那么通过这些标签的使用就可以告诉Spring需要开启的AOP操作需要的通知。
<!--配置AOP-->
<aop:config>
<aop:aspect id="OpenTransactionManager" ref="transactionManager">
<aop:pointcut id="transactionManagerExpression" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<aop:before method="beginTransaction" pointcut-ref="transactionManagerExpression"></aop:before>
<aop:after-returning method="commitTransaction" pointcut-ref="transactionManagerExpression"></aop:after-returning>
<aop:after-throwing method="rollbackTransaction" pointcut-ref="transactionManagerExpression"></aop:after-throwing>
<aop:after method="release" pointcut-ref="transactionManagerExpression"></aop:after>
</aop:aspect>
</aop:config>
测试结果
数据库中数据
运行无异常的代码时:
@Test
public void testTransfer(){
as.transfer("aaa" , "bbb" , 200f);
}
结果:
此结果可以证明代码可以运行并没有异常。
然后在其中加入之前的异常
int i =1/0;
运行结果:
可以从中看到,出现了异常。那么数据库信息改变了吗?
答案是没有的,证明经过我们的配置,已经将事务管理器增强到了上篇没有的代码上了。这样,我们使用xml去实现AOP操作,就完成了。
细节
我们总结一下全部步骤:
spring中基于XML的AOP配置步骤
1.把通知的Bean也交给spring来管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签表明配置切面
id:给切面提供一个唯一标志
ref:是指定通知类bean的Id
4.在aop:aspect标签内部使用对应的标签来配置通知的类型
我们现在的示例是让pringLog方法在切入点方法执行前执行,所以是前置执行
aop:before:表示配置前置通知
method:用于指定Logger类中哪个方法是前置通知
5.pringcut属性:用于指定切入点表达式:该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execute(表达式)
表达式:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
标准的表达式写法:
public void com.service.impl.AccountService.saveAccount()
访问修饰符可以省略
void com.service.impl.AccountService.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.service.impl.AccountService.saveAccount()
包名可以使用通配符,表示任意包,但是有几级包就要写几个*.
* *.*.*.*.AccountService.saveAccount()
包名可以使用..表示当前包及其子包
* *..AccountService.saveAccount()
类名和方法名都可以使用*实现通配
* *..*.*()
参数列表可以直接写数据类型:
基本类型直接写名称 int
引用类型直接写包名.类名的方法 java.lang.String
可以使用通配符,表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* * com.service.impl.*.*(..)
那么我们基于xml简单配置AOP就完成了。