赞
踩
对于我们开发来讲,不管跟任何关系型数据库打交道都无法规避这三巨头,数据库的会话-Sqlsession、链接-Connection和事务-Transaction,今天让我们一起来梳理下这三者之间的工作原理和关系
1、首先来解析下会话
会话是Mybatis持久化层跟关系型数据库交互的基础,所有的查询、数据更新(包含保存、更新、删除)操作都在与数据库建立会话的基础上进行的;MyBatis中的会话是SqlSession,默认实现是DefaultSqlSession。可以通过SqlSessionFactory的openSession来获取的。
通过SqlSessionFactory获取SqlSession的代码如下:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
打开一个会话的时序图大致流程如下:
第一步:通过new SqlSessionFactoryBuilder().build(inputStream)来构造SqlSessionFactory,参数是配置文件的输入流。*
主要实现的代码块如下:*
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
第二步:XMLConfigBuilder的parse方法会解析配置文件,解析的结果就是得出一个Configuration对象。其中一步就是根据配置文件中的datasource节点解析出数据源
主要实现的代码块如下:
<dataSource type="POOLED">
<!--这里会替换为local-mysql.properties中的对应字段的值-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
<property name="poolMaximumActiveConnections" value="2"/>
<property name="poolMaximumIdleConnections" value="2"/>
</dataSource>
第三步:SqlSessionFactory的openSession会获取SqlSession。具体实现代码如下:
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(); } } ```yaml 在这里插入代码片
2、接下来再分析下Mybatis的链接
MyBatis在第一次执行SQL操作时,在获取Statement时,会去获取数据库链接。
我们配置的数据源为POOLED,这里会使用PooledDataSource来获取connection。
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.isEmpty()) { // 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); 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()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException e) { /* Just log a message for debug and continue to execute the following statement like nothing happend. Wrap the bad connection with a new PooledConnection, this will help to not intterupt current executing thread and give current thread a chance to join the next competion for another valid/good database connection. At the end of this loop, bad {@link @conn} will be set as null. */ log.debug("Bad connection. Could not roll back"); } } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); 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) { // ping to server and check the connection is valid or not 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 + poolMaximumLocalBadConnectionTolerance)) { 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;
}
这里进行了数据库的链接进行了池化管理:如果idle的connection,就直接取出一个返回。数据库链接的获取底层代码如下:
获取链接后的第一件事,就是设置connection的autoCommit属性。这里可以看出MyBatis通过自身的数据源PooledDataSource来进行数据库链接的管理。
3、然后再来说下事务
在执行sqlSession.commit时,会去提交事务。
UserMapperExt userMapperExt = sqlSession.getMapper(UserMapperExt.class);
userMapperExt.insert(new UserDTO("houliu",23));
userMapperExt.findUserListByName("zhangsan");
userMapperExt.update("name" ,"wangwu",22);
sqlSession.commit();
执行commit后,会调用如下代码:
一个sqlSession中可以进行多个事务提交:
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapperExt userMapperExt1 = sqlSession1.getMapper(UserMapperExt.class);
userMapperExt1.insert(new UserDTO("houliu",23));
userMapperExt1.findUserListByName("zhangsan");
userMapperExt1.update("name" ,"wangwu",22);
sqlSession1.commit();
//SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapperExt userMapperExt2 = sqlSession1.getMapper(UserMapperExt.class);
userMapperExt2.insert(new UserDTO("houliu",23));
userMapperExt2.findUserListByName("zhangsan");
userMapperExt2.update("name" ,"wangwu",22);
sqlSession1.commit();
原生jdbc中一个connection可以执行多次commit:
Class.forName(“com.mysql.cj.jdbc.Driver”); //classLoader,加载对应驱动
Connection connection = DriverManager.getConnection(“jdbc:mysql://127.0.0.1:3306/test?useUnicode=true”, “root”, “12345678”);
connection.setAutoCommit(false);
PreparedStatement preparedStatement = connection.prepareStatement(“update cnt_user set age = 201 where name = ‘zhangsan’”);
preparedStatement.execute();
connection.commit();
preparedStatement = connection.prepareStatement("update cnt_user set age = 233 where name = 'zhangsan'");
preparedStatement.execute();
preparedStatement = connection.prepareStatement("insert into cnt_user (age , name) values(100 ,'liusi')");
preparedStatement.execute();
connection.commit();
可以看出,事务是依附在SqlSession上的。
好了,讲完了上面三者的原理后,最后我们来总结下三者的关系
**
链接可以通过数据库链接池被复用。在MyBatis中,不同时刻的SqlSession可以复用同一个Connection,同一个SqlSession中可以提交多个事务。因此,链接—会话—事务的关系如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。