1、MyBatis 是优秀的持久层框架
2、MyBatis 使用 XML 将 SQL 与程序解耦,便于维护
3、MyBatis 学习简单,执行高效,是 JDBC 的延伸
一、mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--goods_id => goodsId 驼峰命名转换-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--设置默认指向的数据库-->
<environments default="dev">
<!--配置环境,不同的环境不同的id名字-->
<!--开发环境-->
<environment id="dev">
<!--采用JDBC方式对数据库事务进行commit/rollback-->
<transactionManager type="JDBC"></transactionManager>
<!--采用连接池方式管理数据库连接-->
<!--如果 type="POOLED", 代表使用连接池,close则是将连接回收到连接池中-->
<!--如果 type="UNPOOLED", 代表直连,close会调用Connection.close()方法关闭连接-->
<dataSource type="POOLED">
<!--数据库驱动-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--数据库连接字符串-->
<property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=ture&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<!--数据库用户名-->
<property name="username" value="root"/>
<!--数据库密码-->
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--生产环境-->
<environment id="prd">
<!--采用JDBC方式对数据库事务进行commit/rollback-->
<transactionManager type="JDBC"></transactionManager>
<!--采用连接池方式管理数据库连接-->
<dataSource type="POOLED">
<!--数据库驱动-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--数据库连接字符串-->
<property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=ture&characterEncoding=UTF-8"/>
<!--数据库用户名-->
<property name="username" value="root"/>
<!--数据库密码-->
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
二、初始化工具类 MyBatisUtils
/**
* MyBatis工具类,创建全局唯一的 SqlSessionFactory 对象
*/
public class MyBatisUtils {
// 利用 static(静态) 属于类不属于对象,且全局唯一
private static SqlSessionFactory sqlSessionFactory = null;
// static块用于初始化静态变量
// 利用静态块在初始化类时实例化 sqlSessionFactory 对象
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
// 初始化错误时,通过抛出 ExceptionInInitializerError 通知调用者
throw new ExceptionInInitializerError(e);
}
}
/**
* 创建一个新的 SqlSession 对象
* @return SqlSession对象
*/
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
/**
* 释放一个有效的 SqlSession 对象
* @param session 准备释放的 SqlSession 对象
*/
public static void closeSession(SqlSession session) {
if (session != null) {
session.close();
}
}
}
三、MyBatis 数据操作
- 1、创建实体类(Entity)
- 2、创建Mapper XML
- 3、编写<select> SQL标签
- 4、开启驼峰命名映射
- 5、新增<mapper>
- 6、SqlSession执行select语句
(一)数据查询
1、无参数查询
<select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods order by goods_id desc limit 10
</select>
2、单参数查询
<!--单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数-->
<select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods where goods_id = #{ value }
</select>
// 测试用例
@Test
public void testSelectOne() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 1602);
System.out.println(goods.getTitle());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
3、多参数查询
<!--多参数传递,使用parameterType指定Map接口,SQL中#{key}提取参数-->
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods
where current_price between #{ min } and #{ max }
order by current_price
limit 0, #{ limit }
</select>
// 测试用例
@Test
public void testSelectByPriceRange() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
HashMap map = new HashMap<>();
map.put("min", 100);
map.put("max", 500);
map.put("limit", 10);
List<Goods> list = sqlSession.selectList("goods.selectByPriceRange", map);
for (Goods g : list) {
System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
4、获取多表关联查询结果
<!--利用LinkedHashMap保存多表关联结果(使用Map查询结果没有顺序)
MyBatis会将每一条记录包装为LinkedHashMap对象
key是字段名,value是字段对应的值,字段类型根据标结构进行自动判断
优点:易于扩展,易于使用
缺点:太过灵活,无法进行编译时检查
-->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
select t.*, c.category_name
from t_goods t join t_category c
where t.category_id=c.category_id;
</select>
// 测试用例
@Test
public void testSelectGoodsMap() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<Map> list = sqlSession.selectList("goods.selectGoodsMap");
for (Map map :
list) {
System.out.println(map);
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
5、 ResultMap结果映射
<!--结果映射-->
<resultMap id="rsGoods" type="com.imooc.mybatis.dto.GoodsDTO">
<!--设置主键字段与属性映射-->
<id property="goods.goodsId" column="goods_id"></id>
<!--设置非主键字段与属性映射-->
<result property="goods.title" column="title"></result>
<result property="goods.originalCost" column="original_cost"></result>
<result property="goods.currentPrice" column="current_price"></result>
<result property="goods.discount" column="discount"></result>
<result property="goods.isFreeDelivery" column="is_free_delivery"></result>
<result property="goods.categoryId" column="category_id"></result>
<result property="category.categoryId" column="category_id"></result>
<result property="category.categoryName" column="category_name"></result>
<result property="category.parentId" column="parent_id"></result>
<result property="category.categoryLevel" column="category_level"></result>
<result property="category.categoryOrder" column="category_order"></result>
</resultMap>
<select id="selectGoodsDTO" resultMap="rsGoods">
select t.*, c.*
from t_goods t join t_category c
where t.category_id=c.category_id;
</select>
// 测试用例
@Test
public void testSelectGoddsDTO() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<GoodsDTO> list = sqlSession.selectList("goods.selectGoodsDTO");
for (GoodsDTO g : list) {
System.out.println(g.getGoods().getTitle());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
6、 动态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 < #{ currentPrice }
</if>
</where>
</select>
// 测试用例
@Test
public void testDynamicSQL() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Map map = new HashMap<>();
map.put("category_id", 44);
map.put("currentPrice", 500);
List<Goods> list = sqlSession.selectList("goods.dynamicSQL", map);
for (Goods g : list) {
System.out.println(g.getTitle());
}
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
7、OneToMany对象关联查询
<!--
resultMap可用于说明一对多或者多对一的映射逻辑
id 是 resultMap 属性引用的标志
type 指向One的实体(Goods)
-->
<resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
<!--映射goods对象的主键到goods_id字段-->
<id column="goods_id" property="goodsId"></id>
<!--
collection的含义是,在
select * from t_goods limit 0,10 得到结果后,对所有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,10;
</select>
// 测试用例
@Test
public void testOneToMany() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<Goods> list = sqlSession.selectList("goods.selectOneToMany");
for (Goods g : list) {
System.out.println(g.getTitle() + ":" + g.getGoodsDetails().size());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
8、ManyToOne对象关联查询
<resultMap id="rmGoodsDetail" type="com.imooc.mybatis.entity.GoodsDetail">
<id column="gd_id" property="gdId" />
<association property="goods" select="goods.selectById" column="goods_id" />
</resultMap>
<select id="selectManyToOne" resultMap="rmGoodsDetail">
select * from t_goods_detail limit 32,20;
</select>
// 测试用例
@Test
public void testManyToOne() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<GoodsDetail> list = sqlSession.selectList("goodsDetail.selectManyToOne");
for (GoodsDetail g : list) {
System.out.println(g.getGdPicUrl() + ":" + g.getGoods().getTitle());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
(二)数据插入
<!--selectKey标签是通用方案,适用于所有数据库,但编写麻烦
selectKey标签需要明确编写获取最新主键的SQL语句
-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
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 resultType="Integer" keyProperty="goodsId" order="AFTER">
select last_insert_id();
</selectKey>
</insert>
<!--useGeneratedKeys属性只支持"自增主键"数据库,使用简单
useGeneratedKeys属性会自动根据驱动生成对应的SQL语句
-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" useGeneratedKeys="true" keyProperty="goodsId" keyColumn="goods_id">
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>
// 测试用例
@Test
public void testInsert() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = new Goods();
goods.setTitle("测试标题");
goods.setSubTitle("测试副标题");
goods.setOriginalCost(12.f);
goods.setCurrentPrice(1231.f);
goods.setDiscount(0.1f);
goods.setIsFreeDelivery(1);
goods.setCategoryId(1);
int num = sqlSession.insert("goods.insert", goods);
sqlSession.commit();
System.out.println(goods.getGoodsId());
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
(三)数据更新
<update id="update" parameterType="com.imooc.mybatis.entity.Goods">
update t_goods
set
title=#{title},
sub_title=#{subTitle},
original_cost=#{originalCost},
current_price=#{currentPrice},
discount=#{discount},
is_free_delivery=#{isFreeDelivery},
category_id=#{categoryId}
where goods_id=#{goodsId};
</update>
// 测试用例
@Test
public void testUpdate() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 740);
goods.setTitle("更新测试商品标题");
int num = sqlSession.update("goods.update", goods);
sqlSession.commit();
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
(四)数据删除
<delete id="delete" parameterType="Integer">
delete from t_goods where goods_id=#{value}
</delete>
// 测试用例
@Test
public void testDelete() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
int num = sqlSession.delete("goods.delete", 740);
sqlSession.commit();
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
四、MyBatis 预防SQL注入攻击
SQL注入
是指攻击者利用SQL漏洞
,绕过系统约束,越权获取数据的攻击方式
MyBatis两种传值方式
-
${}
文本替换,未经任何处理对SQL
文本替换
-
#{}
预编译传值,使用预编译传值可以预防SQL注入
五、MyBatis工作流程
六、MyBatis日志管理
- 日志文件是用于记录系统操作事件的记录文件或文件集合
- 日志保存历史数据,是诊断问题以及理解系统活动的重要依据
1、在 resources
目录下新建logback.xml
文件(文件名为固定写法),文件内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--
日志输出级别(优先级高到低)
error:错误 - 系统的故障日志
warn:警告 - 存在风险或使用不当的日志
info:一般性消息
debug:程序内部用于调试信息
trace:程序运行的跟踪信息
-->
<root level="debug">
<appender-ref ref="console" />
</root>
</configuration>
七、MyBatis二级缓存
1、一级缓存默认开启,缓存范围 SqlSession 会话
2、二级缓存手动开启,属于范围 Mapper Namespace
1、二级缓存运行规则
- 1、二级缓存后默认所有查询操作均使用缓存
- 2、写操作
commit
提交时对该 namespace
缓存强制清空
- 3、配置
userCache=false
可以不用缓存
- 4、配置
flushCache=true
代表强制清空缓存
@Test
public void testLv1Cache() throws Exception {
// 一级缓存默认开启,缓存范围 SqlSession 会话
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 1603);
Goods goods1 = sqlSession.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode() + ":" + goods1.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 1603);
// 写操作 commit 提交时对该 namespace 缓存强制清空
sqlSession.commit();
Goods goods1 = sqlSession.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode() + ":" + goods1.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
<!--
开启二级缓存
eviction:是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
1、LRU - 最近最久未使用:移除最长时间不被使用的对象(比如size位512,当缓存超过512时,移除这512个钟最长时间不被使用的对象)
2、LFU - 最近最少使用:移除最近访问频率最低的对象
3、FIFO - 先进先出:按对象进入缓存的顺序来移除它们
4、SOFT - 软引用:移除基于垃圾收集器状态和软引用规则的对象
5、WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象
flushInterval:代表间隔多长时间自动清空缓存,单位毫秒,600000 = 10分钟
size:缓存存储上限,用于保存对象或集合(1个集合算1个对象)的数量上限
readOnly:设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
设置为false,代表每次取出的是缓存对象的"副本",每一次取出的对象都是不同的,这种安全性较高
-->
<!--useCache="false"代表不使用缓存,返回对象为list数据量较大的时候,不建议使用缓存,命中率较低,一般单个对象建议使用缓存-->
<!-- flushCache="true"在sql执行后强制清空缓存,一般在插入数据之后使用-->
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true" />
@Test
public void testLv2Cache() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
八、PageHelper分页插件
官网地址:https://pagehelper.github.io/
1、使用流程
-
1、maven
引入 PageHelper
与 jssqlparser
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.4</version>
</dependency>
-
2、mybatis-config.xml
增加 Plugin
配置
<!--启用Pagehelper分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--设置数据库类型-->
<property name="helperDialect" value="mysql"/>
<!--分页合理化-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
-
3、
代码中使用 PageHelper.startPage()
自动分页
<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
select *
from t_goods
where current_price < 1000
</select>
@Test
public void testSelectPage() throws Exception {
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 g : list) {
System.out.println(g.getTitle());
}
System.out.println("");
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
九、MyBatis批处理
1、批量增加
<!--批量插入-->
<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 tesBatchInsert() throws Exception {
SqlSession sqlSession = null;
try {
long st = new Date().getTime();
sqlSession = 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);
}
sqlSession.insert("goods.batchInsert", list);
sqlSession.commit();
long et = new Date().getTime();
System.out.println("执行时间:" + (et - st) + "毫秒");
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
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 sqlSession = null;
try {
long st = new Date().getTime();
sqlSession = MyBatisUtils.openSession();
List list = new ArrayList();
for (int i = 2674; i < 10750; i++) {
list.add(i);
}
sqlSession.delete("goods.batchDelete", list);
sqlSession.commit();
long et = new Date().getTime();
System.out.println("执行时间:" + (et - st) + "毫秒");
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}