zoukankan      html  css  js  c++  java
  • Mybatis中几个重要类

    本文基于Mybatis3.2.0版本的代码。

    1.org.apache.ibatis.mapping.MappedStatement

    MappedStatement类在Mybatis框架中用于表示XML文件中一个sql语句节点,即一个<select />、<update />或者<insert />标签。Mybatis框架在初始化阶段会对XML配置文件进行读取,将其中的sql语句节点对象化为一个个MappedStatement对象。比如下面这个非常简单的XML mapper文件:

    01 <?xml version="1.0" encoding="UTF-8" ?>
    02 <!DOCTYPE mapper
    03   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    04   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    05 <mapper namespace="mybatis.UserDao">
    06  
    07     <cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
    08  
    09     <resultMap id="userResultMap" type="UserBean">
    10         <id property="userId" column="user_id" />
    11         <result property="userName" column="user_name" />
    12         <result property="userPassword" column="user_password" />
    13         <result property="createDate" column="create_date" />
    14     </resultMap>
    15  
    16     <select id="find" parameterType="UserBean" resultMap="userResultMap">
    17         select * from user
    18         <where>
    19             <if test="userName!=null and userName!=''">
    20                 and user_name = #{userName}
    21             </if>
    22             <if test="userPassword!=null and userPassword!=''">
    23                 and user_password = #{userPassword}
    24             </if>
    25             <if test="createDate !=null">
    26                 and create_date = #{createDate}
    27             </if>
    28         </where>
    29     </select>
    30  
    31     <!-- 说明mybatis中的sql语句节点和映射的接口中的方法,并不是一一对应的关系,而是独立的,可以取任意不重复的名称 -->
    32     <select id="find2" parameterType="UserBean" resultMap="userResultMap">
    33         select * from user
    34         <where>
    35             <if test="userName!=null and userName!=''">
    36                 and user_name = #{userName}
    37             </if>
    38             <if test="userPassword!=null and userPassword!=''">
    39                 and user_password = #{userPassword}
    40             </if>
    41             <if test="createDate !=null">
    42                 and create_date = #{createDate}
    43             </if>
    44         </where>
    45     </select>
    46  
    47 </mapper>

    Mybatis对这个文件的配置读取和解析后,会注册两个MappedStatement对象,分别对应其中id为find和find2的<select />节点,通过org.apache.ibatis.session.Configuration类中的getMappedStatement(String id)方法,可以检索到一个特定的MappedStatement。为了区分不同的Mapper文件中的sql节点,其中的String id方法参数,是以Mapper文件的namespace作为前缀,再加上该节点本身的id值。比如上面生成的两个MappedStatement对象在Mybatis框架中的唯一标识分别是mybatis.UserDao.find和mybatis.UserDao.find2。

    打开MappedStatement对象的源码,看一下其中的私有属性。

    01 public final class MappedStatement {
    02  
    03   private String resource;
    04   private Configuration configuration;
    05   private String id;
    06   private Integer fetchSize;
    07   private Integer timeout;
    08   private StatementType statementType;
    09   private ResultSetType resultSetType;
    10   private SqlSource sqlSource;
    11   private Cache cache;
    12   private ParameterMap parameterMap;
    13   private List<ResultMap> resultMaps;
    14   private boolean flushCacheRequired;
    15   private boolean useCache;
    16   private boolean resultOrdered;
    17   private SqlCommandType sqlCommandType;
    18   private KeyGenerator keyGenerator;
    19   private String[] keyProperties;
    20   private String[] keyColumns;
    21   private boolean hasNestedResultMaps;
    22   private String databaseId;
    23   private Log statementLog;
    24   private LanguageDriver lang;
    25  
    26   private MappedStatement() {
    27     // constructor disabled
    28   }
    29   ..........
    30

    我们可以看到其中的属性基本上和xml元素的属性有对应关系,其中比较重要的有表示查询参数的ParameterMap对象,表示sql查询结果映射关系的ResultMap列表resultMaps,当然最重要的还是执行动态sql计算和获取的SqlSource对象。通过这些对象的通力合作,MappedStatement接受用户的查询参数对象,动态计算出要执行的sql语句,在数据库中执行sql语句后,再将取得的数据封装为JavaBean对象返回给用户。MappedStatement对象的这些功能,也体现出了Mybatis这个框架的核心价值,“根据用户提供的查询参数对象,动态执行sql语句,并将结果封装为Java对象”。

    2.org.apache.ibatis.mapping.SqlSource

    SqlSource是一个接口类,在MappedStatement对象中是作为一个属性出现的,它的代码如下:

    01 package org.apache.ibatis.mapping;
    02  
    03 /**
    04  *
    05  * This bean represets the content of a mapped statement read from an XML file
    06  * or an annotation. It creates the SQL that will be passed to the database out
    07  * of the input parameter received from the user.
    08  *
    09  */
    10 public interface SqlSource {
    11  
    12   BoundSql getBoundSql(Object parameterObject);
    13  
    14 }
    SqlSource接口只有一个getBoundSql(Object parameterObject)方法,返回一个BoundSql对象。一个BoundSql对象,代表了一次sql语句的实际执行,而SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的<if />节点的计算,是由SqlSource对象完成的。SqlSource最常用的实现类是DynamicSqlSource,来看一看它的代码:
    01 package org.apache.ibatis.scripting.xmltags;
    02  
    03 import java.util.Map;
    04  
    05 import org.apache.ibatis.builder.SqlSourceBuilder;
    06 import org.apache.ibatis.mapping.BoundSql;
    07 import org.apache.ibatis.mapping.SqlSource;
    08 import org.apache.ibatis.session.Configuration;
    09  
    10 public class DynamicSqlSource implements SqlSource {
    11  
    12   private Configuration configuration;
    13   private SqlNode rootSqlNode;
    14  
    15   public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    16     this.configuration = configuration;
    17     this.rootSqlNode = rootSqlNode;
    18   }
    19  
    20   public BoundSql getBoundSql(Object parameterObject) {
    21     DynamicContext context = new DynamicContext(configuration, parameterObject);
    22     rootSqlNode.apply(context);
    23     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    24     Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    25     SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    26     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    27     for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
    28       boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    29     }
    30     return boundSql;
    31   }
    32  
    33 }

    其中的

    1 rootSqlNode.apply(context);

    这句调用语句,启动了一个非常精密的递归实现的动态计算sql语句的过程,计算过程使用Ognl来根据传入的参数对象计算表达式,生成该次调用过程中实际执行的sql语句。

    3.org.apache.ibatis.scripting.xmltags.DynamicContext

    DynamicContext类中,有对传入的parameterObject对象进行“map”化处理的部分,也就是说,你传入的pojo对象,会被当作一个键值对数据来源来进行处理,读取这个pojo对象的接口,还是Map对象。从DynamicContext的源码中,能看到很明显的线索。

    001 import java.util.HashMap;
    002 import java.util.Map;
    003  
    004 import ognl.OgnlException;
    005 import ognl.OgnlRuntime;
    006 import ognl.PropertyAccessor;
    007  
    008 import org.apache.ibatis.reflection.MetaObject;
    009 import org.apache.ibatis.session.Configuration;
    010  
    011 public class DynamicContext {
    012  
    013   public static final String PARAMETER_OBJECT_KEY = "_parameter";
    014   public static final String DATABASE_ID_KEY = "_databaseId";
    015  
    016   static {
    017     OgnlRuntime.setPropertyAccessor(ContextMap.classnew ContextAccessor());
    018   }
    019  
    020   private final ContextMap bindings;
    021   private final StringBuilder sqlBuilder = new StringBuilder();
    022   private int uniqueNumber = 0;
    023  
    024   public DynamicContext(Configuration configuration, Object parameterObject) {
    025     if (parameterObject != null && !(parameterObject instanceof Map)) {
    026       MetaObject metaObject = configuration.newMetaObject(parameterObject);
    027       bindings = new ContextMap(metaObject);
    028     else {
    029       bindings = new ContextMap(null);
    030     }
    031     bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    032     bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
    033   }
    034  
    035   public Map<String, Object> getBindings() {
    036     return bindings;
    037   }
    038  
    039   public void bind(String name, Object value) {
    040     bindings.put(name, value);
    041   }
    042  
    043   public void appendSql(String sql) {
    044     sqlBuilder.append(sql);
    045     sqlBuilder.append(" ");
    046   }
    047  
    048   public String getSql() {
    049     return sqlBuilder.toString().trim();
    050   }
    051  
    052   public int getUniqueNumber() {
    053     return uniqueNumber++;
    054   }
    055  
    056   static class ContextMap extends HashMap<String, Object> {
    057     private static final long serialVersionUID = 2977601501966151582L;
    058  
    059     private MetaObject parameterMetaObject;
    060     public ContextMap(MetaObject parameterMetaObject) {
    061       this.parameterMetaObject = parameterMetaObject;
    062     }
    063  
    064     @Override
    065     public Object get(Object key) {
    066       String strKey = (String) key;
    067       if (super.containsKey(strKey)) {
    068         return super.get(strKey);
    069       }
    070  
    071       if (parameterMetaObject != null) {
    072         Object object = parameterMetaObject.getValue(strKey);
    073         if (object != null) {
    074           super.put(strKey, object);
    075         }
    076  
    077         return object;
    078       }
    079  
    080       return null;
    081     }
    082   }
    083  
    084   static class ContextAccessor implements PropertyAccessor {
    085  
    086     public Object getProperty(Map context, Object target, Object name)
    087         throws OgnlException {
    088       Map map = (Map) target;
    089  
    090       Object result = map.get(name);
    091       if (result != null) {
    092         return result;
    093       }
    094  
    095       Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
    096       if (parameterObject instanceof Map) {
    097           return ((Map)parameterObject).get(name);
    098       }
    099  
    100       return null;
    101     }
    102  
    103     public void setProperty(Map context, Object target, Object name, Object value)
    104         throws OgnlException {
    105       Map map = (Map) target;
    106       map.put(name, value);
    107     }
    108   }
    109 }
    在DynamicContext的构造函数中,可以看到,根据传入的参数对象是否为Map类型,有两个不同构造ContextMap的方式。而ContextMap作为一个继承了HashMap的对象,作用就是用于统一参数的访问方式:用Map接口方法来访问数据。具体来说,当传入的参数对象不是Map类型时,Mybatis会将传入的POJO对象用MetaObject对象来封装,当动态计算sql过程需要获取数据时,用Map接口的get方法包装 MetaObject对象的取值过程。


    我们都知道,Mybatis中采用了Ognl来计算动态sql语句,DynamicContext类中的这个静态初始块,很好的说明了这一点

    1 static {
    2   OgnlRuntime.setPropertyAccessor(ContextMap.classnew ContextAccessor());
    3 }

    ContextAccessor也是DynamicContext的内部类,实现了Ognl中的PropertyAccessor接口,为Ognl提供了如何使用ContextMap参数对象的说明,这个类也为整个参数对象“map”化划上了最后一笔。

    现在我们能比较清晰的描述一下Mybatis中的参数传递和使用过程了:将传入的参数对象统一封装为ContextMap对象(继承了HashMap对象),然后Ognl运行时环境在动态计算sql语句时,会按照ContextAccessor中描述的Map接口的方式来访问和读取ContextMap对象,获取计算过程中需要的参数。ContextMap对象内部可能封装了一个普通的POJO对象,也可以是直接传递的Map对象,当然从外部是看不出来的,因为都是使用Map的接口来读取数据。

    结合一个例子来理解一下:

    01 @Test
    02     public void testSqlSource() throws Exception {
    03         String resource = "mybatis/mybatis-config.xml";
    04         InputStream inputStream = Resources.getResourceAsStream(resource);
    05         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
    06                 .build(inputStream);
    07         SqlSession session = sqlSessionFactory.openSession();
    08  
    09         try {
    10             Configuration configuration = session.getConfiguration();
    11             MappedStatement mappedStatement = configuration
    12                     .getMappedStatement("mybatis.UserDao.find2");
    13             assertNotNull(mappedStatement);
    14              
    15             UserBean param = new UserBean();
    16             param.setUserName("admin");
    17             param.setUserPassword("admin");
    18             BoundSql boundSql = mappedStatement.getBoundSql(param);
    19             String sql = boundSql.getSql();
    20  
    21             Map<String, Object> map = new HashMap<String, Object>();
    22             map.put("userName""admin");
    23             map.put("userPassword""admin");
    24             BoundSql boundSql2 = mappedStatement.getBoundSql(map);
    25             String sql2 = boundSql2.getSql();
    26  
    27             assertEquals(sql, sql2);
    28              
    29             UserBean bean = session.selectOne("mybatis.UserDao.find2", map);
    30             assertNotNull(bean);
    31  
    32         finally {
    33             session.close();
    34         }
    35  
    36     }

    上面这个Junit测试方法,是我写的一个测试用例中的一小段,其中的UserBean对象,就是一个有三个属性userName,userPassword,createDate的POJO对象,对应的Mapper文件是文章开头给出的配置文件。

    第一次测试,我使用的是一个UserBean对象,来获取和计算sql语句,而第二次我是使用了一个HashMap对象,按照属性的名字,我分别设置了两个键值对象,我甚至还直接使用它来启动了一次session对象的查询selectOne。所有这些操作,都是测试通过(绿条)。这充分说明了,Mybatis参数获取过程中,对Map对象和普通POJO对象的无差别化,因为在内部,两者都会被封装,然后通过Map接口来访问!

    下一篇说说Mybatis中接口和Mapper文件是如何通过创建代理类来进行绑定的,这个特性也是Mybatis设计的一大亮点。

  • 相关阅读:
    利用百度云盘API上传文件至百度云盘
    测试Centos硬盘读写速度
    into outfile 生成sql脚本
    Nginx设置Js、Css等静态文件的缓存过期时间
    mysql查询区分大小写
    Table './mysql/proc' is marked as crashed and should be repaired 解决方法
    CentOS 6.6 下配置软RAID5
    管道限流利器pv
    mydumper使用
    RAID详解
  • 原文地址:https://www.cnblogs.com/daichangya/p/12958643.html
Copyright © 2011-2022 走看看