Mybatis源码解析-sql执行
一、傳統的jdbc操作步驟
- 獲取驅動
- 獲取jdbc連接
- 創建參數化預編譯的sql
- 綁定參數
- 發送sql到數據庫執行
- 將將獲取到的結果集返回應用
- 關閉連接
傳統的jdbc代碼:
package com.zjp;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet;public class JDBCTest {public static void main(String[] args) {try {Connection con = null; //定義一個MYSQL鏈接對象Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驅動con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true", "root", "root"); //鏈接本地MYSQL//更新一條數據String updateSql = "UPDATE user_t SET user_name = 'test' WHERE id = ?";PreparedStatement pstmt = con.prepareStatement(updateSql);pstmt.setString(1, "1");long updateRes = pstmt.executeUpdate();System.out.print("UPDATE:" + updateRes);//查詢數據并輸出String sql = "select * from user_t where id = ?";PreparedStatement pstmt2 = con.prepareStatement(sql);pstmt2.setString(1, "1");ResultSet rs = pstmt2.executeQuery();while (rs.next()) { //循環輸出結果集String id = rs.getString("id");String username = rs.getString("user_name");System.out.print("\r\n\r\n");System.out.print("id:" + id + ",username:" + username);}//關閉資源rs.close();pstmt.close();pstmt2.close();con.close();} catch (Exception e) {e.printStackTrace();}} }二、mybatis 執行sql
mybatis要執行sql,同樣也需要獲取到數據庫連接,這個在mybatis里面就是sqlSession
// 使用MyBatis提供的Resources類加載mybatis的配置文件 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); // 構建sqlSession的工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sqlSessionFactory.openSession();2.1 獲取mapper對象
獲取到了session對象之后就是獲取mapper對象了,在mybatis中是使用動態代理的方式獲取
@Overridepublic <T> T getMapper(Class<T> type) {//最后會去調用MapperRegistry.getMapperreturn configuration.<T>getMapper(type, this);} /*** 返回代理類* @param type* @param sqlSession* @return*/@SuppressWarnings("unchecked")public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// MapperProxyFactory去把代理類做出來final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);}} @SuppressWarnings("unchecked")protected T newInstance(MapperProxy<T> mapperProxy) {//用JDK自帶的動態代理生成映射器return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}- 通過session獲取mapper,session通過MapperProxyFactory代理對象去獲取mapper對象,最終在newInstance中我們看到了使用jdk自帶的動態代理方式獲取到了mapper對象。
2.2 執行sql語句
現在獲取到了mapper對象,那么下一步就是執行sql語句了。
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//代理以后,所有Mapper的方法調用時,都會調用這個invoke方法//并不是任何一個方法都需要執行調用代理對象進行執行,如果這個方法是Object中通用的方法(toString、hashCode等)無需執行if (Object.class.equals(method.getDeclaringClass())) {try {return method.invoke(this, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}//這里優化了,去緩存中找MapperMethodfinal MapperMethod mapperMethod = cachedMapperMethod(method);//執行 sqlreturn mapperMethod.execute(sqlSession, args);}所有的sql語句都會調用invoke方法,會后都要調用execute來執行sql語句
public Object execute(SqlSession sqlSession, Object[] args) {Object result;//可以看到執行時就是4種情況,insert|update|delete|select,分別調用SqlSession的4大類方法if (SqlCommandType.INSERT == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));} else if (SqlCommandType.UPDATE == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));} else if (SqlCommandType.DELETE == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));} else if (SqlCommandType.SELECT == command.getType()) {if (method.returnsVoid() && method.hasResultHandler()) {//如果有結果處理器executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {//如果結果有多條記錄result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {//如果結果是mapresult = executeForMap(sqlSession, args);} else {//否則就是一條記錄Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}} else {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;}這個方法也就是通過使用枚舉的方式執行insert|update|delete|select,分別執行不同的方法。這里跟著select,這個也是最復雜的。在這里我們看到了很重要的一個方法
result = sqlSession.selectOne(command.getName(), param);通過源碼可知selectOne方法轉而去調用selectList,很簡單的,如果得到0條則返回null,得到1條則返回1條,得到多條報TooManyResultsException錯,特別需要主要的是當沒有查詢到結果的時候就會返回null。因此一般建議在mapper中編寫resultType的時候使用包裝類型而不是基本類型,比如推薦使用Integer而不是int。這樣就可以避免NPE
//核心selectOne@Overridepublic <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.<T>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 {//根據statement id找到對應的MappedStatementMappedStatement ms = configuration.getMappedStatement(statement);//轉而用執行器來查詢結果,注意這里傳入的ResultHandler是nullreturn 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();}}從selectList方法中可以看出來,最終的查詢還是交給了executor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {//得到綁定sqlBoundSql boundSql = ms.getBoundSql(parameter);//創建緩存KeyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//查詢return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}從MappedStatement對象中獲取到BoundSql對象,BoundSql對象包含了我們需要執行的sql語句。
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//這里看到ResultHandler傳入了StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//準備語句stmt = prepareStatement(handler, ms.getStatementLog());//StatementHandler.queryreturn handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}通過一些列的跟蹤,定位到了doQuery方法,最終sql語句的執行交給了StatementHandler對象,這個對象也就是我們最常用的,封裝的是PreparedStatement
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();// 結果交給了ResultSetHandler 去處理return resultSetHandler.<E> handleResultSets(ps);}很明顯這里就是使用的PreparedStatement進行處理。
public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());final List<Object> multipleResults = new ArrayList<Object>();int resultSetCount = 0;ResultSetWrapper rsw = getFirstResultSet(stmt);List<ResultMap> resultMaps = mappedStatement.getResultMaps();//一般resultMaps里只有一個元素int resultMapCount = resultMaps.size();validateResultMapsCount(rsw, resultMapCount);while (rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = resultMaps.get(resultSetCount);handleResultSet(rsw, resultMap, multipleResults, null);rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}String[] resultSets = mappedStatement.getResulSets();if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();ResultMap resultMap = configuration.getResultMap(nestedResultMapId);handleResultSet(rsw, resultMap, null, parentMapping);}rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}}return collapseSingleResultList(multipleResults);}最后就是返回結果集了。
總結
以上是生活随笔為你收集整理的Mybatis源码解析-sql执行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mybatis源码解析之Mybatis初
- 下一篇: 英语在线听力翻译器_仁爱版初一英语免费听