文章目录
mybatis连接池和事务管理
最近在复习框架的基础知识,利用国庆假期,想复习下mybatis,jpa,hibernate等orm框架,如果觉得写得还不错,有帮助到你,可以点波关注,我会继续更,一起努力!
一,连接池
为什么要使用连接池?
创建一个java.sql.Connection对象的代价是如此巨大,是因为创建一个Connection对象的过程,在底层就相当于和数据库建立的通信连接,在建立通信连接的过程,消耗了这么多的时间,而往往我们建立连接后(即创建Connection对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!
所以对于需要频繁地跟数据库交互的应用程序,可以在创建了Connection对象,并操作完数据库后,可以不释放掉资源,而是将它放到内存中,当下次需要操作数据库时,可以直接从内存中取出Connection对象,不需要再创建了,这样就极大地节省了创建Connection对象的资源消耗
手写一个简单的数据库连接池:
public class ConnectionPool {
private static String DRIVER = "com.mysql.jdbc.Driver";
private static String URL="";
private static String USERNAME="";
private static String PASSWORD="";
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private LinkedList<Connection> poollist = new LinkedList<Connection>();
//向连接池中放入10个连接对象,封装到一个集合中
public ConnectionPool() throws Exception {
for(int i=0;i<10;i++) {
poollist.addLast(this.creatConnection());
}
}
//创造连接对象
public Connection creatConnection() throws Exception {
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
}
//从连接池中取出一个连接对象
public Connection getConnection() {
return poollist.removeFirst();
}
//关闭连接,释放资源,将连接对象放回连接池中
public void close(Connection conn) {
poollist.addLast(conn);
}
}
mybatis的数据源分为:
- 不使用连接池的UnpooledDataSource
- 使用连接池的PooledDataSource
- 使用JNDI实现的数据源的JndiDataSource
1.不使用连接池的UnpooledDataSource
将数据库驱动,用户名密码等属性封装到properties中,底层还是使用熟悉的jdbc获取数据库连接
/**
* 底层还是熟悉的jdbc的数据库连接
* @param properties
* @return
* @throws SQLException
*/
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
//属性的前缀是以“driver.”开 头的,它 是 通 过 DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
2.使用连接池的PooledDataSource
mybatis中PooledDataSource连接池的设计
PooledDataSource将connection包装成PooledConnection对象放到PoolState连接容器中,这个连接容器中有两种连接
protected PooledDataSource dataSource;
//空闲的连接
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
//活动的连接
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
PooledDataSource获取连接会优先从空闲连接list中取,当前正在被使用的连接会放入活动连接的list中
获取连接的过程:
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();
}
接着从连接池中获取
popConnection()主要获取连接的逻辑:
- while循环不断尝试获取
-
先判断空闲连接池中是否还有空闲连接
- 有空闲连接:返回连接池第一个连接
- 没有空闲连接:查看活动连接池信息
-
判断活动连接池中是否达到了最大连接数
- 没有达到:直接new 一个新连接
- 达到:不能再new新连接,获取连接池中最老的了、第一个连接,删除该连接后new 一个新连接
//最外面是while死循环,如果一直拿不到connection,则不断尝试
while (conn == null) {
synchronized (state) {
//先看看是否还有空闲连接
if (!state.idleConnections.isEmpty()) {
//如果有空闲的连接的话,删除空闲列表里第一个,返回,拿到这个连接
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
//如果没有空闲的连接,则判断活动连接list还有没有空间
if (state.activeConnections.size() < poolMaximumActiveConnections) {
//如果activeConnections太少,那就new一个PooledConnection
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
//如果activeConnections已经很多了,那不能再new了,此时取得activeConnections列表的第一个(最老的)
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
//获取该连接的checkout时间
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
//如果checkout时间过长,则这个connection标记为overdue(过期)
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
oldestActiveConnection.getRealConnection().rollback();
}
//删掉最老的连接,然后再new一个新连接
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
归还连接pushConnection
synchronized (state) {
//先从activeConnections中删除此connection
state.activeConnections.remove(conn);
//判断连接是否失效
if (conn.isValid()) {
//如果空闲连接池还有空间
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
//空闲的连接太少,
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//new一个新的Connection,加入到idle列表
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
//通知其他线程可以来抢connection了
state.notifyAll();
} else {
//否则,即空闲的连接已经足够了
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//那就将connection关闭就可以了
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
关于JNDI连接池就不说了,我们一般使用阿里巴巴的druid或者HikariCP
二,mybatis事务管理
1,数据库事务复习
1.ACID基本性质
-
原子性
一个事务中的所有操作,要么全部完成,要么全部不完成
-
一致性
事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
-
隔离性
是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
- 读未提交
- 读已提交
- 可重复读(innoDB默认隔离级别)
- 不可重复读
-
持久性
是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
2.java中的基本事务模型代码
jdbc中事务基本编写模板,还可以设定savepoint保存点,回滚事务时回滚到保存点
try {
conn.setAutoCommit(false); //将自动提交设置为false
ps.executeUpdate("修改SQL"); //执行修改操作
ps.executeQuery("查询SQL"); //执行查询操作
conn.commit(); //当两个操作成功后手动提交
} catch (Exception e) {
conn.rollback(); //一旦其中一个操作出错都将回滚,使两个操作都不成功
e.printStackTrace();
}
2,mybatis事务管理
mybatis事务模块分层:
可以看到Transaction定义了事务的创建,提交,回滚等操作
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
也可以看到MyBatis的事务管理分为两种形式:
- 一、使用JDBC的事务管理机制:即利用java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等
- 二、使用MANAGED的事务管理机制:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理
重点分析下JDBC的事务管理机制在mybatis中的实现
JdbcTransaction直接使用JDBC的提交和回滚事务管理机制 。它依赖与从dataSource中取得的连接connection 来管理transaction 的作用域,connection对象的获取被延迟到调用getConnection()方法。如果autocommit设置为on,开启状态的话,它会忽略commit和rollback。
直观地讲,就是JdbcTransaction是使用的java.sql.Connection 上的commit和rollback功能
我们在spring整合mybatis时,mybatis配置文件中的一般这样使用事务
转发自https://blog.csdn.net/luanlouis/article/details/37992171
那么,mybatis事务管理的流程是:
-
在初始化mybatis配置时,加载相关配置到conguration对象
-
根据配置创建事务工厂
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"))
-
从事务工厂中获取事务
/** * * @param ds 数据源 * @param level Desired isolation level事务隔离级别 * @param autoCommit Desired autocommit是否自动提交 * @return */ @Override public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit); }
-
将事务封装在执行器中
protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>(); this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; }
-
在具体的jdbcTrancation中底层还是jdbc的事务操作
@Override public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } //熟悉的commit connection.commit(); } } @Override public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); } //熟悉的rollback connection.rollback(); } }
-
上面仅仅提到对jdbc的事务封装,那如何实现真正的事务管理呢
之前我们有说过mybatis的执行过程
sqlSession是与数据库交互的工具,可以理解为一组数据库增删改查的一组操作,事务也是数据库的一组操作,那么mybatis对事务的管理就发生在一个一个的session中
-
从sqlSessionFactory中获取sqlSession
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
在进入openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //通过事务工厂来产生一个事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //生成一个执行器(事务包含在执行器里) final Executor executor = configuration.newExecutor(tx, execType); //然后产生一个DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { //如果打开事务出错,则关闭它 closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { //最后清空错误上下文 ErrorContext.instance().reset(); } }