原生Mybatis框架源码解析
前言:
mybatis是我们常用的一种操作数据库的框架。
我们在使用的mybatis有多种方式:原生mybatis、与Spring结合使用的mybatis、与SprinBoot结合使用的mybatis。
使用的方式越来越简单,需要我们配置的项也越来越少,但是原理都是通用的,底层都是mybatis框架,而mybatis框架的底层也就是我们熟悉的JDBC。
万变不离其宗,下面请随着笔者来以对照JDBC的方式来剖析mybatis源码,看完其源码可以发现其实框架没有我们想象中的那么难,只是封装的好一点,考虑的情况多一点
1.JDBC操作数据库的方式
String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; String sql = "update blog set name = ? where id=?"; try { // 1.获取连接 Connection connection = DriverManager.getConnection(url, username, password); // 2.创建 preparedStatement PreparedStatement prepareStatement = connection.prepareStatement(sql); // 3.初始化参数 prepareStatement.setString(1, "lucy"); prepareStatement.setInt(2, 1); // 4.执行update prepareStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace();
主要分四步:获取连接、创建preparedStatement、封装参数、执行
下面我们也按照这四步来分析Mybatis
2.使用原生mybatis方式来操作数据库
关于mybatis的详细使用读者可参考 易佰教程 https://www.yiibai.com/mybatis/install_configure.html
笔者直接摘录其中使用mybatis的经典方式,代码如下(注意:以下代码来自 易佰教程):
public class HelloWord { private static SqlSessionFactory sqlSessionFactory; private static Reader reader; static { try { // 1.读取mybatis配置文件,并生成SQLSessionFactory reader = Resources.getResourceAsReader("config/Configure.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e) { e.printStackTrace(); } } public static SqlSessionFactory getSession() { return sqlSessionFactory; } /** * @param args */ public static void main(String[] args) { // 2.获取session,主要的CRUD操作均在SqlSession中提供 SqlSession session = sqlSessionFactory.openSession(); try { // 3.执行查询操作 // 该方法包括三个步骤:封装入参数、执行查询、封装结果为对象类型 User user = (User) session.selectOne( "com.yiibai.mybatis.models.UserMapper.GetUserByID", 1); if(user!=null){ String userInfo = "名字:"+user.getName()+", 所属部门:"+user.getDept()+", 主页:"+user.getWebsite(); System.out.println(userInfo); } } finally { session.close(); } } }
通过上面的使用可知,最关键的代码就是session.selectOne(),里面包括了入参的封装、查询的执行、结果封装。这句话基本对应了JDBC中全部的四步关键操作。下面我们先来看下SqlSession的获取,然后再对其方法进行分析。
3.SqlSession的获取
// 1.读取config目录下Configure.xml文件 Reader reader = Resources.getResourceAsReader("config/Configure.xml"); // 2.使用SqlSessionFactoryBuilder创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); // 3.在SqlSessionFactory中创建SqlSession SqlSession session = sqlSessionFactory.openSession();
1)Resources.getResourceAsReader("config/Configure.xml")读取文件
public static Reader getResourceAsReader(String resource) throws IOException { Reader reader; if (charset == null) { // 默认为null,走该步骤 reader = new InputStreamReader(getResourceAsStream(resource)); } else { reader = new InputStreamReader(getResourceAsStream(resource), charset); } return reader; } //getResourceAsStream() public static InputStream getResourceAsStream(String resource) throws IOException { return getResourceAsStream(null, resource); } //getResourceAsStream() public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { // ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper(); InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); if (in == null) throw new IOException("Could not find resource " + resource); return in; } //classLoaderWrapper.getResourceAsStream(resource, loader) InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { for (ClassLoader cl : classLoader) { if (null != cl) { // 关键就是这句话 InputStream returnValue = cl.getResourceAsStream(resource); // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource if (null == returnValue) returnValue = cl.getResourceAsStream("/" + resource); if (null != returnValue) return returnValue; } } return null; }
总结1):主要就是通过ClassLoader.getResourceAsStream()来获取指定classpath路径下的Resource
2)new SqlSessionFactoryBuilder().build(reader)获取SessionFactory
public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } //build() public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); // 主要就是这句话 // 实现了两个功能parse.parse()解析了xml;build(configuration)创建了SqlSessionFactory 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. } } }
* 下面看下parse.parse()方法如何进行xml解析
//XMLConfigBuilder.parse() public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } // parseConfiguration() // 可以看到主要是对xml各节点的分析,我们重点关注下mapperElement()方法 private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers"));// 重点关注下这个方法 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } // mapperElement(root.evalNode("mappers")) private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // 1.获取resource信息,也就是对应的mapper.xml对应路径 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // 2.解析对应的mapper.xml文件, ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 3.将解析出来的Mapper对象添加到Configuration中 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); 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."); } } } } }
可以看到,通过解析configuration.xml文件,获取其中的Environment、Setting,重要的是将<mappers>下的所有<mapper>解析出来之后添加到Configuration,Configuration类似于配置中心,所有的配置信息都在这里
* 解析完成之后,我们来看下parse(configuration)如何生成一个SQLSessionFactory
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
非常简单,直接把Configuration当做参数,直接new一个DefaultSqlSessionFactory
3)sqlSessionFactory.openSession()开启一个SqlSession
public SqlSession openSession() { //configuration.getDefaultExecutorType() = ExecutorType.SIMPLE; return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } // openSessionFromDataSource() private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); // 1.transactionFactory默认为 ManagedTransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 2.创建一个Transaction,注意该Transaction是org.apache.ibatis.transaction.Transaction // Connection即从此处获取的 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 3.创建一个Executor,默认为SimpleExecutor,参考Executor代码可知,主要的CRUD操作就是再此处完成的 final Executor executor = configuration.newExecutor(tx, execType); // 4.将Executor和Configuration做为参数封装到DefaultSqlSession,默认返回该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(); } }
总结3):该方法中有许多新的类出现,下面我们集中看下这些类,简单了解下这些类的作用,以便我们更好的理解代码
* TransactionFactory
类似于我们的SessionFactory,主要是用来生产Transaction的,注意这个Transaction是org.apache.ibatis.transaction.Transaction
public interface TransactionFactory { void setProperties(Properties props); Transaction newTransaction(Connection conn); Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); }
* org.apache.ibatis.transaction.Transaction
可以看到getConnection()方法返回的就是我们梦寐以求的java.sql.Connection,后续的操作都是基于此的
并且还有一些关于事务的操作commit、rollback等
public interface Transaction { // java.sql.Connection Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
* Executor
根据其接口方法可以看到CRUD操作在这里被实现,看来SqlSession将具体的操作都委托为Executor了
public interface Executor { ... int update(MappedStatement ms, Object parameter) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; Transaction getTransaction(); ... }
总结3:
* 解析configuration.xml文件,生成对应的Environment、Setting、Mapper,并注册到Configuration。Configuration相当于配置管理中心,所有的配置都在这里体现
* 生成org.apache.ibatis.transaction.Transaction实现类,里面我们需要的java.sql.Connection
* 根据Transaction封装Executor,Executor负责主要的CRUD操作
* 将Configuration和Executor封装到SqlSession中,SqlSession对外提供统一操作入口,内部委托为Executor来执行
4.SqlSession.selectOne()方法的执行过程
SqlSession默认实现为DefaultSqlSession
public <T> T selectOne(String statement, Object parameter) { // 直接调用了selectList()方法 List<T> list = this.<T>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; } } //selectList() public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 1.获取对应的MappedStatement // MappedStatement包装了每一个CRUD操作对应的详细信息 MappedStatement ms = configuration.getMappedStatement(statement); // 2.executor默认实现为CachingExecutor List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
注意:有关于Cache的笔者就不分析了,网络上有很多关于Cache的文章,读者可自行查看
可以看到主要的实现都委托给executor了,下面我们重点来看其query()方法的实现
5.CachingExecutor.query()
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); } //query() public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... // 重点就是这句,默认实现在BaseExecutor return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } //BaseExecutor.query() public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... 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--; } ... return list; } //queryFromDatabase() 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 { // 主要在这里,该方法是一个抽象方法,由子类实现,当前项目子类为SimpleExecutor list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } ... } //SimpleExecutor.doQuery() 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(); // 1.StatementHandler封装了JDBC Statement操作,如设置参数、将Statement结果集转换成List集合 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 2.封装preparedStatement的参数(主要功能就在这里实现) stmt = prepareStatement(handler, ms.getStatementLog()); // 3.执行execute操作 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
总结:分析可知,主要的功能实现在StatementHandler中。
* StatementHandler创建了PreparedStatement;
* 封装了其所需要的参数;
* 并且对其结果进行处理,封装为对象
下面我们逐个方法来看
1)configuration.newStatementHandler()获取StatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } //RoutingStatementHandler构造方法 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: // 我们的默认实现为这个 delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }
返回的StatementHandler为PreparedStatementHandler
2)prepareStatement(handler, ms.getStatementLog())
功能:获取Statement;封装PreparedStatement参数;
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 1.获取java.sql.Connection // Connection connection = transaction.getConnection(); Connection connection = getConnection(statementLog); // 2.获取对应的Statement stmt = handler.prepare(connection); // 3.封装参数 handler.parameterize(stmt); return stmt; }
* handler.prepare()获取Statement
在这里,最终还是通过java.sql.Connection.prepareStatement(sql)的方式来创建Statement
public Statement prepare(Connection connection) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // 重点在这里 statement = instantiateStatement(connection); setStatementTimeout(statement); setFetchSize(statement); return statement; ... } //PreparedStatementHandler.instantiateStatement() 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() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { // 采用了默认实现, // 直接对应了我们传统JDBC方式的从connection中获取PreparedStatement return connection.prepareStatement(sql); } }
* handler.parameterize(stmt)封装参数
public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } // public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 1.parameterMappings包含了需要拼装的参数 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; // 2.获取参数名称 String propertyName = parameterMapping.getProperty(); // 3.获取参数值 if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 4.获取参数对应的类型处理器 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull(); // 5.对应的封装参数操作还是要委托给TypeHandler来处理 typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } } //BaseTypeHandler.setParameter() public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { // 由于参数值非空,会走该方法 setNonNullParameter(ps, i, parameter, jdbcType); } }
本例中参数类型为Integer,则对应的typeHandler为IntegerTypeHandler,我们可以看到
// IntegerTypeHandler.setNonNullParameter() public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter); }
对应于JDBC的处理为preparedStatement.setInt(index,value)
总结2):进过详细分析,我们可以看到,最终的处理还是JDBC那一套,通过connection创建preparedStatement;对preparedStatement进行参数封装;
下面就是最终执行
3)handler.<E>query(stmt, resultHandler)执行execute操作
//PreparedStatementHandler.query() public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 1.进行execute操作 ps.execute(); // 2.对结果进行封装 return resultSetHandler.<E> handleResultSets(ps); }
preparedStatement.execute()这个是常规操作,直接进行执行操作
最后是对结果进行封装,交由resultSetHandler来操作
写这篇博客太累了,哈哈,笔者就不打算继续分析resultSetHandler的操作了,但其核心操作也会与JDBC封装结果集一致的
总结:分析下mybatis核心操作如下
* 解析configuration.xml,生成Environment、Setting、Mapper等对象,并注册到Configuration
* 从SqlSessionFactory中获取SqlSession,SqlSession作为操作入口,接收用户的数据库操作,并委托给内部的Executor来实现
* Executor内部StatementHandler负责Statement的创建;PreparedStatement参数的注入;execute方法的执行,所以,可以说重要的执行操作都在StatementHandler中
* execute方法执行过会,ResultSetHandler进行结果的封装
推荐一篇很不错的博客 https://blog.csdn.net/luanlouis/article/details/40422941
以下内容来自该博客
Mybatis的核心部件:
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
Configuration MyBatis所有的配置信息都维持在Configuration对象之中。
参考地址:https://blog.csdn.net/qq_26323323/article/details/81335058