Spring框架(9) —— 三种事务管理机制的转账案例

简介

  • 本文的三个案例是为了更好的理解 Spring AOP 的机制,以及复习数据库事务管理。具体的功能是实现数据库的转账操作,核心知识主要分为三部分:数据库的连接、事务管理、依赖注入和功能测试。
    • 案例一:直接在业务层添加代码,来进行事务管理
      • 数据库的管理问题:通过Connection工具类来实现
      • 事务管理:通过 Transaction 工具类,在 Service 业务层中实现
      • 依赖注入:通过 xml配置文件
      • 测试:通过Junit测试
    • 案例二:通过代理模式增强代码,来进行事务管理
      • 数据库的管理问题:通过Connection工具类来实现
      • 事务管理:通过 Transaction 工具类,在 BeanFactory 代理类中实现。(获取增强过的业务层接口)
      • 依赖注入:通过 xml配置文件
      • 测试:通过 Junit 测试
    • 案例三:通过SpringAOP,来进行事务管理
      • 数据库的管理问题:通过Connection工具类来实现
      • 事务管理:完全通过 Transaction 工具类实现(AOP,高内聚)
      • 依赖注入:通过 xml配置文件 + 注解
      • 测试:通过 Spring Junit 测试

项目环境

数据库代码

  • 创建数据库spring,在数据库中创建 Account表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建数据库
CREATE DATABASE spring;

# 使用数据库
USE spring;

# 创建表
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money FLOAT
)CHARACTER SET utf8 COLLATE utf8_general_ci;

# 插入数据
INSERT INTO account(NAME,money) VALUES('Cat',1000);
INSERT INTO account(NAME,money) VALUES('Dog',1000);
INSERT INTO account(NAME,money) VALUES('Rat',1000);

Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<dependencies>
<!-- Spring 框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!-- MySQL数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- 操作数据库的工具包 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<!-- Spring 测试 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

业务层实现事务

目录结构

  • src

    • main

      • java

        • cn.water

          • momain:用于存放实体类。

            • Account.java(实体类)
          • dao:用于存放持久层的接口和实现类。(具体的CRUD操作代码)

            • AccountDao.java(持久层接口)
            • AccountDaoImp.java(持久层实现类)
          • service:用于存放业务层的接口和实现类。

            • AccountService.java(业务层接口)
            • AccountServiceImp.java(业务层接口)
          • utils:用于存放工具类

            • ConnectionUtils.java(数据库连接工具类)
            • TransactionManager.java(事务管理工具类)
      • resources:用于存放配置文件

      • Beans.xml(Spring配置文件)

    • test:用于测试。

      • cn.water.test
        • SpringTest.java(测试类)

实体类

  • 封装数据库查询操作的结果集

Account.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package cn.water.domain;


public class Account {

/* 成员变量 */
private Integer id;
private String name;
private Float money;

/* 构造函数 */
public Account() {
}

public Account(Integer id, String name, Float money) {
this.id = id;
this.name = name;
this.money = money;
}

/* 设值函数 */
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 Float getMoney() {
return money;
}

public void setMoney(Float money) {
this.money = money;
}

/* toString */
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}

持久层

  • 实现具体的数据库操作代码

AccountDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.water.dao;

import cn.water.domain.Account;

import java.util.List;


public interface AccountDao {

/** 查询单个 */
Account findByID(int id);
/** 修改 */
void update(Account account);


}

AccountDaoImp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package cn.water.dao;


import cn.water.domain.Account;
import cn.water.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;


public class AccountDaoImp implements AccountDao {

/* 成员方法 */
private QueryRunner queryRunner;
private ConnectionUtils connectionUtils;

/* 设值方法 */
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}


/** 查询单个 */
public Account findByID(int id) {
try {
/* QueryRunner.query */
return queryRunner.query(
/* Connection对象 */
connectionUtils.getThreadConnection(),
/* SQL语句 */
"SELECT * FROM account WHERE id = ? ",
/* 结果集 */
new BeanHandler<Account>(Account.class),
id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}


/** 修改 */
public void update(Account account) {
try {
/* QueryRunner.update */
queryRunner.update(
/* Connection对象 */
connectionUtils.getThreadConnection(),
/* SQL语句 */
" UPDATE account SET name=?,money=? WHERE id=? ",
/* 参数 */
account.getName(),
account.getMoney(),
account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}


}

数据库连接工具类

  • 获取当前线程上的Connection对象

ConnectionUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package cn.water.utils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class ConnectionUtils {

/* 成员变量 */
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private DataSource dataSource;

/* 设值函数 */
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/** 获取Connection对象 */
public Connection getThreadConnection(){
try {
/* 获取从当前线程上的Connection对象 */
Connection connection = threadLocal.get();
/* 如果当前线程没有绑定Connection */
if (connection==null) {
/* 从数据库连接池中,获取一个Connection对象 */
connection = dataSource.getConnection();
/* 并绑定至当前线程 */
threadLocal.set(connection);
}
/* 返回此Connection对象 */
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/** 将Connection和当前线程 解绑 */
public void removeConnection(){
threadLocal.remove();
}

}

事务管理工具类

  • 对当前线程上的 Connection对象 进行事务管理的具体方法

TransactionManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package cn.water.utils;

import java.sql.Connection;
import java.sql.SQLException;

public class TransactionManager {

/* 成员变量 */
private ConnectionUtils connectionUtils;
private Connection connection;

/* 设值函数 */
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

/** 开启事务 */
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 提交事务 */
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 回滚事务 */
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 释放连接 */
public void release(){
try {
/* 返回至连接池 */
connectionUtils.getThreadConnection().close();
/* 将Connection对象与线程解绑 */
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}


}

业务层(TM)

  • 直接调用事务管理工具类的方法,实现事务管理

AccountService.java

1
2
3
4
5
6
7
8
9
10
11
12
package cn.water.service;


import cn.water.domain.Account;


public interface AccountService {

/** 转账 */
void transfer(int sourceId, int targetId, float money);

}

AccountServiceImp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package cn.water.service;

import cn.water.dao.AccountDao;
import cn.water.domain.Account;
import cn.water.utils.TransactionManager;

import java.util.List;


public class AccountServiceImp implements AccountService {

/* 成员变量 */
private AccountDao dao;
private TransactionManager txManager;

/* 设值方法 */
public void setDao(AccountDao dao) {
this.dao = dao;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}

/** 转账 */
public void transfer(int sourceId, int targetId, float money) {
try {
/* 开启事务 */
txManager.beginTransaction();
/* 执行操作 */
Account source = dao.findByID(sourceId);
Account target = dao.findByID(targetId);
Float sourceMoney = source.getMoney();
Float targetMoney = target.getMoney();
source.setMoney(sourceMoney - money);
// int i = 10/0;
target.setMoney(targetMoney + money);
dao.update(source);
dao.update(target);
/* 提交事务 */
txManager.commit();
} catch (Exception e) {
/* 回滚 */
txManager.rollback();
/* 抛出异常 */
throw new RuntimeException();
} finally {
/* 释放连接 */
txManager.release();
}

}


}

配置文件

  • ServiceImp(业务层)
    • DaoImp
  • DaoImp(持久层)
    • QueryRunner
  • QueryRunner(数据库操作类)
    • DataSource
  • TransactionManager(事务管理工具类)
    • ConnectionUtils
  • ConnectionUtils(数据库连接工具类)
    • DataSource
  • DataSource(数据库连接池)

Beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<!-- DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>

<!-- ConnectionUtils -->
<bean id="connectionUtils" class="cn.water.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- TransactionManager -->
<bean id="txManager" class="cn.water.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>

<!-- QueryRunner -->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>

<!-- Dao -->
<bean id="daoImp" class="cn.water.dao.AccountDaoImp">
<property name="connectionUtils" ref="connectionUtils"/>
<property name="queryRunner" ref="runner"/>
</bean>

<!-- Service -->
<bean id="serviceImp" class="cn.water.service.AccountServiceImp">
<property name="dao" ref="daoImp"/>
<property name="txManager" ref="txManager"/>
</bean>


</beans>

测试类

SpringTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.water.test;

import cn.water.service.AccountService;
import cn.water.service.AccountServiceImp;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class SpringTest {


@Test
public void testTransfer(){
ApplicationContext app = new ClassPathXmlApplicationContext("Beans.xml");
AccountService service = app.getBean("serviceImp", AccountServiceImp.class);
service.transfer(1,2,10f);
}



}

代理类实现事务

目录结构

  • src

    • main

      • java

        • cn.water

          • momain:用于存放实体类。

            • Account.java(实体类)
          • dao:用于存放持久层的接口和实现类。(具体的CRUD操作代码)

            • AccountDao.java(持久层接口)
            • AccountDaoImp.java(持久层实现类)
          • service:用于存放业务层的接口和实现类。

            • AccountService.java(业务层接口)
            • AccountServiceImp.java(业务层接口)
          • utils:用于存放工具类

            • ConnectionUtils.java(数据库连接工具类)
            • TransactionManager.java(事务管理工具类)
          • factory:用于存放工厂类

            • BeanFactory.java(代理工厂类)
      • resources:用于存放配置文件

  • Beans.xml(Spring配置文件)

  • test:用于测试。

    • cn.water.test
      • SpringTest.java(测试类)

实体类(不变)

  • 封装数据库查询操作的结果集

Account.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package cn.water.domain;


public class Account {

/* 成员变量 */
private Integer id;
private String name;
private Float money;

/* 构造函数 */
public Account() {
}

public Account(Integer id, String name, Float money) {
this.id = id;
this.name = name;
this.money = money;
}

/* 设值函数 */
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 Float getMoney() {
return money;
}

public void setMoney(Float money) {
this.money = money;
}

/* toString */
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}

持久层

  • 实现具体的数据库操作代码
  • 引入 ConnectionUtils ,获取 Connection对象,在调用QueryRunner类的方法时传入。
    • 我们不难看出,QueryRunner类和Connection对象的关系:
      • 一、向QueryRunner类传入DataSource对象;
      • 二、调用QueryRunner类的方法时传入Connection对象。

AccountDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.water.dao;

import cn.water.domain.Account;

import java.util.List;


public interface AccountDao {

/** 查询单个 */
Account findByID(int id);
/** 修改 */
void update(Account account);


}

AccountDaoImp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package cn.water.dao;


import cn.water.domain.Account;
import cn.water.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;


public class AccountDaoImp implements AccountDao {

/* 成员方法 */
private QueryRunner queryRunner;
private ConnectionUtils connectionUtils;

/* 设值方法 */
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}


/** 查询单个 */
public Account findByID(int id) {
try {
/* QueryRunner.query */
return queryRunner.query(
/* Connection对象 */
connectionUtils.getThreadConnection(),
/* SQL语句 */
"SELECT * FROM account WHERE id = ? ",
/* 结果集 */
new BeanHandler<Account>(Account.class),
id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}


/** 修改 */
public void update(Account account) {
try {
/* QueryRunner.update */
queryRunner.update(
/* Connection对象 */
connectionUtils.getThreadConnection(),
/* SQL语句 */
" UPDATE account SET name=?,money=? WHERE id=? ",
/* 参数 */
account.getName(),
account.getMoney(),
account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}



}

数据库连接工具类(不变)

  • 获取当前线程上的Connection对象(不变)

ConnectionUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package cn.water.utils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class ConnectionUtils {

/* 成员变量 */
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private DataSource dataSource;

/* 设值函数 */
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/** 获取Connection对象 */
public Connection getThreadConnection(){
try {
/* 获取从当前线程上的Connection对象 */
Connection connection = threadLocal.get();
/* 如果当前线程没有绑定Connection */
if (connection==null) {
/* 从数据库连接池中,获取一个Connection对象 */
connection = dataSource.getConnection();
/* 并绑定至当前线程 */
threadLocal.set(connection);
}
/* 返回此Connection对象 */
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/** 将Connection和当前线程 解绑 */
public void removeConnection(){
threadLocal.remove();
}

}

事务管理工具类(不变)

  • 对当前线程上的 Connection对象 进行事务管理的具体方法

TransactionManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package cn.water.utils;

import java.sql.Connection;
import java.sql.SQLException;

public class TransactionManager {

/* 成员变量 */
private ConnectionUtils connectionUtils;

/* 设值函数 */
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

/** 开启事务 */
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 提交事务 */
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 回滚事务 */
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 释放连接 */
public void release(){
try {
/* 返回至连接池 */
connectionUtils.getThreadConnection().close();
/* 将Connection对象与线程解绑 */
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}


}

业务层

  • 直接调用事务管理工具类的方法,实现事务管理

AccountService.java

1
2
3
4
5
6
7
8
9
10
11
12
package cn.water.service;


import cn.water.domain.Account;


public interface AccountService {

/** 转账 */
void transfer(int sourceId, int targetId, float money);

}

AccountServiceImp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package cn.water.service;

import cn.water.dao.AccountDao;
import cn.water.domain.Account;

import java.util.List;


public class AccountServiceImp implements AccountService {

/* 成员变量 */
private AccountDao dao;

/* 设值方法 */
public void setDao(AccountDao dao) {
this.dao = dao;
}


/** 转账 */
public void transfer(int sourceId, int targetId, float money) {
Account source = dao.findByID(sourceId);
Account target = dao.findByID(targetId);
Float sourceMoney = source.getMoney();
Float targetMoney = target.getMoney();
source.setMoney(sourceMoney - money);
dao.update(source);
// int i = 10/0;
target.setMoney(targetMoney + money);
dao.update(target);
}


}

代理工厂类(TM)

  • 代理模式:创建代理对象,并增强代码
  • 工厂模式:返回一个代理对象。

ProxyFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package cn.water.factory;

import cn.water.service.AccountService;
import cn.water.utils.TransactionManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class BeanFactory {

/* 成员变量 */
private AccountService service;
private TransactionManager txManager;

/* 设值函数 */
public void setService(AccountService service) {
this.service = service;
}

public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}

/** 获取代理对象 */
public AccountService getProxyService(){
return (AccountService) Proxy.newProxyInstance(
/* 类加载器 */
service.getClass().getClassLoader(),
/* 接口 */
service.getClass().getInterfaces(),
/** InvocationHandler接口:事务管理 */
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/* 查询方法不需要进行事务管理 */
if ("find".equals(method.getName())){
return method.invoke(service,args);
/* 增删改方法进行进行事务管理 */
}else {
try {
/* 开启事务 */
txManager.beginTransaction();
/* 执行操作 */
Object result = method.invoke(service, args);
/* 提交事务 */
txManager.commit();
/* 返回结果集 */
return result;
} catch (Exception e) {
/* 回滚 */
txManager.rollback();
/* 抛出异常 */
throw new RuntimeException();
} finally {
/* 释放连接 */
txManager.release();
}
}
}
}
);
}


}

配置文件

  • ProxyService(代理类)
  • BeanFactory(代理工厂类)
    • ServiceImp
    • TransactionManager
  • ServiceImp(业务层)
    • DaoImp
  • DaoImp(持久层)
    • QueryRunner
    • ConnectionUtils
  • QueryRunner(数据库操作类)
    • ConnectionUtils 能够为 DaoImp 提供 Connection对象,因此不必再为 QueryRunner 提供 DataSource
    • 但要转而为 DaoImp 提供 ConnectionUtils
  • TransactionManager(事务管理工具类)
    • ConnectionUtils
  • ConnectionUtils(数据库连接工具类)
    • DataSource
  • DataSource(数据库连接池)

Beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<!-- DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>

<!-- ConnectionUtils -->
<bean id="connectionUtils" class="cn.water.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- TransactionManager -->
<bean id="txManager" class="cn.water.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"/>
</bean>

<!-- QueryRunner -->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner"></bean>

<!-- Dao -->
<bean id="daoImp" class="cn.water.dao.AccountDaoImp">
<property name="connectionUtils" ref="connectionUtils"/>
<property name="queryRunner" ref="runner"/>
</bean>

<!-- Service -->
<bean id="serviceImp" class="cn.water.service.AccountServiceImp">
<property name="dao" ref="daoImp"/>
</bean>

<!-- BeanFactory -->
<bean id="factory" class="cn.water.factory.BeanFactory">
<property name="service" ref="serviceImp"/>
<property name="txManager" ref="txManager"/>
</bean>

<!-- 代理service -->
<bean id="proxyService" factory-bean="factory" factory-method="getProxyService"></bean>



</beans>

测试类

  • Spring框架整合junit

SpringTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package cn.water.test;

import cn.water.domain.Account;
import cn.water.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;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:Beans.xml")
public class SpringTest {

/* 获取代理对象 */
@Resource(name = "proxyService")
private AccountService service;

@Test
public void testTransfer(){
service.transfer(1,2,10f);
}


}

通知类实现事务(AOP)

目录结构

  • src

    • main

      • java
        • cn.water
          • dao:用于存放持久层的接口和实现类。(具体的CRUD操作代码)
            • AccountDao.java(持久层接口)
            • AccountDaoImp.java(持久层实现类)
          • momain:用于存放实体类。
            • Account.java(实体类)
          • service:用于存放业务层的接口和实现类。
            • AccountService.java(业务层接口)
            • AccountServiceImp.java(业务层接口)
          • utils:用于存放工具类
            • ConnectionUtils.java(数据库连接工具类)
            • TransactionManager.java(事务管理工具类)
      • resources:用于存放配置文件
        • Beans.xml(Spring配置文件)
    • test:用于测试。

      • cn.water.test
        • SpringTest.java(测试类)

实体类(不变)

  • 封装数据库查询操作的结果集

Account.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package cn.water.domain;


public class Account {

/* 成员变量 */
private Integer id;
private String name;
private Float money;

/* 构造函数 */
public Account() {
}

public Account(Integer id, String name, Float money) {
this.id = id;
this.name = name;
this.money = money;
}

/* 设值函数 */
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 Float getMoney() {
return money;
}

public void setMoney(Float money) {
this.money = money;
}

/* toString */
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}

持久层

  • 实现具体的数据库操作代码

AccountDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.water.dao;

import cn.water.domain.Account;

import java.util.List;


public interface AccountDao {

/** 查询单个 */
Account findByID(int id);
/** 修改 */
void update(Account account);


}

AccountDaoImp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package cn.water.dao;


import cn.water.domain.Account;
import cn.water.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;


@Component("daoImp")
public class AccountDaoImp implements AccountDao {

/* 成员方法 */
@Resource(name = "runner")
private QueryRunner queryRunner;

@Resource(name = "connectionUtils")
private ConnectionUtils connectionUtils;



/** 查询单个 */
public Account findByID(int id) {
try {
/* QueryRunner.query */
return queryRunner.query(
/* Connection对象 */
connectionUtils.getThreadConnection(),
/* SQL语句 */
"SELECT * FROM account WHERE id = ? ",
/* 结果集 */
new BeanHandler<Account>(Account.class),
id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}


/** 修改 */
public void update(Account account) {
try {
/* QueryRunner.update */
queryRunner.update(
/* Connection对象 */
connectionUtils.getThreadConnection(),
/* SQL语句 */
" UPDATE account SET name=?,money=? WHERE id=? ",
/* 参数 */
account.getName(),
account.getMoney(),
account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}



}

数据库连接工具类(不变)

  • 获取当前线程上的Connection对象

ConnectionUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package cn.water.utils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class ConnectionUtils {

/* 成员变量 */
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private DataSource dataSource;

/* 设值函数 */
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/** 获取Connection对象 */
public Connection getThreadConnection(){
try {
/* 获取从当前线程上的Connection对象 */
Connection connection = threadLocal.get();
/* 如果当前线程没有绑定Connection */
if (connection==null) {
/* 从数据库连接池中,获取一个Connection对象 */
connection = dataSource.getConnection();
/* 并绑定至当前线程 */
threadLocal.set(connection);
}
/* 返回此Connection对象 */
return connection;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/** 将Connection和当前线程 解绑 */
public void removeConnection(){
threadLocal.remove();
}

}

事务管理工具类(TM)

  • 使用Spring框架的AOP机制,进行事务管理

TransactionManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package cn.water.utils;

import org.aspectj.lang.annotation.*;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.SQLException;


@Aspect
public class TransactionManager {

/* 成员变量 */
@Resource(name = "connectionUtils")
private ConnectionUtils connectionUtils;

/** 切入点 */
@Pointcut(value = "execution(* cn.water.utils.*.*(..))")
public void all(){}

/** 开启事务(前置通知) */
@Before("all()")
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 提交事务(后置通知) */
@AfterReturning("all()")
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 回滚事务(异常通知) */
@AfterThrowing("all()")
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/** 释放连接(最终通知) */
@After("all()")
public void release(){
try {
/* 返回至连接池 */
connectionUtils.getThreadConnection().close();
/* 将Connection对象与线程解绑 */
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}


}

业务层

  • 直接调用事务管理工具类的方法,实现事务管理

AccountService.java

1
2
3
4
5
6
7
8
9
10
11
12
package cn.water.service;


import cn.water.domain.Account;


public interface AccountService {

/** 转账 */
void transfer(int sourceId, int targetId, float money);

}

AccountServiceImp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package cn.water.service;

import cn.water.dao.AccountDao;
import cn.water.domain.Account;
import cn.water.utils.TransactionManager;

import java.util.List;


public class AccountServiceImp implements AccountService {

/* 成员变量 */
private AccountDao dao;
private TransactionManager txManager;

/* 设值方法 */
public void setDao(AccountDao dao) {
this.dao = dao;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}

/** 转账 */
public void transfer(int sourceId, int targetId, float money) {
try {
/* 开启事务 */
txManager.beginTransaction();
/* 执行操作 */
Account source = dao.findByID(sourceId);
Account target = dao.findByID(targetId);
Float sourceMoney = source.getMoney();
Float targetMoney = target.getMoney();
source.setMoney(sourceMoney - money);
// int i = 10/0;
target.setMoney(targetMoney + money);
dao.update(source);
dao.update(target);
/* 提交事务 */
txManager.commit();
} catch (Exception e) {
/* 回滚 */
txManager.rollback();
/* 抛出异常 */
throw new RuntimeException();
} finally {
/* 释放连接 */
txManager.release();
}

}


}

配置文件

  • QueryRunner(数据库操作类)
  • DataSource(数据库连接池)
  • 开启Spring注解扫描
  • 开启SpringAOP注解支持

Beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>

<!-- QueryRunner -->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner"></bean>

<!-- 开启Spring注解扫描 -->
<context:component-scan base-package="cn.water"/>

<!-- 开启SpringAOP注解支持 -->
<aop:aspectj-autoproxy/>

</beans>

测试类

SpringTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.water.test;

import cn.water.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;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:Beans.xml")
public class SpringTest {

@Resource(name = "serviceImp")
private AccountService service;

@Test
public void testTransfer(){
service.transfer(1,2,10f);
}


}
-------------本文结束-------------
Donate comment here