MyBatis执行Sql的流程实例解析

网友投稿 545 2023-07-04

MyBatis执行Sql的流程实例解析

MyBatis执行Sql的流程实例解析

这篇文章主要介绍了MyBatis执行Sql的流程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

本博客着重介绍MyBatis执行Sql的流程,关于在执行过程中缓存、动态SQl生成等细节不在本博客中体现,相应内容后面再单独写博客分析吧。

还是以之前的查询作为列子:

public class UserDaoTest {

private SqlSessionFactory sqlSessionFactory;

@Before

public void setUp() throws Exception{

ClassPathResource resource = new ClassPathResource("mybatis-config.xml");

InputStream inputStream = resource.getInputStream();

sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

}

@Test

public void selectUserTest(){

String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";

SqlSession sqlSession = sqlSessionFactory.openSession();

CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);

Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);

System.out.println(cbondissuer);

sqlSession.close();

}

}

之前提到拿到sqlSession之后就能进行各种CRUD操作了,所以我们就从sqlSession.getMapper这个方法开始分析,看下整个Sql的执行流程是怎么样的。

获取Mapper

进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:

public T getMapper(Class type, SqlSession sqlSession) {

//mapperRegistry实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml

//mapperRegistry的key是接口的全限定名,比如com.csx.demo.spring.boot.dao.SysUserMapper

//mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy(动态代理类)

return mapperRegistry.getMapper(type, sqlSession);

}

进入getMapper方法:

public T getMapper(Class type, SqlSession sqlSession) {

final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);

//如果配置文件中没有配置相关Mapper,直接抛异常

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);

}

}

进入MapperProxyFactory的newInstance方法:

public class MapperProxyFactory {

private final Class mapperInterface;

private final Map methodCache = new ConcurrentHashMap();

public MapperProxyFactory(Class mapperInterface) {

this.mapperInterface = mapperInterface;

}

public Class getMapperInterface() {

return mapperInterface;

}

public Map getMethodCache() {

return methodCache;

}

//生成Mapper接口的动态代理类MapperProxy

@SuppressWarnings("unchecked")

protected T newInstance(MapperProxy mapperProxy) {

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

public T newInstance(SqlSession sqlSession) {

final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);

return newInstance(mapperProxy);

}

}

下面是动态代理类MapperProxy,调用Mapper接口的所有方法都会先调用到这个代理类的invoke方法(注意由于Mybatis中的Mapper接口没有实现类,所以MapperProxy这个代理对象中没有委托类,也就是说MapperProxy干了代理类和委BhsnQNI托类的事情)。好了下面重点看下invoke方法。

//MapperProxy代理类

public class MapperProxy implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;

private final SqlSession sqlSession;

private final Class mapperInterface;

private final Map methodCache;

public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {

this.sqlSession = sqlSession;

this.mapperInterface = mapperInterface;

thisBhsnQNI.methodCache = methodCache;

}

@Override

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 (isDefaultMethod(method)) {

return invokeDefaultMethod(proxy, method, args);

}

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

//获取MapperMethod,并调用MapperMethod

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

private MapperMethod cachedMapperMethod(Method method) {

MapperMethod mapperMethod = methodCache.get(method);

if (mapperMethod == null) {

mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

methodCache.put(method, mapperMethod);

}

return mapperMethod;

}

@Usesjava7

private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)

throws Throwable {

final Constructor constructor = MethodHandles.Lookup.class

.getDeclaredConstructor(Class.class, int.class);

if (!constructor.isAccessible()) {

constructor.setAccessible(true);

}

final Class> declaringClass = method.getDeclaringClass();

return constructor

.newInstance(declaringClass,

MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED

| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)

.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);

}

/**

* Backport of java.lang.reflect.Method#isDefault()

*/

private boolean isDefaultMethod(Method method) {

return ((method.getModifiers()

& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)

&& method.getDeclaringClass().isInterface();

}

}

所以这边需要进入MapperMethod的execute方法:

public Object execute(SqlSession sqlSession, Object[] args) {

Object result;

//判断是CRUD那种方法

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);

}

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;

}

然后,通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

Statement stmt = null;

try {

Configuration configuration = ms.getConfiguration();

//内部封装了ParameterHandler和ResultSetHandler

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

stmt = prepareStatement(handler, ms.getStatementLog());

//StatementHandler封装了Statement, 让 StatementHandler 去处理

return handler.query(stmt, resultHandler);

} finally {

closeStatement(stmt);

}

}

接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:

public List query(Statement statement, ResultHandler resultHandler) throws SQLException {

//到此,原形毕露, PreparedStatement, 这个大家都已经滚瓜烂熟了吧

PreparedStatement ps = (PreparedStatement) statement;

ps.execute();

//结果交给了ResultSetHandler 去处理,处理完之后返回给客户端

return resultSetHandler. handleResultSets(ps);

}

到此,整个调用流程结束。

简单总结

这边结合获取SqlSession的流程,做下简单的总结:

SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、-配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;

拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的-方法。

获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;

获得SqlSession对象后就能执行各种CRUD方法了。

以上是获得SqlSession的流程,下面总结下本博客中介绍的Sql的执行流程:

调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制);

MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;

往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。

Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。

调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。

Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。

重要类

MapperProxyFactory

MapperProxy

MapperMethod

SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;

Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;

StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,

ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换

MappedStatement MappedStatement维护了一条

SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

BoundSql 表示动态生成的SQL语句以及相应的参数信息

Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:JVM内存结构划分实例解析
下一篇:Spring @Transactional注解失效解决方案
相关文章

 发表评论

暂时没有评论,来抢沙发吧~