Mybatis

Posted top啦它

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis相关的知识,希望对你有一定的参考价值。

JDBC执行流程

https://blog.csdn.net/YJT180/article/details/99828947


Mybatis执行流程


详细来看就是

首先学习会话和执行器两部分

Mybatis核心执行组件——会话(SqlSession)

首先先看Executor最简单的实现——SimpleExecutor

先引入Mybatis

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
package com.example.xuexitongtwo;

import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransaction;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
class XuexitongtwoApplicationTests 

    private JdbcTransaction jdbcTransaction;
    private Configuration configuration;
    private Connection connection;
    @Test
    public void test1() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        SimpleExecutor executor = new SimpleExecutor(configuration,jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.selectIdByUsername");
        // 之所以需要再传一次参数名,是因为没有使用executor.query
        List<Object> es = executor.doQuery(ms, "a1", RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER ,ms.getBoundSql("a1"));
        executor.doQuery(ms, "a1", RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER ,ms.getBoundSql("a1"));
        System.out.println("id:"+es.get(0));
    

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!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="$spring.datasource.driver-class-name"></property>
            <property name="url" value="$spring.datasource.url"></property>
            <property name="username" value="$spring.datasource.username"></property>
            <property name="password" value="$spring.datasource.password"></property>
        </dataSource>
    </environment>
</environments>
<mappers>
    <mapper resource="mapper/testmapper.xml"/>
</mappers>
</configuration>

运行测试类后输出:

可以看到一共预编译了两次。因为我使用的是SimpleExecutor

如果我没有执行两次,而是执行两百次,那么每一次都要进行预处理,是非常耗时并且浪费性能的。

那么可以使用下面的可重用执行器

可重用执行器——ReuseExecutor

simple每次调用都会创建一个preparestatement,然后去预编译。而resue使用了一个map来存statement,每次调用,直接从map里找,找到了就复用,不用再去编译sql。
ReuseExecutor代码如下:

//    可重用执行器
    @Test
    public void test2() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        ReuseExecutor executor = new ReuseExecutor(configuration,jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.selectIdByUsername");
        // 之所以需要再传一次参数名,是因为没有使用executor.query
        List<Object> es = executor.doQuery(ms, "a1", RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER ,ms.getBoundSql("a1"));
        executor.doQuery(ms, "a1", RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER ,ms.getBoundSql("a1"));
        System.out.println("id:"+es.get(0));
    

输出如下:

可见,只预编译了一次。

如果要执行大量的修改操作,如果要执行一万条修改操作,一条一条的执行的话,就要执行一万次,有没有其他方法呢?怎么做?这里就引出了批处理执行器——BatchExecutor(比如说:先处理一百条,处理完后在执行一百条)

批处理执行器——BatchExecutor

    @Test
    public void test3() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        BatchExecutor executor = new BatchExecutor(configuration,jdbcTransaction);
                MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.selectIdByUsername");
        // 之所以需要再传一次参数名,是因为没有使用executor.query
        List<Object> es = executor.doQuery(ms, "a1", RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER ,ms.getBoundSql("a1"));
        executor.doQuery(ms, "a1", RowBounds.DEFAULT,
                SimpleExecutor.NO_RESULT_HANDLER ,ms.getBoundSql("a1"));
        System.out.println("id:"+es.get(0));
    


可以看到,预编译了两次,明明使用了批处理执行器,那为什么出现了两次呢?这是因为BatchExecutor仅在进行改操作时才会进行批处理。

下面进行修改操作

@CacheNamespace(blocking = true)
public interface TestMapper 

    public Integer selectIdByUsername(String username);

    @Update("update user set email=#arg1 where username=#arg0")
    int setEmail(String username,String email);

    @Test
    public void test3() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        BatchExecutor executor = new BatchExecutor(configuration,jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.setEmail");
        Map param = new HashMap();
        param.put("arg0","a1");
        param.put("arg1","emailss");
        executor.doUpdate(ms,param);
        executor.doUpdate(ms,param);
    

之后查看数据库,发现并没有修改数据。因为批处理操作需要手动刷新。
代码如下:

    @Test
    public void test3() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        BatchExecutor executor = new BatchExecutor(configuration,jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.setEmail");
        Map param = new HashMap();
        param.put("arg0","a1");
        param.put("arg1","emailss");
        executor.doUpdate(ms,param);
        executor.doUpdate(ms,param);
        executor.doFlushStatements(false);//执行刷新
    

数据库数据被修改

执行了一次预处理,两次数据填充



不是说Executor中会用到到缓存吗?我发现里面并没有使用到缓存。
下面看

因为要在SimpleExecutor和ReuseExecutor和BatchExecutor中都使用缓存,所以给他们继承了同一个父类BaseExecutor



在BaseExecutor中实现了一级缓存,以及获取连接的方法。因为上面我都是直接调用的这三个实现类,所以没有使用到缓存。
在BaseExecutor中实现了两个方法query和update


在query中调用了抽象方法doQuery
在update中调用了抽象方法doUpdate
如下:



doQuery和doUpdate需要在SimpleExecutor和ReuseExecutor和BatchExecutor中实现。

执行query方法

    @Test
    public void test4() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        Executor executor = new SimpleExecutor(configuration,jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.selectIdByUsername");
        executor.query(ms,"a1",RowBounds.DEFAULT,Executor.NO_RESULT_HANDLER);
        executor.query(ms,"a1",RowBounds.DEFAULT,Executor.NO_RESULT_HANDLER);
    

运行结果

可以发现在使用的是SimpleExecutor执行器的情况下,因为使用到了一级缓存,所以只预编译了一次。并且也只进行了一次调用。
我看下ReuseExecutor,发现执行结果也是这样。因为第二次调用会走缓存的逻辑。

    @Test
    public void test4() throws SQLException, IOException 
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory build = factoryBuilder.build(is);
        configuration = build.getConfiguration();
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xuexitong?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC","root","root");
        jdbcTransaction = new JdbcTransaction(connection);
        Executor executor = new ReuseExecutor(configuration,jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.example.xuexitongtwo.mapper.TestMapper.selectIdByUsername");
        executor.query(ms,"a1",RowBounds.DEFAULT,Executor.NO_RESULT_HANDLER);
        executor.query(ms,"a1",RowBounds.DEFAULT,Executor.NO_RESULT_HANDLER);
    


下面进行调试。

1、

executor.query(ms,"a1",RowBounds.DEFAULT,Executor.NO_RESULT_HANDLER);

2、

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException 
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 

3、

  @Override
  public <E> List<E以上是关于Mybatis的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis 缓存

MyBatis缓存

Mybatis 系列5

MyBatis源码浅析

MyBatis源码浅析

简述Mybatis