MyBatis进阶使用

Mybatis高级特性

MyBatis日志管理

什么是日志

  • 日志文件是用于记录系统操作时间的记录文件或者文件集合
  • 日志保存历史数据,是诊断问题以及理解系统活动的重要依据

SLF4J与Logback

在这里插入图片描述

首先在代码中配置logback的日志信息
  1. pom中添加slf4j的依赖
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

2.resource下新增配置文件logback.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration >
    <!--appender代表 输出器/追加器,用于说明在何处进行日志输出 class固定指向 ConsoleAppender用意是向控制台打印日志输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder 代表编码 -->
        <encoder>
            <!--表示日志输出格式 %d{HH:mm:ss.sss}:时间到毫秒 ; [%thread]:线程名; %-5level :日志级别 -5代表按5字符右对齐  %logger{36} :说明是哪个类产生的日志; %msg%n :具体内容+换行-->
            <pattern> %d{HH:mm:ss.sss} [%thread] %-5level %logger{256} -%msg%n</pattern>
        </encoder>
    </appender>
    <!-- level 有五个备选值,日志输出级别(优先值从高到低)
    error:错误-系统错误日志
    warn:警告-存在风险或者使用不当的日志
    info:一般性消息
    debugger:程序内部用于调试信息
    trace:程序运行的跟踪信息
    -->
    <root level ="debug">
        <appender-ref ref ="console"/>
    </root>
</configuration>

效果如下:
在这里插入图片描述

MyBatis动态SQL

应用场景:

在这里插入图片描述

动态SQL

  • 动态SQL是根据参数数据动态组织SQL的技术
    在这里插入图片描述
    下面给出一个动态SQL的示例:
 <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>
            <if test="categoryId != null">
                and category_id =#{categoryId}
            </if>

            <if test="currentPrice != null">
                and current_price <![CDATA[<]]> #{currentPrice}
            </if>

        </where>
    </select>
    ```
测试类:

```java
    @Test
    public void testDynamicSQL() {
        SqlSession sqlSession = null;
        try {
            Map param = new HashMap();
            param.put("categoryId", 44);
            param.put("currentPrice", 500);
            sqlSession = MyBatisUtils.openSession();
            // 返回表示插入成功的记录条数
            List<Goods> list = sqlSession.selectList("goods.dynamicSQL", param);

            for (Goods good : list) {
                System.out.println(good.toString());
            }

        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

MyBatis二级缓存

  • 一级缓存默认开启,缓存范围SqlSession回话
  • 二级缓存手动开启,属于范围Mapper Namespace

缓存的范围

一级缓存存放在当前sqlsession中,当会话结束时,缓存被清空,可能使用率不高,还会浪费内存。
二级缓存属于整个Namespace,存储在红色区域的数据会被所有sqlsession共享。
在这里插入图片描述

二级缓存运行规则

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

下面给出一个示例演示一级缓存 和二级缓存
xml:

<!-- cache这行是开启二级缓存的语句,eviction="LRU"是缓存策略,flushInterval="600000"是缓存时间  size="512"是缓存最大个数 readOnly="true"表示缓存只读-->
 <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        SELECT *
        FROM t_goods
        ORDER BY goods_id
                DESC LIMIT 10
    </select>

    <select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">
        SELECT *
        FROM t_goods
        WHERE goods_id = #{value}
    </select>

一级缓存(默认开启的就是一级缓存)
测试代码LV1:

    @Test
    public void testLv1Cache(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            // 语句执行了两次,但是sql只执行了一次
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);
            Goods goods1 = sqlSession.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode()+" V S "+goods1.hashCode());
            System.out.println(goods.getTitle());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

效果:(两次调用,sql只执行了一次,说明第二次是缓存读取)
在这里插入图片描述

测试2:(二级缓存开启):两个sqlsession只有一次执行sql,原因是缓存在namesapce中,并且Cache Hit Ratio [goods]: 0.5说明使用了缓存读取数据

@Test
    public void testLv2Cache(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            // 语句执行了两次,但是sql只执行了一次
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);

            System.out.println(goods.hashCode());
            System.out.println(goods.getTitle());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.commit();
            System.out.println("do 1");
            MyBatisUtils.closeSession(sqlSession);
        }

        try {
            sqlSession = MyBatisUtils.openSession();
            System.out.println("do 2");
            // 语句执行了两次,但是sql只执行了一次
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);

            System.out.println(goods.hashCode());
            System.out.println(goods.getTitle());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

效果:
在这里插入图片描述

二级缓存的四个参数

eviction

代表了缓存的清楚策略,当缓存对象达到上限后,自动触发对应算法对缓存对象进行清除
LRU --最近最久未使用:移除最长时间不被使用的对象(默认)
【LFU】–最近最少被使用:移除最少被使用的对象
FIFO–先进先出:按对象进入缓存的顺序来清理它们
SOFT–软引用:移除基于垃圾收集器状态和软引用规则的对象
WEAK–弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象

flushInterval

代表间隔多长时间自动清空缓存,单位毫秒,60000ms =10分钟

size

代表缓存存储上限,用于保存对象或者集合(1个集合是一个对象)的数量上限

readOnly

设置为true,代表返回只读缓存,每次从缓存中取出的是缓存本身,执行效率较高
设置为false,代表每次取出的都是对象副本,每一次取出的对象都是不同的,这种安全性较高

在其它查询标签中,也有一些标签约束,例如

useCache

表示是否使用缓存,一次返回很多数据的sql一般设置为false,代表查询结果不放入缓存。

flushCache

表示执行完sql后立即清除缓存

MyBatis多表级联查询

下面给出一个示例来阐释级联关系
班级和学生是 1:n关系,所以一般在学生表中都有班级的主键做外键
学生和学籍档案是1;1关系,所以一般都是主键关联
学生和课程是n:n关系,一般会有一张中间表存储关系数据
###

1:n关系

下面给出一个示例来看看MyBatis中oneToMany是怎么进行级联查询的

  1. 首先新建goods-detail的mapper文件
<?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">
    <cache eviction="LRU" flushInterval="600000" readOnly="true" size="512"/>
    <select id="selectByGoodsId" parameterType="Integer" resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id =#{value}
    </select>
</mapper>
  1. 接着在goods的mapper文件中增加以下代码:
 <resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
        <!--映射对象的主键到goods_id字段 -->
        <id column="goods_id" property="goodsId"></id>
        <!-- collection的含义是,在select * from t_goods limit 0,1 得到结果后,对所有的Goods对象遍历得到goods_id字段值,并带入到
            goodsdetail命名空间的findByGoodsID的sql中执行查询,
            将得到的商品详情信息集合赋值给goodsdetail对象
        -->
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
    </resultMap>

    <select id="selectOneToMany" resultMap="rmGoods1">
        select * from t_goods limit 0,10
    </select>
  1. 对应的goods实体类也需要增加:
    private List<GoodsDetail> goodsDetails;
  1. 最后执行测试类
    @Test
    public void testSelectOneToMany(){
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();

            List<Goods> list = sqlSession.selectList("goods.selectOneToMany");
            for (Goods goods : list) {


                System.out.println(goods.hashCode());
                System.out.println(goods.getTitle());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.commit();

            MyBatisUtils.closeSession(sqlSession);
        }
    }

最终结果:在执行完goods查询sql后会接着去查询details信息,进行了级联查询
在这里插入图片描述

那么我们如何完成从多的一方关联一的一方数据呢?接着往下看 多对一关联查询

ManyToOne (多对一关联查询)

与上面一对多关联查询步骤基本一致,只是在对应的查询xml中使用的标签不一样,如下:因为一对多时需要由一个值查出多个,所以采用的标签是<collection ,而现在是多对一,所以使用的标签是 < association ,意思是关联。

    <resultMap id="rmGoodsDeatil1" type="com.imooc.mybatis.entity.GoodsDetail">
        <!--映射对象的主键到goods_id字段 -->
        <id column="goods_id" property="goodsId"></id>
        <!-- collection的含义是,在select * from t_goods limit 0,1 得到结果后,对所有的Goods对象遍历得到goods_id字段值,并带入到
            goodsdetail命名空间的findByGoodsID的sql中执行查询,
            将得到的商品详情信息集合赋值给goodsdetail对象
        -->
        <association property="goods" select="goods.selectById" column="goods_id"/>
    </resultMap>

效果如下:
在这里插入图片描述

PageHelper分页插件

分页查询的麻烦事

  1. 当前页查询–select * from tab limit 0 ,10 (起始行号的计算)
  2. 总记录数查询 – select count(*) from table (涉及分页需要知道原始数据记录总数)
  3. 程序计算总页数,上一页页码,下一页页码

为了解决上述问题,MyBatis提供了分页插件PageHelper,那么如何使用分页插件呢,接着往下看。

PageHelper使用流程

  • maven 引入PageHelper 和 jsqlparser
  • mybatis-config.xml增加plugin配置
  • 代码中直接使用PageHelper.startPage()自动分页

下面逐步操作一遍

引入依赖

pom.xml增加

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>3.1</version>
        </dependency>
mybatis-config.xml增加plugin配置
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--helperDialect设置数据库类型,可参考官方文档 -->
            <property name="helperDialect" value="mysql"/>
            <!-- 分页合理化-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
代码使用
 @Test
    public void testSelectPage() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            // startPage方法会自定将下一次查询进行分页
            PageHelper.startPage(2,10);
            Page<Goods> page = (Page)sqlSession.selectList("goods.selectPage");
            System.out.println("总页数:"+page.getPages());
            System.out.println("总记录数:"+page.getTotal());
            System.out.println("开始行号:"+page.getStartRow());
            System.out.println("结束行号:"+page.getEndRow());
            System.out.println("当前页码:"+page.getPageNum());
            List<Goods> list = page.getResult();
            for (Goods goods:list) {
                System.out.println(goods.getTitle());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            MyBatisUtils.closeSession(sqlSession);
        }
    }

结果:
在这里插入图片描述

不同数据库分页的实现原理

Mysql分页

在这里插入图片描述

ORACLE分页

三层结构,外两层固定
在这里插入图片描述

SQL Server 2000

在这里插入图片描述

SQL Server 2000+

在这里插入图片描述

MyBatis配置C3P0连接池

MyBatis配置C3P0一共分3步

添加依赖

 <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
  </dependency>

实现数据源C3P0化

package com.imooc.mybatis.datasource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

/**
 * C3P0与MyBatis兼容使用的数据源工厂类
 */
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory{
    public C3P0DataSourceFactory() {
        this.dataSource = new ComboPooledDataSource();
    }
}

修改配置文件

 <environment id="prd">
            <!-- -->
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="com.imooc.mybatis.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&amp;useSSL=false"/>
                <property name="user" value="root"/>
                <property name="password" value="123456"/>
                <property name="initialPoolSize" value="5"/>
                <property name="maxPoolSize" value="20"/>
                <property name="minPoolSize" value="5"/>
            </dataSource>
        </environment>

效果:
在这里插入图片描述

MyBatis配置Druid连接池

添加依赖

 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.5</version>
        </dependency>

实现数据源Druid化

和C3P0的不同,Druid需要重写getDataSource方法。

package com.imooc.mybatis.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;

public class DuridDataSourceFactory extends UnpooledDataSourceFactory {
    public DuridDataSourceFactory(){
        this.dataSource = new DruidDataSource();
    }

    @Override
    public DataSource getDataSource(){
        try {
            ((DruidDataSource)this.dataSource).init();
        } catch (SQLException e) {
            // TODO: handle exception
            throw new RuntimeException(e);
        }
        return this.dataSource;
    }
}

修改配置文件

<environment id="druid">
            <!-- -->
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="com.imooc.mybatis.datasource.DuridDataSourceFactory">
                <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
                <property name="initialSize" value="5"/>
                <property name="maxActive" value="20"/>

            </dataSource>
        </environment>

MyBatis批处理

xml:

<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
            <!-- collection 数九来源-->
            <foreach collection="list" item="item" index="index" separator=",">
                (#{item.title}, #{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})

            </foreach>
    </insert>

java代码:

 @Test
    public void testBatchInsert(){
        SqlSession sqlSession = null;
        try {
            long bt =new Date().getTime();
            sqlSession = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 0; i < 10000; i++) {
                Goods goods =new Goods();
                goods.setSubTitle("测试副标题"+i);
                goods.setTitle("测试标题"+i);
                goods.setCurrentPrice(500D);
                goods.setOriginalCost(1000d);
                goods.setDiscount(0.5d);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                list.add(goods);
            }
            sqlSession.insert("goods.batchInsert",list);
            sqlSession.commit();
            long et =new Date().getTime();
            System.out.println("执行时间为"+(et-bt)+"毫秒");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            MyBatisUtils.closeSession(sqlSession);
        }
    }

MyBatis注解开发

使用MyBatis注解可以替代在xml中的标签,简化开发。

MyBatis常用注解

在这里插入图片描述
下面具体示例使用注解开发应该怎么做

配置mybatis-config.xml
 <mappers>
        <!-- 一个DAO配置一次,很麻烦-->
        <!--<mapper class ="com.mysql.mybatis.dao.GoodsDAO"/>-->
        <!-- 包下所有的都会扫到,很方便-->
        <package name="com.mysql.mybatis.dao"/>
    </mappers>
创建DAO接口并使用注解开发
package com.mysql.mybatis.dao;

import DTO.GoodsDTO;
import entity.Goods;
import org.apache.ibatis.annotations.*;

import java.util.List;

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)\n" +
            "        VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
    @SelectKey(statement = "select last_insert_id()" ,before = false ,keyProperty = "goodsId",resultType=Integer.class)
    public Integer Insert(Goods goods);

    @Select("SELECT * FROM T_GOODS")
    @Results({
            @Result(column = "goods_id",property = "goodsId" ,id = true),
            @Result(column = "title",property = "title" ),
            @Result(column = "current_price" ,property = "price")

    })
    public List<GoodsDTO> SelectAll();
}

测试类中获取Dao并执行方法
import DTO.GoodsDTO;
import com.mysql.mybatis.dao.GoodsDAO;
import entity.Goods;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import utils.MyBatisUtils;

import java.util.List;

public class MyBatisAnnociationTest {
    @Test
    public void testSelectByRange() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            // 虽然持有的是接口 mybatis会根据配置信息动态生成实现类
            GoodsDAO goodsDAO = sqlSession.getMapper(GoodsDAO.class);
            List<Goods> list = goodsDAO.selectByPriceRange(100f, 500f, 20);
            System.out.println(list.size());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

    @Test
    public void testInsert() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods good = new Goods();
            good.setSubTitle("测试商品");
            good.setTitle("测试商品");
            good.setOriginalCost(200d);
            good.setCurrentPrice(100d);
            good.setDiscount(0.5);
            good.setIsFreeDelivery(1);
            good.setCategoryId(43);
            // 返回表示插入成功的记录条数
            GoodsDAO goodsDAO = sqlSession.getMapper(GoodsDAO.class);
            int num = goodsDAO.Insert(good);
            sqlSession.commit();
            System.out.println(num);
            System.out.println(good.getGoodsId());

        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

    @Test
    public void testSelectAll() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();


            GoodsDAO goodsDAO = sqlSession.getMapper(GoodsDAO.class);
            List<GoodsDTO> goodsDTOS = goodsDAO.SelectAll();
            for (GoodsDTO g : goodsDTOS) {
                System.out.println(g.getTitle());
            }


        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知虚

权当做笔记,打赏随您心意

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值