MyBatis历史
MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。
iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
特性 => 半自动化持久层框架
其它持久化层技术
JDBC
1. SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
2. 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
3. 代码冗长,开发效率低
Hibernate 和 JPA (全自动框架)
1. 操作简便,开发效率高
2. 程序中的长难复杂 SQL 需要绕过框架
3. 内部自动生产的 SQL,不容易做特殊优化
4. 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
5. 反射操作太多,导致数据库性能下降
MyBatis
1. 轻量级,性能出色
2. SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
3. 开发效率稍逊于HIbernate,但是完全能够接受
初始准备
MySQL
pojo -> User.java
package com.nuo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @author nuo
* @version 1.0
* @description: TODO
* @date 2022/2/14 20:01
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private int id;
private String name;
private String pwd;
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
}
引入相关 jar
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.29version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
dependencies>
创建数据库的配置文件 Db.properties
MySQL 不同版本的注意事项
1. 驱动类driver-class-name
MySQL 5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver
MySQL 8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver
2. 连接地址url
MySQL 5版本的url:
jdbc:mysql://localhost:3306/mybatis
MySQL 8版本的url:
jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
否则运行测试用例报告如下错误:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8
username=root
password=123456
创建MyBatis的核心配置文件 mybatis-config.xml
可去官网 v 一份过来,咳咳
官网链接 => MyBatis
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="Db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<typeAliases>
<typeAlias type="com.nuo.pojo.User" alias="user"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/nuo/dao/UserMapper.xml"/>
mappers>
configuration>
创建 mapper 接口
UserMapper.java
public interface UserMapper {
// 添加一个 user
int addUser();
}
创建MyBatis的映射文件
UserMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuo.dao.UserMapper">
<insert id="addUser">
insert into user (id, name, pwd)
values (1, 'nuo', '123456');
insert>
mapper>
通过junit测试
public class UserMapperTest {
@Test
public void addUser() throws IOException {
//读取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
//SqlSession sqlSession = sqlSessionFactory.openSession();
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// int res = sqlSession.insert("com.nuo.dao.UserMapper.addUser") 不推荐
//通过代理模式创建UserMapper接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.addUser();
if (res > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
sqlSession.close();
}
}
Mybatis 执行流程
1.Resource 获取配置文件输入流
2.实例化 SqlSessionFactoryBuilder
3.build(inputStream) (解析配置文件) -> SqlSessionFactory
4.transactional 事务管理器
5.创建 executor 执行器
6.sqlSessionFactory.openSession() 获取 sqlSession
7.实现 CRUD (可能回滚事务 ------> 4)
8.查看是否执行成功 (失败 回滚事务 ------> 4)
9.提交事务
10.关闭资源
加入log4j日志功能
日志的级别:
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
jar
<dependencies>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
log4j 的配置文件 => log4j.xml
DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
layout>
appender>
<logger name="java.sql">
<level value="debug" />
logger>
<logger name="org.apache.ibatis">
<level value="info" />
logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
root>
log4j:configuration>
package com.nuo.dao;
import com.nuo.pojo.User;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
// 添加一个 user
int addUser(User user);
// 删除一个用户
int deleteUser(User user);
// 按照 id 批量删除 user
int deleteUsers(@Param("ids") String ids);
// 修改用户
int updateUser(User user);
// 根据 id 查询 用户
User getUserById(int id);
// 模糊查询
List<User> getUserListByLike(String value);
// 获取全部用户
List<User> getUserList();
// 测试使用 map
int addUserTestMap(Map<String, Object> map);
// 单条查询结果用 map 接收 k->v 字段->值
// {name=诺, id=1, pwd=123456}
//Map getUserByIdToMap(@Param("id") int id);
// 单条查询结果用 map 接收 k->v 对应字段的值->所有字段键值对
// {1={name=诺, id=1, pwd=123456}}
@MapKey("id")
Map<String, Object> getUserByIdToMap(@Param("id") int id);
// 多条查询结果用 map 接收
// [{name=诺, id=1, pwd=123456}, {name=nuo, id=2, pwd=123123}, {name=aaa, id=3, pwd=123123}, {name=test, id=5, pwd=aaaaaaa}, {name=nnnnn, id=6, pwd=123123}]
// List
// {1={name=诺, id=1, pwd=123456}, 2={name=nuo, id=2, pwd=123123}, 3={name=aaa, id=3, pwd=123123}, 5={name=test, id=5, pwd=aaaaaaa}, 6={name=nnnnn, id=6, pwd=123123}}
@MapKey("id")
Map<String, Object> getUserListToMap();
// MyBatis 动态设置表名需要使用${},而不能使用#{}
// 因为 ${} => 不带'' #{} => 含''
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nuo.dao.UserMapper">
<insert id="addUser" useGeneratedKeys="true" keyProperty="id" parameterType="com.nuo.pojo.User">
insert into user (id, name, pwd)
values (null, #{name}, #{pwd});
insert>
<insert id="addUserTestMap" parameterType="map">
insert into user (id, name, pwd)
values (#{userId}, #{userName}, #{userPwd});
insert>
<delete id="deleteUser" parameterType="com.nuo.pojo.User">
delete
from user
where id = #{id};
delete>
<delete id="deleteUsers">
delete
from user
where id in (${ids});
delete>
<update id="updateUser" parameterType="com.nuo.pojo.User">
update user
set name=#{name},
pwd=#{pwd}
where id = #{id};
update>
<select id="getUserById" parameterType="int" resultType="com.nuo.pojo.User">
select *
from user
where id = #{id};
select>
<select id="getUserListByLike" parameterType="String" resultType="com.nuo.pojo.User">
select *
from user u
where u.name like concat('%', #{value}, '%');
select>
<select id="getUserList" resultType="com.nuo.pojo.User">
select *
from user;
select>
<select id="getUserByIdToMap" resultType="map">
select *
from user u
where u.id = #{id};
select>
<select id="getUserListToMap" resultType="map">
select *
from user;
select>
mapper>
注意:
1. 查询的标签select必须设置属性 `resultType` 或 `resultMap`,用于设置实体类和数据库表的映射关系
1. resultType:自动映射,用于属性名和表中字段名一致的情况
2. resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
2. 常见的使用${}的情况:
1.当sql中表名是从参数中取的情况
2.批量删除的 where id in (ids)
3.order by排序语句中, 因为order by 后边必须跟字段名,这个字段名不能带引号,如果带引号会被识别会字符串,而不是字段。
demo
查询
1)一对多
集合 => collection
2)多对一
对象 => association
Student
package com.nuo.pojo;
import lombok.Data;
import org.apache.ibatis.type.Alias;
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
Teacher
package com.nuo.pojo;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.util.List;
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
按照查询嵌套处理
思路
1.查询获取所有学生信息
2.根据查询出来学生的 tid ,寻找对应的老师 => 子查询
<select id="getStudentList1" resultMap="StudentMap1">
select *
from student;
</select>
<resultMap id="StudentMap1" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select *
from teacher
where id = #{id};
</select>
按照结果嵌套处理
思路
多表查询
<select id="getStudentList2" resultMap="StudentMap2">
select s.id sid,s.name sname,s.tid,t.name tname
from student s,teacher t
where s.tid = t.id;
select>
<resultMap id="StudentMap2" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
association>
resultMap>
javaType & ofType
javaType => pojo 中的属性类型
ofType => pojo 实体类 (泛型)
if 的使用
<select id="queryBlogIf" parameterType="map" resultType="blog">
select *
from blog b
<where>
<if test="title != null">
title like concat('%',#{title},'%')
</if>
<if test="author != null">
and author like concat('%',#{author},'%')
</if>
</where>
</select>
【PS】:
1. 若 where 标签中的 if 条件都不满足,则where标签没有任何功能, 即不会添加 where 关键字
2. 若 where 标签中的 if 条件满足,则 where 标签会自动添加 where 关键字,并将条件最前方多余的 and 去掉
注意:where 标签不能去掉条件最后多余的 and
trim
trim 用于去掉或添加标签中的内容
常用属性:
prefix :在 trim 标签中的内容的前面添加某些内容
prefixOverrides:在 trim 标签中的内容的前面去掉某些内容
suffix :在 trim 标签中的内容的后面添加某些内容
suffixOverrides:在 trim 标签中的内容的后面去掉某些内容
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
ename = #{ename} and
if>
<if test="age != '' and age != null">
age = #{age} and
if>
<if test="sex != '' and sex != null">
sex = #{sex}
if>
trim>
select>
choose(when,otherwise)
类似 Java 中的 switch
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select *
from blog b where
<choose>
<when test="title != null">
title like concat('%',#{title},'%')
</when>
<when test="author != null">
author like concat('%',#{author},'%')
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</select>
foreach (集合遍历) 【通常在构建 in 条件语句的时候使用】
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
sql片段 (代码复用)
类似方法调用
=> 1.最好基于单表来定义 sql 片段
=> 2.不要存在 where 标签
<sql id="if-title-author">
<if test="title != null">
title like concat('%',#{title},'%')
</if>
<if test="author != null">
and author like concat('%',#{author},'%')
</if>
</sql>
<select id="queryBlogIf" parameterType="map" resultType="blog">
select *
from blog b
<where>
<include refid="if-title-author"/>
</where>
</select>
一级缓存
生命周期 : SqlSession
0.关于 SqlSession
每个线程都应该有它自己的 SqlSession 实例
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession
实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比
如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域
中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次
都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
1.如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中
的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
2.当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
3.可手动清空缓存
sqlSession.clearCache();
4.mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使
用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
eg.-1242243203:1146242777:winclpt.bean.userMapper.getUser:0:2147483647:select * from user where id=?:19
使一级缓存失效的四种情况:
1) 不同的SqlSession对应不同的一级缓存
2) 同一个SqlSession但是查询条件不同
3) 同一个SqlSession两次查询期间执行了任何一次增删改操作
4) 同一个SqlSession两次查询期间手动清空了缓存
二级缓存 (全局缓存) => 基于 namespace
生命周期 : SqlSessionFactory
1) 步骤 :
1.mybatis-config.xml 中显示的开启全局缓存 (虽然它默认是开启的,显示开启可提高可读性)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2....Mapper.xml 中配置 '<cache/>'
eg. <mapper namespace="com.nuo.dao.UserMapper">
<cache/>
<select>...</select>
</mapper>
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/>
缓存的收回策略 刷新间隔 (ms) 是否只读 引用数目
默认 LRU 不清空 false 1024
eviction :缓存的收回策略
LRU (最近最少使用的) 移除最长时间不被使用的对象,这是默认值
FIFO (先进先出) 按对象进入缓存的顺序来移除它们
SOFT (软引用) 移除基于垃圾回收器状态和软引用规则的对象
WEAK (弱引用) 更积极地移除基于垃圾收集器状态和弱引用规则的对象
readOnly:是否只读
true:只读,设置为true后,mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据,因此为了加快获取速度, 一般会直接将数据在缓存中的引用交给用户,虽然速度快,但不安全。
false:非只读,设置为false后,mybatis认为获取的数据可能会被修改,因此会利用序列化和反序列化的技术克隆一份新的数据给你,虽然速度慢,但安全。
3.给 POJO 类实现序列化接口
public class User implements Serializable {...}
2)在开启二级缓存时,查出来的数据默认先存储在一级缓存中,当有 SqlSession关闭 时,它里面一级缓存中的数据就会被存
储到 Mapper 的二级缓存中,这样该Mapper中的其他会话执行了相同方法时,就会在二级缓存中找到匹配的数据,如果没有找到,才
会去数据库中查找。注意只有在该会话关闭时,它一级缓存中的数据才会被刷到二级缓存中。另外如果只是开启二级缓存的全局(config)
开关,而会话(student)没有开启二级缓存,查询时也不会在二级缓存中查询。
查询顺序
二级缓存 ----> 一级缓存 ----> 数据库
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
自定义缓存
EhCache
一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布
式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet
过滤器,支持REST和SOAP api等特点。
使用步骤
1.导包
org.mybatis.caches
mybatis-ehcache
1.2.1
2.配置
3.创建 .xml 配置文件(可自定义)
配置说明 :
ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
ehcache>
mybatis用于生成代码的配置文件 generatorConfig.xml
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8"
userId="root"
password="xiaonuo123..">
<property name="nullCatalogMeansCurrent" value="true"/>
jdbcConnection>
<javaModelGenerator targetPackage="com.nuo.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
javaModelGenerator>
<sqlMapGenerator targetPackage="com.nuo.mapper" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.nuo.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
javaClientGenerator>
<table tableName="user" domainObjectName="User"/>
context>
generatorConfiguration>
pom.xml 中配置生成器
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.0version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xmlconfigurationFile>
<verbose>trueverbose>
<overwrite>trueoverwrite>
configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.27version>
dependency>
dependencies>
plugin>
plugins>
build>
jar
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.0version>
dependency>
mybatis-config.xml 中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
相关参数
pageSize:每页显示的条数
pageNum:当前页的页码
index:当前页的起始索引,index=(pageNum-1)*pageSize
count:总记录数
totalPage:总页数
totalPage = count / pageSize;
相关返回数据
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]