mybatis源码分析
SqlSession sqlSession = null;
try {
// 加载mybatis核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 构建工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 创建sqlSession
sqlSession = sqlSessionFactory.openSession();
// 获取映射器
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 查询结果
User user = userMapper.findOne(1L);
System.out.println(userMapper.getClass());
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}finally {
// 关闭连接
MybatisUtil.INSTANCE.closeSession(sqlSession);
}
1.创建sqlsessionfactory的流程
1.创建sqlsessionfactorybuilder对象
2.build(inputstream)
3.xmlconfigbuilder创建解析器parser
4.解析核心配置文件mybatis-config每一个标签信息,封装在configuration中
5.解析mapper.xml,返回configuration,其中包含所有配置信息,例如resulttype,resultmap,sql的原信息
5.1 其中mappedstatement对应一个包含增删改查所有信息,例如select等,
5.2 mapperregistry中有生成代理mapper类
6.build(config)返回创建的defaultsqlsessionfactory
总结:解析文件的配置信息,封装在configuration中,返回defaultsqlsessionfactory
2.创建sqlsession的流程
1.从数据库中opensession
2.configuration根据配置的类型创建对应的执行器executor,默认是simple,executor做crud
3.每个executor创建时会调用拦截器链(interceptorchain.pluginAll)加载所有插件
4.最后返回defaultsqlsession,其中包含executor以及configuration等
3.getmapper,返回底层动态代理对象-mapperproxy
configuration.getmapper(type)
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//=========================================================
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 {
// 创建mapperproxy代理对象并返回,其中包含sqlsession做crud,这里使用到了jdk动态代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//=================================================================
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 这里是jdk动态代理代码
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
mapperInterface }, mapperProxy);
}
总结:使用mapperRegistry获取mapper,底层会调用mapperProxyFactory产生动态代理对象MapperProxy并返回
MapperProxy中包含sqlsession做crud
4.mybatis查询流程
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// MapperProxy调用MapperMethod执行sql语句
return mapperMethod.execute(sqlSession, args);
}
// ===================================================================
public Object execute(SqlSession sqlSession, Object[] args) {
// 根据crud的类型执行不同
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 这里就是查询单个的结果
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// =================================================================
// 这里查询单个结果
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 单个也是查询的selectList然后取第1条,如果查出多条就会报错
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
// =======================================================================
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用执行器执行查询,这里是cachingExecutor
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// ===========================================================================
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 这里获取sql信息,BoundSql中有sql语句以及参数等
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建缓存的key,key的规则是namespace+sql语句+id+参数+环境等
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 执行查询
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// ================================================================================
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;
}
}
// 这里调用simpleExcutor执行查询,没有开启二级缓存
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//================================================================================
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 这里从数据库中去查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
//===========================================================
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;
}
//================================================================
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 ,StatementHandler也会加入Configuration总的 interceptorChain拦截器链
// 这里父类的BaseStatementHandler会赋值2个handler,
//this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
// 其中parameterHandler-->defaultParameterHandler.setParameters(PreparedStatement ps)设置预编译参数
// 其中resultsetHandler-->defaultResultsetHandler.handleResultSets(Statement stmt)处理结果集
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler去执行Statement语句
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// ====================================================
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 这里使用的是原生jdbc操作
ps.execute();
// 处理结果集,这里使用DefaultResultSetHandler处理结果集,然后返回流程结束
return resultSetHandler.handleResultSets(ps);
}
mybatis查询流程总结
- MapperProxy调用MapperMethod执行sql语句
- 根据crud类型,sqlSession.selectList方法
- 没有开启二级缓存,调用simpleExcutor执行查询
- 查询先从二级缓存(cache)中拿,再从(localcache)中拿,没有的话最后调用queryfromDB,查出的数据放入一级缓存中
- baseExecutor#doQuery,创建StatementHandler,里面BaseStatementHandler会赋值2个handler
- 其中parameterHandler-->defaultParameterHandler.setParameters(PreparedStatement ps)设置预编译参数
- 其中resultsetHandler-->defaultResultsetHandler.handleResultSets(Statement stmt)处理结果集
- 底层是原生jdbc操作 ,使用DefaultResultSetHandler处理结果集,然后返回流程结束
创建mybatis四大对象 [parameterHandler,executor,resultsetHandler,StatementHandler] 时
都会interceptorChain.pluginAll()加载插件然后返回,这也是mybatis插件的原理,接下来就来自定义插件
自定义mybatis插件
- 步骤1:实现mybatis中interceptor接口
- 步骤2:配置签名,拦截目标类的目标方法
- 步骤3:配置插件到核心配置文件中
/**
* 自定义插件
*/
// 配置插件签名,当前插件拦截什么对象什么方法,这里拦截StatementHandler设置参数的方法
@Intercepts({
@Signature(type = StatementHandler.class,method = "parameterize",args = Statement.class)
})
public class MyPlugin1 implements Interceptor {
/**
* 拦截的目标对象目标方法的执行
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
System.out.println("MyPlugin1....拦截的目标对象:"+target);
System.out.println("MyPlugin1....拦截的方法:"+invocation.getMethod());
System.out.println("MyPlugin1....拦截的参数:"+ Arrays.toString(invocation.getArgs()));
// 动态改变查询的参数
// 获取目标对象的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
// 获取参数PreparedStatementHandler中的parameterObject属性 parameterHandler.parameterObject
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("获取的参数值:"+value);
// 修改一下参数
metaObject.setValue("parameterHandler.parameterObject",3L);
// 执行目标方法
Object proceed = invocation.proceed();
return proceed;
}
/**
* 为目标对象创建一个代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
// 使用动态代理,mybatis提供了一个包装工具类Plugin.wrap
System.out.println("将要包装的对象"+target);
Object wrap = Plugin.wrap(target, this);
return wrap;
}
/**
* 插件注册时,设置属性值
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("设置的属性值:"+properties);
}
}
<!--配置插件-->
<plugins>
<plugin interceptor="cn.demo.interceptor.MyPlugin1">
<!--设置属性-->
<property name="name" value="zs"/>
<property name="age" value="18"/>
</plugin>
</plugins>
备注:配置多个插件执行流程:创建动态代理时,按照插件配置顺序层层代理,执行目标方法是倒序执行
分页插件pageHelper的使用
- 引入依赖
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
- 注册分页插件
<!--注册分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
- 代码中使用
// 使用分页插件,用PageHelper或者pageInfo
Page<Object> page = PageHelper.startPage(1, 2);
List<User> list = userMapper.findAll();
list.forEach(e->System.out.println(e));
System.out.println("总条数:"+page.getTotal());
System.out.println("当前页:"+page.getPageNum());
System.out.println("数据:"+page.getResult());
mybatis批量操作
创建sqlsession时指定ExecutorType.BATCH,默认是simple,也可以在全局配置setting中指定,但这样不是很灵活
在spring中使用单独配置一个sqlsession,指定为批量执行器,使用时注入sqlsession即可
<!--配置批量执行器 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
mybatis自定义类型处理器-typeHandler
<!--指定类型处理器-->
<typeHandlers>
<!--处理枚举类型用索引方式,指定处理的java类型为xx,不指定就是所有的枚举类型都用这个处理
保存枚举的时候默认保存的是枚举的名字,用的EnumTypeHandler
-->
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="cn.demo.domain.Gender"/>
</typeHandlers>
保存枚举希望保存的是状态码,这里需要用到自定义类型处理器
- 实现typeHandler接口或者继承BaseTypeHandler
package cn.demo.typehandler;
import cn.demo.domain.Gender;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 自定义类型处理器,处理枚举类型
*/
public class MyEnumTypeHandler implements TypeHandler<Gender> {
@Override
public void setParameter(PreparedStatement ps, int i, Gender parameter, JdbcType jdbcType) throws SQLException {
// 将枚举类型保存为状态码
ps.setString(i, String.valueOf(parameter.getCode()));
}
@Override
public Gender getResult(ResultSet rs, String columnName) throws SQLException {
// 根据数据库中拿到的状态码返回枚举对象
String code = rs.getString(columnName);
return Gender.getGenderByCode(Integer.valueOf(code));
}
@Override
public Gender getResult(ResultSet rs, int columnIndex) throws SQLException {
// 根据数据库中拿到的状态码返回枚举对象
return Gender.getGenderByCode(Integer.valueOf(rs.getString(columnIndex)));
}
@Override
public Gender getResult(CallableStatement cs, int columnIndex) throws SQLException {
// 根据数据库中拿到的状态码返回枚举对象
return Gender.getGenderByCode(Integer.valueOf(cs.getString(columnIndex)));
}
}
- 重写方法,设置要保存的东西
- 配置在核心配置文件,typeHandler中
<typeHandlers>
方式1:直接配置在核心配置文件中
<typeHandler handler="cn.demo.typehandler.MyEnumTypeHandler" javaType="cn.demo.domain.Gender"/>
方式2:在插入字段上指定类型处理器,结果映射集上指定返回的枚举类型处理
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into user (name,pwd,gender)
values (#{name},#{pwd},#{gender,typeHandler=类型处理器全类名})
</insert>
<!--自定义结果映射集-->
<resultMap id="baseResult" type="User">
<id column="id" property="id"/>
<!--返回结果字段处理枚举类型,注意设置参数和返回结果集枚举类型处理须保持一致-->
<result column="gender" property="gender" typeHandler="cn.demo.typehandler.MyEnumTypeHandler"/>
</resultMap>
</typeHandlers>