Mybatis分层框架图
MyBatis的执行流程及核心组件如下图所示。
这些组件的作用如下:
- Configuration:用于描述MyBatis的主配置信息,其他组件需要获取配置信息时,直接通过Configuration对象获取。除此之外,MyBatis在应用启动时,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息时,也可以从Configuration对象中获取。
- MappedStatement:MappedStatement用于描述Mapper中的SQL配置信息,是对Mapper XML配置文件中<select|update|delete|insert>等标签或者@Select/@Update等注解配置信息的封装。
- SqlSession:SqlSession是MyBatis提供的面向用户的API,表示和数据库交互时的会话对象,用于完成数据库的增删改查功能。SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的数据库操作接口。
- Executor:Executor是MyBatis的SQL执行器,MyBatis中对数据库所有的增删改查操作都是由Executor组件完成的。
- StatementHandler:StatementHandler封装了对JDBC Statement对象的操作,比如为Statement对象设置参数,调用Statement接口提供的方法与数据库交互等等。
- ParameterHandler:当MyBatis框架使用的Statement类型为CallableStatement和PreparedStatement时,ParameterHandler用于为Statement对象参数占位符设置值。
- ResultSetHandler:ResultSetHandler封装了对JDBC中的ResultSet对象操作,当执行SQL类型为SELECT语句时,ResultSetHandler用于将查询结果转换成Java对象。
- TypeHandler:TypeHandler是MyBatis中的类型处理器,用于处理Java类型与JDBC类型之间的映射。它的作用主要体现在能够根据Java类型调用PreparedStatement或CallableStatement对象对应的setXXX()方法为Statement对象设置值,而且能够根据Java类型调用ResultSet对象对应的getXXX()获取SQL执行结果。
了解了MyBatis的核心组件后,我们再来了解一下使用MyBatis操作数据库的过程。实际上SqlSession是Executor组件的外观,目的是为用户提供更友好的数据库操作接口,这是设计模式中外观模式的典型应用。真正执行SQL操作的是Executor组件,Executor可以理解为SQL执行器,它会使用StatementHandler组件对JDBC的Statement对象进行操作。当Statement类型为CallableStatement和PreparedStatement时,会通过ParameterHandler组件为参数占位符赋值。ParameterHandler组件中会根据Java类型找到对应的TypeHandler对象,TypeHandler中会通过Statement对象提供的setXXX()方法(例如setString()方法)为Statement对象中的参数占位符设置值。StatementHandler组件使用JDBC中的Statement对象与数据库完成交互后,当SQL语句类型为SELECT时,MyBatis通过ResultSetHandler组件从Statement对象中获取ResultSet对象,然后将ResultSet对象转换为Java对象。
Configuration详解
MyBatis框架的配置信息有两种,一种是配置MyBatis框架属性的主配置文件;另一种是配置执行SQL语句的Mapper配置文件。Configuration的作用是描述MyBatis主配置文件的信息。Configuration类中定义了一系列的属性用来控制MyBatis运行时的行为,这些属性代码如下:
public class Configuration {
protected Environment environment;
// 允许在嵌套语句中使用分页(RowBounds),如果允许
// 使用,则设置为true,否则设置为false,默认 false
protected boolean safeRowBoundsEnabled;
// 允许在嵌套语句中使用分页(ResultHandler),如果允许
// 使用,则设置为true,否则设置为false,默认true
protected boolean safeResultHandlerEnabled = true;
// 是否开启自动驼峰命名规则映射,即从经典数据库列名A_COLUMN
// 到经典 Java 属性名 aColumn 的映射,默认 false
protected boolean mapUnderscoreToCamelCase;
// 当开启时,任何方法的调用都会加载该对象的所有属性。
// 否则,每个属性会按需加载,默认 false,3.4.1 之前默认为 true
protected boolean aggressiveLazyLoading;
// 是否允许单一语句返回多结果集,默认 true
protected boolean multipleResultSetsEnabled = true;
// 允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置
// 强制使用自动生成主键,尽管一些驱动不能兼容,但仍可正常工作,默认 false
protected boolean useGeneratedKeys;
// 使用列标签代替列名,不同的驱动在这方面会有不同的表现,默认 true
protected boolean useColumnLabel = true;
// 是否开启Mapper缓存,即二级缓存,默认 true
protected boolean cacheEnabled = true;
// 指定当结果集中的值为 null 的时候是否调用映射对象的Setter方法,
// 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
// 注意基本类型(int、boolean 等)是不能设置成 null 的,默认 false
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
// 当返回行的所有列都是空时,MyBatis 默认返回null。当开启这个设置时,MyBatis会返回
// 一个空实例。请注意,也适用于嵌套的结果集(collection、association),默认 false
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
protected boolean nullableOnForEach;
protected boolean argNameBasedConstructorAutoMapping;
// 指定 MyBatis 增加到日志名称的前缀,默认未设置
protected String logPrefix;
// 指定MyBatis 所用日志的具体实现,未指定时将自动查找,可设置 SLF4J、LOG4J等,默认未设置
protected Class<? extends Log> logImpl;
// 指定 VFS 的实现,参数我 VFS 的实现类的全限定名,以逗号分隔,默认未设置
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
// MyBatis利用本地缓存机制防止循环引用和加速重复查询。默认值为SESSION,
// 这种情况下会缓存个会话中执行的所有查询。若设置值为STATEMENT,本地会话
// 仅用在语句执行上,对相同SqlSession的不同调用将不会共享数据
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 当没有为参数指定JDBC类型时,指定JDBC类型的值为null。一些驱动需要指定JDBC类型,
// 多数情况下直接用一般类型即可,比如NULL、VARCHAR或OTHER,默认为OTHER
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 指定哪个对象的方法会触发一次延迟加载,参数是用逗号分割的方法列表
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
// 设置超时时间,它决定驱动等待数据库响应的秒数,可以说任意正整数,默认为 null 未设置
protected Integer defaultStatementTimeout;
// 默认的 FetchSize,用于设置 Statement 对象的 fetchSize 属性,
// 用于限制从数据库中获取数据的最大行数,默认为 null 未设置
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
// 配置默认的 Executor 类型,SIMPLE 就是普通的 Executor;
// REUSE 会复用 Statement 对象;BATCH 会批量执行所有更新语句,默认 SIMPLE
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 指定 MyBatis 应该如何自动映射列到 Java 实体属性。NONE 表示取消自动映射,
// PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。FULL 会自动映射任意
// 复杂的结果集(无论是否嵌套),默认 PARTIAL
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 指定发现自动映射目标未知列(或者未知属性)的行为。NONE:不做任何反应,
// WARNING:输出提醒日志,FAILING:映射失败,抛出异常,默认 NONE
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
// 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态,默认 false
protected boolean lazyLoadingEnabled = false;
// 指定MyBatis创建具有延迟加载能力的对象所用到的代理工具,可选值有JAVASSIST、CGLIB,默认 JAVASSIST
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
protected String databaseId;
// configuration的工厂类,用于加载被序列化的未读属性来创建configuration
protected Class<?> configurationFactory;
// 用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory
// 对象之间的关系,其中MapperProxyFactory对象用于创建Mapper动态代理对象
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 用于注册 MyBatis 插件信息,MyBatis 插件实际上就是一个拦截器
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 用于注册所有的TypeHandler,并建立Jdbc类型、JDBC类型与TypeHandler之间的对应关系
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
public TypeHandlerRegistry getTypeHandlerRegistry() {
return typeHandlerRegistry;
}
/**
* Set a default {@link TypeHandler} class for {@link Enum}.
* A default {@link TypeHandler} is {@link org.apache.ibatis.type.EnumTypeHandler}.
* @param typeHandler a type handler class for {@link Enum}
* @since 3.4.5
*/
public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
if (typeHandler != null) {
getTypeHandlerRegistry().setDefaultEnumTypeHandler(typeHandler);
}
}
// 用于注册所有的类型别名
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
public LanguageDriverRegistry getLanguageRegistry() {
return languageRegistry;
}
public void setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) {
if (driver == null) {
driver = XMLLanguageDriver.class;
}
getLanguageRegistry().setDefaultDriverClass(driver);
}
// MappedStatement对象描述<insert|selectlupdateldelete>等标签或者通过@Select|@Delete|
// @Update|@Insert等注解配置的SQL信息。MyBatis将所有的MappedStatement对象注册到该属性中,
// 其中Key为Mapper的Id,Value为MappedStatement对象
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
// 用于注册Mapper中配置的所有缓存信息,其中Key为Cache的id,也就是Mapper的命名空间,Value为 Cache对象
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
// 用于注册Mapper配置文件中通过标签配置的ResultMap信息,ResultMap用于建立Java实体属性
// 与数据库字段之间的映射关系,其中Key为ResultMap的id,该id是由Mapper命名空间和标签的
// id属性组成的,Value为解析标签后得到的ResultMap对象
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
// 用于注册Mapper中通过 标签注册的参数映射信息。Key为ParameterMap的id,
// 由Mapper命名空间和标签的id属性构成,Value为解析标签后得到的ParameterMap对象
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
// 用于注册KeyGenerator,KeyGenerator是MyBatis的主键生成器,MyBatis提供了三种
// KeyGenerator,即Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、
// SelectKeyGenerator(通过select语句查询自增主键,例如 oracle 的 sequence)
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
// 用于注册所有 Mapper XML 配置文件路径
protected final Set<String> loadedResources = new HashSet<>();
// 用于注册 Mapper 中通过 标签配置的 SQL 片段,Key 为 SQL 片段的 id,
// Value 为 MyBatis 封装的表示 XML 节点的 XNode 对象
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
// 用于注册解析出现异常的 XMLStatementBuilder 对象
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
// 用于注册解析出现异常的 CacheRefResolver 对象
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
// 用于注册解析出现异常的 ResultMapResolver 对象
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
// 用于注册解析出现异常的 MethodResolver 对象
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
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);
// 指定动态 SQL 生成的默认语言,参数值为一个类型别名或完全限定类名,
// 默认值:org.apache.ibatis.XMLLanguageDriver
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
配置来源
Configuration 配置来源有三项:
- Mybatis-config.xml 启动文件,全局配置、全局组件都是来源于此。
- Mapper.xml SQL映射(MappedStatement) \结果集映射(ResultMapper)都来源于此。
- @Annotation SQL映射与结果集映射的另一种表达形式。
配置元素
Configuration 配置信息来源于xml和注解,每个文件和注解都是由若干个配置元素组成,并呈现嵌套关系,总体关系如下图所示:
这些属性的值可以在MyBatis主配置文件中通过<setting>标签指定,例如:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="org/apache/ibatis/databases/blog/blog-derby.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="false"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
</settings>
<typeAliases>
<typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
<typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog"/>
<typeAlias alias="Comment" type="org.apache.ibatis.domain.blog.Comment"/>
<typeAlias alias="Post" type="org.apache.ibatis.domain.blog.Post"/>
<typeAlias alias="Section" type="org.apache.ibatis.domain.blog.Section"/>
<typeAlias alias="Tag" type="org.apache.ibatis.domain.blog.Tag"/>
</typeAliases>
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
</typeHandlers>
<objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
<property name="objectFactoryProperty" value="100"/>
</objectFactory>
<plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="100"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/CachedAuthorMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/PostMapper.xml"/>
<mapper resource="org/apache/ibatis/builder/NestedBlogMapper.xml"/>
</mappers>
</configuration>
元素承载
无论是xml 还是我注解这些配置元素最弱都要被转换成JAVA配置属性或对象组件来承载。其对应关系如下:
- 全配置(config.xml) 由Configuration对像属性承载
- sql映射<select|insert...> 或@Select 等由MappedStatement对象承载
- 缓存<cache..> 或@CacheNamespace 由Cache对象承载
- 结果集映射 由ResultMap 对象承载
配置文件解析
配置文件解析需要我们分开讨论,首先来分析XML解析过程。xml配置解析其底层使用dom4j先解析成一棵节点树,然后根据不同的节点类型与去匹配不同的解析器。最终解析成特定组件。
解析器的基类是BaseBuilder 其内部包含全局的configuration 对象,这么做的用意是所有要解析的组件最后都要集中归属至configuration。接下来了解一下每个解析器的作用:
- XMLConfigBuilder :解析config.xml文件,会直接创建一个configuration对象,用于解析全局配置 。
- XMLMapperBuilder :解析Mapper.xml文件,内容包含 等
- MapperBuilderAssistant:Mapper.xml解析辅助,在一个Mapper.xml中Cache是对Statement(sql声明)共享的,共享组件的分配即由该解析实现。
- XMLStatementBuilder:SQL映射解析 即<select|update|insert|delete> 元素解析成MapperStatement。
- SqlSourceBuilder:Sql数据源解析,将声明的SQL解析可执行的SQL。
- XMLScriptBuilder:解析动态SQL数据源当中所设置 SqlNode脚本集。
XML文件解析流程
整体解析流程是从XmlConfigBuilder 开始,然后逐步向内解析,直到解析完所有节点。我们通过一个MappedStatement 解析过程即可了解到期整体解析流程。
其实Configuration类是由XMLConfigBuilder(继承自BaseBuilder类)解析而来的,由如下方法(parseConfiguration)解析
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);
}
}
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.setVariables(defaults);
}
}
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setReflectorFactory(factory);
}
}
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
configuration.setArgNameBasedConstructorAutoMapping(booleanValueOf(props.getProperty("argNameBasedConstructorAutoMapping"), false));
configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));
}
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
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);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(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.");
}
}
}
}
}
流程说明:
【XmlConfigBuilder】 接收一个config.xml 输入流,然后创建一个空Configuration对象
【XmlConfigBuilder】解析全局配置
【XmlConfigBuilder】mapperElements解析,通过Resource或url 指定mapper.xml文件
【XmlMapperBuilder】解析缓存、结果集配置等公共配置
【XmlMapperBuilder】解析Sql映射<select|insert|upate|delete>
【XMLScriptBuilder】解析生成SQL数据源,包括动态脚本
【XmlMapperBuilder】构建Statement
【MapperBuilderAssistant】设置缓存并添加至Configuration
注解配置解析
注解解析底层实现是通过反射获取Mapper接口当中注解元素实现。有两种方式一种是直接指定接口名,一种是指定包名然后自动扫描包下所有的接口类。这些逻辑均由Mapper注册器(MapperRegistry)实现。其接收一个接口类参数,并基于该参数创建针对该接口的动态代理工厂,然后解析内部方法注解生成每个MapperStatement 最后添加至Configuration 完成解析。
Executor详解
SqlSession是MyBatis提供的操作数据库的API,但是真正执行SQL的是Executor组件。Executor接口中定义了对数据库的增删改查方法,其中query()和queryCursor()方法用于执行查询操作,update()方法用于执行插入、删除、修改操作。Executor接口有几种不同的实现类,如下图所示:
MyBatis提供了3种不同的Executor,分别为SimpleExecutor、ResueExecutor、BatchExecutor,这些Executor都继承至BaseExecutor,BaseExecutor中定义的方法的执行流程及通用的处理逻辑,具体的方法由子类来实现,是典型的模板方法模式的应用。SimpleExecutor是基础的Executor,能够完成基本的增删改查操作,ResueExecutor对JDBC中的Statement对象做了缓存,当执行相同的SQL语句时,直接从缓存中取出Statement对象进行复用,避免了频繁创建和销毁Statement对象,从而提升系统性能,这是享元思想的应用。BatchExecutor则会对调用同一个Mapper执行的update、insert和delete操作,调用Statement对象的批量操作功能。
另外,我们知道MyBatis支持一级缓存和二级缓存,当MyBatis开启了二级缓存功能时,会使用CachingExecutor对SimpleExecutor、ResueExecutor、BatchExecutor进行装饰,为查询操作增加二级缓存功能,这是装饰器模式的应用。
Executor接口
org.apache.ibatis.executor.Executor
,执行器接口。
主要定义了以下内容:
读和写操作相关的方法
事务相关的方法
缓存相关的方法
设置延迟加载的方法
设置包装的 Executor 对象的方法
public interface Executor {
// 空 ResultHandler 对象的枚举
ResultHandler NO_RESULT_HANDLER = null;
// 更新 or 插入 or 删除,由传入的 MappedStatement 的 SQL 所决定
int update(MappedStatement ms, Object parameter) throws SQLException;
// // 查询,带 ResultHandler + CacheKey + BoundSql
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
// 查询,带 ResultHandler
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
// 查询,返回值为 Cursor
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
// 刷入批处理语句
List<BatchResult> flushStatements() throws SQLException;
// 提交事务
void commit(boolean required) throws SQLException;
// 回滚事务
void rollback(boolean required) throws SQLException;
// 创建 CacheKey 对象
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
// 判断是否缓存
boolean isCached(MappedStatement ms, CacheKey key);
// 清除本地缓存
void clearLocalCache();
// 延迟加载
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
// 获得事务
Transaction getTransaction();
// 关闭事务
void close(boolean forceRollback);
// 判断事务是否关闭
boolean isClosed();
// 设置包装的 Executor 对象
void setExecutorWrapper(Executor executor);
}
BaseExecutor(基础执行器)
基础执行器:维护一级缓存,是simple、reuse、batch这三个执行器的父类。主要逻辑是维护缓存,其他实现交给子类。org.apache.ibatis.executor.BaseExecutor
实现 Executor 接口,提供骨架方法,从而使子类只要实现指定的几个抽象方法即可。
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
// 事务对象
protected Transaction transaction;
// 包装的 Executor 对象
protected Executor wrapper;
// DeferredLoad( 延迟加载 ) 队列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 本地缓存,即一级缓存
protected PerpetualCache localCache;
// 本地输出类型的参数的缓存
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
// 记录嵌套查询的层级
protected int queryStack;
// 是否关闭
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
@Override
public Transaction getTransaction() {
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return transaction;
}
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// <2> 清空本地缓存
clearLocalCache();
// <3> 执行写操作
return doUpdate(ms, parameter);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// <2> 执行刷入批处理语句
return doFlushStatements(isRollBack);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter
, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// <1> 获得 BoundSql 对象
BoundSql boundSql = ms.getBoundSql(parameter);
// <2> 创建 CacheKey 对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// <3> 查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
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());
// <1> 已经关闭,则抛出 ExecutorException 异常
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;
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {
deferredLoad.load();
} else {
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
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);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return localCache.getObject(key) != null;
}
@Override
public void commit(boolean required) throws SQLException {
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
/**
* clearLocalCache() 方法,清理一级(本地)缓存
*/
@Override
public void clearLocalCache() {
if (!closed) {
// 清理 localCache
localCache.clear();
// 清理 localOutputParameterCache
localOutputParameterCache.clear();
}
}
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Apply a transaction timeout.
*
* @param statement
* a current statement
* @throws SQLException
* if a database access error occurs, this method is called on a closed <code>Statement</code>
* @since 3.4.0
* @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
*/
protected void applyTransactionTimeout(Statement statement) throws SQLException {
StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
}
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
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;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
@Override
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
private static class DeferredLoad {
private final MetaObject resultObject;
private final String property;
private final Class<?> targetType;
private final CacheKey key;
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
private final ResultExtractor resultExtractor;
// issue #781
public DeferredLoad(MetaObject resultObject,
String property,
CacheKey key,
PerpetualCache localCache,
Configuration configuration,
Class<?> targetType) {
this.resultObject = resultObject;
this.property = property;
this.key = key;
this.localCache = localCache;
this.objectFactory = configuration.getObjectFactory();
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
this.targetType = targetType;
}
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
public void load() {
@SuppressWarnings("unchecked")
// we suppose we get back a List
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
}
}
SimpleExecutor(简单执行器)
每次读或写操作都会创建一个新的预处理器(PrepareStatement);
每次执行的的SQL都会进行一次预编译;
执行完成后,关闭该 Statement 对象。
org.apache.ibatis.executor.SimpleExecutor
,继承 BaseExecutor 抽象类,简单的 Executor 实现类。
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(
this, ms, parameter, RowBounds.DEFAULT, null, null);
// 初始化 StatementHandler 对象
stmt = prepareStatement(handler, ms.getStatementLog());
// <3> 执行 StatementHandler ,进行写操作
return handler.update(stmt);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds
, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// <1> 创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// <2> 初始化StatementHandler对象
stmt = prepareStatement(handler, ms.getStatementLog());
// <3> 执行StatementHandler,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// <4> 关闭StatementHandler对象
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter
, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// <2.1> 获得 Connection 对象
Connection connection = getConnection(statementLog);
// <2.2> 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
}
ReuseExecutor(可重用执行器)
- 每次开始读或写操作,以sql作为key,查找Statement对象优先从缓存中获取对应的 Statement 对象。如果不存在,才进行创建。( 只要是相同的SQL只会进行一次预处理)
- 执行完成后,不关闭该 Statement 对象,而是放置于Map<String, Statement>内,供下一次使用。
- 其它的,和 SimpleExecutor 是一致的。
//可重用的执行器内部用了一个map,用来缓存SQL语句对应的Statement对象
//Key是我们的SQL语句,value是我们的Statement对象
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
org.apache.ibatis.executor.ReuseExecutor
,继承 BaseExecutor 抽象类,可重用的 Executor 实现类。
public class ReuseExecutor extends BaseExecutor {
private final Map<String, Statement> statementMap = new HashMap<>();
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(
this, ms, parameter, RowBounds.DEFAULT, null, null);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds
, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear();
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 判断缓存中是否存在
if (hasStatementFor(sql)) {
// <1.1> 从缓存中获得 Statement 或 PrepareStatement 对象
stmt = getStatement(sql);
// <1.2> 设置事务超时时间
applyTransactionTimeout(stmt);
} else {
// <2.1> 获得 Connection 对象
Connection connection = getConnection(statementLog);
// <2.2> 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// <2.3> 添加到缓存中
putStatement(sql, stmt);
}
// <2> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
// 判断是否存在对应的 Statement 对象
private boolean hasStatementFor(String sql) {
try {
Statement statement = statementMap.get(sql);
return statement != null && !statement.getConnection().isClosed();
} catch (SQLException e) {
return false;
}
}
private Statement getStatement(String s) {
return statementMap.get(s);
}
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
}
注意:
- ReuseExecutor 考虑到重用性,但是 Statement 最终还是需要有地方关闭。答案就在
#doFlushStatements(boolean isRollback)
方法中。而 BaseExecutor 在关闭#close()
方法中,最终也会调用该方法,从而完成关闭缓存的 Statement 对象们 - BaseExecutor 在提交或者回滚事务方法中,最终也会调用该方法,也能完成关闭缓存的 Statement 对象们。
BatchExecutor(批处理执行器)
批处理只对增删改SQL有效(没有select,JDBC批处理不支持select);
将所有增删改sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;
执行sql需要满足三个条件才能使用同一个Statement(使用同一个Statement是为了压缩体积、减少SQL预处理)
1.sql相同
2.同一个MappedStatement(sql标签的配置都在这里面)
3.执行的顺序必须是连续的
org.apache.ibatis.executor.BatchExecutor
,继承 BaseExecutor 抽象类,批量执行的 Executor 实现类。
public class BatchExecutor extends BaseExecutor {
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
// Statement数组
private final List<Statement> statementList = new ArrayList<>();
// BatchResult 数组,每一个 BatchResult 元素,对应一个 {@link #statementList} 的 Statement 元素
private final List<BatchResult> batchResultList = new ArrayList<>();
// 当前 SQL
private String currentSql;
// 当前MappedStatement对象
private MappedStatement currentStatement;
public BatchExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
// <1> 创建 StatementHandler 对象
final StatementHandler handler = configuration.newStatementHandler(
this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
// <2> 如果匹配最后一次 currentSql 和 currentStatement ,则聚合到 BatchResult 中
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// <2.1> 获得最后一次的 Statement 对象
int last = statementList.size() - 1;
stmt = statementList.get(last);
// <2.2> 设置事务超时时间
applyTransactionTimeout(stmt);
// <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);// fix Issues 322
// <2.4> 获得最后一次的 BatchResult 对象,并添加参数到其中
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
// <3.1> 获得 Connection
Connection connection = getConnection(ms.getStatementLog());
// <3.2> 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// <3.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt); // fix Issues 322
// <3.4> 重新设置 currentSql 和 currentStatement
currentSql = sql;
currentStatement = ms;
// <3.5> 添加 Statement 到 statementList 中
statementList.add(stmt);
// <3.6> 创建 BatchResult 对象,并添加到 batchResultList 中
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// <4> 批处理
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject
, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 刷入批处理语句
flushStatements();
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
// 获得 Connection 对象
Connection connection = getConnection(ms.getStatementLog());
// 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter
, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, null, boundSql);
Connection connection = getConnection(ms.getStatementLog());
Statement stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
// Close statement to close cursor #1109
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
}
- doUpdate代码上
if (sql.equals(currentSql) && ms.equals(currentStatement))
可以看出上一个添加的是否是这个sql(currentSql)并且是同一个MappedStatement currentStatement(映射语句); - 满足条件就将参数放到当前这个BatchResult对象中的参数。属性是(parameterObject)【batchResult.addParameterObject(parameterObject);】
- 不满足条件则获取一个Statement实例 再实例化BatchResult对象。最后放到list中去,再给 current和MappedStatement currentStatement赋值
- 批处理提交必须执行flushStatements才会生效(会将一个个的Statement提交)可以减少与数据库交互次数
CachingExecutor(二级缓存执行器)
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public Transaction getTransaction() {
return delegate.getTransaction();
}
@Override
public void close(boolean forceRollback) {
try {
// issues #499, #524 and #573
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) 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);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return delegate.flushStatements();
}
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
@Override
public void rollback(boolean required) throws SQLException {
try {
delegate.rollback(required);
} finally {
if (required) {
tcm.rollback();
}
}
}
private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement.");
}
}
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return delegate.isCached(ms, key);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
delegate.deferLoad(ms, resultObject, property, key, targetType);
}
@Override
public void clearLocalCache() {
delegate.clearLocalCache();
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
@Override
public void setExecutorWrapper(Executor executor) {
throw new UnsupportedOperationException("This method should not be called");
}
}
Executor的创建:
在上面的学习中,我们已经理解了各种 Executor 的实现代码。那么,Executor 对象究竟在 MyBatis 中,是如何被创建的呢?其实Configuration 类中,提供 newExecutor
方法,代码如下:
/**
* 创建 Executor 对象 -----------------在创建SqlSession执行
*
* @param transaction 事务对象
* @param executorType 执行器类型
* @return Executor 对象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// <1> 获得执行器类型
//可以通过在 mybatis-config.xml 配置 <setting name="defaultExecutorType" value="" />
executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 判断默认
// <2> 3个分支3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor: 默认为 SimpleExecutor 对象
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);
}
// <3> 如果开启缓存,创建 CachingExecutor 对象,装饰者模式进行包装
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// <4> 应用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
小结:
- 我们可以在
ExecutorType
中看到,枚举了三种类型: SIMPLE 、REUSE 、BATCH - 如果没有指定类型,默认为 SimpleExecutor 对象
- 如果开启缓存,创建 CachingExecutor 对象,进行包装
MappedStatement详解
Mybatis通过MappedStatement来描述XML中<select|update|insert|delete>和@Select、@Update等注解配置的SQL信息,Mybatis在解析XML配置构造Configuration的时候一并解析构造好了MappedStatement,以XMLConfigBuilder#parse为切入点,一起来分析下Mybatis是如何完成构造MappedStatement。
XMLConfigBuilder对外提供了一个parse()方法用于解析构造Configuration对象,XMLConfigBuilder#parse方法首先判断当前XML是否正在被解析,如果没有被解析就将parsed属性设置为true,就确保一次调用;然后获取configuration标签下的所有信息;最后调用XMLConfigBuilder#mapperElement方法解析对应标签信息。
XMLConfigBuilder#parseConfiguration方法是XMLConfigBuilder中用于解析各属性信息的一个私有方法,下述代码仅截取了构造MappedStatement的部分代码,不难看出XMLConfigBuilder#parseConfiguration方法在获取到mappers属性标签信息后,直接调用了XMLConfigBuilder#mapperElement私有方法。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记为解析中,保证一次调用
parsed = true;
// 首先获取configuration标签下的所有信息
// 然后调用parseConfiguration方法解析对应标签信息
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 省略部分代码
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解析
XMLConfigBuilder#mapperElement方法相当于一个分流器,将不同类型标识mapper的标签信息指引到所属的处理逻辑上,如下(亦可访问org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement源代码了解更多信息):
- 如果当前XNode节点描述的是package标签,那么执行package标签解析逻辑;
- 如果当前XNode节点描述的是resource标签,在获取到resource对应的信息后,执行resource标签解析逻辑;
- 如果当前XNode节点描述的是url标签,在获取到url对应的信息后,执行url标签解析逻辑;
- 如果当前XNode节点描述的是class标签,获取class对应的接口信息,然后执行class标签解析逻辑;
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement源码如下:
org.apache.ibatis.builder.xml.XMLConfigBuilder
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);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(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.");
}
}
}
}
}
虽然XMLConfigBuilder#mapperElement针对package、resource、url和class标签都有其专属的解析逻辑,但是归根结底就两个逻辑,一个是解析XML配置中的<select|update|insert|delete>标签,另一个则是解析@Select、@Update等注解配置的信息,分别对应XMLMapperBuilder#parse和MapperAnnotationBuilder#parse。
XMLMapperBuilder#parse
当XML配置的是resource和url节点时会使用XMLMapperBuilder#parse来解析构造MappedStatement,但是在调用XMLMapperBuilder#parse之前,需要先使用XMLMapperBuilder的构造方法构造一个XMLMapperBuilder对象。
XMLMapperBuilder#parse处理逻辑大致分为,
- 判断当前资源是否已经被加载,如果已被加载直接解析返回参数、缓存和请求参数;
- 如果当前资源没有被加载,就获取mapper标签下所有信息,调用XMLMapperBuilder#configurationElement私有方法;
- 如果当前资源没有被加载,调用xml.XMLMapperBuilder#bindMapperForNamespace私有方法从命名空间中解析构造MappedStatement。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 首先获取mapper标签信息
// 然后解析mapper标签下所有信息
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 从命名空间构造MappedStatement
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
XMLMapperBuilder#configurationElement分别解析了cache-ref、cache、/mapper/parameterMap、/mapper/resultMap、/mapper/sql和select|insert|update|delete标签信息,可详见org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement源代码。
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
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);
}
}
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
private void parsePendingResultMaps() {
Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
synchronized (incompleteResultMaps) {
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
while (iter.hasNext()) {
try {
iter.next().resolve();
iter.remove();
} catch (IncompleteElementException e) {
// ResultMap is still missing a resource...
}
}
}
}
private void parsePendingCacheRefs() {
Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
synchronized (incompleteCacheRefs) {
Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
while (iter.hasNext()) {
try {
iter.next().resolveCacheRef();
iter.remove();
} catch (IncompleteElementException e) {
// Cache ref is still missing a resource...
}
}
}
}
private void parsePendingStatements() {
Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
while (iter.hasNext()) {
try {
iter.next().parseStatementNode();
iter.remove();
} catch (IncompleteElementException e) {
// Statement is still missing a resource...
}
}
}
}
MapperAnnotationBuilder#parse
当XML配置的是package和class节点时会使用XMLMapperBuilder#parse来解析构造MappedStatement,同样需要先使用MapperAnnotationBuilder构造方法构造MapperAnnotationBuilder对象,才能使用parse方法。
从源代码来看MapperAnnotationBuilder#parse,首先会调用MapperAnnotationBuilder#loadXmlResource私有方法来通过XMLMapperBuilder#parse方法解析构造当前Mapper接口对应XML文件中的信息,在完成XML解析之后才是正在解析Mapper接口中定义的注解,详细可见org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse。
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
无论是使用XMLMapperBuilder#parse还是MapperAnnotationBuilder#parse来解析,最终都会通过MapperBuilderAssistant#addMappedStatement方法来构造MappedStatement对象放入configuration中的mappedStatements属性。
动态代理
了解了Mybatis是如何完成解析构造MappedStatement之后,我们在一起来看看Mybatis是如何根据Mapper接口来动态生成真正调用类。回到XMLMapperBuilder#parse和MapperAnnotationBuilder#parse方法中调用的MapperRegistry#addMapper方法,其源代码中通过MapperProxyFactory类来注册一个Mapper代理类工厂,然后在调用MapperRegistry#getMapper方法时通过MapperProxyFactory#newInstance方法来生成代理类。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 省略部分代码
knownMappers.put(type, new MapperProxyFactory<>(type));
}
}
MapperProxyFactory#newInstance使用JDK动态代理Proxy类来完成生成mapper接口的代理类,这也正是Mybatis的mapper需要以接口形式存在的原因。
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(
sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
不同类型的SQL语句需要使用对应的XML标签进行配置。这些标签提供了很多属性,用来控制每条SQL语句的执行行为。下面是<select>标签中的所有属性:
<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE | DEFAULT) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
useCache (true|false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true|false) #IMPLIED
resultSets CDATA #IMPLIED
>
这些属性的含义如下:
- id:在命名空间中唯一的标识符,可以被用来引用这条配置信息。
- parameterType:用于指定这条语句的参数类的完全限定名或别名。这个属性是可选的,MyBatis能够根据Mapper接口方法中的参数类型推断出传入语句的类型。
- parameterMap:引用通过<parameterMap>标签定义的参数映射,该属性已经废弃。
- resultType:从这条语句中返回的期望类型的类的完全限定名或别名。注意,如果返回结果是集合类型,则resultType属性应该指定集合中可以包含的类型,而不是集合本身。
- resultMap:用于引用通过<resultMap>标签配置的实体属性与数据库字段之间建立的结果集的映射(注意:resultMap和resultType属性不能同时使用)。
- flushCache:用于控制是否刷新缓存。如果将其设置为 true,则任何时候只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值为false。
- useCache:是否使用二级缓存。如果将其设置为 true,则会导致本条语句的结果被缓存在MyBatis的二级缓存中,对应<select>标签,该属性的默认值为true。
- timeout:驱动程序等待数据库返回请求结果的秒数,超时将会抛出异常。
- fetchSize:用于设置JDBC中 Statement对象的fetchSize属性,该属性用于指定SQL执行后返回的最大行数。
- statementType:参数可选值为STATEMENT、PREPARED或CALLABLE,这会让MyBatis分别使用Statement、PreparedStatement或CallableStatement与数据库交互,默认值为PREPARED。
- resultSetType:参数可选值为FORWARD_ONLY、SCROLL_SENSITIVE或SCROLL_INSENSITIVE,用于设置ResultSet对象的特征,具体可参考第2章JDBC规范的相关内容。默认未设置,由JDBC驱动决定。
- databaseId:如果配置了 databaseIdProvider,MyBatis会加载所有不带databaseId或匹配当前 databaseId 的语句。
- resultOrdered:这个设置仅针对嵌套结果 select语句适用,如果为 true,就是假定嵌套结果包含在一起或分组在一起,这样的话,当返回一个主结果行的时候,就不会发生对前面结果集引用的情况。这就使得在获取嵌套结果集的时候不至于导致内存不够用,默认值为false。
- resultSets:这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称使用逗号分隔。
- lang:该属性用于指定LanguageDriver实现,MyBatis中的LanguageDriver用于解析<select|update|insert|delete>标签中的SQL语句,生成SqlSource对象。
上面的属性中,resultMap、resultType、resultSetType、fetchSize、useCache、resultSets、resultOrdered是<select>标签特有的,其他属性是<update|insert|delete>标签共有的。另外,<insert>和<update>标签有几个特有的属性,下面是<insert>标签的所有属性:
<!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST insert
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true|false) #IMPLIED
statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true|false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
这几个属性的作用如下:
useGeneratedKeys:该属性仅对<update>和<insert>标签有用,属性值为true时,MyBatis使用JDBC Statement对象的getGeneratedKeys()方法来取出由数据库内部生成的键值,例如MySQL自增主键。
keyProperty:该属性仅对<update>和<insert>标签有用,用于将数据库自增主键或者<insert>标签中<selectKey>标签返回的值填充到实体的属性中,如果有多个属性,则使用逗号分隔。
keyColumn:该属性仅对<update>和<insert>标签有用,通过生成的键值设置表中的列名,这个设置仅在某些数据库(例如PostgreSQL)中是必需的,当主键列不是表中的第一列时需要设置,如果有多个字段,则使用逗号分隔。MappedStatement类通过下面这些属性保存<select|update|insert|delete>标签的属性信息:
public final class MappedStatement {
// Mapper配置文件路径。
private String resource;
// Configuration对象的引用,方便获取MyBatis配置信息及TypeHandler、TypeAlias等信息。
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
// 解析<select|update|insert|delete>,将SQL语句配置信息解析为SqlSource对象。
private SqlSource sqlSource;
// 二级缓存实例,根据Mapper中的<cache>标签配置信息创建对应的Cache实现。
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
// 主键生成策略,默认为Jdbc3KeyGenerator,即数据库自增主键。
// 当配置了<selectKey>时,使用SelectKeyGenerator生成主键。
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
// <select>标签中通过resultMap属性指定ResultMap是不是嵌套的ResultMap。
private boolean hasNestedResultMaps;
private String databaseId;
// 用于输出日志。
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
MappedStatement() {
// constructor disabled
}
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.resultSetType = ResultSetType.DEFAULT;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
mappedStatement.resultMaps = new ArrayList<>();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
if (configuration.getLogPrefix() != null) {
logId = configuration.getLogPrefix() + id;
}
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
public Builder resource(String resource) {
mappedStatement.resource = resource;
return this;
}
public String id() {
return mappedStatement.id;
}
public Builder parameterMap(ParameterMap parameterMap) {
mappedStatement.parameterMap = parameterMap;
return this;
}
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
for (ResultMap resultMap : resultMaps) {
mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
}
return this;
}
public Builder fetchSize(Integer fetchSize) {
mappedStatement.fetchSize = fetchSize;
return this;
}
public Builder timeout(Integer timeout) {
mappedStatement.timeout = timeout;
return this;
}
public Builder statementType(StatementType statementType) {
mappedStatement.statementType = statementType;
return this;
}
public Builder resultSetType(ResultSetType resultSetType) {
mappedStatement.resultSetType = resultSetType == null ? ResultSetType.DEFAULT : resultSetType;
return this;
}
public Builder cache(Cache cache) {
mappedStatement.cache = cache;
return this;
}
public Builder flushCacheRequired(boolean flushCacheRequired) {
mappedStatement.flushCacheRequired = flushCacheRequired;
return this;
}
public Builder useCache(boolean useCache) {
mappedStatement.useCache = useCache;
return this;
}
public Builder resultOrdered(boolean resultOrdered) {
mappedStatement.resultOrdered = resultOrdered;
return this;
}
public Builder keyGenerator(KeyGenerator keyGenerator) {
mappedStatement.keyGenerator = keyGenerator;
return this;
}
public Builder keyProperty(String keyProperty) {
mappedStatement.keyProperties = delimitedStringToArray(keyProperty);
return this;
}
public Builder keyColumn(String keyColumn) {
mappedStatement.keyColumns = delimitedStringToArray(keyColumn);
return this;
}
public Builder databaseId(String databaseId) {
mappedStatement.databaseId = databaseId;
return this;
}
public Builder lang(LanguageDriver driver) {
mappedStatement.lang = driver;
return this;
}
public Builder resultSets(String resultSet) {
mappedStatement.resultSets = delimitedStringToArray(resultSet);
return this;
}
/**
* Resul sets.
*
* @param resultSet
* the result set
* @return the builder
* @deprecated Use {@link #resultSets}
*/
@Deprecated
public Builder resulSets(String resultSet) {
mappedStatement.resultSets = delimitedStringToArray(resultSet);
return this;
}
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
}
public KeyGenerator getKeyGenerator() {
return keyGenerator;
}
public SqlCommandType getSqlCommandType() {
return sqlCommandType;
}
public String getResource() {
return resource;
}
public Configuration getConfiguration() {
return configuration;
}
public String getId() {
return id;
}
public boolean hasNestedResultMaps() {
return hasNestedResultMaps;
}
public Integer getFetchSize() {
return fetchSize;
}
public Integer getTimeout() {
return timeout;
}
public StatementType getStatementType() {
return statementType;
}
public ResultSetType getResultSetType() {
return resultSetType;
}
public SqlSource getSqlSource() {
return sqlSource;
}
public ParameterMap getParameterMap() {
return parameterMap;
}
public List<ResultMap> getResultMaps() {
return resultMaps;
}
public Cache getCache() {
return cache;
}
public boolean isFlushCacheRequired() {
return flushCacheRequired;
}
public boolean isUseCache() {
return useCache;
}
public boolean isResultOrdered() {
return resultOrdered;
}
public String getDatabaseId() {
return databaseId;
}
public String[] getKeyProperties() {
return keyProperties;
}
public String[] getKeyColumns() {
return keyColumns;
}
public Log getStatementLog() {
return statementLog;
}
public LanguageDriver getLang() {
return lang;
}
public String[] getResultSets() {
return resultSets;
}
/**
* Gets the resul sets.
*
* @return the resul sets
* @deprecated Use {@link #getResultSets()}
*/
@Deprecated
public String[] getResulSets() {
return resultSets;
}
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// 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;
}
private static String[] delimitedStringToArray(String in) {
if (in == null || in.trim().length() == 0) {
return null;
} else {
return in.split(",");
}
}
}
StatementHandler详解
StatementHandler组件封装了对JDBC Statement的操作,例如设置Statement对象的fetchSize属性、设置查询超时时间、调用JDBC Statement与数据库交互等。MyBatis的StatementHandler接口中定义的方法如下:
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
这些方法的作用如下。
- prepare:该方法用于创建JDBC Statement对象,并完成Statement对象的属性设置。
- parameterize:该方法使用MyBatis中的ParameterHandler组件为PreparedStatement和CallableStatement参数占位符设置值。
- batch:将SQL命令添加到批处量执行列表中。
- update:调用Statement对象的execute()方法执行更新语句,例如UPDATE、INSERT、DELETE语句。
- query:执行查询语句,并使用ResultSetHandler处理查询结果集。
- queryCursor:带游标的查询,返回Cursor对象,能够通过Iterator动态地从数据库中加载数据,适用于查询数据量较大的情况,避免将所有数据加载到内存中。
- getBoundSql:获取Mapper中配置的SQL信息,BoundSql封装了动态SQL解析后的SQL文本和参数映射信息。
- getParameterHandler:获取ParameterHandler实例。
StatementHandler接口有几种不同的实现,下图是StatementHandler的继承关系图。
BaseStatementHandler是一个抽象类,封装了通用的处理逻辑及方法执行流程,具体方法的实现由子类完成,这里使用到了设计模式中的模板方法模式。SimpleStatementHandler继承至BaseStatementHandler,封装了对JDBC Statement对象的操作,PreparedStatementHandler封装了对JDBC PreparedStatement对象的操作,而CallableStatementHandler则封装了对JDBC CallableStatement对象的操作。RoutingStatementHandler会根据Mapper配置中的statementType属性(取值为STATEMENT、PREPARED或CALLABLE)创建对应的StatementHandler实现。
TypeHandler详解
使用JDBC API开发应用程序,其中一个比较烦琐的环节是处理JDBC类型与Java类型之间的转换。涉及Java类型和JDBC类型转换的两种情况如下:
(1)PreparedStatement对象为参数占位符设置值时,需要调用PreparedStatement接口中提供的一系列的setXXX()方法,将Java类型转换为对应的JDBC类型并为参数占位符赋值。
(2)执行SQL语句获取ResultSet对象后,需要调用ResultSet对象的getXXX()方法获取字段值,此时会将JDBC类型转换为Java类型。MyBatis中使用TypeHandler解决上面两种情况下,JDBC类型与Java类型之间的转换。TypeHandler接口定义如下:
MyBatis中使用TypeHandler解决上面两种情况下,JDBC类型与Java类型之间的转换。TypeHandler接口定义如下:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the result.
*
* @param rs
* the rs
* @param columnName
* Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the result
* @throws SQLException
* the SQL exception
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
TypeHandler接口中定义了4个方法,setParameter()方法用于为PreparedStatement对象参数的占位符设置值,另外3个重载的getResult()方法用于从ResultSet对象中获取列的值,或者获取存储过程调用结果。MyBatis中的BaseTypeHandler类实现了TypeHandler接口,对调用setParameter()方法,参数为Null的情况做了通用的处理。对调用getResult()方法,从ResultSet对象或存储过程调用结果中获取列的值出现的异常做了处理。因此,当我们需要自定义TypeHandler时,只需要继承BaseTypeHandler类即可。MyBatis中内置了很多TypeHandler,例如StringTypeHandler用于java.lang.String类型和JDBC中的CHAR、VARCHAR、LONGVARCHAR、NCHAR、NVARCHAR、LONGNVARCHAR等类型之间的转换。StringTypeHandler中的逻辑非常简单,代码如下:
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
如上面的代码所示,StringTypeHandler类的setNonNullParameter()方法调用PreparedStatement对象的setString()方法将Java中的java.lang.String类型转换为JDBC类型,并为参数占位符赋值。getNullableResult()方法调用ResultSet对象的getString()方法将JDBC中的字符串类型转为Java中的java.lang.String类型,并返回列的值。
MyBatis通过TypeHandlerRegistry建立JDBC类型、Java类型与TypeHandler之间的映射关系,代码如下:
public final class TypeHandlerRegistry {
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
如上面的代码所示,在TypeHandlerRegistry中,通过Map对象保存JDBC类型、Java类型与TypeHandler之间的关系,在TypeHandlerRegistry类的构造方法中,通过register()方法注册所有的TypeHandler,代码如下:
/**
* The constructor that pass the MyBatis configuration.
*
* @param configuration a MyBatis configuration
* @since 3.5.4
*/
public TypeHandlerRegistry(Configuration configuration) {
this.unknownTypeHandler = new UnknownTypeHandler(configuration);
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new StringTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, unknownTypeHandler);
register(Object.class, JdbcType.OTHER, unknownTypeHandler);
register(JdbcType.OTHER, unknownTypeHandler);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
register(Instant.class, new InstantTypeHandler());
register(LocalDateTime.class, new LocalDateTimeTypeHandler());
register(LocalDate.class, new LocalDateTypeHandler());
register(LocalTime.class, new LocalTimeTypeHandler());
register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
register(OffsetTime.class, new OffsetTimeTypeHandler());
register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
register(Month.class, new MonthTypeHandler());
register(Year.class, new YearTypeHandler());
register(YearMonth.class, new YearMonthTypeHandler());
register(JapaneseDate.class, new JapaneseDateTypeHandler());
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
当我们自定义TypeHandler后,也可以调用TypeHandlerRegistry类的register()方法进行注册,该方法的逻辑比较简单,将Java、JDBC类型和TypeHandler的对应关系添加到Map对象中,读者可参考MyBatis源码中的实现。另外,TypeHandlerRegistry提供了一系列重载的getTypeHandler()方法,该方法能够根据Java类型或者JDBC类型获取对应的TypeHandler对象。
ParameterHandler详解
当使用PreparedStatement或者CallableStatement对象时,如果SQL语句中有参数占位符,在执行SQL语句之前,就需要为参数占位符设置值。ParameterHandler的作用是在PreparedStatementHandler和CallableStatementHandler操作对应的Statement执行数据库交互之前为参数占位符设置值。ParameterHandler接口中只有两个方法,使用方法如下:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps) throws SQLException;
}
这两个方法的作用如下。getParameterObject:该方法用于获取执行Mapper时传入的参数对象。setParameters:该方法用于为JDBC PreparedStatement或者CallableStatement对象设置参数值。ParameterHandler接口只有一个默认的实现类,即DefaultParameterHandler,我们重点关注setParameters()方法,代码如下:
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
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 {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
如上面的代码所示,MyBatis通过ParameterMapping描述参数映射的信息。在DefaultParameterHandler类的setParameters()方法中,首先获取Mapper配置中的参数映射,然后对所有参数映射信息进行遍历,接着根据参数名称获取对应的参数值,调用对应的TypeHandler对象的setParameter()方法为Statement对象中的参数占位符设置值。
ResultSetHandler详解
ResultSetHandler用于在StatementHandler对象执行完查询操作或存储过程后,对结果集或存储过程的执行结果进行处理。ResultSetHandler接口定义如下:
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
如上面的代码所示,ResultSetHandler接口中有3个方法,这些方法的作用如下:handleResultSets:获取Statement对象中的ResultSet对象,对ResultSet对象进行处理,返回包含结果实体的List对象。handleCursorResultSets:将ResultSet对象包装成Cursor对象,对Cursor进行遍历时,能够动态地从数据库查询数据,避免一次性将所有数据加载到内存中。handleOutputParameters:处理存储过程调用结果。ResultSetHandler接口只有一个默认的实现,即DefaultResultHandler。接下来我们重点关注DefaultResultHandler对handleResultSets()方法的实现,代码如下:
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;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
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);
}
如上面代码所示,DefaultResultHandler类的handleResultSets()方法的逻辑如下:(1)从Statement对象中获取ResultSet对象,然后将ResultSet包装为ResultSetWrapper对象,通过ResultSetWrapper对象能够更方便地获取表字段名称、字段对应的TypeHandler信息。(2)获取解析Mapper接口及Mapper SQL配置生成的ResultMap信息,一条语句一般对应一个ResultMap。(3)调用handleResultSet()方法对ResultSetWrapper对象进行处理,将生成的实体对象存放在multipleResults列表中。handleResultSet()方法的具体细节将会在后面介绍MyBatis高级映射时详细介绍。