十二、Mybatis
(1)什么是Mybatis?
- Mybatis是一个ORM(Object-relational mapping)的数据库持久化框架
- 同类的ORM框架还有Hibernate,Hibernate是完整映射:不需要写sql语句,框架自动生成
- Mybatis是一个半自动化的ORM框架,需要手动写sql
- Mybatis是apache的一个开源项目iBatis,MyBatis前身就是【iBatis】
- 任何ORM框架的底层都是JDBC
(2)Mybatis架构设计
Mybatis的功能架构分为三层:
- API接口层
- 数据处理层
- 基础支撑层
- 接口层
主要就是和数据库交互,提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库,接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
以使用Mapper接口为例,将Mapper文件抽象为一个Mapper接口,这个接口中声明的方法和跟Mapper.xml中的节点项对应。
id值对应方法名称,parameterType 值对应方法的入参类型,而resultMap值则对应返回值类型。
配置好后,MyBatis 会根据接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,当调用接口方法时,根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过 SqlSession.select/update( “statementId”, parameter) 等来实现对数据库的操作。
- 数据处理层
数据处理层:可以说是MyBatis 的核心,负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等,它主要的目的是根据调用的请求完成一次数据库操作。
从大的方面上讲,它要完成两个功能:
- 通过传入参数构建动态SQL语句;
- 动态语句生成能够说是MyBatis框架很是优雅的一个设计,MyBatis 经过传入的参数值,使用 Ognl 来动态地构造SQL语句,使得MyBatis 有很强的灵活性和扩展性。
- 参数映射指的是对于java 数据类型和jdbc数据类型之间的转换,这里有包括两个过程:
- 查询阶段
- 查询结果集转换阶段
- 查询阶段要将java类型的数据,转换成jdbc类型的数据,经过 preparedStatement.setXXX() 来设值;
- 另外一个就是对resultset查询结果集的jdbcType 数据转换成java 数据类型。
- SQL语句的执行以及封装查询结果集。
- 动态SQL语句生成以后,MyBatis 将执行SQL语句,并将可能返回的结果集转换成List 列表。
- MyBatis 在对结果集的处理中,支持结果集关系一对多和多对一的转换,而且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。
- 基础支撑层
基础支撑层是整个MyBatis框架的地基,负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。
(3)MyBatis的主要构件及其相互关系
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
- SqlSessionFactory:SqlSessionFactory是MyBatis框架的核心接口之一,它负责创建SqlSession对象,SqlSession是执行持久化操作的主要对象。
- SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
- Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
- ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数,
- ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
- TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换
- MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装,
- SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql:表示动态生成的SQL语句以及相应的参数信息
- Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。
(4)Mybatis工作原理
// 配置文件的名称
String resource = "mybatis-config.xml";
// 1.通过Mybatis包中的Resources对象很轻松的获取到配置文件
Reader reader = Resources.getResourceAsReader(resource);
// 2.通过SqlSessionFactoryBuilder创建
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 3.获得session实例
SqlSession session =sqlSessionFactory.openSession();
User user = new User();
user.setId(8);
// 完成数据库的插入
session.insert("add", user);
session.commit();
session.close();
以上是mybatis的基本使用方式,下面将进行对核心代码解读
- 获取SqlsessionFactory对象
根据mybatis-config.xml全局配置文件创建SqlSessionFactory对象、就是把配置文件的详细信息解析保存在了configuration对象中,返回包含了configuration的defaultSqsessionFactory对象
- new SqlSessionFactoryBuilder().build(reader)方法解读
- 查看public SqlSessionFactory build(Reader reader)方法的源码
// 一个参数的build方法,调用了三个参数的build方法(下面的build方法)
public SqlSessionFactory build(Reader reader) {
return this.build((Reader)reader, (String)null, (Properties)null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 这里返回的是一个Configuration对象
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
}
}
return var5;
}
// 这个方法是上面this.build(parser.parse());执行的build方法,返回一个DefaultSqlSessionFactory对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}// 这里就是上面var5 = this.build(parser.parse());一行代码调用的parse()方法
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
// this.parser的类型就是XPathParser
// XPathParser作用:用dom解析mybatis-config.xml标签的configuration标签
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}package org.apache.ibatis.session;
import org.apache.ibatis.mapping.MappedStatement;
// 全局configuation的一个重要属性:MappedStatement
public class Configuration {
...
// 这个类的作用是使用其成员变量knownMappers生成一个Mapper接口的代理工厂
protected final MapperRegistry mapperRegistry;
...
// 一个MappedStatement对象代表一个增删改查标签的详细信息(id sqlResource等)
protected final Map<String, MappedStatement> mappedStatements;
...
}public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
}
总结:从new SqlSessionFactoryBuilder().build(reader);方法执行开始
- 在build(Reader reader);执行后,在其重载的build方法中先创建一个XMLConfigBuilder对象
- 然后执行parse()方法获取Configuration对象
- 执行parse()过程中,使用XPathParser类对dom解析mybatis-config.xml标签的configuration标签,将解析到的信息保存到configuration对象中
- 最终执行this.build(configuration);方法返回一个DefaultSqlSessionFactory对象
了解:
- 在Configuration类中,mapperRegistry用于生成mapper代理工厂
- 在Configuration类中,一个MappedStatement对象代表一个增删改查标签的详细信息
- 获取sqlsession对象
返回sqlsession的实现类defaultSqlsession对象,defaultSqlsession对象包含了executor和configuration,Executor(四大对象)对象会在这一步被创建
- 获取Mapper接口代理对象(MapperProxy)
返回getMapper接口的代理对象、包含了SqlSession对象
- 执行增删改查方法
Mybatis运行原理总结
- 根据配置文件(全局、SQL映射文件)初始化出configuration对象
- 创建一个defaultSqlSession对象,它里面包含configuration和executor(根据配置文件中的defaultEXecutorType创建出对应的Executor)
- defaultSqlSession.getMapper()获取Mapper接口对应的MapperProxy
- MapperProxy里面有defaultSqlSession
- 执行增删改查方法:
- 调用的是defaultSqlsesion的增删改查(会调用Executor的crud)
- 会创建一个StatementHandler对象(同时也会创建出ParameterHandler和ResultSetHandler)
- 调用StatementHandler的prepareStatement()方法进行预编译handler.prepare()和参数设置handler.parameterize(stmt)
- 设置完成后调用StatementHandler的增删改查方法query()
- 参数预编译完成后使用resultSetHandler封装结果集
(5)Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
MyBatis提供了多种映射形式,可以将SQL执行结果封装为目标对象并返回,包括:
- 基本类型映射:将查询结果映射为基本数据类型或其包装类型,如int、String、boolean等。
- 实体类映射:将查询结果映射为Java对象,通过将列名与属性名进行映射来实现自动封装。
- 嵌套结果映射:将查询结果映射为包含其他Java对象的Java对象,通过association标签来实现。
- 集合映射:将查询结果映射为集合类型的Java对象,包括List、Map等,通过collection和map标签来实现。
- 关联集合映射:将查询结果映射为包含其他Java对象集合的Java对象,通过association和collection/map标签来实现。
MyBatis将查询结果映射为Java对象的过程中,会根据结果集的列名和Java对象的属性名进行自动映射。如果列名与属性名不一致,可以通过配置resultMap来指定映射关系,也可以通过注解@Results和@Result来指定映射关系。
在映射过程中,MyBatis会根据Java对象的属性类型、getter/setter方法等信息来决定如何进行数据类型转换。如果类型不匹配,MyBatis会尝试进行自动类型转换,如果无法转换,则会抛出类型转换异常。
(6)通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问, 这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时, 方法能重载吗?
Dao接口是一种面向接口编程的方式,用于将Java方法与MyBatis映射配置文件中的SQL语句关联起来,实现Java代码和数据库操作的解耦。Dao接口与映射文件相对应,通常一个接口方法对应一个SQL语句。
在MyBatis中,我们可以通过定义一个接口和接口方法来操作数据库,具体的实现由MyBatis框架来完成。MyBatis会根据接口方法的名称和参数类型,自动查找对应的SQL语句并执行,将结果映射为Java对象并返回给调用者。
例如,假设我们有一个UserDao接口,定义了一个getUserById方法:
public interface UserDao {
User getUserById(int id);
}
这个接口定义了一个getUserById方法,通过id参数获取一个User对象。
然后,在映射配置文件中定义一个getUserById SQL语句:
<select id="getUserById" parameterType="int" resultType="com.example.User">
SELECT * FROM user WHERE id = #{id}
</select>
在这个SQL语句中,我们定义了一个id为getUserById的查询语句,通过parameterType指定参数类型为int,resultType指定结果类型为com.example.User。
最后,在Java代码中调用这个getUserById方法:
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getUserById(1);
通过SqlSession对象的getMapper方法获取UserDao接口的实现,然后调用getUserById方法获取User对象,其中1是查询参数。
当Dao接口中的方法参数不同时,方法是可以重载的。例如,我们可以定义一个getUserByName方法:
public interface UserDao {
User getUserById(int id);
User getUserByName(String name);
}
这个接口定义了一个getUserByName方法,通过name参数获取一个User对象。MyBatis框架会根据方法参数的类型,自动选择对应的SQL语句进行执行。
(7)如何执行批量插入?
使用
<foreach>
标签实现批量插入
<insert id="batchInsertUsers" parameterType="java.util.List">
INSERT INTO user (name, age, gender) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.gender})
</foreach>
</insert>
(8)如何获取自动生成的(主)键值?
使用
<selectKey>
标签获取自动生成的主键值.<selectKey>
标签可以嵌套在<insert>
或者<update>
标签内部,用于在执行SQL语句之前获取主键值。
<selectKey>
标签有以下属性:
keyProperty
: 指定自动生成的主键值要设置到Java对象的哪个属性中。order
: 指定<selectKey>
标签的执行顺序,可以是BEFORE
或者AFTER
。如果是BEFORE
,则先执行<selectKey>
标签,再执行<insert>
或者<update>
标签。如果是AFTER
,则先执行<insert>
或者<update>
标签,再执行<selectKey>
标签。resultType
: 指定自动生成的主键值的数据类型。
<insert id="insertUser" parameterType="User">
<!-- 定义自动生成主键的SELECT语句 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
SELECT LAST_INSERT_ID()
</selectKey>
<!-- 定义插入用户数据的INSERT语句 -->
INSERT INTO user (name, age, gender) VALUES (#{name}, #{age}, #{gender})
</insert>
在这个示例中,首先使用
<selectKey>
标签定义了自动生成主键的SELECT语句,使用keyProperty
属性指定自动生成的主键值要设置到User
对象的id
属性中,使用order
属性指定在插入数据之后执行。然后定义插入用户数据的INSERT语句。在执行完INSERT语句之后,MyBatis会执行<selectKey>
标签中定义的SELECT语句,从而获取自动生成的主键值。
(9)在 mapper 中如何传递多个参数?
- 使用
@Param
注解。可以在mapper接口方法的参数前添加@Param
注解,指定参数的名称。在SQL语句中,可以使用${paramName}
或者#{paramName}
的方式引用参数。
例如:
public User getUserByIdAndName(@Param("id") Long id, @Param("name") String name);
对应的SQL语句是:
SELECT * FROM user WHERE id = #{id} AND name = #{name}
- 使用
Map
类型的参数。可以将多个参数封装成一个Map
对象,在SQL语句中使用${key}
或者#{key}
的方式引用参数。
例如:
public User getUser(Map<String, Object> paramMap);
对应的SQL语句是:
SELECT * FROM user WHERE id = #{id} AND name = #{name}
在Java代码中,可以通过以下方式将参数封装成Map
对象:
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", id);
paramMap.put("name", name);
User user = sqlSession.selectOne("getUser", paramMap);
- 使用
Javabean
对象作为参数。可以将多个参数封装成一个Javabean
对象,在SQL语句中使用${propertyName}
或者#{propertyName}
的方式引用参数。
例如:
public User getUser(User user);
对应的SQL语句是:
SELECT * FROM user WHERE id = #{id} AND name = #{name}
需要注意的是,如果是使用
Map
或者Javabean
对象作为参数,在SQL语句中使用${propertyName}
的方式引用参数时,会将参数解析为字符串进行拼接。因此,需要确保参数的类型和SQL语句中引用参数的类型匹配,否则可能会出现类型转换异常。同时,也需要确保参数的属性名称与SQL语句中引用参数的名称相同,否则参数将无法传递到SQL语句中。
(10)Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql?
MyBatis动态SQL是指能够根据不同的条件拼接不同的SQL语句,从而实现在同一个SQL语句中完成多种不同的操作。动态SQL可以使SQL语句更加灵活,减少冗余代码,提高SQL语句的复用性。
MyBatis动态SQL的执行原理是通过解析XML文件中的SQL语句,并根据传入的参数动态生成SQL语句,最终将生成的SQL语句发送到数据库执行。
MyBatis中支持以下几种动态SQL:
- if元素:根据条件判断生成SQL语句。
<select id="getUser" resultType="User">
SELECT * FROM user
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
</where>
</select>
- choose元素:类似于Java中的switch语句,根据条件选择生成SQL语句。
<select id="getUser" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="name != null">
AND name = #{name}
</when>
<otherwise>
AND age = #{age}
</otherwise>
</choose>
</where>
</select>
- foreach元素:根据集合或数组生成SQL语句。
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (id, name, age)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.id}, #{user.name}, #{user.age})
</foreach>
</insert>
- trim元素:用于去除SQL语句中多余的AND或OR等关键字。
<select id="getUser" resultType="User">
SELECT * FROM user
<where>
<trim prefix="AND" prefixOverrides="OR">
<if test="id != null">
OR id = #{id}
</if>
<if test="name != null">
OR name = #{name}
</if>
</trim>
</where>
</select>
- set元素:用于动态生成UPDATE语句中SET子句。
<update id="updateUser" parameterType="User">
UPDATE user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
</set>
WHERE id = #{id}
</update>
动态SQL的使用可以使SQL语句更加灵活,能够根据不同的条件生成不同的SQL语句,提高SQL语句的复用性,从而减少冗余代码。同时,也可以提高系统的性能,避免生成不必要的SQL语句。
(11)#{}和${}的区别是什么?
- #{}是一个占位符,相当于JDBC中的一个问号?
- #{}底层采用的是PreparedStatement,会进行预编译(主要是里面的setString方法,对一些特殊的字符,例如’'单引号,会在值后面加上一个\右斜线进行转义,让值无效),因此不会产生sql注入
- #{}不会产生字符串拼接
- #{}在使用时自动会主动加双引号""
- ${}会与其他sql进行字符串拼接,不能预防sql注入
- ${}方式一般用于传入数据库对象,例如传入表名.
- ${}不会主动加""双引号,需要我们手动加入
(12)当实体类中的属性名和表中的字段名不一样,怎么办 ?
当实体类中的属性名和表中的字段名不一致时,可以使用MyBatis提供的注解来进行映射,或者在XML配置文件中进行映射。
- 使用注解
可以在实体类中使用MyBatis提供的注解来进行属性与字段之间的映射。例如,使用@Results注解来指定返回结果集中每个属性对应的字段名,使用@Result注解来指定某个属性对应的字段名。示例如下:
public class User {
@Result(column = "user_name", property = "userName")
private String userName;
// 省略其他属性和方法
}
- 在XML配置文件中进行映射
在XML配置文件中,可以使用元素来定义结果集的映射规则,可以使用元素来指定某个属性对应的字段名。示例如下:
<resultMap id="userResultMap" type="User">
<result column="user_name" property="userName"/>
<!-- 省略其他映射规则 -->
</resultMap>
在SQL语句中,可以使用AS关键字来给查询的字段起别名,使其与实体类的属性名一致。例如:
SELECT user_name AS userName, user_age AS userAge FROM user;
(13)Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
MyBatis支持延迟加载,即在需要使用某个属性时才去查询数据库并加载该属性的值。MyBatis的延迟加载是通过动态代理实现的,具体实现原理如下:
- 在查询语句中使用延迟加载配置
可以在查询语句中使用MyBatis提供的lazyLoadingEnabled和aggressiveLazyLoading两个属性来开启延迟加载。例如:
<select id="getUserById" resultType="User" lazyLoadingEnabled="true">
SELECT * FROM user WHERE user_id = #{userId}
</select>
- 生成动态代理对象
当查询语句被执行时,MyBatis会根据返回类型动态生成一个代理对象。这个代理对象会包含查询结果中所有的属性,但是属性值都为空。
- 调用属性方法触发延迟加载
当调用代理对象的某个属性方法时,MyBatis会根据属性名在查询结果中查找对应的字段,并将该字段的值设置到代理对象中。如果该属性被标记为延迟加载,那么在第一次调用属性方法时,MyBatis会根据代理对象中保存的查询语句重新查询数据库,加载该属性的值并设置到代理对象中。
- 执行SQL语句加载延迟属性
当第一次调用延迟属性的方法时,MyBatis会根据代理对象中保存的查询语句重新查询数据库,并加载该属性的值。如果延迟属性是一个集合类型,那么MyBatis会根据查询结果中的主键来查询该集合的所有元素,并设置到代理对象中。
通过以上方式,MyBatis实现了延迟加载功能,可以有效地减少查询数据库的次数,提高应用性能。但是需要注意的是,在使用延迟加载时需要注意懒加载导致的N+1问题,需要合理使用批量查询等技术来避免这个问题。
(14)Mybatis 的一级、二级缓存
MyBatis的缓存机制分为一级缓存和二级缓存两种。一级缓存是指在同一会话(SqlSession)中执行相同SQL语句时,查询结果会被缓存起来,以避免重复查询。二级缓存是指在多个会话(SqlSession)中执行相同SQL语句时,查询结果会被缓存起来,以避免重复查询。
- 一级缓存
MyBatis的一级缓存是SqlSession级别的缓存,也称为本地缓存。在同一SqlSession中,如果执行了相同的查询语句,那么查询结果会被缓存到一级缓存中。当再次查询相同的语句时,MyBatis会优先从一级缓存中获取查询结果,而不会再次查询数据库。
MyBatis的一级缓存默认是开启的,可以通过SqlSession的clearCache()方法来清空一级缓存。
- 二级缓存
MyBatis的二级缓存是mapper级别的缓存,可以被多个SqlSession共享。当执行查询语句时,MyBatis会先从二级缓存中获取查询结果,如果查询结果存在,则直接返回结果;否则会从数据库中查询,并将查询结果放入二级缓存中。当多个SqlSession对同一个mapper执行相同的查询语句时,MyBatis会从二级缓存中获取查询结果,以避免重复查询。
二级缓存默认是关闭的,需要在mapper.xml文件中配置开启:
<cache/>
需要注意的是,二级缓存缓存的是对象的序列化形式,因此缓存的对象必须是可序列化的。此外,由于二级缓存是共享的,因此在进行更新、插入、删除等操作时,需要刷新二级缓存,以避免缓存和数据库数据不一致的问题。
(15)什么是 MyBatis 的接口绑定?有哪些实现方式?
MyBatis的接口绑定是指将Mapper接口与Mapper XML文件进行绑定,从而实现Mapper接口中定义的方法与Mapper XML中的SQL语句进行映射。接口绑定的主要目的是简化代码,提高开发效率。
MyBatis的接口绑定有两种实现方式:
- 基于XML的Mapper配置文件
基于XML的Mapper配置文件是MyBatis最早的接口绑定方式。它将Mapper接口与对应的Mapper XML文件进行映射,通过在XML文件中定义SQL语句,实现Mapper接口中的方法。
<!-- Mapper XML文件 -->
<mapper namespace="com.example.UserMapper">
<select id="getUserById" resultType="com.example.User">
select * from user where id = #{id}
</select>
</mapper>
// Mapper接口
public interface UserMapper {
User getUserById(int id);
}
- 基于注解的Mapper接口
基于注解的Mapper接口是一种更加简洁的接口绑定方式。它将Mapper接口中的方法与SQL语句通过注解的方式进行绑定,省略了XML配置文件。
// Mapper接口
public interface UserMapper {
@Select("select * from user where id = #{id}")
User getUserById(int id);
}
apper配置文件是MyBatis最早的接口绑定方式。它将Mapper接口与对应的Mapper XML文件进行映射,通过在XML文件中定义SQL语句,实现Mapper接口中的方法。
<!-- Mapper XML文件 -->
<mapper namespace="com.example.UserMapper">
<select id="getUserById" resultType="com.example.User">
select * from user where id = #{id}
</select>
</mapper>
// Mapper接口
public interface UserMapper {
User getUserById(int id);
}
- 基于注解的Mapper接口
基于注解的Mapper接口是一种更加简洁的接口绑定方式。它将Mapper接口中的方法与SQL语句通过注解的方式进行绑定,省略了XML配置文件。
// Mapper接口
public interface UserMapper {
@Select("select * from user where id = #{id}")
User getUserById(int id);
}