文章目录
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