前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis 框架

Mybatis 框架

作者头像
Masimaro
发布2019-12-18 17:44:30
6530
发布2019-12-18 17:44:30
举报

在之前的内容中,我写了Java的基础知识、Java Web的相关知识。有这些内容就可以编写各种各样丰富的程序。但是如果纯粹手写所有代码,工作量仍然很大。为了简化开发,隐藏一些不必要的细节,专心处理业务相关内容 ,Java提供了许多现成的框架可以使用

Mybatis介绍

在程序开发中讲究 MVC 的分层架构,其中M表示的是存储层,也就是与数据库交互的内容。一般来说使用jdbc时,需要经历:导入驱动、创建连接、创建statement对象,执行sql、获取结果集、封装对象、关闭连接这样几个过程。里面很多过程的代码都是固定的,唯一有变化的是执行sql并封装对象的操作。而封装对象时可以利用反射的机制,将返回字段的名称映射到Java实体类的各个属性上。这样我们很自然的就想到了,可以编写一个框架或者类库,实现仅配置sql语句和对应的映射关系,来实现查询到封装的一系列操作,从而简化后续的开发。Mybatis帮助我们实现了这个功能。

Mybatis实例

假设现在有一个用户表,存储用户的相关信息,我们现在需要使用mybatis来进行查询操作,可能要经历如下步骤:

  1. 定义对应的实体类
代码语言:javascript
复制
public class User {
    private Integer id;
    private String username;
    private String birthday;
    private char sex;
    private String address;

    //后面省略对应的getter和setter方法
    //为了方便后面的实体类都会省略这些内容
}
  1. 编辑主配置文件,主要用来配置mybati的数据库连接信息以及指定对应dao的配置文件
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTDConfig3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--mybatis主配置文件-->
<configuration>
    <!--配置环境-->
    <environments default="mybatis_demo">
    <environment id="mybatis_demo">
    <!--配置事务的类型-->
    <transactionManager type="JDBC"></transactionManager>
        <!--配置连接池-->
        <dataSource type="POOLED">
            <!--配置数据库连接的4个基本信息-->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
            <property name="username" value="root"/>
            <property name="password" value="masimaro_root"/>
        </dataSource>
        </environment>
        </environments>

        <!--指定配置文件的位置,配置文件是每个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/MybatisDemo/Dao/IUserDao.xml"></mapper>
    </mappers>
</configuration>
  1. 编写dao接口
代码语言:javascript
复制
public interface IUserDao {
    public List<User> findAll();
}
  1. 并提供dao的xml配置文件
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTDMapper3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--每个函数配置一条,标签名是要进行的数据库操作,resultType是需要返回的数据类型-->
<mapper namespace="com.MyBatisDemo.Dao.IUserDao">
<!--标签里面的文本是sql语句-->
    <select id="findAll" resultType="com.MyBatisDemo.domain.User">
        select * from user;
    </select>
</mapper>

写完了对应的配置代码,接下来就是通过简单的几行代码来驱动mybatis,完成查询并封装的操作

代码语言:javascript
复制
InputStream is = null;
SqlSession = null;
try {
    //加载配置文件
    is = Resources.getResourceAsStream("dbconfig.xml");

    //创建工厂对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(is);

    //创建sqlsession对象
    sqlSession = factory.openSession();
    
    //使用sqlsession对象创建dao接口的代理对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    //使用对象执行方法
    List<User> users = this.userDao.findAll();
    System.out.println(users);
} catch (IOException e) {
    e.printStackTrace();
}finally{

    // 清理资源
    if (null != this.is){
        try {
            this.sqlSession.commit();
            this.is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    if (null != this.sqlSession){
        this.sqlSession.close();
    }
}

mybatis大致的执行过程

  1. 根据我们传入的InputStream对象来获取配置xml中对应对象的值
  2. 接着根据配置信息创建连接并生成数据库的连接池对象
  3. 根据配置文件中的mapper项获取到对应的Dao接口的配置文件,在读取该文件时会准备一个Map结构,其中key是mapper中的namespace + id,value是对应的sql语句,例如上述例子中得到的map结构为{"com.MyBatisDemo.Dao.IUserDao.findAll", "select * from user"}
  4. 在创建sqlsession时从连接中获取到一个Statement对象
  5. 在我们调用dao接口时,首先根据dao接口得到详细的类名,然后获取到当前调用的接口名称,由这两项得到一个key,比如在上述例子中,dao接口的名称为com.MyBatisDemo.Dao.IUserDao, 而调用的方法是 findAll,将这两个字符串进行拼接,得到一个key,根据这个key去map中查找对应的sql语句。并执行
  6. 执行sql语句获取查询的结果集
  7. 根据resultType中指定的对象进行封装并返回对应的实体类

使用mybatis实现增删改查操作

在之前的代码上可以看出,使用mybatis来实现功能时,只需要提供dao接口中的方法,并且将方法与对应的sql语句绑定。在提供增删改查的dao方法时如果涉及到需要传入参数的情况下该怎么办呢?

下面以根据id查询内容为例: 我们先在dao中提供这样一个方法:

代码语言:javascript
复制
public User findById(int id);

然后在dao的配置文件中编写sql语句

代码语言:javascript
复制
<!--parameterType 表示传入的参数的类型-->
<select id="findById" resultType="com.MyBatisDemo.domain.User" parameterType="int">
    select * from user where id = #{id}
</select>

从上面的配置可以看到,mybatis中, 使用#{} 来表示输入参数,使用属性parameterType属性来表示输入参数的类型。一般如果使用Java内置对象是不需要使用全限定类名,也不区分大小写。

当我们使用内置类型的时候,这里的id 仅仅起到占位符的作用,取任何名字都可以

看完了使用内置对象的实例,再来看看使用使用自定义类类型的情况,这里我们使用update的例子来说明,首先与之前的操作一样,先定义一个upate的方法:

代码语言:javascript
复制
void updateUser(User user);

然后使用如下配置

代码语言:javascript
复制
<update id="updateUser" parameterType="User">
   update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id = #{id}
</update>

与使用id查询的配置类似,当我们使用的是自定义类类型时,在对应的字段位置需要使用类的属性表示,在具体执行的时候,mybatis会根据传入的类对象来依据配置取出对应的属性作为sql语句的参数。上面在使用内置对象时我们说它可以取任何的名称,但是这里请注意 名称只能是自定义对象的属性名,而且区分大小写

这里使用的都是确定的值,如果要使用模糊查询时该如何操作呢,这里我们按照名称来模糊查询,首先在dao中提供一个对应的方法

代码语言:javascript
复制
User findByName(String name);

接着再来进行配置

代码语言:javascript
复制
<select resultType="com.MyBatisDemo.domain.User" parameterType="String">
    select * from User where username like #{username}
</select>

从sql语句来看我们并没有实现模糊的方式,这时候在传入参数的时候就需要使用模糊的方式,调用时应该在参数中添加 %%, 就像这样 userDao.findByName("%" + username + "%")

当然我们可以使用另外一种配置

代码语言:javascript
复制
<select resultType="com.MyBatisDemo.domain.User" parameterType="String">
    select * from User where username like %${username}%
</select>

这样我们在调用时就不需要额外添加 % 了。

既然他们都可以作为参数,那么这两个符号有什么区别呢?区别在于他们进行查询的方式,$ 使用的是字符串拼接的方式来组成一个完成的sql语句进行查询,而#使用的是参数话查询的方式。一般来说拼接字符串容易造成sql注入的漏洞,为了安全一定要使用参数话查询的方式

mybatis的相关标签

resultMap标签

在之前的配置中,其实一直保持着数据库表的字段名与对应的类属性名同名,但是有些时候我们不能保证二者同名,为了解决这问题也为了以后进行一对多和多对多的配置,可以使用resultMap来定义数据库表字段名和类属性名的映射关系 下面是一个使用它的例子。 我们简单修改一下User类的属性定义

代码语言:javascript
复制
public class User {
    private Integer uid;
    private String name;
    private String userBirthday;
    private char userSex;
    private String userAddress;

    //后面省略对应的getter和setter方法
}

这样直接使用之前的配置执行会报错,报找不到对应属性的错误,这个时候就可以使用resultMap属性来解决这个问题

代码语言:javascript
复制
<resultMap id="UserMapper" type="User">
    <id column="id" property="uid"></id>
    <result column="username" property="username"></result>
    <result column="sex" property="sex"></result>
    <result column="birthday" property="birthday"></result>
    <result column="address" property="address"></result>
</resultMap>

<select id="findAll" resultMap="UserMapper">
    select * from user;
</select>

其中 id属性来唯一标示这个映射关系,在需要使用到这个映射关系的地方,使用resultMap这个属性来指定 type属性表示要将这些值封装到哪个自定义的类类型中 resultMap中有许多子标签用来表示这个映射关系

  1. id用来表明表结构中主键的映射关系
  2. result表示其他字段的映射关系
  3. 每个标签中的column属性表示的是对应的表字段名
  4. 标签中的property对应的是类属性的名称

properties 标签

properties标签可以用来定义数据库的连接属性,主要用于引入外部数据库连接属性的文件,这样我们可以通过直接修改连接属性文件而不用修改具体的xml配置文件。

假设现在在工程中还有一个database.properties文件

代码语言:javascript
复制
jdbc.driver ="com.mysql.jdbc.Driver"
jdbc.url = "jdbc:mysql://localhost:3306/mybatis_demo"
jdbc.username ="root"
jdbc.password" ="masimaro_root"

然后修改对应的主配置文件

代码语言:javascript
复制
<!--引入properties文件-->
<properties resource="database.properties">
</properties>

<!--修改对应的dataSource标签-->
<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>

typeAliases 标签

之前我们说过,使用内置类型时不需要写全限定类名,而且它不区分大小写。而使用自定义类型时需要写很长一串,如何使自定义类型与内置类型一样呢?这里可以使用typeAliases标签。它用来定义类名的别名

代码语言:javascript
复制
<typeAliases>
    <!--typeAlias中来定义具体类的别名,type表示真实类名,alias表示别名-->
    <typeAlias type="com.MyBatisDemo.domain.User" alias="user"></typeAlias>
</typeAliases>

使用typeAlias标签时,每个类都需要提供一条对应的配置,当实体类多了,写起来就很麻烦了,这个时候可以使用package子标签来代替typeAlias

代码语言:javascript
复制
<typeAliases>
    <package name="com.MyBatisDemo.domain"/>
</typeAliases>

它表示这里包中的所有类都使用别名,别名就是它的类名

package标签

在定义对应的mapper xml文件时,一个dao接口就需要一条配置。dao接口多了,一条条的写很麻烦,为了减轻编写的工作量可以使用package标签

代码语言:javascript
复制
<mappers>
    <!--它表示这个包中的所有xml都是mapper配置文件-->
    <package name="com/MyBatis/Dao"/>
</mappers>

连接池

在配置数据库连接 dataSource 标签中有一个type属性,它用来定义使用的连接池,该属性有三个取值:

  1. POOLE:使用连接池,采用javax.sql.DataSource 规范中的连接池,mybatis中有针对它的数据库连接池的实现
  2. UNPOOLED:与POOLED相同,使用的都是javax.sql.DataSource 规范,但是它使用的是常规的连接方式,没有采用池的思想
  3. JNDI:根据服务器提供的jndi基础来获取数据库的连接 ,具体获取到的连接对象又服务器提供

动态sql

当我们自己拼接sql的时候可以根据传入的参数的不同来动态生成不同的sql语句执行,而在之前的配置中,我们事先已经写好了使用的sql语句,但是如果碰上使用需要按照条件搜索,而且又不确定用户会输入哪些查询条件,在这样的情况下,没办法预先知道该怎么写sql语句。这种情况下可以使用mybatis中提供的动态sql

假设我们提供一个findByValue的方法,根据值来进行查询。

代码语言:javascript
复制
public List<User> findByValue(User user);

事先并不知道user的哪些属性会被赋值,我们需要做的就是判断user的哪些属性不为空,根据这些不为空的属性来进行and的联合查询。这种情况下我们可以使用if标签

代码语言:javascript
复制
<select id="findByValue" resultType="User" parameterType="User">
    select * from user where  
    <if test="id != null">
        id = #{id} and 
    </if>
    
    <if test="username != null">
        username=#{username} and 
    </if>

    .....
    1=1
</select>

if标签中使用test来进行条件判断,而判断条件可以完全使用Java的语法来进行。 这里在最后用了一个1=1的条件来结束判断,因为事先并不知道用户会传入哪些值,不知道哪条语句是最后一个条件,因此我们加一个恒成立的条件来确保sql语句的完整

当然mybatis中也有办法可以省略最后的1=1,我们可以使用 where标签来包裹这些if,表明if中的所有内容都是作为查询条件的,这样mybatis在最后会在生成查询条件后自动帮助我们进行格式的整理

使用if标签我们搞定了不确定用户会使用哪些查询条件的问题,如果有这样一个场景:用户只知道某个字段的名字有几种可能,我们在用户输入的几种可能值中进行查找,也就是说,用户可以针对同一个查询条件输入多个可能的值,根据这些个可能的值进行匹配,只要有一个值匹配上即可返回;

针对这种情况没办法使用if标签了,我们可以使用循环标签,将用户输入的多个值依次迭代,最终组成一个in的查询条件

我们在这里提供一个根据多个id查找用户的方法

代码语言:javascript
复制
public List<User> findByIds(List<Integer> ids);

这里我们为了方便操作,额外提供一个类用来存储查询条件

代码语言:javascript
复制
public class QueryVo {
    List<Integer> ids;
}
代码语言:javascript
复制
<select id="findUserByIds" resultType="User" parameterType="QueryVo">
    select * from user
    <where>
        <if test="ids != null and ids.size() != 0">
            <foreach collection="ids" open="and id in (" close=")" item=    "id" separator=",">
                ${id}
            </foreach>
        </if>
    </where>
</select>

在上面的例子中使用foreach来迭代容器其中使用collection表示容器,这里取的是parameterType中指定类的属性,open表示在迭代开始时需要加入查询条件的sql语句,close表示在迭代结束后需要添加到查询语句中的sql,item表示每个元素的变量名,separator表示每次迭代结束后要添加到查询语句中的字符串。当我们迭代完成后,整个sql语句就变成了这样: select * from user where 1=1 and id in (id1, id2, ...)

多表查询

一对多查询

在现实中存在着这么一些一对多的对应关系,像什么学生和班级的对应关系,用户和账户的对应关系等等。关系型数据库在处理这种一对多的情况下,使用的是在多对应的那张表中添加一个外键,这个外键就是对应的一那张表的主键,比如说在处理用户和账户关系时,假设一个用户可以创建多个账户,那么在账户表中会有一个外键,指向的是用户表的ID 在上面例子的基础之上,来实现一个一对多的关系。 首先添加一个账户的实体类,并且根据关系账户中应该有一个唯一的用户类对象,用来表示它所属的用户

代码语言:javascript
复制
public class Account {
    private int id;
    private int uid;
    private double money;
    private User user;
}

同时需要在User这个实体类上添加一个Account的列表对象,表示一个User下的多个Account

代码语言:javascript
复制
public class User {
    private Integer id;
    private String username;
    private String birthday;
    private char sex;
    private String address;
    private List<Account> accounts;
}

首先根据user来查询多个account,我们可以写出这样的sql语句来查询

代码语言:javascript
复制
select u.*, a.id as aid, a.money, a.uid from user as u left join account as a on a.uid = u.id;

那么它查询出来的结果字段名称应该是id, username, sex, birthday, address, aid, money, uid 这些,前面的部分可以封装为一个User对象,但是后面的部分怎么封装到Accounts中去呢,这里可以在resultMap中使用collection标签,该标签中对应的对象会被封装为一个容器。因此这里的配置可以写为:

代码语言:javascript
复制
<resultMap id="UserAccountMap" type="user">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>

    <collection property="accounts" ofType="account">
        <id property="id" column="aid"></id>
        <result property="money" column="money"></result>
        <result property="uid" column="uid"></result>
    </collection>
 </resultMap>
 
 <select id="findAll" resultMap="UserAccountMap">
    select u.*, a.ID as aid, a.MONEY, a.UID from user as u left join acc    ount as a on u.id = a.uid
</select>

我们需要一个resultMap来告诉Mybatis,这些多余的字段该怎么进行封装,为了表示一个容器,我们使用了一个coolection标签,标签中的property属性表示这个容器被封装到resultType对应类的哪个属性中,ofType表示的是,容器中每一个对象都是何种类型,而它里面的子标签的含义与resultMap子标签的含义完全相同

从User到Account是一个多对多的关心,而从Account到User则是一个一对一的关系,当我们反过来进行查询时,需要使用的配置是 association 标签,它的配置与使用与collection相同

代码语言:javascript
复制
<resultMap id="AccountUserMap" type="Account">
    <id property="id" column="aid"></id>
    <result property="uid" column="uid"></result>
    <result property="money" column="money"></result>
 
    <association property="user" column="uid" javaType="user">
        <id property="id" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
    </association>
</resultMap>
 
<select id="findUserAccounts" resultType="Account" parameterType="User">
    select * from account where uid = ${id}
</select>

多对多查询

说完了一对多,再来说说多对多查询。多对多在关系型数据库中使用第三张表来体现,第三张表中记录另外两个表的主键作为它的外键。

这里使用用户和角色的关系来演示多对多查询 与之前一样,在两个实体类中新增对方的一个list对象,表示多对多的关系

代码语言:javascript
复制
public class Role implements Serializable {
    private int id;
    private String roleName;
    private String roleDesc;
    private List<User> users;
}

利用之前一对多的配置,我们只需要修改一下ResultMap和sql语句就可以完成多对多的查询

代码语言:javascript
复制
<mapper namespace="com.liuhao.Dao.IUserDao">
    <resultMap id="UserRoleMapper" type="User">
    <id property="id" column="id"></id>
    <result column="username" property="username"></result>
    <result column="sex" property="sex"></result>
    <result column="address" property="address"></result>
    <result column="birthday" property="birthday"></result>
 
    <collection property="roles" ofType="role">
        <id property="id" column="rid"></id>
        <result column="role_desc" property="roleDesc"></result>
        <result column="role_name" property="roleName"></result>
    </collection>
 </resultMap>
 
 <select id="findAll" resultMap="UserRoleMapper">
    select user.*, role.ID as rid, role.ROLE_DESC, role.ROLE_NAME from u    ser left outer join user_role on user_role.uid = user.id left OUTER join role on user_role.RID = role.ID
     </select>
</mapper>

另一个多对多的关系与这个类似,这里就不再单独说明了

延迟加载

之前说了该如何做基本的单表和多表查询。这里有一个问题,在多表查询中,我们是否有必要一次查询出它所关联的所有数据,就像之前的一对多的关系中,在查询用户时是否需要查询对应的账户,以及查询账户时是否需要查询它所对应的用户。如果不需要的话,我么采用上面的写法会造成多执行一次查询,而且当它关联的数据过多,而这些数据我们用不到,这个时候就会造成内存资源的浪费。这个时候我们需要考虑使用延迟加载,只有需要才进行查询。

之前的sql语句一次会同时查询两张表,当然不满足延迟加载的要求,延迟加载应该将两张表的查询分开,先只查询需要的一张表数据,另一张表数据只在需要的时候查询。

根据这点我们进行拆分,假设我们要针对User做延迟加载,我们先不管accounts的数据,只查询user表,可以使用sql语句select * from user, 在需要的时候执行select * from account where uid = id

在xml配置中可以在collection标签中使用select属性,该属性指向一个方法,该方法的功能是根据id获取所有对象的列表。也就说我们需要在AccountDao接口中提供这么一个方法,并且编写它的xml配置

代码语言:javascript
复制
public List<Account> findByUid(int uid);

接着我们对之前的xml进行改写

代码语言:javascript
复制
<resultMap id="UserMapper" type="User">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="sex" property="sex"></result>
    <result column="birthday" property="birthday"></result>
    <result column="address" property="address"></result>

    <collection property="accounts" ofType="Account" select="com.liuhao.Dao.IAccountDao.findByUid" column="id">
    </collection>
</resultMap>

<select id="findAll" resultMap="UserMapper">
    select * from user;
</select>

完成了接口的编写与配置,还需要对主配置文件做一些配置,我们在主配置文件中添加settings节点,开启延迟加载

代码语言:javascript
复制
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

缓存

缓存用来存储一些不经常变化的内容,使用缓存可以减少查询数据库的次数,提高效率。mybatis有两种缓存,一种是在每个sqlsession中的缓存,一种是在每个SqlSessionFactory中的缓存

在SqlSession中的缓存又被叫做是Mybatis的一级缓存。每当完成一次查询操作时,会在SqlSession中形成一个map结构,用来保存调用了哪个方法,以及方法返回的结果,下一次调用同样的方法时会优先从缓存中取

当我们执行insert、update、delete等sql操作,或者执行SqlSession的close或者clearCache等方法时缓存会被清理

在SqlSessionFactory中的缓存被称做二级缓存,所有由同一个SqlSessionFactory创建出来的SqlSessin共享同一个二级缓存。二级缓存是一个结果的二进制值,每当我们使用它时,它会取出这个二进制值,并将这个值封装为一个新的对象。在我们多次使用同一片二级缓存中的数据,得到的对象也不是同一个

使用二级缓存需要进行一些额外的配置:

  1. 在主配置文件中添加配置 在settings的子标签setting 中添加属性 enableCache=True开启二级缓存
  2. 在对应的dao xml配置中添加 cache标签(标签中不需要任何属性或者文本内容),使接口支持缓存
  3. 在对应的select、update等标签上添加属性 useCache=true,为方法开启二级缓存

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-12-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Mybatis介绍
  • Mybatis实例
    • mybatis大致的执行过程
    • 使用mybatis实现增删改查操作
    • mybatis的相关标签
      • resultMap标签
        • properties 标签
          • typeAliases 标签
            • package标签
            • 连接池
            • 动态sql
            • 多表查询
              • 一对多查询
                • 多对多查询
                • 延迟加载
                • 缓存
                相关产品与服务
                数据库
                云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档