Java Web|day5.MyBatis

MyBatis

定义

它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低

**ORM: **Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据

半自动: 用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。

优点

  1. 灵活性高,自由地对sql进行定制

  2. 但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。

  3. 还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。

缺点

当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。

占位符

#{}

  • #{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程
  • 预编译
    • 它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入
  • 注意
    • 如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意
    • 如果入参类型是pojo,比如是Student类,那么#{name}表示取入参对象Student中的name属性,#{age}表示取age属性

${}

  • ${},一般会用在模糊查询的情景,比如SELECT * FROM student WHERE name like '%${name}%';
  • 处理阶段在#{}之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,
  • 入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like ‘%zhangsan%’;
  • 模糊查询只能用${}
  • 对于简单类型(8种java原始类型再加一个String)的入参,${}中参数的名字必须是value

使用过程

原生流程

  1. 编写mapper.xml,书写SQL,并定义好SQL的输入参数,和输出参数
  2. 编写全局配置文件,配置数据源,以及要加载的mapper.xml文件
  3. 通过全局配置文件,创建SqlSessionFactory
  4. 每次进行CRUD时,通过SqlSessionFactory创建一个SqlSession
  5. 调用SqlSession上的selectOne,selectList,insert,delete,update等方法,传入mapper.xml中SQL标签的id,以及输入参数
  6. 提交sqlSession.commit()
  7. 关闭资源sqlSession.close()

基于Mapper代理

为了简化开发,mybatis提供了mapper接口代理的开发方式,不需要再编写dao类,只需要编写一个mapper接口,一个mapper的接口和一个mapper.xml相对应,只需要调用SqlSession对象上的getMapper(),传入mapper接口的class信息,即可获得一个mapper代理对象,直接调用mapper接口中的方法,即相当于调用mapper.xml中的各个SQL标签,此时就不需要指定SQL标签的id字符串了,mapper接口中的一个方法,就对应了mapper.xml中的一个SQL标签

注意
  • mapper接口的全限定名,要和mapper.xml的namespace属性一致
  • mapper接口中的方法名要和mapper.xml中的SQL标签的id一致
  • mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致
  • mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致
使用方法
  1. 全局配置文件
<!-- 普通加载xml --> 
<mappers>    
    <mapper resource="StudentMapper.xml"/> 
</mappers>
  1. mapper.java
    在这里插入图片描述

  2. mapper.xml
    在这里插入图片描述

  3. 测试调用

请添加图片描述

基于注解的开发

还是得有一个全局配置的xml文件,不过mapper.xml就可以省掉了

注意

当使用注解开发时,若需要传入多个参数,可以结合@Param注解

  • @Param标签会被mybatis处理并封装成一个Map对象,比如上面的示例中,实际传入的参数是一个Map对象,@Param标签帮忙向Map中设置了值,即它做了

  • Map<String,Object> map = new HashMap<>(); map.put("name", name); 
    map.put("major",major);
    

步骤

  1. 创建一个Mapper接口
<!-- 在mapper接口中使用注解 --> 
<mappers>    
    <mapper class="com.yogurt.mapper.PureStudentMapper"/> 
</mappers>
  1. 在全局配置文件中修改标签,直接指定加载这个类

三种加载Mapper的方式

  1. <!-- 普通加载xml -->
    <mappers>
        <mapper resource="StudentMapper.xml"/>
    </mappers>
    
  2. <!-- 在mapper接口中使用注解 -->
    <mappers>
        <mapper class="com.yogurt.mapper.PureStudentMapper"/>
    </mappers>
    
  3. <!-- 使用<package>标签 -->
    <mappers>
        <package name="com.yogurt.mapper"/>
    </mappers>
    
  1. 自动加载com.yogurt.mapper包下的所有mapper
  2. 这种方式需要将mapper接口文件和mapper.xml文件都放在com.yogurt.mapper包下,且接口文件和xml文件的文件名要一致。
  3. 若mapper接口采用注解方式,则不需要xml;若mapper接口没有采用注解方式,则mapper接口和xml文件的名称要相同,且在同一目录

注意

是因为,对于src/main/java 源码目录下的文件,maven打包时只会将该目录下的java文件打包,而其他类型的文件都不会被打包进去,去工程目录的target目录下看看maven构建后生成的文件

解决

需要在pom.xml中的 标签下 添加 标签,指定打包时要将xml文件打包进去

<build>
	<resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
</build>

运行流程

流程

  1. 利用Xpath语法解析全局配置文件
  2. 解析全局配置,
    • 是否开启二级缓存
    • 是否开启延迟加载
    • 是否使用插件(plugin)
    • 是否设置类型别名(typeAlias)
    • 数据库连接信息
  3. 解析mapper映射文件,将每个CRUD标签,封装为一个MappedStatement
  4. 所有的信息封装到Configuration对象(这是一个重量级对象)
  5. 将Configuration封装到SqlSessionFactory中
  6. 每次调用SqlSessionFactory开启一个SqlSession
  7. 每个SqlSession,会持有一个私有的Executor对象,以及共享的Configuration对象
  8. 每次操作,选取一个MappedStatement,由Executor执行。包括SQL语句组装,参数解析,返回结果解析等操作

图示

请添加图片描述

关键类

  1. Configuration

    封装了所有的属性

  2. MappedStatement

    一个CRUD标签,对应一个MappedStatement

  3. Executor体系

    mybatis核心执行器,通过Executor来执行数据库操作

  4. SqlSource体系

    封装CRUD标签的一个SqlSource,用于组装SQL语句

  5. SqlNode体系

    以树状形式,存储动态SQL标签,一个SqlSource拥有一个rootSqlNode,每个SqlNode有一个apply方法,在Executor执行时用于拼接SQL语句,SqlNode的设计用到的是设计模式中的组合模式。

  6. StatementHandler

    生成Statement,设置参数,执行查询

  7. ParameterHandler

    设置参数时,进行参数解析,类型转换等

  8. ResultSetHandler

    获取结果集,并进行结果封装

MyBatis缓存

定义

mybatis缓存,将数据库的查询结果,保存到内存(或者硬盘),以便下次执行相同查询时,不经过数据库,直接从内存中取出结果

作用

对于重复的查询,它能够提高响应速度,并减轻数据库的访问压力。适用于对响应时间要求高,而数据实时性要求不高的情况下。

分类

一级缓存

作用范围

  • SqlSession(默认)

持有者

  • BaseExecutor

默认开启

  • 其实是无法关闭的,但可以通过一些方法来使一级缓存失效

清除缓存

  • 在同一个SqlSession里执行 增 删 改 操作时(不必提交),会清除一级缓存
  • SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
  • 对mapper.xml中的某个CRUD标签设置属性flushCache=true (这样会导致该标签的一级缓存和二级缓存都失效)
  • 在全局配置文件中设置 ,这样会使一级缓存失效,二级缓存不受影响
二级缓存

作用范围

  • mapper级别(可以跨SqlSession),一个mapper.xml即对应一个二级缓存,每个二级缓存,以mapper文件的namespace作为唯一标识

持有者

  • MappedStatement

默认关闭

  • (其实全局配置文件中的cacheEnabled默认是true,这个是二级缓存总开关,默认是已经打开了的,然而必须要在mapper.xml中配置 标签,才能开启该mapper.xml的二级缓存)

开启效果

  • 映射语句文件中的所有SELECT 语句将会被缓存。
  • 映射语句文件中的所有时INSERT 、UPDATE 、DELETE 语句会刷新缓存。
  • 缓存会使用Least Rece ntly U sed ( LRU ,最近最少使用的)算法来收回。
  • 根据时间表(如no Flush Int erv al ,没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024 个引用。
  • 缓存会被视为read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

注意

  • 对于SELECT节点对应的MappedStatement,默认是开启二级缓存,对其他节点(INSERT/DELETE/UPDATE),默认是关闭(这是由MappedStatement里的useCache属性控制的)
  • 放入二级缓存的数据,默认要实现Serializable接口,因为二级缓存的存储介质除了内存,还可能存在硬盘,所以存储的对象需要可序列化。
    • readOnly=false:通过序列化,返回缓存对象的一份拷贝,速度上会慢一些,但是更加安全。用户取得数据后,执行修改,并不会影响到二级缓存中的对象
    • 这些对象可以随意修改,不会影响后续相同查询条件的结果。这会慢一些,但是安全,因此默认是false。
  • 当然,若把二级缓存的readOnly属性设为true,则对象数据可以不用实现Serializable接口
    • readOnly=true:返回缓存对象的引用。
    • 因此返回的这些缓存对象自己没事不要去修改。一旦修改,就会影响下一个相同查询条件的查询结果。这提供了很重要的性能优势。
    • readOnly=true意在告诉用户,从缓存中取出数据后,不要对数据进行修改,而不是保证缓存的只读性(cache中存的是对象引用,取出数据后若执行修改,则会改变真正的对象,另外的用户再从cache中取对象,则会发现对象已经被修改)
  • 执行一次查询后,需要进行提交,该次查询结果才会保存到二级缓存中

存在问题

  • 由于是每个namespace对应一个二级缓存(一个namespace就是一个mapper.xml)
  • 若mapperA.xml中全是对user表的操作,而在另一个mapperB.xml中也有少许对user表的操作,这2个mapper的二级缓存是互相独立的,然而mapperB若对user表执行了增删改,并提交,却不会刷新到mapperA的二级缓存,此时用mapperA去做查询,则可能取到脏数据。

应用场景

主键返回

在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢

两种方式

  1. 使用useGeneratedKeys和keyProperty属性
<insert id="insert" parameterType="com.yogurt.po.Student" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
</insert>
  1. 使用子标签
<insert id="insert" parameterType="com.yogurt.po.Student">
        INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
        <selectKey keyProperty="id" order="AFTER" resultType="int" >
            SELECT LAST_INSERT_ID();
        </selectKey>
</insert>

标签其实就是一条SQL,这条SQL的执行,可以放在主SQL执行之前或之后,并且会将其执行得到的结果封装到入参的Java对象的指定属性上。

注意子标签只能用在和标签中。上面的LAST_INSERT_ID()实际上是MySQL提供的一个函数,可以用来获取最近插入或更新的记录的主键id。

批量查询

主要是动态SQL标签的使用,注意如果parameterType是List的话,则在标签体内引用这个List,只能用变量名list,如果parameterType是数组,则只能用变量名array

mapper定义

<select id="batchFind" resultType="student" parameterType="java.util.List">
        SELECT * FROM student
        <where>
            <if test="list != null and list.size() > 0">
                AND id in
                <foreach collection="list" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
        </where>
</select>

传参

List<Student> students = mapper.batchFind(Arrays.asList(1, 2, 3, 7, 9));

动态sql

可以根据具体的参数条件,来对SQL语句进行动态拼接。

注意

由于不确定查询参数是否存在,许多人会使用类似于where 1 = 1 来作为前缀,然后后面用AND 拼接要查询的参数,这样,就算要查询的参数为空,也能够正确执行查询,如果不加1 = 1,则如果查询参数为空,SQL语句就会变成SELECT * FROM student where ,SQL不合法。

  1. if
<select id="find" resultType="student" parameterType="student">
        SELECT * FROM student WHERE age >= 18
        <if test="name != null and name != ''">
            AND name like '%${name}%'
        </if>
</select>
  1. choose
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select> 
  1. where

    标签只会在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加WHERE,并且如果WHERE之后是以AND或OR开头,会自动将其删掉

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>
  1. trim

    标签可以用标签代替

<trim prefix="WHERE" prefixOverrides="AND | OR">
   ...
</trim>
  1. foreach

    用来做迭代拼接的,通常会与SQL语句中的IN查询条件结合使用,注意,到parameterType为List(链表)或者Array(数组),后面在引用时,参数名必须为list或者array。如在foreach标签中,collection属性则为需要迭代的集合,由于入参是个List,所以参数名必须为list

<select id="batchFind" resultType="student" parameterType="list">
        SELECT * FROM student WHERE id in
        <foreach collection="list" item="item" open="(" separator="," close=")">
          #{item}
        </foreach>
</select>
  1. sql

    可将重复的SQL片段提取出来,然后在需要的地方,使用标签进行引用

<select id="findUser" parameterType="user" resultType="user">
	SELECT * FROM user
	<include refid="whereClause"/>
</select>

<sql id="whereClause">
     <where>
         <if test="user != null">
         	AND username like '%${user.name}%'
         </if>
     </where>
</sql>
  1. bind

    mybatis的动态SQL都是用OGNL表达式进行解析的,如果需要创建OGNL表达式以外的变量,可以用bind标签

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

关联查询

使用 标签以及和 子标签,进行关联查询,

延迟加载

定义

在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息

优点

这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

用法

在mybatis中,resultMap标签 的association标签和collection标签具有延迟加载的功能。

原理

开启前

  • 第一种方法:我们直接关联查询出所有订单和用户的信息
  • select * from orders o ,user u where o.user_id = u.id;

开启后

  • 第二种方法:分步查询,首先查询出所有的订单信息,然后如果需要用户的信息,我们在根据查询的订单信息去关联用户信息
  • select * from orders;
  • select * from user where id=user_id

逆向工程

mybatis官方提供了mapper自动生成工具mybatis-generator-core来针对单表,生成PO类,以及Mapper接口和mapper.xml映射文件。针对单表,可以不需要再手动编写xml配置文件和mapper接口文件了,非常方便。美中不足的是它不支持生成关联查询。一般做关联查询,就自己单独写SQL就好了。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值