MyBatis进阶使用

发布时间:2025-12-10 11:25:47 浏览次数:2

Mybatis高级特性

MyBatis日志管理

什么是日志

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

SLF4J与Logback

首先在代码中配置logback的日志信息

  • 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@Testpublic 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_goodsORDER BY goods_idDESC LIMIT 10</select><select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">SELECT *FROM t_goodsWHERE goods_id = #{value}</select>

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

    @Testpublic 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说明使用了缓存读取数据

    @Testpublic 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是怎么进行级联查询的

  • 首先新建goods-detail的mapper文件
  • <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//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>
  • 接着在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>
  • 对应的goods实体类也需要增加:
  • private List<GoodsDetail> goodsDetails;
  • 最后执行测试类
  • @Testpublic 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分页插件

    分页查询的麻烦事

  • 当前页查询–select * from tab limit 0 ,10 (起始行号的计算)
  • 总记录数查询 – select count(*) from table (涉及分页需要知道原始数据记录总数)
  • 程序计算总页数,上一页页码,下一页页码
  • 为了解决上述问题,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>

    代码使用

    @Testpublic 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();}@Overridepublic DataSource getDataSource(){try {((DruidDataSource)this.dataSource).init();} catch (SQLException e) {// TODO: handle exceptionthrow 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代码:

    @Testpublic 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 {@Testpublic 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);}}@Testpublic 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);}}@Testpublic 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);}}}
    需要做网站?需要网络推广?欢迎咨询客户经理 13272073477