你猜一下哪个先执行?反正不要按常规来。
1 <plugins> 2 <plugin interceptor="com.Interceptor1"></plugin> 3 <plugin interceptor="com.Interceptor2"></plugin> 4 </plugins>
之前看有的博客分析源码,都没提到这一点。之前我只是用一下而已,这个顺序测试一下其实结论也很容易获得,但是我有一种看源码的屎命感。MyBatis还算人性化提供了拦截器,iBatis里面就没有了,不过也可以实现。这里要探究拦截器的源码就不得不提到MyBatis的源码,也就是执行流程了。这要是摊开说就有点大了,为了写好这篇,我决定今天晚上回去不打dota了,贡献真实够大的了。
MyBatis的作用
名义上来说MyBatis是一个半ORM框架,用了一个半字是因为MyBatis并没有完全起到一个ORM框架的作用(比如Hibernate),还有一半工作是需要我们参与进来——编写SQL语句。MyBatis替我们干的活是啥?帮我们把参数和配置化SQL语句映射成数据库中真正执行的SQL,然后把结果帮我们封装好,并返回回来。好处很容已说明,配置灵活,增强开发人员对SQL语句的控制,减少了冗余的对象封装工作。
官方的说法如下:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
MyBatis的架构
沿着上面说到的,我们接下来可以看一下MyBatis用了什么架构来完成上面的工作。要注意,talk is cheap,实际上的话还要牵涉很多工作(比如Session,事务等)。在网上看了一些描述MyBatis架构的图,看来一晚上不打dota是不可能了呀。
功能上的架构:
源码中的结构:
架构看上去并不复杂,按三层来分的。之前看的很少,如果你是和我一样的小白话可以一起来看下,从头看起吧,接口和配置文件先开始。
最简单的方式开始MyBatis
这里最简单的意思是,我们先抛开Spring,只在一个简单的Maven项目中使用MyBatis,看看它是如何运行的。先起一个简单的Maven项目并加上MyBatis的依赖。
已经做好了但是,额,写起来估计可以新开一篇了。
单独使用MyBatis代码
其实核心代码只有两个,第一个是SessionUtils用于提供Session,第二个是使用Session进行CRUD操作的代码
1 public class SessionUtils { 2 private static SqlSessionFactory sessionFactory; 3 static { 4 try { 5 // 使用MyBatis提供的Resources类加载mybatis的配置文件 6 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); 7 // 构建sqlSession的工厂 8 sessionFactory = new SqlSessionFactoryBuilder().build(reader); 9 } catch (Exception e) { 10 e.printStackTrace(); 11 } 12 } 13 14 /** 15 * 获取SqlSession 16 * @return SqlSession 17 */ 18 public static SqlSession getSession() { 19 return sessionFactory.openSession(); 20 } 21 }
1 @Test 2 public void testInsert() { 3 SqlSession session = null; 4 try { 5 session = SessionUtils.getSession(); 6 StudentMapper studentMapper = session.getMapper(StudentMapper.class); 7 Long affectedLines = studentMapper.insert(build()); 8 System.out.println("affectedLines = " + (affectedLines == null ? 0 : affectedLines)); 9 session.commit(); 10 } catch (Exception e) { 11 e.printStackTrace(); 12 if(session != null) { 13 session.rollback(); 14 } 15 } finally { 16 if(session != null) { 17 session.close(); 18 } 19 } 20 }
SqlSessionFactory的生成
观察这两段代码引出了两个核心的类:SqlSessionFactory和Session。
SqlSessionFactoryBuilder:单纯的就是为了创建SqlSessionFactory,功能很单一。
1 // 这是一个普类, 而不是接口, 这里把方法都隐去了 2 public class SqlSessionFactoryBuilder { 3 public SqlSessionFactory build(Reader reader); 4 public SqlSessionFactory build(Reader reader, String environment); 5 public SqlSessionFactory build(Reader reader, Properties properties); 6 public SqlSessionFactory build(Reader reader, String environment, Properties properties); 7 public SqlSessionFactory build(InputStream inputStream); 8 public SqlSessionFactory build(InputStream inputStream, String environment); 9 public SqlSessionFactory build(InputStream inputStream, Properties properties); 10 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties); 11 public SqlSessionFactory build(Configuration config); 12 }
通过阅读源码发现生成SqlSessionFactory的简要步骤如下:
通过Reader,XMLConfigBuilder,Configuration和DefaultSqlSessionFactory共同协作把SqlSessionFactory弄出来了。如果正式一点,用时序图画出来就是这样的:
承载MySql所有的配置Configuration类
下面重点关注的类是Configuration。在org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration中解析Configuration的代码如下,看完之后你就会觉得很亲切:
1 private void parseConfiguration(XNode root) { 2 try { 3 //issue #117 read properties first 4 propertiesElement(root.evalNode("properties")); 5 Properties settings = settingsAsProperties(root.evalNode("settings")); 6 loadCustomVfs(settings); 7 typeAliasesElement(root.evalNode("typeAliases")); 8 pluginElement(root.evalNode("plugins")); 9 objectFactoryElement(root.evalNode("objectFactory")); 10 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 11 reflectorFactoryElement(root.evalNode("reflectorFactory")); 12 settingsElement(settings); 13 // read it after objectFactory and objectWrapperFactory issue #631 14 environmentsElement(root.evalNode("environments")); 15 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 16 typeHandlerElement(root.evalNode("typeHandlers")); 17 mapperElement(root.evalNode("mappers")); 18 } catch (Exception e) { 19 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 20 } 21 }
话说Java的这个着色也太稀烂了。typeAliases,typeHandlers和mappers是不是很熟悉。然后Configuration的类图是:
再返回Configuration的时候,mybatis-config.xml中的各个元素都已经解析出来了:
Environment:数据源和事务管理器
Environment就是配置数据源的地方,在mybatis-config.xml中是这样的:
1 <!-- 配置mybatis运行环境 --> 2 <environments default="development"> 3 <environment id="development"> 4 <!-- type="JDBC" 代表使用JDBC的提交和回滚来管理事务 --> 5 <transactionManager type="JDBC" /> 6 <!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI --> 7 <!-- POOLED 表示支持JDBC数据源连接池 --> 8 <!-- UNPOOLED 表示不支持数据源连接池 --> 9 <!-- JNDI 表示支持外部数据源连接池 --> 10 <dataSource type="POOLED"> 11 <property name="driver" value="${jdbc.driver}" /> 12 <property name="url" value="${jdbc.url}" /> 13 <property name="username" value="${jdbc.username}" /> 14 <property name="password" value="${jdbc.password}" /> 15 </dataSource> 16 </environment> 17 </environments>
1 private void environmentsElement(XNode context) throws Exception { 2 if (context != null) { 3 if (environment == null) { 4 environment = context.getStringAttribute("default"); 5 } 6 for (XNode child : context.getChildren()) { 7 String id = child.getStringAttribute("id"); 8 if (isSpecifiedEnvironment(id)) { 9 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); 10 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); 11 DataSource dataSource = dsFactory.getDataSource(); 12 Environment.Builder environmentBuilder = new Environment.Builder(id) 13 .transactionFactory(txFactory) 14 .dataSource(dataSource); 15 configuration.setEnvironment(environmentBuilder.build()); 16 } 17 } 18 } 19 }