mybatis foreach執行多條sql配置_MyBatis原始碼分析三:Sql執行
一、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外掛