结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份
resultMap 能够代替实现同等功能的数千行代码。ResultMap
的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
之前已经使用过简单映射语句的示例,它们没有显式指定 resultMap。比如:
<select id="selectUserById" parameterType="Integer"
resultType="user">
select * from t_user where u_id = #{id}
</select>
上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。看看下面这个 JavaBean:
package pers.goodwin.mybatis.bean;
/**
* @author goodwin
*
*/
public class User {
private Integer id;
private String username;
private String password;
private Integer gender;
private Integer cid;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", gender=" + gender + ", cid="
+ cid + "]";
}
}
基于 JavaBean 的规范,上面这个类有 5个属性:id ,username, password,gender 和cid。这些属性会对应到 select 语句中的列名。
这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。
<select id="selectUserById" parameterType="Integer"
resultType="user">
select * from t_user where u_id = #{id}
</select>
类型别名是你的好帮手。使用它们,你就可以不用输入类的全限定名了。比如:
<!-- sqlMapConfig.xml 中 -->
<typeAliases>
<!-- <typeAlias alias="user" type="pers.goodwin.mybatis.bean.User"/> -->
<!-- 推荐使用扫描包的形式来配置别名 包的形式会扫描包及子包下的所有文件, 以对象类名为别名,大小写不敏感,推荐使用小写 -->
<package name="pers.goodwin.mybatis.bean" />
</typeAliases>
<!-- SQL 映射 XML 中 -->
<select id="selectUserById" parameterType="Integer"
resultType="user">
select * from t_user where u_id = #{id}
</select>
在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。比如:
<select id="selectUsers" resultType="User">
select
u_id as "id",
u_username as "userName"
from t_user
where u_id = #{id}
</select>
你会发现上面的例子没有一个需要显式配置 ResultMap,这就是 ResultMap 的优秀之处——你完全可以不用显式地配置它们。 虽然上面的例子不用显式配置 ResultMap。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。
<resultMap id="userResultMap" type="User">
<id property="id" column="u_id" />
<result property="username" column="u_username"/>
<result property="password" column="u_password"/>
<result property="gender" column="u_gender"/>
<result property="cid" column="u_cid"/>
</resultMap>
然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
<select id="selectUserById" parameterType="Integer" resultMap="userResultMap">
select * from t_user
where u_id = #{id}
</select>
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。
结果映射
id & result
<id property="id" column="u_id"/>
<result property="username" column="u_username"/>
这些元素是结果映射的基础。id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
两个元素都有一些属性:
Id 和 Result 的属性
属性 描述
- property 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
- column 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
- javaType 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
- jdbcType JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
- typeHandler 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。
支持的 JDBC 类型
为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。
一对一
一个用户只属于一个国家
新建一个用户包装类
UserVo.java
package pers.goodwin.mybatis.bean;
public class UserVo extends User {
private Country country;
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
@Override
public String toString() {
return "UserVo [getId()=" + getId() + ", getUsername()=" + getUsername() + ", getPassword()=" + getPassword()
+ ", getGender()=" + getGender() + ", country=" + country + "]";
}
}
UserMapper.java
public List<UserVo> selectUserVo();
mapper配置
<resultMap id="userVoMap" type="UserVo">
<id property="id" column="u_id" />
<result property="username" column="u_username"/>
<result property="password" column="u_password"/>
<result property="gender" column="u_gender"/>
<!-- 一对一 -->
<association property="country" javaType="country">
<id property="c_id" column="c_id" />
<result property="c_countryname" column="c_countryname"/>
</association>
</resultMap>
<select id="selectUserVo" resultMap="userVoMap">
select u_id,u_username,u_password,u_gender,c_id,c_countryname
from t_user
left join t_country
on u_cid = c_id
</select>
测试
@Test
public void Test8() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuilder ssb = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = ssb.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<UserVo> userlist = mapper.selectUserVo();
for (UserVo u : userlist) {
System.out.println(u);
}
}
一对多
一个国家可以有多个用户
新建一个国家包装类
CountryVo.java
package pers.goodwin.mybatis.bean;
import java.util.List;
public class CountryVo extends Country {
private List<User> userList;
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
@Override
public String toString() {
return "CountryVo [getC_id()=" + getC_id() + ", getC_countryname()=" + getC_countryname() + ", userList="
+ userList + "]";
}
}
CountryMapper.java
package pers.goodwin.mybatis.mapper;
import java.util.List;
import pers.goodwin.mybatis.bean.CountryVo;
public interface CountryMapper {
public List<CountryVo> selectCountryVo();
}
CountryMapper.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="pers.goodwin.mybatis.mapper.CountryMapper">
<resultMap type="CountryVo" id="countryVo">
<id property="c_id" column="c_id"/>
<result property="c_countryname" column="c_countryname"/>
<!-- 一对多关系 -->
<collection property="userList" ofType="User">
<id property="id" column="u_id"/>
<result property="username" column="u_username"/>
<result property="gender" column="u_gender"/>
</collection>
</resultMap>
<select id="selectCountryVo" resultMap="countryVo">
select c_id,c_countryname,u_id,u_username,u_gender
from t_country
left join t_user
on c_id = u_cid
</select>
</mapper>
测试
@Test
public void Test9() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuilder ssb = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = ssb.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);
List<CountryVo> list = mapper.selectCountryVo();
for (CountryVo c : list) {
System.out.println(c);
}
}
==> Preparing: select c_id,c_countryname,u_id,u_username,u_gender from t_country left join t_user on c_id = u_cid
==> Parameters:
<== Total: 6
CountryVo [getC_id()=1, getC_countryname()=中国, userList=[User [id=1, username=隔壁老王, password=null, gender=0, cid=null], User [id=4, username=李四, password=null, gender=1, cid=null]]]
CountryVo [getC_id()=2, getC_countryname()=法国, userList=[User [id=3, username=张三, password=null, gender=0, cid=null]]]
CountryVo [getC_id()=3, getC_countryname()=美国, userList=[User [id=2, username=王五, password=null, gender=1, cid=null]]]
CountryVo [getC_id()=4, getC_countryname()=英国, userList=[User [id=5, username=王八, password=null, gender=0, cid=null]]]
CountryVo [getC_id()=5, getC_countryname()=俄罗斯, userList=[User [id=6, username=李白, password=null, gender=0, cid=null]]]
其他文件
User.java
package pers.goodwin.mybatis.bean;
/**
* @author goodwin
*
*/
public class User {
private Integer id;
private String username;
private String password;
private Integer gender;
private Integer cid;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", gender=" + gender + ", cid="
+ cid + "]";
}
}
Country.java
package pers.goodwin.mybatis.bean;
/**
* @author goodwin
*
*/
public class Country {
private Integer c_id;
private String c_countryname;
public Integer getC_id() {
return c_id;
}
public void setC_id(Integer c_id) {
this.c_id = c_id;
}
public String getC_countryname() {
return c_countryname;
}
public void setC_countryname(String c_countryname) {
this.c_countryname = c_countryname;
}
@Override
public String toString() {
return "Country [c_id=" + c_id + ", c_countryname=" + c_countryname + "]";
}
}
db.properties
#database configuration information
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_ssm_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=111111
log4j.properties
#Global configuration
log4j.rootLogger=DEBUG,stdout
#Console configuration
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.layout.ConversionPattern=%5p [%t] - %m%n
sqlMapConfig.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>
<!-- 读取数据库配置文件 -->
<properties resource="db.properties" />
<typeAliases>
<!-- <typeAlias alias="user" type="pers.goodwin.mybatis.bean.User"/> -->
<!-- 推荐使用扫描包的形式来配置别名 包的形式会扫描包及子包下的所有文件, 以对象类名为别名,大小写不敏感,推荐使用小写 -->
<package name="pers.goodwin.mybatis.bean" />
</typeAliases>
<!-- 配置环境-默认环境id为MySQL -->
<environments default="MySQL">
<environment id="MySQL">
<!-- 使用JDBC事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 将sql映射文件注册到全局配置文件中 -->
<mappers>
<!-- 使用相对于类路径的资源引用 -->
<!-- <mapper resource="pers/goodwin/mybatis/mapper/UserMapper.xml" /> -->
<!-- 使用完全限定资源定位符(URL) -->
<!-- <mapper url="file:///G:\Java\ssm_mybatis_part1\src\pers\goodwin\mybatis\mapper\UserMapper.xml"/> -->
<!-- 使用映射器接口实现类的完全限定类名 -->
<!-- <mapper class="pers.goodwin.mybatis.mapper.UserMapper"/> -->
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<package name="pers.goodwin.mybatis.mapper" />
</mappers>
</configuration>