Mybatis学习笔记
Mybatis工作原理
-
解析配置文件(mybatis-config.xml、mapper.xml),Mybatis基于XML配置文件生成Configuration和多个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着<select|update|delete|insert>标签项。
-
SqlSessionFactoryBuilder创建会话工厂SqlSessionFactory。
-
SqlSessionFactory创建会话SqlSession。
-
执行器将mapperStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbcStatement对象,使用parameterHandler绑定参数。
-
JDBC执行sql,借助MappedStatement中的结果映射关系,使用ResultSetHandler将返回结果转化成HashMap、JavaBean等存储结构并返回。
-
关闭SqlSession会话。
1、搭建环境
1.1 搭建数据库
以个人为例,在WampServer中的MySQL创建mybatis数据库,同时创建mybatis表
1.2 创建Maven项目(模块)
1.3 导入依赖
在pom.xml的dependencies中加入以下代码。
如果在父项目的pom.xml中添加以下依赖,那么在子项目中即可使用,无需再次添加。
是否需要将依赖写在父项目或者子项目中,视情况而定。
<dependencies>
<!--导入mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--导入mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--导入junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
1.4 添加核心配置文件
在模块中的resource文件夹下添加mybatis-config.xml,并写入以下代码
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 使用mapper.xml实现sql用resource -->
<mapper resource="MybatisMapper.xml"/>
<!-- 使用接口注解实现sql用class -->
<mapper class="usermapper.class"/>
</mappers>
</configuration>
- url、username、password视情况修改
- 对于每一个新创建的mapper的xml文件,都需要在mappers标签下进行注册,格式如上所示。
- 为了避免引用错误,尽量将Mapper文件写在Resource文件夹下
2、SqlSession工具类创建
- 这个工具类主要用于对SqlSessionFactory的初始化配置
package mybatis;
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.InputStream;
public class Utils {
private static SqlSessionFactory sqlSessionFactory;
// 初始化Mybatis配置
static {
try {
String resource = "mybatis-config.xml"; // 配置文件名,可更改
InputStream inputStream = Resources.getResourceAsStream( resource );
sqlSessionFactory = new SqlSessionFactoryBuilder().build( inputStream );
} catch ( IOException e ) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
3、创建实体类
- 实体类根据需求创建
- 一般包含的元素与数据库表中的字段一一对应。
package pojo;
public class User {
private int id;
private String name;
public User() {
}
public User( int id, String name ) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
3、编写接口
- 根据业务需求,编写所需要的业务方法
package mybatis;
import org.apache.ibatis.annotations.Param;
import pojo.User;
import java.util.List;
public interface userMapper {
List< User > getUserList();
void insertUser( @Param( "user" ) User user );
void deleteUser( int id );
void updateUser( @Param("id") int id, @Param("name") String name );
}
4、编写接口Mapper.xml
-
这个xml文件的作用于接口实现类的作用相同,只是Mybatis将JDBC代码省略,我们只需写出相应的sql语句即可
-
namespace里面放的是接口名(全限定名)
-
每个标签里面的 id 对应 方法名
-
resultType可以放实体名(全限定名)和String等类型
-
Mapper写完之后记得在配置文件 config.xml 中注册
-
参数类型需要在接口方法参数中通过注解@param来进行配置
<?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="mybatis.userMapper">
<select id="getUserList" resultType="pojo.User">
select * from user
</select>
<insert id="insertUser">
insert into user(id, user) values(#{user.id}, #{user.name})
</insert>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
<update id="updateUser">
update user SET user = #{name} WHERE id = #{id}
</update>
</mapper>
5、获取接口实现类
- 在业务层需要与数据库交互时,可通过以下代码来获取DAO实现类
SqlSession sqlSession = Utils.getSqlSession();
userMapper userMapper = sqlSession.getMapper( userMapper.class );
userMapper.deleteUser( 3 );
sqlSession.commit();
sqlSession.close();
// Mybatis CRUD操作
public class MybatisTest {
@Test
public void test() {
// 在try的括号中开启sqlSession,无需管理流的关闭
// 语句块结束之后,能够自动关闭sqlSession
try ( SqlSession sqlSession = Utils.getSqlSession() ) {
// sqlSession.getMapper(接口名.class)
// 在配置文件xml中实现接口,得到实现类
userMapper userMapper = sqlSession.getMapper( userMapper.class );
// select
List< User > userList = userMapper.getUserList();
for ( User user : userList ) {
System.out.println( user );
}
// insert
userMapper.insertUser( new User( 3, "杨鑫" ) );
// update
userMapper.updateUser( 3, "林耀博" );
// delete
userMapper.deleteUser( 3 );
// select 不需要commit
// insert、delete、update 事务操作需要commit
sqlSession.commit();
}
}
}
6、CRUD
6.1 select
<select id="getUserList" resultType="pojo.User">
select * from user
</select>
<select id="getUserList" resultType="pojo.User" parameterType="map">
select * from user where id = #{key1}, name = #{key2}
</select>
Map<String,Object> map = new HashMap<String,Object>();
map.put("key1","value1");
map.put("key2","value2");
mapper.getUserList(map);
6.2 insert
<insert id="insertUser">
<!-- user是接口那边的@param("user") -->
insert into user(id, user) values(#{user.id}, #{user.name})
</insert>
6.3 update
<update id="updateUser">
<!-- @param("id") -->
update user set user = #{name} where id = #{id}
</update>
6.4 delete
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
6.5 模糊查询SQL
select * from user where name like "%"#{name}"%"
select * from user where name like concat('%',#{name},'%')
7、配置优化
7.1 属性优化
- 通过配置文件properties存储相关信息
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
username=root
password=
- 在核心配置文件mybatis-config.xml中添加内容
- 通过${key}获取配置文件中对应的value值
<!-- db.properties文件和mybatis-config.xml在同一目录下 -->
<properties resource="db.properties"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
7.2 别名优化
7.2.1 typeAlias
- 在核心配置文件mybatis-config.xml中添加字段 typeAliases
- 存在的意义是减少类全限定名的冗余
- 在底下的所有Mapper中均可使用这些别名
<typeAliases>
<typeAlias type="pojo.User" alias="User"/>
</typeAliases>
7.2.2 package
- 也可以指定一个包名,Mybatis会在包名下面搜索需要的Java Bean
<typeAliases>
<package name="pojo"/>
</typeAliases>
7.2.3 注解@Alias (最方便)
- 也可以通过注解@Alias(" "),在实体类的上方声明
- 无需通过typeAliases标签配置
@Alias("User")
public class User {
private int id;
private String name;
/*
* todo
*/
}
8、设置
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
9、分页
9.1 SQL Limit分页
select * from user limit 0,3; //从第0(1)条记录开始,选取3条
9.2 RowBounds分页
RowBounds rowBounds = new RowBounds(0,3);
//第一个参数要写全限定名,指向某一个Mapper接口中的某一个方法
List<User> list = sqlSession.selectList("mybatis.userMapper.getUserList", null, rowbounds);
9.3 PageHelper分页插件
9.3.1 使用步骤
- 导入Maven依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.2.0</version>
</dependency>
- 在mybatis-config.xml中添加默认plugin配置(添加在environment上面)
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="true"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
9.3.2 使用范例
@Test
public void searchUserByParam()
{
int pageIndex = 2; //获取第2页的数据
int pageSize = 10; //每页10条数据
String orderBy = "create_time ASC"; //排序
//分页信息
//PageHelper.startPage只会在接下来最近的一条查询语句中生效
//且必须接的是select语句,不能是增删改语句,否则失效
PageHelper.startPage(pageIndex, pageSize, orderBy);
//查询条件参数类
UserSearchParam userSearchParam = new UserSearchParam();
//userSearchParam.setUserName("pan_junbiao的博客"); //查询条件1
//userSearchParam.setProvince("广东省"); //查询条件2
//执行分页查询
//userMapper.searchUserList()返回类型为 Page<UserInfo> 然后转化为 PageInfo<UserInfo>
PageInfo<UserInfo> userInfoPage = new PageInfo<UserInfo>(userMapper.searchUserList(userSearchParam));
//打印用户列表
List<UserInfo> userInfoList = userInfoPage.getList();
userInfoList.stream().forEach(System.out::println);
//打印分页信息
System.out.println("当前页码:第" + userInfoPage.getPageNum() + "页");
System.out.println("分页大小:每页" + userInfoPage.getPageSize() + "条");
System.out.println("数据总数:共" + userInfoPage.getTotal() + "条");
System.out.println("总页数:共" + userInfoPage.getPages() + "页");
}
10、多对一
public class student {
int id;
String name;
Teacher teacher;
}
10.1 按照查询嵌套处理(子查询)
<!--
思路:
1.查询所有的学生信息
2.根据查询出来的学生的tid,寻找对应的老师!子查询
-->
<select id="getstudent" resultMap="studentTeacher1">
select * from student
</select>
<resultMap id="studentTeacher1" type="student">
<result property="id" column="id" />
<result property="name" column="name" />
<!-- 复杂的属性,我们需要单独处理 -->
<!-- 对象:association 集合: collection -->
<!-- column="tid" 作为参数传递给子查询的#{tid},#{}里面的参数名任意取 -->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher" />
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid}
</select>
10.2 按照结果嵌套处理(联表查询)
<!-- 按照结果嵌套处理 -->
<select id="getstudent2" resultMap="studentTeacher2">
select s.id sid,s.name sname ,t.name tname
from student s,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="Teacher">
<result property="name" column="tname" />
</association>
</resultMap>
11、一对多
public class Teacher {
int id;
String name;
List<Student> students;
}
11.1 按照查询嵌套处理(子查询)
<select id="getTeacher" resultMap="Teacherstudent">
select * from mybatis.teacher where id = #{tid}
</select>
<resultMap id="Teacherstudent" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student"
select="getstudentByTeacherId" column="id" />
</resultMap>
<select id="getstudentByTeacherId" resultType="student">
select * from mybatis.student where tid = #{tid}
</select>
11.2 按照结果嵌套处理(联表查询)
<!-- 按结果嵌套查询 -->
<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>
12、动态SQL
12.1 IF
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog where 1=1
<if test="title != nu11">
and title = #{title}
</if>
<if test="author !=-nui1">
and author = #{author}
</if>
</select>
12.2 choose、when、otherwise(类似switch)
<select id="findActiveBlogLike" resultType="B1og">
select * from blog where state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
12.3 where
- where元素只会在至少有一个子元素的条件返回SQL子句的情况下才去插入WHERE"子句。
- 而且,若语句的开头为AND"或'OR'"where元素也会将它们去除。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author! name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
12.4 set
- set元素会动态前置SET关键字,同时也会删掉多余的逗号
<update id="updateAuthorIfNecessary" >
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{fid}
</update>
12.5 trim
属性 | 描述 |
---|---|
prefix | 给sql语句拼接的前缀 |
suffix | 给sql语句拼接的后缀 |
prefixOverrides | 去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND" |
suffixOverrides | 去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定 |
示例:
<select id="findUserInfoByTrim" parameterType="Map"
resultMap="UserInfoResult">
select * from userinfo
<trim prefix="where" prefixOverrides="and|or">
<if test="department!=null">
AND department like #{department}
</if>
<if test="gender!=null">
AND gender=#{gender}
</if>
<if test="position!=null">
AND position like #{position}
</if>
</trim>
</select>
12.6 SQL片段
- 使用sql标签抽取公共的部分
- 在需要的地方使用include标签引用即可
<sql id="if-title-author">
<if test="title != nu11">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
12.7 foreach
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(1);
map.put( "ids",ids ) ;
List<B1og> blogs = mapper.queryBlogForeach( map );
for (Blog blog : blogs) {
system.out.print1n(blog);
}
- collection的对应:
- map的key为ids,value为id列表
- id列表变量名为ids,select标签的parameterType为java.util.List
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
13、缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
13.1 一级缓存
- 一级缓存也叫本地缓存:
- 默认情况开启一级缓存。
- 与数据库同一次会话期间(SqlSession)查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
13.2 二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
-
基于namespace级别的缓存,一个名称空间(Mapper接口),对应一个二级缓存。
-
工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中。
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
-
使用步骤:
- 在mybatis-config.xml中开启全局缓存
<!-- cacheEnabled默认为true --> <settings> <setting name="cacheEnabled" value="true" /> </settings>
- 在需要使用二级缓存的Mapper中开启
<!--在当前Mapper.xml中使用二级缓存--> <cache />
也可以自定义参数
<!--在当前Mapper.xml中使用二级缓存--> <cache eviction="FIFO" flushInterva1="60000" size="512" readonly="true" />
小结:
-
只要开启了二级缓存,在同一个Mapper下就有效。
-
所有的数据都会先放在一级缓存中。
-
只有当会话提交,或者关闭的时候,才会提交到二级缓存中!
-
缓存查询顺序:
- 二级缓存
- 一级缓存
- 数据库查询
14、注意事项
14.1 xml文件资源导出失败时
- 在pom.xml添加以下代码
- 这样写在java文件夹下的配置文件和Mapper.xml都能导出了
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
14.2 resource,路径要用 / 分开 ,不要用 .
14.3 实体类属性名和数据库字段名不一致的问题
- 方案一:SQL语句中,对应字段起别名 as xxx
- 方案二:resultMap
数据库:id user
实体类:id name
第二个字段没有对应
<!-- 在Mapper.xml中添加以下代码 -->
<!-- 结果集映射 -->
<resultMap id="UserMap" type="User">
<!-- column为数据库中的字段, property为实体类中的属性 -->
<result column="id" property="id"/>
<result column="user" property="name"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
select* from mybatis.user where id = #{id}
</select>
15、SpringBoot+Mybatis
import org.apache.ibatis.annotations.*;
import springboot.bean.Department;
//Mapper接口通过@Autowired注入
@Mapper //告诉mybatis这是一个操作数据库的mapper
public interface DepartmentMapper {
@Select("select * from department where id=#{id}")
public Department getDeptById(Integer id);
@Delete("delete from department where id=#{id}")
public int deleteDeptById(Integer id);
@Options(useGeneratedKeys = true,keyProperty = "id") //使用自动生成主键,keyProperty = "id"表示id为封装主键
@Insert("insert into department(departmentName) values(#{departmentName})")
public int insertDept(Department department);
@Update("update department set departmentName=#{departmentName} where id=#{id}")
public int updateDept(Department department);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!