赞
踩
这个mybatis源码分析系列会尽量全面地分析mybatis的核心执行流程、与springboot的集成流程。
系列文档:
windows 10
java 1.8
mybatis 3.5.6依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
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); } } }
<?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>
<?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>
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sessionFactory.openSession();
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()方法。
这个方法非常重要,主要做了以下事情:
此处只列出了核心的内容,其余的例如别名、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()); } } } }
事务管理器的类型:
是在这里注册的:
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.");
}
这行代码:
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
DataSourceFactory接口:
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
dataSource的type有三种类型:
按照我们的配置,使用的应该是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内置了两个实现:
核心代码:
// 每次都获取一个新的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); } }
核心代码:
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++; } } } }
封装当前空闲、活跃连接等信息。
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;
}
这个类使用代理方式实现,覆盖了默认的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.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的集合保存所有的拦截器。
以上这些内容就是初始化阶段的插件 - 拦截器加载。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。