Mybtais是一种ORM框架,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
本文我将从三个步骤进行Mybatis解读。
现在把我们的目光转会一开始的mybatis-config.xml中执行sql语句的xml文件。
<mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> 复制代码
这时候有个面试官问你,mappers加载mapper文件有几种方式?
这几种哪个优先级最高?
哈哈,不知道了吧,现在由我来告诉你
<mappers> <!-- 使用相对于类路径的资源引用 --> <mapper resource="org/mybatis/example/BlogMapper.xml"/> <!-- 使用完全限定资源定位符(URL) --> <mapper url="file:///var/mappers/BlogMapper.xml"/> <!-- 使用映射器接口实现类的完全限定类名 --> <mapper class="org.mybatis.builder.BlogMapper"/> <!-- 将包内的映射器接口实现全部注册为映射器 --> <package name="org.mybatis.builder"/> </mappers> 复制代码
我主要解释第三种。第三种的映射器接口实现类是这样的
// 定义接口映射器 public interface TestMapper { // 通过MyBatis的注解在Java接口方法上编写SQL语句 @Select("select * from test where id = #{id}") Test selectOneTest(long id); } 复制代码
我在文章开头也说过了,本文的Mybatis配置只是独立使用MyBatis,不是在Spring框架中集成MyBatis。(下图集成Spring)
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 定义接口映射器所在的Java包 --> <property name="basePackage" value="org.chench.test.mybatis.mapper.impl"/> </bean> 复制代码
哪个优先级最高,我们可以查看parseConfiguration()这个方法调用的mapperElement(root.evalNode("mappers"));(注:前文代码已给出)
从而可以看出顺序为name、resource、url、class。
解决掉这个面试题之后,我们可以接着看上图的代码,在resource != null的条件下,进入parse()
//解析 public void parse() { //如果没有加载过再加载,防止重复加载 if (!configuration.isResourceLoaded(resource)) { //配置mapper configurationElement(parser.evalNode("/mapper")); //标记一下,已经加载过了 configuration.addLoadedResource(resource); //绑定映射器到namespace bindMapperForNamespace(); } //还有没解析完的东东这里接着解析? parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } 复制代码
发现关键字mapper,进入configurationElement()
private void configurationElement(XNode context) { try { //1.配置namespace String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); //2.配置cache-ref cacheRefElement(context.evalNode("cache-ref")); //3.配置cache cacheElement(context.evalNode("cache")); //4.配置parameterMap(已经废弃,老式风格的参数映射) parameterMapElement(context.evalNodes("/mapper/parameterMap")); //5.配置resultMap(高级功能) resultMapElements(context.evalNodes("/mapper/resultMap")); //6.配置sql(定义可重用的 SQL 代码段) sqlElement(context.evalNodes("/mapper/sql")); //7.配置select|insert|update|delete TODO buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } 复制代码
context显示为
<mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" parameterType="int" resultType="Blog"> select * from Blog where id = #{id} </select> </mapper> 复制代码
接下来就是解析sql语句的内容
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 复制代码
/* 解析语句(select|insert|update|delete) <select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY"> SELECT * FROM PERSON WHERE ID = #{id}*/ public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); ... .. . } 复制代码
到这里mybatis就已经将sql语句解析完成了,接下来就需要进行封装。
//又去调助手类 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); 复制代码
进入addMappedStatement()中调用的addMappedStatement(),发现其所在的类为Configuration,这个类应该是个全局的配置类,里面还包括我之前提到的Environment。
这时候又有问题了?最佳实践中,通常个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么? Dao接口里的方法,参数不同时,方法能重载吗?
这时候我们进入addMappedStatement()方法,发现MappedStatement这个类对应着映射的语句,包含SQL源码。由此我们可以解答上面的问题。
答: Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中Mappedstatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可确定Mappedstatement,例: com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace为com.mybatis3.mappers.studentDao下面id = findstudentById的MappedStatement。
在Mybatis中,每一个select、insert、update、delete标签,都会被解析为一个Mappedstatement对象。Dao接口里的方法是不能重载的,因为是全限名+方法名的保存和寻找策略。 Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
在解析mapper的过程中,有mapperProxyFactory这个代理工厂,生成代理类的实例。其作用是生成了DAO层的实例。这也就是为什么DAO层的接口,能够直接调用方法的原因了——其实不是接口调用方法,而是它的代理类调用方法。
此时就拿到了sql语句。
一篇放不下传送门