本篇为原创文章,如需转载,请标明原创地址。java
mapper.xmlmysql
<mapper namespace="com.example.demo1.mybatis.ArticleMapper"> <select id="selectById" resultType="com.example.demo1.mybatis.Article" parameterType="java.lang.Long"> select <include refid="baseColumns"/> from article where 1= 1 and id = #{id} </select> <sql id="baseColumns"> id,title </sql> </mapper>
实体类sql
@Data public class Article { private Long id; private String title; }
测试类数据库
public class MybatisTest { public static void main(String[] args) throws IOException { // sqlSessionFactory是一个复杂对象,一般建立一个复杂对象会使用建造器来构建,这里首先建立建造器 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // configuration对象对应mybatis的config文件,为了测试简便,我这里直接建立Configuration对象而不经过xml解析得到 Configuration configuration = new Configuration(); configuration.setEnvironment(buildEnvironment()); // 解析一个mapper.xml为MappedStatement并加入到configuration中 InputStream inputStream = Resources.getResourceAsStream("mybatis/Article.xml"); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mybatis/Article.xml", configuration.getSqlFragments()); mapperParser.parse(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(configuration); // 建立一个sqlSession,这里使用的是简单工厂设计模式 SqlSession sqlSession = sqlSessionFactory.openSession(); // 执行最终的sql,查询文章id为1的文章 Article article = sqlSession.selectOne("com.example.demo1.mybatis.ArticleMapper.selectById",1L); // 打印文件的标题 System.out.println(article.getTitle()); // sqlSession默认不会自动关闭,咱们须要手动关闭 sqlSession.close(); } private static Environment buildEnvironment() { return new Environment.Builder("test") .transactionFactory(getTransactionFactory()) .dataSource(getDataSource()).build(); } private static DataSource getDataSource() { String url = "url"; String user = "user"; String password = "password"; Properties properties = new Properties(); properties.setProperty("url", url); properties.setProperty("username", user); properties.setProperty("password", password); properties.setProperty("driver", "com.mysql.jdbc.Driver"); properties.setProperty("driver.encoding", "UTF-8"); PooledDataSourceFactory factory = new PooledDataSourceFactory(); factory.setProperties(properties); DataSource dataSource = factory.getDataSource(); return dataSource; } private static TransactionFactory getTransactionFactory() { return new JdbcTransactionFactory(); }
分析sqlSession.selectOne("com.example.demo1.mybatis.ArticleMapper.selectById",1L);设计模式
public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>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; } }
经过查看源码,咱们发现无论是查询一条数据仍是查询多条数据都是执行的selectList方法,查询一条的时候只要取list的第一条数据便可。缓存
public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { /* 根据statement id找到对应的MappedStatement,而statement id对应的就是mapper的namespace+crud操做的id 在本例中就是com.example.demo1.mybatis.ArticleMapper.selectById */ MappedStatement ms = configuration.getMappedStatement(statement); // 委托执行器来执行查询操做 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(); } }
使用执行器来执行查询操做,简单的执行器只会执行sql,并将结果放入到一级缓存中,带二级缓存的执行器会增长一层缓存读写操做,这里先只讨论简单执行器的执行mybatis
========================================================================app
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获得绑定sql BoundSql boundSql = ms.getBoundSql(parameter); //建立缓存Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
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 StatementHandler的做用主要有如下几个: 1.从sqlSource获取最终须要执行的sql 2.建立jdbc的statement对象 3.给statement对象赋值 4.执行statement.execute方法执行赋值的sql 5.经过resultHandler对resultSet结果集进行处理收集,得到最终的结果 6.返回最终的结果 */ StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 经过jdbc链接建立一个全新的prepareStatement,并对其进行赋值,对应上面步骤的2,3 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行赋值后的sql并对结果进行处理收集,对应上面步骤的4,5 return handler.<E>query(stmt, resultHandler); } finally { // 执行statement.close方法 closeStatement(stmt); } }
建立statementHandler对象框架
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { /* 建立一个路由statementHandler 根据statementType进行路由,根据jdbc的基本知识咱们知道经常使用的statementType有三种: 1.STATEMENT 硬编码的语句,有sql注入风险 2.PREPARED 预编译sql的语句,通常状况下都使用这个 3.CALLABLE 执行存储过程的语句 一般咱们使用的都是preparedStatement */ StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); /* 经过上一个步骤statementHandler已经被建立好,preparedStatement也已被初始化 咱们获得了一个sql为select id ,title from article where id = ? 的preparedStatement 想想,若是咱们的sql为 select id,title from article 返回多条记录,咱们要分页的话怎么办? 一种方法是咱们在mapper.xml中在sql语句尾部手动添加limit *,*来进行分页(以mysql举例) 另外一种方法咱们能够经过插件的方式来实现,像经常使用的PageHelper插件就是基于此来实现的分页功能。 使用插件的好处是将分页功能和sql语句分离,达到去耦合的目的。这样咱们切换数据库的时候sql语句并不须要改动。 * */ statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
/** * 这个方法没有什么好分析的,就是执行sql语句,并对结果resultSet进行处理。 * 默认的结果集处理器就是DefaultResultSetHandler,其处理方案就是遍历resultSet集合, * 将全部的数据追加到List<Article>集合中去。 */ @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
该对象是mapper.xml在对象中的体现,是整个mybatis框架中最为核心的对象,咱们也能够没必要经过xml文件来构建该对象,能够直接经过编码方式构建,像最经常使用的简单的增删改查操做彻底能够手动构建mappedStatement对象并加入到mybatis容器中,这样咱们就不须要在xml文件中手写CRUD操做了,mybatis-plus框架设计的思想就是鉴于此。ide
其实Mybatis执行一条sql,底层仍是用的最基本的jdbc操做,只不过将事物,数据源,参数的设置,结果的收集转换都封装了起来,让咱们在开发中专一于sql自己,而忽略那些与业务不相关的步骤(结果对象的映射,开启事物,关闭事物等等操做),提升了项目的内聚性。