这个mybatis源码分析系列会尽量全面地分析mybatis的核心执行流程、与springboot的集成流程。
系列文档:
- mybatis源码之创建SqlSessionFactory代码分析
- mybatis源码之创建SqlSessionFactory代码分析 - mapper xml解析
- mybatis源码之执行查询SQL代码分析
- mybatis源码之执行insert代码分析
- mybatis源码之mapper接口扫描原理分析
- mybatis源码之集成spring原理
- mybatis源码之集成springboot原理
- mybatis源码之集成mybatis-plus源码
- mybatis源码之mybatis-plus执行查询(基础篇完结)
- MybatisPlusAutoConfiguration源码分析
- Autowired注入Service变成了biaomidou的Mapper代理
待分析的课题
- xml mapper独立使用源码分析
- mapper接口独立使用源码分析
- springboot集成源码分析
- mybatis-plus核心源码分析
- mybatis-plus与springboot集成源码分析
本文分析的内容
- 开发环境
- 示例代码
- 创建SqlSessionFactory代码的第1部分:事务管理器、数据源的解析以及插件的解析
环境准备
开发环境
windows 10
java 1.8
mybatis版本
mybatis 3.5.6依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
示例代码 - xml配置方式
示例代码
public class XmlBlogMapperTest {
private SqlSession session;
@Before
public void before() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sessionFactory.openSession();
}
@After
public void after() {
session.close();
}
@Test
public void testSelectBlogById() {
String statement = "org.net5ijy.mybatis.test.BlogMapper.selectBlogById";
Blog blog = session.selectOne(statement, 1);
System.out.println(blog);
}
@Test
public void testSelectBlogByBlogSearchParameter() throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
Blog blog = new Blog();
blog.setContent("mybatis源码分析");
blog.setTitle("mybatis");
BlogSearchParameter parameter = new BlogSearchParameter();
parameter.setId(1);
parameter.setIds(ids);
parameter.setCreateTime(format.parse("2020-01-01 00:00:00"));
parameter.setBlog(blog);
List<Blog> list = session.selectList(
"org.net5ijy.mybatis.test.BlogMapper.selectBlogByParameter", parameter);
for (Blog b : list) {
System.out.println(b);
}
}
}
主配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
BlogMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.net5ijy.mybatis.test.BlogMapper">
<resultMap id="BlogResultMap" type="org.net5ijy.mybatis.test.entity.Blog">
<id column="id" property="id"/>
<id column="title" property="title"/>
<id column="content" property="content"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="selectBlogById" resultMap="BlogResultMap"
parameterType="java.lang.Integer">
select * from blog where id = #{id}
</select>
<select id="selectBlogByParameter" resultMap="BlogResultMap"
parameterType="org.net5ijy.mybatis.test.parameter.BlogSearchParameter">
select *
from blog
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="ids != null and ids.size > 0">
and id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="blog.title != null">
and title like concat(concat('%',#{blog.title}),'%')
</if>
<if test="blog.content != null">
and content like concat(concat('%',#{blog.content}),'%')
</if>
<if test="createTime != null">
and create_time <![CDATA[ > ]]> #{createTime}
</if>
</where>
</select>
</mapper>
创建SqlSessionFactory代码分析 - 第1部分
入口
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sessionFactory.openSession();
创建SqlSessionFactory
SqlSessionFactory - Creates an SqlSession out of a connection or a DataSource.
在示例代码中,通过这行代码创建SqlSessionFactory:
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
build方法:
public SqlSessionFactory build(
InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse()会解析配置文件,返回org.apache.ibatis.session.Configuration对象
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.
}
}
}
// 创建了一个DefaultSqlSessionFactory对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
所以,重点就是parser.parse()方法。
XMLConfigBuilder.parse()方法
这个方法非常重要,主要做了以下事情:
- 主配置文件的解析
- 解析事务管理器和数据源
- 插件
- mapper xml文件的解析
- 解析parameter map
- 解析result map
- 解析statement
此处只列出了核心的内容,其余的例如别名、settings之类的就不做重点分析了。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
// 加载插件
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 加载事务管理器、数据源
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析mapper xml文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration.");
}
}
解析事务管理器和数据源
配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
源码入口
environmentsElement(root.evalNode("environments"));
environmentsElement方法:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 解析environments的default属性 = development
environment = context.getStringAttribute("default");
}
// 遍历所有的environment节点
for (XNode child : context.getChildren()) {
// 解析environment的id属性
String id = child.getStringAttribute("id");
// 判断environment的id属性 = development
if (isSpecifiedEnvironment(id)) {
// 解析transactionManager
TransactionFactory txFactory =
transactionManagerElement(child.evalNode("transactionManager"));
// 解析dataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
解析transactionManager
事务管理器的类型:
- JDBC - Transaction that makes use of the JDBC commit and rollback facilities directly. It relies on the connection retrieved from the dataSource to manage the scope of the transaction. Delays connection retrieval until getConnection() is called. Ignores commit or rollback requests when autocommit is on.
- MANAGED - Transaction that lets the container manage the full lifecycle of the transaction. Delays connection retrieval until getConnection() is called. Ignores all commit or rollback requests. By default, it closes the connection but can be configured not to do it.
是在这里注册的:
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
// ...
}
这是mybatis内置两种Transaction实现,如果在spring环境中,使用的是SpringManagedTransaction实现。
根据我们的配置,使用的是JdbcTransactionFactory和JdbcTransaction实现。
这行代码:
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
transactionManagerElement方法:
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
// type = jdbc
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// 使用type到typeAliasRegistry获取到对应的实现
// 并调用默认无参构造方法实例化
TransactionFactory factory = (TransactionFactory)
resolveClass(type).getDeclaredConstructor().newInstance();
// 设置属性
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
解析dataSource
这行代码:
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
DataSourceFactory接口:
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
dataSource的type有三种类型:
- JNDI - JndiDataSourceFactory从jndi环境获取数据源,此处不做详细介绍
- POOLED - PooledDataSourceFactory使用连接池机制
- UNPOOLED - UnpooledDataSourceFactory不使用连接池机制
按照我们的配置,使用的应该是PooledDataSourceFactory类型。
dataSourceElement方法:
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 获取到数据源实现
// JNDI、POOLED、UNPOOLED或者使用DataSourceFactory的实现类全名
// 所以如果mybatis集成druid就可以从这里入手
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// 实例化
DataSourceFactory factory = (DataSourceFactory)
resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
这个DataSource就是java.sql.DataSource类型,C3P0、Druid等第三方库都有实现,mybatis内置了两个实现:
- POOLED - PooledDataSource使用连接池机制
- UNPOOLED - UnpooledDataSource不使用连接池机制
UnpooledDataSource实现
核心代码:
// 每次都获取一个新的Connection对象
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);
}
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);
}
}
}
private void configureConnection(Connection conn) throws SQLException {
if (defaultNetworkTimeout != null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
}
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
PooledDataSource实现
核心代码:
public class PooledDataSource implements DataSource {
private static final Log log = LogFactory.getLog(PooledDataSource.class);
private final PoolState state = new PoolState(this);
private final UnpooledDataSource dataSource;
// OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected int poolMaximumLocalBadConnectionTolerance = 3;
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;
// ...
@Override
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
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection ");
}
} 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) {
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 ...");
}
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 ...");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount >
(poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get good connection.");
}
throw new SQLException("...");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition ...");
}
throw new SQLException("...");
}
return conn;
}
// 归还连接
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
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();
}
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 to pool.");
}
state.notifyAll();
} else {
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 ...");
}
state.badConnectionCount++;
}
}
}
}
PoolState类
封装当前空闲、活跃连接等信息。
public class PoolState {
protected PooledDataSource dataSource;
protected final List<PooledConnection> idleConnections = new ArrayList<>();
protected final List<PooledConnection> activeConnections = new ArrayList<>();
protected long requestCount = 0;
protected long accumulatedRequestTime = 0;
protected long accumulatedCheckoutTime = 0;
protected long claimedOverdueConnectionCount = 0;
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
protected long accumulatedWaitTime = 0;
protected long hadToWaitCount = 0;
protected long badConnectionCount = 0;
}
PooledConnection类
这个类使用代理方式实现,覆盖了默认的close方法实现了关闭连接归还连接池的机制:
// 这个类实现连接池的方式是比较基础而且最容易上手的
class PooledConnection implements InvocationHandler {
private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
private final int hashCode;
private final PooledDataSource dataSource;
private final Connection realConnection;
private final Connection proxyConnection;
private long checkoutTimestamp;
private long createdTimestamp;
private long lastUsedTimestamp;
private int connectionTypeCode;
private boolean valid;
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
// 创建代理
this.proxyConnection = (Connection)
Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
// 返回代理对象
public Connection getProxyConnection() {
return proxyConnection;
}
/**
* Required for InvocationHandler implementation.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 覆盖掉默认的close方法,把连接归还给连接池
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
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);
}
}
private void checkConnection() throws SQLException {
if (!valid) {
throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
}
}
}
构建Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
插件 - 拦截器
插件 - 拦截器概述
mybatis的插件 - 拦截器机制:开发者编写Interceptor接口的实现类,对mybatis中的特定组件的特定方法做拦截实现,在主配置文件通过plugins注册使其生效。
配置方式
<configuration>
<!-- other code -->
<plugins>
<plugin interceptor="org.net5ijy.mybatis.test.interceptor.PrintSqlInterceptor"/>
</plugins>
<!-- other code -->
</configuration>
源码分析
pluginElement(root.evalNode("plugins"));
pluginElement方法:
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)
resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
实例化和设置属性的方式与之前的事务管理器、数据源一致。
configuration.addInterceptor(interceptorInstance);
在Configuration中使用InterceptorChain保存拦截器,在InterceptorChain中使用一个Interceptor的集合保存所有的拦截器。
以上这些内容就是初始化阶段的插件 - 拦截器加载。