在前面的文章中,写了一个mybatis样例执行了一次查询,大致分为四步:加载配置文件、获得session、获得Mapper对象、执行查询。
1.配置文件加载
老规矩由于配置文件加载时候用到了建造者模式,先介绍下这个设计模式。
1.1 建造者模式
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
建造者模式中有4个角色:
- Builder:抽象接口,规定要实现复杂对象组成部分和构建对象步骤,并不涉及具体的对象部件的创建
- ConcreteBuilder:Builder接口的具体实现
- Director:调用具体建造者来创建复杂对象的各个部分,保证对象各部分完整创建或按某种顺序创建
- Product:要创建的复杂对象
1.2 mybatis加载xml文件
先回看下我们在前面样例中加载配置文件的代码。
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder的build方法就是加载配置文件的入口。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
这里出现了一个新的对象XMLConfigBuilder,这个对象就是用来解析我们的mybatis-config.xml文件。
我们看下它的parse方法怎么解析。
1.3 XMLConfigBuilder
(1)parse方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//这个是说配置只能被加载一次,因为所有的配置文件最后都会转换为一个Configuration对象,这个对象全局唯一
parsed = true;
//配置文件的根节点是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
(2)parseConfiguration方法
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//sfsf
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"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
看到这些属性是不是很熟悉了,这些就是配置文件中的属性。具体怎么解析的就不分析了,使用了xml的解析器,最要的是解析出来的属性都设置到了Configuration对象,比如propertiesElement方法。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//在这里,将属性赋值给Configuration对象
configuration.setVariables(defaults);
}
}
(3)Configuration对象
上面多次提到了Configuration对象,Configuration就是将配置文件所有属性解析出来转换成的对象。我们看下它的属性。
protected Environment environment;
/* 是否启用行内嵌套语句**/
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
/* 是否启用数据组A_column自动映射到Java类中的驼峰命名的属性**/
protected boolean mapUnderscoreToCamelCase;
/*当对象使用延迟加载时 属性的加载取决于能被引用到的那些延迟属性,否则,按需加载(需要的是时候才去加载)**/
protected boolean aggressiveLazyLoading;
/*是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true **/
protected boolean multipleResultSetsEnabled = true;
/*-允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false**/
protected boolean useGeneratedKeys;
/* 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。**/
protected boolean useColumnLabel = true;
/*配置全局性的cache开关,默认为true**/
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
/* 日志打印所有的前缀 **/
protected String logPrefix;
/* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找**/
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
/* 设置本地缓存范围,session:就会有数据的共享,statement:语句范围,这样不会有数据的共享**/
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
/* 设置但JDBC类型为空时,某些驱动程序 要指定值**/
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
/* 设置触发延迟加载的方法**/
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
/* 设置驱动等待数据响应超时数**/
protected Integer defaultStatementTimeout;
/* 设置驱动返回结果数的大小**/
protected Integer defaultFetchSize;
/* 执行类型,有simple、resue及batch**/
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
/*指定 MyBatis 应如何自动映射列到字段或属性*/
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
/*MyBatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO*/
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
/*延迟加载的全局开关*/
protected boolean lazyLoadingEnabled = false;
/*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
/*插件集合*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
/*TypeHandler注册中心*/
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
/*TypeAlias注册中心*/
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//-------------------------------------------------------------
/*mapper接口的动态代理注册中心*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/*mapper文件中增删改查操作的注册中心*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
/*mapper文件中配置cache节点的 二级缓存*/
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
/*mapper文件中配置的所有resultMap对象 key为命名空间+ID*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
/*mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID*/
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
/*加载到的所有*mapper.xml文件*/
protected final Set<String> loadedResources = new HashSet<>();
/*mapper文件中配置的sql元素,key为命名空间+ID*/
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
是不是很属性,基本和我们配置文件的属性一一对应。
接下来我们看下配置文件解析中mapper接口的解析。
(4)mapperElement方法
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果子节点是package属性,那属性值就是类名
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//如果是配置的xml,用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 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.");
}
}
}
}
}
1.4 XMLMapperBuilder
XMLMapperBuilder是用来解析Mapper配置文件的。
(1)parse方法
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//从mapper节点开始解析
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
(2)configurationElement方法
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"));
//解析resultMap属性
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql属性
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete属性
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);
}
}
(3)resultMapElements方法
这个方法解析resultMap属性
private void resultMapElements(List<XNode> list) throws Exception {
//循环解析resultMap属性
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
上述方法首先就是将resultMap的每一个子节点封装成一个ResultMapping对象。
看下ResultMapping对象的属性。
private Configuration configuration;
private String property;
private String column;
private Class<?> javaType;
private JdbcType jdbcType;
private TypeHandler<?> typeHandler;
private String nestedResultMapId;
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
private boolean lazy;
最后调用resultMapResolver.resolve()方法将所有属性封装成一个resultMap对象。
(4)buildStatementFromContext方法
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。这个主要负责解析映射配置文件中的SQL节点
(5)XMLStatementBuilder的parseStatementNode方法
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))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
这个方法是将select、insert、update和delete节点封装成了MappedStatement对象。其中有个SqlSource封装了sql语句。
(6)bindMapperForNamespace
上述属性解析完后,会调用一个bindMapperForNamespace方法。这个方法会将Mapper接口放到MapperRegistry中。
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);
//将Mapper接口放到MapperRegistry中
configuration.addMapper(boundType);
}
}
}
}
看下这个addMapper方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(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);
}
}
}
}
knownMappers这个属性实际上就是注册中心,是个map对象,key是Mapper对象的class类,value是个代理工厂MapperProxyFactory。
(7)MapperRegistry
MapperRegistry接口是动态代理工厂类的注册中心,MapperProxyFactory是其实现类,用于生成动态代理的实例对象。创建实例对象方法如下:
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
mapperProxy实现InvocationHandler接口。mapperProxy接口内部有个MapperMethod,它封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息,是mapper接口与映射配置文件中sql语句的桥梁。
最后创建了DefaultSqlSessionFactory对象,将上述Configuration对象当参数传入。
总结一下,配置文件初始化实际上使用了XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder三个对象解析xml配置。最后将所有属性封装成了Configuration对象。其中比较重要的有
- MapperRegistry:mapper接口动态代理工厂类的注册中心。其中mapperProxy实现InvocationHandler接口,MapperProxyFactory用于生成动态代理的实例对象
- ResultMap:用于解析mapper.xml文件中的resultMap节点,使用ResultMapping来封装id,result等子元素
- MappedStatement:用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包含了这些节点的很多重要属性
- SqlSource:mapper.xml文件中的sql语句会被解析成SqlSource对象,经过解析SqlSource包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;
2.创建SqlSession
配置文件解析完后得到SqlSessionFactory,接着就是创建SqlSession。
SqlSession sqlSession = sqlSessionFactory.openSession();
创建SqlSession使用了策略者模式,老规矩,先介绍设计模式。
2.1 策略者模式
策略模式(Strategy Pattern)策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。
策略模式针对的问题是同一个问题有多种算法解决,由客户端自行选择。
策略模式中又三个角色:
- Context:算法调用者,使用setStrategy方法灵活的选择策略(strategy)
- Strategy:算法的统一接口
- ConcreteStrategy:算法的具体实现
2.2 创建SqlSession
(1)openSession
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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
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();
}
}
(2)newExecutor方法
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;
}
这里根据不同的配置创建不同的Executor,这就是策略模式的体现。我们一般使用的就是SimpleExecutor。最后将SimpleExecutor当做参数创建了SqlSession。
2.3 创建mapper对象
获得SqlSession后,就是使用SqlSession创建mapper对象。这里的SqlSession默认是DefaultSqlSession。
// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMa```
(1)getMapper方法
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
这里调用的是Configuration对象的getMapper方法。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
这里的mapperRegistry就是我们前面说过的注册中心。再看下注册中心的getMapper方法。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//创建代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
这里的knownMappers就是我们之前说的注册中心的Map对象,key是Map接口的class类,value就是创建Mapper接口代理对象的工厂。
再下面就是我们熟悉的JDK动态代理,这个newInstance方法之前我们也提到过,这里再看下。
(2)newInstance方法
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
这里的MapperProxy就是之前说的InvocationHandler。
所以最后返回的Mapper对象是一个动态代理对象。
2.4 执行查询语句并返回结果
获得Mapper对象后,就是执行对象的查询方法。
// 4.执行查询语句并返回结果
TUser user = mapper.selectByPrimaryKey(1);
这里的Mapper对象是个动态代理对象,执行查询方法调用的是InvocationHandler的invoke方法。
(1)invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
//如果执行的方法是Object的方法,不增强,直接执行返回
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
cachedMapperMethod方法是将MapperMethod缓存起来。
(2)cachedMapperMethod方法
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;
}
mapperMethod方法里面保存了Mapper接口中对应方法的信息,以及对应的sql语句的信息。
这里分析下mapperMethod的构造方法,因为后续会用到其中的属性。
(3)MapperMethod的构造方法
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
这里构造了两个对象SqlCommand和MethodSignature对象。
(4)SqlCommand对象
SqlCommand对象封装了方法的命名空间、方法名以及SQL语句的类型。看下其构造函数。
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//方法的名称
final String methodName = method.getName();
//方法所属类的class对象
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
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 {
//name就是mapper接口的全限定名称+"."+方法名称
name = ms.getId();
//type中就是当前方法是查询、新增、删除、还是修改
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
resolveMappedStatement就是从Configuration对象中获取MappedStatement,这个里面封装的就是加载的xml里面的mapper标签的属性。
(5)MethodSignature
再看下MethodSignature的构造方法。
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.returnsOptional = Jdk.optionalExists && Optional.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);
}
(6)mapperMethod.execute方法
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 {
//解析方法的入参,如果是多个返回Map对象
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;
}
首先判断方法执行的是查询,删除、新增还是修改,我们这里是查询。然后判断放回的返回类型,我们这里返回的是单个对象。所以调用的是selectOne方法。
(7)selectOne方法
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
这里说明下mybatis的所有查询方法最终都会调用selectList方法。
public <E> List<E> 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();
}
}
最后调用了executor.query,这里的executor就是我们之前提到的SimpleExecutor。
(8)SimpleExecutor的query方法
SimpleExecutor的query方法调用的是其父类BaseExecutor的方法,这里用的是模板模式,模板模式不多说,在之前的其他源码分析中介绍过。
(9)BaseExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//BoundSql封装的是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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//如果需要情况缓存,则情况缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//查询缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//缓存中没有,查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
//如果需要清空缓存
clearLocalCache();
}
}
return list;
}
(10)queryFromDatabase方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查询方法由子类实现
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;
}
(11)doQuery
这里调用的是子类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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
到这里是不是很熟悉了,这里就是我们之前在日志模块分析中说到的。后续就是标准的JDBC查询样板,只是日志模块使用了动态代理进行了增强。
在之前的日志模块中,我们只是对最后的将查询结果复制给对象没有分析,也就是ResultSetHandler。这个其实原理并不复杂,只是考虑的情况比较多,比如按照我们在resultMap中定义的关系进行映射,还要考虑继承的resultMap,是否开启了自动映射等等,有兴趣的可以自行去看下。
2.5 总结
整个查询主流程总结下就是:
(1)SqlSessionFactoryBuilder使用XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder三个类解析配置文件,最后将所有属性赋值给Configuration对象,最后使用Configuration对象作为构造参数创建SqlSessionFactory。
(2)使用SqlSessionFactory创建SqlSession。SqlSession内部包含了Configuration对象、以及真正执行SQL的执行器Executor。
(3)使用SqlSession创建Mapper对象,这个Mapper对象是基于JDK动态代理创建的代理对象。代理对象包含的InvocationHandler对象只中包含了SqlSession以及对应的Mapper接口的Class类。
(4)使用创建的Mapper代理对象执行查询。首先根据Mapper的class类名和方法名组装成一个statementId。然后根据这个id去Configuration判断当前语句是查询、新增或者修改、删除、接着根据方法返回类型,判断执行SqlSession中的什么方法。
也就是查询语句
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
// 4.执行查询语句并返回结果
TUser user = mapper.selectByPrimaryKey(1);
转变为了sqlSession.selectOne(“com.aaa.mapper.TUserMapper.selectByPrimaryKey”,1)。
最后由Executor执行SQL语句。接着就是标准的JDBC查询,组装查询结果返回给客户端。当然查询的时候会查询一级缓存,缓存的key是CacheKey,如果缓存有则返回,不再查询DB。这里的JDBC查询也是个动态代理,主要是增强了日志打印功能。
2.6 二级缓存
看到这里是不是有点疑惑,怎么没看到二级缓存在哪。就是在执行器查询查询的时候(executor.query),如果开启了二级缓存,这里的executor就是CachingExecutor。这个缓存执行器使用装饰器模式封装了上述执行器。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//装饰器封装的缓存,其实就是配置文件中配置使用的缓存,比如是否要使用
//BlockCache,使用什么清空策略,这个cache里面的id是命名空间
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
//这里就是二级缓存,这里根据Cache和CacheKey取值
//这里同样说明了二级缓存是根据命名空间区分,然后还是根据CacheKey取值
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果二级缓存不存在,这里就完全是上述执行过程
//先查一级缓存,一级缓存不存在,就查DB
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2.7 执行器
上述说的是一般的主流程,其实整个过程会因为执行器的不同而有所不同
(1)执行器有三种
- SimpleExecutor:默认配置,使用PrepareStatement对象访问数据库,每次访问都要创建新的PrepareStatement对象
- ReuseExecutor:使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象
- BatchExecutor:实现批量执行多条SQL语句的能力
(2)Executor执行器
Executor执行的时候用到了三个类:
- StatementHandler:它的作用是使用数据库的Statement或PrepareStatement执行操作
- ParameterHandler:对预编译的SQL语句进行参数设置,SQL语句中的的占位符“?”都对应BoundSql.parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
- ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型
(3)StatementHandler - BaseStatementHandler:所有子类的抽象父类,定义了初始化statement的操作顺序,由子类实现具体的实例化不同的statement
- SimpleStatmentHandler :使用statement对象访问数据库,无须参数化
- PreparedStatmentHandler :使用预编译PrepareStatement对象访问数据库
- CallableStatmentHandler :调用存储过程
- RoutingStatementHandler:使用静态代理模式,根据上下文决定创建哪个具体实体类