MyBatis 入门到精通(四)

上一篇博客的传送门:MyBatis 入门到精通(三)

MyBatis 的核心点在于映射,顾名思义,将数据库表的数据和 Java 实体类进行一一对应,那么对应的原则是什么呢?

当我们执行查询操作时,通过查看日志我们看到如下效果:

从数据库中查询了一条数据,而我们在后边输出的 Emp 对象时,发现大部分内容都有值,只有 ename1 为 null,这是为什么呢?

答:数据库查询出来的虚拟列和 Java 实体类中的属性名一致(不区分大小写),就可以自动映射。

虚拟列名和属性名不一致

如果我们的查询出来的虚拟列和属性名不一致如何解决呢?

别名查询

从 SQL 语句入手,给查询的列起一个别名,别名和属性名一致就行。

select emp.*, ename as 'ename1' from emp where empstate = 1

手动映射

ResultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 ResultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

<!--手动映射-->
<resultMap id="guanwei" type="emp">
    <!--代表主键-->
    <id column="empNo" property="empNo"/>
    <result property="ename" column="EnAme"/>
    <result property="job" column="job"/>
    <result property="mgr" column="mgr"/>
    <result property="hireDate" column="hireDate"/>
    <result property="sal" column="sal"/>
    <result property="comm" column="comm"/>
    <result property="deptNo" column="deptNo"/>
</resultMap>

ResultMap 的属性列表:

属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

id|result 子元素内容 

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

这些元素是结果映射的基础。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 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

支持的 JDBC 类型

为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。

BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

实际使用中,我们会涉及多张表的操作,它们之间存在一定的关系,比较常见的有多对一和一对多关系。 

多对一关系

以 dept(部门) 和 emp(员工)来举例子,一个员工隶属于一个部门,一个部门有多个员工,那么员工和部门之间存在多对一的关系。在 java 中我们使用两种方式来处理这个问题:

  • 关联
  • 拼接

关联(association)元素处理。

比如,在我们的示例中,一个员工有一个部门。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。

<resultMap id="guanwei" type="emp">
   <id property="empNo" column="empno"/>
   <result property="ename" column="EnAme"/>
   <result property="job" column="job"/>
   <result property="mgr" column="mgr"/>
   <result property="hireDate" column="hireDate"/>
   <result property="sal" column="sal"/>
   <result property="comm" column="comm"/>    
   <result property="deptNo" column="deptNo"/>
   <!--将deptNo、dname和loc 赋予dept对象的deptNo、dname和loc属性-->
   <association property="dept" column="deptNo" javaType="dept">
       <id column="DEPTNO" property="deptNo"/>
       <result column="DNAME" property="dname"/>
       <result column="LOC" property="loc"/>
   </association>
</resultMap>

关联的不同之处是,你需要告诉 MyBatis 如何加载关联。 它的相关属性是:

属性描述
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
property映射到列结果的字段或属性。
javaType  一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。
select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
fetchType

可选的。有效值为 lazy 和 eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

实际的查询操作是:

<select id="findEmpByNo" parameterType="_int" resultMap="guanwei">
    select *
        from emp,
             dept
        where emp.deptno = dept.DEPTNO
          and empno = #{empNo} and empstate=1
</select>

关联是通过表连接的方式来查询数据,只连接一次数据库,能有效的节约连接资源,就可以将多张表的数据获取出来,在通过 resultMap 的 association 将数据“关联”起来。

拼接方式处理

关联方式可以有效节约连接资源,但随之产生的笛卡尔问题却不可避免,那么我们可以多次查询数据库,将多个结果获取出来后封装到一个对象中。

查询员工

<select id="findEmpByNo" parameterType="_int" resultType="emp" >
     select * from emp where  empno = #{empNo} and empstate=1
</select>

查询这个员工的部门

<select id="findDeptByNo" parameterType="int" resultType="dept">
    select * from dept where deptno = ${deptNo}
</select>

组装到一起

EmpMapper mapper = session.getMapper(EmpMapper.class);
DeptMapper mapper2 = session.getMapper(DeptMapper.class);
Emp emp = mapper.findEmpByNo(7566);
Dept dept = mapper2.findDeptByNo(emp.getDeptNo());
emp.setDept(dept);
session.close();

组装方式可以避免笛卡尔积问题,但是多次连接数据库也会耗费资源,具体应用可以根据实际来使用。

一对多关系 

如果说,员工对于部门是多对一,那么部门对于员工就是一对多。一对多也是一样的两种方式

  • 集合
  • 拼接

集合方式

和关联比较接近,只不过返回的结果有多条需要通过集合进行封装。

<resultMap id="onetomany" type="dept">
   <id column="deptNo" property="deptNo"/>
   <result column="dname" property="dname"/>
   <result column="loc" property="loc"/>
   <!--描述一对多的关系-->
   <collection property="empSet" ofType="emp" column="deptNo" fetchType="lazy">        
       <id column="empNo" property="empNo"/>
       <result property="ename" column="EnAme"/>
       <result property="job" column="job"/>
       <result property="mgr" column="mgr"/>
       <result property="hireDate" column="hireDate"/>
       <result property="sal" column="sal"/>
       <result property="comm" column="comm"/>
       <result property="deptNo" column="deptNo"/>
   </collection>
</resultMap>

collection 的属性和 association 基本一致,引用方式也一致,这里不在特别说明。

拼接方式

也是查询多次,一次查询部门信息,一次查询这个部门的所有员工信息,将它们组装到一起。

这里只引用组装的代码

SqlSession session = ssf.openSession();
EmpMapper mapper = session.getMapper(EmpMapper.class);
DeptMapper mapper2 = session.getMapper(DeptMapper.class);
// 只能查询到部门信息
Dept dept = mapper2.findDeptByNo(10);
Set<Emp> empSet = mapper.findEmpByDeptNo(10);
// 组装
dept.setEmpSet(empSet);

对关联或集合的映射,并没有深度、广度或组合上的要求。但在映射时要留意性能问题。 在探索最佳实践的过程中,应用的单元测试和性能测试会是你的好帮手。 而 MyBatis 的好处在于,可以在不对你的代码引入重大变更(如果有)的情况下,允许你之后改变你的想法。 

高级关联和集合映射是一个深度话题。章节的介绍只能到此为止。配合少许的实践,你会很快了解全部的用法。

在下一篇博客中我会带领大家一起感受 MyBatis 动态SQL的魅力---- MyBatis 入门到精通(五)

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值