Mybatis中几个重要类

来源:互联网 发布:斐波那契数列java输出 编辑:程序博客网 时间:2024/06/06 02:38

本文基于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对象的源码,看一下其中的私有属性。

01public 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对象中是作为一个属性出现的,它的代码如下:

01package 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 */
10public interface SqlSource {
11 
12  BoundSql getBoundSql(Object parameterObject);
13 
14}
SqlSource接口只有一个getBoundSql(Object parameterObject)方法,返回一个BoundSql对象。一个BoundSql对象,代表了一次sql语句的实际执行,而SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的<if />节点的计算,是由SqlSource对象完成的。SqlSource最常用的实现类是DynamicSqlSource,来看一看它的代码:
01package org.apache.ibatis.scripting.xmltags;
02 
03import java.util.Map;
04 
05import org.apache.ibatis.builder.SqlSourceBuilder;
06import org.apache.ibatis.mapping.BoundSql;
07import org.apache.ibatis.mapping.SqlSource;
08import org.apache.ibatis.session.Configuration;
09 
10public 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}

其中的

1rootSqlNode.apply(context);

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

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

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

001import java.util.HashMap;
002import java.util.Map;
003 
004import ognl.OgnlException;
005import ognl.OgnlRuntime;
006import ognl.PropertyAccessor;
007 
008import org.apache.ibatis.reflection.MetaObject;
009import org.apache.ibatis.session.Configuration;
010 
011public 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类中的这个静态初始块,很好的说明了这一点

1static {
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接口来访问!

转载地址:http://www.open-open.com/lib/view/open1363572227609.html

原创粉丝点击