`

Mybatis中几个重要类

 
阅读更多

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

1.org.apache.ibatis.mapping.MappedStatement

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

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

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

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

2.org.apache.ibatis.mapping.SqlSource

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.apache.ibatis.mapping;
 
/**
 *
 * This bean represets the content of a mapped statement read from an XML file
 * or an annotation. It creates the SQL that will be passed to the database out
 * of the input parameter received from the user.
 *
 */
public interface SqlSource {
 
  BoundSql getBoundSql(Object parameterObject);
 
}

SqlSource 接口只有一个getBoundSql(Object parameterObject)方法,返回一个BoundSql对象。一个BoundSql对象,代表了一次sql语句的实际执行,而 SqlSource对象的责任,就是根据传入的参数对象,动态计算出这个BoundSql,也就是说Mapper文件中的<if />节点的计算,是由SqlSource对象完成的。SqlSource最常用的实现类是DynamicSqlSource,来看一看它的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.apache.ibatis.scripting.xmltags;
 
import java.util.Map;
 
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.session.Configuration;
 
public class DynamicSqlSource implements SqlSource {
 
  private Configuration configuration;
  private SqlNode rootSqlNode;
 
  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }
 
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }
 
}

其中的

1
rootSqlNode.apply(context);

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import java.util.HashMap;
import java.util.Map;
 
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
 
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
 
public class DynamicContext {
 
  public static final String PARAMETER_OBJECT_KEY = "_parameter";
  public static final String DATABASE_ID_KEY = "_databaseId";
 
  static {
    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
  }
 
  private final ContextMap bindings;
  private final StringBuilder sqlBuilder = new StringBuilder();
  private int uniqueNumber = 0;
 
  public DynamicContext(Configuration configuration, Object parameterObject) {
    if (parameterObject != null && !(parameterObject instanceof Map)) {
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      bindings = new ContextMap(metaObject);
    } else {
      bindings = new ContextMap(null);
    }
    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  }
 
  public Map<String, Object> getBindings() {
    return bindings;
  }
 
  public void bind(String name, Object value) {
    bindings.put(name, value);
  }
 
  public void appendSql(String sql) {
    sqlBuilder.append(sql);
    sqlBuilder.append(" ");
  }
 
  public String getSql() {
    return sqlBuilder.toString().trim();
  }
 
  public int getUniqueNumber() {
    return uniqueNumber++;
  }
 
  static class ContextMap extends HashMap<String, Object> {
    private static final long serialVersionUID = 2977601501966151582L;
 
    private MetaObject parameterMetaObject;
    public ContextMap(MetaObject parameterMetaObject) {
      this.parameterMetaObject = parameterMetaObject;
    }
 
    @Override
    public Object get(Object key) {
      String strKey = (String) key;
      if (super.containsKey(strKey)) {
        return super.get(strKey);
      }
 
      if (parameterMetaObject != null) {
        Object object = parameterMetaObject.getValue(strKey);
        if (object != null) {
          super.put(strKey, object);
        }
 
        return object;
      }
 
      return null;
    }
  }
 
  static class ContextAccessor implements PropertyAccessor {
 
    public Object getProperty(Map context, Object target, Object name)
        throws OgnlException {
      Map map = (Map) target;
 
      Object result = map.get(name);
      if (result != null) {
        return result;
      }
 
      Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
      if (parameterObject instanceof Map) {
          return ((Map)parameterObject).get(name);
      }
 
      return null;
    }
 
    public void setProperty(Map context, Object target, Object name, Object value)
        throws OgnlException {
      Map map = (Map) target;
      map.put(name, value);
    }
  }
}

在 DynamicContext的构造函数中,可以看到,根据传入的参数对象是否为Map类型,有两个不同构造ContextMap的方式。而 ContextMap作为一个继承了HashMap的对象,作用就是用于统一参数的访问方式:用Map接口方法来访问数据。具体来说,当传入的参数对象不 是Map类型时,Mybatis会将传入的POJO对象用MetaObject对象来封装,当动态计算sql过程需要获取数据时,用Map接口的get方 法包装 MetaObject对象的取值过程。

 

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

1
2
3
static {
  OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}

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

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

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

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

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

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

分享到:
评论

相关推荐

    mybatis自动生成Java实体类和映射文件的自动生成类工具

    具体来说,这个自动生成工具的工作流程可能包括以下几个步骤: 1. **连接数据库**:首先,工具需要获取数据库的连接信息,包括数据库URL、用户名和密码,以便于访问数据库。 2. **获取表信息**:连接成功后,工具...

    Mybatis核心的几个包

    Mybatis作为一个轻量级的持久层框架,其核心组件对于理解和使用这个框架至关重要。下面将详细阐述Mybatis的核心包以及其中包含的关键知识点。 1. **SqlSessionFactoryBuilder**: 这是创建SqlSessionFactory的入口,...

    mybatis xml文件自动生成

    "mybatis xml文件自动生成"是开发过程中的一个重要环节,它可以帮助开发者提高效率,减少手动编写XML映射文件和对应的POJO(Plain Old Java Object)类的工作量。 MyBatis的Mapper文件是其核心组成部分之一,它包含...

    mybatis-3-mybatis-3.2.6

    在3.2.6版本中,MyBatis的源码包含以下几个关键模块: 1. **配置解析**:MyBatis的配置文件(mybatis-config.xml)被解析成Configration对象,这个对象包含了所有的全局配置信息,如数据源、事务管理器、Mappers等...

    Mybatis中文API多语言版

    Mybatis API文档主要包含以下几个部分: 1. **Mybatis概述**:这部分通常会介绍Mybatis的基本概念,包括它的设计理念、架构以及与其他ORM框架的比较。它会解释Mybatis如何帮助开发者处理SQL与Java对象的映射,以及...

    mybatis_eclipse_插件

    这通常包括以下几个步骤: 1. 打开Eclipse,进入"Help"菜单,选择"Install New Software"。 2. 在打开的窗口中,点击"Add"按钮,输入插件的更新站点地址。对于MyBatis插件,可能需要去官方网站或者社区找到对应的...

    maven中的mybatis依赖

    在选择MyBatis版本时,应该考虑以下几个方面: - **兼容性**:确保所选版本与你的项目需求和其他依赖兼容。 - **新特性**:新版本可能包含新的功能和改进,但同时也可能存在未知的bug。 - **社区支持**:较新的版本...

    Mybatis3中文手册【带目录】

    手册的主要内容可以分为以下几个部分: 1. **简介**:介绍Mybatis的基本概念,包括框架的目标、设计理念以及相对于其他ORM框架的优势。 2. **快速入门**:提供一个简单的示例,展示如何创建Mybatis的配置文件,...

    MyBatis中文学习手册(PDF)

    本中文学习手册将带你全面了解MyBatis的核心概念和技术,包括以下几个方面: 1. **简介**:首先,手册会介绍MyBatis的起源、设计理念以及在Java开发中的重要性。它解释了为什么MyBatis比传统的JDBC更易于使用,以及...

    mybatis延迟加载样例

    延迟加载是ORM框架的一个重要特性,能够有效防止大数据量的加载导致的内存溢出问题。 描述中提到的“附uml图说明”,表明我们还将通过UML(统一建模语言)图表来理解MyBatis延迟加载的结构和工作原理。UML图通常...

    mybatis自定义标签.zip

    首先,创建自定义标签需要以下几个步骤: 1. **定义标签处理类**:这是自定义标签的核心部分,你需要创建一个实现了`org.apache.ibatis.scripting.xmltags.XMLTag`接口的类。这个类将负责解析XML中的自定义标签,并...

    简单MyBatis所需jar包

    通常,为了运行一个基本的MyBatis程序,我们需要以下几个关键的jar文件: 1. **mybatis-x.x.x.jar**:这是MyBatis的核心库,包含了MyBatis框架的所有核心类和接口。这个jar文件提供了SqlSession、Mapper接口、...

    【MyBatis入门】—— MyBatis日志

    在MyBatis中,日志系统对于跟踪SQL语句和参数,以及检查执行效率至关重要。MyBatis支持两种主要的日志实现:Log4j和SLF4J。以下是对这两个日志框架的简要介绍: 1. **Log4j**:Apache Log4j 是一个广泛使用的日志库...

    Eclipse集成mybatis插件

    MyBatis-Eclipse插件是开发者在Eclipse中使用MyBatis时的重要辅助工具,它可以提供诸如自动生成Mapper接口、XML配置文件和实体类等便捷功能,减少手动编写这些代码的时间。集成这个插件后,开发者可以更高效地进行...

    springmybatis

    sessionFactory 用的,里面主要包含了数据库连接相关东西,还有 java 类所对应的别名,比如 &lt;typeAlias alias="User" type="com.yihaomen.mybatis.model.User"/&gt; 这个别名非常重要,你在 具体的类的映射中,比如User...

    《一头扎进MyBatis3》第八讲 Mybatis杂项

    在本讲《一头扎进MyBatis3》的第八部分,我们将探讨MyBatis框架中的几个重要但不常被深入讨论的主题,包括处理CLOB和BLOB类型数据、支持多个输入参数、分页功能以及缓存机制。这些知识点是MyBatis在实际项目开发中不...

    从iBatis迁移到MyBatis

    迁移过程通常涉及以下几个关键点: 1. **API变更**:MyBatis的API与iBatis有所不同,比如SqlSession接口和Mapper接口的使用方式。开发者需要理解并适应这些变化。 2. **配置文件迁移**:iBatis使用的是`...

    spring+mybatis 整合中文教程+例子

    整合过程涉及以下几个关键知识点: 1. **环境搭建**:首先,你需要安装JDK,配置好Java环境,然后选择一个集成开发环境(IDE),如IntelliJ IDEA或Eclipse。同时,确保你的项目是基于Maven或Gradle构建的,以便管理...

    MyBatis框架的学习(六)——MyBatis整合Spring

    整合MyBatis与Spring主要有以下几个步骤: 1. **引入依赖**:在项目中,你需要添加MyBatis和Spring的相关依赖库。如果使用Maven,可以在pom.xml文件中添加对应的依赖项,例如MyBatis-Spring的依赖。 2. **配置数据...

    MyBatisDemo && JAVA把一个对象的全部属性复制到另一个相同的对象

    这篇博文“MyBatisDemo && JAVA把一个对象的全部属性复制到另一个相同的对象”探讨的是如何在Java编程中实现对象属性的深度复制。MyBatis是一个流行的Java持久层框架,它简化了数据库操作,而对象复制则是处理业务...

Global site tag (gtag.js) - Google Analytics