MyBatis必知必会

流程

  • 创建持久化类(POJO类)
  • 编写持久化操作的Mapper文件,其中定义SQL语句
  • 创建配置文件:连接哪种数据库、配置数据源、mappers文件路径
  • 获取SqlSessionFactory和SqlSession,通过SqlSession操作数据库
  • 关闭SqlSession
public class MyBatisTest
{
    public static void main(String[] args)
    {
        //读取MyBatis的配置文件,其中包括连接哪种数据库,配置数据源,mappers文件路径等
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        //获得Session实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = SqlSessionFactory.openSession();

        //创建User
        User user = new User("admin", "男", 26);

        //插入数据,insert的第一个参数为UserMapper文件中id为save的动作,这里是<insert ..>
        session.insert("org.test.mapper.UserMapper.save", user);
        session.commit();
        session.close();
    }
}

详解

UserMapper
<mapper namespace="org.test.mapper.UserMapper">
    <insert id="save" parameterType="org.test.domainUser" useGeneratedKeys="true">
        INSERT INTO TB_USER(name, sex, age) VALUES(#{name}, #{sex}, #{age})
    </insert>
</mapper>

id:用于在程序中引用该语句,也是session.insert()的第一个参数
parameterType:语句的接收参数,这里是我们定义的POJO类User

SqlSessionFactory

MyBatis的关键对象,它是单个数据库映射关系经过编译后的内存镜像

SqlSession
  • MyBatis的关键对象,是执行持久化操作的对象,类似于JDBC中的Connection。
  • 底层封装了JDBC连接,可用Session实例直接执行已映射的SQL语句
  • 线程非安全对象,每个线程都应用自己的SqlSession
MyBatis配置文件结构
  • properties:mapper文件中的"变量",可在Mapper文件的其它地方引用
  • settings:MyBatis中极为重要的调整,会改变MyBatis的运行时行为,例如useGeneratedKeys允许JDBC自动生成主键
  • typeAliases:为Java类型的全限定名设置别名,目的是更简短
  • typeHandlers:类型处理器。无论是在预处理语句(PreparedStatement)中设置一个参数,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换为Java类型
  • objectFactory:对象工厂,用于创建结果对象的新实例
  • Environments:配置数据源
  • Mapper映射器:指明Mapper文件在哪,进而找到其中的SQL语句
深入Mapper XML映射文件

这是MyBatis的魔力所在。跟具有相同功能的JDBC代码对比,将省掉约95%的代码。常用元素如下:

  • select/insert/update/delete:分别映射CRUD语句
  • sql:可被其它语句引用的可重用语句块
  • cache:给定命名空间的缓存配置
  • cache-ref:其它命名空间缓存配置的引用
  • resultMap:最复杂和最强大的元素,描述如何从数据库结果集中加载对象
select

CRUD的使用类似,这里用select举例说明

<select id="selectUser" parameterType="int" resultType="hashmap">
    SELECT * FROM TB_USER WHERE ID =  #{id}
</select>
  • id:命名空间中的唯一标识符,可以被用来引用这条语句
  • parameterType:语句接收的参数类型。可省略,MyBatis可以通过TypeHandler推出来
  • resultType:语句返回hash类型的对象,键是列名,值是结果行中的对应值,如{name=wxs, sex=男, grade=99}
  • #{id}:占位符,类似于sql语句中的"?"
    注意:若parameterType类型是自定义类型如User,则#{id/name}中的字段将会去查询User中的id/name

以上配置文件,在执行时会生成如下JDBC代码

String selectUser = "SELECT * FROM TB_USER WHERE ID=?";
Prepared ps = conn.preparedStatement(selectUser);
ps.setInt(1, id);
ResultMap

若使用resultType="map"的映射语句查询所有用户数据:

<select id="selectUser" resultType="map">
        SELECT * FROM TB_USER
</select>

则结果看起来是这样的,查询语句的每一行都被封装成一个Map:
{name=wxs, sex=男, grade=99}
{name=bdm, sex=女, grade=98}
...
更好的方式是将resultType赋值为对应的User类型,此时MyBatis会将查询到的数据的列和需要返回的对象(User)的属性逐一进行匹配赋值,但如果两者有不一致的现象,则就不会进行自动赋值了。这时可以使用resultMap来处理。

首先,我们构造一个上述的不一致现象——POJO的属性和数据库表中的属性名字不同

//POJO类
public class User
{
    private Integer id;
    private String name;
    private String sex;
    private Integer age;
    //省略get/set方法
}
//创建数据库
CREATE TABLE TB_USER2(
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(18),
    user_sex VARCHAR(18),
    user_age INT
)

接着,我们在Mapper文件中利用resultMap完成这种“不一致”的映射

<resultMap id="userResultMap" type="org.test.domain.User">
        <id property="id" column="user_id" />
        <result property="name" column="user_name">
        <result property="sex" column="user_sex">
        <result property="age" column="user_age">
</resultMap>

可以看出,上述中间四条语句说明要将column映射到property,即数据库中的user_id/user_name/user_sex/user_age列映射到POJO对象的id/name/sex/age属性

在实际项目开发中,还有更加复杂的情况,例如执行的是一个多表查询语句,而返回的对象关联到另一个对象,此时简单的映射已经无法解决问题,必须使用<resultMap.../>元素来完成“定制的”映射

举一个关联查询的例子
首先,创建两个表,并通过外键关联。

CREATE TABLE TB_CLAZZ(
    id INT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(18)
)
CREATE TABLE TB_STUDENT(
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    user_name VARCHAR(18),
    user_sex VARCHAR(18),
    user_age INT,
    clazz_id INT,
    FOREIGN KEY (clazz_id) REFERENCES TB_CLAZZ(id)
)

接着,创建对应的POJO(省略get/set)

public class Clazz
{
    private Integer id;
    private String code;
}
public class Student
{
    private Integer id;
    private String name;
    private String sex;
    prvate Integer age;
    //关联的Clazz对象
    private Clazz clazz;

}

接着,编辑对应的Mapper文件

<!-- 映射学生对象的resultMap -->
<resultMap id="studentResultMap" type="org.test.domain.Student">
    <id property="id" column="id"/>
    <result property="name", column="name"/>
    <result property="sex", column="sex"/>
    <result property="age", column="age"/>
    <!-- 关联映射:根据clazz_id查询到Clazz对象并完成映射 -->
    <association property="clazz" column="clazz_id" 
      javaType="org.test.domain.Clazz"
      select="selectClazzWithId"/>
</resultMap>

<!-- 根据班级Id查询班级 -->
<select id="selectClazzWithId" resultType="org.test.domain.Clazz">
    SELECT * FROM TB_CLAZZ where id = #{id}
</select>

<!-- 查询所有学生信息,注意返回类型是resultMap -->
<select id="selectStudent" resultMap="studentResultMap">
    SELECT * FROM TB_STUDENT
<select>

<association>各元素说明
property:Student的属性名
column:数据表的列名
javaType:概属性对应的名称
select:依据clazz_id执行查询语句,将结果封装到property对应的属性中

最后,在程序中执行关联查询

List<Student> list = session.selectList("org.test.mapper.UserMapper.selectStudent")

查询结果类似:
Student [id = 1, name=wxs, sex=男, age=28, clazz=Clazz[id=1, code=j1601]]
Student [id = 2, name=bdm, sex=女, age=28, clazz=Clazz[id=1, code=j1601]]

类似的,如果在查询班级的同时也要查询出该班的所有学生,此时会用到<collection>标签,不详记录啦

MyBatis关联映射

一对一

一对一关系映射:比如一个人只能有一个身份证,一个身份证只能给一个人用。推荐使用唯一(unique)主外键关联:

CREATE TABLE tb_card(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18)
);
CREATE TABLE tb_person(
id INT PRIMARY KEY AUTO_INCREMENT,
...,
cart_id INT UNIQUE,
FOREIGN KEY (card_id) REFERENCES tb_card(id)
);

mapper文件的编辑和POJO都类似上边的Student和Clazz,同样利用<association>完成关联查询,不写出来啦

  • mapper接口对象
    注意,之前测试时均使用SqlSession对象调用insert/update/delete/select测试,但官方建议通过mapper接口的代理对象访问mybatis,该对象关联了SqlSession对象,可通过该对象直接调用方法操作数据库。
    下面定义一个mapper接口对象,需要注意的是,mapper接口对象的类名和之前的XML文件中的mapper的namespace一致,而方法名和参数也必须和XML文件中的<select/update.../>元素的id属性和parameterType属性一致
<!-- mapper文件 -->
<mapper namespace="org.test.mapper.PersonMapper">
    <select id="selectPersonById" parameterType="int" .../>
</mapper>
//mapper接口对象
public interface PersonMapper{
    Person selectPersonById(Integer id);
}
//测试
//读取MyBatis的配置文件,其中包括连接哪种数据库,配置数据源,mappers文件路径等
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

    //获得Session实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = SqlSessionFactory.openSession();

    //获得mapper接口的代理对象
    PersonMapper personMapper = session.getMapper(PersonMapper.class);
    Person p = personMapper.selectPersonById(1);
    session.commit();
    session.close();
多对一

上面讲解resultMap时的clazz和student就是,就是多对一关系:一个学生只属于一个班级,一个班级可以有多个学生。多对一关系推荐使用主外键关联(与一对一的区别是没有unique)。

具体例子参考讲解resultMap时的clazz和student即可,通过<association>和<collection>完成映射。此外,了解一个概念,在执行关联查询时可配置立即加载或懒加载,即查询班级信息时,立即查询出相关学生信息或者在需要操作相关学生信息时,再去执行对应的关联查询。书中例子,一般情况下,一对多的关系时都使用懒加载

多对多

订单和商品就是多对多的关系:一个订单中可以购买多种商品,一种商品也可以属于多个不同的订单。多对多关系建议使用一个中间表来维护,中间表的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。例子树上有

小结
  • A和B是一/多对一关系时,则类A的数据结构中包含对象B,对应Mapper文件中使用<association>
  • A和B是一/多对多关系时,则类A的数据结构汇中包含List<B>,对应Mapper文件中使用<collection>

动态SQL

解决了根据不同条件拼接SQL语句的问题
MyBatis采用ONGL的表达式完成动态SQL
常用元素有if、choose(when、otherwise)、where、set、foreach、bind,接下来介绍如何在Mapper文件中使用。

if
<mapper namespace="...EmployeeMapper">
    <select id="selectEmployee" resultType=...>
        SELECT * FROM tb_employee WHERE state = 'ACTIVE'
        <if test="id != null">
            and id = #{id}
        </if>
</mapper>

说明:
1、<if>条件表明如果传入了id则还需通过id查询
2、#{id}获取参数有两种方式:从JavaBean中获取或者从HashMap中获取,即对应的mapper接口对象的selectEmployee方法的入参应为javaBean或HashMap,例如

public interface EmployeeMapper{
    List<Employee> selectEmployee(HashMap<String, Object> params)
}
choose(when、otherwise)

choose...when...otherwise处理逻辑类似switch...case...default

<select ...>
    SELECT * FROM tb_employee WHERE state = 'ACTIVE'
    <choose>
        <when test = "id != null">
            and id = #{id}
        </when>
        <when test="loginname != null and password != null">
            and loginname = #{loginname} and password = #{password}
        </when>
        <otherwise>
            and sex='男'
        </otherwise>
    </choose>
</select>

上述<select>表明:

  • 当id != null时,则根据id查询;
  • 否则,当loginame != null and password != null时,根据loginname和password查询;
  • 否则,根据sex='男'查询

任何一个条件满足后,就不再匹配后续的<when>或<otherwise>了

where

先看以下如果没有where,则下面的<select>会有什么问题

<select...>
    SELECT * FROM tb_employee WHERE
    <if test="state != null ">
        state = #{state}
    </if>
    <if test=" id != null ">
        and id= #{id}
    <if/>
</select>

如果执行上述select时,如果没有传入任何参数,则sql语句如下

SELECT * FROM tb_employee WHERE

如果执行上述select时,如果只传入id,则sql语句如下

SELECT * FROM tb_employee WHERE and id = #{id}

使用<where>后,它知道如何纠正上述错误,是否需要where字句,是否需要去掉and/or

SELECT * FROM tb_employee
<where>
    <if test=" state != null ">
        state = #{state}
    </if>
    <if test=" id != null ">
        and id= #{id}
    <if/>
</where>
set

用于更新语句:动态取舍需要更新的列

<update ...>
    UPDATE tb_employee 
        <set>
            <if test="loginname != null">loginname=#{loginname},</if>
            <if test="password != null">password=#{password}</if>
        </set>
</update>

说明

  • <set>会自动处理好逗号
  • 对应的Mapper接口对象的对应更新方法,入参为JavaBean(通常时之前查询出来的JavaBean),如
void updateEmployee(Employee employee)
foreach

对一个集合遍历,通常用于构建IN条件语句时

<select>
    SELECT * FROM tb_employee WHERE ID in
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
    #{item}
    </foreach>
</select>

对应mapper接口对象的对应方法的入参应该为一个id的List

bind

从OGNL表达式中创建一个变量,将其绑定到上下文

<select id="selectEmployeeLikeName" ...>
    <bind name="pattern" value="'%' + _parameter.getName() + '%'"" />
    SELECT * FROM tb_employee WHERE loginname LIKE #{pattern}
</select>
...
//查询loginname包含"o"的所有员工信息
Employee employee = new Employee();
employee.setName("o");
List<Employee> list = employeeMapper.selectEmployeeLikeName(employee)

笔记总结于《Spring MVC+MyBatis企业应用实战》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352