Mybatis文档是非常好的学习资料,最重要的是支持中文,比谷歌翻译好【你懂的】,但是,有些东西,还是不是很理解,所以,就写了这篇博客记录一下。
我们先来回顾一下使用JDBC访问数据库的过程:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
MyBatis是一个半自动化的持久层框架。
Mybatis官网 (强烈推荐)
1、创建数据库及表
CREATE TABLE employee(
id INT(11) PRIMARY KEY AUTO_INCREMENT,
last_name VARCHAR(255),
gender CHAR(1),
email VARCHAR(255)
);
2、创建Maven工程,添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
3、创建对应的JavaBean
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
//getter and setter and toString()
}
4、创建mybatis配置文件,sql映射文件
全局配置文件(mybatis-config.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>
<!--和spring整合后 environments配置将废除 -->
<environments default="test">
<environment id="test">
<!--使用jdbc事务管理 -->
<transactionManager type="JDBC"/>
<!--数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybaits01?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
sql映射文件(EmployeeMapper.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="abc">
<!--
namespace:名称空间;通常指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
public Employee getEmpById(Integer id);
-->
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee">
select id,last_name lastName,email,gender from employee where id = #{id}
</select>
</mapper>
这里只写了查询,更新、删除、插入和这类似,只需换成相应的标签和语句即可。
5、测试
public class HelloWorldTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "c01/mybatis-config.xml";
//加载SqlMapConfig.xml文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSession对象,并返回
return new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
* 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
* 3、将sql映射文件注册在全局配置文件中
* 4、写代码:
* 1)、根据全局配置文件得到SqlSessionFactory;
* 2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
* 一个sqlSession就是代表和数据库的一次会话,用完关闭
* 3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
*
* @throws IOException
*/
@Test
public void test() throws IOException {
// 2、获取sqlSession实例,能直接执行已经映射的sql语句
// sql的唯一标识:statement Unique identifier matching the statement to use.
// 执行sql要用的参数:parameter A parameter object to pass to the statement.
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
Employee employee = openSession.selectOne(
"abc.getEmpById", 1);
System.out.println(employee);
} finally {
openSession.close();
}
}
}
MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:
注意mybatis全局配置文件中的标签顺序,需要按以下顺序排列,否则抛异常
<configuration>
<!--
1、mybatis可以使用properties来引入外部properties配置文件的内容;
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源
-->
<properties resource ="dbconfig.properties"></properties>
</configuration>
dbconfig.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
jdbc.username=root
jdbc.password=123
如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:
这是 MyBatis 中极为重要的调整设置,它们会改变MyBatis 的运行时行为。
<configuration>
...
<!--
2、settings包含很多重要的设置项
setting:用来设置每一个设置项
name:设置项名
value:设置项取值
-->
<settings>
<!--开启驼峰命名规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true/false | TRUE |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时。所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true/false | FALSE |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true/false | TRUE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则( camel case )映射即从经典数据库列名A_ COLUMN到经典Java属性名aColumn的类似映射 | true/false | FALSE |
更过设置,可以参考官方文档
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
<typeAliases>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<!--可以指定多个类的别名-->
</typeAliases>
当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
也可以指定一个包名,MyBatis 会在指定包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
值得注意的是, MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
更过类型处理器,参考官方文档
日期类型的处理
日期时间处理上,我们可以使用MyBatis基于JSR310( Date and Time API)编写的各种日期时间类型处理器。MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.InstantTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.LocalTime TypeHandler" />
<typeHandler handler="org.apache.ibatis.type.0ffsetDateTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.OffsetTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.ZonedDateTimeTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.YearTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.MonthTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.YearMonthTypeHandler" />
<typeHandler handler="org.apache.ibatis.type.JapaneseDateTypeHandler" />
</typeHandlers>
自定义类型处理器
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型
步骤:
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。
了解mybatis运行原理才能更好开发插件。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
environments 元素定义了如何配置环境。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
实际开发中,mybatis一般是和spring或者springboot整合,环境不需要单独配置
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
<databaseIdProvider type="DB_VENDOR" />
databaseIdProvider
对应的 DB_VENDOR
实现会将 databaseId
设置为 DatabaseMetaData#getDatabaseProductName()
返回的字符串。 由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>
databaseId
属性在映射xml中的使用
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
databaseId="mysql">
select * from employee where id = #{id}
</select>
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
databaseId="oracle">
select e.* from employee e where id = #{id}
</select>
通过
切换数据库,便能切换SQL
MyBatis匹配规则如下:
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 主要有如下三种方式(都是在
标签下的)
<mapperresource="mapper/user.xml"/>
<mapper class="com.ewen.mybatis.mapper.UserMapper"/>
注意
- 有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
- 没有sql映射文件,所有的sql都是利用注解写在接口上;
推荐:
- 比较重要的,复杂的Dao接口我们来写sql映射文件
- 不重要,简单的Dao接口为了开发快速可以使用注解;
<packagename ="com.itheima.mybatis.mapper"/>
创建XXXDao
接口,定义抽象方法,创建XXXDaoImpl实现类,这个类中通过sqlSession
调用实体类.xml
中相应的方法,这种方式,很少用到。
上面使用的就是接口式变成,定义一个XXXMapper
接口,在XXXMapper.xml
中写响应的sql语句,mapper
接口没有实现类,但是mybatis
会为这个接口生成一个代理对象。(将接口和xml进行绑定)EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
,直接使用这个XXXMapper 对象进行操作。
接口和xml文件要满足以下几点要求
- namespace必需是接口的全路径名
- 接口的方法名必需与映射文件的sql id一致
- 接口的输入参数必需与映射文件的parameterType类型一致
- 接口的返回类型必须与映射文件的resultType类型一致
这是mybatis官方推荐的方式,也是我们会经常用到的一种方式。
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
期望从这条语句中返回结果的类全限定名或别名。 如果返回的是简单类型或者是pojo类型,直接设置为相应的简单类型或者pojo类型,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。这个属性,只有select标签有。
对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。这里只是简单介绍一下,下面还会详细介绍。
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys()
<insert id="addEmp" parameterType="com.lun.c01.helloworld.bean.Employee"
useGeneratedKeys="true" keyProperty="id" >
insert into employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
</insert>
上面两个属性,仅适用于仅适用于 insert 和 update标签
对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键
Oracle不支持自增,Oracle使用序列来模拟自增,每次插入的数据的主键是从序列中拿到的值,如何获取到这个值
#从序列获取新主键值
select employee_seq.nextval from dual;
获取非自增主键的值
<insert id="addEmp" databaseId="oracle">
<!--
keyProperty:查出的主键值封装给javaBean的哪个属性
order="BEFORE":当前sql在插入sql之前运行
AFTER:当前sql在插入sql之后运行
resultType:查出的数据的返回值类型
BEFORE运行顺序:
先运行selectKey查询id的sql;查出id值封装给javaBean的id属性
在运行插入的sql;就可以取出id属性对应的值
AFTER运行顺序:
先运行插入的sql(从序列中取出新值作为id);
再运行selectKey查询id的sql;
-->
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
<!-- 编写查询主键的sql语句 -->
<!-- BEFORE-->
select EMPLOYEES_SEQ.nextval from dual
<!-- AFTER:
select EMPLOYEES_SEQ.currval from dual -->
</selectKey>
<!-- 插入时的主键是从序列中拿到的 -->
<!-- BEFORE:-->
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(#{id},#{lastName},#{email})
<!-- AFTER:
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(employees_seq.nextval,#{lastName},#{email}) -->
</insert>
在上面的示例中,首先会运行 selectKey 元素中的语句,并设置 employees的 id,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。
#{参数名/任意名}:取出参数值。
通常操作:
方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
如果使用#{id},#{lastName},会抛出异常:org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [1, 0, param1, param2]
要使用#{param1}、#{param2}…从map中获取指定的key的值,也可以使用索引如#{0}、#{1}
多个参数,使用param1、param2…,程序可读性不高,可以使用@Param来命名参数
方法:
public Employee getEmpByIdAndLastName4(@Param("id")Integer id, @Param("lastName")String name);
多个参数会被封装成 一个map,
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性值
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象,如:
Page{ int index; int size; }
举例
public Employee getEmp(@Param("id")Integer id,String lastName);
public Employee getEmp(Integer id,@Param("e")Employee emp);
注意: 如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(collection),如果是List还可以使用这个key(list),数组key(array)
比如:public Employee getEmpById(List
取值:取出第一个id的值: #{list[0]}
在idea中,debug
点进selectById(1)
方法
在mapperMethod.execute()
方法中,会判断是那种类型的sql语句,并进行参数转换,之后,还是用sqlSession
中的相应方法操作
点进convertArgsToSqlCommandParam
方法
names
是ParamNameResolver
类的一个属性
private final SortedMap<Integer, String> names;
并且在构造方法中进行的赋值
public ParamNameResolver(Configuration config, Method method) {
Class<?>[] paramTypes = method.getParameterTypes();
Annotation[][] paramAnnotations = method.getParameterAnnotations();
SortedMap<Integer, String> map = new TreeMap();
int paramCount = paramAnnotations.length;
for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
if (!isSpecialParameter(paramTypes[paramIndex])) {
String name = null;
Annotation[] arr$ = paramAnnotations[paramIndex];
int len$ = arr$.length;
//判断是否有@Param注解
for(int i$ = 0; i$ < len$; ++i$) {
Annotation annotation = arr$[i$];
if (annotation instanceof Param) {
this.hasParamAnnotation = true;
//有的话,获取@param中的值
name = ((Param)annotation).value();
break;
}
}
if (name == null) {
if (config.isUseActualParamName()) {
name = this.getActualParamName(method, paramIndex);
}
//如果没有注解的话,会获取参数的个数
if (name == null) {
name = String.valueOf(map.size());
}
}
//存到一个map中,paramIndex 是索引从0开始,如果有注解,name是注解中的值,没有的话,name就是索引
map.put(paramIndex, name);
}
}
//最后存到names中
this.names = Collections.unmodifiableSortedMap(map);
}
下面再来分析getNamedParams
方法
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
if (args != null && paramCount != 0) { //参数不为空
//如果只有一个元素,并且没有注解,直接返回单个参数
if (!this.hasParamAnnotation && paramCount == 1) {
return args[(Integer)this.names.firstKey()];
} else {
//如果有多个元素或者有@param注解
Map<String, Object> param = new ParamMap();
int i = 0;
//遍历names集合
for(Iterator i$ = this.names.entrySet().iterator(); i$.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)i$.next();
//names集合的value作为key; names集合的key又作为从参数args取值的索引args[0]:args【1,"Tom"】:
//names既可以是@Param中的值(指定的key),也可以是索引
param.put(entry.getValue(), args[(Integer)entry.getKey()]);
//额外的将每一个参数也保存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
String genericParamName = "param" + String.valueOf(i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
//如果参数为空,直接返回null
return null;
}
}
#{}
和${}
都可以获取map中的值或者pojo对象属性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
#Preparing:
select * from tbl_employee where id=2 and last_name=?
区别:
默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法。
原生jdbc不支持占位符的地方我们就可以使用${}
进行取值,比如分表、排序…,按照年份分表拆分,以及直接在 SQL 语句中直接插入一个不转义的字符串(比如模糊查询)的时候,要使用${}
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
另外,使用#{}
取值时,可以规定参数的一些规则
jdbcType
通常需要在某种特定的条件下被设置:
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle DB(报错) `JdbcType OTHER:无效的类型
因为mybatis对所有的null都映射的是原生Jdbc
的OTHER
类型,Oracle DB不
能正确处理;
由于全局配置中:jdbcTypeForNull=OTHER,Oracle DB不支持,两种解决方法:
#{email,jdbcType=NULL}
使用resultType
属性指定返回结果的类型,如果返回结果是简单类型、Pojo类型,直接在resultType
中指定即可。
如果返回结果是List
类型,resultType
中直接指定为list
中的Pojo类
resultType
是value
属性//多条记录封装一个map:Map:键是这条记录的主键,值是记录封装后的javaBean
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);
<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName); -->
<select id="getEmpByLastNameLikeReturnMap" resultType="employee">
select * from employee where last_name like #{lastName}
</select>
resultType
是map
//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);
<!--public Map<String, Object> getEmpByIdReturnMap(Integer id); -->
<select id="getEmpByIdReturnMap" resultType="map">
select * from employee where id=#{id}
</select>
下面来介绍一下resultMap
resultMap
元素是 MyBatis
中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap
的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
之前你已经见过简单映射语句的示例,它们没有显式指定 resultMap。比如:
<select id="selectById" parameterType="int" resultType="User">
select * from `user` where id=#{id}
</select>
如果,数据库中的字段名和实体类中的列名不一样的时候(或者根据驼峰命名法也不能匹配),可以使用
标签
<resultMap id="userResultMap" type="User">
<!--指定主键列的封装规则
id定义主键会底层有优化;
column:指定哪一列
property:指定对应的javaBean属性 -->
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
的高级用法,可以参考我的另一篇文章
参考:
如有不足之处,欢迎指正,谢谢