MyBatis源碼分析:SqlSession

1.SqlSession

源碼:

package org.apache.ibatis.session;

import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;

/**
 * The primary Java interface for working with MyBatis.
 * Through this interface you can execute commands, get mappers and manage transactions.
 *
 * @author Clinton Begin
 */
public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}

從接口的註釋中可以看出,SqlSession接口主要提供如下功能:

  1. 執行特定SQL:ibatis遺留的使用方式,可通過update,insert,select,delete等方法,帶上命名空間和SQL的id來執行配置好的SQL;
  2. 獲取映射器(Mapper):讓映射器通過命名空間和方法名稱找到對應SQL,發送給數據庫執行後返回結果;
  3. 管理事物:ibatis遺留的使用方式,通過commit,rollback方法提交或回滾事物;

2.實現類

SqlSession接口在mybatis中有2個實現類:分別爲DefaultSqlSessionSqlSessionManager
UML圖如下:
在這裏插入圖片描述

3.ibatis遺留的使用方式

涉及接口(省略了重載):

selectOne()
selectList()
selectMap()
selectCursor()
select()
insert()
update()
delete()
commit()
rollback()

我們以

  /**
   * Retrieve a list of mapped objects from the statement key and parameter,
   * within the specified row bounds.
   * @param <E> the returned list element type
   * @param statement Unique identifier matching the statement to use.
   * @param parameter A parameter object to pass to the statement.
   * @param rowBounds  Bounds to limit object retrieval
   * @return List of mapped object
   */
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)

接口爲例進行分析,此接口的註釋中聲明:該接口根據statement key和parameter返回查詢結果映射的列表,長度爲irowBounds;泛型T爲返回結果的類型;statement參數爲命名空間+SQL id;parameter爲傳遞給statemtent的參數;

我們來看一下這個接口在實現類DefaultSqlSession中的實現:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //1.從配置中讀取MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2.調用executor的query方法進行查詢
      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();
    }
  }

這裏涉及到了兩個Mybatis的組件:MappedStatementExecutor

selectList方法中使用了一個wrapCollection方法,該方法將輸入object參數進行封裝,邏輯如下:

  1. 首先判斷object的類型,若既不是Collection也不是Array,則直接返回;
  2. 若object的類型爲Collection,且爲List,則構造一個StrictMap對象,並設置兩個enetity,key分別爲"collection"和"list",val均爲object,並將map返回;
  3. 若object的類型爲Collection,但不爲List,則構造一個StrictMap對象,並設置一個entity,key爲“collection”,val爲object,,並將map返回;
  4. 若object的類型爲Array,則構造一個StrictMap對象,並設置一個entity,key爲“array”,value爲object,並將map返回;

wrapCollection方法:

  private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
  }

selectList demo:

package mybatis;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class HelloWorldMyBatis {

    private static SqlSessionFactory sqlSessionFactory = null;

    private SqlSessionFactory getSqlSessionFactory() {
        if (null == sqlSessionFactory) {
            final String resource = "mybatis_config.xml";
            try {
                Reader reader = Resources.getResourceAsReader(resource);
                return new SqlSessionFactoryBuilder().build(reader);
            } catch (IOException e) {
                throw new IllegalStateException("Cannot build SqlSessionFactory", e);
            }
        } else {
            return sqlSessionFactory;
        }
    }

    public static void main(String[] args) {
        final SqlSession sqlSession = new HelloWorldMyBatis().getSqlSessionFactory().openSession();
        //mapper的命名空間:mybatis.UserMapper
        //SQL id:getUser
        User user = sqlSession.selectOne("mybatis.UserMapper.getUser", 1L);
        System.out.println(user);
        sqlSession.close();
    }
}

4.通過映射器的方式

涉及接口:

<T> T getMapper(Class<T> type);

此接口在DefaultSqlSession中的實現如下:

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

此方法涉及到Mybatis組件:Configuration
demo:

public class HelloWorldMyBatis {

    private static SqlSessionFactory sqlSessionFactory = null;

    private SqlSessionFactory getSqlSessionFactory() {
        if (null == sqlSessionFactory) {
            final String resource = "mybatis_config.xml";
            try {
                Reader reader = Resources.getResourceAsReader(resource);
                return new SqlSessionFactoryBuilder().build(reader);
            } catch (IOException e) {
                throw new IllegalStateException("Cannot build SqlSessionFactory", e);
            }
        } else {
            return sqlSessionFactory;
        }
    }

    public static void main(String[] args) {
        final SqlSession sqlSession = new HelloWorldMyBatis().getSqlSessionFactory().openSession();
        ////mapper的命名空間:mybatis.UserMapper
        final UserMapper mapper = sqlSession.getMapper(mybatis.UserMapper.class);
        //SQL id:getUser
        final User user = mapper.getUser(1L);
        System.out.println(user);
        sqlSession.close();
    }
}

5.兩種方式對比

《深入淺出Mybatis技術原理與實戰》一書強烈建議讀者使用映射器的方式來操作數據庫,原因如下:

  1. Mapper是一個接口,可以進一步屏蔽SqlSession對象,提高代碼可讀性;
  2. sqlSession.selectOne方法是功能性代碼,參數晦澀難懂,不包含業務邏輯,不符合面向對象編程規範,而mapper.getUser方法符合面向對象編程規範,也更符合業務邏輯;
  3. 使用mapper的方式,IDE可以檢查java語法,避免不必要的錯誤;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章