1、mybatis原始工作代码
public static void main(String[] args) throws IOException {
//1、读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3、使用工厂生产对象
SqlSession session = factory.openSession();
//4、使用session对象创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5、使用代理对象执行方法
List users = userDao.findAll();
for (User user : users){
System.out.println(user);
}
//6、释放资源
session.close();
is.close();
}
可以看的出来
- 第一步读取一下配置文件
- 第二部就是用sqlsession来读取配置文件。
- 使用工厂生产对象
- 根据IUserDao的接口来创建代理对象,这个代理对象是不用你来写实现类的。之后直接使用即可。
这里有我的xml配置文件。
2、读取配置文件的源码分析
//1、读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2、创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
我们进入getResourceAsStream,看看是如何读取xml文件的。
/**
* Returns a resource on the classpath as a Stream object
*
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
可以从注解中看出来,返回的是一个流对象,而这个流对象其实就是你传入的xml文件,这个原理不是我们分析mybatis的重点,
因此我们就一笔带过,我们只需要知道,通过这个方法可以返回一个流对象,而这个流对象下一步就是要作为一个参数传递进sqlSessionFactory
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
进入重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//用XMLConfigBuilder这个类解析这个文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
/**
这里buildbuild(parser.parse())调用的是
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
**/
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.
}
}
}
我们发现返回的是SqlsessionFactory的对象,而这个对象又是根据build方法的,而build方法传入的参数是一个XMLConfigBuilder类型的参数。
因此我们需要首先研究如何传入这个参数,以及如何创建这个SqlsessionFactory的对象。
我们来看看XMLConfigBuilder这个类如何创建
循序渐进的进入调用的函数
//我们传进去了inputStream,其他两个参数都是null,这个是构造函数
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//进入这个函数,可以发现这个函数里进行了两个操作,一个是进行常规的构建,另一个是创建了一个文档。
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
//这是一些变量的初始化
/** validation(是否开启验证标记)
entityResolver (加载本地的DTD文件)
XPath (XPath对象)
variables(配置参数集合)
**/
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
接下去,我们就要进入最关键的一步,看他是如何根据我们的输入流来创建出文档的。
其实也就是看createDocument函数
//在源码中我们也可以看见,他给予了我们一个注释,这个函数必须要在commonconstructor之后运行,也就是我们之前说的那个变量初始化函数。
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//这里先创建一个对象,在此对象里构建属性
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//带有set的方法很明显就是设置属性值了,大家可以根据英文来明白意思,例如setIgnoringComments的意思就是是否忽略注释。
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
//当工厂的属性都搞定后,就根据这个工厂创建一个DocumentBuilder的对象
DocumentBuilder builder = factory.newDocumentBuilder();
//这里也是进行两个设置,一个是EntityResolver 解析要解析的 XML 文档中存在的实体。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。
//另一个是设置句柄,关于这两个函数,大家都可以点开这两个函数的抽象类,上面的文档用英文注释讲清楚了其的作用。
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
//最后根据这个builder会依据你的输入流返回一个Ducument文件,这里显然也是用了工厂模式,不需要你去new,而是builder帮你自动的申请空间。
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
讲了这么多的函数,是不是有点绕晕了,其实我们还没讲到正题,我们还得清楚,我们目的究竟是什么?
我们的目的其实就是要去解析XML文档,而我们已经将其转化为了Document文档了,接下去就要进入另外一个函数了。
我们已经将这个XMLConfigBuilder对象给创建出来了,其实我们也知道这个对象里面有什么东西了。接着我们就要进入这个build(parser.parse())了。
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
/**进入这个函数,注意返回的类型就是SqlSessionFactory,就是我们要用的sqlSession工厂,我们来看看它是怎么创建出来的。
我们惊讶的发现传递进去的参数是Configuration类型的参数,那我们明白了,parser.parse()返回的是一个config类型的参数,
我们首先要看看它是怎么创建出来的。
**/
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
/**
我们首先进入parser.parse()函数,我们可以看见一开始它会判断parsed这个变量是否为true,其实就是看是否分析过你这个内容了,
一开始应该是没有,我们立马变为true,之后进行分析,进入parseConfiguration函数。在此之前还要进入parser.evalNode()这个函数。
**/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//可以看到我们之前分析了很久的文档在这里排上了用处,传递了进去,我们也进入重载方法一探究竟。
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
//我们需要注意,这里的root是xml转化成的doc文档,expression是/configuration,我们需要进入evaluate来得到一个Node,然后通过这个Node初始化为一个Xnode,最后我们再返回这个Xnode即可。我们看文档可以知道Node其实就是<>
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
//我们进入实现类
//-Override-
public Object evaluate(String expression, Object item, QName returnType)
throws XPathExpressionException {
//this check is necessary before calling eval to maintain binary compatibility
//验证我们的expression是否为空,我们的参数是/configuration
requireNonNull(expression, "XPath expression");
//验证是否支持返回的类型,这里我们的参数是Node
isSupported(returnType);
try {
XObject resultObject = eval(expression, item);
return getResultAsType(resultObject, returnType);
} catch (java.lang.NullPointerException npe) {
// If VariableResolver returns null Or if we get
// NullPointerException at this stage for some other reason
// then we have to reurn XPathException
throw new XPathExpressionException (npe);
} catch (TransformerException te) {
Throwable nestedException = te.getException();
if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
throw (javax.xml.xpath.XPathFunctionException)nestedException;
} else {
// For any other exceptions we need to throw
// XPathExpressionException (as per spec)
throw new XPathExpressionException (te);
}
}
}
//我们继续深入探索eval()
/**
* Evaluate an {@code XPath} expression in the specified context.
* @param expression The XPath expression.
* @param contextItem The starting context.
* @return an XObject as the result of evaluating the expression
* @throws TransformerException if evaluating fails
*/
//在注解里我们可以看得很清晰,这个函数通过调用了Xpath的构造函数,创建了该类型
private XObject eval(String expression, Object contextItem)
throws TransformerException {
requireNonNull(expression, "XPath expression");
com.sun.org.apache.xpath.internal.XPath xpath = new com.sun.org.apache.xpath.internal.XPath(expression,
null, prefixResolver, com.sun.org.apache.xpath.internal.XPath.SELECT);
return eval(contextItem, xpath);
}
//因此我们可以理解这一系列得到的结果其实就是通过"/configuration"来得到xml文件里以为标签的内容。
parseConfiguration(parser.evalNode("/configuration"));//这一步就是分析以节点里的内容。
/**我们这个函数,然而就是最关键的一个函数了,这个函数就是解析xml文件的核心部分了,我们就不一一进入各个函数了,
但是顺着我们刚刚理的脉络,我们能够知道各个函数都在对xml的相关标签进行解析,例如第一个函数propertiesElement,
就是提取出xml文件中properties里的内容,然后对configuration类的属性进行设置。
**/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
当以上配置完成后就可以返回Config对象了,进入下面这个函数,以config为参数,新建一个SqlSession工厂,看名称就知道是默认的。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我们进入这个构造方法,这里我只截取了一小部分,大家也能看得见在这个类里面有个私有变量Configuration,我们之前传入的参数就等于这个私有变量,
这样之后我们就能用的到它了。直到这一步,我们的工厂也已经新建好了。步骤看似很复杂,但其实剖析一遍就容易理解了。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
[图片上传失败...(image-36069f-1596118699454)]
经过以上的分析,再看这张图是不是就豁然开朗了,这是在调试中的数据信息,我们可以看见工厂就是有那么一个configuration对象,而这个对象里储存的就是xml的信息,我们的信息大部分都储存在了environment里面,里面的变量都经过了解析得到了结果,这就是mybatis读取配置文件的方式。
3、SqlSession的分析
mybatis读取完了xml,返回了一个工厂,我们就要用这个工厂来进行sql语句的执行了。
//3、使用工厂生产对象
SqlSession session = factory.openSession();
//4、使用session对象创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
我们进入openSession()方法
@Override
public SqlSession openSession() {
//getDefaultExecutorType()传入的是一个Simply类型,是一个枚举的执行类型。
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//进入这个函数,注意我们传入的参数分别是执行类型是Simply,事务隔离级别是null,自动提交是否
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//一开始的executor只是一个简单类型的,但是现在是根据我们的configuration里的信息新建的一个执行器,将其作为参数,新建了一个sqlSession。
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
理解了上面的代码,我们就知道openSession()的目的其实就是将工厂里的configuration信息传递到了SqlSession对象里,这也是工厂模式的运用,
我们不再自己new Sqlsession,而是让工厂帮我们new这个对象,我们直接拿来用就可以了。参数什么的都不需要我们自己来传递,我们只需要传递
xml文件,简单的调用几个方法即可完成这一系列复杂的操作。
接着我们进入getMapper方法,我们可以看见,返回的类型是我们传进去的类型。
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
继续进入方法,这里我们会发现有一个mapperProxyFactory,这个东西得到的值其实是dao.IUserDao,也就是我们提供的接口,
要注意的是我们使用mapper接口,我们只需要提供SQL语句,而不需要使用提供实现类,原因就是mybatis使用了动态代理,
从这个工厂名字就能看得出,这一步就是动态代理。
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
直接看上述代码是有点难度的,我们需要先了解一下我们处在的是什么类,怎么样读取的sql语句,也就是mapper文件的内容。
还记得我们之前分析configuration里的以下代码吗?我们没有细细的分析每一个节点的处理,只是讲了个大概,我们现在准备细细讲讲这个mapper节点的处理,
因为我们最关键的sql语句就是在mapper里。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
我们找到这个函数 mapperElement(root.evalNode("mappers")) ,显然这个函数就是解析mapper节点的。
我们进入这个函数。
/** parent 是mappers,以下是我的mapper内容,我们会发现
resource = "dao/IUserDao.xml" ,
child =
因此我们应该进入第一个else里的第一个if。
**/
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) {
//这就是把resource作为参数保存为私有变量
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//重点就在这里,我们看看如何分析mapper文件的
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.");
}
}
}
}
//进入mapperParser.parse()
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private final Map sqlFragments;
private final String resource;
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
我们发现configuration在这里解析mapper标签,首先看下我的mapper标签的内容
我们进入这个函数,可以很明显发现我们需要解析哪些内容。
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"));
//这个是参数,我的这个mapper没有,但是我们能发现他在这里将参数存了起来。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//这个是返回的类型,他会将User给储存起来,这里我也没有统一的用resultMap,而是都在select里
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//重点来了,解析select标签的内容
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);
}
}
//进入最重要的函数,之后进入重载函数。
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
// 这个函数的作用就是,新建一个XMLStatementBuilder对象,将解析的sql语句都保存在这个对象里,最后把这个对象保存到了configuration里。
private void buildStatementFromContext(List 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);
}
}
}
注意!!!重点来了
经过以上的分析,我们可以知道 statementParser.parseStatementNode() 就是解析sql语句的函数。
我们来看看他是如何解析的
public void parseStatementNode() {
//我们的id 是 findall
String id = context.getStringAttribute("id");
//我们mapper里没有这个
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
// 这里SqlCommandType 有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH这几种命令,显然就是对数据库的操作类型,我们解析出来的是select
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//这里isSelect为true
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());
//这里得到parameter,我们的比较简单没有
String parameterType = context.getStringAttribute("parameterType");
Class parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
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);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//最后把所有的临时变量全部都储存到MappedStatement对象里。
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
好的,以上是回顾完了如何解析xml文件,接下去我们继续看这个函数,第一个函数我们已经讲得极为透彻了,我们进入其中的第三个函数
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
//出现了!出现了configuration.addMapper(boundType)
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
//我们总算进入了这个函数
//namespace就是我们提供的接口类,我们不需要提供实现类。
//我们再次进入parse()函数
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//我们注意到,knowMappers就是储存type和代理对象工厂的,type就是xml中的namespace。
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
//这里就是读取那个接口类的信息,这里用的就是反射。不用创建这个类的对象,却可以一览无遗类的信息最后将所有的信息都储存到我们的config里面
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
OK,那倒现在,所有的先决条件都已经讲清楚了,这时候再看getMapper(),思路就清晰多了。
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//进入重载
public T getMapper(Class type, SqlSession sqlSession) {
//这里就是把type给取出来了强制转化为了代理工厂
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//返回的就是我们IuserDao的实现类,这里我们需要注意,看到sqlSession,就是最一开始的openSession的对象。
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
其实讲到这里,getMapper已经讲完了,接下去就是如何调用API了,但其实我们发现分析了那么久源码没有分析sqlSession,这是因为我们没有进行
sql语句的操作就不会进入sqlSession的源码,为了之后的理解更加方便,我们直接在这里分析SqlSession
进入一个select函数
//函数很简单,就是利用的selcetList查,如果结果大于1就是错的,或者0就是错的,我们进入selectList
@Override
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
//继续进入重载函数
@Override
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
//来了来了!这里我们从configure里得到了MappedStatment,就可以进行查询操作了,可以回顾之前的分析里有写Statment里面有哪些内容。
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
//那我们就进入query来研究一下,当搜索executor的时候惊讶的发现,只有cacheExecutor在mybatis里,那意思就是说执行查询会优先进入缓存中,看是否命中
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//boundSql是一个类,打开后会发现里面有Sql语句与参数作为私有变量。
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//继续进入重载函数
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
//在缓存里就执行if,一开始显然不在缓存里,那就执行else
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List list = (List) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//执行这一步
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//因此进入这个函数
@SuppressWarnings("unchecked")
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
queryStack++;
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//由于我们没有在缓存中命中,因此我们去数据库里进行查询,走这步。
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
//进入这个查询数据库里的函数
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
//缓存其实就是类似于一个哈希表,这里因为要从数据库里查询结果,当查完结果这里会放到缓存里,而这里先加的原因是之后删去就不用再判断是否为空了。
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//重点!!!这里就是做一个真正的查询了。
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//这就是为什么要一开始执行putObject,这样就不用判断是否存在了,必定会存在这个key,因为在之前就加入过。
localCache.removeObject(key);
}
//真正的把数据存入缓存中。
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
激动人心的时刻要来临了!解析了半天的源码总算到了真正做查询操作的时候了,真可谓是千呼万唤始出来。
@Override
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//这里明显就是将参数封装到了stmt里
stmt = prepareStatement(handler, ms.getStatementLog());
//这里就是返回值,那就是将结果写入list里
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
我们先研究prepareStatement这个函数
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//有没有发现这个Connection很眼熟,我们在自己做JDBC时候也有个Connection,这里的getConnection其实就是类似于工具类的一个封装,做了很多步操作,但是都给封装了起来,最后返回的connection就是连接数据库的connection
Connection connection = getConnection(statementLog);
//把stmt给写出来
stmt = handler.prepare(connection, transaction.getTimeout());
//这里进行设置参数
handler.parameterize(stmt);
return stmt;
}
//进入重载函数
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
//这里就是将参数插入到sql语句里
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List 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 {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
到此为止,查询的sql语句已经全部完成,那接下去就是做真正的查询操作了。
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
//多么熟悉的代码啊,这不就是我们JDBC最初学的代码吗?原来即使是mybatis这么高端的技术也是合抱之木始于毫末,万丈高楼起于垒土。
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//最后返回包装的结果
return resultSetHandler.handleResultSets(ps);
}
OK!如此一来,mybatis的代码也大致清晰了,只差最后一步了,那就是mapper接口的动态代理。
4、mybatis中的动态代理
我们回到这个函数,这是在MapperRegistry里的函数,而之前已经说过
MapperRegistry这个类里有2个关键元素,一个是configuration,一个是knownMappers,这个是个哈希表储存的是namespace与动态代理工厂。
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//进入mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//进入这个方法,这个方法就是真正的动态代理,返回实现类。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
我们已经得到了那个接口类的方法,并且我们在使用mybatis的时候没有为接口做实现类,但是我们现在直接就可以用这个方法了。
我们来看看是怎么办到的。
List users = userDao.findAll();
//打断点在这个地方,然后单点进入。直接会跳到这个函数,这明显是一个调用反射的函数,说明这个invoke函数会帮我们进行操作。
@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 {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//执行这个方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
//经过一系列的跳转,这里不作演示主要是因为判断的代码量太多了,我们会发现selectList函数出来了!!这就是我们之前分析的sqlSession里执行查询的函数呀。
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
到此为止,mybatis的源码分析也差不多了,整个顺序还是有点乱的,希望大家在阅读的时候能够打开idea一步一步跟着debug来看源码。