MyBatis 应用的组成

news2024/11/23 13:41:00

王有志,一个分享硬核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.coloradmin.cn/o/1581120.html

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

相关文章

PS入门|如何使用“主体”功能进行抠图?

前言 前段时间讲到给各种图标和LOGO抠图的办法&#xff0c;分别使用的是 钢笔工具蒙版 PS入门&#xff5c;规规矩矩的图形怎么抠出来&#xff1f; 魔棒工具蒙版 PS入门&#xff5c;黑白色的图标怎么抠成透明背景 色阶蒙版 PS入门&#xff5c;目标比较复杂&#xff0c;但背景…

HTML+CSS+JS复习回顾

环境搭建 下载VScode&#xff0c;依次下载插件&#xff1a;HTML CSS support、Live Server、Auto Rename Tag 一、HTML篇 HTML通过一系列的标签&#xff08;元素&#xff09;来定义文本、图像、链接等。HTML标签是由尖括号包围的关键字。标签通常成对出现&#xff0c;包括开…

在Spring中使用Redis

端口怎么设置&#xff0c;看我前一篇文章 前面使用jedis&#xff0c;通过Jedis对象中各种方法来操作redis的。 此处Spring中则是通过StringRedisTemplate来操作redis。 最原始提供的类是RedisTemplate StringRedisTemplate是RedisTemplate的子类&#xff0c;专门处理文本数据的…

2014最新AIGC创作系统ChatGPT网站源码+AI绘画网站源码+GPT4-All联网搜索模型

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

无重复的最长字串

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 问题 给定一个字符串&#xff0c;我们需要找到该字符串中的最长无重复子串的长度。 示例 让我们以一个具体的示例来说明这个问题&#…

四、书城开发--3、书城图书部分的开发

书城图书部分 首先我们做书城首页搜索栏下面的图片展示 我们在书城首页组件中通过home请求方法中获取回来的数据中&#xff0c;打印出来可以看到那个banner就是我们现在要的图片 我们在data中定义一个变量banner用来存放获取回来的数据中的banner 然后把它展示出来就可以了&a…

B. Burning Midnight Oil Codeforces Round 112 (Div. 2)

题目链接&#xff1a; Problem - 165B - Codeforceshttps://codeforces.com/problemset/problem/165/B 题目大意&#xff1a; 最后写了至少n个&#xff0c;每次衰减k倍&#xff08;/k&#xff09;&#xff0c;问最初的v最小为多少。 思路&方法&#xff1a; 二分答案。 AC代…

想要品牌传播有效,先清楚这三个本质问题

在互联网时代&#xff0c;企业想要提高市场竞争力就需要做好品牌传播。然而有许多企业在做品牌传播时都会踩坑&#xff0c;原因是因为忽视了这三点&#xff0c;接下来就让媒介盒子和大家分享&#xff1a; 一、 文案本质是“购买理由” 在文案技巧中经常会出现一些词&#xff…

重学Java,JDK安装,Java环境配置,Could not find Java SE Runtime Environment问题解决

文章目录 前言JDK下载什么是JDK下载JDK官网下载历史版本下载 JDK安装生成JRE配置环境变量进入环境变量配置界面新建系统变量JAVA_HOME编辑系统变量PATHPath编辑界面1Path编辑界面2 配置CLASSPATH 验证安装情况问题反馈Error: opening registry key Software\JavaSoft\Java Runt…

WordPress网站备份和迁移教程

我们之前遇到购买了hostease的客户需要进行wordpress的网站备份的迁移操作。 以下是一份完整的指南&#xff0c;介绍了备份和迁移WordPress网站的步骤&#xff1a; 步骤一&#xff1a;备份WordPress网站 使用插件进行备份&#xff1a; 安装并激活备份插件&#xff0c;例如Up…

SSH远程登陆系统(RedHat9)

ssh的基本用法 ssh hostname/IP # 如果没有指定用什么用户进行连接&#xff0c;默认使用当前用户登录 ssh –l username hostname/IP ssh usernamehostname ssh usernameIP在第一次连接到服务器时&#xff0c;会自动记录服务器的公钥指纹信息 如果出现密钥变更导致错误可以…

Spring Cloud 集成 Redis 发布订阅

目录 前言步骤引入相关maven依赖添加相关配置 使用方法发布订阅发布一个消息 注意总结 前言 在当今的软件开发领域&#xff0c;分布式系统已经成为一种主流的架构模式&#xff0c;尤其是在处理大规模、高并发、高可用的业务场景时。然而&#xff0c;随着系统复杂性的增加&…

Training - Kubeflow 的 PyTorchJob 配置 DDP 分布式训练 (ncclInternalError)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137569332 Kubeflow 的 PyTorchJob 是 Kubernetes 自定义资源&#xff0c;用于在 Kubernetes 上运行 PyTorch 训练任务&#xff0c;是 K…

【学习】软件测试中为什么要进行接口测试?

接口测试是软件开发过程中不可或缺的一环&#xff0c;它主要是对软件系统中各个模块之间的接口进行测试&#xff0c;以验证它们是否能够正确地交互和协作。接口测试的目的是确保软件系统的各个部分能够无缝地协同工作&#xff0c;从而提高整个系统的质量和稳定性。 一、什么是接…

【DM8】分区表维护

查询分区 数据字典&#xff1a;dba_tab_pattitions SELECT * FROM SYS.DBA_TAB_PARTITIONS WHERE TABLE_OWNERTEST;添加分区 ALTER TABLE TEST.T1 ADD PARTITION Pn VALUES LESS THAN(MAXVALUE);删除分区 ALTER TABLE TEST.T1 DROP PARTITION Pn;合并分区 ALTER TABLE TES…

PostgreSQL入门到实战-第六弹

PostgreSQL入门到实战 PostgreSQL查询语句(三)官网地址PostgreSQL概述PostgreSQL中ORDER BY理论PostgreSQL中ORDER BY实操更新计划 PostgreSQL查询语句(三) 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://www.post…

网络安全:重要性与应对措施

1. 网络安全的重要性 随着互联网的普及和信息技术的快速发展&#xff0c;网络安全问题已经变得日益突出。网络攻击者可以通过各种手段窃取个人信息、破坏系统、传播病毒等&#xff0c;给个人和社会带来巨大的损失。因此&#xff0c;网络安全已经成为信息化时代的重要问题之一。…

Harmony鸿蒙南向驱动开发-MIPI CSI

CSI&#xff08;Camera Serial Interface&#xff09;是由MIPI联盟下Camera工作组指定的接口标准。CSI-2是MIPI CSI第二版&#xff0c;主要由应用层、协议层、物理层组成&#xff0c;最大支持4通道数据传输、单线传输速度高达1Gb/s。 物理层支持HS&#xff08;High Speed&…

[Java基础揉碎]StringBuffer类 StringBuild类

目录 StringBuffer类 介绍 继承图 String VS StringBuffer StringBuffer的构造器 String和StringBuffer的转换 StringBuffer类常见方法 测试题 StringBuild类 基本介绍 继承图 String、StringBuffer 和StringBuilder的比较 通过字符串拼接循环测试可以看到各自的性…

EasyPoi教程

EasyPoi教程 1. 前传1.1 前言1.2 Easypoi介绍1.3 使用1.4 可能存在的问题 2. Excel 注解版2.1 Excel导入导出2.2 注解2.3 注解导出,导入2.3.1 对象定义2.3.2 集合定义2.3.3 图片的导出2.3.4 Excel导入介绍2.3.5 Excel导入小功能2.3.6 图片的导入2.3.7 Excel多Sheet导出 2.4 注解…