MyBatis深度解析与实战指南:细节完整,从入门到精通
MyBatis深度解析与实战指南:细节完整,从入门到精通
整理这份笔记,是因为学习 MyBatis 时发现很多教程要么只讲基础 CRUD,要么直接跳到 Spring 整合,对 MyBatis 核心特性讲解不全面,基础部分也不够完整。实际开发中,很多问题在教程中找不到答案,比如二级缓存配置后性能提升不明显、动态 SQL 拼接后查询结果不符合预期、事务管理不当导致数据不一致等。
这些问题让我意识到,掌握 MyBatis 不仅要知其然,更要知其所以然。于是,我决定边学边记录,用最直白的语言和图示梳理核心知识点,注重细节的完整性和实用性,希望能帮你少走弯路,真正掌握 MyBatis 的精髓。
一、Mybatis的介绍
- Myba是一款优秀的持久层框架
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程,减少了代码的冗余,减少程序员的操作。
二、Mybatis的构建
首先是整体结构
1、首先是util,主要用于的是构建一个SqlSessionFactory的框架
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
static部分可以在Mybatis3中文文档中查找
2、然后是pojo,主要是用于设置一个user的,用于规定类型
public class user {
private int id;
private String name;
private String pwd;
}
实现里面的无参,有参,set,toStrring方法即可
最后是dao部分的内容,主要是用于实现方法,设置增删改查可以在这里面进行
dao里面有两部分,Mapper是一个接口,用于设置需要实现类的方法,另一个用于数据库操作的功能实现
3、dao部分
xml部分
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chen.dao.Mapper">
<select id="getUserList" resultType="com.chen.pojo.user">
select * from mybatis.user;
</select>
<select id="getUserId" resultType="com.chen.pojo.user" parameterType="int" >
select * from mybatis.user where id=#{id};
</select>
<insert id="addUser" parameterType="com.chen.pojo.user">
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
<update id="UpdateUser" parameterType="com.chen.pojo.user">
update mybatis.user set name=#{name},pwd=#{pwd} where id= #{id}
</update>
</mapper>
namespce用于dao中的接口部分
id用于实现的方法
resultTypr用于返回的类型
parameterType用于规范文件的类型
4、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核心配置文件-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&characterEncoding=UTF-8&useUnicode=true&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/chen/dao/UserMapper.xml"/>
</mappers>
</configuration>
5、测试类(可以在user类快捷键ctrl+shift+T)
@Test
public void testGetUserList() {
SqlSession sqlSession= MaybaitsUtils.getSqlSession();
Mapper userDao = sqlSession.getMapper(Mapper.class);
List<user> userList = userDao.getUserList();
for (user user : userList) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void testgetId(){
SqlSession sqlSession = MaybaitsUtils.getSqlSession();
Mapper mapper = sqlSession.getMapper(Mapper.class);
user user = mapper.getUserId(2);
System.out.println(user);
}
@Test
public void testAdd() {
SqlSession sqlSession = MaybaitsUtils.getSqlSession();
try {
Mapper mapper = sqlSession.getMapper(Mapper.class);
int res = mapper.addUser(new user(6, "张胜男", "1234588"));
if (res > 0) {
System.out.println("插入成功");
sqlSession.commit();
} else {
System.out.println("插入失败");
}
} catch (Exception e) {
e.printStackTrace();
// 处理异常,可能需要回滚事务
// sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testdelete(){
SqlSession sqlSession = MaybaitsUtils.getSqlSession();
Mapper mapper = sqlSession.getMapper(Mapper.class);
int row = mapper.deleteUser(5);
if(row>0)
System.out.println("删除成功");
sqlSession.commit();
sqlSession.close();
}
commit用于预编译,可以在前面的Mybatis中加入true,可以自动预编译
map的使用
int addUser2(Map<String,Object> map);
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,pwd) values (#{userid},#{password})
</insert>
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("userid",4);
map.put("password","123321");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
三、核心配置文件
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
可以在Mybatis3中文文档上查找相关用法
1、properties(属性)
可以通过设置properties来引用配置文件
eg:
<!--引入外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="pwd" value="123123"/>
</properties>
可以不写中的内容,这样写了该值会二次覆盖资源里面的内容
resource代表着内置资源的位置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&characterEncoding=UTF-8&useUnicode=true&serverTimezone=GMT
name=root
password=123456
2、typeAliases(别名设置)
<!--可以给实体类起别名-->
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User" />
</typeAliases>
这个为直接设置别名
<!--可以给实体类起别名-->
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
这个为默认首字母小写的类名
注解:@Alias(“user”)实体类可以直接设置
3、setting(其他效果在这里面进行实现)
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
这个就是设置日志的,可以让日志输出更加的完整
4、mapper的三种用法
<mappers>
<mapper resource="chen/dao/UserMapper.xml"/>
<!-- 接口名与资源名必须一致,放在同一个包下面-->
<!-- <mapper class="chen.dao.UserMapper.xml"/>-->
<!-- 接口名与资源必须一致,放在同一个包下面-->
<!-- <package name="chen.dao"/>-->
</mappers>
class和name都需要接口名与资源名必须一致,放在同一个包下面
四、ResultMap的使用
ResultMap主要是用于类名中有别名存在又不能直接更改类之中的内容,就可以通过resultmap进行改名
<resultMap id="users" type="user">
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserList" resultMap="users">
select * from mybatis.user;
</select>
resultMap对应id,types对应要改的类型
column代表mysql数据库中的名字,property代表类中的名字
五、日志的使用
1、STDOUT_LOGGING标准日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
2、Log4j
原理:以控制日志信息输送的目的地是控制台、文件、GUI组件
设置先关资源
相关配置
log4j.rootLogger = DEBUG,console ,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/kuang.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
可以自定义进行设置
@Test
public void testLogger(){
logger.info("info:系统进入info");
logger.debug("debug:系统进入debug");
logger.error("eroor:系统报错");
}
必须使用下面内容
static Logger logger = Logger.getLogger(UserDaoTest.class);
最后可以生成一个log日志文文件
六、Limit分页的使用
1、sql中的分页
//分页
List<User> getUserByLimit(Map<String,Integer> map);
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
2、RowBounds分页
//分页2
List<User> getUserByRowBounds();
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(0, 2);
//通过java代码层面实现分页
List<User> userList = sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
七、注解开发
优点:简单代码更加便捷,可以在接口中直接完成,不需要xml中进行操作
缺点:负载的代码还是无法进行,仍然需要不注解的使用方法
1、基础增删改查
@Select(" select * from mybatis.user")
List<user> getListuser();
@Select("select * from mybatis.user where id=#{id}")
user getid(@Param("id") int id);
@Insert("insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd})" )
int addUser(user user);
@Delete("delete from mybatis.user where id = #{id}")
int deleteUser(@Param("id") int id);
@Update("update mybatis.user set name=#{name},pwd=#{pwd} where id= #{id}")
int updateuser(user user);
mybatis-config.xml需要改配置
<!--绑定接口!-->
<mappers>
<mapper class="com.kuang.dao.UserMapper" />
</mappers>
改成class以应对没有resource
测试类不进行改变
@Param设置基础类型,以这里面的内容为优先
2、其他类型
lombok的设置,可以从maven中导入
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
</dependencies>
@Data
@AllArgsConstructor
@NoArgsConstructor
Data包含基本所有的基本类型(无参构造、get、set、toString、hashCode、equals)
AllArgsConstructor有参构造
NoArgsConstructor无参构造
八、一对一和一对多
概念:
- 多个学生,对应一个老师
- 对于学生而言,关联–多个学生,关联一个老师【多对一】
- 对于老师而言,集合–一个老师,有很多个学生【一对多】
1、association一对一
两个类进行表示
student表示学生类
private int id;
private String name;
private teacher teacher;
student类中的teahcer属性跟下方的teacher类是有联系的,这个也代表了数据库中的tid
teacher老师类
public class teacher {
private int id;
private String name;
}
方法一:
原理:在两个类中分别进行查找,再将两个类查找的结果进行结合
<mapper namespace="chen.dao.studentMapper">
<resultMap id="student" type="chen.pojo.student">
<id property="id" column="id"></id>
<result property="name" column="name"/>
<association property="teacher" column="tid" select="getTeacher" javaType="chen.pojo.teacher"/>
</resultMap>
<select id="getStudent" resultMap="student">
select * from Mybatis.student
</select>
<select id="getTeacher" resultType="chen.pojo.teacher">
select * from Mybatis.teacher
</select>
</mapper>
namespace是和接口相互联系的,填写对应接口的位置
property指类中的名字
column指数据库中的名字
select指要选择的名字和下方id对应
javaType和resultType对应
方法二:
原理主:要在于数据库的相关设计
<!--按照结果嵌套处理 -->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from mybatis.student s,mybatis.teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="chen.pojo.teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
2、collection一对多
方法一:
teacher类:
public class teacher {
private int id;
private String name;
private List<student> students;
}
teacher类的xml
<mapper namespace="chen.dao.teacherMapper">
<resultMap id="Teacher" type="chen.pojo.teacher">
<id property="id" column="id"></id>
<result property="name" column="name"/>
<collection property="students" select="studentMapper" javaType="Arraylist" ofType="chen.pojo.student" column="id"/>
</resultMap>
<select id="getTeacher" resultMap="Teacher">
select * from Mybatis.teacher;
</select>
<select id="studentMapper" resultType="chen.pojo.student">
select * from Mybatis.student;
</select>
</mapper>
一对多有五个属性
1、property=“students” 类中有的
2、select=“studentMapper” 选择的名字
3、javaType="Arraylist 数列类型
4、ofType=“chen.pojo.student” 设定约束的泛型
5、column=“id” 数据库中的代表
方法二:
<!-- 按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
SELECT s.id sid,s.name sname,t.name tname,t.id,tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- 复杂的属性,我们需要单独处理 对象:association 集合:collection
javaType="" 指定属性的类型!
集合中的泛型信息,我们使用ofType获取
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
九、动态SQL
原来:动态SQL就是 指根据不同的条件生成不同的SQL语句
1、if语句
可以选择的进行添加方法
List<blog> queryblogs(Map map);
<select id="queryblogs" parameterType="map" resultType="pojo.blog">
select * from mybatis.blog
<where>
<if test="title!=null">
AND title = #{title}
</if>
<if test="id!=null">
and id = #{id}
</if>
</where>
</select>
如果里面的值不为空就会添加(通过map实现比较好)
@Test
public void testblog(){
SqlSession sqlSession = Mybatis.getSqlsession();
blogMapper mapper = sqlSession.getMapper(blogMapper.class);
HashMap map = new HashMap();
map.put("title","Java");
List<blog> queryblog = mapper.queryblogs(map);
for (blog blog : queryblog) {
System.out.println(blog);
}
}
2、choose语句
从许多语句中选一句来进行
<select id="chooseIF" parameterType="map" resultType="pojo.blog">
select * from mybatis.blog
<where>
<choose>
<when test="title!=null">
title = #{title}
</when>
<when test="author!=null">
author = #{author}
</when>
<otherwise>
views = #{views}
</otherwise>
</choose>
</where>
</select>
3、set语句
专门进行修改操作
<update id="updates" parameterType="map">
update Mybatis.blog
<set>
<if test="title!=null"> title = #{title}</if>
<if test="author!=null">author = #{author}</if>
</set>
where id = #{id}
</update>
4、Foreachs语句
可以选择的进行需要输出多少个
<select id="Foreachs" parameterType="map" resultType="pojo.blog">
select * from mybatis.blog
<where>
<foreach item="id" open="and (" close=")" separator="or" collection="items">
id = #{id}
</foreach>
</where>
</select>
select * from blog where 1=1 and (id=1 or id=2 or id=3)
item代表下面的属性名,就是id
open代表从1=1 后面开始的内容
close代表结束的内容
separator表示中间的分隔符号
collection主要是后面测试的Arraylist的名字
测试
@Test
public void testlimit(){
SqlSession sqlsession = Mybatis.getSqlsession();
blogMapper mapper = sqlsession.getMapper(blogMapper.class);
ArrayList<Integer> item = new ArrayList<>();
item.add(1);
item.add(3);
HashMap hashMap = new HashMap();
hashMap.put("items",item);
List<blog> foreachs = mapper.Foreachs(hashMap);
for (blog foreach : foreachs) {
System.out.println(foreach);
}
sqlsession.close();
}
5、sql片段
将一些功能部分抽出,方便使用
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
sql里面的就为封装的
使用:
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
十、缓存
将数据存储在缓存当中,用户就不用再去重新查找数据,可以加快效率
1、一级缓存
Sqlsession默认是开启的,close来进行关闭
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
在查询不同东西的时候会重新进行
在增删改查之后会重新进行缓存操作
clearCache可以手动清理缓存
2、二级缓存
一级缓存范围太小,不够用
二级缓存可以在一级缓存用close关闭后还存在
在mybatis-config.xml开启全局缓存
<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
在要使用二级缓存的Mapper中开启
<!--在当前Mapper.xml中使用二级缓存-->
<cache/>
自定义参数
<!--在当前Mapper.xml中使用二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
readonly是必须要有的,不然会报错
参考资料
- 狂神说MyBatis系列视频
- MyBatis官方文档