时间:2024-05-25 星期五
MyBatis高级特性
MyBatis日志管理
日志
-
日志文件是用于记录系统操作事件的记录文件或文件集合
-
日志保存历史数据,使诊断问题以及理解系统活动的重要依据
SLF4J与Logback日志组件关系
SLF4j作为日志输出的门面,负责日志输出的表现;logback是对日志的底层实现。
使用MyBatis日志管理
步骤:
-
在Maven工程中下的pom.xml文件中添加logback组件依赖,调价成功后,自动进行日志管理,运行程序能看到控制台有日志输出
-
<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> <!-- %d{HH:mm:ss.SSS}时间格式 {%thread} 运行线程 %-5level 输出最低级别 %logger{36} 记录字符最长不超过36个 - %msg 日志信息 %n 换行 --> <pattern>%d{HH:mm:ss.SSS} {%thread} %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 日志输出级别(优先级高到低) error:错误-系统的故障日志 warn:警告-存在风险或使用不当的日志 info:一般性消息 debug:程序内部用于调试信息 trace:程序运行的跟踪信息 --> <root level="debug"> <!-- 引用上面定义的<appender>标签name为console ,这个name名字可以随意,但是其class的值要按规定进行书写 --> <appender-ref ref="console"/> </root> </configuration>
-
MyBatis动态SQL
动态SQL是指在程序运行中根据参数数据动态组织SQL的技术。在MyBatis中有标签如<where>、<if test="条件1">这样根据条件动态添加SQL语句和参数。
使用过程:
-
在MyBatis的映射.xml文件下添加动态SQL标签内容
-
<!--MyBatis动态修改SQL语句--> <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods"> select * from t_goods <where> <!--如果传入的map参数中的categoryId不为空,则添加if标签内的SQL条件语句--> <if test="categoryId != null"> and category_id = #{categoryId} </if> <!--<是小于号<.在这里进行转义,不然语句出错--> <if test="currentPrice != null"> and current_price < #{currentPrice} </if> </where> </select>
-
-
测试脚本
-
// 测试MyBatis动态SQL @Test public void testDynamicSQL() throws Exception { SqlSession sqlSession = null; try { sqlSession = MybatisUtils.openSession(); Map maps = new HashMap(); maps.put("categoryId",68); maps.put("currentPrice",100); List<Goods> list = sqlSession.selectList("goods.dynamicSQL", maps); for (Goods goods : list){ System.out.println(goods.getTitle() +":"+goods.getCurrentPrice()); } }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(sqlSession); } }
-
MyBatis二级缓存
-
一级缓存默认开启,缓存范围是SqlSession会话
-
在一个会话开启与关闭之间,进行对数据库的相同的操作,如查询相同的数据结果,第二次进行查询时就会使用第一次查询到的结果;如果SqlSession关闭后该缓存数据也会被清空
-
-
二级缓存手动开启,属于范围Mapper Namespace,即缓存的数据是对同一个mapper的命名空间中的所有sql标签操作
-
二级缓存运行规则
-
二级缓存开启后默认所有查询操作均使用缓存
-
写操作commit提交时对该namespace缓存强制清空
-
配置uerCache=false可以不用缓存
-
这个配置是在如<select id="" result="" useCache=false>这样的标签语句内容的进行属性设置
-
-
配置flushCache=true代表强制清空缓存,这也是在SQL标签中进行设置
-
<!-- <select>SQL标签名为selectAll,返回的数据结果为Goods实体类 --> <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="true" flushCache="true"> select * from t_goods order by goods_id desc limit 10 </select>
-
-
-
-
开启缓存,在resources目录mapper文件夹下的映射文件中添加配置
-
<!-- 开启了二级缓存 eviction:是缓存的清楚策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除 1.LRU - 最近最久未使用,移除最长事件不被使用的对象 2.FIFO - 先进先出:按对象进入缓存的顺序来移除它们 3.SOFT - 软引用:以处基于垃圾收集器状态和软引用规则的对象 4.WEAK - 弱引用:更积极的以处基于垃圾收集器状态和弱引用规则的对象 flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒-10分钟 size 缓存存储上限,用于保存对象或集合(1个集合算一个对象)的数量上限 readOnly 设置true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高 设置为false,代表每次取出的是缓存对象的”副本“,每一次取出的对象都是不同的,这种安全性最高 --> <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
-
-
MyBatis多表级联查询
与多表联合查询不同,多表联合查询使用同一个SQL语句去进行多个表之间的联合查询,而多表级联查询是关系型数据库中一对一、一对多、多对多的思想的实现。
案例:一个商品对应着多个商品详情
-
编写商品详情实体类,mapper映射文件,商品查询语句
-
编写商品详情实体类
-
package com.imooc.mybatis.entity; public class GoodsDetail { private Integer gdId;//自增属性,商品详情序号 private Integer goodsId;//商品Id private String gdPicUrl;//商品图片链接 private Integer gdOrder;//商品订单编号 //省略getter和setter方法 }
-
-
编写mapper映射类,goodsDetail.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="findGoodsById"> <select id="selectGoodsByGoodsId" resultType="com.imooc.mybatis.entity.GoodsDetail"> select * from t_goods_detail where goods_id = #{goodsId} </select> </mapper>
-
-
-
在商品实体类中添加,属性为List<GoodsDetail>的商品详情信息属性,实现一个商品有多个商品详情的思想
-
package com.imooc.mybatis.entity; import java.util.List; //商品实体类 public class Goods { private Integer goodsId;//商品编号 private String title;//标题 private String subTitle;//子标题 private Float originalCost;//原价 private Float currentPrice;//当前价格 private Float discount;//折扣率 private Integer isFreeDelivery;//是否包邮,1-包邮 0-不包邮 private Integer categoryId;//分类编号 private List<GoodsDetail> goodsDetailList;//商品详情,一个商品有着多个商品描述信息 //省略getter和setter方法 }
-
-
在商品类mapper映射中,编写对指定商品Id进行详情查询的操作
-
定义新的结果映射,包含指定商品Id和对应的商品详情信息
-
<!-- 编写一对多,结果映射结果 resultMap可用于说明一对多获知多对一的映射逻辑 id 是resultMap属性引用的标签 type 执行one的实体(Goods) --> <resultMap id="rmGoods2" type="com.imooc.mybatis.entity.Goods"> <!-- 映射goods对象的主键到goods_id字段--> <id column="goods_id" property="goodsId"/> <!-- collection的含义是,在 select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历,以得到的goods_id字段值, 并带入到goodsDetail命名空间得findByGoodsId得SQL执行查询, 将得到的”商品详情“集合赋值给goodsDetails List对象--> <collection property="goodsDetailList" select="findGoodsById.selectGoodsByGoodsId" column="goods_id"/> </resultMap>
-
-
查询指定商品,返回的结果类型为定义的结果映射数据格式
-
<select id="selectOneToMany" resultMap="rmGoods2"> select * from t_goods limit 0,10 </select>
-
-
测试脚本
-
//测试多表级联查询 @Test public void testSelectOneToMany() throws Exception { SqlSession session = null; try { session = MybatisUtils.openSession(); List<Goods> list = session.selectList("goods.selectOneToMany"); for (Goods goods : list){ List<GoodsDetail> goodsDetailList = goods.getGoodsDetailList(); for (GoodsDetail goodsDetail : goodsDetailList){ System.out.println(goods.getGoodsId()+" :"+goods.getTitle()+" :"+ goodsDetail.getGdPicUrl()+": "+goodsDetail.getGoodsId()); } } }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); } }
-
-
MyBatis分页插件PageHelper
使用流程
-
maven工程中,在pom.xmle文件中引入PageHelper与jsqlparser依赖
-
<!-- PageHelper分页插件依赖 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.11</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>
-
-
在java程序代码中使用PageHelper.startPage()自动分页
-
在映射类中编写一个过滤查询语句,id为selectPage用于提供分页数据
-
<!-- 查询现价小于1000得商品 --> <select id="selectPage" resultType="com.imooc.mybatis.entity.Goods"> select * from t_goods where current_price < 1000 </select>
-
-
测试脚本
-
//测试使用Pagehelper分页插件 @Test public void testSelectPage() throws Exception { SqlSession session = null; try { session = MybatisUtils.openSession(); /*startPage方法会自动将下一次查询进行分页*/ PageHelper.startPage(2,10);//查询第二页,每一页展示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()); System.out.println("当前页码:"+ page.getPageNum()); List<Goods> data = page.getResult();//当前页数据 for (Goods goods : data){ System.out.println(goods.getTitle()); } }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); } }
-
-
MyBatis整合C3P0连接池
替换Mybatis默认配置得数据库连接池,因为其不是最好得数据库连接池,通过替换,可以提高性能。
整合过程:
-
在maven工程中得pom.xml文件中,添加C3P0数据库连接池依赖
-
<!-- C3P0数据库连接池依赖 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency>
-
-
编写C3P0数据库连接池配置类,该类继承自org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory,是属于Mybatis框架的。
-
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();//使用c3p0数据源替换UnpooledDataSourceFactory得数据源, // 将mybatis中得UnpooledDataSourceFactory属性进行更改,进而整合MyBatis和C3P0 } }
-
-
到mybatis-config.xml中进行数据库连接池的配置
-
<!-- 采用C3P0连接池方式管理数据库连接--> <dataSource type="com.imooc.mybatis.datasource.C3P0DataSourceFactory"> <!-- C3P0与MyBatis的数据库连接池的属性名不太相同,所以需要对 name进行修改 --> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&characterCoding=UTF-8"/> <property name="user" value="root"/> <property name="password" value="123456"/> <!-- 设置数据库连接池的连接配置 --> <!-- 初始化连接池大小为5 --> <property name="initialPoolSize" value="5"/> <!-- 设置最大最小连接数 --> <property name="maxPoolSize" value="20"/> <property name="minPoolSize" value="5"/> </dataSource> </environment>
-
完成以上步骤,就完成了C3P0整合过程。
MyBatis批处理数据
批处理与单处理插入
-
<insert>语句
-
<!-- 批量插入数据,使用集合数据类型进行批量插入 --> <!-- 一次性输入多条数据的sql语句格式为 INSERT INTO table VALUES ("a","a1","a2"),("b","b1","b2"),(值1,值2,值3),(....) --> <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> <!--一条一条的插入--> <insert id="normalInsert" parameterType="com.imooc.mybatis.entity.Goods"> insert into `babytun`.`t_goods`(`title`,`sub_title`,`original_cost`,`current_price`,`discount`,`is_free_delivery`, `category_id`) values (#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery},#{categoryId}) </insert>
-
-
测试脚本
-
//测试批量插入 @Test public void testBatchInsert() throws Exception { SqlSession session = null; try { long st = new Date().getTime(); session = MybatisUtils.openSession(); List list = new ArrayList(); for (int i = 0; i < 10000; 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); } int result = session.insert("goods.batchInsert", list); System.out.println("插入"+result+"条数据"); session.commit();//提交事务数据 long et = new Date().getTime(); System.out.println("执行时间:"+(et-st) + "毫秒"); }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); } } //测试每次单条插入 @Test public void testNormalInsert() throws Exception { SqlSession session = null; Integer result = 0;//累计插入条数 try { long st = new Date().getTime(); session = MybatisUtils.openSession(); List list = new ArrayList(); for (int i = 0; i < 10000; 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.normalInsert",goods); result += 1; } System.out.println("插入"+result+"条数据"); session.commit();//提交事务数据 long et = new Date().getTime(); System.out.println("执行时间:"+(et-st) + "毫秒"); }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); } }
-
-
对比处理速度,批处理比单处理速度快了两到三倍。
删除批处理
-
<delete>语句
-
<!-- 批量删除数据 --> <!-- 一次性删除多条数据的sql语句格式为 delete from table where 字段1 in(值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() throws Exception { SqlSession session = null; try { long st = new Date().getTime(); session = MybatisUtils.openSession(); List list = new ArrayList(); list.add(1930); list.add(1931); list.add(1932); int result = session.insert("goods.batchDelete", list); session.commit();//提交事务数据 long et = new Date().getTime(); System.out.println("执行时间:"+(et-st) + "毫秒"); }catch (Exception e){ session.rollback(); throw e; }finally { MybatisUtils.closeSession(session); } }
-
MyBatis注解开发方式
除了使用mapper映射文件.xml进行SQL语句的编写之外,还可以使用注解得方式进行MyBatis的SQL语句编写,实现对数据库的操作。
在Maven工程的src/main/java/../../../目录下新建DAO包用于存放数据访问对象接口类,如GoodsDAO
注解方式的查询操作
实现步骤:
-
在接口类GoodsDAO中添加查询注解和方法定义
-
package com.imooc.mybatis.dao; import com.imooc.mybatis.entity.Goods; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; //使用MyBatis的注解进行数据访问对象编写 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> selectGoodsByPrice(@Param("min") Float min,@Param("max") Float max, @Param("limit") Integer limit); }
-
在mybatis-config.xml中的<mapper>标签处,进行GoodsDAO的声明,有两种方式,推荐使用包路径配置
-
<!-- MyBatis使用注解进行开发 --> <!-- (1).加载GoodsDAO接口类 --> <!-- <mapper class="com.imooc.mybatis.dao.GoodsDAO"/>--> <!-- (2).加载com.imooc.mybatis.dao该路径下的所有dao资源 --> <package name="com.imooc.mybatis.dao"/>
-
-
测试脚本
-
//测试注解查询接口 @Test public void testGoodsDAOSelect() throws Exception { SqlSession session = null; try { session = MybatisUtils.openSession(); GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class); List<Goods> list = goodsDAO.selectGoodsByPrice(10f, 100f, 10); for (Goods goods : list){ System.out.println(goods.getTitle()); } }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); } }
-
-
注解方式的插入操作
-
顶入插入方法
-
//数据插入,并获取插入的goods_id(这是自增的数据字段) @Insert("insert into `babytun`.`t_goods`(`title`,`sub_title`,`original_cost`,`current_price`,`discount`,`is_free_delivery`,`category_id`) values (#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery},#{categoryId})") //下面的内容同xml文件中的selectKeys等价 @SelectKey(statement = "select last_insert_id()",before = false, keyProperty = "goodsId", resultType = Integer.class) public int insert(Goods goods);
-
-
测试
-
//测试注解插入接口 @Test public void testGoodsDAOInsert() throws Exception { SqlSession session = null; try { session = MybatisUtils.openSession(); GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class); Goods goods = new Goods(); goods.setTitle("测试Title"); goods.setSubTitle("测试subTitle"); goods.setOriginalCost(1000f); goods.setCurrentPrice(1200f); goods.setDiscount(0f); goods.setIsFreeDelivery(1); goods.setCategoryId(2001); Integer num = goodsDAO.insert(goods); session.commit();//提交事务数据 System.out.println("插入成功条数为:"+ num); System.out.println("插入的goodsId为:"+goods.getGoodsId()); }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); }
-
注解方式进行结果映射
-
定义接口方法
-
//使用注解进行结果映射resultMap @Select("select * from t_goods") //xml文件中结果映射 /** * <!-- 结果映射 --> * <resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO"> * <!-- 设置主键与属性映射--> * <id property="goods.goodsId" column="goods_id"></id> * <!-- 设置非主键字段与属性映射 --> * <result property="goods.title" column="title"></result> * <result property="goods.currentPrice" column="current_price"></result> * </resultMap> */ //下面的注解内容,等价于<resultMap>标签内容 @Results({ //id,主键 @Result(column = "goods_id",property = "goodsId",id = true), //<result> @Result(column = "title", property = "title"), @Result(column = "current_price", property = "currentPrice") }) public List<GoodsDTO2> selectAll();
-
-
测试
-
//测试注解方式进行结果映射 @Test public void testSelectAll2() throws Exception { SqlSession session = null; try { session = MybatisUtils.openSession(); GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class); List<GoodsDTO2> list = goodsDAO.selectAll(); System.out.println(list.size()); }catch (Exception e){ throw e; }finally { MybatisUtils.closeSession(session); } }
-