MyBatis
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
在深入的了解MyBatis之前,我们先来看一下,没有MyBatis的时候,我们是怎么去处理与数据库的交互的。
一、从JDBC开始
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问。
看一下下面这个例子,逻辑非常简单,就是从数据库中查询出所有的的用户:
import java.sql.*;
public class FirstExample {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/EMP";
static final String USER = "username";
static final String PASS = "password";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
stmt = conn.createStatement();
String sql = "SELECT id, first, last, age FROM user";
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<>();
while(rs.next()){
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
User newUser = new User();
newUser.setId(id);
newUser.setAge(age);
newUser.setFirst(first);
newUser.setLast(last);
userList.add(newUser);
}
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
}
}
这段代码看起来没什么复杂的,这样进行数据库操作也是完全的可以,可以借助代码生成工具快速的生成对一个表的CRUD操作,但是对于比较复杂的查询,生成工具就力不从心了。
那么,我们就来对比一下MyBatis是怎么与数据库进行交互的。
二、Get started with MyBatis
和很多的Java框架一样,如果你需要使用MyBatis,那么就需要MyBatis的jar包出现在classpath中,可以通过下载、Maven、Gradle等等方式来处理这个问题。
根据国际惯例,框架都需要配置。
2.1 配置
下面是一个非常基础的配置mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<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/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
BlogMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
2.2 加载配置
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.3 执行数据库操作
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
使用MyBatis对数据库进行操作的风格与使用JDBC是完全不一样的,接下来,我们就来详细的看一下。
三、MyBatis
Mybatis大致可以分为两个phase,一,配置阶段,二,执行阶段
-
在启动阶段主要完成的:
- 配置文件的解析
- 创建核心的Configuration对象,并通过配置创建相关联的对象,并用这些对象填充Configuration对象
- 创建SqlSession实例
- 在执行阶段,根据之前的配置,通过SqlSession执行,参数处理,SQL语句解析处理,语句执行,处理返回。
3.1 配置阶段
3.1.1 配置都包含了什么内容
- 对于配置文件的读取,Mybatis抽象了一个名为
Resources
的类,用于从classpath中找到配置文件,并将其转为InputStream
或者是Reader
。 - 将上一步中获得的
InputStream
或者Reader
对象传递给SqlSessionFactoryBuilder
,builder会new XMLConfigBuilder
,调用其parse方法,得到Configuration
的实例,将Configuration
的实例传递给DefaultSqlSessionFactory
。由此可见,Mybatis的配置的解析是通过XMLConfigBuilder
完成的。
3.1.1.1 包罗万象的Configuration
在详细了解XMLConfigBuilder
之前,我们需要看一下Mybatis的Configuration
都包含了哪些内容。从官方给出的文档上来看,Configuration
包含以下的配置信息:
- properties
These are externalizable, substitutable properties that can be configured in a typical Java Properties file instance, or passed in through sub-elements of the properties element. For example:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
-
settings
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
-
typeAliases
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
@Alias("author")
public class Author {
...
}
- typeHandlers
Whenever MyBatis sets a parameter on a PreparedStatement or retrieves a value from a ResultSet, a TypeHandler is used to retrieve the value in a means appropriate to the Java type. The following table describes the default TypeHandlers - objectFactory
Each time MyBatis creates a new instance of a result object, it uses an ObjectFactory instance to do so. The default ObjectFactory does little more than instantiate the target class with a default constructor, or a parameterized constructor if parameter mappings exist. If you want to override the default behaviour of the ObjectFactory -
plugins
MyBatis allows you to intercept calls to at certain points within the execution of a mapped statement. By default, MyBatis allows plug-ins to intercept method calls of:-
Executor
(update
,query
,flushStatements
,commit
,rollback
,getTransaction
,close
,isClosed
) -
ParameterHandler
(getParameterObject
,setParameters
) -
ResultSetHandler
(handleResultSets
,handleOutputParameters
) -
StatementHandler
(prepare
,parameterize
,batch
,update
,query
)
-
-
environments
- environment
MyBatis can be configured with multiple environments. This helps you to apply your SQL Maps to multiple databases for any number of reasons.
One important thing to remember though: While you can configure multiple environments, you can only choose ONE per SqlSessionFactory instance.
- transactionManager
There are two TransactionManager types (i.e. type="[JDBC|MANAGED]") that are included with MyBatis
- dataSource
- databaseIdProvider
MyBatis is able to execute different statements depending on your database vendor.
- mappers
Now that the behavior of MyBatis is configured with the above configuration elements, we’re ready to define our mapped SQL statements. But first, we need to tell MyBatis where to find them. Java doesn’t really provide any good means of auto-discovery in this regard, so the be.
在Configuration
类中,包含了
- environment
- databaseId
- setting的配置项
- jdbcTypeForNull
- lazyLoadTriggerMethods
- defaultStatementTimeout
- defaultFetchSize
- defaultExecutorType
-
autoMappingBehavior
- NONE
Disables auto-mapping. - PARTIAL(default)
Will only auto-map results with no nested result mappings defined inside - FULL
Will auto-map result mappings of any complexity (containing nested or otherwise)
- NONE
-
autoMappingUnknownColumnBehavior
- NONE(default)
Do nothing - WARNING
Output warning log - FAILING
Fail mapping
- NONE(default)
- variables(properties)
- reflectorFactory
Reflector
represents a cached set of class definition information that allows for easy mapping between property names and getter/setter methods - objectFactory
MyBatis uses an ObjectFactory to create all needed new Objects. - objectWrapperFactory
Wrapper of the object, 主要在MetaObject
中使用 - proxyFactory
proxyFactory,default isJavassistProxyFactory
- configurationFactory
Configuration factory class. Used to create Configuration for loading deserialized unread properties. - mapperRegistry
通过字段Map<Class<?>, MapperProxyFactory<?>> knownMappers
缓存mapper
,有两个重要的方法:
-
public <T> void addMapper(Class<T> type)
首先会添加一个type
与MapperProxyFactory
实例的对应关系到knownmapper
中,随后创建一个MapperAnnotationBuilder
的实例,并调用其parse方法 -
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
尝试从knownmapper
中,通过type
找到对应的MapperProxyFactory
,如果有MapperProxyFactory
,则调用其newInstance()
创建一个mapper
的代理实例。
- interceptorChain
使用一个ArrayList
保存了所有的Interceptor
,其最重要的方法为:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
-
typeHandlerRegistry
//JDBC Type private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); //Java Type private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>(); //Unknown Type private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); //All Type private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); //NULL Type private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();
- typeAliasRegistry
- languageRegistry
- mappedStatements(Map<String, MappedStatement>)
- caches (Map<String, Cache>)
- resultMaps(Map<String, ResulMap>)
- parameterMaps(Map<String, ParameterMap>)
- keyGenerators(Map<String, KeyGenerator>)
- loadedResources(Set<String>)
- sqlFragments(<String, XNode>)
- cacheRefMap(Map<String, String>)
- incompleteStatements(Collection<XMLStatementBuilder>)
- incompleteCacheRefs(Collection<CacheRefResolver>)
- incompleteResultMaps(Collection<ResultMapResolver>)
- incompleteMethods(Collection<MethodResolver>)
3.1.1.2 new Configuration时都做了些什么
在new Configuration的时候,初始化了一些默认的type alias,
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);
同时设置了默认的language driver为XMLLanguageDriver
,并注册是RawLanguageDriver
。
3.1.2 配置文件的处理过程
3.1.2.1 XML
在Mybatis中Configuration
实例的各项配置是通过XMLConfigBuilder
完成的,具体的来看,主要是通过:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
进行的。
这里需要关注一下的是mapperElement
,因为在Mybatis的xml配置中,mapper的配置方式有两类四种:
- 第一类是通过XML,
- 另外一类是通过java的interface,
- 两种类型的配置可以混合。
XML的配置是通过:XMLMapperBuilder
进行解析处理的。
Interface的配置是通过:MapperAnnotationBuilder
进行解析处理的。
<!-- 1. Using classpath relative resources -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 2. Using url fully qualified paths -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 3. Using mapper interface classes -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 4. Register all interfaces in a package as mappers -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
在XMLMapperBuilder
解析xml配置的时候,首先会处理namespace
,然后依次处理:
-
cache-ref
- 读取出
cache-ref
中的namespace
,然后通过CacheRefResolver
的resolveCacheRef()
方法处理 -
resolveCacheRef()
将处理逻辑代理到MapperBuilderAssistant
private final MapperBuilderAssistant assistant; public Cache resolveCacheRef() { return assistant.useCacheRef(cacheRefNamespace); }
-
而
MapperBuilderAssistant
则是从Configuration
中getCache(namespace)
public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { unresolvedCacheRef = true; Cache cache = configuration.getCache(namespace); if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); } currentCache = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }
- 读取出
与其他的配置类似,最终的信息都会汇总到Configuration对象中去。
-
cache
- 读取xml中的配置
-
通过
builderAssistant
完成Cache
对象的创建,并将新创建出来的Cache
对象添加到Configuration
中。builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
- parameterMap
Deprecated -
resultMap
- 抓出xml文件中,所有的
resultMap
,逐个进行解析 - 在处理每一个
resultMap
的时候,XMLMapperBuilder
从xml中读取resultMap的各项属性配置,然后同样是将处理代理到builderAssistant
完成resultMap
的创建 - 在获得
resultMap
对象之后,因为resultMap
是可以extends
其他的resultMap
的,且被继承的resultMap
有可能已经解析,也有可能还没有解析,此时,ResultMapResolver
负责处理这个问题,而ResultMapResolver
又将问题抛给builderAssistant
来完成最终的处理。 -
在
builderAssistant
中- 如果
parentResultMap
还没有加载完成,Mybatis会抛出异常
IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
在
XMLMapperBuilder
会捕获IncompleteElementException
,并将ResultMapResolver
添加到Configuration
中:configuration.addIncompleteResultMap
-
如果
parentResultMap
已经加载完成,- 则使用
subResultMap
的constructor
替换parentResultMap
的constructor
- 合并
subResultMap
和parentResultMap
- 通过
ResultMap.Builder
完成ResultMap
实例的创建,并将其添加到Configuration
中
- 则使用
- 如果
- 抓出xml文件中,所有的
- sql
This element can be used to define a reusable fragment of SQL code that can be included in other statements. It can be statically (during load phase) parametrized. Different property values can vary in include instances. For example:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
The SQL fragment can then be included in another statement, for example:
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
从上述可以看出,sql配置的解析应该是比较简单:将sql作为应该XNode,以(Id,XNode)
的形式,存储到XMLMapperBuilder.sqlFragments
中。
- select|Insert|update|delete
select|Insert|update|delete
是最为复杂的配置项,Mybatis将其处理交由XMLStatementBuilder
进行处理。
在XMLStatementBuilder
的parseStatementNode()
中,包括以下的处理:
- 简单配置项的读取
- 通过
XMLIncludeTransformer.applyIncludes
完成对include节点的处理 - 处理
selectKey
-
处理完
include
和selectKey
,Mybatis会将初始的include
和selectKey
标签进行替换或者移除,然后通过LanguageDriver
完成对SQL语句的处理,返回一个SqlSource
的实例。默认的
LanguageDriver
为XMLLanguageDriver
,其将SQL语句的解析处理代理给XMLScriptBuilder.parseScriptNode()
:public SqlSource parseScriptNode() { List<SqlNode> contents = parseDynamicTags(context); MixedSqlNode rootSqlNode = new MixedSqlNode(contents); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
在`parseDynamicTags`中返回的`List<SqlNode>`,包含以下几种`SqlNode`
| tag | SqlNode | handler |
| --------- | ----------------- | ---------------- |
| ${} | TextSqlNode | N/A |
| trim | TrimSqlNode | TrimHandler |
| where | WhereSqlNode | WhereHandler |
| set | SetSqlNode | SetHandler |
| foreach | ForEachSqlNode | ForEachHandler |
| if | IfSqlNode | IfHandler |
| choose | ChooseSqlNode | ChooseHandler |
| when | IfSqlNode | IfHandler |
| otherwise | MixedSqlNode | OtherwiseHandler |
| bind | VarDeclSqlNode | BindHandler |
| 普通 | StaticTextSqlNode | N/A |
- 在获得
SqlSource
对象之后,Mybatis更加配置初始化KeyGenerator
- 最后通过
MappedStatement.Builder
生成MappedStatement
,并将其添加到Configuration
中。
到此为止,一个mapper的xml配置已经处理完成,接下来就是要把namespace所对应的java class通过configuration.addMapper
添加到Configuration上去。
最后处理,分别调用pending的resolver:
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
3.1.2.2 Annotation
那么,现在还有一个问题没有解决,那就是Mapper的定义,可以通过XML的方式,也可以通过Annotation
的方式,或者是两种混合使用,我们已经清楚了XML方式的配置是怎么处理的,那么Annotation
是在哪里处理的,是怎么处理的呢?
当调用configuration.addMapper
的时候,Mybatis会代理到MapperRegistry
,进而通过MapperAnnotationBuilder
来完成Annotation
的处理,Mybatis支持的Annotation
如下表:
Annotation | Target | XML equivalent |
---|---|---|
@CacheNamespace |
Class |
<cache> |
@Property |
N/A | <property> |
@CacheNamespaceRef |
Class |
<cacheRef> |
@ConstructorArgs |
Method |
<constructor> |
@Arg |
N/A | <arg> `` |
@TypeDiscriminator |
Method |
<discriminator> |
@Case |
N/A | <case> |
@Results |
Method |
<resultMap> |
@Result |
N/A | <result> `` |
@One |
N/A | <association> |
@Many |
N/A | <collection> |
@MapKey |
Method |
|
@Options |
Method |
Attributes of mapped statements. |
@Insert @Update @Delete @Select |
Method |
<insert> <update> <delete> <select> |
@InsertProvider @UpdateProvider @DeleteProvider @SelectProvider |
Method |
<insert> <update> <delete> <select> |
@Param |
Parameter |
N/A |
@SelectKey |
Method |
<selectKey> |
@ResultMap |
Method |
N/A |
@ResultType |
Method |
N/A |
@Flush |
Method |
N/A |
当MapperAnnotationBuilder开始处理Annotation之前,MapperAnnotationBuilder会通过configuration.isResourceLoaded(resource)
检查以Mapper为namespace的相关资源有没有被加载,如果没有被加载过,则先加载并解析XML的配置,然后处理Cache及CacheRef,因为这两个Annotation是在Type层面的,所以优先处理,处理的方式和XML没有太多的区别。
接下来遍历Type
上所有的methods
:
- 处理
ParameterType
如果是单个parameter
,则ParameterType
为该参数的Type
,否则,则为ParamMap.class
。
- 获取
LanguageDriver
-
通过
LanguageDriver
获取SqlSource
- 获取
@Insert/@Update/@Delete/@Select
- 获取
@InsertProvider/@UpdateProvider/@DeleteProvider/@SelectProvider
- 两种注解不能同时出现,只能二选一,
-
如果是第1种,则:
- 从
sqlAnnotation
中获取sql语句,结果为一个String类型的数组 - 用空格连接String数组
-
通过
LanguageDriver
创建SqlSource
,- 如果在sql语句中有
<script>
,则通过XMLScriptBuilder
来处理SQL。 - 如果没有
<script>
,则通过PropertyParser.parse
,然后通过创建SqlSource
。
- 如果在sql语句中有
- 从
- 如果是第2种,则返回一个
ProviderSqlSource
的实例。
- 获取
- 如果sql语句类型是
INSERT
或者是UPDATE
,则根据SelectKey
和Options
这两个Annotation
确定KeyGenerator
- 处理
@ResultMap
,将ResultMap
的value
,以,
拼接到一起,形成resultMapId
。 -
如果是
SELECT
,则通过调用resultMapId = parseResultMap(method)
;- 获取
returnType
,ConstructorArgs
,Results
,TypeDiscriminator
- 如果
Results
有指定id
的话,则resultMapId=type.getName() + "." + results.id()
如果没有指定的话,则
resultMapId=type.getName() + "." + method.getName() + suffix
- 将ConstructorArg转为ResultMapping对象。
- 处理Results,将其转为ResultMapping对象。
- 处理Discriminator
- 获取
- 最终通过MapperBuilderAssistant得到MappedStatement对象。
3.2 数据库操作的执行过程
到目前为止,无论是XML的配置,还是Annotation的配置,都已经加载完成了,接下来就可以对mapper的方法进行调用了。
-
通过SqlSession获取Mapper的代理实例:
//1. DefaultSqlSession.java public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } //2. Configuration.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //3. MapperRegistry.java 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); } }
-
从上面的代码可以看出,我们得到的Mapper的对象,是一个MapperProxy的实例,MapperProxy的声明如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {...}
当我们执行一个方法时,其执行过程是这样的:
- MapperProxy尝试从methodCache中获取MapperMethod实例,如果没有,则创建一个实例,并添加到methodCache中。
- 执行
mapperMethod.execute(sqlSession, args);
-
MapperMethod包含两个实例:
- SqlCommand
通过MappedStatement获取类型和MappedStatement的ID -
MethodSignature
private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver;
- SqlCommand
在执行execute方法时,
1. 通过SqlCommand判断是INSERT/UPDATE/DELETE/SELECT/FLUSH,下面以INSERT为例。
2. 通过MethodSignature的ParamNameResolver获取到参数对象,会有三种情况:
* 没有任何参数时,返回null
* 如果只有一个参数,则是参数本身
* 如果有多个参数,则返回一个Map
3. 执行`rowCountResult(sqlSession.insert(command.getName(), param))`
- DefaultSqlSession通过MappedStatement的ID,从Configuration中获取到MappedStatement,然后交由Executor执行。
-
在配置Mybatis的时候,我们可以通过defaultExecutorType配置Executor为SIMPLE/REUSE/ BATCH,默认为SIMPLE。
- 清理本地缓存
-
通过MappedStatement获取到Configuration对象,通过:
configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
获取到StatementHandler对象,此时得到的对象为RoutingStatementHandler,该对象会根据MappedStatement的类型,将实际的操作代理给`SimpleStatementHandler/PreparedStatementHandler(默认)/CallableStatementHandler`。
在创建PreparedStatementHandler的时候,会通过MappedStatement.getBoundSql->sqlSource.getBoundSql(parameterObject)得到BoundSql。
最后,将所有的interceptor实例plugin到这个StatementHandler上去。
plugin的动作,其实就是对这个StatementHandler进行逐层的动态代理。
3. Mybatis封装了一个Transaction对象,从实现类JdbcTransaction中获取Connection,同时设定TransactionIsolation:
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
4. 调用StatementHandler的prepare方法,获取到Statement对象:
//1. 创建Statement实例
stmt = handler.prepare(connection, transaction.getTimeout());
//2. 添加参数
handler.parameterize(stmt);
/*
1. 通过boundSql获取到List<ParameterMapping>
2. 逐个遍历,通过相对应的TypeHandler往Statement上添加参数。
parameterMapping.getTypeHandler();
*/
//PreparedStatementHandler.java,创建Statement实例,
//最终还是通过JDBC的connection对象创建处理PreparedStatement对象。
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
5. 调用Handler的update/query方法执行:
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
这两个方法的区别在于,update需要对key进行处理,而query则需要对resultSet进行处理。
* Jdbc3KeyGenerator
1. 通过PreparedStatement.getGeneratedKeys
2. 获取配置中的keyProperties
3. 获取相对应类型的TypeHandler,将TypeHandler处理过的值设置到对象中去。
* SelectKeyGenerator
1. 通过SimpleExecutor执行KeyStatement获取到Key的值
2. 将值设置到Parameter对象中去
* ResultHandler
执行SELECT的时候,MapperMethod需要根据返回值的类型,选择不同的执行策略:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
//使用指定的resultHandler或者null
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//executor.query时传入的resultHandler为null
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
//executor.query时传入的resultHandler为null
//返回前,通过DefaultMapResultHandler对结果进行处理
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
//executor.query时传入的resultHandler为null
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
//executor.query时传入的resultHandler为null
}
在Executor执行是,如果传入的ResultHandler为null,则尝试从localCache获取结果,如果结果为null才会从Database中查询。
SimpleExecutor在doQuery时,会通过`configuration.newStatementHandler`时,会通过
`configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);`
创建ResultSetHandler,将resultHandler作为传入参数。
ResultSetHandler在其方法:
public List<Object> handleResultSets(Statement stmt) throws SQLException {...}
中,如果发现resultHandler为null,则会new DefaultResultHandler的实例,对result进行处理。