Administrator
发布于 2024-04-15 / 0 阅读
0
0

MyBatis & MyBatis-Plus-202509120214

MyBatis & MyBatis-Plus-202509120214

MyBatis & MyBatis-Plus

一、概述

Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 S

QL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。

程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。

MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,

避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

再说一下缺点

SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的

功底有一定要求

SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

ORM是什么?

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简

单Java对象(POJO)的映射关系的技术。简单来说,ORM是通过使用描述对象和数据库之

间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可

以根据对象关系模型直接获取,所以它是全自动的。

而Mybatis在查询关联对象或关联集合对象时,需要手动编写SQL来完成,所以,被称之为半

自动ORM映射工具。

二、MyBatis 基本用法

MyBatis 的核心组件

SqlSessionFactory:它是 MyBatis 的核心接口,用于创建 SqlSession 实例。通常通过 SqlSessi

onFactoryBuilder 从 XML 配置文件或 Java 配置创建。

SqlSession:SqlSession相当于JDBC中的Connection,SqlSession 的实例不是线程安全的,因

此是不能被共享的,所以它的最佳的生命周期是一次请求或一个方法。

Mapper:映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的,它

的生命周期在sqlsession事务方法之内,一般会控制在方法级。

Configuration:MyBatis 的配置文件,包含数据源、事务管理器、映射器等配置信息。

Mapped Statement:SQL 语句的映射信息,包括 SQL 语句、参数映射、结果映射等。

1. 配置文件

application.yml

Mapper 接口

DataSource Config

spring:

datasource:

driver-class-name: com.mysql.cj.jdbc.Driver

mysql url

url: jdbc:mysql://rm-

8vb736ke4x19z86r7no.rwlb.zhangbei.rds.aliyuncs.com:3306/dyf?

useUnicode=true&characterEncoding=UTF-

8&useSSL=false&serverTimezone=Asia/Shanghai

username: [用户名]

password: [密码]

mybatis:

config-location:

log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

local-cache-scope: SESSION

开启二级缓存

cache-enabled: true

logging:

level:

org.example.mapper: debug

@Mapper

public interface UserMapper {

User getUserById(Integer id);

Mapper XML

User queryUserById(Integer id);

void updateUserById(@Param("name") String name, @Param("id")

Integer id);

void insertUsers(List userList);

}

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


select="org.example.mapper.ListeningCourseMapper.getListeningCourseByCo

urseId"/>

update user set name = #{name} WHERE id = #{id}

2.MyBatis的执行过程

加载配置文件

配置文件解析:MyBatis 从配置文件(如 mybatis-config.xml)中加载配置信息,包括数据库连

接信息、事务管理器、映射器(Mapper)等。

构建 SqlSessionFactory:使用 SqlSessionFactoryBuilder 从配置文件创建 SqlSessionFactory 实

例。

创建 SqlSession

打开 SqlSession:通过 SqlSessionFactory 打开一个 SqlSession 实例。SqlSession 提供了执行 S

QL 命令、提交或回滚事务、获取 Mapper 实例等功能。

获取 Mapper 接口

获取 Mapper 实例:通过 SqlSession 获取 Mapper 接口的代理对象。MyBatis 会根据 Mapper 接

口生成代理对象,代理对象会拦截接口方法的调用,并执行相应的 SQL 语句。

执行 SQL 语句

Mapper 方法调用:调用 Mapper 接口的方法。

SQL 语句解析:MyBatis 根据 Mapper 接口的方法名或注解找到对应的 SQL 语句。

参数处理:将方法参数转换为 SQL 语句中的参数。

SQL 执行:执行 SQL 语句,与数据库进行交互。

结果映射

结果处理:将数据库查询结果映射为 Java 对象。

返回结果:将映射后的 Java 对象返回给调用者。

insert into user (id, name, age, email) values

(#{item.id},#{item.name},#{item.age},#{item.email})

3.动态SQL

定义和执行原理:

MyBatis的动态SQL就是Mybatis提供一些标签,例如if, choose等,然后可以利用这些标签在xml

文件里编写条件化或循环的 SQL 片段。当读取和加载xml时,MyBatis会解析这些标签,将它们

转换成一个SQL节点树,当要实际执行这些SQL语句时,MyBatis就会动态解析这个SQL节点树,

拼接和生成最终的SQL语句,整个根据条件和标签动态生成 SQL 语句的过程就是 MyBatis 的动

态 SQL。

if:

根据条件来组成where子句

choose (when, otherwise):

这个和Java 中的 switch 语句有点像

trim (where, set):

可以用在所有的查询条件都是动态的情况

可以用在动态更新的时候

update Author

username=#{username},

password=#{password},

email=#{email},

bio=#{bio}

where id=#{id}

foreach:

用来循环的,可以对集合进行遍历

#{}和${}区别:

模糊查询like语句该怎么写?

1 ’%${question}%’ 可能引起SQL注入,不推荐

2 "%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所

以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。

3 CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,(推荐✨)

4 使用bind标签(不推荐)

特性

#{} 

${} 

处理方式

预编译处理(PreparedState

ment)

直接字符串替换

安全性

安全,防止 SQL 注入

不安全,容易导致 SQL 注

入,还有可能导致索引失效

适用场景

动态参数传递,用户输入

固定参数,如表名、列名

示例

WHERE id = #{id} 

FROM ${tableName} 

com.baomidou

mybatis-plus-generator

3.5.9

org.freemarker

freemarker

2.3.31

org.apache.velocity

velocity-engine-core

2.3

package org.example.util;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;

import com.baomidou.mybatisplus.generator.config.OutputFile;

import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;

import

com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.sql.Types;

import java.util.Collections;

2.持久层接口

/**

  • @author caodingwei
  • */

    public class CodeGenerator {

    public static void main(String[] args) {

    String url =

    "jdbc:mysql://rm-

    8vb736ke4x19z86r7no.rwlb.zhangbei.rds.aliyuncs.com:3306/dyf?

    useUnicode=true&characterEncoding=UTF-

    8&useSSL=false&serverTimezone=Asia/Shanghai";

    String username = "**";

    String password = "**";

    FastAutoGenerator.create(url, username,

    password).globalConfig(builder -> {

    builder.author("cdw") // 设置作者


    .outputDir("D:\\ideaProject\\MyBatisDemo\\src\\main\\java"); // 指定输出

    目录

    }).dataSourceConfig(builder ->

    builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {

    int typeCode = metaInfo.getJdbcType().TYPE_CODE;

    if (typeCode == Types.TINYINT) {

    // 默认把tinyint转为Byte类型,可以使用自定义类型转换

    return DbColumnType.INTEGER;

    }

    return typeRegistry.getColumnType(metaInfo);

    })).packageConfig(builder -> builder.parent("org.example")

    // 设置父包名

    .pathInfo(

    Collections.singletonMap(OutputFile.xml,

    "D:\\ideaProject\\MyBatisDemo\\src\\main\\resources\\mapper"))

    // 设置mapperXml生成路径

    ).strategyConfig(builder ->

    builder.addInclude("listening_course") // 设置需要生成的表名

    //.addTablePrefix("t_", "c_") // 设置过滤表前缀

    ).templateEngine(new FreemarkerTemplateEngine()) // 使用

    Freemarker引擎模板,默认的是Velocity引擎模板

    .execute();

    }

    }

    Service Interface

    IService是 MyBatis-Plus 提供的一个通用 Service 层接口,它封装了常见的 CRUD 操作,包括插

    入、删除、查询和分页等。通过继承 IService 接口,可以快速实现对数据库的基本操作,同时保

    持代码的简洁性和可维护性。

    Mapper Interface

    BaseMapper 是 Mybatis-Plus 提供的一个通用 Mapper 接口,它封装了一系列常用的数据库操作

    方法,包括增、删、改、查等。通过继承 BaseMapper,开发者可以快速地对数据库进行操作,

    而无需编写繁琐的 SQL 语句。

    public interface UserService extends IService {

    }

    @Service

    public class UserServiceImpl extends ServiceImpl

    implements UserService {

    }

    @Test

    public void testPlus_Service() {

    //条件构造器

    QueryWrapper queryWrapper = new QueryWrapper<>();

    queryWrapper.gt("id", 1)//大于

    //.eq("name", "Sandy")//等于

    .in("name", "Sandy", "Billie").orderByDesc("id");

    //IService封装的查询方法

    List userList = userService.list(queryWrapper);

    userList.forEach(System.out::println);

    //IService封装的新增方法

    User userForInsert = User.builder()

    .age(10)

    .email("test@163.com")

    .id(100)

    .name("test")

    .build();

    userService.save(userForInsert);

    }

    @Mapper

    public interface UserMapper extends BaseMapper {

    注意:MyBatis-Plus的伪批量插入

    User getUserById(Integer id);

    User queryUserById(Integer id);

    void updateUserById(@Param("name") String name, @Param("id")

    Integer id);

    @Test

    public void testPlus_Mapper() {

    //条件构造器

    QueryWrapper queryWrapper = new QueryWrapper<>();

    queryWrapper.gt("id", 1)//大于

    //.eq("name", "Sandy")//等于

    .in("name", "Sandy", "Billie");

    //BaseMapper封装的查询方法

    List userList = userMapper.selectList(queryWrapper);

    userList.forEach(System.out::println);

    //BaseMapper封装的新增方法

    User userForInsert = User.builder()

    .age(10)

    .email("test@163.com")

    .id(100)

    .name("test")

    .build();

    userMapper.insert(userForInsert);

    }

    @Test

    public void testBatch() {

    //创建10个用户

    List userList = createUsers(10);

    //userService.saveBatch(userList);//循环调用mapper的insert方法,性能差

    userMapper.insertUsers(userList);//拼接sql方式插入

    }

    public List createUsers(int count) {

    List userList = new ArrayList<>();

    for (int i = 0; i < count; i++) {

    执行结果

    使用自定义的批量插入方法:

    执行结果

    3.条件构造器

    MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wra

    pper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发

    效率并减少 SQL 注入的风险。

    等于 (eq): 用于构建等于条件,例如 column = val

    大于等于 (ge): 用于构建大于等于条件,例如 column >= val

    IN 查询 (in): 用于构建 IN 条件,例如 column IN (val1, val2, ...)

    BETWEEN 查询 (between): 用于构建 BETWEEN 条件,例如 column BETWEEN val1 AND val

    2

    User user = User.builder()

    .age(i)

    .email("email" + i)

    .id(i+10)

    .name("user" + i)

    .build();

    insert into user (id, name, age, email) values

    (#{item.id},#{item.name},#{item.age},#{item.email})

    分组查询 (groupBy): 用于构建分组查询,例如 GROUP BY column1, column2, ...

    升序排序 (orderByAsc): 用于构建升序排序,例如 ORDER BY column1 ASC, column2 ASC, ...

    AND 条件块 (and): 用于添加 AND 条件块。

    设置列值 (set): 用于设置列值,例如 column = val

    设置 SQL 片段 (setSql): 用于执行自定义 SQL 更新语句

    ......

    四、MyBatis缓存

    MyBatis 缓存中的常用概念

    MyBatis 缓存:它用来优化 SQL 数据库查询的,但是可能会产生脏数据。

    SqlSession:代表和数据库的一次会话,向用户提供了操作数据库的方法。

    MappedStatement:代表要发往数据库执行的指令,可以理解为是 SQL 的抽象表示。

    Executor:代表用来和数据库交互的执行器,接受 MappedStatment 作为参数。

    wrapper.eq("name", "张三");

    wrapper.ge("age", 18);

    wrapper.in("id", Arrays.asList(1, 2, 3));

    wrapper.between("age", 18, 30);

    wrapper.groupBy("department");

    wrapper.orderByAsc("age", "name");

    wrapper.and(w -> w.eq("name", "张三").gt("age", 18));

    updateWrapper.set("age", 20);

    updateWrapper.setSql("age = age + 1");

    namespace:每个 Mapper 文件只能配置一个 namespace,用来做 Mapper 文件级别的缓存共

    享。

    映射接口:定义了一个接口,然后里面的接口方法对应要执行 SQL 的操作,具体要执行的 SQL

    语句是写在映射文件中。

    映射文件:MyBatis 编写的 XML 文件,里面有一个或多个 SQL 语句,不同的语句用来映射不同

    的接口方法。通常来说,每一张单表都对应着一个映射文件。

    一级缓存

    基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为SqlSession,各个SqlSession之间

    的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,My

    Batis默认打开一级缓存。

    在一次 SqlSession 中(数据库会话),程序执行多次查询,且查询条件完全相同,多次查询之间

    程序没有其他增删改操作,则第二次及后面的查询可以从缓存中获取数据,避免走数据库。

    每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,My

    Batis根据当前执行的语句生成MappedStatement ,在Local Cache进行查询,如果缓存命中

    的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cach

    e ,最后返回结果给用户。

    Local Cache 其实是一个 hashmap 的结构:

    如下图所示,有两个 SqlSession,分别为 SqlSession1 和 SqlSession2,每个 SqlSession 中都有

    自己的缓存,缓存是 hashmap 结构,存放的键值对。

    失效条件

    执行insert,update,delete操作,会清空sqlsession的一级缓存

    sqlsession关闭后,一级缓存会失效

    手动清空缓存:sqlsession.clearCache()

    Statement:缓存只对当前执行的这一个Statement有效,一级缓存无法关闭

    private Map cache = new HashMap();

    示例代码

    执行结果

    public void testLv1Cache() {

    try {

    SqlSession session1 = sqlSessionFactory.openSession();

    UserMapper userMapper1 = session1.getMapper(UserMapper.class);

    SqlSession session2 = sqlSessionFactory.openSession();

    UserMapper userMapper2 = session2.getMapper(UserMapper.class);

    User user1 = userMapper1.getUserById(1);

    System.out.println("User1: " + user1);

    User user2 = userMapper1.getUserById(1);//从一级缓存读取数据

    System.out.println("User2: " + user2);

    //更新user

    User updatedUser = new User();

    updatedUser.setId(1);

    updatedUser.setName("userUPD888");

    userMapper2.updateById(updatedUser);

    User user3 = userMapper1.getUserById(1);

    System.out.println("User3: " + user3);//从一级缓存中读取的脏数据

    User user4 = userMapper2.getUserById(1);

    System.out.println("User4: " + user4);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    二级缓存

    二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同之处在

    于其存储作用域为 Mapper(Namespace),可以在多个SqlSession之间共享,并且可自定义存储

    源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializ

    able序列化接口(可用来保存对象的状态),可在它的映射文件中配置。

    一级缓存最大的共享范围就是一个  SqlSession  内部,如果多个  SqlSession  之间需要共

    享缓存,则需要使用到二级缓存。

    开启二级缓存后,会使用  CachingExecutor  装饰  Executor ,进入一级缓存的查询流程

    前,先在CachingExecutor 进行二级缓存的查询。

    二级缓存开启后,同一个  namespace 下的所有操作语句,都影响着同一个Cache。

    每个 Mapper 文件只能配置一个 namespace,用来做 Mapper 文件级别的缓存共享。

    二级缓存被同一个  namespace  下的多个  SqlSession  共享,是一个全局的变量。MyBati

    s 的二级缓存不适应用于映射文件中存在多表查询的情况。

    通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace 的,

    多表查询语句所在的namspace 无法感应到其他namespace 中的语句对多表查询中涉及的表

    进行的修改,引发脏数据问题。

    配置文件

    mybatis:

    config-location:

    Mapper XML

    示例代码

    开启二级缓存

    cache-enabled: true

    update user set name = #{name} WHERE id = #{id}

    public void testLv2Cache() {

    try {

    SqlSession session1 = sqlSessionFactory.openSession();

    UserMapper userMapper1 = session1.getMapper(UserMapper.class);

    SqlSession session2 = sqlSessionFactory.openSession();

    UserMapper userMapper2 = session2.getMapper(UserMapper.class);

    User user1 = userMapper1.getUserById(1);

    System.out.println("User1: " + user1);

    session1.close();//二级缓存需要在一级缓存失效后才会生效

    User user2 = userMapper2.getUserById(1);

    System.out.println("User2: " + user2);

    //更新后缓存刷新

    userMapper2.updateUserById("userUPD345", 1);//xml中

    updateUserById增加了flushCache操作

    User user3 = userMapper2.getUserById(1);

    System.out.println("User3: " + user3);

    } catch (Exception e) {

    e.printStackTrace();

    }

    执行结果

    一级缓存和二级缓存的使用顺序

    }

    先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用

    如果二级缓存没有命中,再查询一级缓存

    如果一级缓存也没有命中,则查询数据库

    SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

    五、总结

    MyBatis 提供了灵活的 SQL 映射和强大的动态 SQL 功能。

    MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,提供

    了更多的便捷功能,如代码生成器、条件构造器等。


    本文整理自实践经验,如有问题欢迎交流讨论。


    评论