Mybatis入门到源码解析

目录

一、什么是MyBatis

二、MyBatis入门

1.配置mysql数据库连接yml

2.导入mybatis依赖

3.Mybatis配置

4.编码

(1)、编写实体类User

(2)、编写DAO层代码UserMapper

(3)、编写接口配置文件UserMapper.xml

(4)、编写控制层controller

5.测试

四、缓存

1.什么是缓存

2.一级缓存

3.二级缓存

五、动态SQL

1.if

2.choose、when、otherwise

3.trim、where、set

4.foreach

六、mybatis底层实现

1.首先我们需要复习一些jdbc的操作

2.首先写一个UserMapper接口

3.注解

4.实现Mapper接口的代理类

5.把#{}替换成?

6.参数赋值

七、mybatis源码解析


看完有收获给我点个赞这对我很重要🌹🌹🌹🌹

你的点赞让我的创作更有动力⭐⭐⭐⭐⭐

一、什么是MyBatis

MyBatis是一款优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需要关注SQL语句本身,而不需要花费精力去处理加载驱动、创建连接、创建Statement等繁杂的过程。

持久化

持久化是将程序数据在持久状态和瞬时状态间转换的机制。(理解一下这个持久状态转化为瞬时状态,持久化是mybatis最重要的特性)

  • 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
  • JDBC就是一种持久化机制。文件IO也是一种持久化机制。

为什么需要持久化服务?

  • 内存断点后数据会丢失,但是有些业务不允许这种情况的存在
  • 比起硬盘,内存过于昂贵,如果有够量的内存,则不需要持久化服务,但是正是因为内存太贵,储存有限,因此需要持久化来缓存

持久层

  • 持久层,顾名思义是完成持久化工作的代码块,也就是Date Access Object(Dao层)
  • 大多数情况下特别是企业级应用,数据持久化往往也就意味着将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系数据库来完成。
  • 不过这里有一个字需要特别强调,也就是所谓的“层”。对于应用系统而言,数据持久功能大多是必不可少的组成部分。也就是说,我们的系统中,已经天然的具备了“持久层”概念?也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模块”,“持久单元”,也就意味着,我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现.
  • 与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。(也就是改层就是为了操作数据库而设计的)

二、MyBatis入门

1.配置mysql数据库连接yml

#mysql配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    password: wsndqd857857
    username: root
    #这里要更改数据库只需要更改voredb那个地方就可以了
    url: jdbc:mysql://localhost:3306/votedb?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai

2.导入mybatis依赖

<!--mybatis依赖-->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.3.0</version>
</dependency>

3.Mybatis配置

#整合mybatis
mybatis:
  #给pojo实体类起别名
  type-aliases-package: com.ltx.mybatis.pojo
  #扫描的xml文件目录在那
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    #开启小驼峰命名
    map-underscore-to-camel-case: true
    #sql语句输出日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.编码

(1)、编写实体类User

首先在mysql中创建user表:

create table user
(
  id        bigint auto_increment comment '主键'
  primary key,
  user_name varchar(255) null comment '用户名',
  password  varchar(255) null comment '密码',
  phone     varchar(255) null comment '手机',
  email     varchar(255) null comment '邮箱',
  age       int          null comment '年龄',
  role      varchar(2)   null comment '角色'
)
    charset = utf8mb3;

编写pojo实体类中的User类:

以下代码使用了lombok简化代码,自动生成getter/setter、有无参构造方法。

package com.ltx.mybatis.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键id
    private Long id;
    //用户名
    private String userName;
    //密码
    private String passWord;
    //手机号
    private String phone;
    //邮箱
    private String email;
    //年龄
    private int age;
    //角色
    private String role;
}

(2)、编写DAO层代码UserMapper

package com.ltx.mybatis.Dao;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    //查询用户姓名
    String getUserName(Long id);

    //删除用户
    int deleteUser(int id);

    //修改用户Email
    int updateEmail(int id , String email);

}

(3)、编写接口配置文件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.ltx.mybatis.Dao.UserMapper">


</mapper>

<?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.ltx.mybatis.Dao.UserMapper">

  <select id="getUserName" parameterType="Long" resultType="String">
    select user_name from user where id = #{id}
  </select>

  <delete id="deleteUser" parameterType="int">
    delete from user where id = #{id}
  </delete>

  <update id="updateEmail" parameterType="int" >
    update user
    set email = #{email}
    where id = #{id}
  </update>

</mapper>

(4)、编写控制层controller

package com.ltx.mybatis.controller;


import com.ltx.mybatis.Dao.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/getUserName")
    public String getUserName(){
        String userName = userMapper.getUserName(1358687078200619010L);
        return userName;
    }

}

5.测试

1.首先介绍直接在测试类中测试代码,也就是springboot生成的时候自动帮你生成的测试启动类

@SpringBootTest
class MybatisApplicationTests {

    //映入dao层,但是这是直接映入的实际的业务中应该映入的是service层
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        String userName = userMapper.getUserName(1358687078200619010L);
        System.out.println(userName);
    }

}

输出结果如下图所示:

2.使用controller层,用web浏览器结果进行测试

首先先启动主启动类

用浏览器访问 loacal:8080

在浏览器的url中添加/user/getUserName

三、映射

在springboot中暂时我还是没有使用到需要用到requestMap映射的地方,因为springboot帮我们配置的很好别名什么的都在底层帮我们封装的很好了。

四、缓存

1.什么是缓存

缓存通俗的说就是一个临时存储区域,因为每次执行sql语句时,都需要去访问数据库,当访问量成千上万的时候,会出现访问缓慢等问题,但假若设置一个缓存区,执行sql返回的结果集,先暂存到一个临时区,下次再用就不需要去访问数据库,可以直接从临时区获取,这个临时区就是缓存区。

举个例子,你有一个保险箱,书包放在里面,近三天你需要使用书包,每次使用书包的时候,你都需要输入保险箱密码,把书包拿出来,用完打开保险箱把书包放回去。但你可以第一次从保险箱把书包拿出来后,先暂时放到桌上,等三天后,再把书包放回保险箱,这样会方便许多。

2.一级缓存

Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

@Resource
private SqlSessionFactory sqlSessionFactory;

@Test
void contextLoads() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    for (int i = 0; i < 4; i++) {
        User user = mapper.getUser(1358687078200619010L);
        System.out.println(user);
    }
}

输出为:

观看以上输出我们可以发现为什么输出了四个user而只调用了一次的sql语句进行查询呢?

因为在此方法中是使用的sqlSession创建的mapper,在单次的会话中mybatis会走一级缓存。

我们当然也可以查看出一句缓存是在什么范围有效

System.out.println("一级缓存范围: " + sqlSessionFactory.getConfiguration().getLocalCacheScope());

输出:

然后我们可以去看一下当我们使用注入的mapper直接的去搜索的时候会是应该什么结果代码如下所示:

@Test
void contextLoads() {
    for (int i = 0; i < 4; i++) {
        User user = userMapper.getUser(1358687078200619010L);
        System.out.println(user);
    }
}

结果输出如下,我们可以看见使用注入的mapper查询的时候会有一个问题就是在每一次查询之前都会创建一个新的sqlsession所以我们查询四次会要调用四次sql语句进行查询。

3.二级缓存

二级缓存是针对不同SqlSession直接的缓存,可以理解为mapper级别。这些SqlSession需要是同一个namespace。那namespace在哪里体现呢?

就是我们在xxMapper.xml文件中配置的namespace:

<mapper namespace="com.ltx.mybatis.Dao.UserMapper">

我们只需要在.xml文件中插入语句,就可以实现开启二级缓存了,因为在全局的二级缓存默认是开启的

<cache/>

这个时候我们再次测试刚刚的通过注入的mapper的代码看看是否还会调用多次sql语句

首先我们直接执行代码会报如下的错

报上述错是因为开启了缓存我们需要把实体类继承序列化接口,pojo类实现序列化接口是为了将缓存数据取出执行反序列化操作。也就是implements Serializable。

再次执行代码的时候我们就可以发现sql语句只调用了一次结果如下所示:

我们进行了四次查询发现缓存的命中率从0.5到0.66到0.75说明只有第一次查询了数据库,使用缓存可以大大提高持久层的效率。

但是在大型的项目中是不推荐使用二级缓存的原因如下:

  1. 缓存一致性问题:二级缓存是mapper级别的,多个mapper操作同一个表的数据,会导致缓存数据不一致。
  2. 脏读问题:如果数据库中的数据被其他用户修改,而缓存中的数据未被更新,那么使用二级缓存读取到的将是脏数据。
  3. 缓存失效问题:由于二级缓存的数据不一定总是最新的,所以对于一些实时性要求较高的数据,二级缓存可能会导致数据失效,影响业务。
  4. 缓存击穿问题:某些热点数据由于高频次的访问,可能会导致缓存击穿,影响系统性能。
  5. 配置复杂:与一级缓存相比,二级缓存的配置相对复杂,需要更多的开发和维护成本。
  6. 无法应对分布式场景:MyBatis的二级缓存是基于本地的,无法很好地应对分布式场景,这在很大程度上限制了其应用。

综上所述,虽然在某些场景下,MyBatis二级缓存能够提高查询效率,但由于存在上述问题,一般情况下在项目中不推荐使用。在实际项目中,可以结合项目具体需求和数据访问特点,选择合适的缓存策略,例如使用Redis等分布式缓存方案来替代MyBatis二级缓存。

五、动态SQL

1.if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

2.choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员挑选的 Blog)。

<select id="findActiveBlogLike"
     resultType="Blog">
  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>

3.trim、where、set

前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。

<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>
</select>

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG WHERE AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:

<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>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<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=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

4.foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

对于我们想要传入多个id进行查询就可以使用到foreach

//查询多个id的用户
List<User> listUser(@Param("idList") List<Integer> idList);
<select id="listUser" resultType="User">
      select *
      from user
      where id IN 
      <foreach collection="idList" index="index" item="id" open="(" separator="," close=")">
           #{id}
      </foreach>
</select>

执行代码可得sql语句输出为如下,所以是动态sql也就是 帮我们动态拼接的一个操作

六、mybatis底层实现

1.首先我们需要复习一些jdbc的操作

package com.ltx.mybatis;

import com.ltx.mybatis.pojo.User;

import java.sql.*;
import java.util.ArrayList;

/**
 * 此类是jdbc来实现mysql操作
 */
public class mysqlJdbc {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet resultSet = null;

        try {
            //配置驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/votedb?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai","root","wsndqd857857");
            //定义sql语句
            String sql = "select * from user where user_name = ?";
            //获取执行SQL对象
            stmt = conn.prepareStatement(sql);
            //参数赋值,注意jdbc的下标 都是从1开始
            stmt.setString(1,"罗添煦");
            //执行SQL语句
            stmt.execute();
            //获取返回结果
            resultSet = stmt.getResultSet();
            ArrayList<User> users = new ArrayList<>();
            while(resultSet.next()){
                User user =new User();
                user.setUserName(resultSet.getString("user_name"));
                user.setAge(resultSet.getInt("age"));
                user.setId(resultSet.getLong("id"));
                user.setEmail(resultSet.getString("email"));
                user.setRole(resultSet.getString("role"));
                user.setPassWord(resultSet.getString("password"));
                user.setPhone(resultSet.getString("phone"));
                users.add(user);
            }
            System.out.println("-----------"+users+"-------------");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //释放资源
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

执行的输出结果如下:

[User(id=1362299967155175426, userName=罗添煦, passWord=123, phone=123132, email=2222, age=18, role=u), 
User(id=1380808280388952066, userName=罗添煦, passWord=674f6e6d9b25a084110e992042ad0cdb, phone=18553657776, email=24423@qq.com, age=18, role=u)]

jdbc操作可以分为以下几步:

  • 加载驱动
  • 获取连接
  • 定义SQL
  • 预编译SQL
  • 赋值参数替换?
  • 执行sql
  • 获取结果集
  • 释放资源

我们可以手写一个简单的mybatis框架来先了解一下mybatis做了什么,然后就是说我们框架首先是通过动态代理的方式把mapper接口中的方法可以映射到xml文件中写的sql语句。

2.首先写一个UserMapper接口

package org.ltx;

public interface UserMapper {

    @Select("select user_name from user where id = #{id}") //这是mybatis中的xml文件的sql语句的写法
    String getUserName(@Param("id") Long id);

}

我们可以知道在mybatis中就是有上面使用到的注解,mybatis的sql也就是注解输入也就是如果注解实现的,所以我们在pom依赖中直导入了mysql驱动的情况下,要去写注解

3.注解

package org.ltx;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    String value();
}
package org.ltx;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
    String value();
}

4.实现Mapper接口的代理类

package org.ltx;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class MapperProxyFactory {

    public static  <T> T getMapper(Class<T> mapper){

        Object invocationHandler = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Connection conn = null;
                PreparedStatement stmt = null;
                ResultSet resultSet = null;

                //配置驱动
                Class.forName("com.mysql.cj.jdbc.Driver");
                //获取数据库连接
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/votedb?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai","root","wsndqd857857");
                //定义sql语句
                String sql = "select user_name from user where  id= ?";
                //获取执行SQL对象
                stmt = conn.prepareStatement(sql);
                //参数赋值,注意jdbc的下标 都是从1开始
                stmt.setLong(1,136229996715517542L);
                //执行SQL语句
                stmt.execute();
                //获取返回结果
                resultSet = stmt.getResultSet();
                String res = null;
                if (resultSet.next()){
                    res = resultSet.getString("user_name");
                }
                return res;
            }
        });
        return (T) invocationHandler;
    }
}

我们上面的代理类是通过了jdbc实现的,而我们应该是可以知道的,上面的代码只可以执行指定的sqql语句而不可以执行特定的sql语句,所以我们需要通过注解拿到注解上的sql语句

public class Main {
    public static void main(String[] args) {
        UserMapper mapper = MapperProxyFactory.getMapper(UserMapper.class);
        String userName = mapper.getUserName(1362299967155175426L);
        System.out.println(userName);
    }
}

首先看一下上面的代码的执行结果吧

现在我们要做的就是解决上面的问题如何拿到sql语句呢,这个地方很明显就是通过函数上的注解拿到的。

我们可以拿到Select注解中的value值从而拿到sql语句,代码如下所示:

Select annotation = method.getAnnotation(Select.class);
String sql = annotation.value();
System.out.println(sql);

String sql = "select user_name from user where  id= ?";
上面的sql语句是在jdbc中的,然而我们mybatis中的格式是   id = #{id}, 所以我们要让sql语句满足jdbc格式的话就需要把 #{id}变为?,所以我们现在就是要实现把#{id}变为?。

5.把#{}替换成?

package org.ltx;

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SqlReplace {
    public static String Replace(String sql) {
        ArrayList<String> strings = new ArrayList<>();
        String pattern = "\\#\\{\\w+\\}";

        Pattern r = Pattern.compile(pattern);
        Matcher m = r.matcher(sql);

        while (m.find()) {
            sql = sql.replaceAll(m.group().replace("#","#\\"),"?");
        }
        return sql;
    }
}

测试代码:

public class Main {
    public static void main(String[] args) {
        System.out.println(SqlReplace.Replace("select user_name from user where id = #{id} and phone = #{phone}"));
    }
}

输出结果为:

把字符串替换的问题解决了之后我们就要向怎么去解决参数赋值的问题

6.参数赋值

首先我们需要看jdbc中的参数是如何赋值上的

stmt.setLong(1,1362299967155175426L);
stmt.setString(2,"1231");

在jdbc中只可以通过下标赋值,然后我们又知道invoke方法中又args数组,这个数组里面有我们需要的参数的值,所以我们可以通过如下的操作给?赋值,但是这种方法也是在你知道参数格式参数类型才可以这样做的。

//参数赋值,注意jdbc的下标 都是从1开始
stmt.setLong(1, (Long) args[0]);
stmt.setString(2, (String) args[1]);

所以我们首先要拿到所有的#{里面对应的值}存在一个列表中,然后还要拿到函数参数名以及其对应的值存在一个map中

HashMap<String, Object> nameMap = new HashMap<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
    Parameter parameter =parameters[i];
    String name = parameter.getAnnotation(Param.class).value();
    nameMap.put(name,args[i]);
}
ArrayList<String> list = SqlReplace.ReplaceList(sql);
    public static ArrayList<String> ReplaceList(String sql) {
        ArrayList<String> strings = new ArrayList<>();
        String pattern = "\\#\\{\\w+\\}";

        Pattern r = Pattern.compile(pattern);
        Matcher m = r.matcher(sql);
        ArrayList<String> strings1 = new ArrayList<>();

        while (m.find()) {
            strings1.add(m.group().replace("#{","").replace("}",""));
        }

        return strings1;
    }

得到后我们就可以去给?赋值了通过遍历列表,拿到列表中的值,查询map中key对应的value值,然后判读value是什么类型的选择不同的赋值方法具体代码如下但是不是完整的

for (int i = 0; i < list.size(); i++) {
    Object value = nameMap.get(list.get(i));
    if(value.getClass() == String.class){
        stmt.setString(i+1, (String) value);
    }else  if(value.getClass() == Long.class){
        stmt.setLong(i+1, (Long) value);
    }
}

写到这个地方简单的mybatis也就是写完了测试代码如下:

public class Main {
    public static void main(String[] args) {
        UserMapper mapper = MapperProxyFactory.getMapper(UserMapper.class);
        String userName = mapper.getUserName(1362299967155175426L,"123132");
        System.out.println(userName);
    }
}

结果为:

七、mybatis源码解析

MyBatis 的源码执行过程大致可以分为以下几个步骤:

  1. 加载配置并初始化: 这个阶段主要是加载 mybatis 的配置文件,包括全局配置文件和映射文件。然后创建 SqlSessionFactoryBuilder,使用配置文件的输入流创建 SqlSessionFactory。
  2. 创建 SqlSession: 在这个阶段,使用 SqlSessionFactory 创建一个 SqlSession。SqlSession 是 MyBatis 的核心接口,用于执行命令,获取映射器,以及管理事务。
  3. 创建 Mapper: 使用 SqlSession 创建 Mapper(映射器)。Mapper 是绑定映射语句的接口,你可以通过它来调用 SQL 语句。MyBatis 通过使用 JDK 的动态代理来实现 Mapper。
  4. 执行 SQL: 通过 Mapper 执行 SQL 语句。这里可以是查询,插入,更新或删除操作。
  5. 处理结果: 如果是查询操作,MyBatis 会将查询结果映射到 Java 对象上,然后返回。如果是插入、更新或删除操作,MyBatis 会返回执行的操作影响的行数。
  6. 关闭 SqlSession: 操作完成后,需要关闭 SqlSession。如果不关闭,可能会导致资源泄露。

流程把以下的图结合手写源码就可以更加清晰的理解了。

下面的可以看这个网站:Mybatis2 解析流程-ProcessOn

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

用草书谱写兰亭序

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值