接下来安排
- 完善 account 案例
- 分析案例中问题
- 回顾之前讲过的一个技术:动态代理
- 动态代理另一种实现方式
- 解决案例中的问题
- AOP 的概念
- spring 中的 AOP 相关术语
- spring 中基于 XML 和注解的 AOP
一、创建新工程
创建新的 maven 工程,命名为 spring07
做一个转账的小操作
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>spring07</groupId>
<artifactId>spring07</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.结构
3.接口与实现类添加的操作
ⅠIAccountDao
/**
* 根据名称查询账户
* @param accountName 账户名称
* @return 如果有唯一结果就返回,没有就返回 Null, 如果结果有多个,这返回错误
*/
Account findAccountByName(String accountName);
Ⅱ AccountDaoImpl
public void updateAccount(Account account) {
try {
runner.update("update account02 set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (SQLException e){
e.printStackTrace();
}
}
public Account findAccountByName(String accountName) {
List<Account> accounts=null;
try {
accounts= runner.query("select *from account02 where name=?",new BeanListHandler<Account>(Account.class),accountName);
if (accounts==null || accounts.size()==0){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
} catch (SQLException e) {
e.printStackTrace();
}
return accounts.get(0);
}
Ⅲ IAccountService
/**
* 转账
* @param sourceName 转出账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName,String targetName,Float money);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
四 IAccountService
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户
Account source=accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target=accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
accountDao.updateAccount(source);
//6.更新转入账户
accountDao.updateAccount(target);
}
Ⅴ Test
@Test
public void testTransfer(){
as.transfer("aaa","bbb",10f);
}
测试结果没有问题
Ⅵ 问题
假如 我们在AccountServiceImpl 这样操作一波,多加了个 int i=1/0;
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户
Account source=accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target=accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//6.更新转入账户
accountDao.updateAccount(target);
}
运行,你会看到 source 的账户减钱了,而 target 的账户却没有价钱,这怎么办?
原因:
这是一个多例对象
解决:
要让四个操作全为同一个 connection 来实现
需要使用 ThreadLocal 对象把 Connection 和当前线程绑定,从而使一个线程中只有一能控制事务的对象
二、编写 ConnectionUtils
创建 utiles 文件夹
创建 ConnectionUtils 类
创建 TransactionManager类
1. ConnectionUtils 类
package com.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 描述:
* 〈连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定〉
*
* @author zuiren
* @create 2019/8/30
* @since 1.0.0
*/
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public Connection getThreadConnection(){
//1.先从 ThreadLocal 上获取
Connection conn=tl.get();
try {
//2.判断当前线程是否有连接
if (conn==null){
//3.从数据源中获取一个链接,并且存入 ThreadLocal 中
conn=dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
2. TransactionManager类
package com.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* 描述:
* 〈和事务相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接〉
*
* @author zuiren
* @create 2019/8/30
* @since 1.0.0
*/
@Component(value = "txManager")
public class TransactionManager {
@Autowired
private 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();
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
细节:
连接使用了连接池,
连接池的好处:把消耗时间连接获取连接的这部分放到应用加载一开始。
在 web 工程中,当我们启动tomcat加载应用时,我们创建一些连接,从而在后续项目运行阶段不在跟数据库获取链接保证了我们使用 connection 时的使用效率。
我们使用服务器,服务器也会有一个池的技术叫做线程池,它的特点是当 connection 启动时,会启动一大堆的线程放到一个容器中,接下来我们每次访问,它都是从线程池中拿出一个线程给我们使用。
这样的话线程池中的线程也跟我们连接池中的一样,所以我们最后调用
connectionUtils.getThreadConnection().close();
的方法并不是将其关闭,而是将他放回线程池中。
造理推断,线程用完了,也不是真正的关了,而是把线程还回线程池中。
所以这个线程中是绑着一个连接的,当我们把连接关闭,线程还回池中时,线程上是有连接的,只不过这个连接已经被关闭了,当我们下次再获取这个线程判断上面有没有连接时,你得到的结果一定是有,但是这个连接已经不能用了,因为它已经被 close 过了,被加载过池子里去了。
所以从这点上来说:我们应该在整个这个线程用完了之后,把这个线程和这个连接进行解绑。
在 ConnectionUtils 类中添加一方法
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
在 TransactionManager 类
/**
* 释放连接
*/
public void release(){
try {
//还回池中
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
三、编写业务层和持久层事务控制代码并配置 spring 的 ioc
1.AccountServiceImpl
package com.service.Impl;
import com.dao.IAccountDao;
import com.domain.Account;
import com.service.IAccountService;
import com.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.TreeMap;
/**
* 描述:
* 〈〉
*
* @author zuiren
* @create 2019/8/29
* @since 1.0.0
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Autowired
private TransactionManager txManager;
public List<Account> findAllAccount() {
List<Account> accounts=null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accounts=accountDao.findAllAccount();
//3.提交事务
txManager.commit();
//4.返回结果
return accounts;
}catch (Exception e){
//5.回滚操作
txManager.rollback();
}finally {
//6.释放资源
txManager.release();
}
return accounts;
}
public Account findAccountById(Integer accountId) {
Account account=null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
account=accountDao.findAccountById(accountId);
//3.提交事务
txManager.commit();
//4.返回结果
return account;
}catch (Exception e){
//5.回滚操作
txManager.rollback();
}finally {
//6.释放资源
txManager.release();
}
return account;
}
public void saveAccount(Account account) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accountDao.saveAccount(account);
//3.提交事务
txManager.commit();
//4.返回结果
}catch (Exception e){
//5.回滚操作
txManager.rollback();
}finally {
//6.释放资源
txManager.release();
}
}
public void updateAccount(Account account) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accountDao.updateAccount(account);
//3.提交事务
txManager.commit();
//4.返回结果
}catch (Exception e){
//5.回滚操作
txManager.rollback();
}finally {
//6.释放资源
txManager.release();
}
}
public void deleteAccount(Integer accountId) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
accountDao.deleteAccount(accountId);
//3.提交事务
txManager.commit();
//4.返回结果
}catch (Exception e){
//5.回滚操作
txManager.rollback();
}finally {
//6.释放资源
txManager.release();
}
}
public void transfer(String sourceName, String targetName, Float money) {
try {
txManager.beginTransaction();
//1.根据名称查询转出账户
Account source=accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target=accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//6.更新转入账户
accountDao.updateAccount(target);
txManager.commit();
}catch (Exception e){
txManager.rollback();
}finally {
txManager.release();
}
}
}
2.AccountDaoImpl
这里只是在 runner 执行操作时,给它配置连接
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
import java.util.List;
/**
* 描述:
* 〈〉
*
* @author zuiren
* @create 2019/8/29
* @since 1.0.0
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
public List<Account> findAllAccount() {
try {
return runner.query(connectionUtils.getThreadConnection(),"select *from account02",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return runner.query(connectionUtils.getThreadConnection(),"select *from account02 where id = ?",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"insert into account02(name,money) values(?,?)",account.getName(),account.getMoney());
}catch (SQLException e){
e.printStackTrace();
}
}
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account02 set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (SQLException e){
e.printStackTrace();
}
}
public void deleteAccount(Integer accountId) {
try {
runner.update(connectionUtils.getThreadConnection(),"delete from account02 where id=?",accountId);
}catch (SQLException e){
e.printStackTrace();
}
}
public Account findAccountByName(String accountName) {
List<Account> accounts=null;
try {
accounts= runner.query(connectionUtils.getThreadConnection(),"select *from account02 where name=?",new BeanListHandler<Account>(Account.class),accountName);
if (accounts==null || accounts.size()==0){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
} catch (SQLException e) {
e.printStackTrace();
}
return accounts.get(0);
}
}
3.Test
@Test
public void testTransfer(){
as.transfer("aaa","bbb",10f);
}
4.结果
你会发现报错,事务回滚,aaa 的 money 没有变化,转账正常执行。
问题是现在依赖特别混乱,以后会解决