读书笔记:mybatis的使用入门
概述
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代
码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的
POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
-----搬自官网
利用mybatis操作对数据的操作的过程一般是这样的
1. 从配置文件(通常是XML配置文件中)得到 sessionfactory.
2. 由sessionfactory 产生 session
3. 在session 中完成对数据的增删改查和事务提交等.
4. 在用完之后关闭session 。
5. 在Java对象和数据库之间有做mapping 的配置文件,也通常是xml 文件。
利用IntelliJ Idea搭建一个mybatis开发环境
所需资源
1. IntelliJ Idea 15
2. mybatis-3.4.4.jar
3. mysql-connector-java-5.1.39-bin.jar
准备工作:先创建一个数据库user
Create TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(50) DEFAULT NULL,
`userAge` int(11) DEFAULT NULL,
`userAddress` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
Insert INTO `user` VALUES ('1', 'Peter', '15', 'NewYork US');
Insert INTO `user` VALUES ('2', 'Jack', '18', 'L.A US');
Insert INTO `user` VALUES ('3', 'Linda', '17', 'London UK');
Insert INTO `user` VALUES ('4', 'Lily', '21', 'Shanghai CN');
1、打开IntelliJ Idea,创建一个Java工程,工程命名为DemoForMybatis
将src
目录修改为src_test
,创建src_user
目录,并将src_user
目录变成Sources
类型
2、导入所需包mybatis-3.4.4.jar
和mysql-connector-java-5.1.39-bin.jar
3、现在来创建项目
在目录 src_user
下创建一个配置文件Configuration.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>
<typeAliases>
<typeAlias alias="User" type="com.mybatis.model.User"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mybatis/model/User.xml"/>
</mappers>
</configuration>
在src_user
中创建一个包,命名为com.mybatis.model
,并在这个包中创建一个POJO User
如下
public class User {
private int id;
private String userName;
private String userAge;
private String userAddress;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAge() {
return userAge;
}
public void setUserAge(String userAge) {
this.userAge = userAge;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
}
在包com.mybatis.model
中创建一个xml文件User.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.mybatis.models.UserMapper">
<select id="selectUserByID" parameterType="int" resultType="User">
select * from `user` where id = #{id}
</select>
</mapper>
现在来利用mybatis
里面的类来完成一个简单的查询功能,在src_test
创建一个Java文件Test.java
,代码如下
import com.mybatis.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.Reader;
/**
* Created by Alpha on 17/4/12.
*/
public class Test {
private static SqlSessionFactory sqlSessionFactory;
private static Reader reader;
static{
try{
reader = Resources.getResourceAsReader("Configuration.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}catch(Exception e){
e.printStackTrace();
}
}
public static SqlSessionFactory getSession(){
return sqlSessionFactory;
}
public static void SimpleSearch(int id){
SqlSession session = sqlSessionFactory.openSession();
try {
User user = (User) session.selectOne("com.mybatis.models.UserMapper.selectUserByID", id);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
}
}
创建测试类Main
,代码如下:
/**
* Created by Alpha on 17/4/12.
*/
public class Main {
public static void main(String[] args){
Test.SimpleSearch(2);
}
}
运行测试代码,结果如下
上述的方法可以采用更好维护的一种方式来修改一下
在src_user
目录下面创建一个包com.mybatis.inter
,并在包中创建一个接口IUserOperation
,里面包含一个方法selectUserByID
代码如下
package com.mybatis.model;
/**
* Created by Alpha on 17/4/12.
*/
public interface IUserOperation {
public User selectUserByID(int id);
}
修改User.xml
文件的命名空间为IUserOperation
<?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.mybatis.inter.IUserOperation">
<select id="selectUserByID" parameterType="int" resultType="User">
select * from `user` where id = #{id}
</select>
</mapper>
在Test类中新增一个方法SimpleSearchByInterface
,代码如下
public static void SimpleSearchByInterface(int id){
SqlSession session = sqlSessionFactory.openSession();
try {
IUserOperation userOperation=session.getMapper(IUserOperation.class);
User user = userOperation.selectUserByID(id);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
}
执行,查看结果如下
按照上面的步骤类似,我们可以对数据进行基本的增删改查,过程不再赘述,下面我把最终的代码贴到下面
User.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.mybatis.inter.IUserOperation">
<select id="selectUserByID" parameterType="int" resultType="User">
select * from `user` where id = #{id}
</select>
<resultMap id="resultListUser" type="User">
<id column="id" property="id"/>
<result column="userName" property="userName"/>
<result column="userAge" property="userAge"/>
<result column="userAddress" property="userAddress"></result>
</resultMap>
<select id="selectUsers" parameterType="string" resultMap="resultListUser">
SELECT * from USER WHERE userName LIKE #{userName}
</select>
<insert id="addUser" parameterType="User" useGeneratedKeys="true"
keyProperty="id">
INSERT INTO USER (userName,userAge,userAddress) VALUES (#{userName},#{userAge},#{userAddress})
</insert>
<update id="updateUser" parameterType="User">
UPDATE USER SET userName=#{userName},userAge=#{userAge},userAddress=#{userAddress} WHERE id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
DELETE FROM USER WHERE id=#{id}
</delete>
</mapper>
Test.java
/**
* 执行查询并返回查询的结果集
*/
public static void getResultList(){
SqlSession session = sqlSessionFactory.openSession();
try{
IUserOperation userOperation = session.getMapper(IUserOperation.class);
List<User> users = userOperation.selectUsers("summer");
for(User user : users){
System.out.println(user.getId() + ":" + user.getUserName() + ":" + user.getUserAddress());
}
}finally {
session.close();
}
}
/**
* 添加数据到数据库中
*/
public static void addUser(User user){
SqlSession session = sqlSessionFactory.openSession();
try{
IUserOperation userOperation = session.getMapper(IUserOperation.class);
userOperation.addUser(user);
//添加后需要提交事务,否则数据库中不会有改变
session.commit();
System.out.println("当前增加的用户id为:" + user.getId());
}finally {
session.close();
}
}
/**
* 更新数据库的数据
*/
public static void updateUser(int id){
SqlSession session = sqlSessionFactory.openSession();
try{
IUserOperation userOperation = session.getMapper(IUserOperation.class);
User user = userOperation.selectUserByID(id);
user.setUserName("猪头三");
userOperation.updateUser(user);
session.commit();
}finally {
session.close();
}
}
/**
* 删除数据库中的数据
*/
public static void deleteUser(int id){
SqlSession session = sqlSessionFactory.openSession();
try{
IUserOperation userOperation = session.getMapper(IUserOperation.class);
userOperation.deleteUser(id);
session.commit();
}finally {
session.close();
}
}
IUserOperation.java
package com.mybatis.inter;
import com.mybatis.model.User;
import java.util.List;
/**
* Created by Alpha on 17/4/12.
*/
public interface IUserOperation {
public User selectUserByID(int id);
public List<User> selectUsers(String userName);
public void addUser(User user);
public void updateUser(User user);
public void deleteUser(int id);
public List<Article> getUserArticles(int id);
}
好了,既然我们已经利用mybatis完成了一个数据库的增删改查操作,那么接下来我们讨论一下mybatis的XML的配置
XML的配置
mybatis3的配置大标签Configuration
的树结构如下所示
configuration
|--- properties
|--- settings
|--- typeAliases
|--- typeHandlers
|--- objectFactory
|--- plugins
|--- environments
|--- |--- environment
|--- |--- |--- transactionManager
|--- |--- |__ dataSource
|__ mappers
在配置的时候需要注意的是配置文件中的属性顺序必须和上面的Configuration的树结构顺序一致,否则就会出现错误
下面让我们来详细讲一讲mybatis的配置
properties
我们对之前的配置文件做一下修改,修改的结果如下:
<configuration>
<properties resource="com/mybatis/model/config.properties">
<property name="username" value="root"/>
<property name="password" value="Jackson"/>
</properties>
<typeAliases>
<typeAlias alias="User" type="com.mybatis.model.User"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mybatis/model/User.xml"/>
</mappers>
</configuration>
上面在peoperties
中有两个成员username
和password
,在下面引用的时候我们采用的方式是${username}
和${password}
,也就是说properties的作用是定义在其它地方使用的属性,properties
的resource
的属性定义的是将它所指向的配置文件给包含进来,也就是说我们这么做了就可以直接使用resource
中的属性
settings
setting是mybatis中极其重要的属性,它们会改变mybatis运行时的行为。
看下面一个代码
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
让我们具体看一下其中的意义
cacheEnabled :该配置影响的所有映射器中配置的缓存的全局开关。默认值为true,一般不用进行设置即可
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置`fetchType`属性来覆盖该项的开关状态,默认值为false
multipleResultSetsEnabled:是否允许单一语句返回多结果集(需要兼容驱动),默认为true,所以一般不用进行设置
useColumnLabel:使用列标签代替列名。默认设置为true
useGeneratedKeys:允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,默认为false
autoMappingBehavior:指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集,默认为PARTIAL
autoMappingUnknownColumnBehavior:指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE: 不做任何反应,WARNING: 输出提醒日志,('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN),FAILING: 映射失败 (抛出 SqlSessionException),默认为NONE
defaultExecutorType:配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。默认为SIMPLE
defaultStatementTimeout:设置超时时间,它决定驱动等待数据库响应的秒数。默认什么都没设置,表示等待任意时间
defaultFetchSize: 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖,默认什么都没设
safeRowBoundsEnabled:允许在嵌套语句中使用分页(RowBounds),默认为false
mapUnderscoreToCamelCase:是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。默认为false
localCacheScope:MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。默认值为SESSION
jdbcTypeForNull:当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER,默认为OTHER
lazyLoadTriggerMethods:指定哪个对象的方法触发一次延迟加载。默认为equals,clone,hashCode,toString
还有一些其它的配置,在此不再赘述,详情请见官方文档
typeAliases
为Java类型设置一个短的名字,存在的意义仅在于用来减少类完全限定名的冗余,例如
<typeAliases>
<typeAlias alias="User" type="com.mybatis.model.User"/>
</typeAliases>
这个是利用短名User
来替代com.mybatis.model.User
来进行使用
typeHandlers
如果我们想在数据库中存储一个Date型的字段,但是我们将该字段已经设为了varchar类型了,那么我们怎么去做到这一点呢,也许我们可以先将Date转化为String类型再进行存储,其实我们不必这样操作,我们可以使用TypeHandler的工具来做到这一点,下面我们来用一个例子说明一下这个属性的用法
1. 创建一个继承自BaseTypeHandler的类ExampleTypeHandler,代码如下所示
package com.mybatis.util;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
/**
* Created by Alpha on 17/4/13.
*/
public class ExampleTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i,String.valueOf(((Date)parameter).getTime()));
}
@Override
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
String sqlTimetamp = rs.getString(columnName);
if(null != sqlTimetamp){
return new Date(Long.valueOf(sqlTimetamp));
}
return null;
}
@Override
public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String sqlTimetamp = rs.getString(columnIndex);
if(null != sqlTimetamp)
return new Date(Long.valueOf(sqlTimetamp));
return null;
}
@Override
public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String sqlTimetamp = cs.getString(columnIndex);
if(null != sqlTimetamp)
return new Date(Long.valueOf(sqlTimetamp));
return null;
}
}
2. 创建一个pojo类Note用来存储存入数据库的对象
package com.mybatis.model;
import java.util.Date;
/**
* Created by Alpha on 17/4/13.
*/
public class Note {
private int id;
private Date date;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
3. 创建一个队Note对象进行数据库操作的xml文件NoteMappper.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.mybatis.inter.NoteMappper">
<resultMap id="note-base" type="com.mybatis.model.Note">
<result property="id" column="id"/>
<result property="date" column="date" typeHandler="com.mybatis.util.ExampleTypeHandler"/>
</resultMap>
<select id="queryNote" parameterType="int" resultMap="note-base">
select * from note where id = #{id}
</select>
<insert id="insertNote" parameterType="com.mybatis.model.Note">
insert into note (id,date) values(#{id},#{date,typeHandler=com.mybatis.util.ExampleTypeHandler})
</insert>
</mapper>
4. 创建映射接口NoteMappper
public interface NoteMappper {
Note queryNote(int id);
void insertNote(Note note);
}
5. 在Configuration添加typeHandler以及映射的NoteMappper.xml文件
<typeHandlers>
<typeHandler handler="com.mybatis.util.ExampleTypeHandler"/>
</typeHandlers>
<mapper resource="com/mybatis/model/NoteMappper.xml"/>
6. 添加测试代码
public static void testTypeHandler(){
SqlSession session = sqlSessionFactory.openSession();
try {
NoteMappper noteMappper = session.getMapper(NoteMappper.class);
Note note = new Note();
note.setId(1);
note.setDate(new Date());
noteMappper.insertNote(note);
session.commit();//默认的自动提交是关闭的,因此需要在此手动提交
note = noteMappper.queryNote(1);
System.out.println(note.getDate());
}finally {
session.close();
}
}
上述操作的的目录结构如下
下面是运行这个程序的结果
看一下数据库中什么情况
所以我们就利用typeHandler实现了特殊类型的存储,当然除了重写BaseTypeHandler
,mybatis3还提供了一些已经写好的typeHandler,具体的可以见官方文档
objectFactory
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象
工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
--------摘自官网
也就是说默认的对象工厂只会帮我们创建一个未经初始化的类实例,假如我们要对类实例进行修改,我们可以通过覆写默认的对象工厂。
配置如下
<objectFactory type="com.mybatis.util.ExampleObjectFactory">
<property name="someProperty" value="10"/>
</objectFactory>
plugins
mybatis允许你在映射语句执行过程的某一点实施拦截调用,默认情况下,mybatis允许使用插件来拦截的方法调用如下
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
如何使用plugins呢,我们通过实现Interceptor
接口,并指定想要拦截的方法签名即可
environments
mybatis可以配置成多种环境,在我们使用多个数据库时会用到这一点,但是一个SqlSessionFactory实例只能使用其中的一个环境,配置文件的形式如下
<configuration>
<environments default="environment1">
<environment id="environment1">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
.
.
</dataSource>
</environment>
.
.
<environment id="environment3">
<transactionManager type="MANAGED"></transactionManager>
<dataSource type="UNPOOLED">
.
.
</dataSource>
</environment>
</environments>
</configuration>
我们可以使用下面的方法来选择使用哪一个环境
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment,properties);
从上面的代码我们可以看到环境中包括了事务管理器和数据源
事务管理器
事务管理器有两种,JDBC
和MANAGED
,说明如下
JDBC-这个配置就是直接使用了 JDBC 的提交和回滚设置。
MANAGED-这个配置几乎没做什么。
数据源
数据源包含3中,UNPOOLED
、POOLED
以及JNDI
UNPOOLED
-这个对数据的操作是没有启用数据库连接池的,在每次使用的时候都会打开和关闭连接,由于这样的原因,因此它在使用的时候的速度会比较慢,但是占用的资源相对较少,它的配置参数有下面5个
driver – 这是 JDBC 驱动的 Java 类的完全限定名(并不是JDBC驱动中可能包含的数据源类)。
url – 这是数据库的 JDBC URL 地址。
username – 登录数据库的用户名。
password – 登录数据库的密码。
defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
POOLED-相对于UNPOOLED连接,它的做法是创建了一个包含数个数据库连接的连接池,而且连接不会在使用的时候被关闭,所以它的速度更快,性能更好,但是占用的资源也较多,它的配置属性除了上面的属性之外,还包括下面的部分
poolMaximumActiveConnections – 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
poolTimeToWait – 这是一个底层设置,如果获取连接花费的相当长的时间,它会给连接池打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。
poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否处在正常工作秩序中并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。
poolPingEnabled – 是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL),默认值:false。
poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的使用频度。这可以被设置成匹配具体的数据库连接超时时间,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
mappers
在上面的例子中,我们创建的配置文件有Configuration.xml
、User.xml
以及NoteMapper.xml
,其中User.xml
和NoteMapper.xml
是SQL语句映射的配置文件,那么我们在初始化Configuration.xml时如何找到这两个xml文件呢,实际上是通过mappers属性做到的,如下
<mappers>
<mapper resource="com/mybatis/model/User.xml"/>
<mapper resource="com/mybatis/model/NoteMappper.xml"/>
</mappers>
下面我们详细讲一讲Mapper的XML配置文件