自定义事务管理器TransactionManager对象
以aop思想,实现事务管理切面
1. DataSource注册容器
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启包扫描注解-->
<context:component-scan base-package="com.hang"/>
<!--导入jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置druid数据库连接池-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
</bean>
<!--开启aop注解-->
<aop:aspectj-autoproxy/>
</beans>
2. 定义事务管理器MyTransactionManager对象
定义事务管理器MyTransactionManager对象,注册进spring容器。(依赖Druid连接池,获取连接,开始事务,提交事务,回滚事务,关闭连接)
@Component
public class MyTransactionManager {
@Autowired
private DataSource dataSource;
// 获取连接
public Connection getConnection(){
Connection conn = null;
try {
conn = dataSource.getConnection();
ConnUtil.setConn(conn);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
// 开始事务
public void openTransaction(){
try {
ConnUtil.getConn().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
// 提交事务
public void openCommit(){
try {
ConnUtil.getConn().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 回滚事务
public void openRollback(){
try {
ConnUtil.getConn().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭连接
public void closeConnection(){
Connection conn = ConnUtil.getConn();
try {
if (conn!=null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
3. 定义dao接口
定义dao接口,使用jdbc定义dao实现,不要使用mybatis,把dao实现注册spring容器(curd)
public interface SysUserDao {
SysUser queryById(Integer id) throws Exception;
Integer insert(SysUser sysUser) throws Exception;
int update(SysUser sysUser);
int deleteById(Long id);
}
@Repository
public class SysUserDaoImpl implements SysUserDao {
private Connection conn = null;
private ResultSet rs = null;
private PreparedStatement ps = null;
@Override
public SysUser queryById(Integer id) throws Exception {
SysUser sysUser = null;
conn = ConnUtil.getConn();
ps = conn.prepareStatement("select * from db_stage_three.sys_user where id = ?");
ps.setInt(1, id);
rs = ps.executeQuery();
if (rs.next()) {
sysUser = new SysUser();
sysUser.setUname("hello");
}
return sysUser;
}
@Override
public Integer insert(SysUser sysUser) throws Exception {
Integer result = null;
conn = ConnUtil.getConn();
ps = conn.prepareStatement("insert into db_stage_three.sys_user ( uname, uphone, upwd, uwechat, uemail, role_id, dept_id) values (?,?,?,?,?,?,?)");
ps.setString(1, sysUser.getUname());
ps.setString(2, sysUser.getUphone());
ps.setString(3, sysUser.getUpwd());
ps.setString(4, sysUser.getUwechat());
ps.setString(5, sysUser.getUemail());
ps.setInt(6, sysUser.getRoleId());
ps.setInt(7, sysUser.getDeptId());
result = ps.executeUpdate();
return result;
}
@Override
public int update(SysUser sysUser) {
return 0;
}
@Override
public int deleteById(Long id) {
return 0;
}
}
4. 定义service接口
定义service接口,定义service实现,注册容器(curd)。
public interface SysUserService {
SysUser queryById(Integer id);
SysUser insert(SysUser sysUser);
}
@Service()
public class SysUserServiceImpl implements SysUserService {
@Resource
private SysUserDao sysUserDao;
@Override
public SysUser queryById(Integer id) {
Connection conn = null;
// 织入获取连接,开始事务,提交事务,回滚事务,关闭连接
SysUser sysUser = null;
try {
sysUser = this.sysUserDao.queryById(id);
} catch (Exception e) {
e.printStackTrace();
}
return sysUser;
}
@Transaction // 使用基于自定义注解方式定义切入点表达式
@Override
public SysUser insert(SysUser sysUser) {
try {
this.sysUserDao.insert(sysUser);
} catch (Exception e) {
e.printStackTrace();
}
int i = 1/0;
return sysUser;
}
}
自定义注解代码如下:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {
}
5. 定义事务切面(依赖MyTransactionManag)
定义事务切面(依赖MyTransactionManag),定义环绕通知,织入到service层的数据库操作方法,执行方法之前打开事务,异常回滚,方法返回提交事务,最终关闭连接。定义 ServiceTransAspect 类作为切面
@Component
@Aspect
public class ServiceTransAspect {
@Autowired
private MyTransactionManager transactionManager;
// 基于 AspectJ 的切入点表达式
@Pointcut("execution(* com.hang.service.impl.SysUserServiceImpl.*(..))")
public void servicePointCut() {
}
// 后置通知关闭数据库连接对象
@After("servicePointCut()")
public void closeConnection(){
Connection conn = ConnUtil.getConn();
if (conn!=null){
try {
conn.close();
ConnUtil.clear();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 基于自定义注解形式
@Pointcut("@annotation(com.hang.aspect.Transaction)")
public void annotationPointCut() {
}
// 环绕通知 实现事务的开启、回滚、提交和连接对象的关闭
@Around(value = "annotationPointCut()")
public Object serviceManage(ProceedingJoinPoint pjp) {
Object result = null;
try {
transactionManager.getConnection();
transactionManager.openTransaction();
result = pjp.proceed();
transactionManager.openCommit();
} catch (Throwable throwable) {
transactionManager.openRollback();
throwable.printStackTrace();
} finally {
transactionManager.closeConnection();
}
return result;
}
}
6. 自定义工具类
在并发环境下,如果没使用ThreadLocal类维护线程内部的局部变量Connection(单Connection对象),两个线程共同访问一个公共变量。例如A用户获取数据库连接对象,并将Connection对象占为己有,对数据进行操作时,B用户也获取到数据库连接对象,获取Connection对象,此时A不具有Connection对象,B用户完成事务提交关闭连接,此时Connection对象为null,A用户数据则会提交失败。
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。
对ThreadLocal作用的总结:
- 线程并发: 在多线程并发的场景下
- 传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
- 线程隔离: 每个线程的变量都是独立的,不会互相影响
@Component
public class ConnUtil {
private static ThreadLocal<Connection> threadLocalConn = new ThreadLocal<>();
@Resource
private MyTransactionManager tm;
private static MyTransactionManager manager;
public static void clear() {
threadLocalConn.remove();
}
@PostConstruct
public void setTransactionManager(){
manager = tm;
}
public static Connection getConn() {
Connection connection = threadLocalConn.get();
if (connection!=null){
return connection;
}else {
return manager.getConnection();
}
}
public static void setConn(Connection conn) {
threadLocalConn.set(conn);
}
}