简单介绍下mybatis获取SqlSession和进行sql操作的例子
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.selectList("SQL");
本文主要从上面代码中分析Mybatis的实现源码
首先是sqlSession的获取,时序图如下:
第一步首先SqlSessionFactoryBuilder去读取mybatis的配置文件,然后build一个DefaultSqlSessionFactory
关键代码如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//然后返回一个DefaultSqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
第二步,获取到SqlSessionFactory之后,就可以利用SqlSessionFactory方法的openSession来获取SqlSession对象了。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
// execType为执行器类型,配置文件中定义
// SimpleExecutor -- SIMPLE 就是普通的执行器。
//ReuseExecutor -执行器会重用预处理语句(prepared statements)
//BatchExecutor --它是批量执行器
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//定义执行器,是对statement的封装
final Executor executor = configuration.newExecutor(tx, execType);
//最后返回一个SqlSession
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();
}
}
得到SqlSession对象之后就可以利用SqlSession内部的方法进行CRUD操作了。
注意一点,Connection对象是在SqlSession对象创建之后进行CURD操作中创建的。深入查找之后找到在ManagedTransaction类中找到获取Connection对象的关键代码如下:
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
//dataSource 来源有三种,JndiDatasource,PooledDataSource,UnpooledDataSource,配置文件中定义
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
PooledDataSource和UnPooledDataSource的区别是PooledDataSource使用了连接池。为什么使用连接池呢?因为创建一个Connection对象的过程,在底层就相当于和数据库建立的通信连接,在建立通信连接的过程,消耗了非常多的时间,而往往我们建立连接后(即创建Connection对象后),就执行一个简单的SQL语句,然后就要抛弃掉,这是一个非常大的资源浪费!mybatis针对这一个问题提出的PooledDataSource使用了连接池。
本文主要介绍下使用连接池的PooledDataSource
简单介绍下几个概念:IdelConnections,ActiveConnections
PooledDataSource把java.sql.connection对象封装成PooledConnection对象放到PoolState类型的容器中进行维护,Mybatis把PooledConnection对象分为两种状态,空闲状态(idle)和活动状态(active),这两种状态的PooledConnection对象分别放在PoolState容器的idleConnections和activeConnections两个list中。
IdleConnections:空闲状态的PooledConnection放在次集合中,调用getConnection方法时,优先从该集合中获取PooledConnection对象,当连接结束时,Mybatis会把该对象放到该集合中。
ActiveConnections:激活状态的PooledConnection对象放在次集合中,表示当前正在使用的PooledConnection对象,调用getConnection方法时,首先看idleConnections集合是否不为空,如果没有对象,然后看此集合是否已满,不满的话,会新生成一个对象放到此集合中。
首先介绍下PooledConnection对象的获取过程,关键代码是PooledDataSource中的popConnection函数,代码如下:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {
synchronized (state) {
//先判断空闲列表中是否为空,不为空,直接从空闲列表中获取。
if (state.idleConnections.size() > 0) {
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
// 空闲列表中不为空,需要重新创建一个PooledConnection对象,先判断激活列表是否已满,不满可以创建
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this);
@SuppressWarnings("unused")
//used in logging, if enabled
Connection realConn = conn.getRealConnection();
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// 激活列表已满,需要查看最新进入激活列表的PooledConnection对象是否已过期
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// 如果已经过期,移除掉,并创建新的PooledConnection对象
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
oldestActiveConnection.getRealConnection().rollback();
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// 如果没有过期,只能继续等待
// Must wait
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
//成功获取连接,然后更新信息
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
从上述的方法从连接池得到一个pooledConnection对象, 共经历了以下几个步骤
1:查看IdleConnections集合是否为空
合中是否有pooledConnection对象,如果有,直接返回一个可用的PooledConnection对象。没有进行第二步
2:查看activeConnections集合是否已满,如果没有满,则PooledDataSource新建一个PooledConnection对象,然后放到activeConnections集合中,然后返回该对象。如果已满则跳到第三步。
3:查看最先进入activeConnection集合的pooledConnection对象是否已经过期,如果已经过期,删除该对象,然后新建一个新的PooledConnection对象,然后放到该集合中,如果没有过期,则进行第四步
4:线程等待,然后跳转第二步。
连接过程如上,然后需要查看下数据库连接断开过程中Connection对象的操作:
一般情况,Connection对象调用close方法后会释放全部资源,然后Connection对象也就不能用了,使用连接池断开连接,不是直接释放资源,而是把Connection对象放到连接池中。
PooledDataSource使用动态代理模式,为真正的Connection对象进行代理,具体代理代码如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//判断是否是close方法。
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
从上面可以看出,调用close方法时,会调用pushConnection方法
pushConnection方法主要功能就是把PooledConenction从ActiveConnections列表中移除放到IdelConnections列表中,并修改一些状态信息。
以上就是连接池的一些源码信息。实际上MyBatis的PooledDataSource中的PoolState容器内部维护的对象是PooledConnection对象,而PooledConnection则是对真正的数据库连接java.sql.Connection实例对象的包裹器,对Conenction对象进行了代理。