一、MyBatis中Sql执行过程
先上一段代码,看直接调用MyBatis Api是如何执行Sql的:
// 获取配置文件输入流 InputStream inputStream = Resources.getResourceAsStream("META-INF/spring/mybatis-config.xml"); // 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //构造数据源 BoneCPDataSource dataSource= new BoneCPDataSource(); dataSource.setDriverClass(driverClass); dataSource.setJdbcUrl(jdbcUrl); dataSource.setUsername(userNmae); dataSource.setPassword(password); String id = "SqlSessionFactoryBean"; TransactionFactory transactionFactory = new SpringManagedTransactionFactory(); Environment newEnv = new Environment(id, transactionFactory, dataSource); sqlSessionFactory.getConfiguration().setEnvironment(newEnv); SqlSession sqlSession = sqlSessionFactory.openSession(); // 获取UserMapper代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 执行Mapper方法,获取执行结果 List userList = userMapper.listAllUser();
大概过程是先获取到SqlSession实例,然后获取Mapper,再执行Mapper中相应的方法。
在前面文章 MyBatis3使用 介绍了Spring中如何使用MyBatis,这里再总结下:
1、先编写Mapper
@Mapperpublic interface UserMapper { ListlistAllUser();}
2、再编写Sql语句
<?xml version="1.0" encoding="UTF-8" ?>/span> "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.liujh.mapper.UserMapper"> <sql id="userFields"> id,name, phone,create_time sql> <select id="listAllUser" resultType="com.liujh.entity.UserEntity" > select <include refid="userFields"/> from user select>mapper>
注意上面接口中的方法名要和Xml中id对应上,并且Namespace也要对上。
那Sql到底是怎么执行的,明明第一步只定义了一个接口,Java中接口是不能实例化的,只能通过类来实例的,它是如何和我们在Xml中编写的Sql绑定的呢?
二、接口和Sql绑定过程
只要加了@Mapper的注解,框架就会为这个Bean定义的类型设置为MapperFactoryBean:
而MapperFactoryBean的getObject为:
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
其中mapperInterface为定义的接口。
getSqlSession返回SqlSessionTemplate,它的getMapper方法交给Configuration对象了:
public T getMapper(Classtype) { return getConfiguration().getMapper(type, this); }
Configuration对象的getMapper方法则交给了MapperProxyFactory:
public T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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); } }
MapperProxyFactory则返生成一个MapperProxy,再用JDK的动态代理来完成相关操作:
public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
即最后由MapperProxy来执行,我们知道JDK的动态代理要实现InvocationHandler接口,即实现invoke方法:
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); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);
cachedMapperMethod生成MapperMethod并且缓存:
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; }
所以最终的执行是由MapperMethod来完成的:
public MapperMethod(Class> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }
在初始化SqlCommand的时候会得到对应的sql语句的一些信息,如id和sql的类型:SELECT/UPDATE/INSERT:
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) { final String methodName = method.getName(); final Class> declaringClass = method.getDeclaringClass(); //查找对应的sql对应的MapperedStatement对象 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } }
其中resolveMappedStatement会在Configuration中查找是否有注册相应的MappedStatement,具体过程参考下一篇文章 MyBatis源码分析二:启动过程。
private MappedStatement resolveMappedStatement(Class> mapperInterface, String methodName, Class> declaringClass, Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; }
最后我们再看下执行的过程:
public Object execute(SqlSession sqlSession, Object[] args) { 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); } 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; }
这里以SELECT命令并且返回一个集合为例,执行的堆栈如下:
在执行过程中会通过方法和类名从Configuration对象中得到相应的MappedStatement,然后交由相应的Executor执行
@Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); 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(); } }
因代码比较多就不一一详列了,大概的交互如下:
MapperMethod——》SqlSession(SqlSessionTemplate)——》Executor(CachingExecutor)——》SimpleExecutor
接下来是调用JDBC相关API完成操作。
@Override public Listquery(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler. handleResultSets(ps); }
三、总结
MyBatis使用动态代理技术将接口和Xml中的Sql语句关联起来,具体来说为每一个接口创建一个MapperProxy;
接口中每一个方法对应一个MapperMethod,执行接口的方法就是执行MapperMethod的execute方法,在执行过程中通过查找接口名称对应的MapperedStatement对象(代表一条Sql语句)来执行相应的Sql,从而达到接口和Sql关联 。
MyBatis源码分析二:启动过程
MyBatis源码分析一:核心组件
MyBatis3使用
RabbitMQ Fedration插件