Mybatis

本教程来源于【尚硅谷】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
注:注意版本控制

  1. 建个springboot初始化项目,勾上lombok
    在这里插入图片描述
  2. 引入依赖
	   <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>
  1. 建好目录结构
    在这里插入图片描述
  2. 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 #开启延迟加载

  1. 实现具体代码
    实体类-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>

各参数意义见图:
在这里插入图片描述

  1. 测试
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;
}

当字段名和属性名不一致时,两种解决方案:

  1. 查询语句设别名(不常用)
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>
  1. 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里面除了查询结果,还有很多分页的信息:
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值