目录
前言
真的是一刻都不敢停下学习的脚步,只想快点基础过完开始使用,前期学习把这些都记录下来,方便后期的使用,本节我们就来学习MyBatis使用。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
这里直接看看百度百科(MyBatis百度百科)
MyBatis中文文档地址(点击查看)
参考文章(参考文章1)
学习内容
MyBatis入门
首先在我们本地MySQL中导入我们测试数据,sql脚本分享地址:
链接:https://pan.baidu.com/s/1yaHBgoLvUkbeK2vPSKZwgw
提取码:of6m
MyBatis工作流程
MyBatis配置使用
- 使用IDEA新建Maven项目,在pom.xml引入依赖;
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
- 在如下文件夹下文件MyBatis的配置文件数据库信息;
- 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>
<!-- properties属性引入外部配置文件 -->
<properties resource="database.properties">
<!-- property里面的属性全局均可使用 -->
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="123456"/>
<!-- 启用默认值特性,这样${}拼接符才可以设置默认值 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
<!-- 环境配置:事务管理器和数据源配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- 映射器 -->
<!-- <mappers>-->
<!-- <mapper resource="com/brave/dao/mapper/UpmsUserMapper.xml" />-->
<!-- </mappers>-->
</configuration>
- database.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/babytun?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
- 编写测试类,测试配置是否正确;
package com.five.mybatis;
import org.junit.Test;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
/**
* Description:
*
* @Author: kk(专业bug开发)
* DateTime: 2022-01-10 21:23
*/
public class MybatisTester {
@Test
public void testSqlSessionFactory() throws IOException {
//1.读取resources下面的mybatis-config.xml文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//2.使用SqlSessionFactoryBuilder创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = null;
try {
//3.通过sqlSessionFactory创建SqlSession
sqlSession = sqlSessionFactory.openSession();
Connection connection = sqlSession.getConnection();
System.out.println(connection); //打印一下创建的连接
} catch (Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null){
//如果使用的是数据库连接池,连接被回收
//如果没有使用数据库连接池,连接直接关闭
sqlSession.close();
}
}
}
}
- 测试结果;
MyBatisUtils工具类
- 上面经过配置我们已经成功测试了MyBatis的使用,但是我们能明显发现一个问题,这样写我们每次操作数据库都需要读取配置文件等重复操作,显然不合适;
- 新建下面的工具类,存放我们的工具类;
package com.five.mybatis.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
/**
* Description: 用于创建一个全局唯一的SqlSessionFactory对象
*
* @Author: kk(专业bug开发)
* DateTime: 2022-01-11 11:17
*/
public class MyBatisUtils {
//利用static修饰,属于类不属于对象,且全局唯一
private static SqlSessionFactory sqlSessionFactory = null;
//利用静态块在初始化类的时候实例化一个SqlSessionFactory对象
static {
Reader reader = null;
try {
//1.读取resources下面的mybatis-config.xml文件
reader = Resources.getResourceAsReader("mybatis-config.xml");
//2.使用SqlSessionFactoryBuilder创建SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
throw new ExceptionInInitializerError(e);
}
}
/**
* 获得SqlSession
*/
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
/**
* 释放sqlSession对象
*/
public static void closeSession(SqlSession sqlSession) {
if (sqlSession != null) {
sqlSession.close();
}
}
}
- 测试类;
MyBatis查询参数传递
- 传递单个参数
<select id="selectById" parameterType="Integer" resultType="com.five.mybatis.entity.Goods">
select * from t_goods where goods_id = #{value}
</select>
@Test
public void testSelectById() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
// 这时候传递一个参数
Goods goods = (Goods) sqlSession.selectOne("goods.selectById", 1606);
System.out.println(goods.getTitle());
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
- 传递多个参数(Map法)
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.five.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 selectByPriceRange() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
// 这时候传递多个参数
Map param = new HashMap();
param.put("min",100);
param.put("max",500);
param.put("limit",10);
List<Goods> list = sqlSession.selectList("goods.selectByPriceRange",param);
for(Goods g: list){
System.out.println(g.getTitle());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
MyBatis多表关联查询
- 使用LinkedHashMap
<!-- 多表关联查询 结果使用Map-->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
select g.*,c.category_name from t_goods g, t_category c
where g.category_id = c.category_id
</select>
@Test
public void selectGoodsMap() {
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) {
e.printStackTrace();
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
- 使用resultMap映射结果
对实体类进行扩展
<!-- 结果映射-->
<resultMap id="rmGoods" type="com.five.mybatis.dto.GoodsDTO">
<!-- 设置主键字段与属性映射-->
<id property="goods.goodsId" column="goods_id"/>
<!-- 设置非主键字段与属性映射-->
<result property="goods.title" column="title"/>
<result property="goods.originalCost" column="original_cost"/>
<result property="goods.currentPrice" column="current_price"/>
<result property="goods.discount" column="discount"/>
<result property="goods.isFreeDelivery" column="is_free_delivery"/>
<result property="goods.categoryId" column="category_id"/>
<result property="categoryName" column="category_name"/>
<result property="test" column="test"/>
</resultMap>
<select id="selectGoodsDTO" resultMap="rmGoods">
select g.*,c.category_name,'1' as test from t_goods g, t_category c
where g.category_id = c.category_id
</select>
@Test
public void testselectGoodsDTO() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<GoodsDTO> list = sqlSession.selectList("goods.selectGoodsDTO");
for(GoodsDTO goodsDTO: list){
System.out.println(goodsDTO.getGoods().getTitle());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
MyBatis写入操作
- 插入
<insert id="insert" parameterType="com.five.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返回最近一次插入的id
<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
@Test
public void testInsert() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = new Goods();
goods.setTitle("测试商品标题");
goods.setSubTitle("测试商品子标题");
goods.setOriginalCost(200.0);
goods.setCurrentPrice(100.0);
goods.setDiscount(0.5);
goods.setIsFreeDelivery(1);
goods.setCategoryId(43);
//返回值表示插入的数据总数
int num = sqlSession.insert("goods.insert", goods);
//提交事务数据
sqlSession.commit();
System.out.println(num);
} catch (Exception e) {
e.printStackTrace();
if(sqlSession != null){
//遇到错误,数据库事务回滚
sqlSession.rollback();
}
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
- 更新和删除
<!--更新数据-->
<update id="update" parameterType="com.five.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>
<!--删除数据-->
<delete id="delete" parameterType="Integer">
delete from t_goods where goods_id = #{value}
</delete>
@Test
public void testUpdate() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
Goods goods = sqlSession.selectOne("goods.selectById", 740);
goods.setTitle("测试更新的标题");
goods.setSubTitle("测试更新子标题");
System.out.println(goods);
int num = sqlSession.update("goods.update", goods);
//提交数据库事务
sqlSession.commit();
System.out.println("更新数据条数:"+ num);
} catch (Exception e) {
e.printStackTrace();
if(sqlSession != null){
//遇到错误,数据库事务回滚
sqlSession.rollback();
}
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
@Test
public void testDelete() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
int num = sqlSession.update("goods.delete", 739);
//提交数据库事务
sqlSession.commit();
System.out.println("删除数据条数:"+ num);
} catch (Exception e) {
e.printStackTrace();
if(sqlSession != null){
//遇到错误,数据库事务回滚
sqlSession.rollback();
}
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
MyBatis防止SQL注入
文档地址:官方文档地址
使用#{}而不使用${}!!!即可避免
Mybatis进阶
MyBatis日志管理
- pom.xml文件引入依赖;
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
-
直接引入以后就会打印日志;
-
自定义日志;
-
logback.xml文件内容;
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/logs" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/Test.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
动态SQL
<!-- 根据Map里面的信息动态组织SQL语句-->
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.five.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 param = new HashMap();
//两个参数和一个参数决定查询语句(动态SQL)
param.put("categoryId", 66);
param.put("currentPrice", 300);
List<Goods> list = sqlSession.selectList("goods.dynamicSQL", param);
for (Goods g : list){
System.out.println(g.getTitle() + '-' + g.getCategoryId() + '-' + g.getCurrentPrice());
}
} catch (Exception e) {
e.printStackTrace();
if(sqlSession != null){
//遇到错误,数据库事务回滚
sqlSession.rollback();
}
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
MyBatis二级缓存机制
- 一级缓存默认开启,缓存范围为SqlSession会话
- 二级缓存手动开启,范围为Mapper Namespace
二级缓存规则
- 二级开启后默认所有查询操作均使用缓存
- 写操作commit提交时对namespace缓存强制清空
- 配置useCache-false可以不用缓存
- 配置flushCache=true代表强制清空缓存
- 测试一级缓存机制
@Test
public void tesLv1Cache(){
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) {
e.printStackTrace();
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
这里我们连续两次通过id查找同一数据,通过打印两次数据的哈希地址比较;通过哈希地址我们可以很清楚的看到,其实MyBatis并没有再执行一次查询,而是将第二次的查询直接指向第一次;
测试一级缓存的范围:
- 测试写操作commit提交时对namespace缓存强制清空
---------------------------------------------分隔-------------------------------------------- - 测试二级缓存机制
<!-- 配置二级缓存机制-->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
eviction属性是清除策略,默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
测试使用:
tip:如果查询的操作数据量很大,我们不使用缓存!!
MyBatis处理一对多关系
这里我们的数据库中有一张商品表和一张商品详情表,很明显这两张表之间的关系是一对多的关系,当我们利用MyBatis进行查询操作的时候如果进行呢,下面我们一起来看一下。
- 首先将我们的商品详情表映射为实体类GoodsDetail;
- goods.xml
<!-- resultMap可以用于说明一对多或者是多对一的映射逻辑-->
<!-- id是resultMap属性引用的标志-->
<!-- type指向1对应的实体-->
<resultMap id="rmGoods1" type="com.five.mybatis.entity.Goods">
<!-- 映射goods对象的主键到goods_id字段-->
<id column="goods_id" property="goodsId"/>
<!-- collection的含义是当查询语句得到结果后,对所有的Goods对象遍历得到goods_id字段值-->
<!-- 带入到goodsDetail命名空间的findByGoodsId的SQL语句中查询-->
<!-- 将得到的商品详情信息赋值给goodsDetails List对象-->
<collection property="goodsDetail" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,1
</select>
goods_detail.xml
<select id="selectByGoodsId" parameterType="Integer" resultType="com.five.mybatis.entity.GoodsDetail">
select *
from t_goods_detail
where goods_id = #{value}
</select>
- 编写测试类;
@Test
public void testOneToMany() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.openSession();
List<Goods> list = sqlSession.selectList("goods.selectOneToMany");
for (Goods g : list){
System.out.println(g.getTitle() + '-' + g.getGoodsDetail());
}
} catch (Exception e) {
e.printStackTrace();
if(sqlSession != null){
//遇到错误,数据库事务回滚
sqlSession.rollback();
}
} finally {
MyBatisUtils.closeSession(sqlSession);
}
}
}
总结
本节我们一起学习了MyBatis的基础和进阶使用,相信通过这次的使用会让你对于MyBatis有一个更深的认识,文中的原理不是很多,但是所有涉及到原理的地方都在代码中添加了注释,加油~