什么是 MyBatis ?

Apahce的一个开源项目

一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射

免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作

通过简单的 XML 或注解来配置和映射 Java对象 到 数据库中的记录

官方地址:https://mybatis.org/mybatis-3/

依赖地址 https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.4

核心流程: https://mybatis.org/mybatis-3/zh/getting-started.html

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心

SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得

SqlSessionFactoryBuilder 可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例

工厂设计模式里面 需要获取SqlSession ,里面提供了在数据库执行 SQL 命令所需的所有方法

流程图

 

 

新建项目添加依赖

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>

        <!-- 使用JDBC链接mysql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

</dependencies>

创建目录,配置文件

配置mybatis-config.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/xdclass?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="xdclass.net"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/VideoMapper.xml"/>
    </mappers>
</configuration>

配置VideoMapper.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 namespace="net.xdclass.online_class.dao.VideoMapper">


</mapper>

video对象以xml形式读取数据库

public static void main(String [] args) throws IOException {

       String resouce = "config/mybatis-config.xml";

       //读取配置文件
       InputStream inputStream =  Resources.getResourceAsStream(resouce);

       //构建Session工厂
       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

       //获取Session
       try(SqlSession sqlSession = sqlSessionFactory.openSession()){

           VideoMapper videoMapper = sqlSession.getMapper(VideoMapper.class);

           Video video = videoMapper.selectById(44);

           //System.out.println(video.toString());


           List<Video> videoList =  videoMapper.selectList();

           System.out.println(videoList.toString());
       }

   }

通过注解读取(如果sql简单,没有过多的表关联,则用注解相对简单)

/**
   * 查询全部视频列表
   * @return
   */
  @Select("select * from video")
  List<Video> selectList();

 

调试之控制台打印Sql

内置的日志工厂提供日志功能, 使用log4j配置打印sql,添加依赖

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
</dependency>

在应用的classpath中创建名称为log4j.properties的文件

log4j.rootLogger=ERROR, stdout
log4j.logger.net.xdclass=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

运行

日常开发可使用debug模式,trace会打印更多信息

精确到某个mapper进行打印

使用模糊查询

<select id="selectByPointAndTitleLike" resultType="net.xdclass.online_class.domain.Video">

select * from video where point=#{point} and title like concat('%', #{title},'%')

</select>

下划线自动映射驼峰

<!--下划线自动映射驼峰字段-->
   <settings>
       <setting name="mapUnderscoreToCamelCase" value="true"/>
   </settings>

取参数值,具体某个字段的类型,从java类型映射到数据库类型

例子 #{title, jdbcType=VARCHAR}

多数情况不加是正常使用,但是如果出现报错:无效的列类型,则是缺少jdbcType;

只有当字段可为NULL时才需要jdbcType属性

常见的数据库类型和java列席对比

JDBC Type           Java Type 

CHAR                String 
VARCHAR             String 
LONGVARCHAR         String 
NUMERIC             java.math.BigDecimal 
DECIMAL             java.math.BigDecimal 
BIT                 boolean 
BOOLEAN             boolean 
TINYINT             byte 
SMALLINT            short 
INTEGER             INTEGER 
INTEGER       int
BIGINT              long 
REAL                float 
FLOAT               double 
DOUBLE              double 
BINARY              byte[] 
VARBINARY           byte[] 
LONGVARBINARY       byte[] 
DATE                java.sql.Date 
TIME                java.sql.Time 
TIMESTAMP           java.sql.Timestamp 
CLOB                Clob 
BLOB                Blob 
ARRAY               Array 
DISTINCT            mapping of underlying type 
STRUCT              Struct 
REF                 Ref 
DATALINK            java.net.URL

Mybatis插入语法的使用和如何获得自增主键

新增一条视频记录

 <insert id="add" parameterType="net.xdclass.online_class.domain.Video">

        INSERT INTO `video` ( `title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
        VALUES
      (#{title,jdbcType=VARCHAR},#{summary,jdbcType=VARCHAR},#{coverImg,jdbcType=VARCHAR},#{price,jdbcType=INTEGER},
       #{createTime,jdbcType=TIMESTAMP},#{point,jdbcType=DOUBLE});

</insert>

获得插入自增主键

<insert id="add" parameterType="net.xdclass.online_class.domain.Video" useGeneratedKeys="true" keyProperty="id" keyColumn="id" >

新增多条视频记录

<insert id="addBach" parameterType="net.xdclass.online_class.domain.Video">

INSERT INTO `video`(`title`, `summary`, `cover_img`, `price`,`point`, `create_time`)
 VALUES
 <foreach collection="list" item="video" separator=",">
(#{video.title,jdbcType=VARCHAR},#{video.summary,jdbcType=VARCHAR},#{video.coverImg,jdbcType=VARCHAR},#{video.price,jdbcType=INTEGER},
#{video.point,jdbcType=DOUBLE},#{video.createTime,jdbcType=TIMESTAMP})
 </foreach>
</insert>
动态选择更新
<update id="updateVideoSelective" parameterType="net.xdclass.online_class.domain.Video">
update video
<trim prefix="set" suffixOverrides=",">

    <if test="title != null">title = #{title,jdbcType=VARCHAR},</if>

    <if test="summary != null">summary = #{summary,jdbcType=VARCHAR},</if>

    <if test="coverImg != null">cover_img = #{coverImg,jdbcType=VARCHAR},</if>

    <if test="price != 0">price = #{price,jdbcType=INTEGER},</if>
    <!-- 特别注意: 一定要看pojo类里面的是基本数据类型,还是包装数据类型-->
    <if test="point != null">point = #{point,jdbcType=DOUBLE},</if>

    <if test="createTime != null">create_time = #{createTime,jdbcType=TIMESTAMP}</if>
</trim>
    where
    id = #{id}
</update>

delete删除语法

删除某个时间段之后 且金额大于 10元的数据

<delete id="deleteByCreateTimeAndPrice" parameterType="java.util.Map">
    delete from video where create_time <![CDATA[>]]> #{createTime} and price <![CDATA[<]]> #{price}
</delete>

 

由于MyBatis的sql写在XML里面, 有些sql的语法符号和xml里面的冲突
大于等于 <![CDATA[ >= ]]>

Mybatis的查询类别名typeAlias的使用

typeAlias
<!--<select id="selectById" parameterType="java.lang.Integer" resultType="net.xdclass.online_class.domain.Video">-->
    <select id="selectById" parameterType="java.lang.Integer" resultType="Video">
        select * from video where id = #{video_id,jdbcType=INTEGER}

    </select>

如果有很多类,包扫描即可

<typeAliases>

        <!--<typeAlias type="net.xdclass.online_class.domain.Video" alias="Video"/>-->
        <package name="net.xdclass.online_class.domain"/>

</typeAliases>

Mybatis的sql片段的使用

根据业务需要,自定制要查询的字段,并可以复用

Mybatis的resultMap

resultMap需要自定义字段,或者多表查询,一对多等关系,比resultType更强大,适合复杂查询

<resultMap id="VideoResultMap" type="Video">
    <!--
    id指定查询列的唯一标识
    column数据库字段的名称
    property pojo类的名称
    -->
    <id column="id" property="id" jdbcType="INTEGER"/>
    <result column="video_title" property="title" jdbcType="VARCHAR" />
    <result column="summary" property="summary" jdbcType="VARCHAR" />
    <result column="cover_img" property="coverImg" jdbcType="VARCHAR" />
</resultMap>
<select id="selectBaseFidldByIdWithResultMap" resultMap="VideoResultMap">
    select id,title as video_title, summary, cover_img from video where id = #{video_id}
</select>

复杂查询一对多结果映射

namespace:名称空间一般需要全局唯一,最好和dao层的java接口一致,
可以映射sql语句到对应的方法名称和参数,返回类型
mybatis是使用接口动态代理
-->
<mapper namespace="net.xdclass.online_class.dao.VideoOlderMapper">
    <resultMap id="VideoOrderResultMap" type="VideoOrder">
        <result column="user_id" property="userId"/>
        <result column="out_trade_no" property="outTradeNo"/>
        <result column="create_time" property="createTime"/>
        <result column="state" property="state"/>
        <result column="total_fee" property="totalFee"/>
        <result column="video_id" property="videoId"/>
        <result column="video_title" property="videoTitle"/>
        <result column="video_img" property="videoImg"/>

        <!--
        association 配置属性一对一
        property对应videoOrder里面的user属性名
        javaType这个属性的类型
        -->
        <association property="user" javaType="User">
            <id property="id" column="user_id"/>
            <result property="name" column="name"/>
            <result property="headImg" column="head_img"/>
            <result property="createTime" column="create_time"/>
            <result property="phone" column="phone"/>
        </association>
    </resultMap>
    <select id="queryVideoOrderList" resultMap="VideoOrderResultMap">
            select
            o.id id,
            o.user_id,
            o.out_trade_no,
            o.create_time,
            o.state,
            o.total_fee,
            o.video_id,
            o.video_title
            o.video_img,
            u.name,
            u.head_img,
            u.create_time,
            u.phone
            from video_order o left join user u on o.user_id = u.id

        </select>

</mapper>

代码

VideoOlderMapper videoOlderMapper = sqlSession.getMapper(VideoOlderMapper.class);
List<VideoOrder> videoOrderList = videoOlderMapper.queryVideoOrderList();
System.out.println(videoOrderList.toString());

总结复杂查询对象

<!-- column不做限制,可以为任意表的字段,而property须为type 定义的pojo属性-->
<resultMap id="唯一的标识" type="映射的pojo对象">
  <id column="表的主键字段,或查询语句中的别名字段" jdbcType="字段类型" property="映射pojo对象的主键属性" />
  <result column="表的一个字段" jdbcType="字段类型" property="映射到pojo对象的一个属性"/>

  <association property="pojo的一个对象属性" javaType="pojo关联的pojo对象">
    <id column="关联pojo对象对应表的主键字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
    <result  column="表的字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
  </association>

  <!-- 集合中的property 需要为oftype定义的pojo对象的属性-->
  <collection property="pojo的集合属性名称" ofType="集合中单个的pojo对象类型">
    <id column="集合中pojo对象对应在表的主键字段" jdbcType="字段类型" property="集合中pojo对象的主键属性" />
    <result column="任意表的字段" jdbcType="字段类型" property="集合中的pojo对象的属性" />  
  </collection>
</resultMap>

 

缓存介绍和验证

什么是缓存:

程序经常要调用的对象存在内存中,方便其使用时可以快速调用,不必去数据库或者其他持久化设备中查询,主要就是提高性能

mybatis一级缓存:

 

一级缓存的作用域是SQLSession,同一个SqlSession中执行相同的SQL查询(相同的SQL和参数),第一次会去查询数据库并写在缓存中,第二次会直接从缓存中取

基于PerpetualCache 的 HashMap本地缓存

默认开启一级缓存

失效策略:当执行SQL时候两次查询中间发生了增删改的操作,即insert、update、delete等操作commit后会清空该SQLSession缓存; 比如sqlsession关闭,或者清空等

Mybatis二级缓存和配置实操

Mybatis二级缓存:

 

二级缓存是namespace级别的,多个SqlSession去操作同一个namespace下的Mapper的sql语句,多个SqlSession可以共用二级缓存,如果两个mapper的namespace相同,(即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中,但是最后是每个Mapper单独的名称空间)

基于PerpetualCache 的 HashMap本地缓存,可自定义存储源,如 Ehcache/Redis等

默认是没有开启二级缓存

操作流程:第一次调用某个namespace下的SQL去查询信息,查询到的信息会存放该mapper对应的二级缓存区域。 第二次调用同个namespace下的mapper映射文件中,相同的sql去查询信息,会去对应的二级缓存内取结果

 

失效策略:执行同个namespace下的mapepr映射文件中增删改sql,并执行了commit操作,会清空该二级缓存

注意:实现二级缓存的时候,MyBatis建议返回的POJO是可序列化的, 也就是建议实现Serializable接口

  • 缓存淘汰策略:会使用默认的 LRU 算法来收回(最近最少使用的)
  • 如何开启某个二级缓存 mapper.xml里面配置

 

全局配置:
<settings>
<!--这个配置使全局的映射器(二级缓存)启用或禁用缓存,全局总开关,这里关闭,mapper中开启了也没用-->
        <setting name="cacheEnabled" value="true" />
</settings>

如果需要控制全局mapper里面某个方法不使用缓存,可以配置 useCache=”false”

<select id="selectById" parameterType="java.lang.Integer" resultType="Video" useCache="false">

select <include refid="base_video_field"/>  from video where id = #{video_id,jdbcType=INTEGER}

</select>

Mybatis3.x的懒加载

什么是懒加载: 按需加载,先从单表查询,需要时再从关联表去关联查询,能大大提高数据库性能,并不是所有场景下使用懒加载都能提高效率

Mybatis懒加载: resultMap里面的association、collection有延迟加载功能

<!--全局参数设置-->
<settings>
    <!--延迟加载总开关-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--将aggressiveLazyLoading设置为false表示按需加载,默认为true-->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
<resultMap id="VideoOrderResultMapLazy" type="VideoOrder">
        <id column="id" property="id"/>

        <result column="user_id" property="userId"/>
        <result column="out_trade_no" property="outTradeNo"/>
        <result column="create_time" property="createTime"/>
        <result column="state" property="state"/>
        <result column="total_fee" property="totalFee"/>
        <result column="video_id" property="videoId"/>
        <result column="video_title" property="videoTitle"/>
        <result column="video_img" property="videoImg"/>

<!-- 
select: 指定延迟加载需要执行的statement id 
column: 和select查询关联的字段
-->
        <association property="user" javaType="User" column="user_id" select="findUserByUserId"/>


</resultMap>

    <!--一对一管理查询订单, 订单内部包含用户属性  懒加载-->
<select id="queryVideoOrderListLazy" resultMap="VideoOrderResultMapLazy">

        select
         o.id id,
         o.user_id ,
         o.out_trade_no,
         o.create_time,
         o.state,
         o.total_fee,
         o.video_id,
         o.video_title,
         o.video_img
         from video_order o

</select>


<select id="findUserByUserId" resultType="User">

       select  * from user where id=#{id}

</select>
// resultmap association关联查询(测试懒加载)
VideoOrderMapper videoOrderMapper = sqlSession.getMapper(VideoOrderMapper.class);
List<VideoOrder> videoOrderList = videoOrderMapper.queryVideoOrderListLazy();
System.out.println(videoOrderList.size());

//课程里面演示是6条订单记录,但是只查询3次用户信息,是因为部分用户信息走了一级缓存sqlsession
for(VideoOrder videoOrder : videoOrderList){

    System.out.println(videoOrder.getVideoTitle());
    System.out.println(videoOrder.getUser().getName());
}

mybatis事务

简介:mysql常见的两种存储引擎的区别

区别项 Innodb myisam
事务 支持 不支持
锁粒度 行锁,适合高并发 表锁,不适合高并发
是否默认 默认 非默认
支持外键 支持外键 不支持
适合场景 读写均衡,写大于读场景,需要事务 读多写少场景,不需要事务
全文索引 可以通过插件实现, 更多使用ElasticSearch 支持全文索引

 

为什么原先没进行commit操作,也可以插入成功?

因为原先是myisam引擎,没有事务,直接插入成功

检查数据库的 引擎 ,改为innodb

多个表video/chapter/episode/user/video_order

video_banner

案例

事务管理记得改为这个mybatis-config.xml
<transactionManager type="JDBC"/>

事务管理形式 MANAGED,设置非自动提交,然后注释 commit, 依旧可以保存成功

不用重点关注,公司开发项目的事务控制基本是交给Spring,或者使用分布式事务