言成言成啊 | Kit Chen's Blog

Spring的AOP

发布于2021-03-07 00:11:47,更新于2021-04-22 01:50:18,标签:spring  转载随意,文章会持续修订,请注明来源地址:https://meethigher.top/blog

Spring官网

Spring学习资料

目录

  1. Spring的IOC和DI
  2. Spring的AOP
  3. Spring的事务控制
  4. 源码

六、整合Junit

对于重复代码,我们可以通过junit的@Before与@After注解来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SpringAndJunitTest {
private AnnotationConfigApplicationContext aac;
private AccountService accountService;

@Before
public void setUp() throws Exception {
aac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
accountService = aac.getBean("accountService", AccountService.class);
}

@After
public void tearDown() throws Exception {
aac.close();
}
}

这算是一个解决办法,但是我们需要在该类中定义成员变量,而且这只是junit测试时所采用的手段,spring开发时,总不能使用以前的测试的手段吧。

所以,就有了Spring整合Junit的测试方式。

6.1 区别

一个常规的应用程序入口:main方法

junit单元测试中,没有main方法也能执行,junit集成了一个main方法,该方法会判断当前测试类中,哪些方法有@Test注解。如果有,junit就会让有@Test注解的方法执行。无疑会调用method.invoke()

在执行测试方法时,junit根本不知道我们使用了Junit框架,也就不会为我们读取配置文件或者配置类,来创建spring核心容器。

当测试方法执行时,没有ioc容器,就算写了注解,也无法实现注入。

6.2 解决方法

Spring整合Junit的配置

  1. 导入spring整合junit的jar(或者坐标)
  2. 使用Junit提供的一个注解把原有的main方法替换,替换成Spring提供的
    • @Runwith:@RunWith(SpringJUnit4ClassRunner.class)
  3. 告知运行器,Spring和IOC创建时基于xml还是注解的,并且说明位置
    • @ContextConfiguration
      • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
      • classes:指定注解类所在的位置

版本要求:当我们使用spring5.x时,要求必须使用junit4.12及以上版本

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.4</version>
<scope>test</scope>
</dependency>

测试类

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
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
//@ContextConfiguration(locations = "classpath:spring.xml")
public class SpringAndJunitTest {
@Autowired
private AccountService accountService;


@Test
public void testFindAll() {
List<Account> all = accountService.findAll();
System.out.println(all);
}

@Test
public void testFindById() {
Account byId = accountService.findById(1);
System.out.println(byId);
}

@Test
public void testUpdateAccount() {
Account account = new Account();
account.setId(4);
account.setName("水冰儿");
account.setMoney(7000f);
accountService.updateAccount(account);
}

@Test
public void testDeleteAccount() {
accountService.deleteAccount(4);
}

/**
* 重点:保存,并且返回自增的id
*/
@Test
public void testSaveAccount() {
Account account = new Account();
account.setName("水冰儿");
account.setMoney(8000f);
accountService.saveAccount(account);
System.out.println(account);
}

/**
* 重点:聚合查询
*/
@Test
public void testFindCount() {
System.out.println(accountService.findCount());
}

/**
* 测试传递对象(地址),形参改变,实参是否改变
*/
@Test
public void test() {
Account account = new Account();
m(account);
System.out.println(account);
}

public void m(Account account) {
account.setId(1);
}

}

七、事务控制案例

7.1 转账案例

先放上代码

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
public class AccountServiceImpl implements AccountService {


private AccountDao accountDao;
private TransactionManager tManager;

public void settManager(TransactionManager tManager) {
this.tManager = tManager;
}

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public List<Account> findAll() {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
List<Account> accounts = accountDao.findAll();
//3.提交事务
tManager.commitTransaction();
//4.返回结果
return accounts;
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public Account findById(Integer id) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
Account byId = accountDao.findById(id);
//3.提交事务
tManager.commitTransaction();
//4.返回结果
return byId;
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void saveAccount(Account account) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
accountDao.saveAccount(account);
//3.提交事务
tManager.commitTransaction();
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void updateAccount(Account account) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
accountDao.updateAccount(account);
//3.提交事务
tManager.commitTransaction();
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void deleteAccount(Integer id) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
accountDao.deleteAccount(id);
//3.提交事务
tManager.commitTransaction();
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public int findCount() {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
int count = accountDao.findCount();
//3.提交事务
tManager.commitTransaction();
//4.返回结果
return count;
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void transfer(String sourceName, String targetName, Float money) {
try {

tManager.beginTransaction();

//1.根据名称查询转出账户、转入账户
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
//2.转出账户减钱,转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3.更新转出账户、转入账户
accountDao.updateAccount(source);

//用来模拟问题
// int i = 2 / 0;

accountDao.updateAccount(target);

tManager.commitTransaction();

} catch (Exception e) {
System.out.println("转账失败!开始回滚");
tManager.rollbackTransaction();
} finally {
tManager.release();
}

}
}

如果中途出错了,比方说int i=2/0这个位置出错了,但是前面那三条语句依然是执行成功了。

原因是每条与数据库有交互的语句,是一个Connection,对应自己的一个事务,4个就是4个事务,语句执行完,事务就提交了。

前3个执行成功,最后1个失败。

我们需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中,只有一个能控制事务的对象

将连接与线程绑定,实现一个ConnectionUtils类

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
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<>();
private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取
Connection conn = tl.get();
try {
//2.判断当前线程上是否有连接
if (conn == null||conn.isClosed()) {
//3.从数据源中获取一个连接,并且和线程绑定,存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}

/**
* 将线程与连接解绑
*/
public void removeConnection(){
tl.remove();
}
}

对事务进行相关的管理,实现工具类TransactionManager

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
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 commitTransaction(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 回滚事务
*/
public void rollbackTransaction(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}

将AccountDaoImpl重新实现

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class AccountDaoImpl implements AccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;

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

public void setRunner(QueryRunner runner) {
this.runner = runner;
}

@Override
public List<Account> findAll() {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account", new BeanListHandler<Account>(Account.class));
} catch (SQLException e) {
//因为sqlexception是编译时期异常,必须处理。把他作为运行时异常,就可以不用处理了。
throw new RuntimeException();
}
}

@Override
public Account findById(Integer id) {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id=?", new BeanHandler<Account>(Account.class), id);
} catch (SQLException e) {
throw new RuntimeException();
}
}


@Override
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}

@Override
public void deleteAccount(Integer id) {
try {
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?", id);
} catch (SQLException e) {
e.printStackTrace();
}
}


/**
* 重点:保存并返回id
* @param account
*/
@Override
public void saveAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
//形参是对象时,实际传的是地址。改变形参会影响实参。
account.setId(Integer.parseInt(runner.query("select last_insert_id() id",new MapHandler()).get("id").toString()));
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 重点:聚合查询
* @return
*/
@Override
public int findCount() {
try {
// 法一
// return Integer.parseInt(runner.query("select count(*) count from account", new MapHandler()).get("count").toString());
// 法二
return Integer.parseInt(runner.query(connectionUtils.getThreadConnection(),"select count(*) from account",new ScalarHandler<>(1)).toString());
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();

}
}

@Override
public Account findByName(String name) {
try {
List<Account> list = runner.query(connectionUtils.getThreadConnection(),"select * from account where name=?", new BeanListHandler<Account>(Account.class), name);
if(list==null||list.size()==0){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return list.get(0);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}

将AccountServiceImpl重新实现

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionManager tManager;

public void settManager(TransactionManager tManager) {
this.tManager = tManager;
}

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public List<Account> findAll() {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
List<Account> accounts = accountDao.findAll();
//3.提交事务
tManager.commitTransaction();
//4.返回结果
return accounts;
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public Account findById(Integer id) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
Account byId = accountDao.findById(id);
//3.提交事务
tManager.commitTransaction();
//4.返回结果
return byId;
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void saveAccount(Account account) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
accountDao.saveAccount(account);
//3.提交事务
tManager.commitTransaction();
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void updateAccount(Account account) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
accountDao.updateAccount(account);
//3.提交事务
tManager.commitTransaction();
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void deleteAccount(Integer id) {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
accountDao.deleteAccount(id);
//3.提交事务
tManager.commitTransaction();
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public int findCount() {
try {
//1.开启事务
tManager.beginTransaction();
//2.执行操作
int count = accountDao.findCount();
//3.提交事务
tManager.commitTransaction();
//4.返回结果
return count;
} catch (Exception e) {
//5.回滚操作
tManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
//6.释放连接
tManager.release();
}
}

@Override
public void transfer(String sourceName, String targetName, Float money) {
try {

tManager.beginTransaction();

//1.根据名称查询转出账户、转入账户
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
//2.转出账户减钱,转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3.更新转出账户、转入账户
accountDao.updateAccount(source);

//用来模拟问题
// int i = 2 / 0;

accountDao.updateAccount(target);

tManager.commitTransaction();

} catch (Exception e) {
System.out.println("转账失败!开始回滚");
tManager.rollbackTransaction();
} finally {
tManager.release();
}

}
}

spring的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
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="top.meethigher.demo11"></context:component-scan>
<!--set方法注入-->
<bean id="accountDao" class="top.meethigher.demo11.dao.impl.AccountDaoImpl">
<property name="runner" ref="runner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="accountService" class="top.meethigher.demo11.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="tManager" ref="tManager"></property>
</bean>
<!--配置QueryRunner,避免多个用户使用同一个对象,我们应该将作用范围设置为多例-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg name="ds" ref="ds"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置ConnectionUtils-->
<bean id="connectionUtils" class="top.meethigher.demo11.utils.ConnectionUtils">
<property name="dataSource" ref="ds"></property>
</bean>
<!--配置事务管理器-->
<bean id="tManager" class="top.meethigher.demo11.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>

上面这个案例,可以实现转账的基本功能了,但是还存在一些问题,比如AccountServiceImpl里面有事务的重复代码,如何解决呢?

解决方案是可以使用动态代理。

7.2 动态代理

动态代理

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上,对方法增强
  • 分类
    1. 基于接口的动态代理
    2. 基于子类的动态代理

基于接口的动态代理

涉及的类:Proxy

提供者:JDK官方

如何创建代理对象:使用Proxy类中的newProxyInstance方法

创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用

newProxyInstance方法参数

  1. ClassLoader:类加载器。它是用于加载代理对象字节码的,写的是被代理对象使用相同的类加载器。固定写法
  2. Class[]:接口字节码数组。它是用于让代理对象和被代理对象有相同的方法。固定写法
  3. InvocationHandler:用于增强的代码。它是让我们写如何代理,通常情况下是匿名内部类,但不是必须的。该接口实现类是谁用谁写。
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
public class Consumer {
public static void main(String[] args) {
final ProducerImpl producer = new ProducerImpl();
/**
* 动态代理
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上,对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
*/
Producer proxyProducer = (Producer) Proxy.newProxyInstance(ProducerImpl.class.getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 执行任何被代理对象的任何接口方法都会经过该方法,相当于拦截功能
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return method.invoke(producer,args);//相当于什么也没做
//进行增强
Object returnValue=null;
Float money = (Float) args[0];
if("saleProduct".equals(method.getName())){
returnValue=method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}

基于子类的动态代理

涉及的类:Enhancer

提供者:第三方CGLib库

如何创建代理对象:使用Enhancer类中的create方法

创建代理对象的要求:被代理类不能是最终类

create方法参数

  1. Class:字节码。它是用于指定被代理对象的字节码
  2. Callback:用于提供增强的代码。我们一般写的是该接口的子接口的实现类MethodInterceptor
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
public class Consumer1 {
public static void main(String[] args) {
final ProducerImpl producer = new ProducerImpl();
/**
* 动态代理
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上,对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
*/
Producer proxyProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象任何方法,都会经过该方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 参数
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;
Float money = (Float) args[0];
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});

proxyProducer.saleProduct(10000f);
}
}

7.3 转账案例优化

连接池中连接的归还,我们可以通过动态代理的方式,将close方法增强,变为归还连接到池中。

那么转账的事务的处理,我们也可以通过动态代理来实现。

因为AccountServiceImpl中,每一步都需要执行以下步骤

  1. 开启事务
  2. 执行操作
  3. 提交事务/回滚事务
  4. 释放连接

其实我们只需要第二步执行操作,其他步骤都属于通用型的步骤。那么我们可以通过动态代理的方式来优化。

下面只放置进行修改的代码

AccountServiceImpl

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
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public List<Account> findAll() {
return accountDao.findAll();
}

@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}

@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}

@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);

}

@Override
public void deleteAccount(Integer id) {
accountDao.deleteAccount(id);
}

@Override
public int findCount() {
return accountDao.findCount();

}

@Override
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户、转入账户
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
//2.转出账户减钱,转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3.更新转出账户、转入账户
accountDao.updateAccount(source);

//用来模拟问题
int i = 2 / 0;

accountDao.updateAccount(target);
}
}

BeanFactory

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
public class BeanFactory {
private AccountService accountService;
private TransactionManager tsManager;

public final void setAccountService(AccountService accountService) {
this.accountService = accountService;
}

public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}

public AccountService getAccountService() {
return (AccountService) Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtVale = null;
try {
tsManager.beginTransaction();
Object invoke = method.invoke(accountService, args);
System.out.println("提交事务...");
tsManager.commitTransaction();
return invoke;
} catch (Exception e) {
System.out.println("出现异常,回滚事务...");
tsManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
System.out.println("释放连接...");
tsManager.release();
}
}
});
}
}

spring.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
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="top.meethigher.demo13"></context:component-scan>
<!--避免多个用户使用同一个QueryRunner对象,所以设置为多例-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg name="ds" ref="ds"></constructor-arg>
</bean>
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/spring?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="connectionUtils" class="top.meethigher.demo13.utils.ConnectionUtils">
<property name="dataSource" ref="ds"></property>
</bean>
<bean id="tsManager" class="top.meethigher.demo13.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="accountDao" class="top.meethigher.demo13.dao.impl.AccountDaoImpl">
<property name="runner" ref="runner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="accountService" class="top.meethigher.demo13.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="beanFactory" class="top.meethigher.demo13.factory.BeanFactory">
<property name="tsManager" ref="tsManager"></property>
<property name="accountService" ref="accountService"></property>
</bean>
<!--获取对象的三种方式之一-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
</beans>

测试类

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
70
71
72
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class SpringAndJunitTest13 {
@Resource(name = "proxyAccountService")
private AccountService accountService;

@Test
public void transfer() {
accountService.transfer("白沉香","小舞",500f);
}

@Test
public void testFindAll() {
List<Account> all = accountService.findAll();
System.out.println(all);
}

@Test
public void testFindById() {
Account byId = accountService.findById(1);
System.out.println(byId);
}

@Test
public void testUpdateAccount() {
Account account = new Account();
account.setId(4);
account.setName("水冰儿");
account.setMoney(7000f);
accountService.updateAccount(account);
}

@Test
public void testDeleteAccount() {
accountService.deleteAccount(4);
}

/**
* 重点:保存,并且返回自增的id
*/
@Test
public void testSaveAccount() {
Account account = new Account();
account.setName("水冰儿");
account.setMoney(8000f);
accountService.saveAccount(account);
System.out.println(account);
}

/**
* 重点:聚合查询
*/
@Test
public void testFindCount() {
System.out.println(accountService.findCount());
}

/**
* 测试传递对象(地址),形参改变,实参是否改变
*/
@Test
public void test() {
Account account = new Account();
m(account);
System.out.println(account);
}

public void m(Account account) {
account.setId(1);
}

}

八、AOP

Aspect Oriented Programming,面向切面编程

AOP技术的本质是动态代理。动态代理的本质是在调用时拦截对象方法,不修改源码的基础上对方法进行改造、增强。

优势

  1. 减少重复代码
  2. 提高开发效率
  3. 维护方便

8.1 Spring AOP代理的选择

Spring AOP的动态代理的实现

  1. 基于接口的动态代理
  2. 基于子类的动态代理

如果实现了接口,就可以使用基于接口的动态代理

如果被代理类不是最终类,就可以使用基于子类的动态代理

8.2 Spring AOP术语

术语

  1. Joinpoint
    • 连接点
    • 连接点就是指那些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  2. Pointcut
    • 切入点
    • 切入点就比如转账案例中被增强的方法,就是切入点
    • 所有的切入点都是连接点,连接点不一定是切入点,因为有的方法不需要增强
  3. Advice
    • 通知
    • 拦截到Joinpoint之后要做的事情,就是通知
    • 类型:前置通知、后置通知、异常通知、最终通知、环绕通知
  4. Introduce
    • 引介
    • 是一种特殊的通知,在不修改类代码的前提下,introduction可以在运行期为类动态地添加一些方法或者变量
  5. Target
    • 目标对象
    • 代理的目标对象,即被代理对象
  6. Weaving
    • 织入
    • 把增强应用到目标对象来创建新的代理对象的过程
  7. Proxy
    • 代理对象
    • 一个类被AOP织入增强后,就产生一个结果代理类
  8. Aspect
    • 切面
    • 切入点和引介的结合

8.3 基于XML的Spring AOP

切入点表达式

约束去官网文档core下,ctrl+f搜xmlns:aop即可。

配置spring的aop步骤

  1. 把通知bean也交给spring来管理,比如Logger类
  2. 使用aop:config标签表明开始aop的配置
  3. 使用aop:aspect标签表明开始配置切面
    • id:给切面提供一个唯一的标识
    • ref:指定通知类bean的id
  4. 在aop:aspect标签的内部使用对应的标签来配置通知的类型
    • 前置通知:aop:before
      • method:指定通知的方法
      • pointcut:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
    • 后置通知:aop:after-returning
    • 异常通知:aop:after-throwing
    • 最终通知:aop:after
    • 环绕通知:aop:around
    • 切入点表达式写法
      • 标准格式:execution(表达式)
      • 标准表达式:访问修饰符 返回值 包名.包名…类名.方法名(参数列表)
      • 举例标准表达式写法:public void top.meethigher.service.impl.AccountServiceImpl.saveAccount()
      • 注意
        1. 访问修饰符可以省略;
        2. 返回值用*表示可以是任何返回值;
        3. 包名可以使用*表示任意包,有几级包,就要写几个*.
        4. 包名还可以使用..表示当前包及子包
        5. 类名和方法名,都可以使用*来实现通配
        6. 参数列表
          • 基本类型直接写名称,如int
          • 引用类型写包名.类名,如java.lang.String
          • 使用*表示任意类型,但是必须有参数的
          • 使用..表示有无参数都行,有参数可以表示任意类型
      • 全通配写法:* *..*.*(..)
      • 实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法,如* top.meethigher.service.impl.*.*(..)
    • 配置切入点表达式aop:pointcut
      • 写在aop:aspect内部,只能当前切面用
      • 写在aop:aspect外部,所有切面都可以用

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
System.out.println("保存账户!");
}

@Override
public void updateAccount(int i) {
System.out.println("更新账户!");
}

@Override
public int deleteAccount() {
System.out.println("删除账户!");
return 0;
}
}

Logger

1
2
3
4
5
6
7
8
public class Logger {
/**
* 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的printLog开始记录日志!");
}
}

spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="top.meethigher.demo14.service.impl.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="top.meethigher.demo14.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logger" ref="logger">
<!--配置通知类型并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void top.meethigher.demo14.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
<!-- <aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>-->
<!-- <aop:before method="printLog" pointcut="execution(* top.meethigher.demo14.service.impl.*.*(..))"></aop:before>-->
</aop:aspect>
</aop:config>
</beans>

四种常用通知类型

在上面代码的基础上,进行修改

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
// int i = 2 / 0;
System.out.println("保存账户!");
}

@Override
public void updateAccount(int i) {
System.out.println("更新账户!");
}

@Override
public int deleteAccount() {
System.out.println("删除账户!");
return 0;
}
}

Logger

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
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("Logger类中的--前置通知--开始记录日志!");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("Logger类中的--后置通知--开始记录日志!");
}

/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("Logger类中的--异常通知--开始记录日志!");
}

/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("Logger类中的--最终通知--开始记录日志!");
}
}

spring.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
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="top.meethigher.demo15.service.impl.AccountServiceImpl"/>
<!--配置Logger类-->
<bean id="logger" class="top.meethigher.demo15.utils.Logger"/>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logger" ref="logger">
<!--统一配置切入表达式 id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容-->
<aop:pointcut id="pc" expression="execution(* top.meethigher.demo15.service.impl.*.*(..))"/>
<!--配置前置通知-->
<aop:before method="beforePrintLog" pointcut-ref="pc"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc"/>
<!--配置异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc"/>
<!--配置最终通知-->
<aop:after method="afterPrintLog" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
</beans>

环绕通知

直接在原有spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="top.meethigher.demo15.service.impl.AccountServiceImpl"/>
<!--配置Logger类-->
<bean id="logger" class="top.meethigher.demo15.utils.Logger"/>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logger" ref="logger">
<!--统一配置切入表达式 id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容-->
<aop:pointcut id="pc" expression="execution(* top.meethigher.demo15.service.impl.*.*(..))"/>
<!--配置前置通知-->
<aop:before method="beforePrintLog" pointcut-ref="pc"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc"/>
<!--配置异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc"/>
<!--配置最终通知-->
<aop:after method="afterPrintLog" pointcut-ref="pc"/>
<!--配置环绕通知-->
<aop:around method="aroundPrintLog" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
</beans>

出现问题:当我们配置了环绕通知之后,切入点方法没有执行,通知方法执行。

分析:通过对比动态代理中环绕通知代码,发现动态代理中的环绕通知有明确的切入点方法调用。

解决:Spring框架为我们提供了一个接口ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。

Spring中环绕通知的作用:它是为我们的一种可以在代码中手动控制增强方法何时执行的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 环绕通知
*/
public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint){
Object rtValue=null;
try{
Object[] args=proceedingJoinPoint.getArgs();
/*日志写在这里算前置通知*/
rtValue= proceedingJoinPoint.proceed(args);
/*日志写在这里算后置通知*/
System.out.println("Logger类中的--环绕通知--开始记录日志!");
return rtValue;
}catch (Throwable t){
/*日志写在这里算异常通知*/
throw new RuntimeException(t);
}finally {
/*日志写在这里算最终通知*/
}
}

8.4 基于XML与注解的Spring AOP

注解

  • @Aspect:表示当前类是个切面类
  • @Pointcut:切面表达式
  • @Before:前置通知
  • @AfterReturning:后置通知
  • @AfterThrowing:异常通知
  • @After:最终通知
  • @Around:环绕通知

spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="top.meethigher.demo16"/>
<!--配置spring开启对aop注解的支持-->
<aop:aspectj-autoproxy/>
</beans>

AccountServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Override
public void saveAccount() {
int i = 2 / 0;
System.out.println("保存账户!");
}

@Override
public void updateAccount(int i) {
System.out.println("更新账户!");
}

@Override
public int deleteAccount() {
System.out.println("删除账户!");
return 0;
}
}

Logger

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
@Component("logger")
@Aspect
public class Logger {
@Pointcut("execution(* top.meethigher.demo16.service.impl.*.*(..))")
private void pt(){}
/**
* 前置通知
*/
@Before("pt()")
public void beforePrintLog(){
System.out.println("Logger类中的--前置通知--开始记录日志!");
}
/**
* 后置通知
*/
@AfterReturning("pt()")
public void afterReturningPrintLog(){
System.out.println("Logger类中的--后置通知--开始记录日志!");
}

/**
* 异常通知
*/
@AfterThrowing("pt()")
public void afterThrowingPrintLog(){
System.out.println("Logger类中的--异常通知--开始记录日志!");
}

/**
* 最终通知
*/
@After("pt()")
public void afterPrintLog(){
System.out.println("Logger类中的--最终通知--开始记录日志!");
}

/**
* 环绕通知
*/
@Around("pt()")
public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint){
Object rtValue=null;
try{
Object[] args=proceedingJoinPoint.getArgs();
//日志写在这里算前置通知
rtValue= proceedingJoinPoint.proceed(args);
//日志写在这里算后置通知
System.out.println("Logger类中的--环绕通知--开始记录日志!");
return rtValue;
}catch (Throwable t){
//日志写在这里算异常通知
throw new RuntimeException(t);
}finally {
//日志写在这里算最终通知
}

}
}

8.5 基于完全注解的Spring AOP

SpringConfiguration

1
2
3
4
5
@Configuration
@ComponentScan("top.meethigher.demo17")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

测试类

1
2
3
4
5
6
7
public class SpringAopTest17 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService as = (AccountService) ac.getBean("accountService");
as.saveAccount();
}
}

其他的保持跟8.4的内容一样

8.5 转账案例再次优化

SpringConfiguration.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
@Configuration
@ComponentScan("top.meethigher.demo18")
@EnableAspectJAutoProxy
public class SpringConfiguration {
@Bean("ds")
public DataSource createDataSource(){
ComboPooledDataSource ds = null;
try {
ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false");
ds.setUser("root");
ds.setPassword("root");
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
return ds;
}
@Bean("runner")
@Scope("prototype")
public QueryRunner createRunner(@Qualifier("ds") DataSource ds){
return new QueryRunner(ds);
}
}

AccountDaoImpl.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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@Component("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;

@Override
public List<Account> findAll() {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account", new BeanListHandler<Account>(Account.class));
} catch (SQLException e) {
//因为sqlexception是编译时期异常,必须处理。把他作为运行时异常,就可以不用处理了。
throw new RuntimeException();
}
}

@Override
public Account findById(Integer id) {
try {
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id=?", new BeanHandler<Account>(Account.class), id);
} catch (SQLException e) {
throw new RuntimeException();
}
}


@Override
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}

@Override
public void deleteAccount(Integer id) {
try {
runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?", id);
} catch (SQLException e) {
e.printStackTrace();
}
}


/**
* 重点:保存并返回id
* @param account
*/
@Override
public void saveAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
//形参是对象时,实际传的是地址。改变形参会影响实参。
account.setId(Integer.parseInt(runner.query("select last_insert_id() id",new MapHandler()).get("id").toString()));
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 重点:聚合查询
* @return
*/
@Override
public int findCount() {
try {
// 法一
// return Integer.parseInt(runner.query("select count(*) count from account", new MapHandler()).get("count").toString());
// 法二
return Integer.parseInt(runner.query(connectionUtils.getThreadConnection(),"select count(*) from account",new ScalarHandler<>(1)).toString());
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();

}
}

@Override
public Account findByName(String name) {
try {
List<Account> list = runner.query(connectionUtils.getThreadConnection(),"select * from account where name=?", new BeanListHandler<Account>(Account.class), name);
if(list==null||list.size()==0){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return list.get(0);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}

AccountServiceImpl.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
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;


@Override
public List<Account> findAll() {
return accountDao.findAll();
}

@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}

@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}

@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);

}

@Override
public void deleteAccount(Integer id) {
accountDao.deleteAccount(id);
}

@Override
public int findCount() {
return accountDao.findCount();

}

@Override
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户、转入账户
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
//2.转出账户减钱,转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3.更新转出账户、转入账户
accountDao.updateAccount(source);

//用来模拟问题
// int i = 2 / 0;

accountDao.updateAccount(target);
}
}

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
@Component
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<>();
@Autowired
private DataSource dataSource;

/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection(){
//1.先从ThreadLocal上获取
Connection conn = tl.get();
try {
//2.判断当前线程上是否有连接
if (conn == null||conn.isClosed()) {
//3.从数据源中获取一个连接,并且和线程绑定,存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}

/**
* 将线程与连接解绑
*/
public void removeConnection(){
tl.remove();
}
}

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
@Component("tsManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* top.meethigher.demo18.service.impl.*.*(..))")
private void pt(){}

/**
* 开启手动提交事务
*/
@Before("pt()")
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
System.out.println("开启手动提交事务");
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 提交事务
*/
@AfterReturning("pt()")
public void commitTransaction(){
try {
connectionUtils.getThreadConnection().commit();
System.out.println("提交事务");
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 回滚事务
*/
@AfterThrowing("pt()")
public void rollbackTransaction(){
try {
connectionUtils.getThreadConnection().rollback();
System.out.println("回滚事务");
} catch (SQLException e) {
e.printStackTrace();
}
}

/**
* 释放连接
*/
@After("pt()")
public void release(){
try {
connectionUtils.getThreadConnection().close();//归还连接
System.out.println("归还连接");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
发布:2021-03-07 00:11:47
修改:2021-04-22 01:50:18
链接:https://meethigher.top/blog/2021/spring2/
标签:spring 
付款码 打赏 分享
shift+ctrl+1可控制目录显示