MyBatis高级特性

1.日志

用于记录系统操作记录,保存历史数据,诊断问题

1.1.SLF4j与Logback

日志门面:统一的对外接口

1-1.jpg

pom中插入依赖

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>

resources下面添加logback.xml日志配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- %thread对应的线程 %-5表示按5个字符左对齐
            %logger表示哪个类产生的日志 %msg日志具体输出,%n换行-->
            <pattern>[%thread] %d{HH:mm:ss.SSS}  %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <!--
            日志输出级别(高-低)
            error:错误 - 系统的故障日志
            warn:警告 - 存在风险或者使用不当的日志
            info: 一般性信息
            debug: 程序内部用于调试的信息
            trace:程序运行的跟踪信息
       -->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

2.动态SQL

根据参数数据动态组织SQL

goods.xml:

<!--    动态SQL-->
<select id="dynamicSQL" parameterType="java.util.Map" resultType="org.example.entity.Goods">
    select * from t_goods
    <where>
        <if test="categoryId != null">
            and category_id = #{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
    </where>
</select>

调用:

@Test
public void testDynamicSQL() throws Exception {
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    Map param = new HashMap();
    param.put("category", 44);
    param.put("currentPrice", 500);
    //可以不加namespace,确保id是唯一即可
    List<Goods> list = session.selectList("goods.dynamicSQL",param);
    for (int i = 0; i < list.size(); i++) {
        Goods goods =  list.get(i);
        System.out.println(goods.getTitle());
    }
    MyBatisUtils.closeSession(session);
}

3.缓存

主要是MyBatis二级缓存

  • 一级缓存默认开启,缓存范围SqlSession会话
  • 二级缓存手动开启,缓存范围是Mapper Namespace
1-2.jpg

3.1.二级缓存运行规则

  • 二级开启后默认所有查询操作均使用缓存
  • 写操作commit提交时对该namespace缓存强制清空
  • 配置useCache=false可以不使用缓存
  • 配置flushCache=true代表强制清空缓存

一级缓存的测试代码:

@Test
public void testLv1Cache() {
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    Goods goods = session.selectOne("goods.selectById",1603);
    Goods goods1 = session.selectOne("goods.selectById",1603);
    System.out.println("goods hashCode:" + goods.hashCode() + "-goods1 hashCode:" +  goods1.hashCode());
    MyBatisUtils.closeSession(session);



    SqlSession session1 = null;
    session1 = MyBatisUtils.openSession();
    Goods goods2 = session1.selectOne("goods.selectById",1603);
    Goods goods3 = session1.selectOne("goods.selectById",1603);

    System.out.println("goods2 hashCode:" + goods2.hashCode() + "-goods3 hashCode:" +  goods3.hashCode());
    MyBatisUtils.closeSession(session1);
}

运行结果:

goods hashCode:574268151-goods1 hashCode:574268151
goods2 hashCode:423583818-goods3 hashCode:423583818

可以看到同一个session的hashCode相同,说明是同一个对象,并且debug日志也只会执行一次sql查询,如果在goods和goods1之间插入session.commit()方法,则goods和goods1则会是两个不同的对象。

二级缓存测试代码:

goods.xml中添加

<!--    开启二级缓存-->
<cache eviction="LRU" flushInterval="600000" readOnly="true"/>

测试代码与一级缓存一样不变

运行结果:

goods hashCode:270095066-goods1 hashCode:270095066
goods2 hashCode:270095066-goods3 hashCode:270095066

说明开启了二级缓存后,不同session也会公用一个缓存数据。

3.2.二级缓存参数说明

二级缓存相关:

<mapper namespace="goods">
    <!--
        开启二级缓存
        eviction是缓存的清除策略,当缓存对象数量达到上限后,自动出发对应算法对缓存对象清除
        1.LRU-最近最久未使用:移除最长时间不被使用的对象
        O1 O2 ... O512
        14 99     893
        则会移除O512的对象,因为它举例上次使用时间最长
        2.LFU-最近最少使用:移除最近访问频率最低的对象
        3.FIFO-先进先出:按对象进入缓存的顺序来移除
        4.SOFT-软引用:移除基于垃圾收集器状态和软引用规则的对象
        5.WEAK-弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象

        3,4,5很少使用。

        flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒=10分钟
        size 缓存的存储上限,用于保存对象或集合(1个集合算1个对象)的数量上限
        readOnly 设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,执行效率高
                 设置为false,代表每次取出的是缓存对象的副本,每次取出对象是不同的,安全性高。
    -->
    <cache eviction="LRU" flushInterval="600000" readOnly="true"/>
...

单条相关:

    <!--    useCache="false"代表不使用缓存-->
     <select id="selectAll" resultType="org.example.entity.Goods" useCache="false">
         select * from t_goods order by goods_id desc limit 10
     </select>

    ----------
    
    <!--    flushCache="true"在sql执行后强制清空缓存,同时本条sql执行结果也不会缓存-->
    <insert id="insert"
            parameterType="org.example.entity.Goods"
            useGeneratedKeys="true"
            keyProperty="goodsId"
            keyColumn="goods_id"
            flushCache="true"
    >
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
    </insert>

4.多表级联查询

1-3.jpg

4.1.一对多查询

商品与商品图片详情就是1对多的关系

开发步骤:

4.1.1.resources下创建goods_detail.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="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer" resultType="org.example.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>
</mapper>

4.1.2.goods.xml中配置一对多

<!--
    resultMap可用于说明一对多或者多对一的映射逻辑
    id是resultMap属性引用的标志
    type指向One的实体(Goods)
-->
<resultMap id="rmGoods1" type="org.example.entity.Goods">
    <!--
        映射goods对象的主键到goods_id字段
        由于其他字段都能与数据库对应上,所以不用再次列举
    -->
    <id column="goods_id" property="goodsId"></id>
    <collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
                column="goods_id"/>
</resultMap>
<!--    一对多查询-->
<select id="selectOneToMany" resultMap="rmGoods1">
    select * from t_goods limit 0,1
</select>

4.1.3.mybatis-config.xml中加入应用

加入对goods_detail.xml的引用

<mapper resource="mappers/goods_detail.xml"/>

4.1.4.调用测试

@Test
public void testOneToMany(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    List<Goods> list = session.selectList("goods.selectOneToMany");
    for (int i = 0; i < list.size(); i++) {
        Goods goods =  list.get(i);
        System.out.println(goods.getTitle() + " : " +goods.getGoodsDetails().size());
    }
    MyBatisUtils.closeSession(session);
}

4.2.多对一查询

商品图片详情与商品是多对一

开发步骤:

4.2.1.mapper.xml配置

goods_detail.xml中添加多对一查询语句和resultMap:

<!--    多表对应查询实体的resultMap-->
<resultMap id="rmGoodsDetail" type="org.example.entity.GoodsDetail">
    <!--        多表的主键-->
    <id column="gd_id" property="gdId"></id>
    <!--        多表的外键,一表的主键-->
    <result column="goods_id" property="goodsId"/>
    <!--        对表对应的selectById查询语句-->
    <association property="goods" select="goods.selectById" column="goods_id"></association>
</resultMap>

<!--    多表的查询语句-->
<select id="selectManyToOne" resultMap="rmGoodsDetail">
    select * from t_goods_detail limit 0,10
</select>

4.2.2.调用

@Test
public void testManyToOne(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    List<GoodsDetail> list = session.selectList("goodsDetail.selectManyToOne");
    for (int i = 0; i < list.size(); i++) {
        GoodsDetail goodsDetail =  list.get(i);
        if (goodsDetail.getGoods() == null)
            continue;
        System.out.println(goodsDetail.getGdPicUrl() + " : " + goodsDetail.getGoods().getTitle());
    }
    MyBatisUtils.closeSession(session);

}

5.分页PageHelper

原理:

  • 当前数据查询使用语句

select * from tab limit 0,10

  • 总记录数查询

select count(*) from tab

  • 程序计算总页数、当前页、上一页下一页码

5.1.PageHelper使用流程

  • maven引入PageHelper与jsqlparser

      <dependency>
          <groupId>com.github.pagehelper</groupId>
          <artifactId>pagehelper</artifactId>
          <version>5.1.10</version>
      </dependency>
      <dependency>
          <groupId>com.github.jsqlparser</groupId>
          <artifactId>jsqlparser</artifactId>
          <version>2.0</version>
      </dependency>
    
  • mybatis-config.xml增加Plugin配置

      <!--    启用PageHelper分页插件-->
      <plugins>
          <plugin interceptor="com.github.pagehelper.PageInterceptor">
              <!--            设置数据库类型-->
              <property name="helperDialect" value="mysql"/>
              <!--            分页合理化,页数小于查询第一页,大于总页数则显示最后一页-->
              <property name="reasonable" value="true"/>
          </plugin>
      </plugins>
    
  • mapper.xml中添加查询sql

    <!--    查询价格小于1000的数据-->
    <select id="selectPage" resultType="org.example.entity.Goods">
        select * from t_goods where current_price &lt; 1000
    </select>
  • 代码中使用PageHelper.startPage()自动分页
    @Test
    public void testPageHelper(){
        SqlSession session = null;
        session = MyBatisUtils.openSession();
        PageHelper.startPage(2, 10);
        Page<Goods> page = (Page)session.selectList("goods.selectPage");
        System.out.println("总页数:" + page.getPages());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("开始行号:" + page.getStartRow());
        System.out.println("当前页码:" + page.getEndRow());
        List<Goods> data = page.getResult();//当前页数据
        for (int i = 0; i < data.size(); i++) {
            Goods goods =  data.get(i);
            System.out.println(goods.getTitle());
        }
        MyBatisUtils.closeSession(session);
    }


    --结果

    总页数:181
    总记录数:1810
    开始行号:10
    当前页码:20
    康泰 家用智能胎心仪 分体探头操作方便 外放聆听 与家人分享宝宝心声

6.C3P0连接池

配置流程:

  • maven引入c3p0的引用

      <dependency>
          <groupId>com.mchange</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.5.4</version>
      </dependency>
    
  • 创建C3P0DataSourceFactory

      //C3P0与MyBatis兼容使用的数据源工厂类
      public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
          public C3P0DataSourceFactory(){
              this.dataSource = new ComboPooledDataSource();
          }
      }
    
  • mybatis.xml中引入datasource配置

        <!--            C3P0连接池-->
        <dataSource type="org.example.datasource.C3P0DataSourceFactory">
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="user" value="root"/>
            <property name="password" value="111111"/>
            <property name="initialPoolSize" value="5"/>
            <property name="maxPoolSize" value="20"/>
            <property name="minPoolSize" value="5"/>
        </dataSource>

剩下的不便

7.MyBatis批处理

在mapper.xml中使用foreach标签

<!--    insert into table-->
<!--    values("a","a1","a2"),("a","a1","a2"),(....)-->
<insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
    VALUES
    <foreach collection="list" item="item" index="index" separator=",">
        (#{item.title}, #{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
    </foreach>
</insert>

调用:

@Test
//分别插入
public void testBatchInsert1(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    long st = new Date().getTime();
    for (int i = 0; i < 1000; i++) {
        Goods goods = new Goods();
        goods.setTitle("测试批量插入商品");
        goods.setSubTitle("子标题");
        goods.setOriginalCost(200f);
        goods.setCurrentPrice(100f);
        goods.setDiscount(0.5f);
        goods.setIsFreeDelivery(1);
        goods.setCategoryId(43);
        session.insert("goods.insert", goods);
    }
    session.commit();
    long et = new Date().getTime();
    System.out.println("执行时间:" + (et - st) + "毫秒");
    MyBatisUtils.closeSession(session);
}

@Test
//批量插入
public void testBatchInsert2(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    long st = new Date().getTime();
    List list = new ArrayList();
    for (int i = 0; i < 1000; i++) {
        Goods goods = new Goods();
        goods.setTitle("测试批量插入商品");
        goods.setSubTitle("子标题");
        goods.setOriginalCost(200f);
        goods.setCurrentPrice(100f);
        goods.setDiscount(0.5f);
        goods.setIsFreeDelivery(1);
        goods.setCategoryId(43);
        list.add(goods);
    }
    session.insert("goods.batchInsert",list);
    session.commit();
    long et = new Date().getTime();
    System.out.println("执行时间:" + (et - st) + "毫秒");
    MyBatisUtils.closeSession(session);
}

分别插入的时间:

执行时间:976毫秒

批处理插入的时间:

执行时间:679毫秒

由此可见批处理的效率很高

批量删除:

<!-- 批量删除 (1, 2)-->
<delete id="batchDelete" parameterType="java.util.List">
    DELETE FROM t_goods WHERE goods_id in
    <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
        #{item}
    </foreach>
</delete>

--调用

@Test
//批量删除
public void testBatchDelete(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    long st = new Date().getTime();
    List list = new ArrayList();
    for (int i = 4670; i <= 4677; i++) {
        list.add(i);
    }
    session.insert("goods.batchDelete",list);
    session.commit();
    long et = new Date().getTime();
    System.out.println("执行时间:" + (et - st) + "毫秒");
    MyBatisUtils.closeSession(session);
}

8.注解

mapper可以通过注解方式配置

新建GoodsDAO:

public interface GoodsDAO {

    @Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}")
    public List<Goods> selectByPriceRange(@Param("min") Float min, @Param("max") Float max, @Param("limit") Integer limit);

    @Insert("INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
    @SelectKey(statement = "select last_insert_id()", before = false, keyProperty = "goodsId", resultType = Integer.class)
    int insert(Goods goods);

    @Select("select * from t_goods")
    //配置返回值map
    @Results({
            @Result(column = "goods_id", property = "goodsId", id = true),
            @Result(column = "current_price", property = "currentPrice")
    })
    List<GoodsDTO> selectAll();
}

mybatis-config.xml中配置dao

<mappers>
<!--        对应的sql mapper映射类或者包-->
<!--        <mapper class="org.example.dao.GoodsDAO"/>-->
    <package name="org.example.dao"/>
</mappers>

调用:

@Test
public void testSelect(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
    List<Goods> list = goodsDAO.selectByPriceRange(100f, 500f, 20);
    System.out.println(list.size());
    MyBatisUtils.closeSession(session);
}

@Test
public void testInsert(){
    SqlSession session = null;
    session = MyBatisUtils.openSession();
    GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
    Goods goods = new Goods();
    goods.setTitle("测试商品");
    goods.setSubTitle("测试子标题");
    goods.setOriginalCost(200f);
    goods.setCurrentPrice(100f);
    goods.setDiscount(0.5f);
    goods.setIsFreeDelivery(1);
    goods.setCategoryId(43);
    goodsDAO.insert(goods);
    session.commit();
    MyBatisUtils.closeSession(session);
}

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

推荐阅读更多精彩内容