文章目录
MyBatis进阶
一、MyBatis日志管理
1、什么是日志
日志文件是用于记录系统操作事件的记录文本或文件集合。
日志保存历史数据,是诊断问题以及理解系统活动的重要依据。
2、SLF4j与Logback
logback和log4j是同一个人开发的,logback是log4j的升级版,从维护性和执行效率上都得到了提升。
在pom.xml中添加logback依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
在resources目录下新增logback.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
appender 输出器,表示在什么地方日志输出
ConsoleAppender 向控制台日志的打印输出
-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--encoder 编码-->
<encoder>
<!--
pattern 规定日志输出的格式
%d{HH:mm:ss.SSS} 时间输出格式
[%thread] 输出的线程的名字
%-5level 日志的级别,按5个字符右对齐
%logger{36} 日志产生的类,这条字符串最多允许36字符,如果超过36字符会把类路径简写压缩
%msg 具体的日志内容
%n 换行
-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--
root 日志打印的根标签
level 日志输出级别(优先级高到低):
error:错误 - 系统故障日志
warn: 警告 - 存在风险或使用不当的日志
info: 一般性消息
debug:程序内部用于调试信息
trace:程序运行的跟踪消息
-->
<root level="debug">
<appender-ref ref="console"/>
</root>
</configuration>
其他详细信息可以参考logback官网
https://logback.qos.ch/
二、MyBatis动态SQL
1、动态SQl
动态SQL是指根据参数数据动态组织SQL的技术。
where标签会动态判断去掉第一个子语句的and。
三、MyBatis二级缓存
一级缓存默认开启,缓存范围SqlSession会话。
二级缓存手动开启,属于范围Mapper Namespace。
1、缓存的范围
2、二级缓存运行规则
二级缓存开启后默认所有查询操作均使用缓存。
写操作commit提交时对该namespace缓存强制清空。
配置useCache=false可以不用缓存。
配置flushCache=true代表强制清空缓存。
3、二级缓存配置
在相关mapper.xml文件中开启并配置二级缓存:
<mapper namespace="goods">
<!--开启了二级缓存
eviction 是缓存的清楚策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清楚
1.LRU - 最近最久未使用:移除最长时间不被使用的对象。(推荐)
2.LFU - 最近最少使用:移除最近访问频率最低的对象。
3.FIFO - 先进先出:按对象进入缓存的顺序来移除它们。(无法保证缓存命中率,不推荐)
4.SOFT - 软引用:移除基于垃圾收集器状态和软引用规则的对象。(基于JVM,而不受MyBatis控制,不推荐)
5.WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象。(基于JVM,而不受MyBatis控制,不推荐)
flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒=10分钟。
size 缓存存储上限,用于保存对象或集合(1个集合算1个对象)的数量上限。
readOnly 设置为true,代表返回只读缓存,每次从缓存取出是缓存对象本身,这种执行效率高。(推荐)
设置为false,代表每次取出的是缓存对象的“副本”,每一次取出的对象都是不同的,这种安全性较高。
-->
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
<!--useCache="false" 代表不使用缓存,返回集合不推荐使用缓存,因为缓存命令率低,占用内存大-->
<select id="selectAll" useCache="false" ...>
select ...
</select>
<!--flushCache="true" 在sql执行后强制清空缓存,用在select语句时,该语句结果也不会放入缓存里-->
<insert id="insert" flushCache="true" ...>
insert ...
</insert>
</mapper>
四、MyBatis多表级联查询
1、OneToMany对象关联查询
首先在主实体类中添加关联集合:
public class Goods{
private Integer goodsId;
private String title;
private List<GooodsDetail> goodsDetails;
...
}
然后在对应的mapper.xml文件中添加级联查询:
<!--
resultMap可用说明一对多或者多对一的映射逻辑
id是resultMap属性引用的标志
type 指向One的实体(Goods)
-->
<resultMap id="rmGoods1" type="com.ql.mybatis.entity.Goods">
<!--映射goods对象的主键到goods_id字段
除了id字段,其他字段只要符合驼峰命名转换规则,且开启了驼峰命名转换,就不需要一一配置-->
<id column="goods_id" property="goodsId"></id>
<!--
collection的含义是,在select语句得到结果以后,对所有Goods对象遍历得到goods_id字段值,并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,将得到的“商品详情”集合赋值给goodsDetails List对象。
-->
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,1
</select>
2、ManyToOne对象关联查询
首先在多的一方实体类中添加关联集合:
public class GoodsDetail{
private Integer gdId;
private Integer goodsId;
private Goods goods;
...
}
然后在对应的mapper.xml文件中添加级联查询:
<resultMap id="rmGoodsDetail" type="com...GoodsDetail">
<id column="gd_id" property="gdId"/>
<!--因为在association标签中配置了goods_id字段,所以额外配置一下,不然goodsId属性接受不到值,其他字段不用-->
<result column="goods_id" property="goodsId"/>
<!--用于一对一和多对一-->
<association property="goods" select="goods.selectById" column="goods_id"></association>
</resultMap>
<select id="selectManyToOne" resultMap="rmGoodsDetail">
select * from t_good_detail
</select>
五、分页插件PageHelper
1、分页查询的麻烦事
当前页数据查询 - select * from tab limit 0,10
总记录数查询 - select count(*) from tab
程序计算总页数、上一页页码、下一页页码。
PageHelper官网:
https://pagehelper.github.io/
2、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配置
<configuration>
<!--启用PageHelper分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--设置数据库方言-->
<property name="helperDialect" value="mysql"/>
<!--分页合理化-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
</configuration>
代码中使用PageHelper.startPage()自动分页
/*startPage(pageNum, paseSize)方法会自动将下一次查询进行分页*/
PageHelper.startPage(2,10);
Page<Goods> page = (Page)session.selectList("goods.selectPage");
//selectPage的sql语句就是普通的select语句
page.getPages();//总页数
page.getTotal();//总记录数
page.getStartRow();//开始行号
page.getEndRow();//结束行号
page.getPageNum();//当前页码
List<Goods> data = page.getResult();//当前页数据
3、不同数据库分页的实现原理
MySQL分页
Oracle
SQL Server 2000
SQL Sever 2012+
六、MyBatis配置C3P0连接池
首先在pom.xml中引入依赖:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
创建C3P0DataSourceFactory(可以在项目目录下创建datasource包,在创建此类)
/*
C3P0与MyBatis兼容使用的数据源工厂类
*/
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory{
public C3P0DataSourceFactory(){
this.dataSource = new ComboPooledDataSource();
}
}
mybatis-config.xml中配置数据源
<dataSource type="com.ql.mybatis.datasource.C3P0DataSourceFactory">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/databaseName?useUnicode=true&characterEncoding=UTF-8"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="initialPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<property name="minPoolSize" value="5"/>
</dataSource>
七、MyBatis批处理
1、批量新增
在相应的Mapper.xml中编写SQL
<!--其中collection的值固定为list-->
<insert id="batchInsert" parameterType="java.util.List">
insert into t_goods (title, sub_title, original_cost ... )
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.title},#{item.subTitle}...)
</foreach>
</insert>
在Java中调用时传List即可
session.insert("goods.batchInsert", list);
批量插入数据的局限
1、无法获得插入数据的id
2、批量生成的SQL太长,可能会被服务器拒绝。需要插入的数据太多时可以分批次插入。
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>
session.delete("goods.batchDelete", list);
八、MyBatis注解开发
在dao包里创建GoodsDAO接口
public interface GoodsDAO{
@Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limt}")
public List<Goods> selectByPriceRange(@param("min") Float min, @Param("max") Float max, @Param("limt") Integer limt);
@Insert("insert into t_goods (title, sub_title,...) values (#{title},#{subTitle},...)")
//<selectKey>
@SelectKey(statement="select last_insert_id()", before=false, keyProperty="goodsId", resultType=Integer.class)
public int insert(Goods goods);
@Select("select * from t_goods")
//<resultMap>
@Results({
//<id>
@Result(column="goods_id", property="goodsId", id=true),
@Result(column="title", property="title"),
@Result(column="current_price", property="currentPrice")
})
public List<GoodsDTO> selectAll();
}
在mybatis-config.xml配置文件中增加相应的说明:
<mappers>
<!--
使用注解开发时mapper配置有两种写法,我们更推荐直接配置包,这样里面的文件不用一个一个再去配置
<mapper class="com.ql.mybatis.dao.GoodsDAO"/>
-->
<package name="com.ql.mybatis.dao"/>
</mappers>
最后在Java代码中调用
//采用注解方式开发时先获取映射器,它会基于代理模式自动生成对应的实现类
GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
List<Goods> list = goodsDAO.selectByPriceRange(100f,500f,20);
//insert()方法返回值代表本次成功插入的记录总数
int num = goodsDAO.insert(goods);
session.commit();//提交事务数据
List<GoodsDTO> list2 = goodsDAO.selectAll();
1、xml和注解开发区别
利用xml有更好的维护性,可以让我们在xml中对sql灵活的修改,更适合大型团队协作的项目。
利用注解有更好的程序编码的体验,不需要添加xml文件,在程序中直接修改即可,更适合小型敏捷开发的工程。