共计 38654 个字符,预计需要花费 97 分钟才能阅读完成。
MyBatis 差不多在我刚学编程就始终在用,始终没有去看它源码,这次,正好钻研到了,把源码看了一遍,还是有不小的播种的,特意整顿了下,如果有任何问题,欢送指出
概述
MyBatis 这个 orm 框架其实就是把 JDBC 给封装了一层,用过的敌人必定晓得,创立一个 mybatis_config.xml 配置文件,创立一个 mapper 接口,创立一个 mapper.xml 文件,而后在 service 层中调用了。暂且先不剖析源码,如果假如本人开发这么个 orm 框架,性能齐全喝 MyBatis 一样,那摆在本人背后的问题总共有如下 3 个
- 怎么把配置封装起来(数据库链接地址,用户名,明码),达成只注册一次,后续就不须要管这个
- 怎么绑定 mapper 接口和 mapper.xml 文件
- 如何生成一个代理对象,让接口中的办法,找到对应的 mapper 语句,而后把参数带进去执行
带着这几个问题,一步一步的比拟好学习源码,当然,光凭这几个问题是无奈齐全开发进去的,这里我会尽可能带着讲一下,如果有些比拟冷门的配置,可能就要本人去深入研究下了。
JDBC& 原生 MyBatis 调用回顾
首先,MyBatis 是对传统 jdbc 的一层封装,首先咱们先来回顾一下传统的 jdbc
JDBC
public class User {
//user 表的 id
private Integer id;
// 用户名
private String username;
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username == null ? null : username.trim();
}
@Override
public String toString() {return "User [id=" + id + ", username=" + username ;}
}
public class JDBCDemo {
// 创立一个数据库的连贯
private static Connection getConnection() {
Connection connection = null;
try {
// 加载用户驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 连贯数据库的地址
String url = "jdbc:mysql://127.0.0.1:3306/test1";
// 数据库的用户名
String user = "root";
// 数据库的明码
String password = "12345678";
// 失去一个数据库的连贯
connection = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {System.out.println(JDBCDemo.class.getName() + "数据库驱动包未找到!");
return null;
} catch (SQLException e) {System.out.println(JDBCDemo.class.getName() + "SQL 语句有问题,无奈查问胜利!");
return null;
}
return connection;// 返回该连贯
}
public User getUser(int id) {
// 失去该数据库的连贯
Connection connection = getConnection();
// 申明一个 null 的预处理的 Statement
PreparedStatement ps = null;
// 申明一个后果集,用来寄存 SQL 的查问后的后果
ResultSet rs = null;
try {
// 对查问的 User 表的 SQL 进行预处理编译
ps = connection.prepareStatement("select * from user where id=?");
// 把参数 Id 设值到数据的条件中
ps.setInt(1, id);
// 执行查问语句。把后果返回到 ResultSet 后果集中
rs = ps.executeQuery();
// 遍历从后果集中取数
while (rs.next()) {
// 取出 Statement 的用户 id
int user_id = rs.getInt("id");
// 取出 Statement 的用户名
String username = rs.getString("username");
User user = new User();
// 寄存在 user 对象中
user.setId(user_id);
user.setUsername(username);
return user;
}
} catch (SQLException e) {e.printStackTrace();
} finally {this.close(rs, ps, connection);
}
return null;
}
/**
* 判断数据库是否敞开
* @param rs 查看后果集是滞敞开
* @param stmt 预处理 SQL 是否敞开
* @param conn 数据库连贯是否敞开
*/
private void close(ResultSet rs, Statement stmt, Connection conn) {
try {if (rs != null) {rs.close();
}
} catch (SQLException e) {System.out.println(JDBCDemo.class.getName() + "ResultSet 敞开失败!");
}
try {if (stmt != null) {stmt.close();
}
} catch (SQLException e) {System.out.println(JDBCDemo.class.getName() + "Statement 敞开失败!");
}
try {if (conn != null) {conn.close();
}
} catch (SQLException e) {System.out.println(JDBCDemo.class.getName() + "Connection 敞开失败!");
}
}
public static void main(String[] args) {
// 咱们查问用户的 id 为 1 用户
User user = new JDBCDemo().getUser(1);
// 打印输出查问进去的数据
System.out.println(user);
}
}
这里就简略的介绍下 3 个次要的类,前面会对介绍如何封装的
- DriverManager: 当调用办法
getConnection
时,DriverManager
将尝试从初始化中加载的驱动程序中找到适合的驱动程序,并应用与以后小程序或应用程序雷同的类加载器显式加载驱动程序。 - Connection: 与数据库的连贯。执行 SQL 语句并在连贯的上下文中返回后果。
- Statement: 用于执行动态 SQL 语句并返回其生成的后果的对象。
-
ResultSet: 示意数据库后果集
原生 MyBatis 调用
大抵理解当前咱们能够看下原生 MyBatis 写法
mybatis_config<?xml version="1.0" encoding="UTF-8" ?> <!-- Copyright 2009-2017 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- autoMappingBehavior should be set in each test case --> <environments default="development"> <environment id="development"> <!-- 配置事务管理器 --> <transactionManager type="JDBC" /> <!-- 配置数据源类型,以及数据库链接想干信息 --> <dataSource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/> <property name="username" value="root"/> <property name="password" value="12345678"/> </dataSource> </environment> </environments> <!--mapper 文件地位配置 --> <mappers> <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/> </mappers> </configuration>
mapper
public interface AutoConstructorMapper {PrimitiveSubject getSubject(final int id); @Select("SELECT * FROM subject") List<PrimitiveSubject> getSubjects(); @Select("SELECT * FROM subject") List<AnnotatedSubject> getAnnotatedSubjects(); @Select("SELECT * FROM subject") List<BadSubject> getBadSubjects(); @Select("SELECT * FROM extensive_subject") List<ExtensiveSubject> getExtensiveSubjects();}
xml
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright 2009-2017 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper"> <select id="getSubject" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject"> SELECT * FROM subject WHERE id = #{id} </select> </mapper>
应用
private static SqlSessionFactory sqlSessionFactory; @BeforeAll static void setUp() throws Exception { // create a SqlSessionFactory try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } } @Test void fullyPopulatedSubject() {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class); final Object subject = mapper.getSubject(1); assertNotNull(subject); } }
解析 mybatis_config.xml
首先咱们来到第一个问题,MyBatis 是如何解析 mybatis_config.xml
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {ErrorContext.instance().reset();
try {reader.close();
} catch (IOException e) {// Intentionally ignore. Prefer previous error.}
}
}
MyBatis 外围类 Configuration
就是通过 XMLConfigBuilder::parse
办法创立进去,mybatis 把所有配置加载都放在了初始化的时候,而后果保留基本上都保留进了Configuration
,它只在初始化的时候创立一个, 它蕴含了 mybatis-config.xml 外面所有的配置,还包含了所有解析好的 mapper,及其映射关系,再加载实现之后都是可能在这外面找到的,是 MyBatis 外面的外围类。成员变量如下,你能够依据名称看到不少 mybatis_config 的身影。(成员变量如下,能够先提前感触下 mybatis 源码没有简直没正文的代码, 顺带感触下我看源码的苦楚 QAQ)
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
// 罕用类的别名
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check" + savedValue.getResource() + "and" + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
... 办法掠过
}
XPathParser 用法
上面咱们看一下 XMLConfigBuilder
的构造方法
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;// 是否曾经解析果 mybatis-config.xml
// 解析 mybatis-config.xml
private final XPathParser parser;
private String environment;
// 创立和缓存 Reflctor 对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public XMLConfigBuilder(Reader reader, String environment, Properties props) {this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
}
它的外部会封装一个 XPathParser
对象,咱们先来看一下它的用法
<employee id="${id_var}">
<blah something="that"/>
<first_name>Jim</first_name>
<last_name>Smith</last_name>
<birth_date>
<year>1970</year>
<month>6</month>
<day>15</day>
</birth_date>
<height units="ft">5.8</height>
<weight units="lbs">200</weight>
<active bot="YES" score="3.2">true</active>
</employee>
@Test
void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {try (InputStream inputStream = Resources.getResourceAsStream(resource)) {XPathParser parser = new XPathParser(inputStream, false, null, null);
System.out.println(parser.evalLong("/employee/birth_date/year").equals(1970L));//true
System.out.println(parser.evalNode("/employee/birth_date/year").getLongBody().equals(1970L));//true
System.out.println(parser.evalNode("/employee").evalString("@id"));//${id_var}
System.out.println(parser.evalNode("/employee/active").getDoubleAttribute("score"));//3.2 }
}
XPathParser
它封装了 JDK 原生的 Document
,EntityResolver
,XPath
,Properties
这几个对象,更不便的能够解析 XML 文件,具体用法我下面写了个例子,这样各个值都能够拿到了。
节点解析
初始化完 XMLConfigBuilder 后,就会调用它的 parse()
办法,解析 xml,mapper 解析,mapper 绑定,都是在这个办法内实现的。上面咱们来看下这个办法
/**
* 解析 mybatis-config.xml
* 初始化调用
* @return
*/
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 查找 configuration 节点解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parser.evalNode("/configuration")
这一步就是获取 configuration
节点,下一步就是在 parseConfiguration
办法中解析各个子节点
private void parseConfiguration(XNode root) {
try {
// 解析各个节点
// issue #117 read properties first
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);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 typeHandler
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
}
}
看到这里应该就很相熟了,mybatis_config.xml
各个节点放在 +Element 就是对应的解析办法。因为咱们只配置了 environments
和mappers
咱们就看看这两个办法。
解析 Environment
private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创立 datasource,datasourceFactory,设置对应的属性值
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());
break;
}
}
}
}
Transaction
这里 TransactionFactory
负责创立 Transaction
,MyBatis 中Transaction
的子类有 2 个
Transaction
定义了
public interface Transaction {
/**
* 获取对应的数据库连贯对象
*/
Connection getConnection() throws SQLException;
/**
* 提交事务
*/
void commit() throws SQLException;
/**
* 回滚事务
*/
void rollback() throws SQLException;
/**
* 敞开数据库连贯
*/
void close() throws SQLException;
/**
* 获取事务超时工夫
*/
Integer getTimeout() throws SQLException;}
JdbcTransaction
封装了事务隔离级别,连贯,数据源这几个对象,办法基本上就是调用 Connction 对应办法
public class JdbcTransaction implements Transaction {private static final Log log = LogFactory.getLog(JdbcTransaction.class);
// 事务对应数据库连贯
protected Connection connection;
// 数据库连贯所属 datasource
protected DataSource dataSource;
// 事务隔离级别
protected TransactionIsolationLevel level;
// 是否主动提交
protected boolean autoCommit;
@Override
public void rollback() throws SQLException {if (connection != null && !connection.getAutoCommit()) {if (log.isDebugEnabled()) {log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();}
}
.. 其余略
}
还有一个是 ManagedTransaction
它把提交和回滚交给容器实现
public class ManagedTransaction implements Transaction {
... 其余略
// 容器实现
@Override
public void commit() throws SQLException {// Does nothing}
@Override
public void rollback() throws SQLException {// Does nothing}
}
DataSource
创立完 TransactionFactory
上面就轮到 DataSourceFactory
, 这个就没啥好说的了,Configuration
的构造方法中会注册一堆类的别名,而后通过反射创立,这个没啥好说的。
public Configuration() {typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);。。。其余略
}
次要来说一下 DataSource,也就是数据源,它其实就是对驱动的封装。咱们来看下 MyBatis 是怎么进行封装的。UnpooledDataSource
外围办法如下,是不是很亲切,其实就是对传统 JDBC 的一种封装。和下面写的例子没啥太大的却别,次要区别是这里能够反对多个,进行了封装。
/**
* 实现了 getConnection()办法及其重载办法
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class UnpooledDataSource implements DataSource {
// 加载 Driver 类的类加载器
private ClassLoader driverClassLoader;
// 数据库连贯驱动的相干配置
private Properties driverProperties;
// 缓存所有已注册的数据库连贯驱动
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
// 数据库连贯的驱动名称
private String driver;
// 数据库连贯的 url
private String url;
// 用户名
private String username;
// 明码
private String password;
// 是否主动提交
private Boolean autoCommit;
// 事务隔离级别
private Integer defaultTransactionIsolationLevel;
private Integer defaultNetworkTimeout;
static {
// 向 DriverManager 注册 JDBC 驱动
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
@Override
public Connection getConnection(String username, String password) 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);
}
if (username != null) {props.setProperty("user", username);
}
if (password != null) {props.setProperty("password", password);
}
return doGetConnection(props);
}
// 每次创立一个新的连贯
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化数据库驱动
initializeDriver();
// 创立真正的数据库连贯
Connection connection = DriverManager.getConnection(url, properties);
// 配置数据库连贯的 autoCommit 和隔离级别
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();// 创立 Driver 对象
// 注册驱动,DriverProxy 是定义在 UnpooledDataSource 中的外部类,是 Driver 的动态代理类
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 将驱动增加到 registeredDrivers
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);
}
}
private static class DriverProxy implements Driver {
private Driver driver;
DriverProxy(Driver d) {this.driver = d;}。。。略
}。。。略
}
你认为就讲到这里就完了,远远没有。老夫看代码的时候还看到了一个PooledDataSource
,家喻户晓,每次用的时候创立一个 Connection,用完销毁很节约,最好可能服用,当然有更好的实现,例如 Druid 等等,Mybatis 封装的这个我认为学习学习还是蛮不错的。这块感兴趣的能够理解下,不感兴趣的能够不看(能够间接去看 Druid 源码)。
先来看看 PoolState
,作者用 2 个 list 保护了Connection
对象。
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;// 连贯累积的 checkoutTime 时长
protected long claimedOverdueConnectionCount = 0;// 超时连贯个数
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;// 累积超时工夫
protected long accumulatedWaitTime = 0;// 累积等待时间
protected long hadToWaitCount = 0;// 期待次数
protected long badConnectionCount = 0;// 有效的连接数
}
上面咱们再来看看 MyBatis 是如何实现的 PooledDataSource
,最外围的就是获取和放回Connection
了,个人感觉线程池的逻辑差不多。
public class PooledDataSource implements DataSource {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();// 累积 checkout 时长
if (!conn.getRealConnection().getAutoCommit()) {// 回滚未提交事务
conn.getRealConnection().rollback();
}
// 创立 PooledConnection
// 理论打消的就是代理对象,理论用的就是 realConnection
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);// 增加进不沉闷汇合
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();// 将原 PooledConnection 对象设置为有效
if (log.isDebugEnabled()) {log.debug("Returned connection" + newConn.getRealHashCode() + "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 (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
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) {
/**
* 有无闲暇连贯的解决
*/
//idleConnections 闲暇状态连贯
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 创立新数据库连贯,并封装成 PooledConnection 对象
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;
// 将超时连贯移出 activeConnections 汇合
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");
}
}
// 创立新 PooledConnection 对象,复用老的 Collection 对象
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
// 复用工夫戳
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// 超时 PooledConnection 设置成有效
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {log.debug("Claimed overdue connection" + conn.getRealHashCode() + ".");
}
} else {
// 无闲暇连贯、无奈创立新连贯且无超时连贯,则只能阻塞期待
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;
}。。。略
}
上面就是把 TransactionFactory
和DataSource
封装进了 Environment
,再把 environment 塞进了Configuration
其中用到了建造者模式,如果对建造者模式感兴趣能够看我这边博客 https://juejin.cn/post/698594…
解析 Mapper
mapper 配置大概有四种,晓得这个,来看源码就很好了解了内容都大同小异,咱们这里来看下 resource 的解析
public void parse() {
// 判断是否曾经加载过该映射文件
if (!configuration.isResourceLoaded(resource)) {
// 通过 xml 绑定 sql
configurationElement(parser.evalNode("/mapper"));// 解决 mapper 节点
// 解析过的 xml 增加进 loadedResources
configuration.addLoadedResource(resource);
// 扫描注解绑定 sql
bindMapperForNamespace();}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();}
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {
//package 独自解决
if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 解析对应配置
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();}
} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();}
} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
因为 mybatis_config
指定了 mapper 的门路,会加载指定资源,mapperParser.parse();
最终会调用如下办法解析 select 标签,把解析好的后果增加configuration::mappedStatements
public void parseStatementNode() {String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 先解决 include 节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 依据全局中的 useGeneratedKeys 配置
// 是否为 insert 语句,决定是否应用 KeyGenerator 接口实现
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 解析出原始 sql 语句,把 #{}替换成?,把参数
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取 select 标签中 resultType 的值
String resultType = context.getStringAttribute("resultType");
//typeAliasRegistry 中没有对应的,就反射获取对应的 class 对象
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 生成 mappedStatements 并增加进 configuration 的 mappedStatements
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
置信大家也看到注解版的了它是在
注解的是通过 MapperRegistry::addMapper
具体流程和上述的流程相似,也是解析下面办法中的各个值通过 `
MapperBuilderAssistant
` 增加进 configuration
因为所有解析的后果都塞进了 Configuration
外面,最初,把 configuration 塞进 `
DefaultSqlSessionFactory
`
解析局部算是实现了
获取 SqlSession
上面就是获取 SqlSession 了,Mybatis 默认是给你DefaultSqlSession
。
// 通过数据源获取数据库连贯
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {final Environment environment = configuration.getEnvironment();
// 获取 environment 中的 transactionFactory 没有就创立一个
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 具体执行 sql 语句
final Executor executor = configuration.newExecutor(tx, execType);
// 创立 sqlsession
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();}
}
在你获取 mapper 的时候,这里就是通过动静代理,给你生成了一个代理对象 `
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
` 源码如下
@Override
public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}
//Mapper 接口代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {throw new BindingException("Type" + type + "is not known to the MapperRegistry.");
}
try {return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause:" + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
// 创立代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
}
mapper 办法执行
在执行的时候咱们只有看代理是如何执行的就行了也就是 `
MapperProxy它外部封装了一个
PlainMethodInvoker`,最终执行也是调用这个外部类的 invoke 办法
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
//
return mapperMethod.execute(sqlSession, args);
}
}
在执行的时候,会用到后面曾经解析好的参数并结构一个MapperMethod
public class MapperMethod {
// 具体的执行
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {// 依据 sql 类型调用不同办法
case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);
} else {
// 解析参数
Object param = method.convertArgsToSqlCommandParam(args);
// 调用 sqlSession 执行
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for:" + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method'" + command.getName()
+ "attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {return list.get(0);
} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found:" + list.size());
} else {return null;}
}
// 最终调用这一个
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 后面解析 mybatis_config.xml 的时候就曾经加载进 configuration 了
MappedStatement ms = configuration.getMappedStatement(statement);
//
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause:" + e, e);
} finally {ErrorContext.instance().reset();}
}
最终调用 CachingExecutor
,还记得之前 sql 语句封装进SqlSource
么上面的 ms.getBoundSql(parameterObject);
最终会调用 `
sqlSource.getBoundSql(parameterObject);创立一个新的
BoundSql`,这下 sql,参数都有了
//
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
接下来就执行查问操作了,最终会调用 BaseExecutor
来执行查问操作
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 查问一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 针对存储过程的解决
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查问实现
queryStack--;
}
if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 缓存中增加占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用 doQuery
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 删除占位符
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);
}
return list;
}
@Override
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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {closeStatement(stmt);
}
}
// 获取 Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
// 获取 Connection
protected Connection getConnection(Log statementLog) throws SQLException {Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {return connection;}
}
// 通过 BaseStatementHandler 获取
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {closeStatement(statement);
throw e;
} catch (Exception e) {closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause:" + e, e);
}
}
// 创立 PreparedStatement
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.prepareStatement(sql);
} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
//PreparedStatementHandler 执行查问,这个就应该很亲切了
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
至此整个原生 MyBatis 的查问流程就完结了,这里值介绍了查问的流程,增删改的流程也是差不多的,这里就不多赘述了。
参考资料 –《MyBatis 技术底细》