MyBatis,你真的了解了吗

MyBatis简介

MyBatis是什么?

借用官网的话来说:MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
简单的来说,Mybatis是半自动ORM映射轻量级框架。

ORM是什么?

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

为什么说Mybatis是半自动ORM映射框架?半自动体现在哪里?

Mybatis的半自动化体现在以下两大点:

  1. 手动编写SQL语句
    Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,但其支持动态 SQL、处理列表、动态生成表名、支持存储过程,所以,称之为半自动ORM映射工具。
  2. 参数自动动态映射
    Mybatis参数自动映射本质上是mybatis.xml解析配置信息(使用连接池管理数据库连接,提高性能),将SQL语句配置在XXXmapper.xml(实现数据库操作和java代码的动静分离),以及通过java注解和反射的原理,去将POJO中的java对象动态映射成接口XXXmapper方法中的与数据库相对应的SQL语句,同时也可以将数据库SQL语句返回的结果集反向映射成java对象 。这样优化了传统JDBC中SQL语句定义、参数设置、结果集处理的硬编码问题。

MyBatis的原理和常用配置

MyBatis的解析和运行原理

如果用一张图来解释一下MyBatis的解析和运行原理:
在这里插入图片描述

  1. 读取MyBatis配置文件:mybatis-config.xml为MyBatis的全局配置文件,配置了 Properties(属性)、typeAliases(类型别名)、environments(环境信息集合)、environment(单个环境信息)和mappers(映射器)的基本信息,主要是MyBatis运行环境数据库连接信息的配置。
  2. 加载映射文件XXXmapper.xml:映射文件即 SQL 映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应XXXmapper的接口方法。
  3. 构造会话工厂SqlSessionFactory:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
  4. 创建会话对象SqlSession :由会话工厂创建SqlSession对象,该对象中包含了执行 SQL 语句的所有方法。
  5. Executor执行器:MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
  6. MappedStatement对象:在 Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。最常用的接口就是XXXmapper接口。
  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。同时最新的MyBatis提供了参数注解Param、方法注解insert、update、select、delete来动态映射SQL语句,输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

以上8点,我们把Mybatis的功能架构分为四层:

  1. 引导层(1和2): 基于XML配置方式和基于java API方式的配置。
  2. 框架支撑层(3和4): MyBatis核心对象的创建,负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
  3. API接口层Mapper接口(5和6):提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  4. 数据处理层(7和8):负责具体的SQL参数映射、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

Mybatis的一级、二级缓存

  1. 一级缓存: mybatis提供查询缓存,如果缓存中有数据,就不用从数据库中获取,用于减轻数据压力,提高系统性能。 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
    下图:第一次和第二次查询id为1 的用户读取的是一级缓存:

  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置< cache/> ;
    二级缓存是Mapper级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存。在这里插入图片描述

  3. 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

MyBatis的配置(IDEA)

  1. 依赖jar包:
    Mysql driver
    Mybatis
  2. 核心配置
    a. 数据库访问配置文件datasource.properties
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://192.168.40.103:3306/school?useUnicode=true&characterEncoding=utf-8&useSSL=true
mysql.username=root
mysql.password=kb08

b. MyBatis的全局配置文件mybatis.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>

</configuration>

常用配置信息对应如下:
在这里插入图片描述
c. 映射文件XXXmapper.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>
</mapper>

常用配置信息对应如下:
在这里插入图片描述
从MyBatis的配置可以看出MyBatis的工作步骤如下:

  1. 加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个pojo中java对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
  2. SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的id和传入对象(可以是Map、Java对象或者基本数据类型),Mybatis会根据SQL的id找到对应的Mapper接口的方法,然后根据传入参数对象对Mapper进行解析,解析后可以得到最终要执行的SQL语句和参数。
  3. SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
  4. 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、Java对象或者基本数据类型,并将最终结果返回。

MyBatis 核心对象

  1. 配置文件解析
    InputStream config = Resources.getResourceAsStream(String path);
  2. SQL会话工厂
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
  3. SQL会话 其中open(true:自动提交/false:不自动提交(启动事务))
    SqlSession session = factory.openSession(true);
  4. 获取Mapper接口对象
    XxxMapper mapper = session.getMapper(XxxMapper.class);
  5. 调用接口对象方法,处理返回结果
  6. 关闭SQL会话
    session.close();

Mapper配置文件常用标签

数据库增删改查标签

  1. 查询操作
<select id = “Mapper接口中方法名称”  resultType=”自定义类型”/resultMap=”resultMap中id的方法”>
</select> 

例如:

<select id="findAll" resultType="VStuScore">
        select * from vstuscore limit 0,50
</select>
  1. 增删改操作
<insert id=”Mapper接口中方法名称”></insert>
<delete id=”Mapper接口中方法名称”></delete>
<update id=”Mapper接口中方法名称”></update>

例如:

 <!--新增单个值-->
<insert id="add">
    insert into scoreinfo(stuId,subId,score) value(#{stuId},#{subId},#{score})
</insert>

<!--新增array形式-->
<insert id="addAll">
    insert into scoreinfo(stuId,subId,score)
    <foreach collection="array" item="sco" open="values" separator=",">
        (#{sco.stuId},#{sco.subId},#{sco.score})
    </foreach>
</insert>

<!--新增list形式-->
<insert id="addAllList">
    insert into scoreinfo(stuId,subId,score)
    <foreach collection="list" item="sco" open="values" separator=",">
        (#{sco.stuId},#{sco.subId},#{sco.score})
    </foreach>
</insert>

<!--删除array形式-->
<delete id="removeAll">
    delete from scoreinfo
    <where>
        id in
        <foreach collection="array" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </where>
</delete>

<!--修改形式-->
<update id="modify">
    update scoreinfo
    <set>
        <if test="null!=subId">
            subId=#{subId},
        </if>
        <if test="null!=stuId">
            stuId=#{stuId},
        </if>
        <if test="null!=score">
            score=#{score},
        </if>
    </set>
    where
      id=#{id}
</update>
  1. 函数和存储过程的调用
<select id="Mapper接口中方法名称" parameterType="返回java对象类型或者map" resultType="自定义类型" statementType="CALLABLE">
  {
      call 函数或存储过程名(#{输出参数名称,mode=OUT,jdbcType=数据库输出参数类型},
      #{输入参数名称},
      ...
      )
    }
</select>

例如:

<!--map形式-->
<select id="findByPage" parameterType="map" resultType="VStudent" statementType="CALLABLE">
  {
      call proPageStu(#{total,mode=OUT,jdbcType=INTEGER},#{pageNo},#{pageSize})
    }
</select>

<!--java对象形式-->
<select id="findByPage2" parameterType="PageParam" resultType="VStudent" statementType="CALLABLE">
    {
      call proPageStu(#{total,mode=OUT,jdbcType=INTEGER},#{pageNo},#{pageSize})
    }
</select>

动态SQL标签

  1. < forEach collection=”array/list/map” item=”alias别名” open=”开始符号” close=”结束符号” Seperatro=”分隔符” index=“迭代位置下标”>…< /forEach>
<!--新增array形式-->
<insert id="addAll">
    insert into scoreinfo(stuId,subId,score)
    <foreach collection="array" item="sco" open="values" separator=",">
        (#{sco.stuId},#{sco.subId},#{sco.score})
    </foreach>
</insert>

<!--新增list形式-->
<insert id="addAllList">
    insert into scoreinfo(stuId,subId,score)
    <foreach collection="list" item="sco" open="values" separator=",">
        (#{sco.stuId},#{sco.subId},#{sco.score})
    </foreach>
</insert>
  1. < where>< /where> 去掉第一个and,以where代替
  2. < set>< /set> 去掉最后一个,
  3. < if test=”condtion”>…< /if> 条件判断
  4. #{}和${}:
    #{}是占位符,预编译处理;
    ${}是拼接符,字符串替换,没有预编译处理。(一般不用,有注入SQL风险)
    #{} 对应的变量自动加上单引号 ‘’;变量替换后,${} 对应的变量不会加上单引号 ‘’。
  5. 模糊查询like:使用字符串concat()方法拼接’%'和#{}

MyBatis注解

  1. 方法注解Mapper:
    简单SQL命令
    i. @Select(“SQL COMMAND”)
    ii. @Insert(“SQL COMMAND”)
    iii. @Update(“SQL COMMAND”)
    iv. @Delete(“SQL COMMAND”)
    例如:
    @Select("select totalStu(#{pageSize})")
    int findTotal(int pageSize);
  1. 参数注解Mapper:
    #{}里面的名称对应的是注解@Param括号里面修饰的名称。这种方法在参数不多的情况还是比较直观的,推荐使用。好处是不用写实体类。
    例如:
 @Insert("insert into subjectinfo(proId,subName,classHours) values(#{proId},#{subName},#{classHours})")
    int addSubject(@Param("proId") int proId, @Param("subName") String subName,@Param("classHours") int classHours);

MyBatis常见问题

获取新增操作的主键值

  1. 对于支持主键自增的数据库(MySQL)
    设置keyProperty=“主键字段名” useGeneratedKeys="true"的两个参数,然后可以通过返回结果集的Java对象通过属性获取,map集合通过key去取值。
<!--新增单个值-->
<insert id="add" keyProperty="id" useGeneratedKeys="true">
     insert into scoreinfo(stuId,subId,score) value(#{stuId},#{subId},#{score})
 </insert>

java测试:

 @Test
    public void testInsert(){
        Score score = new Score(1, 1, 100);
        int add = mapper.add(score);
        Integer id = score.getId();
        System.out.println(id);
        System.out.println(add);
    }
  1. 支持所有数据库的获取自增主键
    可以使用<selectKey>标签来获取主键的值,这种方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="BEFORE">
</selectKey> 

resultMap解决字段、类型不匹配的问题

当数据库字段名称,类型和java实体类的属性名称、类型不匹配时的解决方法

<resultMap id="方法名称" type="自定义类型">
        <result property="Java对象属性" column="数据库字段名称" jdbcType="数据库字段类型" javaType="Java字段类型" />
</resultMap>

例如:

<resultMap id="rmCS" type="ClassStu">
    <result property="claName" column="className"/>
    <result property="stuNum" column="num" jdbcType="BIGINT" javaType="java.lang.Integer" />
</resultMap>

<select id="findCS" resultMap="rmCS">
    select
      C.className,count(1) num
    from
      studentinfo U
    inner join
      classinfo C
    on
      U.classId=C.id
    group by
      C.className
</select>

此外,我们也可以通过起别名的方式来解决字段名称和java对象的属性不相同的情况。

加载映射文件的方式有哪些?

方式一:< mapper resource=""/>
该方式是加载相对于类路径下的映射文件:

<mappers>
   <mapper resource="sqlmap/User.xml"/>
</mappers>

方式二:< mapper url=""/>

该方式使用全限定路径

<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />

方式三:< mapper class=""/>

该方式使用mapper接口的全限定类名

<mapper class="cn.itcast.lc.mapper.UserMapper"/>

此方式要求: Mapper接口Mapper映射文件名称相同且在同一个目录下。

方式四:< package name=""/> 最常用的方法

该方式是加载指定包下的所有映射文件

<package name="cn.lc.mybatis.mapper"/>

此方式要求:Mapper接口Mapper映射文件名称相同且在同一个目录下。

MyBatis的mapper接口与XXXmapper.xml的联系

一、使用MyBatis的mapper接口与XXXmapper.xml的对应关系

  1. Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
  2. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。
  3. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
  4. Mapper.xml文件中的namespace即是mapper接口的类路径。

二、使用MyBatis的mapper接口方法不能重载
这里所谓的不能重载,不是说mapper接口本身不支持重载,而是MyBatis框架在解析映射的时候只支持唯一的XXXmapper.xml的id值里面对应的方法名称,必须是唯一的才能映射出来,如果相同,不会报错,会匹配最后一个的mapper方法进行调用。

三、不同mapper接口方法名称可以重名
不同mapper接口方法名称可以重名不是绝对的。
xml映射文件本质上相当于Map<id,mapper_method>,通过map的key,也就是id,去调用mapper接口的mapper_method方法 。而mapper_method所对应的配置SQL语句本质上也是一个Map<id,resultType>,每条动态SQL语句的id对应mapper接口的方法,resultType对应的返回类型是java对象、map或者基本数据类型 。不同的Xml映射文件,如果配置了namespace,那么id可以重复,那么不同mapper接口方法名称就可以重名;如果没有配置namespace,那么id不能重复;毕竟namespace不是必须的,那么不同mapper接口方法名称不可以重名。

关联查询

关联查询,也就是多表查询,查询的结果集也不是一个表所对应的Java对象所能进行直接映射的。因此,我们在进行关联查询要进行合理的Java对象处理和扩展,保证查询出来的结果集都有所对应的Java属性和之对应。这样就能保证查询出来的结果正确无误。
在关联查询中我们常用的标签有:association和collection标签。association标签是一对一关联映射所需要的标签。collection标签是一对多所需要的标签。在Mybatis中,可以理解为多对一也是特殊的一对一(如同:多个员工对应一个部门;但是也可以理解为一个员工对应一个部门,只不过有多个员工而已;可以理解为:在一个Employee对象中有一个department属性;同时又多个Employee对象,每个对象中的department对应同一个部门);多对多是特殊的一对多。
在结果集映射中,我们用的结果集映射总共有两种,分别是:resultType和resultMap;那在关联映射的时候,我们该如何选择使用哪种结果映射方式呢?其实只需要理解两种映射的不同和映射原理。resultType映射时把查询出来的结果集和对应的Java属性进行一一对应。因此,在采用resultType映射,需要映射结果集的javaBean中的所有属性都是与查询结果集进行相互对应的(属性不能进行嵌套)。而使用resultMap结果集映射,则需要先声明resultMap,后使用。先声明resultMap就是制定查询出来的结果集中的列数和java对象中的哪些属性进行关联映射(属性可以嵌套)。

猜你喜欢

转载自blog.csdn.net/BigData_Hobert/article/details/107729839