Mybatis深入之获取数据库连接
来源:互联网 发布:纳粹党卫军军服淘宝 编辑:程序博客网 时间:2024/05/16 07:26
Mybatis深入之获取数据库连接
简介
主要记录Mybatis何时获取数据库连接以及获取数据库连接的过程。难点在于明白在使用Mybatis数据库连接池情况下的数据库连接的获取过程。
何时获取数据库连接
Mybatis只有在真正执行sql操作的时候才会去获取数据库连接。至于如何验证:
不深入源码
简单来讲就是有意将数据库配置信息写成、在一个sql执行过程中看哪一步抛数据库连接异常。
public static void main(String[] args) throws Exception { String mybatisConfigPath = "config/mybatis/mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount"); System.out.println(count); }
- 上面是一段Mybatis执行代码
- 我们可以将Mybatis连接数据库的信息有意写错
- 再DEBUG模式下一步一步调试看哪一步会抛异常
- 抛异常的那一步就是真正获取数据库连接的一步
异常信息:
深入源码
这里简单提一下、具体后面会有。最有迷惑性的是觉得在openSession()
的时候会获取数据库连接、其实不然:
openSession()最终只是返回一个操作数据库的会话、并不包含数据库连接,DefaultSqlSession(这个是Mybatis初始化的时候返回的一个SqlSession)中的方法:
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); 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(); } }
- 主要装配DefaultSqlSession中执行Sql的Excutor、后面关于一个Sql完整的执行过程会有对其的详细分析
显然真正获取数据库连接的操作是在sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");
进行的。
获取数据库连接
在真正的获取数据库连接代码之前、还有许多为sql执行而生的代码、这里暂时忽略或者一些必要的说明、主要重心放在如何获取数据库连接。
书接上回、从前面执行sql代码开始:
int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");
下图是上面代码一系列方法调用过程:
经过一系列调用到SimpleExecutor——》doQuery():
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //RoutingStatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //MappedStatement、这一句是关键 stmt = prepareStatement(handler, ms.getStatementLog()); /* *参数: * stmt: PreparedStatementLogger * resultHandler: null */ return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
- 上面经过一系列跳转之后各个引用的实例我加了注释、有兴趣的可以自己跟一下、有时候可能要多跟几遍
- 关键代码
stmt=prepareStatement(handler,ms.getStatementLog());
、这一句根据方法名就能猜测、是根据Connection来获取执行Sql的PrepareStatement - 但是到现在为止我们都没有看到方法的参数中有关于数据库连接的、在此方法中
SimpleExecutor——》prepareStatement();
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //获取数据库连接。statementLog:org.apache.ibatis.loggin.slf4j.Slf4Impl Connection connection = getConnection(statementLog); //获取执行Sql的Statement——PrepareStatementLogger //PrepareStatementLogger是PrepareStatement的代理、多了对使用Mybatis执行sql语句时记录sql语句的功能 stmt = handler.prepare(connection); //将执行Sql需要的参数设置到PrepareStatement中。 handler.parameterize(stmt); return stmt; }
其他的不关注、这里只看数据库连接代码:Connection connection = getConnection(statementLog);
BaseExecutor——》getConnection():
protected Connection getConnection(Log statementLog) throws SQLException { //如果关于数据库连接的日志记录级别是DEBUG级别、则为获取的Connection进行代理、新增日志记录功能、这里不是重点。 if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
- Mybatis中关于事务配置项的值是”JDBC”、所以从Mybatis深入之事务管理 知道这里的transaction其实是:JdbcTransaction
- 最终到JdbcTransaction获取连接的方法中
JdbcTransaction——》openConnection()
protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); }
- 上面的dataSource从 Mybatis深入之DataSource实例化过程 知道当使用数据库连接池的时候实例化的是PooledDataSource
PooledDataSource——》getConnection():
public Connection getConnection() throws SQLException {/* * 为理解方便、将原来代码拆分如下: */ PooledConnection pooledConnection = popConnection(dataSource.getUsername(), dataSource.getPassword()); Connection connection = pooledConnection.getProxyConnection(); return connection ;//return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); }
- 从拆分代码看分两步
- 获取数据库真正连接
- 获取真正数据库连接的代理类作为最终返回结果、至于代理是做什么、后面继续
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 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 { // Cannot create new connection PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); 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; }
- 先解释一下逻辑、再关注具体的数据库连接方法
1. 先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。2. 查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步;3. 看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。4. 线程等待,循环2步
具体的创建数据库连接代码conn = new PooledConnection(dataSource.getConnection(), this);
- 上面代码中的dataSource为UnpooledDataSource、可以从 Mybatis深入之DataSource实例化过程 了解原因。
所以先要看UnpooledDataSource——getConnection() 经过一系列跳转到同类如下方法:
private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); configureConnection(connection); return connection; }
- 上面我们可以看到很熟悉的加载驱动、获取数据库连接
补充
其实关于数据库部分还有很多要写的、比如数据库连接池工作原理、数据库连接何时关闭。这里暂不准备一次将所有的东都放在一起。觉得分开点更容易说清楚理解起来不是那么费劲。
后面会有一篇Mybatis数据库连接池原理来分析它。
更多内容Mybatis 目录
- Mybatis深入之获取数据库连接
- Mybatis深入之数据库连接池原理
- Mybatis源码阅读之数据库连接
- [MyBatis]获取数据库连接、批量执行SQL语句
- [MyBatis]获取数据库连接、批量执行SQL语句
- Mybatis深入之事务管理
- mybatis性能优化之减少数据库连接
- 深入MyBatis开发之mybatis配置
- 深入MyBatis开发之mybatis映射器
- Mybatis深入之初始化过程
- 深入数据库连接
- Mybatis-update - 数据库死锁 - 获取数据库连接池等待
- spring-mybatis配置使用jdbc.properties导致数据库连接获取不到
- Hibernate之Session获取数据库连接(connection)
- JDBC之使用DBCP数据源获取数据库连接
- mybatis使用之SqlSession获取
- Mybatis实战 之 获取主键
- spring 整合mybatis之 BoneCP数据库连接池配置
- 数据结构
- 详解C/C++中的typedef和#define
- 从AFDS 2012看异构计算 无处不在的应用zz
- 黑马程序员--迷宫小游戏
- Objective-C Autorelease Pool 的实现原理
- Mybatis深入之获取数据库连接
- Android+OpenCV实现轨迹识别
- Email5.0 代码结构
- 多文件编译
- Kafka剖析(一):Kafka背景及架构介绍
- hdoj 1058 Humble Numbers 【好题】
- 黑马程序员——Java中的枚举
- GPU基本介绍
- 03-树1. 二分法求多项式单根(20)