MyBatis 源碼分析

MyBatis 運行過程

傳統的 JDBC 編程查詢數據庫的代碼和過程總結。php

  1. 加載驅動。
  2. 建立鏈接,Connection 對象。
  3. 根據 Connection 建立 Statement 或者 PreparedStatement 來執行 sql 語句。
  4. 返回結果集到 ResultSet 中。
  5. 手動將 ResultSet 映射到 JavaBean 中。
public static void main(String[] args) {
            //聲明Connection對象
            Connection con = null;
            //遍歷查詢結果集
            try {
                //加載驅動程序
                Class.forName("com.mysql.jdbc.Driver");
                //建立 connection 對象
                con = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","username","password");

                //使用 connection 對象建立statement 或者 PreparedStatement 類對象,用來執行SQL語句
                Statement statement = con.createStatement();
                //要執行的SQL語句
                String sql = "select * from emp";
                //3.ResultSet類,用來存放獲取的結果集!!
                ResultSet rs = statement.executeQuery(sql);
   
                String job = "";
                String id = "";
                while(rs.next()){
                    //獲取stuname這列數據
                    job = rs.getString("job");
                    //獲取stuid這列數據
                    id = rs.getString("ename");

                    //輸出結果
                    System.out.println(id + "\t" + job);
                }
            } catch(ClassNotFoundException e) {
                e.printStackTrace();
            } catch(SQLException e) {
                //數據庫鏈接失敗異常處理
                e.printStackTrace();
            }catch (Exception e) {
                e.printStackTrace();
            }finally{
                rs.close();
                con.close();
            }
        }
複製代碼

編碼方式實現 MyBatis 查詢數據庫,方便你們理解,不使用 SpringMybatis,加入 Spring 後總體流程會複雜不少。使用 MyBatis 後能將原來的傳統的 JDBC 編程編的如此簡單。具體流程總結。java

  1. 使用配置文件構建 SqlSessionFactory。
  2. 使用 SqlSessionFactory 得到 SqlSession,SqlSession 至關於傳統 JDBC 的 Conection。
  3. 使用 SqlSession 獲得 Mapper。
  4. 用 Mapper 來執行 sql 語句,並返回結果直接封裝到 JavaBean 中。
//獲取 sqlSession,sqlSession 至關於傳統 JDBC 的 Conection
public static SqlSession getSqlSession(){
		InputStream configFile = new FileInputStream(filePath);
  	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(configFile);
  	return sqlSessionFactory.openSession();
}

//使用 sqlSession 得到對應的 mapper,mapper 用來執行 sql 語句。
public static User get(SqlSession sqlSession, int id){
  	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  	return userMapper.selectByPrimaryKey(id);
}
複製代碼

總結mysql

MyBatis 源碼分析

下面來具體分析 MyBatis 代碼的執行過程**git

總體架構github

源碼分析sql

先說一下大部分框架的代碼流程:數據庫

再看咱們的配置文件。apache

<configuration>
	<environments default="development">
  	<environment id="development">
    	<transactionManager type="JDBC">
        <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/db"/>
          <property name="username" value="root"/>
          <property name="password" value="123456"/>
        </dataSource>
      </transactionManager>
    </environment>
  </environments>
</configuration>
<mappers>
  <mapper resource="xml/UserMapper.xml"/>
</mappers>
複製代碼
public static SqlSession getSqlSession(){
  	//讀取上面的配置文件
		InputStream configFile = new FileInputStream(filePath);
    //根據上面配置的 dataSource 配置 SqlSessionFactory,而且創建 Mapper 接口和 xml 之間的關係。
  	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder.build(configFile);
  	//工廠方法返回一個 sqlSession
  	return sqlSessionFactory.openSession();
}

//咱們來重點看看 openSession 作了什麼操做, DefaultSqlSessionFactory.java
@Override
public SqlSession openSession() {
  return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

public Configuration getConfiguration() {
  return this.configuration;
}
//這個函數裏面有着事務控制相關的代碼。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;

  DefaultSqlSession var8;
  try {
    Environment environment = this.configuration.getEnvironment();
    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
    //根據上面的參數獲得 TransactionFactory,經過 TransactionFactory 生成一個 Transaction,能夠理解爲這個 SqlSession 的事務控制器
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 將這個事務控制器封裝在 Executor 裏
    Executor executor = this.configuration.newExecutor(tx, execType);
    // 使用 configuration 配置類,Executor,和 configuration(是否自動提交) 來構建一個 DefaultSqlSession。
    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
  } catch (Exception var12) {
    this.closeTransaction(tx);
    throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
  } finally {
    ErrorContext.instance().reset();
  }

  return var8;
}
複製代碼

SqlSession 的實現流程。編程

SqlSession 的接口定義:裏面定義了增刪改查和提交回滾等方法。緩存

public interface SqlSession extends Closeable {
    <T> T selectOne(String var1);

    <T> T selectOne(String var1, Object var2);

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

    <E> List<E> selectList(String var1, Object var2);

    <E> List<E> selectList(String var1, Object var2, RowBounds var3);

    <K, V> Map<K, V> selectMap(String var1, String var2);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);

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

    <T> Cursor<T> selectCursor(String var1, Object var2);

    <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);

    void select(String var1, Object var2, ResultHandler var3);

    void select(String var1, ResultHandler var2);

    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);

    int insert(String var1);

    int insert(String var1, Object var2);

    int update(String var1);

    int update(String var1, Object var2);

    int delete(String var1);

    int delete(String var1, Object var2);

    void commit();

    void commit(boolean var1);

    void rollback();

    void rollback(boolean var1);

    List<BatchResult> flushStatements();

    void close();

    void clearCache();

    Configuration getConfiguration();

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

    Connection getConnection();
}
複製代碼

接下來用 sqlSession 獲取對應的 Mapper:

//使用 sqlSession 得到對應的 mapper,mapper 用來執行 sql 語句。
public static User get(SqlSession sqlSession, int id){
  	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  	return userMapper.selectByPrimaryKey(id);
}
複製代碼

DefaultSqlSession 的 getMapper 實現:

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

//從 configuration 裏面 getMapper,Mapper 就在 Configuration 裏
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return this.mapperRegistry.getMapper(type, sqlSession);
}
複製代碼

MapperRegistry 裏 getMapper 的最終實現:

這裏就要說明一下,咱們的接口裏面只定義了抽象的增刪改查,而這個接口並無任何實現類,那麼這個 xml 究竟是如何與接口關聯起來並生成實現類那?

public class MapperRegistry {
    private final Configuration config;
    // 用一個 Map 來存儲接口和 xml 文件之間的映射關係,key 應該是接口,可是 value 是 MapperProxyFactory
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      //獲取到這個接口對應的 MapperProxyFactory。
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                //用上一步獲取的 MapperProxyFactory 和 sqlSession 構建對應的 Class
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
}
複製代碼

接下來咱們看看 newInstance 的具體實現:

public T newInstance(SqlSession sqlSession) {
  // mapperInterface 就是接口
  MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
  return this.newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
  //動態代理,這裏的動態代理有一些不同
  return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
複製代碼

正常流程的動態代理:

與傳統的動態代理相比,MyBatis 的接口是沒有實現類的,那麼它又是怎麼實現動態代理的那?

咱們來看一下 MapperProxy 的源碼:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
		// 正常的動態代理中 Object proxy 這個參數應該是接口的實現類
    // com.paul.pkg.UserMapper@5a123uf
    // 如今裏面是 org.apache.ibatis.binding.MapperProxy@6y213kn, 這倆面
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
        // Mapper 走這個流程,先嚐試在緩存裏獲取 method
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
        if (mapperMethod == null) {
            // mapperMethod 的構建,經過接口名,方法,和 xml 配置(經過 sqlSession 的 Configuration 得到)
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
          //經過 execute 執行方法,由於 sqlSession 封裝了 Executor,因此還要傳進來,execute 方法使用
          //sqlSession 裏面的方法。
            this.methodCache.put(method, mapperMethod);
        }

        return mapperMethod;
    }

}
複製代碼

來看 MapperMethod 的定義:

// command 裏面包含了方法名,好比 com.paul.pkg.selectByPrimaryKey
// type, 表示是 SELECT,UPDATE,INSERT,或者 DELETE
// method 是方法的簽名
public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }
}
複製代碼

進入 DefaultSqlSession 執行對應的 sql 語句:

public <T> T selectOne(String statement, Object parameter) {
  List<T> list = this.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) {
  List var5;
  try {
    // 這裏又須要 configuration 來獲取對應的 statement
    // MappedStatement 裏面有 xml 文件,和要執行的方法,就是 xml 裏面的 id,statementType,以及 sql 語句。
    MappedStatement ms = this.configuration.getMappedStatement(statement);
    // 用 executor 執行 query,executor 裏面應該是包裝了 JDBC。
    var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception var9) {
    throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
  } finally {
    ErrorContext.instance().reset();
  }

  return var5;
}
複製代碼

Executor 的實現類裏面執行 query 方法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  Cache cache = ms.getCache();
  if (cache != null) {
    this.flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      this.ensureNoOutParams(ms, boundSql);
      List<E> list = (List)this.tcm.getObject(cache, key);
      if (list == null) {
        list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        this.tcm.putObject(cache, key, list);
      }

      return list;
    }
  }
  // 使用 delegate 去 query,delegate 是 SimpleExecutor。裏面使用 JDBC 進行數據庫操做。
  return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
複製代碼

手動實現一個簡單的 MyBatis

  1. 建立 SqlSessionFactory 實例。
  2. 實例化過程,加載配置文件建立 Configuration 對象。
  3. 經過 factory 建立 SqlSession。
  4. 經過 SqlSession 獲取 mapper 接口動態代理。
  5. 動態代理回調 SqlSession 中某查詢方法。
  6. SqlSession 將查詢方法轉發給 Executor。
  7. Executor 基於 JDBC 訪問數據庫獲取數據。
  8. Executor 經過反射將數據轉換成 POJO並返回給 SqlSession。
  9. 將數據返回給調用者。

項目總體使用 Maven 構建,mybatis-demo 是脫離 Spring 的 MyBatis 使用的例子。paul-mybatis 是咱們本身實現的 mybatis 框架。

  • 首先按照咱們之前的使用 mybatis 代碼時的流程,建立 mapper 接口,xml 文件,和 POJO以及集一些配置文件。

    接口:TUserMapper

    package com.paul.mybatis.mapper;
      
      import com.paul.mybatis.entity.TUser;
      
      import java.util.List;
      
      public interface TUserMapper {
      
          TUser selectByPrimaryKey(Integer id);
      
          List<TUser> selectAll();
      }
    複製代碼

    xml 文件

    <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.paul.mybatis.mapper.TUserMapper">
      
          <select id="selectByPrimaryKey" resultType="TUser">
              select *
              from t_user
              where id = #{id,jdbcType=INTEGER}
          </select>
      
          <select id="selectAll" resultType="TUser">
              select *
              from t_user
          </select>
      
      </mapper>
    複製代碼

    實體類,屬性應該與數據庫想匹配

    package com.paul.mybatis.entity;
      
      public class TUser {
      
      private Integer id;
      
      private String userName;
      
      private String realName;
      
      private Byte sex;
      
      private String mobile;
      
      public Integer getId() {
          return id;
      }
      
      public void setId(Integer id) {
          this.id = id;
      }
      
      public String getUserName() {
          return userName;
      }
      
      public void setUserName(String userName) {
          this.userName = userName;
      }
      
      public String getRealName() {
          return realName;
      }
      
      public void setRealName(String realName) {
          this.realName = realName;
      }
      
      public Byte getSex() {
          return sex;
      }
      
      public void setSex(Byte sex) {
          this.sex = sex;
      }
      
      public String getMobile() {
          return mobile;
      }
      
      public void setMobile(String mobile) {
          this.mobile = mobile;
      }
      }
    
    複製代碼

    數據庫鏈接配置文件,db.properties

    jdbc.driver=com.mysql.jdbc.Driver
      jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
      jdbc.username=root
      jdbc.password=root
    複製代碼
  • 下面咱們來關注 xml 文件,mapper 文件裏的 namespace,id,resultType 和 sql 語句都要存儲起來,咱們定義一個 POJO 來存儲這些信息。

    package com.paul.mybatis.confiuration;
      
      
      /** * * XML 中的 sql 配置信息加載到這個類中 * */
      public class MappedStatement {
      
      private String namespace;
      
      private String id;
      
      private String resultType;
      
      private String sql;
      
      public String getNamespace() {
          return namespace;
      }
      
      public void setNamespace(String namespace) {
          this.namespace = namespace;
      }
      
      public String getId() {
          return id;
      }
      
      public void setId(String id) {
          this.id = id;
      }
      
      public String getResultType() {
          return resultType;
      }
      
      public void setResultType(String resultType) {
          this.resultType = resultType;
      }
      
      public String getSql() {
          return sql;
      }
      
      public void setSql(String sql) {
          this.sql = sql;
      }
      }
    
    複製代碼
  • 下面來建立一個 Configuration 類,用來保存全部配置文件和 xml 文件裏的信息。

    package com.paul.mybatis.confiuration;
      
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      /** * * 全部的配置信息 * */
      public class Configuration {
      
      private String jdbcDriver;
      
      private String jdbcUrl;
      
      private String jdbcPassword;
      
      private String jdbcUsername;
      
      private Map<String,MappedStatement> mappedStatement = new HashMap<>();
      
      public Map<String, MappedStatement> getMappedStatement() {
          return mappedStatement;
      }
      
      public void setMappedStatement(Map<String, MappedStatement> mappedStatement) {
          this.mappedStatement = mappedStatement;
      }
      
      public String getJdbcDriver() {
          return jdbcDriver;
      }
      
      public void setJdbcDriver(String jdbcDriver) {
          this.jdbcDriver = jdbcDriver;
      }
      
      public String getJdbcUrl() {
          return jdbcUrl;
      }
      
      public void setJdbcUrl(String jdbcUrl) {
          this.jdbcUrl = jdbcUrl;
      }
      
      public String getJdbcPassword() {
          return jdbcPassword;
      }
      
      public void setJdbcPassword(String jdbcPassword) {
          this.jdbcPassword = jdbcPassword;
      }
      
      public String getJdbcUsername() {
          return jdbcUsername;
      }
      
      public void setJdbcUsername(String jdbcUsername) {
          this.jdbcUsername = jdbcUsername;
      }
      }
    
    複製代碼
  • 有了配置類以後,咱們能夠經過這個配置類構建一個 SqlSessionFactory 了。 SqlSessionFactory 抽象模版

    package com.paul.mybatis.factory;
    
      import com.paul.mybatis.sqlsession.SqlSession;
      
      public interface SqlSessionFactory {
      
          SqlSession openSession();
      }
    
    複製代碼

    Default 實現類主要完成了兩個功能,加載配置信息到 Configuration 對象裏,實現建立 SqlSession 的功能。

    package com.paul.mybatis.factory;
      
      import com.paul.mybatis.confiuration.Configuration;
      import com.paul.mybatis.confiuration.MappedStatement;
      import com.paul.mybatis.sqlsession.DefaultSqlSession;
      import com.paul.mybatis.sqlsession.SqlSession;
      import org.dom4j.Document;
      import org.dom4j.DocumentException;
      import org.dom4j.Element;
      import org.dom4j.io.SAXReader;
      
      import java.io.File;
      import java.io.IOException;
      import java.io.InputStream;
      import java.net.URL;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Properties;
      
      /** * * 1.初始化時就完成了 configuration 的實例化 * 2.工廠類,生成 sqlSession * */
      public class DefaultSqlSessionFactory implements SqlSessionFactory{
      
          //但願Configuration 是單例子而且惟一的
          private final Configuration configuration = new Configuration();
      
          // xml 文件存放的位置
          private static final String MAPPER_CONFIG_LOCATION = "mappers";
      
          // 數據庫信息存放的位置
          private static final String DB_CONFIG_FILE = "db.properties";
      
      
          public DefaultSqlSessionFactory() {
              loadDBInfo();
              loadMapperInfo();
          }
      
          private void loadDBInfo() {
              InputStream db = this.getClass().getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
              Properties p = new Properties();
      
              try {
                  p.load(db);
              } catch (IOException e) {
                  e.printStackTrace();
              }
              //將配置信息寫入Configuration 對象
              configuration.setJdbcDriver(p.get("jdbc.driver").toString());
              configuration.setJdbcUrl(p.get("jdbc.url").toString());
              configuration.setJdbcUsername(p.get("jdbc.username").toString());
              configuration.setJdbcPassword(p.get("jdbc.password").toString());
      
          }
      
          //解析並加載xml文件
          private void loadMapperInfo(){
              URL resources = null;
              resources = this.getClass().getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
              File mappers = new File(resources.getFile());
              //讀取文件夾下面的文件信息
              if(mappers.isDirectory()){
                  File[] files = mappers.listFiles();
                  for(File file:files){
                      loadMapperInfo(file);
                  }
              }
          }
      
          private void loadMapperInfo(File file){
              SAXReader reader = new SAXReader();
              //經過read方法讀取一個文件轉換成Document 對象
              Document document = null;
              try {
                  document = reader.read(file);
              } catch (DocumentException e) {
                  e.printStackTrace();
              }
              //獲取根結點元素對象<mapper>
              Element e = document.getRootElement();
              //獲取命名空間namespace
              String namespace = e.attribute("namespace").getData().toString();
              //獲取select,insert,update,delete子節點列表
              List<Element> selects = e.elements("select");
              List<Element> inserts = e.elements("select");
              List<Element> updates = e.elements("select");
              List<Element> deletes = e.elements("select");
      
              List<Element> all = new ArrayList<>();
              all.addAll(selects);
              all.addAll(inserts);
              all.addAll(updates);
              all.addAll(deletes);
      
              //遍歷節點,組裝成 MappedStatement 而後放入到configuration 對象中
              for(Element ele:all){
                  MappedStatement mappedStatement = new MappedStatement();
                  String id = ele.attribute("id").getData().toString();
                  String resultType = ele.attribute("resultType").getData().toString();
                  String sql = ele.getData().toString();
      
                  mappedStatement.setId(namespace+"."+id);
                  mappedStatement.setResultType(resultType);
                  mappedStatement.setNamespace(namespace);
                  mappedStatement.setSql(sql);
      
                  configuration.getMappedStatement().put(namespace+"."+id,mappedStatement);
              }
          }
      
          @Override
          public SqlSession openSession() {
              return new DefaultSqlSession(configuration);
          }
      }
    
    複製代碼
  • 在 SqlSessionFactory 裏建立了 DefaultSqlSession,咱們看看它的具體實現。SqlSession裏面應該封裝了全部數據庫的具體操做和一些獲取 mapper 實現類的方法。使用動態代理生成一個增強類。這裏面最終仍是把數據庫的相關操做轉給 SqlSession,使用 mapper 能使編程更加優雅。 SqlSession 接口,定義模版方法

    package com.paul.mybatis.sqlsession;
      
      import java.util.List;
      
      /** * * 封裝了全部數據庫的操做 * 全部功能都是基於 Excutor 來實現的,Executor 封裝了 JDBC 操做 * * */
      public interface SqlSession {
      
          /** * 根據傳入的條件查詢單一結果 * @param statement 方法對應 sql 語句,namespace+id * @param parameter 要傳入 sql 語句中的查詢參數 * @param <T> 返回指定的結果對象 * @return */
          <T> T selectOne(String statement, Object parameter);
      
          <T> List<T> selectList(String statement, Object parameter);
      
          <T> T getMapper(Class<T> type);
      }
    
    複製代碼

    Default 的 SqlSession 實現類。裏面須要傳入 Executor,這個 Executor 裏面封裝了 JDBC 操做數據庫的流程。咱們重點關注 getMapper 方法。

    package com.paul.mybatis.sqlsession;
      
      import com.paul.mybatis.bind.MapperProxy;
      import com.paul.mybatis.confiuration.Configuration;
      import com.paul.mybatis.confiuration.MappedStatement;
      import com.paul.mybatis.executor.Executor;
      import com.paul.mybatis.executor.SimpleExecutor;
      
      import java.lang.reflect.Proxy;
      import java.util.List;
      
      public class DefaultSqlSession implements SqlSession {
      
          private final Configuration configuration;
      
          private Executor executor;
      
          public DefaultSqlSession(Configuration configuration) {
              super();
              this.configuration = configuration;
              executor = new SimpleExecutor(configuration);
          }
      
          @Override
          public <T> T selectOne(String statement, Object parameter) {
              List<T> selectList = this.selectList(statement,parameter);
              if(selectList == null || selectList.size() == 0){
                  return null;
              }
              if(selectList.size() == 1){
                  return (T) selectList.get(0);
              }else{
                  throw new RuntimeException("too many result");
              }
          }
      
          @Override
          public <T> List<T> selectList(String statement, Object parameter) {
              MappedStatement ms = configuration.getMappedStatement().get(statement);
              return executor.query(ms,parameter);
          }
      
          @Override
          public <T> T getMapper(Class<T> type) {
              MapperProxy mp = new MapperProxy(this);
              //給我一個接口,還你一個實現類
              return (T)Proxy.newProxyInstance(type.getClassLoader(),new Class[]{type},mp);
          }
      }
    
    複製代碼
  • 動態代理的 InvocationHandler。

    package com.paul.mybatis.bind;
      
      import com.paul.mybatis.sqlsession.SqlSession;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.util.Collection;
      import java.util.Collections;
      
      /** * * 將請求轉發給 sqlSession * */
      public class MapperProxy implements InvocationHandler {
      
          private SqlSession sqlSession;
      
          public MapperProxy(SqlSession sqlSession) {
              this.sqlSession = sqlSession;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println(method.getDeclaringClass().getName()+"."+method.getName());
              if(Collection.class.isAssignableFrom(method.getReturnType())){
                  return sqlSession.selectList(method.getDeclaringClass().getName()+"."+method.getName(),args==null?null:args[0]);
              }else{
                  return sqlSession.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),args==null?null:args[0]);
              }
          }
      }
    
    複製代碼
  • 最後來看咱們的測試類

    package com.paul.mybatis;
     
     import com.paul.mybatis.entity.TUser;
     import com.paul.mybatis.factory.DefaultSqlSessionFactory;
     import com.paul.mybatis.factory.SqlSessionFactory;
     import com.paul.mybatis.mapper.TUserMapper;
     import com.paul.mybatis.sqlsession.SqlSession;
     
     public class TestDemo {
     
         public static void main(String[] args) {
             SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory();
     
             SqlSession sqlSession = sqlSessionFactory.openSession();
     
             TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
     
             TUser user = mapper.selectByPrimaryKey(1);
     
             System.out.println(user.toString());
         }
     }
    
    複製代碼

整個項目的源碼在項目源碼,但願你們 mark 一下,一塊兒改進。

相關文章
相關標籤/搜索