Mybatis做爲你們熟知的持久層框架,可以經過XML或註解方便地實現ORM和數據庫操做,到底它是怎麼實現的呢,這裏經過源碼帶你們揭開它的神祕面紗。java
首先建立srping boot的demo項目,添加mybatis依賴、數據庫配置等,如下貼上部分代碼sql
// 初始化mybatis的SqlSessionFactory,dataSource爲數據庫配置 @Configuration public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) { TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("dev", transactionFactory, dataSource); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(environment); configuration.addMapper(StudentMapper.class); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); return sqlSessionFactory; } } // 經過註解定義mapper public interface StudentMapper { @Select({ "select * from student where id = #{id,jdbcType=INTEGER}" }) @Results({ @Result(column = "id", property = "id", jdbcType = JdbcType.BIGINT, id = true), @Result(column = "order_no", property = "orderNo", jdbcType = JdbcType.VARCHAR) }) Student getById(@Param("id")Integer id); } @RestController @SpringBootApplication public class MybatisTestApplication { @Autowired private SqlSessionFactory sqlSessionFactory; // main方法省略 // 查詢數據庫 @GetMapping("/test") public String test() { SqlSession sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = studentMapper.getById(1); return student.getName(); } }
從demo能夠看出Mybatis的操做順序:數據庫
繼續debug源碼,看看mybatis主要經過哪些核心類進行操做,從StudentMapper.getById方法進入後,到達org.apache.ibatis.binding.MapperProxy#invoke方法:apache
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // …… 省略不相關的類操做 // 建立mapper方法的緩存對象 final MapperMethod mapperMethod = cachedMapperMethod(method); // 執行操做 return mapperMethod.execute(sqlSession, args); }
進入cachedMapperMethod,發現MapperMethod的構造函數是實例化SqlCommand和MethodSignature,而SqlCommand的屬性是經過MappedStatement獲取,MethodSignature則是經過Configuration、Mapper類和方法計算返回相關信息,所以咱們重點關注下MappedStatement緩存
public final class MappedStatement { // 部分屬性 private Configuration configuration; private String id; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private SqlCommandType sqlCommandType; private String[] resultSets; // …… }
從MappedStatement的屬性大體能夠猜到它是記錄sql、輸入參數、輸出結果類型等信息,有了這些信息後再回到mapperMethod.execute就能夠真正發起數據庫操做了。session
以SELECT爲例,首先包裝參數,統一調用SqlSession.selectList方法:mybatis
MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
能夠看出仍是須要MappedStatement,而具體執行則經過Executor來操做。app
進入Executor方法,首先BoundSql boundSql = ms.getBoundSql(parameterObject);,從其屬性能夠看出包含sql和輸入參數框架
最終跳到org.apache.ibatis.executor.SimpleExecutor#doQuery方法函數
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(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 到這裏就是咱們熟悉的jdbc對象了 stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } // handler最終到達的方法 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { // 是否是十分熟悉了 PreparedStatement ps = (PreparedStatement) statement; ps.execute(); // 返回結果處理器 return resultSetHandler.handleResultSets(ps); }
至此,mybatis一次數據庫操做完成,結合以上代碼從新將開頭的時序圖補充完整:
所以,mybatis的原理仍是經過JDBC實現的。這時候你們是否是依然存在很多疑問,具體sql到底何時生成,mapper接口又是如何實例化,不用急,下一篇將繼續帶你們一塊兒解開這些疑問