之前说到AccountDao需要继承JdbcDaoSupport这个类,那么现在来看一下这个类的内容
JdbcDaoSupport.java
package com.itheima.dao.impl;
/**
* 此类用于抽取dao中的重复代码
*/
public class JdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setDataSource(DataSource dataSource) {
if(jdbcTemplate == null){
jdbcTemplate = createJdbcTemplate(dataSource);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
看一下bean.xml的配置
那么这个bean.xml还可以这样配置,既然有JbdcTemplate对象还有DataSource对象,那么就也可以注入DataSource去创建一个JdbcTemplate对象
为什么说要这样配置呢,就是因为在我们以后的开发中,不止有一个Dao文件,可能会有很多的Dao文件,那么这些文件,最后都要去操作数据库,也就是说,这些文件都必须有一个JdbcTemplate对象和数据源,注意这里的数据源是封装到JdbcTemplate模板上面的
实际业务操作
但是你能想到的事儿,Spring也能做到,那么Spring怎么来做的
换句话说,Spring内部也是提供了那么一个JdbcSupport来供我们使用,这样也就是Spring给我们提供的一个jar包,内部封装好了JdbcTemplate和DataSource对象,我们就可以通过Bean.xml来进行配置,但是注解去配置不行,因为Spring给我们提供的jar是只读的
Spring中的事务控制
1.之前做了一个银行转账事务,现在我们通过xml来配置一下这个事务的实现过程
现在的要求就是,我们不要下面这个文件,也就是说我们不需要这个代理工厂去给我们生产一个代理对象了
BeanFactory.java
package com.pxx.factory;
import com.pxx.service.IAccountService;
import com.pxx.utils.TransactionManager;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 把service里面的方法全部进行增强
*/
public IAccountService getInstance() {
return (IAccountService)Enhancer.create(accountService.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//这里面就是具体增强代码
try {
System.out.println("开始增强了");
Object value = null;
//主要是我们需要在执行一个方法前后添加事务控制
txManager.beginTransaction();//开启事务
value = method.invoke(accountService,args);//有啥参数就拿过来匹配
//提交事务
txManager.commit();
System.out.println("增强结束");
return value;
} catch (Exception e) {
//有异常出现,把数据进行回滚
txManager.rollback();;
System.out.println("出现饿了异常");
throw new RuntimeException(e);
} finally {
//释放连接
txManager.release();
}
}
});
}
}
那么这个文件删除之后
在bean.xml文件中,也会报错
现在要求就是通过配置Spring,调用自己的Servcie来完成代码的编写工作
既然我们要通过AOP来进行一个事务控制,就必修在xml头部引入AOP的约束
配好了之后,检查一下pom.xml是否有如下这个依赖
这个依赖没有,是不能去解析切入点表达式的
正常测试没问题,钱不会少
下面把上面的过程全部改成注解的AOP实现以及问题分析
既然要用到注解配置,那么bean.xml约束就要从新去修改一下,要引入context部分的内容
然后我们去修改service层
下面我们去配置一下Dao层的注解
注意QueryRunner这个类里面又有一个数据源的依赖,但是这个数据源又是依赖于配置文件的所以下面的不能删除
下面去配置一下ConnectionUtils这个类
下面我们去配置一下事务管理器
package com.pxx.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("txManger")
@Aspect
public class TransactionManager {
//引入一个连接对象
@Autowired
private ConnectionUtils connectionUtils;
/* //我们要采用set注入,必须添加一个set方法
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}*/
//配置一个切入点
@Pointcut("execution(* com.pxx.service.impl.*.*(..))")
private void pt1(){}
//开启事务
@Before("pt1()")
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
@AfterReturning("pt1()")
public void commit(){
try {
System.out.println("事务提交");
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt1()")
public void rollback(){
try {
System.out.println("事务回滚");
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
@After("pt1()")
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
配置bean.xml对事务的支持
测试一下代码
首先看一下正常转账能不能行
按照上面单独这种配置,会出现这种报错
这里涉及到一个问题,Spring的通知执行流程就是:
那如果我们要解决这个问题,我们就去配置一个环绕通知,让每一个通知明确知道在何时何地的为我们服务
完整代码
TransactionManager.java
package com.pxx.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("txManger")
@Aspect
public class TransactionManager {
//引入一个连接对象
@Autowired
private ConnectionUtils connectionUtils;
/* //我们要采用set注入,必须添加一个set方法
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}*/
//配置一个切入点
@Pointcut("execution(* com.pxx.service.impl.*.*(..))")
private void pt1(){}
//开启事务
// @Before("pt1()")
public void beginTransaction() {
try {
System.out.println("事务开启");
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
// @AfterReturning("pt1()")
public void commit(){
try {
System.out.println("事务提交");
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
// @AfterThrowing("pt1()")
public void rollback(){
try {
System.out.println("事务回滚");
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
// @After("pt1()")
public void release(){
try {
System.out.println("事务关闭");
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
//1.获取参数
Object[] args = pjp.getArgs();
//2.开启事务
this.beginTransaction();
//3.执行方法
rtValue = pjp.proceed(args);
//4.提交事务
this.commit();
//返回结果
return rtValue;
}catch (Throwable e){
//5.回滚事务
this.rollback();
throw new RuntimeException(e);
}finally {
//6.释放资源
this.release();
}
}
}
这样就是正常执行业务流程
Spring中事务控制的一组API
基于 XML 的声明式事务控制(配置方式)
先来看一下AccountDaoImpl
看service层
这个里面还没有配任何事务,下面去看一下测试方法
测试一下这个方法
下面来说一下Spring基于xml的声明式事务控制
spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
事务属性的一些配置信息
3、配置AOP中的通用切入点表达式
这个也就是说把这个事务引入到哪一个包里面的方法中,aop就相当于把一个事务类插进去,然后利用动态代理进行增强
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
下面上一下全部的bean.xml配置
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置业务层-->
<bean id="accountService" class="com.pxx.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.pxx.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="5201314"></property>
</bean>
<!--
配置事务管理器
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--
这个里面就需要配置一个数据源
-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.pxx.service.impl.*.*(..))"/>
<!--建立切入点与事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
下面我们来测试一下正常业务转账
出现异常能不能被事务控制
异常有了
数据也没有转成功
Spring基于注解的声明式事务控制
1.一般要配置注解,就要进入context的xml约束
我们直接在原xml上修改
现在就可以注解了,先来配置service
下面就可以不要了
所以上面这个AccountDaoImpl不能去继承JdbcDaoSupport
想要配置成功,还要配置创建容器时需要扫描的包
下面就要开始配置事务了,下面说一下Spring基于注解的声明式事务控制步骤
第一步:
第二步:
引入事务管理器
第三步:
这个属性配置好像确实不如注解方便
这个时候去测试一下
异常报了,去看一下数据
控制住了。
Spring基于纯注解的声明式事务控制
上面还是用到部分xml配置
因为没有xml配置文件,所以我们要写一个配置类来代替xml文件
然后上一个数据库连接的配置文件
下面看一下JdbcConifg.java
package config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
/**
* 和连接数据库相关的配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
在注解上大部分相关配置就配好了
下面我们就要去配置事务注解的支持
下面去配置一下事务管理器
下面又要去创建一个配置类
然后重新改一下配置文件