MyBatis总结
参考
https://mybatis.org/mybatis-3/zh/getting-started.html
【MyBatis系列4】一对一,一对多,多对多查询及延迟加载(N+1问题)分析
【MyBatis系列5】MyBatis4大核心对象SqlSessionFactoryBuiler,SqlSessionFactory,SqlSession,Mapper
1. demo
1.配置数据源:db.properties
2.配置mybatis
- 注意mapper.xml
3.确认实体类:Country
- Country
- CountryMapper
- CountryMapper.xml
4.Demo.main
- 获取mybatis-config.xml的文件流
- 构造SqlSessionFactory
- 得到SqlSession
- 获取CountryMapper
- 执行SQL
2. mybatis-config
<?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>
<properties resource="db.properties"></properties>
<settings>
<!-- 打印查询语句 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
<!-- 开启驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 别名 -->
<typeAliases/>
<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="CountryMapper.xml"/>
</mappers>
</configuration>
3. mapper
默认本地一级缓存:
开启全局二级缓存
3.1 resultMap
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
3.2 sql
if
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
choose、when、otherwise
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim、where、set
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
foreach
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
3.3 cache
<cache/>
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<cache type="com.domain.something.MyCustomCache"/>
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
3.4 CRUD
<select id="selectAuthor" parameterType="int" resultType="hashmap">
SELECT * FROM Author WHERE ID = #{id}
</select>
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
4. 日志
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>
可选值:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log
接口,且构造方法以字符串为参数的类完全限定名。
更细粒度的配置:比如包、mapper、方法
log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE
log4j.logger.org.mybatis.example.BlogMapper=TRACE
log4j.logger.org.mybatis.example=TRACE
5. 接口分析
SqlSessionFactoryBuilder
XMLConfigBuilder
Configuration
SqlSessionFactory
ExecutorType
TransactionFactory
Transaction
TransactionIsolationLevel
SqlSession
根据mybatis的配置,得到文件流,并执行SqlSessionFactoryBuilder#build方法,最后返回SqlSessionFactory
// SqlSessionFactoryBuilder build XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); Configuration config = parser.parse(); return new DefaultSqlSessionFactory(config); // XMLConfigBuilder parse this.parseConfiguration(this.parser.evalNode("/configuration")); // XMLConfigBuilder parseConfiguration(说明:下面方法读取到的信息都填充到config中) 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); environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers"));
根据配置信息,得到SqlSession
根据Environment配置信息(一般会配置事务管理器和数据源),获取事务工厂,JDBC居多,从而获取JdbcTransactionFactory,然后根据数据源 + 事务隔离级别 + 是否自动提交,获取Transaction
// 提供接口指定事务隔离级别 SqlSession openSession(TransactionIsolationLevel level)
public enum TransactionIsolationLevel { NONE(Connection.TRANSACTION_NONE), READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED), READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED), REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ), SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE); private final int level; }
<transactionManager type="JDBC"/> <transactionManager type="MANAGED"/>
上面生成Transaction对象后,用于生成Executor,有三种类型:SIMPLE, REUSE, BATCH
在
new XxxExecutor()
后,会执行interceptorChain.pluginAll(executor);
最后返回SqlSession:
TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit);
获取Mapper代理接口,执行CRUD
mapper工厂什么时候填充到knownMappers呢?
// DefaultSqlSession getMapper return configuration.getMapper(type, this); // Configuration getMapper return mapperRegistry.getMapper(type, sqlSession); // MapperRegistry getMapper(说明:Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>()) MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); return mapperProxyFactory.newInstance(sqlSession);
6. mapper工厂:mybatis动态代理
MapperRegistry.addMapper -> Configuration.addMapper -> XMLMapperBuilder.addMapper, bindMapperForNamespace, parse -> XMLConfigBuilder.mapperElement, parseConfiguration, parse -> SqlSessionFactoryBuilder.build
在获取SqlSessionFactory的时候,会解析mybatis-config.xml,期间会处理mappers
从mybatis-config获取mappers结点并遍历,从而获取mapper.xml的流,构造XMLMapperBuilder
XMLMapperBuilder执行parse,首先处理结点信息,存储到成员属性中
new一个mapper.xml对应接口的工厂,用Map存储,最后在XxxMapper mapper = sqlSession.getMapper时生成代理类
mapperProxyFactory.newInstance(sqlSession);
// XMLMapperBuilder.parse
/*
cache-ref 属性cacheRefMap、builderAssistant
cache 属性caches、builderAssistant
parameterMap
resultMap
sql
select|insert|update|delete
builderAssistant.addMappedStatement方法:针对CRUD SQL语句
最终,保存到config.mappedStatements中(Configuration类属性:Map<String, MappedStatement> mappedStatements)
*/
configurationElement();
/*
获取namespace,获取对应接口的Class<?>类型,添加到configuration
mapperRegistry.addMapper(type);
knownMappers.put(type, new MapperProxyFactory<>(type));
*/
bindMapperForNamespace();
/**
mapperInterface对应mapper.xml的namespace接口的Class对象
methodCache
*/
public class MapperProxyFactory<T> {
private Class<T> mapperInterface;
private Map<Method, MapperMethodInvoker> methodCache =
new ConcurrentHashMap<>();
}
-
使用的时候,通过SqlSession获取XxxMapper代理接口工厂,即MapperProxyFactory
-
执行newInstance,生成对应代理类
MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
关键参数:mapperProxy。
动态代理可以将所有接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法,即MapperProxy的invoke方法
cachedInvoker(method).invoke(proxy, method, args, sqlSession);
cachedInvoker(method)用于获取MapperMethodInvoker
private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { super(); this.mapperMethod = mapperMethod; } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } }
-
获取MappedStatement ms
上述mapperMethod.execute实质根据CRUD类型调用SqlSession执行对应方法executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
Executor,openSession默认为SIMPLE,还有BATCH和REUSE
默认使用StatementType.PREPARED,CRUD中可指定statementType
SQL执行:调用StatementHandler,然后PreparedStatement statement.execute(sql);- SimpleExecutor:
- BatchExecutor:批处理
- CallableStatementHandler:存储过程
7. typeHandler
在XMLConfigBuilder执行时,会读取mybatis-config.xml,此时会注册标签"typeHandlers",使用Map存储到typeHandlerMap,后续从typeHandler中,parameter用于setter,result用于getter。
XMLMapperBuilder
"/mapper/parameterMap" Configuration的成员属性parameterMaps
"/mapper/resultMap" Configuration的成员属性resultMaps
记录
XMLConfigBuilder "/configuration"
Configuration
variables "properties"
vfsImpl "settings/vfsImpl"
logImpl "settings/logImpl"
typeAliasRegistry "typeAliases"
interceptorChain "plugins"
environment "environments"
(id,transactionFactory,dataSource)
typeHandlerRegistry "typeHandlers"
typeHandlerMap
作用:parameter用于setter,result用于get
其他:
"settings/*"
"objectFactory"
"objectWrapperFactory"
"reflectorFactory"
"databaseIdProvider"
XMLMapperBuilder "mappers"
"/mapper" Configuration
"namespace"
"cache-ref" cacheRefMap
"cache" caches
"/mapper/parameterMap" parameterMaps
ParameterMap(id, type, parameterMappings)
ParameterMapping
typeHandler(用于setter)
"/mapper/resultMap" resultMaps
ResultMap(id, type, ..)
说明:type。获取类,然后获取javaType,获取TypeHandler,用于getter
"/mapper/sql"
"select|insert|update|delete"
XMLStatementBuilder
includeParser.applyIncludes(context.getNode());
作用:解析include refid,应用<sql>
lang和selectKey处理
MappedStatement
...
总结
得到mapper代理接口
从knownMappers得到MapperProxyFactory
通过Proxy.newProxyInstance生成MapperProxy
返回代理实例
调用链:DefaultSqlSession, MapperRegistry, MapperProxyFactory, MapperProxy
执行查询
委托给MapperProxy
调用链:MapperProxy, DefaultSqlSession, Executor, StatementHandler, PreparedStatement