# 3.2 对象关系映射

ORM（Object Relational Mapping），对象关系映射，是一种为了解决面向对象与关系型数据库不匹配而出现的技术，使开发者能够用面向对象的方式使用关系型数据库。

目前最为常用的ORM框架主要是MyBatis（前身是ibatis）和Hibernate。两者对比如下：

1. MyBatis非常简单易学，Hibernate相对较复杂，门槛较高。
2. 相比起Hibernate，MyBatis灵活性更好。
3. 系统数据处理量巨大，性能要求极为苛刻的情况下，MyBatis能够高度定制化SQL，因此会有更好的可控性和表现。
4. MyBatis需要手写SQL语句，也可以生成一部分，Hibernate则基本上可以自动生成，偶尔会写一些HQL。同样的需求,MyBatis的工作量比Hibernate要大很多。类似的，如果涉及到数据库字段的修改，Hibernate修改的地方很少，而MyBatis要把那些SQL mapping的地方一一修改。
5. 以数据库字段一一对应映射得到的PO和Hibernte这种对象化映射得到的PO是截然不同的，本质区别在于这种PO是扁平化的，不像Hibernate映射的PO是可以表达立体的对象继承，聚合等等关系的，这将会直接影响到你的整个软件系统的设计思路。

Hibernate现在主要用在传统企业应用的开发，互联网领域中由于流量大、并发高的缘故因此主要以MyBatis为主。笔者也推荐使用MyBatis做为ORM框架。

一般的ORM包括以下几个部分： 　　

* 一个规定Mapping Metadata的工具, 即数据库中的表、列与对象以及对象属性的映射。 　　
* 一个对持久类对象进行CRUD操作的API。
* 一个语言或API用来规定与类和类属性相关的查询。 　　
* 一种技术可以让ORM的实现同事务对象一起进行缓存、延迟加载等操作。

## 3.2.1 Mapping

MyBatis支持XML配置：

```
<!--mybatis-config.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="TestUserMapper.xml"/>
  </mappers>
</configuration>

<!--TestUserMapper.xml-->
<?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="me.rowkey.pje.mybatis.TestUserMapper">
  <select id="selectUser" resultType="TestUser">
    select * from test_user where id = #{id}
  </select>
</mapper>
```

其中，environment元素体中包含了事务管理和连接池的配置,能够根据不同的环境使用不同的数据库配置。这里的dataSource设置为POOLED时使用了MyBatis自己提供的数据连接池。mappers元素则是包含一组mapper映射器（这些 mapper 的 XML 文件包含了 SQL 代码和映射定义信息）。

MyBatis也支持注解映射Mapper:

```
public interface TestUserMapper {
  
  @Select("SELECT * FROM test_user WHERE id = #{id}")
  TestUser selectUser(int id);

}
```

这里需要注意的是，命名空间现在是必需的。并且MyBatis对所有的命名配置元素的解析如果是全限定名则直接用；而如果是一个简单的名称，全局唯一的话没有问题，如果有重复类则会报错。

此外，MyBatis提供了mybatis-spring这个类库用于集成Spring和MyBatis。配置如下：

```
<!-- datasoure数据源  -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="username" value="${mysql.username}"></property>
    <property name="password" value="${mysql.pwd}"></property>
    <property name="maxTotal" value="${mysql.max}"></property>
    <property name="minIdle" value="${mysql.minIdle}"></property>
    <property name="maxIdle" value="${mysql.maxIdle}"></property>
    ...
</bean>	
 
<!-- 配置sqlSessionFactory工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
	<property name="dataSource" ref="dataSource" />  
	<property name="configLocation" value="classpath:sqlMapConfig.xml"></property>  
</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
	<constructor-arg index="0"  ref="sqlSessionFactory" />
</bean>

<bean id="userDAO" class="me.rowkey.pje.mybatis.dao.UserDao">
	<property name="sqlSessionTemplate" ref="sqlSessionTemplate"></property>
</bean>
```

## 3.2.2 CRUD以及属性的查询

MyBatis的核心是SqlSessionFactory，要使用它最基本的操作API，首先获取到SqlSessionFactory,然后再去获取相应的Mapper。

```
String resource = "classpath:mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession session = sqlSessionFactory.openSession();
TestUserMapper mapper = session.getMapper(TestUserMapper.class);
TestUser testUser = mapper.selectUser(1);
...
```

此外，在Spring中可以使用MyBatis提供的org.mybatis.spring.SqlSessionTemplate进行操作。

```
SqlSessionTemplate sqlTemplate = new SqlSessionTemplate(sqlSessionFactory);
TestUser testUser = (TestUser)this.sqlSessionTemplate.selectOne("me.rowkey.pje.mybatis.TestMapper.selecUser", uid);
```

这里需要注意的是，SqlSession是非线程安全的，而SqlSessionTemplate则是线程安全的。

MyBatis对应于CRUD有以下几个映射语句：

* insert映射插入语句
* update映射更新语句
* delete映射删除语句
* select映射查询语句

依赖于上述的四种操作，可以完成各种crud以及对属性之类的查询。MyBatis会自动把数据库查询结果注入到返回对象中。

这里需要注意的是上述的`select * from test_user where id = #{id}`，其中的#{id}是告诉MyBatis创建预处理语句属性并以它为背景设置安全的值, 对应于PreparedStatement中的?。此外，在MyBatis中还存在另一个符号$，如下：

```
<select id="selectUserByStatus" resultType="TestUser">
    select * from test_user where status = #{status} order by ${columnName}
</select>
```

$在这里的作用只是做字符串替换，不会修改或转义字符串。因此，能使用#的地方就不要用$，除非是像order by这种不是参数的地方。

此外，MyBatis对于insert、update、delete以及select都提供了很多选项，用来支持注入缓存、自动映射、列名和属性名的转换等优化功能。

## 3.2.3 缓存

MyBatis支持一级缓存和二级缓存：

* Mybatis默认开启一级缓存，是SqlSession级别的，Session结束那么缓存即清空。另外还支持Statement级别，即缓存只对当前的Statement有效（没什么用）。
* MyBatis的二级缓存是Mapper级别的，被多个SqlSession共享，需要手动开启。

```
<!--TestMapper.xml-->
<?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="me.rowkey.pje.mybatis.TestMapper">
    <cache/>
    <select id="selectUser" resultType="TestUser" useCache="false" flushCache="true">
        select * from test_user where id = #{id}
    </select>
</mapper>
```

上述配置中的useCache设置为true即开启二级缓存，对应mapper中的所有select语句的结果集将会被缓存。也可以针对某个select设置useCache为false关闭二级缓存，设置flushCache为true使得数据直接flush到数据库中防止出现脏读数据。此外，二级缓存也可以配置成使用自定义的缓存实现。

```
<cache type="me.rowkey.pje.mybatis.cache.MyCustomCache"/>
```

需要注意的是MyBatis中的缓存设计初衷是针对单点应用的，都是本地缓存，现在的分布式应用慎重开启，只单纯把MyBastis作为一个ORM框架，在Service层自己实现缓存机制是更好的选择。

## 3.2.4 结果映射

MyBatis默认会自动映射查询结果，根据SQL返回的列名并在Java类中查找相同名字的属性（忽略大小写）。还可以全局配置对数据库列的LowUnderscore（a\_column）命名转换为LowCamel命名（aColumn）。

```
<configuration>  
     <settings>  
          <setting name="mapUnderscoreToCamelCase" value="true" />  
     </settings>  
</configuration>  
```

除此之外，MyBatis还支持自定义映射。

```
public class UserDto{
    private long uid;
    private String userName;
    
    //getter and setters
    ....
}

<mapper namespace="me.rowkey.pje.mybatis">  
    <resultMap type="UserDto" id="userResultMap">  
       <result property="userName" column="uname"/>  
    </resultMap> 
    
    <!-- 根据uid查询用户开放信息 -->  
    <select id="getOpenUserInfo" parameterType="String" resultType="UserDto" resultMap="userResultMap"
        useCache="true" flushCache="true">  
        <![CDATA[  
            select uid,uname from test_user  
                WHERE uid = #{id}   
        ]]>    
    </select>   
</mapper>
```

上面使用的resultMap即做了自定义的映射工作，uid会自动映射, userName会使用自定义映射。

## 3.2.5 SQL语句构建器

虽然Mybatis已经做了很多封装，可以大大简化SQL编写的工作，但是很多时候在代码拼接SQL是无法避免的。如果用字符串自己进行拼接，那么各种+号、括号、引号、格式化问题会带来很多的麻烦，一不小心就会出错。针对这种状况，MyBatis提供了SQL语句构建类org.apache.ibatis.jdbc.SQL, 可以大大简化动态SQL编写的问题。

```
new SQL() {{
    SELECT("user.id, user.user_name");
    SELECT("user.sign, user.gender");
    FROM("user_account user");
    INNER_JOIN("user_base_info uinfo on user.uid = uinfo.uid");
    WHERE("user.user_name like ?");
    OR();
    WHERE("uinfo.nick_name like ?");
    ORDER_BY("user.create_time");
  }}.toString();
```

等同于

```
"SELECT user.id, user.user_name, "
"user.sign, user.gender " +
"FROM user_account user " +
"INNER JOIN user_base_info uinfo on user.uid = uinfo.uid" +
"WHERE (user.user_name like ?) " +
"OR (uinfo.nick_name like ?) " +
"ORDER BY user.create_time";
```

## 3.2.6 使用提示

1. 当几个SQL语句都包含同样的部分SQL逻辑时。可以使用\<include, refid="id" />来进行复用，如：

   ```
   <sql id="getTestByStatus_fragment">
       from test_meta where status = #{status} 
   </sql>

   <select id='getTestList' parameterType='map' resultMap='Test'>
       select id
       <include refid="getTestByStatus_fragment"/>
       order by
       create_time desc
   </select>
   ```
2. 动态SQL中的空字符串判断

   在动态SQL中判断是否是空字符串时，MyBatis的内建机制不太好用。建议使用以下方式：

   ```
   <if test="param != null and param != '' ">
   ```
3. 多ResultMap复用

   一个应用中由于功能的不同经常会有多个mapper.xml，如果一个文件需要另一个文件中的resultMap的定义，可以直接引用，而不需要再重新定义一遍。如：

   ```
   <!--A.mapper.xml-->
   <mapper namespace="me.rowkey.pje.mybatis">
       ...
       <resultMap id="userResultMap" type="me.rowkey.pje.mybatis.UserDto"></resultMap>
   </mapper>

   <!--B.mapper.xml-->
   <mapper namespace="me.rowkey.pje.test">
       ...
       <select id="bSql" parameterType="map" resultMap="me.rowkey.pje.mybatis.userResultMap>
       ...
       </select>
   </mapper>
   ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rowkey-books.gitbook.io/pragmatic-java-engineer/chapter3-framework/orm.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
