本教程来源于【尚硅谷】2022版MyBatis教程(细致全面,快速上手)
1 简介
mybatis是个半自动的ORM框架。有半自动就有手动的(jdbc)和全自动的(hibernate)。
1.1 下载
教学使用3.5.7版本,下载地址:https://github.com/mybatis/mybatis-3/releases/tag/mybatis-3.5.7
里面有个pdf文档,可以看看。
1.2 竞品比较

2 环境搭建
2.1 创建案例数据库和数据表
这里是8版本mysql数据库
这里在库名为testdb的数据库里创个表user_info来示范:
CREATE TABLE `user_info` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '名字',
`user_salary` double DEFAULT NULL COMMENT '工资',
`user_level` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '等级',
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
`update_date` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3
user_info表里填两个数据:

2.2 springboot整合druid+mybatis
注意代码的编写顺序:数据库表–实体类–mapper接口–mapper.xml
注:注意版本控制
- 建个springboot初始化项目,勾上lombok

- 引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--springboot不知道你要用什么数据库,所以数据库驱动自己额外加,注意默认的驱动版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
- 建好目录结构

- yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
#使用的连接池类型
type: com.alibaba.druid.pool.DruidDataSource
#这里druid连接池的配置不是重点,不手动配,用默认的配置,想改配置请查查官方文档啥的
druid:
#druid连接池监控平台:http://localhost:3000/druid
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
# 配置mybatis规则
mybatis:
#全局配置文件,autoconfig都配好了,可以不配
#config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true #开启数据库表里面的字段名xxx_yyy和实体类里面的属性名xxxYyy的自动驼峰对应。
lazy-loading-enabled: true #开启延迟加载
- 实现具体代码
实体类-User:
package com.coderhao.demo.pojos;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String userName;
private double userSalary;
private char userLevel;
private Date createDate;
private Date updateDate;
}
mapper接口-UserMapper:
package com.coderhao.demo.dao;
import com.coderhao.demo.pojos.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
public List<User> getAll();
}
mapper配置文件-UserMapper.xml:
<?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="com.coderhao.demo.dao.UserMapper">
<!-- 查询标签必须设置resultType/resultMap-->
<!-- resultType:默认映射-->
<!-- resultMap:自定义映射,表的字段名和实体类的属性名不一致的情况,手动一一映射-->
<select id="getAll" resultType="com.coderhao.demo.pojos.User">
select * from user_info
</select>
</mapper>
各参数意义见图:

- 测试
package com.coderhao.demo;
import com.coderhao.demo.dao.UserMapper;
import com.coderhao.demo.pojos.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class DemoApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void test1() {
List<User> userList = userMapper.getAll();
System.out.println(userList);
}
}
控制台输出了:

springboot给我们配好了很多设置,简化了很多步骤,如果单纯用spring,是要这样用的:

现在数据库操作的代码是无效的,原始的jdbc方式需要手动提交事务:
mybatis-config.xml:

在操作后提交事务:

2.3 mapper.xml中的小设置
3 mybatis获取sql语句所需的参数

获取参数两种方式:${}和#{}
${}:本质字符串拼接,sql语句需要加单引号
#{}:本质占位符赋值,sql语句不需要单引号(相当于自动加了引号)
因为#{}是占位符,所以不需要加引号,也不怕sql注入攻击,能用#{}就尽量用。
mapper接口的参数建议只用以下情况之一:
1.带@Param()注解
2.实体类
3.1 不需要参数
//无参数
public List<User> getAll();
<!-- 查询标签必须设置resultType/resultMap-->
<!-- resultType:默认映射-->
<!-- resultMap:自定义映射,表的字段名和实体类的属性名不一致的情况,手动一一映射-->
<select id="getAll" resultType="com.coderhao.demo.pojos.User">
select * from user_info
</select>
3.2 字面量参数
//一个参数
public User getUserById(int id);
//多个参数
//用@Param()注解来手动映射参数名,但还是建议:xxx_yyy(字段名)---xxxYyy(属性名),别随便映射乱七八糟的参数名
public User getUserByIdName(@Param("id") int id, @Param("salary") double userSalary);
<!-- 参数是一般变量时以参数名赋值-->
<select id="getUserById" resultType="com.coderhao.demo.pojos.User">
select * from user_info where id=#{id}
<!--select * from user_info where id='${id}'-->
</select>
<!-- 参数名受@Param注解影响-->
<select id="getUserByIdName" resultType="com.coderhao.demo.pojos.User">
select * from user_info where id=#{id} and user_salary=#{salary}
</select>
3.3 map参数
//参数为Map集合
public User getUserByMap(Map<String, Object> map);
<!-- 参数为map时以map中的键名来赋值-->
<select id="getUserByMap" resultType="com.coderhao.demo.pojos.User">
select * from user_info where id=#{id} and user_salary=#{userSalary}
</select>
3.4 实体类参数
//参数为实体类
public void addUserByUser(User user);
<!-- 把实体类参数的属性名拿来赋值-->
<!-- 这里null是因为id是设定的自增的-->
<insert id="addUserByUser">
insert into user_info(id,user_level,user_salary,user_name,create_date,update_date)
values(null,#{userLevel},#{userSalary},#{userName},#{createDate},#{updateDate})
</insert>
3.5 like模糊查询
//like模糊查询
public List<User> getUsersByLikeName(@Param("userName") String userName);
<!-- 使用#{},安全-->
<select id="getUsersByLikeName" resultType="com.coderhao.demo.pojos.User">
<!--select * from user_info where user_name like '%${userName}%'-->
select * from user_info where user_name like concat('%',#{userName},'%')
</select>
3.6 参数列表(如批量删除操作)
//接收参数列表,注意这里要是String类型
//返回值接受的是影响的行数,也可以void
public int deleteUsersByIds(@Param("ids") String ids);
<!-- 在sql语句,delete xxx from xxx where id in (1,2,3)不需要引号-->
<!-- 而#{}会自动加引号,所以像批量删除这样的接收参数列表,要使用${}-->
<delete id="deleteUsersByIds">
delete from user_info where id in (${ids})
</delete>
3.7 根据表名查询数据
public List<User> getUserByTableName(@Param("tableName") String tableName);
<!-- 表名不能加引号,所以要用${},怎么解决sql注入问题,后面会说-->
<select id="getUserByTableName" resultType="com.coderhao.demo.pojos.User">
select * from ${tableName}
</select>
4 mybatis查询的各种返回值
4.1 返回实体类对象
查询出来的记录一条,可以用实体类对象或者集合接收。
public User getUserById(int id);
<select id="getUserById" resultType="com.coderhao.demo.pojos.User">
select * from user_info where id=#{id}
<!--select * from user_info where id='${id}'-->
</select>
4.2 返回list集合
在返回多条记录时,只能用list接收
public List<User> getAll();
<select id="getAll" resultType="com.coderhao.demo.pojos.User">
select * from user_info
</select>
4.3 返回map集合
当没有实体类对象对应返回值的时候,可以用map接收
//返回值为map
@MapKey("id")//map里面key名的设置
public Map<String,Object> getOneToMap(@Param("id") int id);
@MapKey("id")
public Map<String,Object> getAllToMap();
<select id="getOneToMap" resultType="java.util.Map">
select * from user_info where id=#{id}
</select>
<select id="getAllToMap" resultType="java.util.Map">
select * from user_info
</select>


注:可以看到,map里面装的是数据库表里的字段名,而不是实体类属性名。
4.4 添加功能获取自增主键
//crud的返回值都是固定的,add操作的返回值就是受影响的记录总数,返回值不能改成自增的主键值
public int addUserGetId(User user);
<!-- useGeneratedKeys:使用自增的主键-->
<!-- keyProperty:主键回填到形参User的某个(id)属性中-->
<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">
insert into user_info(id,user_level,user_salary,user_name,create_date,update_date)
values(null,#{userLevel},#{userSalary},#{userName},#{createDate},#{updateDate})
</insert>
test:
@Test
void addUserGetId() {
User user = new User(null, "liming", 13, 'b', new Date(), new Date());
userMapper.addUserGetId(user);//不带id
System.out.println(user);//带上了id
}
5 自定义映射:resultMap
5.1 resultMap:字段名和属性名的映射
假如有个新的实体类,要映射到user_info表,字段名-属性名 不符和,怎么办?
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AnotherUser {
private Integer id;
private String name;
private double salary;
private char level;
private Date createTime;
private Date updateTime;
}
当字段名和属性名不一致时,两种解决方案:
- 查询语句设别名(不常用)
public List<AnotherUser> getAllToAnotherUser();
<select id="getAllToAnotherUser" resultType="com.coderhao.demo.pojos.AnotherUser">
SELECT id,user_name `name`,user_salary salary,user_level `level`,create_date createTime,update_date updaeTime
FROM `user_info`
</select>
- resultMap映射
public List<AnotherUser> getAllToAnotherUser();
<resultMap id="anotherUser-user_info" type="com.coderhao.demo.pojos.AnotherUser">
<!-- id:主键映射-->
<!-- result:普通映射-->
<id property="id" column="id"></id>
<result property="name" column="user_name"></result>
<result property="level" column="user_level"></result>
<result property="salary" column="user_salary"></result>
<result property="createTime" column="create_date"></result>
<result property="updateTime" column="update_date"></result>
</resultMap>
<select id="getAllToAnotherUser" resultMap="anotherUser-user_info">
SELECT * FROM `user_info`
</select>
5.2 多对一的映射:使用association
首先创两个表,做多对一映射基础:
员工和部门之间是多对一的关系,每个员工一个部门,一个部门多个员工。
分别为员工表和部门表:
CREATE TABLE `emp` (
`emp_id` int NOT NULL COMMENT '员工编号',
`emp_name` varchar(255) DEFAULT NULL COMMENT '员工姓名',
`dept_id` int DEFAULT NULL COMMENT '部门编号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
CREATE TABLE `dept` (
`dept_id` int NOT NULL COMMENT '部门编号',
`dept_name` varchar(255) DEFAULT NULL COMMENT '部门名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
加点数据:
emp:

dept:

两个对应的实体类:
package com.coderhao.demo.pojos;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
private Integer deptId;
private String deptName;
private List<Emp> emps;
}
package com.coderhao.demo.pojos;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
private Integer empId;
private String empName;
private Integer deptId;
private Dept dept;
}
多对一代码:
public Emp getEmpById(@Param("id") Integer id);
<resultMap id="empAndDept" type="com.coderhao.demo.pojos.Emp">
<id property="empId" column="emp_id"></id>
<result property="empName" column="emp_name"></result>
<result property="deptId" column="dept_id"></result>
<!--对象映射的嵌套-->
<association property="dept" javaType="com.coderhao.demo.pojos.Dept">
<id property="deptId" column="dept_id"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<select id="getEmpById" resultMap="empAndDept">
SELECT *
FROM `emp` e LEFT JOIN `dept` d ON e.`dept_id`=d.`dept_id`
WHERE e.`emp_id`=#{id};
</select>
其实也可以分步查询,分布查询好处是可以复用已有的简单查询,但分布查询是两条sql语句,要与数据库连接两次,会多一倍的数据库连接开销,因此,一般能一次查询就一次,但不鼓励3表及以上的连接查询。
分布查询逻辑代码博主是打算放在service层。
5.3 一对多映射:使用collection
public Dept getDeptById(@Param("deptId") Integer deptId);
<resultMap id="deptMap" type="com.coderhao.demo.pojos.Dept">
<id property="deptId" column="dept_id"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="com.coderhao.demo.pojos.Emp">
<id property="empId" column="emp_id"></id>
<result property="empName" column="emp_name"></result>
<result property="deptId" column="dept_id"></result>
</collection>
</resultMap>
<select id="getDeptById" resultMap="deptMap">
SELECT *
FROM dept d
LEFT JOIN emp e
ON d.dept_id=e.dept_id
WHERE d.dept_id=#{deptId};
</select>
6 动态sql标签
根据条件动态拼接sql语句
6.1 if
public List<Emp> getEmpByEmp(@Param("emp") Emp emp);
<!-- 注意where1=1作为连接,if里面test做判断-->
<select id="getEmpByEmp" resultType="com.coderhao.demo.pojos.Emp">
select * from emp where 1=1
<if test="emp.empId!=null and emp.empId!=''">
and emp_id=#{emp.empId}
</if>
<if test="emp.empName!=null and emp.empName!=''">
and emp_name=#{emp.empName}
</if>
<if test="emp.deptId!=null and emp.deptId!=''">
and dept_id=#{emp.deptId}
</if>
</select>
6.2 where
6.1节的情况还是有些麻烦,可以使用where标签动态生成,(连接词and少了会报错):
public List<Emp> getEmpByEmp2(@Param("emp") Emp emp);
<select id="getEmpByEmp2" resultType="com.coderhao.demo.pojos.Emp">
select * from emp
<where>
<if test="emp.empId!=null and emp.empId!=''">
and emp_id=#{emp.empId}
</if>
<if test="emp.empName!=null and emp.empName!=''">
and emp_name=#{emp.empName}
</if>
<if test="emp.deptId!=null and emp.deptId!=''">
and dept_id=#{emp.deptId}
</if>
</where>
</select>
6.3 trim
感觉不是很重要。。。


6.4 choose,when,otherwise
相当于:if…else if…else…
6.1节的if关键字是条件符合都会执行,而这个是只会执行其中一个。
public List<Emp> getEmpByEmp3(@Param("emp") Emp emp);
<!-- 按照empId,empName,deptId的优先级单一条件查找-->
<!-- 其中之一满足即可,不需要and-->
<select id="getEmpByEmp3" resultType="com.coderhao.demo.pojos.Emp">
select * from emp
<where>
<choose>
<when test="emp.empId!=null and emp.empId!=''">
emp_id=#{emp.empId}
</when>
<when test="emp.empName!=null and emp.empName!=''">
emp_name=#{emp.empName}
</when>
<when test="emp.deptId!=null and emp.deptId!=''">
dept_id=#{emp.deptId}
</when>
<otherwise>
</otherwise>
</choose>
</where>
</select>
6.5 foreach
emp表加四个数据,做删除的对象:

//批量删除
public int deleteEmpsByIds(@Param("empIds") Integer[] empIds);
//通过list批量添加
public int addEmpByEmps(@Param("emps") List<Emp> emps);
<!-- 其实就是通过foreach人为创造了(x,y,...,z)这个字符串,拼到了sql语句里面。-->
<delete id="deleteEmpsByIds">
delete from emp where emp_id in
<foreach collection="empIds" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>
<!-- 人为创造了这个sql形式:insert into emp(emp_id,emp_name,dept_id) values(x,x,x),(x,x,x),....,(x,x,x)-->
<insert id="addEmpByEmps">
insert into emp(emp_id,emp_name,dept_id) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.empId},#{emp.empName},#{emp.deptId})
</foreach>
</insert>
6.6 sql、include
sql:定义sql片段
include:复用定义的sql片段
public List<Emp> getAllEmps1();
public List<Emp> getAllEmps2();
<!-- 正常查询不该写*的,应该查什么写什么,提高效率、保证代码清晰-->
<select id="getAllEmps1" resultType="com.coderhao.demo.pojos.Emp">
select emp_id,emp_name,dept_id
from emp
</select>
<sql id="emp-select-result">
emp_id,emp_name,dept_id
</sql>
<!-- 重复性的代码可以提取出来,变为sql片段,然后include引用-->
<select id="getAllEmps2" resultType="com.coderhao.demo.pojos.Emp">
select
<include refid="emp-select-result"></include>
from emp
</select>
7 mybatis缓存
mybatis自带的缓存,感觉好像用的不多啊,就放这里吧,用的时候再说
7.1 一级缓存
一级缓存默认开启

同一个sqlsession,就会是同一个xxxMapper
7.2 二级缓存


7.3 缓存查询的顺序

8 逆向工程(略)
正向工程:java实体类–>数据库表
逆向工程:数据库表–>java实体类、mapper接口和xml文件
用mybatis的逆向工程,还不如直接用mybatisplus,这里就不贴了。
博主建议是最好别逆向工程,感觉逆向工程对代码的控制不如自己手敲得来的。
简单查询可以用用mybatisplus的basemapper.
9 分页插件
文档:https://pagehelper.github.io/docs/howtouse/
pom依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置类:
package com.coderhao.demo.config;
import com.github.pagehelper.PageInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PageHelperConfig {
@Bean
public PageInterceptor getPageInterceptor(){
PageInterceptor pageInterceptor = new PageInterceptor();
return pageInterceptor;
}
}
使用:
@Test
void pageHelper() {
//分页设置紧贴着mapper,只有分页设置下面的第一个mapper有效
//这里表示要第2页,每页4条记录
//也可以用page来设置参数
Page<Emp> page = PageHelper.startPage(2, 4);
List<Emp> emps = userMapper.getAllEmps2();
System.out.println(emps);
PageInfo<Emp> pageInfo = new PageInfo<>(emps);
System.out.println(pageInfo);
}
emps:
Page{count=true, pageNum=2, pageSize=4, startRow=4, endRow=8, total=9, pages=3, reasonable=false, pageSizeZero=false}[Emp(empId=5, empName=zhaoliu, deptId=1, dept=null), Emp(empId=6, empName=null, deptId=null, dept=null), Emp(empId=7, empName=null, deptId=null, dept=null), Emp(empId=8, empName=null, deptId=null, dept=null)]
pageInfo:
PageInfo{pageNum=2, pageSize=4, size=4, startRow=5, endRow=8, total=9, pages=3, list=Page{count=true, pageNum=2, pageSize=4, startRow=4, endRow=8, total=9, pages=3, reasonable=false, pageSizeZero=false}[Emp(empId=5, empName=zhaoliu, deptId=1, dept=null), Emp(empId=6, empName=null, deptId=null, dept=null), Emp(empId=7, empName=null, deptId=null, dept=null), Emp(empId=8, empName=null, deptId=null, dept=null)], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=8, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
pageInfo里面除了查询结果,还有很多分页的信息:

2559

被折叠的 条评论
为什么被折叠?



