MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
MyBatis并不是一个完整的ORM框架,MyBatis只完成的是Relation到Object的映射,也就是其所说的data mapper framework,旨在更简单、更方便地完成数据库操作功能
核心类
Configuration
Configuration是MyBatis中相当重要的一个类,可以提供许多配置,主要包括了mybatis-configuration.xml基础配置文件和mapper.xml映射器配置文件。在在spring提供的org.mybatis.spring.SqlSessionFactoryBean中,可以配置configLocation提供配置路径,基础配置文件中可以提供properties全局参数、setting设置、typeAliases别名、typeHandlers类型处理器、Mapper映射器。mapper.xml可以在org.mybatis.spring.mapper.MapperScannerConfigurer中定义扫包路径,也可以通过mybatis-configuration.xml中的Mapper映射器定义。其中mybatis-configuration.xml是在XMLConfigBuilder类中完成解析的;而mapper.xml在XMLMapperBuilder中解析完成,其中把对Statement的解析(即mapper.xml中SELECT、INSERT、UPDATE、DELETE定义部分)委托给XMLStatementBuilder来完成
Configuration与DefaultSqlSessionFactory是1:1的关联关系,这也就意味着在一个DefaultSqlSessionFactory衍生出来的所有SqlSession作用域里,Configuration对象是全局唯一的:
public Configuration(Environment environment) {
this();
this.environment = environment;
}
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
SqlSessionFactory
每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为中心的。SqlSessionFactoryBuilder可以从XML配置文件或一个预先定制的Configuration的实例中构建出SqlSessionFactory的实例
看一下SqlSessionFactoryBuilder源码:
// 这里使用了Reader,或另一种使用InputStream;environment指定环境;properties指定配置
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 封装配置文件相关信息: XPathParser + environment + properties
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置信息,封装为Configuration,创建DefaultSqlSessionFactory
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
可知详细的解析工作为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;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析properties元素
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
// 解析environments元素
environmentsElement(root.evalNode("environments"));
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);
}
}
像平时项目里配置的org.mybatis.spring.SqlSessionFactoryBean,内部也是通过SqlSessionFactoryBuilder的这种方式创建的,只不过Configuration的创建不同
SqlSession
SqlSession完全包含了面向数据库执行SQL命令所需的所有方法,可以通过SqlSession实例来直接执行已映射的SQL语句。可以通过SqlSessionFactory获得SqlSession的实例
查看DefaultSqlSessionFactory中最基本的openSession()方法获取SqlSession:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取Environment配置信息,包含了数据源和事务的配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 内部实际上是通过excutor执行SQL的,Executor是对于Statement的封装
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象
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();
}
}
MapperProxy
当我们在项目里配置好mapper和dao后,调用到自己dao里的方法就去执行相应的SQL了,这其实是对应的MapperProxy在代理
获取MapperProxy:
// SqlSession
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry
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 {
// ----> 最后还是通过MapperProxyFactory去创建实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 动态代理我们创建的dao接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Executor
那么既然是MapperProxy在代理我们的dao,那么是怎么执行到到SQL语句的?那么先从MapperProxy的Invoke方法来看看:
// MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Object 或 Default方法
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);
}
// 从methodCache获取,如果不存在创建一个new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod ----> 还是调用SqlSession的方法
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:
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);
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;
}
// DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取
MappedStatement ms = configuration.getMappedStatement(statement);
// 实际还是Executor的方法,CURD最后到Executor就是调用它的update和query方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// Configuration id, true
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
然后看看Executor后续的步骤:
// BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 得到绑定sql
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
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.");
}
// 查询堆栈为0
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 清理本地缓存
clearLocalCache();
}
List<E> list;
try {
// queryStack递增,递归调用时上面不会再清理缓存
queryStack++;
// 先尝试从localCache去获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 如果查到localCache缓存,处理localOutputParameterCache
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 还是到DB去查询
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
// 如果是statement,清本地缓存
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 {
// ----> 实际调用子类方法
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;
}
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);
}
BaseExecutor有3个实现doxxx的子类:SimpleExecutor、BatchExecutor、ReuseExecutor,那么用哪个呢?在DefaultSqlSessionFactory的openSession方法中可以指定ExecutorType,否则使用defaultExecutorType:
// Configuration
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE
// SimpleExecutor
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
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 使用prepareStatement
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用query方法,或update方法
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// 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());
}
}
那么statementType是什么?通过参数往上找,发现是在XMLConfigBuilder的parseConfiguration方法里的mapperElement(root.evalNode("mappers"))
开始,通过解析mapper的配置得到的,默认使用StatementType.PREPARED:
// XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
// -> ....
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
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(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
那么看下PreparedStatementHandler是怎么执行的:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// jdbc的PreparedStatement执行
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
总结:
常见问题
#与$的区别
#{}
: 解析为一个JDBC预编译语句(prepared statement)的参数标记符,一个#{}
被解析为一个参数占位符
${}
: 仅仅为一个纯碎的String替换,在动态SQL解析阶段将会进行变量替换
从前面可以看出,在Executor执行doxxx时,需要创建StatementHandler,里面会通过RoutingStatementHandler来路由选择SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler,分别对应了JDBC中的Statement、PreparedStatement和CallableStatement。看到PreparedStatement里面有个parameterize方法对statement参数化设置:
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
那么是不是用${}
就是用了Statement,还是都使用默认PreparedStatement,在设置参数时和#{}
区分的呢?之前知道创建SqlSessionFactory时候会解析配置信息,在XMLConfigBuilder总解析方法parseConfiguration里,会在mapperElement这步骤创建XMLMapperBuilder来解析mapper映射解析,
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 {
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);
InputStream inputStream = Resources.getResourceAsStream(resource);
// ----> XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
// ----> XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
// ----> MapperAnnotationBuilder
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
在XMLMapperBuilder中根据节点建立statement
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
// XMLStatementBuilder
public void parseStatementNode() {
// ...
String lang = context.getStringAttribute("lang");
// 默认是XMLLanguageDriver.class
LanguageDriver langDriver = getLanguageDriver(lang);
// ...
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");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
最后那个builderAssistant.addMappedStatement是不是很熟悉,前面讲的通过statementType判断就是这里设置的。那么sql解析在哪里呢?其实是在SqlSource中进行的,那么来看langDriver.createSqlSource方法:
// XMLLanguageDriver
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
// XMLScriptBuilder
public SqlSource parseScriptNode() {
// 判断
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// CDATA 或 文本
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// ----> 判断
if (textSqlNode.isDynamic()) {
// 添加TextSqlNode
contents.add(textSqlNode);
isDynamic = true;
} else {
// 添加StaticTextSqlNode
contents.add(new StaticTextSqlNode(data));
}
// 元素节点,用NodeHandler处理各个类型的子节点
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
// 有TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
在TextSqlNode类中创建GenericTokenParser去解析文本:
public boolean isDynamic() {
// DynamicCheckerTokenParser
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
// 创建GenericTokenParser去解析
GenericTokenParser parser = createParser(checker);
// 解析文本,会调用TokenHandler(checker)的handleToken将isDynamic设为true
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
// 通过 ${ 和 } 截取判断
return new GenericTokenParser("${", "}", handler);
}
那么如果有${}
设为true,创建DynamicSqlSource:
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
那么如果没有${}就会创建RawSqlSource:
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 解析
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
// SqlSourceBuilder
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 解析#{},使用ParameterMappingTokenHandler
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// ----> 解析
String sql = parser.parse(originalSql);
// 创建StaticSqlSource,所以RawSqlSource内部持有一个SqlSource,最后将?替换参数放入parameterMappings中
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这边都用到了GenericTokenParser的parse方法:
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// ----> 调用handler的handleToken方法
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
// 对于#{}是用的ParameterMappingTokenHandler
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
很显然,对于#{}
的会使用?占位符,但是如果对于${}
和#{}
都有的SQL来说,DynamicSqlSource不是啥都没做么。先不急,当拿到SqlSource后会创建MappedStatement.Builder,其中持有SqlSource,最后返回MappedStatement。这个类是不是很熟悉,正是在DefaultSqlSession调用select、update等方法时候需要获取的:
// BaseExecutor
BoundSql boundSql = ms.getBoundSql(parameter);
// MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
// 调用getBoundSql方法
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
// 1. RawSqlSource
public BoundSql getBoundSql(Object parameterObject) {
// 之前说过,内部持有的是StaticSqlSource
return sqlSource.getBoundSql(parameterObject);
}
// StaticSqlSource
public BoundSql getBoundSql(Object parameterObject) {
// 很简单,因为都处理过了
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
// 2. DynamicSqlSource
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
// ----> 实际调用的是TextSqlNode的applay方法,将会替换${}
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// ----> 解析#{},和之前的一样
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 全部加入metaParameters
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
// TextSqlNode
public boolean apply(DynamicContext context) {
// GenericTokenParser("${", "}", handler),使用BindingTokenParser处理${}
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
// 调用GenericTokenParser.parse方法,之前讲过,这样就进入BindingTokenParser的handleToken方法
context.appendSql(parser.parse(text));
return true;
}
// TextSqlNode,里面应该就是对${}进行直接参数替换
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
// 通过OGNL获取,返回直接append到SQL中
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
checkInjection(srtValue);
return srtValue;
}
// SqlSourceBuilder,由于有可能同时有#{}的情况,因此还要处理,详细就和之前的一样了
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// 对#{}的GenericTokenParser
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这样就拿到了boundSql,其在创建cacheKey的时候会用到,还会一直传递到SimpleExecutor的比如doQuery方法,之前没有分析是怎么创建Statement的,现在来看一下:
// 获取Configuration
Configuration configuration = ms.getConfiguration();
// 先会创建StatementHandler,即PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建
stmt = prepareStatement(handler, ms.getStatementLog());
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// JDBC步骤 获取Connection
Connection connection = getConnection(statementLog);
// 准备,根据Connection创建prepareStatement(因为是PreparedStatementHandler),设置超时时间
stmt = handler.prepare(connection, transaction.getTimeout());
// ----> 确认参数
handler.parameterize(stmt);
return stmt;
}
// PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
// DefaultParameterHandler
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 获取参数
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;
// 属性名
String propertyName = parameterMapping.getProperty();
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);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 设入PreparedStatement中
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
之后就如前面分析的,调用PreparedStatement的execute()方法
返回怎么绑定对象
之前看了这么请求数据库的,那么Mybatis完成Relation到Object的映射,是怎么操作的呢?通过PreparedStatementHandler的query方法可以看出,最后返回的是resultSetHandler.handleResultSets(ps),使用的是ResultSetHandler:
// DefaultResultSetHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象,通常只会有一个
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 从配置中读取对应的ResultMap,是将jdbc类型数据映射为DTO对象的重要类
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 校验,当查询返回的ResultMap数量小于1时,说明没有找到对应的ResultType或者ResultMap可以将结果映射出来
validateResultMapsCount(rsw, resultMapCount);
// 一个Statement执行了多个sql语句,需要循环处理每个结果集
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// ----> 完成映射,将结果加到入multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// 子映射
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// ----> 处理行信息
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 处理行信息
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 存在结果嵌套,有子映射或内映射的情况
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// ----> 非嵌套
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 跳过offset指定的记录条数
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// discriminator的处理,可以根据条件选择不同的映射
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// ----> 数据columnName与对象propertyName的映射匹配
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 保存当前解析的对象,加入resultHandler.resultList中
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 使用反射创建对应的DTO对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 拿到列信息
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 自动映射,根据columnName和type属性名映射赋值
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// ----> 调用setter方法设置信息,根据我们配置ResultMap的column和property映射赋值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
// ----> 数据转换
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
// ----> 使用了setter方法设置值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
// TypeHandler转换器
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
// 数据库表的列名
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
大体如上代码,为每个结果生成一个java对象,根据构造方法实例化,自动映射、根据ResultMap映射,里面会用到TypeHandler转换。这只是简单映射,还有一个复杂嵌套映射handleRowValuesForNestedResultMap方法,具体没看,以后补充吧
缓存
MyBatis提供了一级缓存和二级缓存的支持:
1、一级缓存: 基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的。当Session flush或close之后,该Session中的所有Cache就将清空
2、二级缓存:与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。并且可自定义存储源,如Ehcache等
注意:对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有select中的缓存将被clear
缓存键CacheKey的格式为:cacheKey = ID + offset + limit + sql + parameterValues + environmentId
通常可能会使用默认的一级缓存;不会开启使用二级缓存,因为是Mapper(namespace)级别的,还容易掉坑。现在实际上都会使用Memcache、Redis进行缓存结果,所以项目上不需要。由于平时没怎么用过Mybatis的缓存,所以扩展看下:
mybatis一级缓存二级缓存
你真的会用Mybatis的缓存么,不知道原理的话,容易踩坑哦
Scope
SqlSessionFactory:可以认为是一个数据库连接池,一个应用连接几个数据库就有几个,但都该是单例,所以最佳作用域是应用作用域
SqlSession:相当于数据库的一个连接,是线程级别的。由于SqlSession是非线程安全的,不能被共享,所以最佳作用域是请求或方法作用域
Mapper:代表SqlSession中的一个数据库请求步骤,从SqlSession中获得,所以肯定小于等于SqlSession的作用域与生命周期,最佳作用域是方法作用域
namespace
命名空间,用于绑定我们写的dao接口,唯一性,使用完全包名+文件名绑定。而dao里的方法名绑定对应mapper里的sql id名。通过接口全限名+方法名可以唯一定位一个MappedStatement,因此dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略
扩展:
mybatis 3.x源码深度解析与最佳实践
Mybatis使用的设计模式