解析 MyBatis 中 Mapper 生效的来龙去脉

目录
  • 一、MyBatis基本使用
    • 1.1 编写配置文件
    • 1.2 编写Mapper接口及测试方法
    • 1.3 结果
  • 二、源码分析
    • 2.1 通过配置文件构建出 SqlSessionFactory
    • 2.2 获取 SqlSession 对象
    • 2.3 根据 SqlSession 获取 Mapper 代理
    • 2.4 通过 Mapper 代理,执行方法操作数据库
    • 2.5 整体流程图

最近闲了快有半个多月了,一直提不起兴致再去看一些书籍(没有以前疯狂吸食知识的欲望了😓)。

不过这一两天不知道是什么筋搭错了非常想写点什么,但又不知道写点啥(苦恼)。所以我就结合了一下本人工作中经常用到但没有深入的技术下手了,最后思来想去就选择了 MyBatis 中 Mapper 文件这一块的知识内容入手了。

以前只是知道写一个 Mapper 接口,对应着再去写一个 Mapper.xml 文件然后将 Mapper 接口位置和 Mapper.xml 文件位置通过 MyBatisConfig.xml 的配置文件关联起来就可以非常方便的操作访问数据库,但究其原因确是说不上个所以然来(汗颜)。

那既然搞出了前因,后面就一起往下学咯!

一、MyBatis基本使用

一切都从最简单的开始,所以先来回顾一下其基本的使用(不会吧不会吧,最基本的hello world别忘了)。

步骤:

1、首先我们要创建一个maven工程

2、添加MyBatis的依赖及MySQL依赖,如下:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

3、再添加一个测试单元依赖吧,等会要通过测试单元进行测试

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

OK,到这一步项目的基本环境就搭建完毕了,下面就是正式的使用 MyBatis 框架相关的内容了。

1.1 编写配置文件

在资源目录下面创建下面两个配置文件:

这里我们先准备数据库连接信息的配置类:jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatistest?useUnicode=true&amp;characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

接着就是最重要的一个配置类了:MyBatisConfig.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>
    <!-- 导入数据库配置文件的信息-->
    <properties resource="jdbc.properties"></properties>
    <!-- 配置setting属性-->
    <settings>
        <!-- 开启了一个驼峰命名规则-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"></setting>
    </settings>
    <!-- 配置数据库-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 配置连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--  mappers中注册我们所有写的dao接口的实现(映射)文件-->
    <mappers>
        <mapper resource="/.../IUserMapper.xml"/>
        <!-- 如果映射文件有十几百个的话,可以用下面的全局注册
    		<package name="文件所在包路径"></package>
    		<package name="cn.liuliang.Dao"></package>
    	-->
    </mappers>
</configuration>

1.2 编写Mapper接口及测试方法

Mapper接口类

public interface IUserMapper {

    /**
     * 查询所有用户
     * @return
     */
     List<User> findAll();

}

开始测试

public class MyBatisTest {

    @Test
    public void test01() throws IOException {

        // 读取配置文件
        InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
        // 创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        // 获得会话
        SqlSession session=sqlSessionFactory.openSession();
        // 得到代理
        IUserMapper iUserMapper =session.getMapper(IUserMapper.class);
        // 查询数据库
        List<User> userList= iUserMapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }
    }

}

1.3 结果

SQL和结果都打印出来了😁。

以后,只要是对数据库的操作,我们就只需要编写 Mapper 接口和其对应的 xml 文件就可以非常快速操作数据库,对比以前原生JDBC操作什么拼接SQL、结果集映射、资源关闭一大堆操作让我们开发人员来处理,也太鸡肋了吧!所以对于这个 MyBatis 持久层框架我只想说(牛逼)。

下面就要全程高能哦!但其实也很简单了,它就只是把原生操作的 JDBC 进行了封装,暴露出按照它所定义的简单规则走而已,多的不说了,你们有资格一睹 MyBatis 源码的芳容了。

二、源码分析

既然要分析源码了,那么从什么地方入手呢!— 测试方法

通过测试方法,我们可以知道 MyBatis 会先加载资源文件(MyBatisConfig.xml),因为这文件是一切的开始,通过这个文件可以知道数据源、特性(日志,驼峰命名…)、Mapper 文件等一系列信息。

2.1 通过配置文件构建出 SqlSessionFactory

第一个类名出现了:SqlSessionFactory ,它的类图如下:

简单熟悉一下图中出现的名字吧:

SqlSessionFactory接口:SqlSessionFactory 负责创建 SqlSession 对象,其中只包含了多个 openSession() 方法的重载,可以通过其参数指定事务的隔离级别、底层使用 Executor 的类型以及是否自动提交事务等方面的配置。

DefaultSqlSessionFactory类:一个具体的工厂,实现了 SqlSessionFactory 接口。它主要提供了两种创建 DefaultSqlSession 对象的方式:

  1. 通过数据源获取数据库连接,并创建 Executor 对象及 DefaultSqlSession 。
  2. 通过用户提供的数据连接对象,DefaultSqlSessionFactory 会使用该数据库连接对象创建 Executor 对象及 DefaultSqlSession。

SqlSessionManager类:同时实现了 SqlSession 接口和 SqlSessionFactory 接口 ,也就同时提供了SqlSessionFactory 创建 SqlSession 以及 SqlSession 操纵数据库的功能。

SqlSession接口:是mybatis的核心操作类,其中对数据库的crud都封装在这个中,是一个顶级接口,其中默认实现类是DefaultSqlSession这个类。

  • DefaultSqlSession类:默认 SqlSession 接口的 CRUD 实现类,且 DefaultSqlsession 不是线程安全的(对于线程安全,关注session和connnect的关系就好了)

好了开始分析,从第一行代码入手:

// 读取配置文件
InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
// 创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSessionFactoryBuilder # build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    // ...
    // 根据文件流,创建 XMLConfigBuilder 对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // 先解析 配置文件,然后构建出 SqlSessionFactory对象
    return build(parser.parse());
    // ...
}

最终会创建一个 DefaultSqlSessionFactory 对象返回出去

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

流程如下:

2.2 获取 SqlSession 对象

在获取到会话工厂之后,就是根据工厂获得具体的会话了。

代码入口:

// 获得会话
SqlSession session=sqlSessionFactory.openSession();

调用:DefaultSqlSessionFactory # openSession()

public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

最终来到:DefaultSqlSessionFactory # openSessionFromDataSource()

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 根据配置文件 configuration 获取对应的会话环境(包括事物,数据源)
        final Environment environment = configuration.getEnvironment();
        // 获取事物工厂
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 根据数据源,配置事物,autoCommit:是否自动提交事物
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 根据配置获取执行器(最终都是它执行对应的数据库操作)
        final Executor executor = configuration.newExecutor(tx, execType);
        // 准备好上面的信息之后,都封装到默认会话对象中返回出去
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

在获取 SqlSession 对象的过程中,都是根据默认的会话工厂,从工厂中获取对应的会话。这样在我看来非常的不错,因为获取一个数据库的操作会话是需要配置非常多的属性的,包括数据源配置、事物配置等。但是有了这个创建会话工厂类之后,那么一切就变得简单起来了,工厂囊括了所有的细节,只需要我们调一个对外的 API 我们就可以获得对应的 SqlSession 对象(工厂帮我们做了细节),进而操作数据库,读了上面的代码就是一个很好的提现😀。

提一点:

配置文件(MyBatisConfig.xml)构造出默认会话工厂(SqlSessionFactory),工厂再创建出具体的操作数据库会话(SqlSession)

2.3 根据 SqlSession 获取 Mapper 代理

在上面,已经分析了如何获取一个会话的源码,那我们得到一个会话之后,就是要根据具体的 Mapper 接口获得对应的操作数据库代理对象了,就是下面这段代码:

// 得到代理
IUserMapper iUserMapper =session.getMapper(IUserMapper.class);

点进去看看

因为 session 对象是由 DefaultSqlSessionFactory 创建出来的 DefaultSqlSession,所以该代码位于此类中

public <T> T getMapper(Class<T> type) {
    // 根据配置类,获取 Mapper
    return configuration.getMapper(type, this);
}

点进去:Configuration # getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从 mapperRegistry 中获取具体 Mapper
    return mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry:可以理解为 Mapper 接口的注册中心,里面存放了所有 Mapper 接口相关属性。

MapperRegistry# getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // knownMappers,一个Map,存放 Mapper 代理工厂
    // 在初始化的时候根据配置文件已经将所有配置的 Mapper 接口注册到此了
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 具体代理生成
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

点进具体代理:MapperProxyFactory # newInstance

public T newInstance(SqlSession sqlSession) {
    // 根据 SqlSession 和 Mapper 接口生成代理对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 真正代理,如下
    return newInstance(mapperProxy);
}
// 下面就是根据 JDK 原生 API 进行代理了,由此返回代理对象给用户使用
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

以上就是 Mapper 接口被代理的全部流程了,其中先是根据会话去获得对应的 Mapper 但其内部调用的是 Mapper 注册中心(MapperRegistry)获取,这里面有所有配置的 Mapper 接口,在 MapperRegistry 中维护了一个 Map 键为 Class 值是 MapperProxyFactory ,这样就可以获得要代理 Mapper 接口的代理工厂,最后通过这个工厂生成我们想要的 Mapper 返回用户。

流程不复杂,就是里面出现了很多 MapperXXX 相关的类,那么下面我梳理一下这些类关系图如下:

对于具体的代理执行类这一步就要到执行这一块了,当用户通过我们返回的代理类(Mapper 接口)执行对应方法时,就会走到图中涉及的类。

按照惯例,来个流程图吧!

2.4 通过 Mapper 代理,执行方法操作数据库

上面的所有分析,都是为了等到一个具体的操作数据库的一个桥梁,那就是 Mapper 代理了(iUserMapper)。

接下来就是分析最后一步了,真正操作数据库,代码如下:

// 查询数据库
List<User> userList= iUserMapper.findAll();
for (User user : userList) {
    System.out.println(user);
}

对于 iUserMapper 对象,我们知道他是代理去执行的,所以直接点进去的话根本行不通,那么我们可以通过 Debug 进去看看。

MapperProxy # invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 方法的类为 object 直接通过原始 JDK 去执行
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            // 根据方法,获得方法的执行器后再执行代理方法
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

我们先进入 MapperProxy # cachedInvoker 这个方法看看

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        // 先查缓存,有就返回,没有就创建
        MapperMethodInvoker invoker = methodCache.get(method);
        if (invoker != null) {
            return invoker;
        }

        return methodCache.computeIfAbsent(method, m -> {
            // ...
            // 返回 PlainMethodInvoker 类型的 Mapper 方法执行器
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            // ...
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

接着进入 PlainMethodInvoker# invoke 这个方法

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    // 调用 mapperMethod 对象的 execute 方法去真正执行了
    return mapperMethod.execute(sqlSession, args);
}

真正执行的开始 execute

MapperMethod # execute

public Object execute(SqlSession sqlSession, Object[] args) {
    // 这里面内容比较多,我简单分析一下
    // 1)封装参数
    // 2)根据对应的执行类型(INSERT,UPDATE,DELETE,SELECT),执行对应的方法
    // 3)根据参数,执行类型封装对应的 sql
    // 4)操作原生 JDBC API 执行数据库操作
    // 5)封装结果集,返回出去
}

我们 Debug 这个方法最后一步,看看结果:

到此,我们的 Mapper 接口及文件生效的原理,就全部过了一边,是不是觉得不是很难呢!

在分析这一块源码时,本人理解的步骤就是:

  • 一步步点进源码看。
  • 画出流程图,不清楚的就 Debug。
  • 很重要一点,对很多出现类似名字的类,一定要画出类图,搞清楚关系在往下走(助于理解每个类的职责)。
  • 最后,那就是写点笔记了,毕竟好记性不如烂笔头。

2.5 整体流程图

到此这篇关于解析 MyBatis 中 Mapper 生效的前因后果的文章就介绍到这了,更多相关 MyBatis 中 Mapper 生效内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解mybatis-plus的 mapper.xml 路径配置的坑

    mybatis-plus今天遇到一个问题,就是mybatis 没有读取到mapper.xml 文件. 特此记录一下,问题如下: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.husy.mapper.SystemUserMapper.findUserByName at com.baomidou.mybatisplus.core.override.MybatisMapperMe

  • 解决Mybatis在IDEA中找不到mapper映射文件的问题

    刚开始在IDEA中做用Mybatis查数据库中的数据时,报mapper映射文件找不到,害的我检查了好几次配置的路径是不是正确,但是看了好几遍都没有发现有拼写错误.(我记得以前在eclipse中这样写是没问题的) <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" &qu

  • 解决Mybatis中mapper.xml文件update,delete及insert返回值问题

    最近写了几个非常简单的接口(CRUD),在单元测试的时候却出了问题,报错如下: Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'messageListener': Unsatisfied dependency expressed through field 'reviewCheckInfoService'; nested exce

  • Mybatis-Plus BaseMapper的用法详解

    1.如何使用BaseMapper进行数据库的操作. 2.使用BaseMapper进行插入实体时如何让UUID的主键自动生成. Student实体类,其中id属性主键为UUID package com.huixiaoer.ant.api.model.bean; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; public class Stud

  • MyBatis直接执行SQL的工具SqlMapper

    可能有些人也有过类似需求,一般都会选择使用其他的方式如Spring-JDBC等方式解决. 能否通过MyBatis实现这样的功能呢? 为了让通用Mapper更彻底的支持多表操作以及更灵活的操作,在2.2.0版本增加了一个可以直接执行SQL的新类SqlMapper. 我们来了解一下SqlMapper. SqlMapper提供的方法 SqlMapper提供了以下这些公共方法: Map<String,Object> selectOne(String sql) Map<String,Object&

  • Mybatis MapperScannerConfigurer自动扫描Mapper接口生成代理注入到Spring的方法

    前言 Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到Spring Mybatis在与Spring集成的时候可以配置 MapperFactoryBean来生成Mapper接口的代理. 例如: <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mappe

  • 详解mybatis-plus配置找不到Mapper接口路径的坑

    mybatis-plus今天遇到一个问题,就是mybatis 没有读取到mapper.xml 文件. 特此记录一下,问题如下: at com.baomidou.mybatisplus.core.override.MybatisMapperMethod$SqlCommand.<init>(MybatisMapperMethod.java:242) at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.<init>(

  • 解决SpringBoot整合Mybatis扫描不到Mapper的问题

    闲来无事,想学学springboot,开始搭建一个项目,但是一直显示mapper扫描不到的错误: "Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController': Unsa

  • 如何自动生成Mybatis的Mapper文件详解

    前言 工作中使用mybatis时我们需要根据数据表字段创建pojo类.mapper文件以及dao类,并且需要配置它们之间的依赖关系,这样的工作很琐碎和重复,mybatis官方也发现了这个问题,因此给我们提供了mybatis generator工具来帮我们自动创建pojo类.mapper文件以及dao类并且会帮我们配置好它们的依赖关系. 实际上,最非常流行MyBatis-Plus中内置了代码生成器:采用代码或者 Maven 插件可快速生成 Mapper . Model . Service . Co

  • 解析 MyBatis 中 Mapper 生效的来龙去脉

    目录 一.MyBatis基本使用 1.1 编写配置文件 1.2 编写Mapper接口及测试方法 1.3 结果 二.源码分析 2.1 通过配置文件构建出 SqlSessionFactory 2.2 获取 SqlSession 对象 2.3 根据 SqlSession 获取 Mapper 代理 2.4 通过 Mapper 代理,执行方法操作数据库 2.5 整体流程图 最近闲了快有半个多月了,一直提不起兴致再去看一些书籍(没有以前疯狂吸食知识的欲望了

  • 关于MyBatis中Mapper XML热加载优化

    前几天在琢磨mybatis xml热加载的问题,原理还是通过定时扫描xml文件去跟新,但放到项目上就各种问题,由于用了mybatisplus死活不生效.本着"即插即用"的原则,狠心把其中的代码优化了一遍,能够兼容mybatisplus,还加入了一些日志,直接上代码 package com.bzd.core.mybatis; import java.io.File; import java.io.FileNotFoundException; import java.io.IOExcept

  • Mybatis中mapper.xml实现热加载介绍

    目录 背景 目的 实现方式 总结 背景 有些需求可能更新sql的频率较高,但又不想频繁发布java应用程序,所以mybatis-mapper.xml热加载的需求顺势而出. 目的 只需调起加载mapper.xml的程序,无需重启整个java应用,低耦合. 实现方式 mapper.xml可以指定路径.如springboot工程resources目录下:亦可独立维护在某个git仓库,然后由程序加载到运行机器上去.具体加载git仓库到运行机器代码如下: package com.jason.git; im

  • Mybatis中Mapper标签总结大全

    一.标签分类 定义SQL语句 insert delete update select 配置关联关系 collection association 配置java对象属性与查询结果集中列名的对应关系 resultMap 控制动态SQL拼接 foreach if choose 格式化输出 where set trim 定义常量 sql 其他 include 二.标签总结 1. 基础SQL标签 1.1 查询select 标签属性 id 唯一的名称,对应dao中mapper的接口名称 paramterTy

  • Mybatis中Mapper映射文件使用详解

    紧接上文所述,在这篇文章中我将对Mapper映射文件进行详细的说明. Mapper映射文件是一个xml格式文件,必须遵循相应的dtd文件规范,如ibatis-3-mapper.dtd.我们先大体上看看支持哪些配置?如下所示,从Eclipse里截了个屏: 从上图可以看出,映射文件是以<mapper>作为根节点,在根节点中支持9个元素,分别为insert.update.delete.select(增删改查);cache.cache-ref.resultMap.parameterMap.sql. 下

  • Mybatis 中Mapper使用package方式配置报错的解决方案

    踩了个坑,写出来 Mybatis 中Mapper使用package方式配置报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) UserDaoTest中调用了UserDao的insert方法. 1.项目结构如下 2.UserDao接口 package com.mybatis.dao; import org.apache.ibatis.annotations.Mapper; import

  • MyBatis中Mapper的注入问题详解

    在 SpringBoot 体系中,MyBatis 对 Mapper 的注入常见的方式我知道的有 2 种: 1.@MapperScan MapperScan 类是 mybatis-spring 包里面的. 通过在启动类上使用 @MapperScan,然后通过 basePackages 属性指定 Mapper 文件所在的目录来进行扫描装载,默认情况下指定目录下的所有.java文件都会被当做 Mapper 来加载处理. @MapperScan(basePackages = "com.test.spri

  • springboot实现指定mybatis中mapper文件扫描路径

    目录 指定mybatis中mapper文件扫描路径 mybatis配置多个扫描路径写法 指定mybatis中mapper文件扫描路径 所有的mapper映射文件 mybatis.mapper-locations=classpath*:com/springboot/mapper/*.xml 或者resource下的mapper映射文件 mybatis.mapper-locations=classpath*:mapper/**/*.xml mybatis配置多个扫描路径写法 百度得到,但是很乱,稍微

  • 解决Mybatis中mapper的ID冲突问题

    mapper 的id冲突原因: 原因一: 在同一个mapper.xml中存在相同的ID 原因二: 同时使用了xml配置和注解配置 解决方案: 只保留xml或者注解即可!!! Mybatis mapper文件下同一id 查询结果列不同问题 场景描述: 订单数据按天分表,正常情况下每一天的表结构都是一样的,表名命名格式order_yyyyMMdd. 定义一个查询如下: <select id="orderSelect" parameterClass="java.util.Ha

随机推荐