MyBatis核心流程源码分析
1.mybatis基础开发流程
1.引入mybatis相关依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
2.准备数据库文件t_user表以及数据
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(10) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of t_user -- ---------------------------- INSERT INTO `t_user` VALUES (1, 'demo1'); INSERT INTO `t_user` VALUES (2, 'demo2'); INSERT INTO `t_user` VALUES (3, 'demo3'); INSERT INTO `t_user` VALUES (4, 'demo4'); INSERT INTO `t_user` VALUES (5, 'demo5'); SET FOREIGN_KEY_CHECKS = 1;
3.创建实体类User以及UserDAO和对应的xml文件,以及mybats主配置文件
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"> <!-- mybatis的主配置文件--> <configuration> <settings> <setting name="cacheEnabled" value="true"/> </settings> <typeAliases> <typeAlias type="com.example.entity.User" alias="User"></typeAlias> <typeAlias type="com.example.entity.Account" alias="Account"></typeAlias> </typeAliases> <!-- 配置环境--> <environments default="default"> <!-- 配置mysql环境(可以配置多个环境完成多数据源场景,只需要在对应xml文件的sql标签中配置环境的id)--> <environment id="default"> <!-- 配置事务环境--> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源(连接池)--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="UserDAOMapper.xml"></mapper> </mappers> </configuration>
实体类:
package com.example.entity; import java.io.Serializable; public class User implements Serializable { private Integer id; private String name; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; } public User() { } public User(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
UserDAO.java:
package com.example.dao; import com.example.entity.User; import java.util.List; public interface UserDAO { void save(User user); List<User> queryAllUsers(); }
UserDAOMapper.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="com.example.dao.UserDAO"> <insert id="save" parameterType="User"> insert into t_user (name) values (#{name}) </insert> <select id="queryAllUsers" resultType="User" useCache="true" > select id,name from t_user </select> </mapper>
4.编写mybatis测试类
package com.example; import com.example.dao.UserDAO; import com.example.entity.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MybatisTest { /** * mybatis基础开发流程 * @throws IOException */ @Test public void test1() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserDAO userDAO = sqlSession.getMapper(UserDAO.class); List<User> users = userDAO.queryAllUsers(); for (User user : users) { System.out.println(user); } } }
5.衍生出以前老版本ibatis中sqlsession的用法
/** * sqlsession的第二种用法 * @throws IOException */ @Test public void test2() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> users = sqlSession.selectList("queryAllUsers"); for (User user : users) { System.out.println(user); } }
综上测试可知俩种方法都可以通过mybatis查询数据,但是通过 List users = sqlSession.selectList("queryAllUsers");这行代码并不能完全定位到具体执行的sql语句,因为不同的mapper.xml中是可以使用同名的方法(同一mapper.xml中方法名(id)必须唯一),所以需要在方法前加上所在类的全限定类名也就是如下代码
/** * sqlsession的第二种用法 * @throws IOException */ @Test public void test2() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> users = sqlSession.selectList("com.example.dao.UserDAO.queryAllUsers"); for (User user : users) { System.out.println(user); } }
核心代码分析
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); 第一种:List<User> users = sqlSession.selectList("queryAllUsers"); 第二种:sqlSession.selectList("com.example.dao.UserDAO.queryAllUsers"); 第一种的表达概念更清晰,能很明确知道是查询所有用户的操作(字符串表达比较模糊,体现出封装定义类型的重要性),第一种比较符合面向对象的概念 第一种本质上是对第二种的开发的封装(代理设计模式)
2.mybatis的核心对象分析
mybatis是JDBC的封装,通过SqlSession这个对象来对JDBC中Connection,Statement,ResultSet进行封装,当然mybatis的核心对象远不止这些.
1.数据存储类对象
1.数据存储类对象 概念:在java中对mybatis相关配置信息进行封装 分为: mybatis-config.xml 最终封装对象----> Configuration ***DAOMapper.xml 最终封装对象----> MappedStatement
Configuration对象
通过mybatis中Configuration的源码来对应配置文件的相关标签如下图(代码只贴出分析相关的):
public class Configuration { //对应mybatis-config中的environment标签 //public final class Environment { //private final String id; //private final TransactionFactory transactionFactory; //private final DataSource dataSource; protected Environment environment; //下面这段属性对应的正是mybatis-config.xml中setting标签中的属性,我们比较熟悉的有cacheEnabled=true(开启二级缓存) 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 String logPrefix; protected Class <? extends Log> logImpl; protected Class <? extends VFS> vfsImpl; protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected JdbcType jdbcTypeForNull = JdbcType.OTHER; protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" })); protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; }
//这几个maps中例如resultMap对应的是***DAOMapper.xml中的标签,而resultMaps则为存储所有的resultMap,他们Map的key为每个***DAOMapper.xml的namespace(也就是对应的命名空间也可以说对应DAO类的全限定类名),Mapper文件的相关内容也在Configuration中进行了汇总 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection"); protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); protected final InterceptorChain interceptorChain = new InterceptorChain(); protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); //对应mybatis-config.xml中typeAliases标签的内容 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection"); protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection"); //对应mybatis-config.xml中mappers标签中的路径 protected final Set<String> loadedResources = new HashSet<String>(); protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
由此可以知道mybatis核心类封装了mybatis-config.xml和***DAOMapper.xml中的部分配置信息,那我们继续往下看
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } 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; } public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; //可以发现mybatis核心类不仅可以封装配置文件,还负责创建了mybatis其他核心对象
总结Configuration: 1.封装了mybatis-config.xml 2.封装了mapper文件 MappedStatement 3.创建了mybatis其他核心对象
MappedStatement对象
MappedStatement对象 对应的就是mapper文中一个一个的配置标签 <select id ---> MappedStatement <insert id ---> MappedStatement 注定了一个mapper文件中有多个MappedStatement对象
下面继续分析MappedStatement相关源码看看是如何封装的mapper文件中的标签
public final class MappedStatement { private String resource; //可以得知MappedStatement也对应一个Configuration,他们的关系是对应关系,Configuration中可以找到所有的MappedStatement,而MappedStatement中也可以获取到对应的Configuration. private Configuration configuration; //这里的id不仅仅是对应mapper文件中id的方法名,而是namespace.id,这样才可以确保id的唯一性 private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; MappedStatement() { // constructor disabled } public static class Builder { private MappedStatement mappedStatement = new MappedStatement(); public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) { mappedStatement.configuration = configuration; mappedStatement.id = id; mappedStatement.sqlSource = sqlSource; //从这里可以得知myabtis底层默认创建的statement类型为PreparedStatement,通过<select statementType="PREARED">的方式可以去自己指定类型 mappedStatement.statementType = StatementType.PREPARED; mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build(); mappedStatement.resultMaps = new ArrayList<ResultMap>(); mappedStatement.sqlCommandType = sqlCommandType; mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; String logId = id; if (configuration.getLogPrefix() != null) { logId = configuration.getLogPrefix() + id; } mappedStatement.statementLog = LogFactory.getLog(logId); mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance(); }
关于标签中sql语句的封装
在MappedStatement对象中也有java对象的体现如下代码
//这个BoundSql对象就是我们sql语句的封装对象,里面一定包含一个String类型的sql语句 public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); }
BoundSql对象
/** * 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. */ package org.apache.ibatis.mapping; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.property.PropertyTokenizer; import org.apache.ibatis.session.Configuration; /** * An actual SQL String got from an {@link SqlSource} after having processed any dynamic content. * The SQL may have SQL placeholders "?" and an list (ordered) of an parameter mappings * with the additional information for each parameter (at least the property name of the input object to read * the value from). * </br> * Can also have additional parameters that are created by the dynamic language (for loops, bind...). * * @author Clinton Begin */ public class BoundSql { //这里便是我们写的sql语句 private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; private final MetaObject metaParameters; public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) { this.sql = sql; this.parameterMappings = parameterMappings; this.parameterObject = parameterObject; this.additionalParameters = new HashMap<String, Object>(); this.metaParameters = configuration.newMetaObject(additionalParameters); } public String getSql() { return sql; } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } public Object getParameterObject() { return parameterObject; } public boolean hasAdditionalParameter(String name) { String paramName = new PropertyTokenizer(name).getName(); return additionalParameters.containsKey(paramName); } public void setAdditionalParameter(String name, Object value) { metaParameters.setValue(name, value); } public Object getAdditionalParameter(String name) { return metaParameters.getValue(name); } }
BoundSql总结
MappedStatment中封装SQL语句的对象是BoundSql,里面不仅仅有sql语句还有sql语句对应的参数,类型...体现了Java面向对象的设计.
2.操作类对象
2.操作类对象 Excutor Excutor 是mybatis中处理功能的核心 增删改 查 StatmentHandler ParmeterHandler TypeHandler ...
Excutor对象
可以知道mybatis处理的核心主要是增删改查功能,带着这个想法进入Excutor的源码中查看
/** * Copyright 2009-2015 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. */ package org.apache.ibatis.executor; import java.sql.SQLException; import java.util.List; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.transaction.Transaction; /** * @author Clinton Begin */ public interface Executor { ResultHandler NO_RESULT_HANDLER = null; //提供了修改操作这里修改指的是(增删改三个操作) int update(MappedStatement ms, Object parameter) throws SQLException; //提供了查询的相关操作可以发现上面的query方法对比下面多了俩个参数cacheKey,boundSql通过参数名称可以猜测为缓存相关查询 <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; <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; List<BatchResult> flushStatements() throws SQLException; //还提供了事务相关的操作如提交和回滚 void commit(boolean required) throws SQLException; void rollback(boolean required) throws SQLException; //也提供了缓存的相关操作 CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); boolean isCached(MappedStatement ms, CacheKey key); void clearLocalCache(); void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType); Transaction getTransaction(); void close(boolean forceRollback); boolean isClosed(); void setExecutorWrapper(Executor executor); }
从上述源码得知,Executor是一个接口提供了增删改查的对应功能,还有事务和缓存的相关操作,大概已经完成了mybatis的全部功能,我们一般开发时也主要是这几个操作.那么Executor为什么会被定义成一个接口?在java语言的设计中所有的操作都会定义成接口,比如我们熟悉的service(业务操作),dao(数据库操作),这样可以方便架构进行拓展延深(哪怕这个接口只有一个实现类)例如后面看到的SqlSession也是接口.
下面看下Executor的几个实现类
常用的主要是三个分别为BatchExecutor,ReuseExecutor,SimpleExecutor,在这三个中最常用的为SimpleExecutor一个简单的执行器.
SimpleExecutor 最常用的 mybatis推荐的 默认实现类 通过Configuration中一段源码可以发现有一个defaultExecutorType的属性如果不进行配置则为 SimpleExecutor protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; ReuseExecutor 复用Statement(这里Statement是来执行sql语句的,也就是当sql语句不变的情况下(sql语句完全一样,参数也必须一样),还是使用之前的Statement对象,这样可以加快效率,这个时候会使用ReuseExecutor执行器) BatchExecutor JDBC中的批处理操作(由于数据库操作是使用操作系统资源所以可以使我们一次连接多次操作这个是数据库的批处理操作)
StatementHandler对象
StatementHandler是mybatis封装了JDBC statement,真正mybatis操作数据库的核心.为什么不直接使用Executor,还要封装StatementHandler对象(单一职责的设计),下面看看源码
/** * Copyright 2009-2016 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. */ package org.apache.ibatis.executor.statement; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.session.ResultHandler; /** * @author Clinton Begin */ public interface StatementHandler { Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; void parameterize(Statement statement) throws SQLException; //批处理操作 void batch(Statement statement) throws SQLException; //增删改操作 int update(Statement statement) throws SQLException; //查询操作 <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(Statement statement) throws SQLException; //数据操作所需要的sql语句对象 BoundSql getBoundSql(); //也需要处理参数的对象 ParameterHandler getParameterHandler(); }
StatementHandler也是一个接口里面封装了增删改查的方法,我们可以通过StatementHandler的实现类来看看具体提供了什么功能.
这里提供了3个实现类分别是存储过程,预编译,和最简单的SimpleStatementHandler.我们可以通过阅读SimpleStatementHandler的源码发现里面实现了查询的接口,而具体实现正是JDBC的常用操作如下代码:
public class SimpleStatementHandler extends BaseStatementHandler { public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); } @Override public int update(Statement statement) throws SQLException { String sql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); int rows; if (keyGenerator instanceof Jdbc3KeyGenerator) { statement.execute(sql, Statement.RETURN_GENERATED_KEYS); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else if (keyGenerator instanceof SelectKeyGenerator) { statement.execute(sql); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else { statement.execute(sql); rows = statement.getUpdateCount(); } return rows; } @Override public void batch(Statement statement) throws SQLException { String sql = boundSql.getSql(); statement.addBatch(sql); } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); //这里就是封装JDBC操作的具体体现 statement.execute(sql); //最终返回了一个返回结果处理器 return resultSetHandler.<E>handleResultSets(statement); }
ParameterHandler对象
ParameterHandler 目的:mybatis参数 ---> JDBC相关参数 mybatis注解 ---> @Parm - #{name}
ResultSetHandler对象
ResultSetHandler 目的:对JDBC的返回结果集的封装
我们还是通过源码来了解这个结果集对象
/** * Copyright 2009-2015 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. */ package org.apache.ibatis.executor.resultset; import org.apache.ibatis.cursor.Cursor; import java.sql.CallableStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.List; /** * @author Clinton Begin */ public interface ResultSetHandler { //在JDBC中必须要使用Statement才能获取ResultSet,所以必须传入Statement才能获取到 <E> List<E> handleResultSets(Statement stmt) throws SQLException; <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException; }
通过查看到这个接口的实现类DefaultResultSetHandler,可以看到源码中handleResultSets()方法的实现就是通过Statement去获取resultSet.
@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }
TypeHandler对象
TypeHandler 主要是处理java程序在操作数据库中java类型和数据库类型的转换,这里我们大概了解后续在进行详细分析实现流程
3.Mybatis核心对象如何与SqlSession建立联系
上面说了mybatis的核心对象,我们使用mybatis操作数据库使用到的仅仅只是SqlSession对象,下面我们看看SqlSession如何使用核心对象帮助我们完成这一系列操作.
我们重新回到mybatis核心代码部分
/** * mybatis基础开发流程 * @throws IOException */ @Test public void test1() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserDAO userDAO = sqlSession.getMapper(UserDAO.class); List<User> users = userDAO.queryAllUsers(); for (User user : users) { System.out.println(user); } } /** * sqlsession的第二种用法 * @throws IOException */ @Test public void test2() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> users = sqlSession.selectList("com.example.dao.UserDAO.queryAllUsers"); for (User user : users) { System.out.println(user); } }
在这里我们说过第一种方法是第二种方法的封装,那么我们先从第二种方法入手,看看SqlSession如何执行的增删改查方法.由于以下操作会追朔源码所以我先进行文字描述然后会贴上相关源码方便理解.
重新复制一个sqlsession的第二中用法
@Test public void test3() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.insert(""); }
我们通过进入sqlSession.insert()源码进行一步步分析,sqlsession是如何调用我们的mybatis核心对象的.
首先看到SqlSession的insert()方法,由于SqlSession是一个接口所以我们需要继续向下查看它的实现
/** * Execute an insert statement. * @param statement Unique identifier matching the statement to execute. * @return int The number of rows affected by the insert. */ int insert(String statement);
这里我们直接点击SqlSession默认的实现类DefaultSqlSession看到对应的insert()方法实现,会发现insert()方法是一个重载方法,而下面的重载方法是调用的是update()方法
@Override public int insert(String statement) { return insert(statement, null); } @Override public int insert(String statement, Object parameter) { return update(statement, parameter); }
然后我们进入update()方法可以看到在SqlSession中关于insert()方法的实现(下面注释已经写的很清楚,我们也可以明白SqlSession如何与executor建立联系进行调用)
//这里的statement参数之前有说过是namespace.id @Override public int update(String statement, Object parameter) { try { dirty = true; //这里是通过namespace.id获取到mapper文件中那条sql语句的标签例如<insert>...</insert> MappedStatement ms = configuration.getMappedStatement(statement); //通过executor成员变量调用executor.update()方法来进行实现 return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
下面我们点击executor.update()方法来查看它的具体实现,因为executor是一个接口,所以我们来看看他的实现,之前说过executor有三个实现类都继承了BaseExcutor,这里我们直接选择BaseExcutor查看update()方法的实现.
int update(MappedStatement ms, Object parameter) throws SQLException;
@Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); //具体执行的是这个方法 return doUpdate(ms, parameter); }
可以发现在BaseExcutor中具体执行的方法是BaseExcutor.doUpdate(),进入doUpdate()方法可以发现doUpdate是一个抽象方法会由子类进行实现那么就还是回到了我们之前说的3个常用的Executor实现类分别为
我们选择默认的SimpleExecutor中查看doUpdate()方法的具体实现,可以发现这里使用了StatementHandler执行update().这里体现了我们之前说过的StatementHandler帮助Executor去对数据库进行具体操作.
@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); //具体执行是这个方法 return handler.update(stmt); } finally { closeStatement(stmt); } }
接下来我们进入handler.update()方法看看具体实现,由于StatementHandler是一个接口我们还是需要去进入底层的实现类
int update(Statement statement) throws SQLException;
这里还是选择SimpleStatementHandler来查看update()方法的具体实现,可以发现这里SimpleStatementHandler里面对update进行我们最传统JDBC操作也体现出StatementHandler是JDBC的封装,至此我们已SimpleStatementHandler经分析完SqlSession的第二种用法是如何与我们myabtis核心对象进行调用的也就是建立联系.
@Override public int update(Statement statement) throws SQLException { String sql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); int rows; if (keyGenerator instanceof Jdbc3KeyGenerator) { statement.execute(sql, Statement.RETURN_GENERATED_KEYS); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else if (keyGenerator instanceof SelectKeyGenerator) { statement.execute(sql); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else { statement.execute(sql); rows = statement.getUpdateCount(); } return rows; }
之前说过SqlSession的用法第一种是第二种的封装,我们现在已经知道第二种用法的具体执行流程,那么如何知道第一种的呢?
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
如果看过mybatis相关面试题那么就知道myabtis这一步操作是使用了动态代理,因为UserDAO是一个接口,我们无法调用接口里面的方法.所以需要动态代理一个实现类来替我们实现UserDAO中的方法.
通过在IDEA中DeBug可以发现这里是使用了代理对象进行相关调用的.
mybatis代理核心大致流程
动态代理是一个动态字节码技术,在JVM运行过程中创建class,在jvm运行结束消失.
动态代理 a.为原始对象(目标对象) 增加额外功能 b.远程代理 1.网络代理 2.输出传输|(RPC)Dubbo c.接口实现类 我们看不到实实在在的.class文件,但是运行时却能体现出来效果(无中生有) 核心代码 Proxy.newProxyIntance(ClassLoader,Interface,InvocationHandler)
为了下面方便测试,我们先将UserDAO中的save()方法注释(对应的我们mapper文件中的save也注释掉),防止我们测试动态代理时还需要做类型的判断因为我们知道动态代理类执行的是SqlSession.insert(),这里我们就只测试SqlSession.select().
package com.example.dao; import com.example.entity.User; import java.util.List; public interface UserDAO { //先注释掉save方法 // void save(User user); List<User> queryAllUsers(); }
<?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="com.example.dao.UserDAO"> <!-- <insert id="save" parameterType="User">--> <!-- insert into t_user (name) values (#{name})--> <!-- </insert>--> <select id="queryAllUsers" resultType="User" useCache="true" > select id,name from t_user </select> </mapper>
开始编写代理测试类
/** * 用于测试代理 * @throws IOException */ @Test public void test4() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Class[] classes = {UserDAO.class}; //这一块的Proxy.newProxyInstance(),第一个参数是类加载器,第二是目标代理类数组,第三个InvocationHandler是一个接口我们去创建一个它的实现类来实现相关方法 UserDAO userDAO = Proxy.newProxyInstance(MybatisTest.class.getClassLoader(),classes, InvocationHandler); List<User> users = userDAO.queryAllUsers(); for (User user : users) { System.out.println(user); } }
创建MyMapperProxy实现InvocationHandler,然后分析我们该如何实现invoke()方法
package com.example.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyMapperProxy implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
代理类最终执行的方法是sqlSession.selectList()方法,我们需要将SqlSession作为成员变量在创建代理对象时作为参数传入.而sqlSession.selectList()方法的参数需要一个namespace.id(例如:com.example.dao.UserDAO.queryAllUsers),我们可以通过method.getName()来获取id(也就是最终执行的方法名),通过获取目标对象的全限定类名来获取前面部分.所以我们将目标类也作为参数传入构造方法中通过daoClass.getName()通过字符串拼接获得最终的namespace.id(也就是最终的方法)
package com.example.proxy; import org.apache.ibatis.session.SqlSession; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyMapperProxy implements InvocationHandler { private SqlSession sqlSession; private Class daoClass; //将需要的sqlSession和daoClass作为成员变量,后面方便去进行操作和获取内容 public MyMapperProxy(SqlSession sqlSession, Class daoClass) { this.sqlSession = sqlSession; this.daoClass = daoClass; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return sqlSession.selectList(daoClass.getName()+"."+method.getName()); } }
这样我们的代理类就已经完成了,回到测试类将之前的Proxy.newProxyInstance()的第三个参数替换为我们的代理类对象,就可以看到打印结果和之前一样,也打印出了具体执行的查询方法.
/** * 用于测试代理 * @throws IOException */ @Test public void test4() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Class[] classes = {UserDAO.class}; UserDAO userDAO = (UserDAO) Proxy.newProxyInstance(MybatisTest.class.getClassLoader(),classes,new MyMapperProxy(sqlSession,UserDAO.class)); List<User> users = userDAO.queryAllUsers(); for (User user : users) { System.out.println(user); } }
但是myabtis对于invoke()方法的实现远比我们设计的复杂多的多,因为我们这个实现类中只有一个selectList(),而没有涉及到其他类型的查询,但是大致流程跟以上是一样的.
mybatis代理源码分析
俩个核心对象帮我们去创建了DAO接口的实现类 MapperProxyFactory对应的就是我们 Proxy.newProxyInstance(); MapperProxy 对应invoke()方法去具体执行我们的增删改查操作
下面我们去进入MapperProxyFactory源码中分析
/** * Copyright 2009-2015 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. */ package org.apache.ibatis.binding; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.ibatis.session.SqlSession; /** * @author Lasse Voss */ public class MapperProxyFactory<T> { //目标类接口.class private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } //跟我们之前写的代理基本上一致,类加载器我们使用的只不过是测试类的类加载器,这里是使用目标接口的类加载器 @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } //这里通过创建invoke()方法的实现也就是MapperProxy,通过newInstance()的重载将mapperProxy传入上面的重载方法作为参数来进行代理,而mapperProxy的创建也跟我们一样需要SqlSession来进行增删改查操作,需要mapperInterface来过去接口方法的namespace.id. public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
可以发现这块跟我们设计的代理流程基本上是一致的,因为我们这里肯定是考虑用更简单的流程能够清晰的看出这块的代理自然不会去设计复杂的流程,下面再看看MapperProxy中源码和我们设计的又有什么区别
/** * 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. */ package org.apache.ibatis.binding; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; import org.apache.ibatis.lang.UsesJava7; import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.session.SqlSession; /** * @author Clinton Begin * @author Eduardo Macarron */ public class MapperProxy<T> implements InvocationHandler, Serializable { //可以发现MapperProxy跟我们设计的MyMapperProxy基本一直,实现了InvocationHandler,提供了SqlSession,Class成员变量,然后实现了invoke()方法 private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } @UsesJava7 private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable { final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class .getDeclaredConstructor(Class.class, int.class); if (!constructor.isAccessible()) { constructor.setAccessible(true); } final Class<?> declaringClass = method.getDeclaringClass(); return constructor .newInstance(declaringClass, MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args); } /** * Backport of java.lang.reflect.Method#isDefault() */ private boolean isDefaultMethod(Method method) { return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC && method.getDeclaringClass().isInterface(); } }
这块我们主要来分析下源码中的invoke()方法
然后我们看下这一步final MapperMethod mapperMethod = cachedMapperMethod(method);具体是在做什么操作,直接进入cachedMapperMethod()方法的源码来查看下
private MapperMethod cachedMapperMethod(Method method) { //从methodCache中获取是否存在这个方法,存在直接返回mapperMethod,不存在则需要创建一个MapperMethod,然后put进methodCache MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
我们直接进入MapperMethod()的这个构造方法来进行分析
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; //可以发现参数中有Configuration,也就是说这里可以获取到所有mybatis相关配置包括myabtis的statement对象,然后可以发现这里有俩个成员变量一个是SqlCommand,一个是MethodSignature,为什么需要将method去包装成MapperMethod呢 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } ... }
我们看看为什么需要上面这俩个对象,new SqlCommand(config, mapperInterface, method)中包装了myabtis的所有配置,DAO接口和DAO接口的方法.下面我们进入SqlCommand的源码中看看SqlCommand具体是做什么的
public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); //可以发现这一块if的代码是为了安全起见进行的判断,因为我们一般使用myabtis不可能会出现MappedStatement为空的情况,所以SqlCommand的核心代码肯定是在else里面 if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { //这里获取了MappedStatement的id也就是我们的namespace.id,然后获取了sql的类型(update,insert,select,delete),这里猜测是因为最终是执行sqlsession中的select(),insert()...方法才进行的判断 name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } }
下面看看MethodSignature对象是具体在做什么,通过上面的成员变量可以发现这个类是处理和判断返回值和参数的,返回值可以发现主要是通过我们之前说的核心对象Configuration中获取mapper文件中的resultTpye等标签来进行判断,我们主要看看ParamNameResolver是如何进行参数处理的.
public static class MethodSignature { private final boolean returnsMany; //返回值类型是否是Map private final boolean returnsMap; //是否没有返回值 private final boolean returnsVoid; private final boolean returnsCursor; //返回值类型 private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; //分页参数 private final Integer rowBoundsIndex; //参数 private final ParamNameResolver paramNameResolver; public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration, method); } }
ParamNameResolver
public ParamNameResolver(Configuration config, Method method) { final Class<?>[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<Integer, String>(); int paramCount = paramAnnotations.length; // get names from @Param annotations for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; //可以发现这一块是通过@Param注解来获取到mybatis中执行的方法参数 for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } if (name == null) { // @Param was not specified. if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); }
最后我们回到下面这行代码,可以发现上面这部是在对mapper方法中的sql类型,返回值类型以及参数进行操作,然后就可以放心的使用sqlSession进行数据库操作了.我们之前因为方便直接使用了selssion.selectList()进行的操作(只有这一种),而源码中使用了mapperMethod.execute()方法,我们来看看mybatis源码中具体是如何进行的操作
final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { 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: //通过之前MethodSignature的处理可以进行判断如果方法没有返回值result=null,如果返回值多个调用executeForMany(),为map调用executeForMap,一个调用sqlSession.selectOne 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); result = sqlSession.selectOne(command.getName(), param); } 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; }
至此,上方红框的代码我们就基本分析完毕了,也就是如下图我们总结出来的执行流程.
4.mybatis读取配置文件(Configuration的封装)
上面我们以及总结了关于myabtsi中关于sql语句的执行,下面我们来看看下面代码在mybatis中是如何去进行操作的.
首先第一步是通过IO流的方式去读取我们的配置文件,这个应该是我们很熟悉的了.然后我们之前说过Configuration对象是对应了mybatis-config.xml中的标签属性,那么具体是如何将myabtis-config.xml封装成Configuration对象的.关于java中XML的解析方式mybatis这里采用的是XPath(因为这种方式比较简单),下图也大概介绍了读取xml文件的一个大致流程.
我们通过上图得知最终xml文件中的标签属性最终会读取到一个叫XNode的对象中,我们直接进入XNode的源码来分析一下.
public class XNode { private final Node node; //这里是标签名称如setting private final String name; //这里是标签内容 private final String body; //这里是标签的属性 private final Properties attributes; private final Properties variables; private final XPathParser xpathParser; public XNode(XPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables; this.attributes = parseAttributes(node); this.body = parseBody(node); } public XNode newXNode(Node node) { return new XNode(xpathParser, node, variables); } public XNode getParent() { Node parent = node.getParentNode(); if (parent == null || !(parent instanceof Element)) { return null; } else { return new XNode(xpathParser, parent, variables); } }
通过源码可以得知XPathParser.evalNode()返回的一个List对象可以获取一个标签内所有的标签内容,进而可以达到封装成java对象(mybatis中封装为Configuration类).我们只需要将输入流作为参数传入XPathParser()的构造方法中就可以获取到封装好的java对象.那么这一步操作很明显是在build()方法中去执行的,如下代码
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); //这里将inputStream传入build()方法中,所以推测build()方法中执行了将xml配置文件封装为Configuration对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
现在目的很明确,我们直接进入build()源码中进行分析
public SqlSessionFactory build(InputStream inputStream) { //进入的是这个build()方法,然后对应到下面的重载方法 return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment) { return build(inputStream, environment, null); } public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //这个XMLConfigBuilder就是封装的我们刚刚说的XPathParser对象,最终也是执行的parser.parse()方法 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 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. } } }
这里我还是用源码看看XMLConfigBuilder是不是封装了XPathParser
public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; //这里可以很明确看到封装为成员变量的XPathParser private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); public XMLConfigBuilder(Reader reader) { this(reader, null, null); }
这样我们就直接进入之前说的parser.parse()方法中看看是不是读取了标签的所有内容.
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //parser.evalNode("/configuration")可以明确看到这里已经读取到了configuration标签下的所有XNode节点,因为这个方法返回的是一个List<XNode>类型,那么我们可以猜测parseConfiguration()方法应该是在将节点的内容封装成一个Configuration对象,因为这个parse()方法的返回值是一个configuration对象. parseConfiguration(parser.evalNode("/configuration")); return configuration; }
我们继续看看parseConfiguration是不是解析了configuration标签下的所有内容,对应mybatis-config.xml的配置都被解析出来了.
//可以发现mybatis-config.xml中所有的标签都在这个方法中进行了解析 private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(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")); typeHandlerElement(root.evalNode("typeHandlers")); //这里读取的是mappers标签下的mapper标签内容 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
我们进入mapperElement()这个方法看看mapper.xml文件中的标签是否封装成了之前说的mappedStatement对象
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 { //这里获取了mapper标签的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) { ErrorContext.instance().resource(resource); //然后通过IO流去读取这个mapper.xml文件,还是通过XMLMapperBuilder去口模型解析 InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //可以发现这里跟解析mybatis-config.xml文件的方式是一样的,我们继续进入mapperParser.parse()方法 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."); } } } } }
看看parse()方法中的操作是不是跟我们读取mybatis-config.xml文件方式差不多
public void parse() { if (!configuration.isResourceLoaded(resource)) { //可以发现这里读取了mapper标签下的所有内容,这个configurationElement()方法应该也是读取mapper标签里面的内容 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
进入configurationElement()方法
private void configurationElement(XNode context) { try { //解析mapper.xml中mapper标签下的所有内容 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); //这个buildStatementFromContext可能是进行MappedStatement封装的方法我们继续进入 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
进入buildStatementFromContext()方法
private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { //继续进入这个重载的方法 buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //推测这个statementParser.parseStatementNode()方法就是封装MappedStatement对象的方法继续进入 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
进入statementParser.parseStatementNode()方法,可以发现这个方法将mapper标签中所有标签的内容全部取了出来作为builderAssistant.addMappedStatement()方法的参数
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 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 Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } //将上述获取的mapper标签中所有的标签内容作为参数,去添加一个MappedStatement. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
进入这个builderAssistant.addMappedStatement()方法看看
public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //这里使用了建造者设计模式,创建一个statementBuilder MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } //调用build()方法创建了MappedStatement对象并且最后将MappedStatement对象存入configuration中 MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }
5.构建SqlSessionFactory
至此,configuration对象已经被封装完毕,我们继续回到最开始的地方.MappedStatement通过建造者模式build()方法创建,而我们之前说到,configuration对象的封装也是在new SqlSessionFactoryBuilder().build(inputStream)中进行的,那么它一定也进行SqlSessionFactory的构建,我们重新进入build()方法看看.
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession();
//中间的重载方法我这里就直接跳过了,我们直接看这个具体执行的方法 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //可以很明确看到其实最终返回的是这个build()方法而parser.parse()是解析配置文件后封装的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. } } }
我们直接进入build()看看是否为我们创建了SqlSessionFactory对象
public SqlSessionFactory build(Configuration config) { //最终返回了DefaultSqlSessionFactory实现类 return new DefaultSqlSessionFactory(config); }
6.SqlSession的创建
回到我们之前写的测试类代码,看看SqlSession创建需要做什么操作,这里我们进入openSession()方法
SqlSession sqlSession = sqlSessionFactory.openSession();
可以发现我们需要去看看openSession()方法的实现,由于我们已经知道最终build()方法返回了一个DefaultSqlSessionFactory,直接进入DefaultSqlSessionFactory的openSession().
@Override public SqlSession openSession() { //从这里的执行器类型参数可以推测最终SqlSession还是由Executor去进行执行的 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
我们进入openSessionFromDataSource()方法中
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //通过configuration获取到环境对象(包括数据库连接配置等) final Environment environment = configuration.getEnvironment(); //获取事务相关内容 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //创建执行器 final Executor executor = configuration.newExecutor(tx, execType); //进行返回 return new ````(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(); } }
至此我们测试类中上面获取配置文件,创建SqlSession流程也分析完毕,下面放上一张总结图
至此mybatis执行流程已经全部分析完毕