解密Mybatis,手寫Mybatis框架(二)

簡化版Mybatis實現思路

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

clipboard.png

上節講到快速入門mybatis的demo三大階段node

// 1.讀取mybatis配置文件創SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
//-------------第二階段-------------
// 2.獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.獲取對應mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

//-------------第三階段-------------
// 4.執行查詢語句並返回結果
TUser user = mapper.selectByPrimaryKey(1);
System.out.println(user.toString());

第一階段:

第一階段先把配置文件加載到內存,包括數據庫信息和mapper.xml。sql

針對mapper.xml咱們定義一個MappedStatement類來存入相應信息.數據庫

public class MappedStatement {
    //此處忽略getset方法
    private String namespace;//xml裏面的namespace即mapper接口路徑
    
    private String sourceId;//mapper接口路徑+xml裏面的每個id
    
    private String sql;//sql語句
    
    private String resultType;//返回類型    

}

再定義一個全局配置信息即Configuration存放全部配置信息:api

public class Configuration {
    //記錄mapper xml文件存放的位置
    public static final String MAPPER_CONFIG_LOCATION = "config";
    //記錄數據庫鏈接信息文件存放位置
    public static final String DB_CONFIG_FILE = "db.properties";
    
    private String dbUrl;

    private String dbUserName;

    private String dbPassword;

    private String dbDriver;

    //mapper xml解析完之後select節點的信息存放在mappedStatements,key爲MappedStatement裏面 
     //的sourceId
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<String, MappedStatement>();
    
    //爲mapper接口生成動態代理的方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return MapperProxyFactory.getMapperProxy(sqlSession, type);
}
}

SqlSessionFactory實例化,並加載configuaration對象信息,這樣就把全部的配置信息加載到內存裏session

public class SqlSessionFactory {
    //配置對象全局惟一 加載數據庫信息和mapper文件信息
    private Configuration conf = new Configuration();

    public SqlSessionFactory() {
        //加載數據庫信息
         loadDbInfo();
         //加載mapper文件信息
         loadMappersInfo();
    }
    
    private void loadMappersInfo() {
        URL resources =null;
        resources = SqlSessionFactory.class.getClassLoader().getResource(conf.MAPPER_CONFIG_LOCATION);
        File mappers = new File(resources.getFile());
        if(mappers.isDirectory()){
            File[] listFiles = mappers.listFiles();
            for (File file : listFiles) {
                loadMapperInfo(file);
            }
        }
    }

    private void loadMapperInfo(File file) {
        // 建立saxReader對象  
        SAXReader reader = new SAXReader();  
        // 經過read方法讀取一個文件 轉換成Document對象  
        Document document=null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
        //獲取根節點元素對象  
        Element node = document.getRootElement();
        //獲取命名空間
        String namespace = node.attribute("namespace").getData().toString();
        //獲取select子節點列表
        List<Element> selects = node.elements("select");
        for (Element element : selects) {//遍歷select節點,將信息記錄到MappedStatement對象,並登記到configuration對象中
            MappedStatement mappedStatement = new MappedStatement();
            String id = element.attribute("id").getData().toString();
            String resultType = element.attribute("resultType").getData().toString();
            String sql = element.getData().toString();
            String sourceId = namespace+"."+id;
            mappedStatement.setSourceId(sourceId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            mappedStatement.setNamespace(namespace);
            conf.getMappedStatements().put(sourceId, mappedStatement);//登記到configuration對象中
        }
    }

    private void loadDbInfo() {
        InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(conf.DB_CONFIG_FILE);
         Properties p = new Properties();
         try {
            p.load(dbIn);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         conf.setDbDriver(p.get("jdbc.driver").toString());
         conf.setDbPassword(p.get("jdbc.password").toString());
         conf.setDbUrl(p.get("jdbc.url").toString());
         conf.setDbUserName(p.get("jdbc.username").toString());
    }
    public SqlSession openSession(){
        SqlSession sqlSession  = new DefaultSqlSession(conf);
        return sqlSession;
    }
}

第二階段

第二階段爲獲取Sqlsession而且從sqlsession獲取mapper動態代理.mybatis

Sqlsessionapp

  • mybatis暴露給外部的接口,實現增刪改查的能力
  • 1.對外提供數據訪問的api
  • 2.對內將請求轉發給executor
  • 3.executor基於JDBC訪問數據庫框架

    public class DefaultSqlSession implements SqlSession {
           
           //配置對象全局惟一 加載數據庫信息和mapper文件信息
           private Configuration conf;
           
           //真正提供數據庫訪問能力的對象
           private Executor executor;
           
       
           public DefaultSqlSession(Configuration conf) {
               super();
               this.conf = conf;
               executor = new SimpleExecutor(conf);
           }
       
           public <T> T selectOne(String statement, Object parameter) {
               List<Object> 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!");
               }
       
           }
       
           public <E> List<E> selectList(String statement, Object parameter) {
               MappedStatement mappedStatement = conf.getMappedStatement(statement);
               try {
                   return executor.query(mappedStatement, parameter);
               } catch (SQLException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
               }
               return null;
           }
       
             @Override
             //獲取當前mapper接口的動態代理
             public <T> T getMapper(Class<T> type) {
               return conf.<T>getMapper(type, this);
             }
       
       }

    Executor是Mybatis核心接口定義了數據庫操做的基本方法,Sqlsession都是基於它來實現的ide

    public interface Executor {
    
    
     <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException;
    
    
     <T> T selectOne(String statement,Object parameter);

    }學習

    Executor實現類:

public class SimpleExecutor implements Executor {

        
        private Configuration conf;
    
    
        public SimpleExecutor(Configuration conf) {
            this.conf = conf;
        }

        public <E> List<E> query(MappedStatement ms, Object parameter)
                throws SQLException {
            //獲取mappedStatement對象,裏面包含sql語句和目標對象等信息;
            MappedStatement mappedStatement = conf.getMappedStatement(ms.getSourceId());
            //1.獲取Connection對象
            Connection conn = getConnect();
            //2.實例化StatementHandler對象,準備實例化Statement
            StatementHandler statementHandler = new DefaultStatementHandler(mappedStatement);
            //3.經過statementHandler和Connection獲取PreparedStatement
            PreparedStatement prepare = statementHandler.prepare(conn);
            //4.實例化ParameterHandler對象,對Statement中sql語句的佔位符進行處理
            ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
            parameterHandler.setParameters(prepare);
            //5.執行查詢語句,獲取結果集resultSet
            ResultSet resultSet = statementHandler.query(prepare);
            //6.實例化ResultSetHandler對象,對resultSet中的結果集進行處理,轉化成目標對象
            ResultSetHandler resultSetHandler = new DefaultResultSetHandler(mappedStatement);
            return resultSetHandler.handleResultSets(resultSet);
        }
    
        @Override
        public <T> T selectOne(String statement, Object parameter) {
            MappedStatement mappedStatement =conf.getMappedStatements().get(statement);
            return null;
        }
    
    
        private Connection getConnect() {
            Connection conn =null;
            try {
                Class.forName(conf.getDbDriver());
                conn = DriverManager.getConnection(conf.getDbUrl(), conf.getDbUserName(), conf.getDbPassword());    
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return conn;
        }
    
        public Configuration getConf() {
            return conf;
        }
    
        public void setConf(Configuration conf) {
            this.conf = conf;
        }
        
    }

mapper接口在咱們工程裏面沒有實現類,是經過動態代理來執行方法的.

/**
 * mapper接口生成動態代理的工程類
 * 
 */
public class MapperProxyFactory<T> {

  
  public static <T> T getMapperProxy(SqlSession sqlSession,Class<T> mapperInterface){
      MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface);
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

InvocationHandler實現類:

public class MapperProxy<T> implements InvocationHandler {

    private SqlSession sqlSession;
    
    private final Class<T> mapperInterface;
    

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        super();
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    private <T> boolean isCollection(Class<T> type) {
        return Collection.class.isAssignableFrom(type);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {// 若是是Object自己的方法不加強
            return method.invoke(this, args);
        }

        Class<?> returnType = method.getReturnType();// 獲取方法的返回參數class對象
        Object ret = null;
        if (isCollection(returnType)) {// 根據不一樣的返回參數類型調用不一樣的sqlsession不一樣的方法
            ret = sqlSession.selectList(mapperInterface.getName()+"."+ method.getName(), args);
        } else {
            ret = sqlSession.selectOne(mapperInterface.getName()+"."+ method.getName(), args);
        }

        return ret;
    }

}

第三階段

第三階段執行查詢並返回結果.剛剛講過咱們執行數據庫操做其實是executor基於jdbc執行的。

jdbc三大巨頭,Connection,PreparedStatement,ResultSet,

結果集Result再經過反射機制映射到對象上面,便作好了數據的映射(關於映射具體內容可查閱資料及源碼),到這咱們已經完成了一個簡易的Mybatis框架了.

經過手寫一個簡單的Mybatis框架,咱們就能夠看得懂源碼了,學習框架設計的思路而且加強咱們Java的內功.

相關文章
相關標籤/搜索