Mybatis笔记

Mybatis

MyBatis中文网

什么是 Mybatis?

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

Mybaits 的优点

  • 基 于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响, SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持 编写动态 SQL 语句,并可重用。

  • 与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动 开关连接;

  • 很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支 持的数据库 MyBatis 都支持)。

  • 能够与 Spring 很好的集成;

  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持 对象关系组件维护。

MyBatis的缺点

  • SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的 功底有一定要求。

  • SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

MyBatis 与 Hibernate

  1. 开发速度的对比
    就开发速度而言,Hibernate 的真正掌握要比 Mybatis 来得难些。Mybatis 框架相对简单很容易上手,但也相对简陋些。个人觉得要用好 Mybatis 还是首先要先理解好 Hibernate。比起两者的开发速度,不仅仅要考虑到两者的特性及性能,更要根据项目需求去考虑究竟哪一个更适合项目开发,比如:一个项目中用到的复杂查询基本没有,就是简单的增删改查,这样选择 hibernate 效率就很快了,因为基本的 sql 语句已经被封装好了,根本不需要你去写 sql 语句,这就节省了大量的时间,但是对于一个大型项目,复杂语句较多,这样再去选择 hibernate 就不是一个太好的选择,选择 mybatis 就会加快许多,而且语句的管理也比较方便。

  2. 开发工作量的对比
    Hibernate 和 MyBatis 都有相应的代码生成工具。可以生成简单基本的 DAO 层方法。针对高级查询,Mybatis 需要手动编写 SQL 语句,以及 ResultMap。而 Hibernate 有良好的映射机制,开发者无需关心 SQL 的生成与结果映射,可以更专注于业务流程。

  3. sql 优化方面
    Hibernate 的查询会将表中的所有字段查询出来,这一点会有性能消耗。Hibernate 也可以自己写 SQL 来指定需要查询的字段,但这样就破坏了 Hibernate 开发的简洁性。而 Mybatis 的SQL 是手动编写的,所以可以按需求指定查询的字段。Hibernate HQL 语句的调优需要将 SQL 打印出来,而 Hibernate 的 SQL 被很多人嫌弃因为太丑了。MyBatis 的 SQL 是自己手动写的所以调整方便。但 Hibernate 具有自己的日志统计。Mybatis 本身不带日志统计,使用 Log4j 进行日志记录。

  4. 对象管理的对比
    Hibernate 是完整的对象/关系映射解决方案,它提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节。也就是说,相对于常见的 JDBC/SQL持久层方案中需要管理 SQL 语句,Hibernate 采用了更自然的面向对象的视角来持久化Java 应用中的数据。换句话说,使用 Hibernate 的开发者应该总是关注对象的状态(state),不必考虑 SQL 语句的执行。这部分细节已经由 Hibernate 掌管妥当,只有开发者在进行系统性能调优的时候才需要进行了解。而 MyBatis 在这一块没有文档说明,用户需要对对象自己进行详细的管理。

  5. 缓存机制
    Hibernate 一级缓存是 Session 缓存,利用好一级缓存就需要对 Session 的生命周期进行管理好。建议在一个 Action 操作中使用一个 Session。一级缓存需要对 Session 进行严格管理。Hibernate 二级缓存是 SessionFactory 级的缓存。 SessionFactory 的缓存分为内置缓存和外置缓存。内置缓存中存放的是 SessionFactory 对象的一些集合属性包含的数据(映射元素据及预定 SQL 语句等),对于应用程序来说,它是只读的。外置缓存中存放的是数据库数据的副本, 其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备。二级缓存称为进程级缓存或 SessionFactory 级缓存,它可以被所有 session 共享,它的生命周期伴随着 SessionFactory 的生命周期存在和消亡。

导入依赖

<!--导入依赖-->
<dependencies>
    <!--mysqlq驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.12</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.4</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

mybatis的核心配置文件

<?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>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

mybatis工具类

​ 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

 public class MybatisUtils {
	static SqlSessionFactory sqlSessionFactory = null;
    static {
        try {
            //使用Mybatis第一步 :获取sqlSessionFactory对象,通过new SqlSessionFactoryBuilder()创建
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例.
    // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
	}
}

Mapper映射语句

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>


<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

日志工厂

如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手!

曾经:sout、debug

现在:日志工厂

  • SLF4J

  • LOG4J 【掌握】

    • 先导入log4j的包
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
    • log4j.properties
    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
    
    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/rzp.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
    
    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sq1.PreparedStatement=DEBUG
    
  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 【掌握】

  • NO_LOGGING

在MyBatis中具体使用哪一个日志实现,在设置中设定

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

作用域(Scope)和生命周期

  • SqlSessionFactoryBuilder:

    • 一旦创建了SqlSessionFactory,就不再需要它了
    • 局部变量
  • SqlSessionFactory:

    • 说白了就可以想象为:数据库连接池

    • SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建一个实例。

    • 因此SqlSessionFactory的最佳作用域是应用作用域(ApplocationContext)。

    • 最简单的就是使用单例模式静态单例模式

  • SqlSession

    • 连接到连接池的一个请求

    • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

    • 用完之后需要赶紧关闭,否则资源被占用!

MyBatis详细执行流程

执行流程
图片引用 https://zhuanlan.zhihu.com/p/150008843

  • 通过Resource去加载全局配置文件
  • 通过build中XmlConfigBuilder类去解析文件流以及环境和属性
  • 将配置信息存放到Configuration中
  • 然后实例化SqlSessionFactory实现类DefaultSqlSessionFactory此时进入底层实现流程
  • 创建执行器Excutor,去执行mapper
  • 创建SqlSession接口实现类DefaultSqlSession
  • 判断是否执行成功,若没有则回滚到事务提交器
  • 事务提交
  • 关闭

分页

  • RowBounds分页
  • limit分页
  • pagehelper分页【https://github.com/pagehelper/Mybatis-PageHelper】

多对一,一对多处理

回顾Mysql多对一查询方式:

  • 子查询 (按照查询嵌套)
<select id="getStudent" resultMap="StudentTeacher">
  select * from student;
</select>

<resultMap id="StudentTeacher" type="com.hou.pojo.Student">
    <result property="id" column="id"></result>
    <result property="name" column="name"></result>
    <!--对象使用assiociation-->
    <!--集合用collection-->
    <association property="teacher" column="tid" javaType="com.hou.pojo.Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="com.hou.pojo.Teacher">
  select * from teacher where id = #{id};
</select>
  • 联表查询 (按照结果嵌套)
<select id="getStudent2" resultMap="StudentTeacher2">
  select s.id sid,s.name sname,t.name tname
  	from student s,teacher t 
  	where s.tid=t.id;
</select>

<resultMap id="StudentTeacher2" type="com.hou.pojo.Student">
    <result property="id" column="sid"></result>
    <result property="name" column="sname"></result>
    <association property="teacher" javaType="com.hou.pojo.Teacher">
        <result property="name" column="tname"></result>
    </association>
</resultMap>
  • 关联 - association 【多对一】
  • 集合 - collection 【一对多】

动态SQL

什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句

所谓的动态SQL,本质上还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码

  • if
<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>
  • choose (when, otherwise)

    有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<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>
  • trim (where, set)
  • foreach
<!--ids是传的,#{id}是遍历的-->
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
      <foreach collection="ids" item="id" open="and ("  close=")" separator="or">
          id=#{id}
      </foreach>
    </where>
</select>

缓存

  • 什么是缓存[Cache]?

    • 存在内存中的临时数据
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
  • 为什么使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率
    • 什么样的数据可以使用缓存?经常查询并且不经常改变的数据 【可以使用缓存】
  • MyBatis缓存

    • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大的提高查询效率。
    • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
      • 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
      • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
      • 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。
  • 一级缓存

    • 一级缓存也叫本地缓存:SqlSession
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 以后如果需要获取相同的 数据,直接从缓存中拿,没必要再去查询数据库
  • 二级缓存

    • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
    • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
    • 工作机制
      • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
      • 如果会话关闭了,这个会员对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
      • 新的会话查询信息,就可以从二级缓存中获取内容
      • 不同的mapper查询出的数据会放在自己对应的缓存(map)中
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    
  • 自定义缓存-ehcache

    • 导包
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.2.1</version>
    </dependency>
    
    • 在mapper中指定使用我们的ehcache缓存实现
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
  • 小结

    • 只要开启了二级缓存,在同一个Mapper下就有效

    • 所有的数据都会放在一级缓存中

    • 只有当前会话提交,或者关闭的时候,才会提交到二级缓存中

缓存图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值