深入理解spring事务底层实现原理

事务

相信大家都在ATM机取过钱,但是是否有人考虑过它的流程是怎样的呢?

我们都知道,假如我们取300块钱,那么当这三百块钱从ATM机出来时,我们的账户相应的会减少300。这两个过程一定是要同时成功才算成功的。否则就会出现账户少了300.但是钱没出来,对于我们来说,我们损失了300.而如果钱出来了但是账户钱没扣掉的话,银行就会损失300.这两种情况都是不可取的。所以就需要保证要么大家一起成功,有一个失败即表示这个过程失败了,就需要还原现场(钱没出来,就需要把账户扣掉的钱给补上)。这,就是事务。

事务都具有以下四个基本特点:

事务的4个属性

Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败(减款,增款必须一起完成)。

● Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。

● Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。

● Durability(持久性):事务完成之后,它对于 系统的影响是永久的,该修改即使出现系统故障也将一直保留,真实的修改了数据库

上面说的是现实生活中的例子,那么在JAVA编程中如何保证对数据库的操作是在一个事务下呢?

首先,要让插入或更新操作在同一事务中,那么就需要保证每次只需数据库操作时使用的必须为同一个连接。有人可能会猜到了,用单例,是的,就是单例模式。

spring的事务的确是很强大,且做好了很多封装。如打开连接,关闭连接这种操作我们都不用做了。但是俗话说万变不离其宗,再牛的框架也是要走底层的。原理弄懂了剩下的还难么?

今天我们就来手写一个Spring的底层事务管理。

先看看工程结构。其实也就三个类搞定。ConnectionHandler.java,SingleConnectHandler.java,TransactionManager.java。我待会儿会根据代码一个个讲解一下。

在这里插入图片描述

首先是连接ConnectionHandler.java

package spring;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

public class ConnectionHandler {
	private Map<DataSource, Connection> map = new HashMap<>();//将数据库连接保存在map中, 以DataSource为键
	
	public Connection getConnectionByDatabase(DataSource dataSource) throws SQLException{
		Connection conn = map.get(dataSource);
		if(conn == null) {
			conn = dataSource.getConnection();
			map.put(dataSource, conn);
		}
		return conn;
	}
	public void openConnection(DataSource dataSource) throws SQLException {
		Connection conn = map.get(dataSource);
		if(conn.isClosed()) {
			conn = dataSource.getConnection();
			map.put(dataSource, conn);
		}
	}
}

我们都知道,springMVC是可以配置多个数据源的,所以这里我们直接以数据源作为map的键。连接作为值保存起来,每次获取连接时,如果map中不存在连接,则重新获取一个连接。这样就保证了在程序运行周期内,同一个数据源获取到的连接都为同一个。也就完成了我们最重要的第一步,每次进行数据库操作时,使用的都是同一个连接。
如果是在单线程模式下,其实这个类已经足够了的。spring可没那么简单,它是一个多线程的。也就是说在并发的情况下,map是线程不安全的。所以这里我们还得再封装一下。

SingleConnectHandler.java

package spring;

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

import javax.sql.DataSource;

public class SingleConnectHandler {
	private static ThreadLocal<ConnectionHandler> localThread = new ThreadLocal<>();
	
	private static ConnectionHandler getConnectionHahdler() {
		ConnectionHandler ch = localThread.get();
		if(ch == null) {
			ch = new ConnectionHandler();
			localThread.set(ch);
		}
		return ch;
	}
	
	public static Connection getConnection(DataSource dataSource) throws SQLException {
		return getConnectionHahdler().getConnectionByDatabase(dataSource);
	}
	
	public static void openConnection(DataSource dataSource) throws SQLException {
		getConnectionHahdler().openConnection(dataSource);
	}
}

这个类我们需要一个ThreadLocal类来帮我们保证在多线程下。每个线程能拿到自己变量副本ConnectionHandler。也就是说并发下其实每个线程其实获取到的连接都是不一样的,ThreadLocal这个类。是jdk1.2就有的,我就不多介绍了,有兴趣的朋友可以参考一下这篇文章,我觉得说的蛮好的:
http://www.cnblogs.com/dolphin0520/p/3920407.html

这样我们的项目就可以支持多线程下操作了,完了之后是TransactionManager.java这个类。spring中这个类的功能可强大了。不过我们这个项目毕竟是阉割版的,我就只写了几个常规的方法。如开启事务,关闭事务,提交事务与回滚事务。

package spring;

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

import javax.sql.DataSource;

public class TransactionManager {
	private static DataSource dataSource;
	private Connection getConnection() throws SQLException {
		return SingleConnectHandler.getConnection(dataSource);
	}
	public TransactionManager(DataSource dataSource) {
		TransactionManager.dataSource = dataSource;
	}
	public void start() throws SQLException {
		Connection conn = getConnection();
		conn.setAutoCommit(false);
	}
	public void close() throws SQLException {
		Connection conn = getConnection();
		conn.close();
	}
	public void commit() throws SQLException {
		Connection conn = getConnection();
		conn.commit();
	}
	public void rollBack() throws SQLException {
		Connection conn = getConnection();
		conn.rollback();
	}
	public boolean isAutoCommit() throws SQLException {
		return getConnection().isClosed();
	}
	public void openConnection() throws SQLException{
		SingleConnectHandler.openConnection(dataSource);
	}
}

这个类完成后到我们的业务层了。userService.java和userDao.java。
userService

package spring;

import java.sql.SQLException;

public interface UserService {
	public void buy() throws SQLException;
	public void addShops() throws SQLException;
}

userDao

package spring;

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

import javax.sql.DataSource;

public class UserDao implements UserService{
	private String sql = "insert into user(account,password) values(?,?)";
	private DataSource dataSource;
	public UserDao(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Override
	public void buy() throws SQLException {
		Connection conn = SingleConnectHandler.getConnection(dataSource);
		PreparedStatement ps = conn.prepareStatement(sql);
		ps.setString(1, Thread.currentThread().getName()+"buy");
		ps.setString(2, new Date().toString());
		ps.execute();
		System.out.println("方法buy,---当前线程:"+Thread.currentThread()+"-------使用的Connection:"+conn.hashCode());
	}

	@Override
	public void addShops() throws SQLException {
		Connection conn = SingleConnectHandler.getConnection(dataSource);
		PreparedStatement ps = conn.prepareStatement(sql);
		ps.setString(1, Thread.currentThread().getName()+"addShops");
		ps.setString(2, new Date().toString());
		ps.execute();
		System.out.println("方法addShops,当前线程:"+Thread.currentThread()+"----使用的Connection:"+conn.hashCode());
	}
}

业务层我模拟了用户添加商品与购买商品操作。我们将这两个数据库操作放在同一个事务中。注意我后面打印的当前线程与连接的hash值。我们都知道,在程序运行周期内,每个对象都有自己唯一的hash。如果两个hash值相等的话,那么这两个对象则相等。
哦,对了,差点忘了我们还有一个自定义的MyDatasource.java

package spring;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

import javax.sql.DataSource;

public class MyDatasource implements DataSource{
	public static final String driverClassName = "com.mysql.jdbc.Driver";
	public static final String password = "root";
	public static final String username = "root";
	public static final String url = "jdbc:mysql://localhost:3306/qq";

	@Override
	public PrintWriter getLogWriter() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public int getLoginTimeout() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public Connection getConnection() throws SQLException {
		Connection conn = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection(url, username, password);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return conn;
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

}

这个类实现了DataSource接口,里面的方法我们就直接重写一个getConnection就好了。这里面是JDBC获取数据库连接的,都是格式固定的,没什么好说的。

完成后进入测试:

package spring;

import java.sql.SQLException;

public class Test {
	public static void main(String[] args) throws SQLException, InterruptedException {
		MyDatasource b = new MyDatasource();
		for(int i=0;i<3;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					UserService u = new UserDao(b);
					TransactionManager t = new TransactionManager(b);
					try {
						t.start();
						u.buy();
						u.addShops();
						t.commit();
						t.close();
					} catch (Exception e) {
						try {
							t.rollBack();
						} catch (SQLException e1) {
							e1.printStackTrace();
						}
						e.printStackTrace();
					}
				}
			}).start();
		}
	}
}

这个类我新建了三个线程。表示三个用户同时执行添加商品与购买操作。完成后直接run。看结果

在这里插入图片描述

在这里插入图片描述

总结:
可以看到每个线程的connection都不一样,但是单个线程下使用的connection,包括TransactionManager中的connection都是一致的。这就是一个小型的spring事务管理,其实你翻开spring的源码,基本和这个差不多的。只不过是spring中进行了更多的封装。但是其实有很多时我们不需要的。只有了解了原理,当项目出现问题或者性能瓶颈时,才能更好的发现问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值