MyBatis 应用的组成

王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人

大家好,我是王有志。在上一篇文章的最后,我们写了一个简单的例子,今天我们就通过这个例子来看一看一个标准的 MyBatis 应用程序由哪些组件组成。

最后,文末会解答小伙伴在私信中提出的问题:当存在多个 Mapper.xml,且每个文件中都有同名的查询语句时在 MyBatis 应用中该如何进行查询?

Tips:文章中的示例,指的是《MyBatis 入门》正文中出现的简单示例和附录中“不使用 XML 构建 SqlSessionFactory”的例子,如无特别说明,默认为正文中的简单示例。

MyBatis 应用的组成

我们先来回忆一下构建简单示例的过程:

  1. 创建数据对象 UserDO,用于映射数据库中的 user 表;
  2. 创建接口 UserDAO,作为 MyBatis 映射器的命名空间;
  3. 创建映射器文件 UserMapper.xml,并编写了查询全部 user 表数据的 SQL 语句
  4. 创建 MyBatis 的核心配置文件 mybatis-config.xml,配置了数据库信息和映射器

以上的 4 步是我们在开始使用 MyBatis 前进行的前期配置工作,接下来是我们在应用程序中使用 MyBatis 的步骤:

  1. 通过 Resources 读取 mybatis-config.xml 文件,获取 Reader 对象;
  2. 通过 Reader 对象构建出 SqlSessionFactory,即 SQL 会话工厂;
  3. 通过 SqlSessionFactory 获取 SqlSession,即 SQL 会话
  4. 通过 SqlSession 执行 UserMapper.xml 中的 SQL 语句,并获取到查询结果。

这 4 步是我们在应用程序中使用 MyBatis 的过程,综合以上两步的内容我们大概可以构建出如下图所示的 MyBatis 应用的基本组成:

图中的部分组件已经在我们的示例中出现过了, 但是如 Executor,MappedStatement,Configuration,ResultHandler 等组件并没有在我们的示例中出现。这是因为它们大都出现在 SqlSession 和 SqlSessionFactory 内部封装的调用过程中,因此我们在平时使用时可能“见不到”它们,但这并不是说它们不重要,相反它们是 MyBatis 中起到关键作用的组件。

关于它们我还会在 MyBatis 系列的源码篇着重进行分析,不过在此之前,我会按照图中自下向上的顺序,逐一对这些组件的作用做一个简单的说明。

Tips:Reader 是 Java 中 io 包下的工具类,因此在下文中不会出现关于 Reader 的内容。

Mapper.xml

Mapper.xml 是 MyBatis 的核心之一,是用于定义 SQL 语句和映射规则的 XML 文件,由核心配置文件 mybatis-config.xml 加载到 MyBaits 应用程序中。

Mapper.xml 的主要作用包括:

  • 定义 SQL 语句:MyBatis 的 SQL 语句编写在 Mapper.xml 中(MyBatis 也支持通过注解的方式编写 SQL 语句),通过 MyBatis 提供的 XML 标签可以实现动态查询条件和嵌套查询等复杂的 SQL 语句;
  • 映射结果集到 Java 对象:通过 MyBatis 的标签可以实现数据库表中的字段与 Java 对象中的字段的映射关系,可以实现一对一,一对多等复杂关系的映射;
  • 接口方法绑定:Mapper.xml 中定义的 SQL 语句可以通过标签中的 id 字段与对应的 Mapper 接口中的方法进行绑定,通过调用 Mapper 接口的方法 MyBatis 将会执行 Mapper.xml 中的 SQL 语句。

下面我们对之前的示例稍作修改,来感受下 MyBatis 的中 Mapper.xml 与 Mapper 接口的方法绑定。

首先,在 UserMapper.xml 中定义一个新的查询语句,用于查询 id = 1 的用户:

<select id="selectFirstUser" resultType="com.wyz.entity.UserDO" >select user_id, name, age, gender, id_type, id_number from user where user_id = 1
</select>

Tips:通常我们不会在 Mapper.xml 中编写如user_id = 1这类硬编码,这里仅仅是为了举例说明,千万不要学~~~

接着我们修改 UserDAO 接口,添加两个对应的方法声明:

public interface UserMapper {List<UserDO> selectAll();UserDO selectFirstUser();
}

最后我们修改测试代码,通过 SqlSession 实例获取 UseDAO 接口的实例,并调用接口中的方法:

@Test
public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();UserDAO userDAO = sqlSession.getMapper(UserDAO.class);List<UserDO> users = userDAO.selectAll();for(UserDO user : users) {System.out.println(user.getName());}UserDO user = userDAO.selectFirstUser();System.out.println(user.getName());
}

可以看到,这里我们通过 SqlSession 实例获取到接口 UserDAO 的实例,分别调用了接口中的方法并能够成功获取到数据,这表明我们已经将 UserMapper.xml 中编写的 SQL 语句与 UserDAO 接口中的方法绑定到了一起。

关于 Mapper.xml 的更多用法,我会在 MyBatis 系列的第 4 篇文章中和大家分享。

mybatis-config.xxml

mybatis-config.xml 是 MyBatis 应用中的核心配置文件,该文件中包含了 MyBatis 应用程序在运行时所需要的各种配置信息。

示例中,我只做了最基础的环境配置(数据库事务管理器配置,数据源配置)和映射器配置(加载映射器 UserMapper.xml),但实际上 mybatis-config.xml 中还提供了非常多的配置内容,如:别名配置(使用 typeAliases 标签),插件配置(使用 plugins 标签)和对象工厂配置(使用 objectFactory 标签)等等。

关于 mybatis-config.xml 的更多用法,我会在 MyBatis 系列的第 3 篇文章中和大家分享。

Resources

MyBatis 提供的资源加载工具,用于各种资源文件的加载和访问。Resources 提供了良好的封装,使用起来非常简单,只需要通过相对路径,即可将资源文件加载到应用程序中。

XMLConfigBuilder

XMLConfigBuilder 继承自 BaseBuilder,负责解析 MyBatis 中的 XML 配置文件(mybatis-config.xml),并通过调用XMLConfigBuilder#parse方法构建出 Configuration 对象。

BaseBuilder 有多个子类:

BaseBuilder体系.png

BaseBuilder 的子类分别负责解析不同的文件,如:XMLConfigBuilder 负责解析 mybatis-config.xml 文件,XMLMapperBuilder 负责解析 Mapper.xml 文件等等。

Configuration

Configuration 是核心配置文件 mybatis-config.xml 在 Java 应用程序中的体现,是 MyBatis 在整个运行周期中的配置信息管理器,包含了 MyBatis 运行期间所需要的全部配置信息和映射器。

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 使用了建造者模式,用来根据配置信息生成 SqlSessionFactory。SqlSessionFactoryBuilder 提供了多个SqlSessionFactoryBuilder#build的重载方法,分别接受 Reader,InputStream 和 Configuration 三种方式输入的配置信息。

示例中,我们已经在SqlSessionFactoryBuilder#build方法中使用了 Reader 和 Configuration,而使用 InputStream 的方式与使用 Reader 的方式一模一样,代码如下所示:

@BeforeClass
public static void init() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);inputStream.close();
}

SqlSessionFactoryBuilder 的唯一作用是创建 SqlSessionFactory,当它完成了这个使命后,我们就应该毫不犹豫的抛弃它,因此 SqlSessionFactoryBuilder 应该作为方法内的局部变量出现,生命周期仅在这个方法中,就像示例中的那样。

SqlSessionFactory

SqlSessionFactory 是 MyBatis 中的接口,也是 MyBatis 的核心组件之一,SqlSessionFactory 使用了工厂方法,定义了 MyBatis 获取 SqlSession 的规范。MyBatis 官方对于 SqlSessionFactory 的定位是每个 MyBatis 应用的核心:

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。

SqlSessionFactory 作为 MyBatis 应用程序中的核心,生命周期与整个 MyBatis 应用程序相同,随着应用的创建而创建,应用的停止而销毁。

SqlSessionFactory 有两个实现类:

SqlSessionFactory体系.png

DefaultSqlSessionFactory 是 SqlSessionFactory 的默认实现类,用于获取非线程安全的 SqlSession 实例,通过 DefaultSqlSessionFactory 获取的 SqlSession 实例在使用时还需要手动关闭(同时会提交事务),即调用SqlSession#close方法。

SqlSessionFactory 接口提供了多个SqlSessionFactory#openSession的重载方法:

public interface SqlSessionFactory {SqlSession openSession();SqlSession openSession(boolean autoCommit);SqlSession openSession(Connection connection);SqlSession openSession(TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType);SqlSession openSession(ExecutorType execType, boolean autoCommit);SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType, Connection connection);
}

使用无参的SqlSession#openSession方法可以获取具有如下特性的 SqlSession 实例:

  • 不会自动提交数据库事务;
  • 通过 mybatis-config.xml 配置的数据源获取的 Connection 实例;
  • 使用数据源默认的事务隔离级别;
  • 不会复用预处理语句,也不会批量进执行更新语句。

那么对于其它SqlSession#openSession重载方法中的参数,我们能够很轻松得想到它们的作用:

  • boolean autoCommit,设置 SqlSession 是否自动提交
  • Connection connection,设置 SqlSession 中使用的 Connection 实例(允许通过其它数据源获取)
  • TransactionIsolationLevel level,设置 SqlSession 中使用的事务隔离级别

至于 ExecutorType 参数,它是用来选择 MyBatis 执行器的,MyBatis 中定义了 3 种类型的执行器:

  • ExecutorType#SIMPLE,该执行器会为每条 SQL 语句创建新的 PreparedStatement 实例;
  • ExecutorType#REUSE,该执行器会复用 PreparedStatement 实例;
  • ExecutorType#BATCH,该执行器会批量执行所有更新语句。

使用哪个SqlSession#openSession的重载方法,需要我们根据具体的业务场景来进行选择。

Tips:因为 SqlSessionManager 同时也实现了 SqlSession 接口,而且在使用过程中更多的是作为 SqlSession 的实现而使用,所以我会将 SqlSessionManager 放在 SqlSession 的章节中进行说明。

SqlSession

SqlSession 是 MyBatis 的接口,同样也是 MyBatis 的核心组件之一,定义了 MyBatis 与数据库交互的规范,提供了执行 SQL 语句,提交/回滚事务以及获取映射器(Mapper 接口)实例的方法。

SqlSession 有两个实现类:

SqlSession体系.png

DefaultSqlSession 是 SqlSession 的默认实现类,通过 DefaultSqlSessionFactory 获取

DefaultSqlSession 与 SqlSessionManager 的主要区别体现在两个方面:

  • 线程安全:
    • DefaultSqlSession 不是线程安全的 SqlSession 实例(也可以说是通过 DefaultSqlSessionFactory 获取的 SqlSession 实例不是线程安全的)
    • SqlSessionManager 提供了线程安全的 SqlSession 实例
  • 事务管理:
    • DefaultSqlSession 需要手动提交事务,或者在执行SqlSession#close方法时自动提交事务
    • 通过 SqlSessionManager 执行 SQL 语句时,会自动的进行事务提交。

SqlSession 实例的生命周期对应一次数据库会话,当我们通过 SqlSessionFactory 获取 SqlSession 实例时是 SqlSession 生命周期的开始,而我们调用SqlSession#close方法后,是 SqlSession 实例的生命周期的结束,这期间的过程通常对应着一项业务操作从开始到结束的过程,因此我们可以认为 SqlSession 实例的生命周期是一次业务操作从开始到结束的时间

特别提醒,虽然每个 SqlSession 实例都有与之对应的 Connection 实例,且数据库交互是由 Connection 实例完成的,但由于数据库连接池的存在,调用SqlSession#close方法后,SqlSession 实例只是将 Connection 实例“归还”到数据库连接池中,而不是调用Connection#close来关闭 Connection 实例,因此我们不能将 SqlSession 实例的生命周期与 Connection 实例的生命周期画上等号。

Tips:通过 SqlSession 执行 SQL 语句是 iBATIS 时代的用法,在当下的环境中,特别是在 MyBatis 与 Spring 集成后,我们通常会选择通过 SqlSession 实例获取映射器实例后直接调用接口方法,即在文章开头中解释映射器接口方法绑定时的使用方式。

Executor

Executor 是 MyBatis 中的接口,同样是 MyBatis 中的核心组件。Executor 接口定义了 MyBatis 与数据库交互的规范。不同 Executor 的实现类提供了不同的特性,例如:SimpleExecutor 每次都会创建 PreparedStatement 对象,ReuseExecutor 会复用已经存在的 PreparedStatement 对象,BatchExecutor 用于批量执行 SQL 更新语句,CachingExecutor 提供了查询结果的缓存能力。

MyBatis 中 Executor 的体系结构如下:

Executor的继承体系.png

关于 Executor 体系的中各实现类的具体作用与功能,我会在 MyBatis 系列的后续文章中继续和大家分享。

MappedStatement

MappedStatement 中封装了 Mapper.xml 文件中映射的 SQL 语句信息,包括 SQL 语句的 id,SQL 语句,参数映射信息,结果集映射信息,以及缓存策略等。

StatementHandler

StatementHandler 是 MyBatis 中的接口,负责 MyBatis 中的 SQL 处理,如预编译,参数设置,SQL 语句执行等。

MyBatis 中 StatementHandler 的体系结构如下:

StatementHandler的体系.png

ResultSetHandler

ResultHandler 是 MyBatis 中的接口,依旧是 MyBatis 中的核心组件。ResultHandler 只有一个实现类 DefaultResultSetHandler,负责将数据库返回的结果集映射为 Java 对象,需要注意的是 ResultSetHandler 与 ResultHandler 是不同的,ResultSetHandler 负责 MyBatis 内部将结果集映射为 Java 对象,而 ResultHandler 提供了对结果集数据的二次处理能力,允许开发者进行自定义,会在 ResultSetHandler 处理完结果集的映射后调用ResultHandler#handlerResult方法。

问题答疑

上一篇文章中,我们只配置了一个 UserMapper.xml,因此有些小伙伴产生了迷惑,当存在多个 Mapper.xml,且每个文件中都有同名的查询语句时在 MyBatis 应用中该如何进行查询?

一句话概括就是通过 namespace + id 方式来关联到唯一的 SQL 语句映射上。类似于,当 Java 应用程序中存在多个同名 Java 类时,我们可以通过全限名的方式访问不同的 Java 类。

我们先随便建一个表,SQL 语句如下:

create table company (id              int          not null primary key,company_name    varchar(50)  not null,company_address varchar(500) not null
);

接着按照上篇文章中的方式分别创建 company 表对应的 CompanyDO,CompanyDAO 和 CompanyMapper.xml,其中 CompanyMapper.xml 需要定义与 UserMapper.xml 中同名的查询语句,CompanyMapper.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.wyz.dao.CompanyDAO"><select id="selectAll" resultType="com.wyz.entity.CompanyDO" >select id, company_name, company_address from company</select>
</mapper>

接着我们修改 mybatis-config.xml 文件,添加映射文件 CompanyMapper.xml:

<configuration><!-- 省略数据库配置的部分 --><mappers><mapper resource="mapper/UserMapper.xml"/><mapper resource="mapper/CompanyMapper.xml"/></mappers>
</configuration>

最后我们修改测试代码:

@Test
public void testSelectAll() {SqlSession sqlSession = sqlSessionFactory.openSession();List<UserDO> users = sqlSession.selectList("com.wyz.dao.UserDAO.selectAll");for(UserDO userDo:users) {log.info(userDo.getName());}
}

这样我们就可以通过 namespace + id 的方式映射到指定的 SQL 语句了


好了,今天的内容就到这里了,如果本文对你有帮助的话,希望多多点赞支持,如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠王有志,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/803515.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis高级-分布式缓存

分布式缓存 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题&#xff1a; 0.目标 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;…

《荒野大镖客》游戏提示emp.dll文件丢失如何解决?

emp.dll它作为一种动态链接库&#xff08;DLL&#xff09;文件&#xff0c;在Windows操作系统中扮演着重要角色。当打开一个程序时&#xff0c;操作系统会将程序的代码和数据加载到内存中&#xff0c;并创建一个进程来运行该程序。在这个过程中&#xff0c;emp.dll负责将这些代…

Dev-C++详细安装教程及中文设置(附带安装包链接)

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍*************安装包链接在文章末尾***************** Dev-C详细安装教程…

动态规划刷题(算法竞赛、蓝桥杯)--区间DP

1、题目链接&#xff1a;[NOI1995] 石子合并 - 洛谷 #include <bits/stdc.h> using namespace std; const int N210; int n,a[N],s[N]; int f[N][N];//存最小值 int g[N][N];//存最大值 int main(){memset(f,0x3f,sizeof f);//求最小初始化为无穷大 memset(g,-0x3f,size…

猫头虎分享已解决Error: 解决“IndexError: list index out of range“

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 文章目录 猫头虎分享已解决Error: 解决"IndexError: list index out of range" &#x1f431;&#x1f989;&#x1f6e0;️摘要正文内容一、错误现场勘察 &#x1f575…

24/04/09总结

异常: 1.异常是什么? 程序中可能出现的问题 2.异常体系的最上层父类是谁?异常分为几类? 父类:Exception。 异常分为两类:编译时异常、运行时异常 编译时异常和运行时异常的区别? 编译时异常:没有继承RuntimeException的异常&#xff0c;直接继承于Exception。 编译阶段就会…

Python实现滑块验证码识别,最简单的一种,没有任何加密

网址链接&#xff1a;衣丰 & 2010-聚衣网(juyi5.cn) - 常熟市聚衣网&#xff0c;聚衣网女装&#xff0c;江苏省女装批发&#xff0c;苏州市女装批发&#xff0c;常熟市女装批发&#xff0c;网销女装一件代发&#xff0c;全国最低价 平时采集数据&#xff0c;频率过快&…

设计模式面试题

概述 设计模式分类 创建型模式 用于描述“怎样创建对象”&#xff0c;主要特点是“将对象的创建与使用分离”。使用者不需要官族对象创建的细节。结构型模式 用于描述如何将类或对象按照某种布局组成更大的结构。行为型模式 用于描述类或对象之间怎样相互协作共同完成单个对象…

Domain Admin:方便快捷的图形化域名和SSL证书监测平台

Domain Admin&#xff1a;一目了然&#xff0c;一键掌握&#xff0c;您的全方位图形化域名与SSL证书智能管家&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Domain Admin是一个基于Python Vue3.js 技术栈实现的域名和SSL证书监测平台&#xff0c;旨在为用户打造一个…

7-17 爬动的蠕虫

题目链接&#xff1a;7-17 爬动的蠕虫 一. 题目 1. 题目 2. 输入输出样例 3. 限制 二、代码 1. 代码实现 #include <stdio.h>int main(void) {unsigned int n, u, d;unsigned int minute, high;if (scanf("%d %d %d", &n, &u, &d) ! 3) {retur…

有关栈的算法

例题一 解法&#xff08;栈&#xff09;&#xff1a; 算法思路&#xff1a; 本题极像我们玩过的「开⼼消消乐」游戏&#xff0c;仔细观察消除过程&#xff0c;可以发现本题与我们之前做过的「括号匹配」问题是类似的。当前元素是否被消除&#xff0c;需要知道上⼀个元素的信息…

C/C++如何快速学习?少走3年弯路

于我而言&#xff0c;最开始学习就是 C&#xff0c;除了计算机专业&#xff0c;其他专业可能学习的第一门编程语言为 C 语言&#xff0c;还是谭浩强爷爷那本&#xff0c;当时想着有点 C 基础&#xff0c;无外乎就是 C 语言的升级版&#xff0c;于是开启了 C 的路程。 语言这个…

《深入Linux内核架构》第4章 进程虚拟内存(1)

目录 4.1 简介 4.2 进程虚拟地址空间 4.2.1 进程地址空间分布 4.2.2 建立布局 第3章讲了两点&#xff1a;物理内存的管理&#xff0c;内核虚拟地址管理。 本章讲&#xff1a;用户进程的虚拟地址空间管理。 4.1 简介 一个进程的整个虚拟地址空间&#xff08;0-3G&#xf…

JKTECH柔性振动盘柔性上料机

柔性供料器&#xff1a;用途广泛与好处显著 在现代工业生产中&#xff0c;随着技术的不断进步和市场的多样化需求&#xff0c;对物料供应系统的要求也日益提高。柔性供料器&#xff0c;作为一种新型的物料供应装置&#xff0c;其用途广泛且好处显著&#xff0c;正逐渐受到各行…

苍穹外卖亮点再梳理 ||

一、项目整体亮点&#xff1a; 【注&#xff1a;基于每个亮点&#xff0c;均有整理的相关知识&#xff0c;可在博客中查看】 1.数据库的设计采用RBAC&#xff08;基于角色访问控制&#xff09;的权限设计。 RBAC将权限授予角色&#xff0c;然后将用户分配给角色&#xff0c;…

算法——倍增

. - 力扣&#xff08;LeetCode&#xff09; 给你一棵树&#xff0c;树上有 n 个节点&#xff0c;按从 0 到 n-1 编号。树以父节点数组的形式给出&#xff0c;其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。 树节点的第 k 个祖先节点是从该节点到根节点路径…

指针 基础知识

本笔记为观看56 指针-指针的定义和使用_哔哩哔哩_bilibili后的学习笔记 指针的定义和使用 1、定义指针 int main () {//1、定义指针int a 10;//指针定义的语法&#xff1a; 数据类型 * 指针变量名&#xff1b;int * p;//让指针记录变量a的地址p &a; //& 为取址符cou…

电商技术揭秘十六:电商中的实时分析与决策支持系统

相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xff1a;电商平台…

如何部署上线项目

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 多环境多环境分类前端多环境实战请求地址启动方式项目配置 后端多环境实战 项目部署原始部署前端…

【项目】棋海争锋

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 项目介绍 WebSocket介绍 使用 项目创建 数据库设计 用户模块 登录接口 注册接口 获取用户信息接口 匹配模块 …