Mybatis源码解析-sql执行
一、傳統(tǒng)的jdbc操作步驟
- 獲取驅(qū)動(dòng)
- 獲取jdbc連接
- 創(chuàng)建參數(shù)化預(yù)編譯的sql
- 綁定參數(shù)
- 發(fā)送sql到數(shù)據(jù)庫(kù)執(zhí)行
- 將將獲取到的結(jié)果集返回應(yīng)用
- 關(guān)閉連接
傳統(tǒng)的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; //定義一個(gè)MYSQL鏈接對(duì)象Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驅(qū)動(dòng)con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true", "root", "root"); //鏈接本地MYSQL//更新一條數(shù)據(jù)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);//查詢數(shù)據(jù)并輸出String sql = "select * from user_t where id = ?";PreparedStatement pstmt2 = con.prepareStatement(sql);pstmt2.setString(1, "1");ResultSet rs = pstmt2.executeQuery();while (rs.next()) { //循環(huán)輸出結(jié)果集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);}//關(guān)閉資源rs.close();pstmt.close();pstmt2.close();con.close();} catch (Exception e) {e.printStackTrace();}} }二、mybatis 執(zhí)行sql
mybatis要執(zhí)行sql,同樣也需要獲取到數(shù)據(jù)庫(kù)連接,這個(gè)在mybatis里面就是sqlSession
// 使用MyBatis提供的Resources類加載mybatis的配置文件 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); // 構(gòu)建sqlSession的工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sqlSessionFactory.openSession();2.1 獲取mapper對(duì)象
獲取到了session對(duì)象之后就是獲取mapper對(duì)象了,在mybatis中是使用動(dòng)態(tài)代理的方式獲取
@Overridepublic <T> T getMapper(Class<T> type) {//最后會(huì)去調(diào)用MapperRegistry.getMapperreturn configuration.<T>getMapper(type, this);} /*** 返回代理類* @param type* @param sqlSession* @return*/@SuppressWarnings("unchecked")public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// MapperProxyFactory去把代理類做出來(lái)final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 這里就是返回代理對(duì)象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自帶的動(dòng)態(tài)代理生成映射器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);}- 通過(guò)session獲取mapper,session通過(guò)MapperProxyFactory代理對(duì)象去獲取mapper對(duì)象,最終在newInstance中我們看到了使用jdk自帶的動(dòng)態(tài)代理方式獲取到了mapper對(duì)象。
2.2 執(zhí)行sql語(yǔ)句
現(xiàn)在獲取到了mapper對(duì)象,那么下一步就是執(zhí)行sql語(yǔ)句了。
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//代理以后,所有Mapper的方法調(diào)用時(shí),都會(huì)調(diào)用這個(gè)invoke方法//并不是任何一個(gè)方法都需要執(zhí)行調(diào)用代理對(duì)象進(jìn)行執(zhí)行,如果這個(gè)方法是Object中通用的方法(toString、hashCode等)無(wú)需執(zhí)行if (Object.class.equals(method.getDeclaringClass())) {try {return method.invoke(this, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}//這里優(yōu)化了,去緩存中找MapperMethodfinal MapperMethod mapperMethod = cachedMapperMethod(method);//執(zhí)行 sqlreturn mapperMethod.execute(sqlSession, args);}所有的sql語(yǔ)句都會(huì)調(diào)用invoke方法,會(huì)后都要調(diào)用execute來(lái)執(zhí)行sql語(yǔ)句
public Object execute(SqlSession sqlSession, Object[] args) {Object result;//可以看到執(zhí)行時(shí)就是4種情況,insert|update|delete|select,分別調(diào)用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()) {//如果有結(jié)果處理器executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {//如果結(jié)果有多條記錄result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {//如果結(jié)果是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;}這個(gè)方法也就是通過(guò)使用枚舉的方式執(zhí)行insert|update|delete|select,分別執(zhí)行不同的方法。這里跟著select,這個(gè)也是最復(fù)雜的。在這里我們看到了很重要的一個(gè)方法
result = sqlSession.selectOne(command.getName(), param);通過(guò)源碼可知selectOne方法轉(zhuǎn)而去調(diào)用selectList,很簡(jiǎn)單的,如果得到0條則返回null,得到1條則返回1條,得到多條報(bào)TooManyResultsException錯(cuò),特別需要主要的是當(dāng)沒(méi)有查詢到結(jié)果的時(shí)候就會(huì)返回null。因此一般建議在mapper中編寫resultType的時(shí)候使用包裝類型而不是基本類型,比如推薦使用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 {//根據(jù)statement id找到對(duì)應(yīng)的MappedStatementMappedStatement ms = configuration.getMappedStatement(statement);//轉(zhuǎn)而用執(zhí)行器來(lái)查詢結(jié)果,注意這里傳入的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方法中可以看出來(lái),最終的查詢還是交給了executor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {//得到綁定sqlBoundSql boundSql = ms.getBoundSql(parameter);//創(chuàng)建緩存KeyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//查詢return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}從MappedStatement對(duì)象中獲取到BoundSql對(duì)象,BoundSql對(duì)象包含了我們需要執(zhí)行的sql語(yǔ)句。
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();//新建一個(gè)StatementHandler//這里看到ResultHandler傳入了StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//準(zhǔn)備語(yǔ)句stmt = prepareStatement(handler, ms.getStatementLog());//StatementHandler.queryreturn handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}通過(guò)一些列的跟蹤,定位到了doQuery方法,最終sql語(yǔ)句的執(zhí)行交給了StatementHandler對(duì)象,這個(gè)對(duì)象也就是我們最常用的,封裝的是PreparedStatement
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();// 結(jié)果交給了ResultSetHandler 去處理return resultSetHandler.<E> handleResultSets(ps);}很明顯這里就是使用的PreparedStatement進(jìn)行處理。
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里只有一個(gè)元素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);}最后就是返回結(jié)果集了。
總結(jié)
以上是生活随笔為你收集整理的Mybatis源码解析-sql执行的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Mybatis源码解析之Mybatis初
- 下一篇: 会员钻是什么