目录
其他参数driver、url、username、password
mybatis封装了jdbc的加载驱动和建立连接,并把连接池化以保证高效率访问数据库。
配置
一般用mybatis时,我们只需如下配置即可
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
其中dataSource中的type=POOLED 表示池化,其他的driver、url、username、password是加载驱动和建立连接需要的
解析
先看下解析<environments>
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//为空从default获取
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
//获取id
String id = child.getStringAttribute("id");
//判断environment与id是否相等
if (isSpecifiedEnvironment(id)) {
//解析transactionManager获取TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//解析dataSource获取DataSourceFactory #1
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取DataSource #2
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
#0处解析<transactionManager>获取TransactionFactory,这里会解析JDBC别名对应的TransactionFactory即JdbcTransactionFactory
别名在org.apache.ibatis.session.Configuration#Configuration()
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
#1处解析<dataSource>标签获取DataSourceFactory,这里会解析POOLED别名,对应的DataSourceFactory 即PooledDataSourceFactory
别名在org.apache.ibatis.session.Configuration#Configuration()
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
#2处根据PooledDataSourceFactory获取DataSource 即PooledDataSource
阶段总结1:
在初始化阶段,会根据environments标签的JDBC和POOLED获取到JdbcTransactionFactory和PooledDataSource用于后续建立连接、池化操作。
执行
我们在用mybatis是一般是调用mapper的方法,类似代码如下
//获取SqlSession #1
try (SqlSession session = sqlMapper.openSession()) {
//获取mapper #2
TestAuthorMapper mapper = session.getMapper(TestAuthorMapper.class);
Author param=new Author();
param.setId(101);
//执行 #3
Author author = mapper.selectAuthor(param,101);
System.out.println(author);
}
#1处获取SqlSession 底层是DefaultSqlSession,其包含了全局配置Configuration,执行器CachingExecutor,Jdbc事务JdbcTransaction等,代码在方法中org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
#2处从全局配置Configuration获取已经注册的mapper,并生成jdk代理,代码在
org.apache.ibatis.binding.MapperRegistry#getMapper
org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
#3调用代理类的方法,实际执行org.apache.ibatis.binding.MapperProxy#invoke方法,最终调用org.apache.ibatis.binding.MapperMethod#execute方法,进行执行。继续跟代码,获取连接在org.apache.ibatis.executor.BaseExecutor#getConnection中。
获取连接
代码如下
protected Connection getConnection(Log statementLog) throws SQLException {
//这里的transaction 为JdbcTransaction #1
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
//#2
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
#1的transaction和#2处的dataSource是在openSession设置的。其中transaction为JdbcTransaction,dataSource为PooledDataSource
继续跟代码,代码比较多耐心看
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
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 #1
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 #2
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// Cannot create new connection #3
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection #4
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 happened.
Wrap the bad connection with a new PooledConnection, this will help
to not interrupt current executing thread and give current thread a
chance to join the next competition 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 #5
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.");
}
}
}
}
}
其中PoolState表示池化状态
#1处判断有没有空闲连接,有的话直接取出,设置池状态返回
#2处没有空闲连接,判断当前活跃的连接是否已达到最大值,没到最大值新建连接并设置池状态返回
#3处表示活跃的连接达到了最大值,会拿出最后一个活跃的连接,判断是否已经超时,如果超时了会把连接删除,回滚,重建,设置池状态返回;如果没有超时则,线程等待有执行完的连接
以上是创建连接,符合有就用没有创建的原则。执行完sql放入连接池代码如下
org.apache.ibatis.datasource.pooled.PooledDataSource#pushConnection,在调用close时执行
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//#1
state.activeConnections.remove(conn);
if (conn.isValid()) {
//#2
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
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.");
}
state.notifyAll();
} else {
//#3
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
#1处调用close方法后,把当前连接在活跃列表中去除
#2处判断当前空闲连接是否达到最大值,没达到最大值,把当前真实连接封装为PooledConnection并放入连接池的空闲列表中
#3处如果空闲连接达到最大值,直接关掉连接
阶段总结2:
在配置dataSource为POOLED情况下会创建连接池PooledDataSource,其中PoolState维持池的状态,重点是idleConnections空闲连接 没有执行任务,activeConnections活跃连接 正在执行任务。poolMaximumActiveConnections池的最大活跃连接数,poolMaximumIdleConnections池的最大空闲连接数
连接池原理,当执行sql建立数据库连接时,会判断有没有空闲的连接,如果有的话直接取来用。如果没有则新建连接(分直接新建和等待新建)。执行完之后,会把连接重新构造后条件允许(空闲连接是否满)的情况下放入池中。
其他参数driver、url、username、password
org.apache.ibatis.datasource.pooled.PooledDataSource#popConnection的#2处在创建PooledConnection是又会通过dataSource.getConnection()去创建真实连接,代码如下
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
//username
if (username != null) {
props.setProperty("user", username);
}
//password
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
可以看到JDBC的模板操作步骤了