MyBatis学习笔记(2)—映射关系篇

4,001 阅读9分钟

MyBatis学习笔记(1)—使用篇

MyBatis学习笔记(2)—映射关系篇

MyBatis学习笔记(3)—高级映射之一对一映射

Mybatis学习笔记(4)-高级映射之一对多映射

Mybatis学习笔记(5)-高级映射之多对多映射

...敬请期待

通过上一篇的入门MyBatis学习笔记(1)—使用篇,可以发现除了一些基本配置外,Mybatis的精华就在于Mapper(映射)部分。Mybatis是针对映射器构造的SQL构建的轻量级框架,并且可以通过配置生成对应的JavaBean给调用者。在Mybatis中可以灵活使用SQL来满足各种不同场景的需求,所以在互联网企业内更偏爱于使用Mybatis来应对高速变化的需求,而传统企业更偏向于使用Hibernate。

映射器的主要元素

上一节我们仅仅定义了一个select语句:

<mapper namespace="com.shuqing28.dao.CommodityDao">
    <select id="getAllCommodity" resultType="com.shuqing28.pojo.Commodity">
        SELECT * FROM commodity
    </select>
</mapper>

实际上增删改查在Mybatis映射器里面都有对应的元素

元素名称 描述 备注
select 查询语句,最常用的 可以自定义参数,返回结果集
insert 插入语句 执行后返回一个整数,代表插入的条数
update 更新语句 执行后返回一个整数,代表更新的条数
delete 删除语句 执行后返回一个整数,代表删除的条数
parameterMap 定义参数映射关系 即将被删除的元素,不建议使用
sql 允许定义一部分的SQL,然后在各个地方引用 例如,定义一个表名,在其它地方的SQL语句中使用
resultMap 用来描述从数据库结果集中加载对象 它将提供映射规则
cache 给定命名空间的缓存配置 ————
cache-ref 其他命名空间缓存配置的引用 ————

下面我们探究主要使用的几个元素

select

select元素是我们最常用的元素,正如在SQL里是查询功能也是最主要的功能之一。在我们常识里,查询语句通常都是根据一个或者多个条件,查询数据库返回一个,多个或者所有字段。之前我们举得getAllCommodity没有查询条件,返回所有字段。

在Mybatis里,我们可以传入简单的参数类型,像int,float,String这些,也可以传入一些复杂的类型,比如JavaBean、Map之流的,Mybatis把这些参数转换成SQL语句需要的类型后,返回结果,并且通过映射,将结果集自动绑定到JavaBean中,所以JDBC里面那一套一个一个从ResultSet获取值的操作都被省略了。

下面我们看一个有参数的select语句:

<select id="getCommodityById" parameterType="int" resultType="com.shuqing28.pojo.Commodity">
            SELECT * FROM commodity where id=#{id}
</select>

同时我们定义了接口方法:

Commodity getCommodityById(Integer id);

不同于getAllCommodity没有传入参数,getCommodityById有一个int类型的传入参数,返回的还是Commodity。 上面的select语句包含以下属性:

  • id标识了这条sql,id与接口名保持一致
  • parameterType定义参数类型,这里就是int
  • resultType定义了返回值类型,这里是我们定义的POJO类

这里看resultType,我们查询的是commodity的所有字段,再次回顾字段定义:

字段名 类型
id int
name varchar(255)
price decimal(8,2)
description varchar(255)

而我们的POJO类包含:

private Integer id;
private String name;
private Double price;
private String description;

这里Mybatis帮我们完成了自动映射,只要返回的SQL列名和JavaBean的属性一致,Mybatis就可以帮助我们自动回填这些字段而无需任何配置。我们可以再Mybatis的配置文件里,通过autoMappingBehavior来设置自动映射,它包含3个值:

  • NONE:取消自动映射
  • PARTIAL:这也是默认值,只会自动映射,没有定义嵌套结果集映射的结果集
  • FULL:会自动映射任意复杂的结果集(无论是否嵌套)

刚才我们举例的是只传递了一个参数,如果传递多个参数呢?这时候就要说说输入映射了。

输入映射

传递多个参数通常来说有3种方式,通过Map,通过注解,以及通过POJO对象

通过Map传递参数

假设我们想查询带有的食物,并且价格低于80的。那么我们可以这么写SQL语句

<select id="getCommodityByMap" parameterType="map" resultType="com.shuqing28.pojo.Commodity">
                SELECT * FROM commodity WHERE name like '%${name}%' AND price&lt;#{price}
        </select>

因为mybatis使用xml定义Mapper,所以这里'<'用了转义符&lt;表示,其实还是一样的。 parameterType定义为map,我们定义的接口也是接受Map作为参数

List<Commodity> getCommodityByMap(Map<String, Object> params);

使用:

CommodityDao commodityDao = sqlSession.getMapper(CommodityDao.class);
Map<String, Object> paramsMap = new HashMap<String , Object>();
paramsMap.put("name", "饼");
paramsMap.put("price", 80.0);
List<Commodity> commodities = commodityDao.getCommodityByMap(paramsMap);

最后打印commodities:

[Commodity{id=1001, name='野葡萄烤饼', price=10.0, description='吃完还有点饿'}, Commodity{id=1003, name='南瓜百吉饼', price=50.0, description='份大量足,可以去很远的地方'}]

虽然Map很简单,但是光是看传入参数Map,你不知道包含了怎样的内容,若是深入还得看如何设置Map的,而且使用时还要一项项指定Map的Key的名字,所以不怎么提倡使用了。

使用注解方式传递参数

注解方式使用了**@Param**注解,我们这样定义接口:

List<Commodity> getCommodityByAnnation(@Param("name") String name, @Param("price") Double price);

Mybatis根据@Param提供的名称,把变量值传到SQL语句中对应的参数中,参数的可读性大大提高,但是不足之处在于,若是参数很多,一个一个写很麻烦,降低了可读性。

所以最经常使用的还是利用POJO传递参数。

使用POJO传递参数

通常我们只需要传递简单的POJO对象就可以了,比如我们这样定义select元素:

<select id="getCommodityByPOJO" parameterType="com.shuqing28.pojo.Commodity" resultType="com.shuqing28.pojo.Commodity">
    SELECT * FROM commodity WHERE name like '%${name}%' AND price&lt;#{price}
</select>

测试:

@Test
public void getLowPriceCommodityByPOJO(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        CommodityDao commodityDao = sqlSession.getMapper(CommodityDao.class);
        Commodity commodity = new Commodity();
        commodity.setName("饼");
        commodity.setPrice(80.0);
        List<Commodity> commodities = commodityDao.getCommodityByPOJO(commodity);
        System.out.println(commodities);
    } finally {
        sqlSession.close();
    }
}

也会返回和上面一样的结果,这里Mybatis会把POJO里对应的属性值传到SQL里,然后返回结果,注意看传入参数就是POJO的完全限定名,和ResultType是一样的,其实可以通过别名减少名称的长度,我们只需要在mybatis-config.xml中的configuration中定义:

<typeAliases>
        <typeAlias alias="commodity" type="com.shuqing28.pojo.Commodity" />
</typeAliases>

就可以用commodity替换所有的全称了,但是不能有重名的,否则会出错。

有些负责的情况,我们需要定义POJO作为查询参数,比如上面的例子,你只需要两个输入参数,你也可以定制一个只包含这两个参数的POJO。

public class CommodityCustom {
    private String name;
    private Double price;

当然使用过程还是一样的,定义自己包装过的POJO通常用来解决传入查询条件很复杂的情况,比如设计到几张表联查的,这时候原先定义的POJO解决不了问题,就需要定义一些复合的POJO。

在输入参数中还有个经常被提到的问题,即#和$的区别

#和$的区别

Mybatis本身是基于JDBC封装的。所以#{para}是预编译处理(PreparedStatement),相当于原来的?,而${para}是字符串替换。Mybatis在处理#时,会调用PreparedStatementset系列方法来赋值;处理$时,就是把${para}替换成变量的值。#方式能够很大程度防止sql注入,$方式一般用于传入数据库对象,例如传入表名,一般能用#的就别用$.

说完输入我们说输出映射。

输出映射

MyBatis的输出映射分为两种方式,resultType和resultMap

resultType

我们上面所有的例子里都是定义的resultType,resultType可以是简单类型,比如我们想获取商品数量啊之类的,还有就是输出POJO对象或者POJO列表。

不管是POJO对象还是POJO列表,我们在resultType中的定义都是一样的,只不过接口定义不一样:

单个对象
Commodity getCommodityById(Integer id);

Mybatis根据接口返回值判断返回一条对象,如果用过ibatis的可以知道内部调用了session.selectOne。

返回列表
List<Commodity> getCommodityByPOJO(Commodity commodity);

内部使用session.selectList,Mapper接口使用List<XXX>作为返回值。

查询出来的列名和POJO属性只要有一个一致就会创建POJO对象,顶多别的字段为默认值,但是如果全部不一致,就不会创建该POJO对象了。

resultMap

上面的resultType在查询的列名和POJO属性值一致的时候才可以映射成功,如果不一致的话,就需要resultMap登场表演了。

如果查询出来的列名和POJO的属性名不一致,通过定义一个resultMap对列名和POJO属性名之间作一个映射关系。

在使用resultMap前我们需要定义resultMap 假设我们查询商品类的两个字段id和name,但是查询的时候定义为id_name_。列名和属性名不一致了,先定义resultMap

<resultMap id="commodityResultMap" type="commodity">
    	<id column="id_" property="id"/>
    	<result column="name_" property="name"/>
    </resultMap>

其中

  • id标识这个resultMap
  • type标识映射哪个POJO里面的属性,这里因为上面说过别名了,所以就是映射的Commodity这个POJO
  • id元素标识这个对象的主键,result就是普通字段
  • property代表POJO的属性名称
  • column表示数据库SQL的列名

再看我们的select元素:

<select id="getCommodityByPrice" parameterType="double" resultMap="commodityResultMap">
       SELECT id id_, name name_ FROM USER WHERE price=#{price}
</select>

使用resultType进行输出映射时,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。 此外,resultMap还可以做一对多、多对多等高级映射,这些内容将在后续文章中介绍。

insert

insert相对于select简单很多,下面是往商品表插入一个商品的实例:

<insert id="insertCommodity" useGeneratedKeys="true"
   keyProperty="id">
   insert into commodity (name,price,description)
   values (#{name},#{price},#{description})
</insert>

接口为:

void insertCommodity(Commodity commodity);

这里我们注意到了insert语句里多了两个属性useGeneratedKeyskeyProperty,这里涉及到的是主键回填的概念。

  • useGeneratedKeys:告诉Mybatis是否使用数据库内置策略生成主键
  • keyProperty:告诉Mybatis哪一列是主键 这样我们无须传入id值,Mybatis就可以帮我们设置主键,同时还会回填POJO内的id值,当我们像下面这样调用时:
@Test
public void insertCommodity(){
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    CommodityDao commodityDao = sqlSession.getMapper(CommodityDao.class);
    Commodity commodity = new Commodity();
    //commodity.setId(1005);
    commodity.setName("艾蒿小麦饼");
    commodity.setPrice(100.0);
    commodity.setDescription("像在艳阳下恋爱");
    commodityDao.insertCommodity(commodity);
    System.out.println(commodity.getId());
} finally {
    sqlSession.close();
}
}

通过打印我们能得到1005,说明commodity已经具备了id,这就是回填的效果。 在这里,系统默认的方式是给主键值加1,如果我们想要定义自己的主键生成方式,可以使用selectKey进行自定义:

<insert id="insertCommodity" useGeneratedKeys="true"
    keyProperty="id">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
        select if (max(id) is null, 1, max(id) + 2) as newId from commodity
    </selectKey>
    insert into commodity (name,price,description)
    values (#{name},#{price},#{description})
</insert>

这里我们定义主键值是最大id加2,如果还没有记录,则初始化为1。

update和delete

update和的delete都会返回影响的条目数 下面仅仅列出配置的实例:

<update id="updateCommodity" parameterType="commodity">
    update commodity set 
    name=#{name},
    price=#{price},
    description=#{description}
    where id=#{id}
</update>

<delete id="deleteCommodity" parameterType="int">
    delete from commodity where id=#{id}
</delete>

这一节主要介绍了主要的几个语句的使用,着重介绍了select语句,同时结合select语句说明了Mybatis中输入映射和输出映射的内容,其它一些高级的映射内容,留到后面的文章介绍。