【Spring】09 后续的学习补充 vol3

原生JDBC事务:

package dao;

import cn.dzz.util.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;

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

/**
 * @author DaiZhiZhou
 * @file OA-Project
 * @create 2020-08-03 15:13
 */
public class TransactionTest {
    
    QueryRunner queryRunner = new QueryRunner();
    DataSource dataSource = DruidUtil.getDataSource();
    
    @Test
    public void tx01() throws SQLException {

        Connection connection = null;
        try {
            // 事务的前提是我们使用的连接需要保持一致
            connection = dataSource.getConnection();

            // 开启事务,关闭MYSQL的自动提交
            connection.setAutoCommit(false);

            int update01 = queryRunner.update(connection, "这是执行影响数据库的SQL1");

            // 中间的执行过程将会产生异常
            
            int update02 = queryRunner.update(connection, "这是执行影响数据库的SQL2");
            
            // 如果一切正常,则进行提交
            connection.commit();
            
        } catch (Exception exception) {
            exception.printStackTrace();
            // 发生了异常,触发Catch,执行回滚
            connection.rollback();
        } finally {
            // 无论回滚还是提交,最终释放资源
            connection.close();
        }
    }
    
}

事务封装类:

即数据源提供给这个封装类,当我们Dao层执行SQL的连接对象就要从这里获取,

不能独立获取,否则事务将无法作用了,因为获取的不是同一个连接对象就没有办法处理了

package cn.dzz.util;

/**
 * @author DaiZhiZhou
 * @file OA-Project
 * @create 2020-08-03 15:27
 */

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

public class TransactionManager {

    // 私有化
    private TransactionManager(){}

    // 当前线程对象,用以存放和绑定连接对象
    private static final ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    // 我们需要一个数据源来获取连接,且事务正常执行的条件是来自于同一个连接对象才会有效
    private static DataSource dataSource ;

    /**
     * 我不希望只能从一个数据源获取,这个对象可以是任意类型的
     * @param dataSource
     */
    public static void setDataSource(DataSource dataSource) {
        TransactionManager.dataSource = dataSource;
    }

    /**
     * 首先是获取连接对象获取,如果当前线程实例中没有连接对象,则从这个数据源中获取
     * @return
     */
    public static Connection getCurrentThreadConnection(){
        Connection connection = null;
        try {
            connection = threadLocal.get();
            if (connection == null) {
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
        return connection;
    }

    /**
     *  开启事务
     */
    public static void begin(){
        Connection connection = getCurrentThreadConnection();
        try {
            connection.setAutoCommit(false);
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }

    /**
     * 如果业务逻辑的每一步代码都正常执行,无任何异常,则开始提交
     */
    public static void commit(){
        Connection connection = getCurrentThreadConnection();
        try {
            connection.commit();
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }

    /**
     * 如果业务逻辑发生了错误,立刻捕获,进入catch,执行回滚
     */
    public static void rollback(){
        Connection connection = getCurrentThreadConnection();
        try {
            connection.rollback();
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }

    /**
     * 最后,不管是失败还是成功,都需要释放资源
     */
    public static void close(){
        Connection connection = getCurrentThreadConnection();
        try {
            connection.close();
            threadLocal.remove();
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }

}

业务层的应用:

如果要在业务添加就必须要为每一个业务的方法这样添加

    public JsonResult altUserById2(HttpServletRequest request) {
        
        try {
            // 开启事务
            TransactionManager.begin();
            
            int i = userDao.updateUserById(new User(
                    Integer.valueOf(request.getParameter("user_id")),
                    request.getParameter("user_name"),
                    request.getParameter("user_password"),
                    Integer.valueOf(request.getParameter("user_status")),
                    Integer.valueOf(request.getParameter("user_is_del"))
            ));
            
            // 提交
            TransactionManager.commit();
            return new JsonResult(100, "修改成功");
        } catch (Exception exception) {
            // 回滚
            TransactionManager.rollback();
            exception.printStackTrace();
        } finally {
            // 释放
            TransactionManager.close();
        }
        return new JsonResult(200, "修改失败");
    }

但是业务方法那么多,怎么可能一个个添加,

何况事务并不是业务的范畴之内,但是这出现了异常就得撤销SQL执行恢复原状啊

演化到动态代理完成:

package cn.dzz.util;

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

/**
 * @author DaiZhiZhou
 * @file OA-Project
 * @create 2020-08-03 15:49
 */
public class ServiceTxProxy <GenericService>{

    /**
     * 目标对象,也就是业务对象
     * 我们希望不仅仅是一个业务逻辑接口,应该整个业务逻辑层的接口都可以被代理
     */
    private GenericService genericService;

    /**
     * 我创建这个代理对象可以不先设置目标对象
     * 对实例重载
     */
    public ServiceTxProxy() {}

    /**
     * 创建实例时就注入目标对象
     * @param genericService
     */
    public ServiceTxProxy(GenericService genericService) {
        this.genericService = genericService;
    }

    /**
     * 调用注入方法来注入目标对象
     * @param genericService
     */
    public void setGenericService(GenericService genericService) {
        this.genericService = genericService;
    }
    
    /**
     * 得到代理对象,我们将由代理对象来操作业务
     * 在得到代理对象之前注入数据源,我们不希望只能使用单一的数据源
     * 业务层的调用,一定是在代理之后执行,所以在这里注入数据源不会造成空指针的问题
     * @param 
     * @return
     */
    public GenericService getGenericService() {
        // 调用代理类的创建代理实例方法
        return (GenericService) Proxy.newProxyInstance(
                // 第一参数:类加载器实例,可以反射任意实例获取
                genericService.getClass().getClassLoader(),
                // 第二参数:目标对象的接口数组,因为接口是允许多实现的
                genericService.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 {

                        // 目标对象就是我们的Service对象,在对象调用之前,开启事务
                        TransactionManager.begin();

                        Object theGenericServiceData = null;

                        try {
                            // 目标对象的方法执行!!!
                            theGenericServiceData = method.invoke(genericService, args);

                            // 对执行完毕的Service对象 进行提交或者回滚
                            TransactionManager.commit();
                        } catch (Exception exception) {
                            exception.printStackTrace();
                            TransactionManager.rollback();
                        }
                        return theGenericServiceData;
                    }
                }
        );
    }
}

注意事务管理器的数据源设置问题。。。

 

Spring事务管理:

依赖的组件坐标:

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.8.RELEASE</version>
</dependency>

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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>

配置Spring提供的事务管理器:

需要数据源注入

<!--事务-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

配置事务的通知,引用事务管理器:

<tx:advice id="txAdvice" transaction-manager="txManager">
       <!--在 tx:advice 标签内部 配置事务的属性 -->
        <tx:attributes>
          <!-- 指定方法名称:是业务核心方法
            read-only:是否是只读事务。默认 false,不只读。
            isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
            propagation:指定事务的传播行为。
            timeout:指定超时时间。默认值为:-1。永不超时。
            rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
            没有默认值,任何异常都回滚。
            no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
            滚。没有默认值,任何异常都回滚。
          -->
          <tx:method name="*" read-only="false" propagation="REQUIRED"/>
          <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

结合动态代理:

<!-- 配置 aop -->
<aop:config>
  <!-- 配置切入点表达式 -->
  <aop:pointcut expression="execution(* com.tc51.service.impl.*.*(..))" id="pt1"/>
</aop:config>

配置切入点表达式和事务通知的对应关系:

<!-- 配置 aop -->
    <aop:config>
    <!-- 配置切入点表达式 -->
    <aop:pointcut expression="execution(* com.tc51.service.impl.*.*(..))" id="pt1"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1" />
</aop:config>

Dao层的设计:

查询功能不需要这个事务,注入好的数据源给的连接对象是独立的

但是如果是更新操作,就需要从事务管理器中获取连接保证连接一致

@Component
public class AccountDao implements IAccountDao {

    @Autowired
    private QueryRunner queryRunner;

    public Account getAccount(String name) throws SQLException {
        String sql = "SELECT * FROM `account` WHERE  `name`=?";

        return queryRunner.query(sql, new BeanHandler<Account>(Account.class),name);
    }

    public int updateAccount(Account account) throws SQLException {
        String sql = "update account set name=?,money=? where id=?";
        Connection conn= DataSourceUtils.getConnection(queryRunner.getDataSource());
        System.out.println(conn);
        Object[] object = {account.getName(), account.getMoney(), account.getId()};
        return queryRunner.update(conn,sql, object);
    }
}

 

posted @ 2020-09-28 00:16  emdzz  阅读(121)  评论(0编辑  收藏  举报