Spring + 事务管理

1. 先用原始的JDBC实现转账操作

在这里插入图片描述

2. 给Service层加上事务管理

service层需要控制事务,开启、提交、回滚、

因为要保证使用同一个连接,所以要在service层创建连接传给dao层

package service.impl;

import dao.AccountDao;
import service.AccountService;
import util.JDBCUtil;

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

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDao();

    public void transfer(String from, String to, double money) throws SQLException {

        //连接从dao层提到service层
        Connection connection = JDBCUtil.getConnection();

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

            //业务处理
            accountDao.sub(connection,from, money);
            accountDao.add(connection,to, money);

            //都执行成功,则提交事务
            connection.commit();
        } catch (RuntimeException e) {
            //失败,回滚
            connection.rollback();
            throw new SQLException("转账失败");
        } finally {
            //释放资源
            connection.close();
        }
    }
}

3. 将Connetion通过线程上下文传给Dao层

package service.impl;

import dao.AccountDao;
import service.AccountService;
import util.JDBCUtil;

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

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDao();

    //使用ThreadLocal来将连接放入当前线程中。
    //由于ThreadLocal实现的约束,谁放的只能谁来拿,因此设为常量让dao能直接访问它。
    public static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL = new ThreadLocal<>();

    public void transfer(String from, String to, double money) throws SQLException {

        //连接从dao层提到service层
        Connection connection = JDBCUtil.getConnection();

        try {
            //关闭自动提交,开启事务.
            connection.setAutoCommit(false);
            
            //将连接放入当前线程中
            CONNECTION_THREAD_LOCAL.set(connection);

            //业务处理
            accountDao.sub(from, money);
            accountDao.add(to, money);

            //都执行成功,则提交事务
            connection.commit();
        } catch (RuntimeException e) {
            //失败,回滚
            connection.rollback();
            throw new SQLException("转账失败");
        } finally {
            //释放资源
            connection.close();
        }
    }
}

package dao;

import service.impl.AccountServiceImpl;

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

public class AccountDao {

    //增加
    public void add(String name, double money) throws SQLException {
        //从当前线程中取出
        Connection connection = AccountServiceImpl.CONNECTION_THREAD_LOCAL.get();
        //预编译sql
        PreparedStatement preparedStatement = connection.prepareStatement("update account set money = money + ? where name = ?");
        //给参数赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setObject(2, name);
        //执行sql
        preparedStatement.execute();
        //关闭资源
        preparedStatement.close();
    }

    //减少
    public void sub(String name, double money) throws SQLException {
        //从当前线程中取出
        Connection connection = AccountServiceImpl.CONNECTION_THREAD_LOCAL.get();
        //预编译sql
        PreparedStatement preparedStatement = connection.prepareStatement("update account set money = money - ? where name = ?");
        //给参数赋值
        preparedStatement.setObject(1, money);
        preparedStatement.setObject(2, name);
        //执行sql
        preparedStatement.execute();
        //关闭资源
        preparedStatement.close();
    }
}

将事务管理从service层提取出来,封装成工具类

package util;

import com.sun.org.apache.bcel.internal.generic.ACONST_NULL;

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

/**
 * @Date: 2022/4/30 22:09
 * @author: ZHX
 * @Description:
 */
public class TransactionManager {

    //使用ThreadLocal来将连接放入当前线程中。
    //由于ThreadLocal实现的约束,谁放的只能谁来拿,因此设为常量让dao能直接访问它。
    public static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL = new ThreadLocal<>();

    public static void start() throws SQLException {
        Connection connection = JDBCUtil.getConnection();
        connection.setAutoCommit(false);
        CONNECTION_THREAD_LOCAL.set(connection);
    }

    public static void commit() throws SQLException {
        CONNECTION_THREAD_LOCAL.get().commit();
    }

    public static void rollback() throws SQLException {
        CONNECTION_THREAD_LOCAL.get().rollback();
    }

    public static void close() throws SQLException {
        CONNECTION_THREAD_LOCAL.get().close();
        CONNECTION_THREAD_LOCAL.remove();
    }

}

package service.impl;

import dao.AccountDao;
import service.AccountService;
import util.TransactionManager;

import java.sql.SQLException;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDao();

    public void transfer(String from, String to, double money) throws SQLException {

        try {
            //开启事务
            TransactionManager.start();

            //业务处理
            accountDao.sub(from, money);
            accountDao.add(to, money);

            //提交事务
            TransactionManager.commit();
        } catch (RuntimeException e) {
            //回滚
            TransactionManager.rollback();
            e.printStackTrace();
        } finally {
            //释放资源
            TransactionManager.close();
        }
    }
}

4. 用动态代理实现普遍的事务管理

package controller;

import service.AccountService;
import service.impl.AccountServiceImpl;
import util.ServiceTransactionProxy;

import java.lang.reflect.Proxy;
import java.sql.SQLException;

public class AccountController {

    private AccountService accountService = (AccountService) Proxy.newProxyInstance(AccountController.class.getClassLoader(), new Class[]{AccountService.class}, new TransactionProxy(new AccountServiceImpl()));

    public void transfer() throws SQLException {

        //模拟前端传来的数据
        String from = "张三";
        String to = "赵丽颖";
        double money = 500.0;

        //调Service层进行业务处理。
        accountService.transfer(from, to, money);
    }
}

package util;

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

/**
 * @Date: 2022/4/30 22:26
 * @author: ZHX
 * @Description:
 */
public class TransactionProxy implements InvocationHandler {
    private Object origin;

    public ServiceTransactionProxy(Object origin) {
        this.origin = origin;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        try {
            //开启事务
            TransactionManager.start();

            //业务处理
            result = method.invoke(origin, args);

            //提交事务
            TransactionManager.commit();
        } catch (RuntimeException e) {
            //回滚
            TransactionManager.rollback();
            e.printStackTrace();
        } finally {
            //释放资源
            TransactionManager.close();
        }
        return result;
    }
}

package service.impl;

import dao.AccountDao;
import service.AccountService;

import java.sql.SQLException;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDao();

    public void transfer(String from, String to, double money) throws SQLException {
        //业务处理
        accountDao.sub(from, money);
        accountDao.add(to, money);
    }
}

5. 使用Spring的AOP实现事务管理

设置方法拦截器,环绕式

package aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class Transaction {

    @Autowired
    private TransactionManager transactionManager;

    @Around("execution(* service..*.*(..))")
    public Object around(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            transactionManager.start();
            result = pjp.proceed();
            transactionManager.commit();
        } catch (Throwable e) {
            transactionManager.rollback();
            e.printStackTrace();
        } finally {
            transactionManager.close();
        }
        return result;
    }

}

事务工具类

package aspect;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

@Component
public class TransactionManager {

    @Autowired
    private DataSource dataSource;

    public static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL = new ThreadLocal<>();

    public void start() throws SQLException {

        Connection connection = dataSource.getConnection();

        connection.setAutoCommit(false);

        CONNECTION_THREAD_LOCAL.set(connection);
    }

    public void commit() throws SQLException {
        CONNECTION_THREAD_LOCAL.get().commit();
    }

    public void rollback() {
        try {
            CONNECTION_THREAD_LOCAL.get().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void close() {
        try {
            CONNECTION_THREAD_LOCAL.get().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        CONNECTION_THREAD_LOCAL.remove();
    }
}

注解配置Spring

package config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sun.media.jfxmediaimpl.platform.PlatformManager;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan({"controller", "service", "dao","aspect"})
@EnableAspectJAutoProxy  //开启AspectJ代理检测
public class SpringConfig {

    @Bean("dataSource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/xin?useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        return druidDataSource;
    }
}

6. Spring提供的事务管理,不用自己封装了。

导入依赖坐标

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

Spring提供的事务管理

package config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sun.media.jfxmediaimpl.platform.PlatformManager;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan({"controller", "service", "dao"})
@EnableAspectJAutoProxy
@EnableTransactionManagement //开启事务管理
public class SpringConfig {

    //使用Spring提供的事务管理器.
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

    @Bean("dataSource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/xin?useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        return druidDataSource;
    }
}

Service

package service.impl;

import dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import service.AccountService;

@Service
@Transactional//给该类所有方法都加事务管理
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    @Transactional//给该方法加事务管理
    public void transfer(String from, String to, double money) {
        //业务处理
        accountDao.add(from, money);
        accountDao.sub(to, money);
    }
}

7. 将Mybatis整合进来

引入依赖坐标

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>

Mybatis整合

package config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

public class MybatisConfig {

    //Mybatis提供的整合到Spring的工具
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        //数据库连接池
        sqlSessionFactoryBean.setDataSource(dataSource);

        //设置别名
        //sqlSessionFactoryBean.setTypeAliasesPackage("pojo");

        //根据通配符  找到所有的mapper.xml文件
        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        try {
            Resource[] resources = patternResolver.getResources("mappers/*.xml");
            sqlSessionFactoryBean.setMapperLocations(resources);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("dao");
        return mapperScannerConfigurer;
    }
}

mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="dao.AccountDao">

    <update id="add">
        update account
        set money= money + #{money}
        where name = #{name}
    </update>

    <update id="sub">
        update account
        set money= money - #{money}
        where name = #{name}
    </update>
</mapper>

Spring注解配置文件导入Mybatis配置文件

@Import(MybatisConfig.class)//整合Mybatis

8. 参考资料

黑马视频

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值