解密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.資料返回給呼叫者
上節講到快速入門mybatis的demo三大階段
// 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。
針對mapper.xml我們定義一個MappedStatement類來存入相應資訊.
public class MappedStatement { //此處忽略getset方法 private String namespace; private String sourceId;//mapper介面路徑+xml裡面的每一個id private String sql;//sql語句 private String resultType;//返回型別 }
再定義一個全域性配置資訊即Configuration存放所有配置資訊:
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物件資訊,這樣就把所有的配置資訊載入到記憶體裡
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動態代理.
Sqlsession
- 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都是基於它來實現的
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的內功.