Configuration组成
configuration的属性主要分为两大部分
- 从mybatis-config.xml中读取的配置
- 从mapper配置文件或Mapper注解读取的配置
主要对象
//与XML配置文件对应的对象
protected Environment environment;
protected boolean cacheEnabled = true; //是否开启一级缓存
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; //默认Type
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); //Mapper仓库
protected final InterceptorChain interceptorChain = new InterceptorChain(); //插件链条
//根据NameSpace+方法名作为ID,存储Mapper信息(sql语句,StatementType,ResultSetType,resultMap)
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//存储所有Mapper配置文件中的ResultMap节点
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
//?
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
其中类MappedStatement存储有如下信息:
sqlSource,ResultSetType,SqlCommandType,resultMaps,StatementType(STATEMENT, PREPARED, CALLABLE)
Configuration加载过程:
- XMLConfigBuilder解析mybatis-config.xml的配置到Configuration中
- 针对Configuration节点下的每个Mapper节点,循环调用XMLMapperBuilder解析Mapper到mappedStatements 属性
- 加载其他配置项:插件/拦截器、对象工厂、setting项。
备注:
一级缓存配置方法:
<setting name="localCacheScope" value="SESSION|STATEMENT"/>
MappedStatement说明
根据namespace+方法名存储Mapper中每个方法对应的所有配置, 主要属性如下:
//节点中的id:amespace+方法名
private String id;
//直接从节点属性中取
private Integer fetchSize;
//直接从节点属性中取
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
//对应一条SQL语句
private SqlSource sqlSource;
//每条语句都对就一个缓存,如果有的话。
private Cache cache;
//这个已经过时了
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
//SQL的类型,select/update/insert/detete
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
//是否有内映射
private boolean hasNestedResultMaps;
private String databaseId;
private String[] resultSets;
SqlSource接口
SqlSource表示从mapper.xml或注解中读取的sql内容,继承类包括:DynamicSqlSource,StaticSqlSource,ProviderSqlSource,RawSqlSource, 主要功能用来生成Sql语句(或动态语句), 不细讲.
SqlSession:
sqlSession主要方法:
private final Configuration configuration;
private final Executor executor;
<T> T selectOne(String statement, Object parameter);
<T> T getMapper(Class<T> type);
//主要操作全部用Executor及Configuration来实现的.
public Connection getConnection() {
return executor.getTransaction().getConnection();
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
//从Configuration获取一个MappedStatement配置
MappedStatement ms = configuration.getMappedStatement(statement);
//直接调用executor.query()方法
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
}
在DefaultSqlSession中有一个Executor对象,对象数据的操作都是由这个Executor来完成。
Executor持有一个Transaction对象
所以sqlsession的处理逻辑可以如下理解:
=> DefaultSqlSession将select/update/insert/delete/commit/rollback/close交由Executor处理
=> Executor又将commit/rollback/close方法交由Transaction处理
=> DefaultSqlSession/Executor/Transaction对象都在DefaultSqlSessionFactory.openSessionFromDataSource方法中创建
备注:
- 每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心.同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,应该在应用执行期间都存在, 没有任何理由对它进行清除或重建。在应用运行期间不要重复创建多次,建议使用单例模式
- SqlSession则需要每个线程持有不同的对象,也就是说它不是线程安全的。
- Executor中有三个对象来帮助他完成MappedStatement的执行工作。
StatementHandler负责从连接中获取一个Statement对象
ParameterHandler负责对Statement设置参数
ResultHandler负责生成查询语句的结果集
Executor
BaseExecutor为模板模式中的模板类, 子类包括ReuseExector, BatchExecutor,SimpleExecutor,
CachingExecutor 为装饰这模式, 结构图如下;
Executor主要结构:
//执行查询
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
//执行update/insert/delete
int update(MappedStatement ms, Object parameter) throws SQLException;
//事务提交
void commit(boolean required) throws SQLException;
//事务回滚
void rollback(boolean required) throws SQLException;
//注意主要操作由StateMentHandler来操作
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
Executor主要操作由StateMentHandler来执行,(事务由Transaction对象处理),
参数映射由ParameterHandler 执行,
type映射由TypeHandler执行
结果映射由ResultSetHandler 执行.
StatementHandler
Executor利用StatementHandler执行SQL, StatementHandler执行过程如下:
- 获取一个数据库连接
- 调用StatementHandler.prepare()方法获取一个statement
- 调用StatementHandler.parameterize()方法设置sql执行时所需要的参数
- 调用StatementHandler.update或query方法执行sql
跟Executor的实现非常相似, 其类结构图如下
拦截器 Plugin
Mybatis是用jdk的动态代理来实现拦截器的,
详情参照 https://blog.csdn.net/yangsnow_rain_wind/article/details/79777137
缓存
一级缓存
- 一级缓存的生命周期与SqlSession的生命周期一样。
- 一级缓存是在BaseExecutor中实现, 是非线程安全的,好在是在sqlSession中使用的, sqlSession也是在单个线程中使用.而且访问完立即释放.
- 一级缓存只在同一个SqlSession中共享数据
- 在同一个SqlSession对象执行相同的sql并参数也要相同,缓存才有效。
- 如果在SqlSession中执行update/insert/detete语句的话,SqlSession中的executor对象会将一级缓存清空。
配置方式:
<setting name="localCacheScope" value="SESSION|STATEMENT"/>
二级缓存:
- 二级缓存对所有的SqlSession对象都有效,
- 在一个SqlSession中可以执行多个不同命名空间中的sql,也是就说一个SqlSession需要对多个Cache进行操作。
- 二级缓存不建议使用,, 最好使用自己其他方案代替,如 redis等
二级缓存的生命周期跟SqlSessionFactory一样,通常在整个应用中有效。二级缓存是通过CachingExecutor来实现的。
MapperProxy
Mapper接口是通过JDK动态代理实现的, 此处的代理可以认为一个”虚假”的动态代理,
方法被调用时会被mapperProxy拦截, myBatis会自动生成MapperProxy实例, 动态调用sqlsession完成连接数据库任务, 与mapper接口的实例压根没有关系,所以也就不需要再定义Mapper接口的实现类,
Mybatis编程中,开发人员只需要定义好Mapper接口(如:UserDao)就好了.
标准JDK动态代理 如下图:
MyBatis下只有接口定义而没有接口实现,代理图如下,可
MapperProxyFactory 源码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//这个MapperProxyFactory是调用addMapper方法时加到knownMappers中的,
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
//说明这个Mapper接口没有注册
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
//生成一个MapperProxy对象
return mapperProxyFactory.newInstance(sqlSession);
}
public T newInstance(SqlSession sqlSession) {
//创建一个Mapperxy对象,这个方法实现了JDK动态代理中的InvocationHandler接口
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//mapperInterface,说明Mapper接口被代理了,这样子返回的对象就是Mapper接口的子类,方法被调用时会被mapperProxy拦截,也就是执行mapperProxy.invoke()方法
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
- 在Mybatis提供的编程接口中,开发人员只需要定义好Mapper接口(如:UserDao),无需去实现
- 每个Mapper接口都会对应一个MapperProxyFactory对象实例,这个对应关系在Configuration.mapperRegistry.knownMappers中。
- 当getMapper()方法被调用时,Mybatis会找到相对应的MapperProxyFactory对象实例,利用这个工厂来创建一个jdk动态代理对象,是这个Mapper接口的实现类,当Mapper定义的方法被调用时,会调用MapperProxy来处理。
- MapperProxy会根据方法找到对应的MapperMethod对象来实现这次调用。
- MapperMethod对应会读取方法中的注解,从Configuration中找到相对应的MappedStatement对象,再执行。
小结:
- Mapperxy对象实现了JDK动态代理中的InvocationHandler接口
- 用户定义的Mapper接口被MapperProxy对象代理了,
- MapperMethod对象. Mapper接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系,这个methodCache是在MapperProxyFactory中持有的,MapperProxyFactory又是在Configuration中持有的,所以每个Mapper接口类对应的MapperProxyFactory和methodCache在整个应用中是共享的,一般只会有一个实例.
- 当getMapper()方法被调用时,Mybatis会找到相对应的MapperProxyFactory对象实例,利用这个工厂来创建一个jdk动态代理对象,是这个Mapper接口的实现类,当Mapper定义的方法被调用时,会调用MapperProxy来处理。
- MapperProxy会根据方法找到对应的MapperMethod对象来实现这次调用。
- MapperMethod对应会读取方法中的注解,从Configuration中找到相对应的MappedStatement对象,再执行。
其他
MyBatis主要对像生命周期:
name | scope | 备注 |
---|---|---|
SqlSessionFactoryBuilder | method | 只是在方法内部创建,方法结束时即消失 |
SqlSessionFactory | application | Application中, 创建之后就一直存在,习惯被集成的Spring的Bean中 |
SqlSession | request/method | |
Mapper | method |
MyBatis分页:
自带逻辑分页为逻辑分页, 全部加载数据, 然后在内存中分页
物理分页:
可以使用分页插件 Pagehelper
地址: https://github.com/pagehelper/Mybatis-PageHelper
不足:
SqlSession在一个生命周期中会产生大量的临时对象,如:Executor、Transaction、Cache、MetaObject、StatementHandler、ParameterHandler、ResultSetHandler等待。但是SqlSession的生命周期是非常短的。这样造成大量对象的产生,给JVM的GC工作带来了很多的消耗,这个应该是Mybatis比较大的缺点了。
备注:
看源码本来已经有点辛苦了, 把有限的知识写出来更累, 把知识写的有条理, 能让他人看懂更NND累.。
因资料比较多,引用的一些代码及图片没有备注出来, 望网友们海涵……
因知识有限,不足之处朋友们及时提出来, 多谢多谢,见谅见谅。