MyBatis

MyBatis

原文地址

配置

属性

这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。例如:

<properties resource="xxx/config.properties">
    <property name="username" value="wwj"></property>
    <property name="password" value="123"></property>
</properties>

然后其中的属性就可以在整个配置文件中被用来替换需要动态配置的属性值。比如:

<dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</dataSource>

这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。

如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

  1. 在 properties 元素体内指定的属性首先被读取。
  2. 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  3. 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:

<!-- 在<properties>中添加下列代码,开启默认值 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>

<!-- 在<dataSource>中修改,如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
<property name="username" value="${username:ut_user}"></property>

如果你已经使用 ":" 作为属性的键(如:db:username) ,或者你已经在 SQL 定义中使用 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : 'global_constants'}),你应该通过设置特定的属性来修改分隔键名和默认值的字符。例如:

<!-- 将分隔键设置为'?:' -->
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>

<!-- 如果属性 'db:username' 没有被配置,'username' 属性的值将为 'ut_user' -->
<property name="username" value="${db:username?:ut_user}"/>

设置

这部分比较复杂,需要时添加必要的设置,请自行查阅。

一个配置完整的settings元素示例。

<settings>
    <!-- 全局地开启配置文件中的所有映射器已经配置的任何缓存 -->
  	<setting name="cacheEnabled" value="true"/>
    <!-- 开启延迟加载 -->
  	<setting name="lazyLoadingEnabled" value="true"/>
    <!-- 允许单一语句返回多结果集 -->
  	<setting name="multipleResultSetsEnabled" value="true"/>
    <!-- 使用列标签代替列名 -->
  	<setting name="useColumnLabel" value="true"/>
    <!-- 允许JDBC自动生成主键 -->
  	<setting name="useGeneratedKeys" value="false"/>
    <!-- 指定MyBatis应如何自动映射列到字段或属性。NONE取消自动映射;PARIAL只会自动映射没有定义嵌套结果集映射的结果集。FULL会自动映射任意复杂的结果集 -->
  	<setting name="autoMappingBehavior" value="PARTIAL"/>
    <!-- 指定发现自动映射目标未知属性类型的行为。NONE不做任何反应;WARNING输出提醒日志;FAILING映射失败 -->
  	<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
    <!-- 配置默认的执行器。SIMPLE普通执行器;REUSE会重用预处理语句;BATCH将重用语句并执行批量更新 -->
  	<setting name="defaultExecutorType" value="SIMPLE"/>
    <!-- 设置超时时间 -->
  	<setting name="defaultStatementTimeout" value="25"/>
  	<!-- 设置驱动的结果集获取数量的提示值 -->
    <setting name="defaultFetchSize" value="100"/>
    <!-- 允许在嵌套语句中使用分页(RowBounds),false为允许 -->
  	<setting name="safeRowBoundsEnabled" value="false"/>
    <!-- 允许在嵌套语句中使用分页(ResultHandler),false为允许 -->
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <!-- 开启本地缓存,并缓存一个会话中执行的所有查询。STATEMENT仅用于语句执行上 -->
  	<setting name="localCacheScope" value="SESSION"/>
    <!-- 当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型。一般类型包括:NULL、VARCHAR或OTHER -->
  	<setting name="jdbcTypeForNull" value="OTHER"/>
    <!-- 指定哪个对象的方法触发一次延迟加载 -->
  	<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

类型别名

设置别名的目的是为了减少冗余。

<typeAliases>
    <typeAlias alias="Author" type="domain.blog.Author"/>
    <typeAlias alias="Blog" type="domain.blog.Blog"/>
    <typeAlias alias="Comment" type="domain.blog.Comment"/>
    <typeAlias alias="Post" type="domain.blog.Post"/>
    <typeAlias alias="Section" type="domain.blog.Section"/>
    <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

<!-- 你也可以设置成这样, MyBatis会在包名下面搜索需要的Java Bean -->
<typeAliases>
    <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。

@Alias("author")
public class Author{
	
}

这是一些Java类型的别名:

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

类型处理器

无论是 MyBatis 在预处理语句中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。比如:

@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  	@Override
  	public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    	ps.setString(i, parameter);
  	}

  	@Override
  	public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    	return rs.getString(columnName);
  	}

  	@Override
  	public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    	return rs.getString(columnIndex);
  	}

  	@Override
  	public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    	return cs.getString(columnIndex);
  	}
}
<!-- mybatis-config.xml -->
<typeHandlers>
  	<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

<!-- 你也可以这样设置,让Mybatis为你查找类型处理器,但这样只能通过注解来指定JDBC的类型 -->
<typeHandlers>
    <package name="org.mybatis.example"/>
</typeHandlers>

使用上述的类型处理器将会覆盖已经存在的处理 Java 的String类型属性和VARCHAR参数及结果的类型处理器。 要注意 MyBatis 不会通过窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是VARCHAR类型的字段, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:

  • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");
  • 在类型处理器的类上(TypeHandler class)增加一个 @MappedTypes 注解来指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解方式将被忽略。

可以通过两种方式来指定被关联的 JDBC 类型:

  • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
  • 在类型处理器的类上增加一个 @MappedJdbcTypes 注解来指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解方式将被忽略。

当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类型], jdbcType=null 的组合来选择一个类型处理器。 这意味着使用 @MappedJdbcTypes 注解可以限制类型处理器的范围,同时除非显式的设置,否则类型处理器在 ResultMap 中将是无效的。 如果希望在 ResultMap 中使用类型处理器,那么设置 @MappedJdbcTypes 注解的 includeNullJdbcType=true 即可。 然而从 Mybatis 3.4.0 开始,如果只有一个注册的类型处理器来处理 Java 类型,那么它将是 ResultMap 使用 Java 类型时的默认值(即使没有 includeNullJdbcType=true)。

处理枚举类型

若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用。 默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。

对象工厂

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。比如:

public class ExampleObjectFactory extends DefaultObjectFactory {
    
    //处理默认构造方法
  	public Object create(Class type) {
    	return super.create(type);
  	}
    
    //处理带参数的构造方法
  	public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    	return super.create(type, constructorArgTypes, constructorArgs);
  	}
    
    //初始化ObjectFactory实例后,objectFactory元素体中定义的属性会被传递给 setProperties方法
  	public void setProperties(Properties properties) {
    	super.setProperties(properties);
  	}
    
  	public <T> boolean isCollection(Class<T> type) {
    	return Collection.class.isAssignableFrom(type);
  	}
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

插件

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。

使用插件时,请务必小心!!!

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

//拦截在Executor实例中所有的“update”方法调用
@Intercepts({@Signature(
  	type= Executor.class,
  	method = "update",
  	args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {

  	private Properties properties = new Properties();
  	
  	public Object intercept(Invocation invocation) throws Throwable {
    	// implement pre processing if need
    	Object returnObject = invocation.proceed();
    	// implement post processing if need
    	return returnObject;
  	}
  	
  	public void setProperties(Properties properties) {
    	this.properties = properties;
  	}
}
<!-- mybatis-config.xml -->
<plugins>
  	<plugin interceptor="org.mybatis.example.ExamplePlugin">
    	<property name="someProperty" value="100"/>
  	</plugin>
</plugins>

除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到SqlSessionFactoryBuilder.build(myConfig)方法即可。

环境配置

可以配置多个使用环境,但每个SqlSessionFactory实例只能选择一种环境,所以一个数据库对应一个实例。

为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

<!-- 如果忽略了环境参数,那么默认环境将会被加载 -->
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

下面展示如何配置环境:

<!-- default指定默认的环境id -->
<environments default="development">
    <!-- 使用id将多个环境区分 -->
  	<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 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC– 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器, 因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

数据源

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。 有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):

UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。 不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

数据库厂商标识

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性只要像下面这样在文件中加入 databaseIdProvider 即可:

<!-- mybatis-config.xml -->
<databaseIdProvider type="DB_VENDOR">
  	<property name="SQL Server" value="sqlserver"/>
  	<property name="DB2" value="db2"/>
  	<property name="Oracle" value="oracle" />
</databaseIdProvider>

DB_VENDOR 对应的 databaseIdProvider 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。 由于通常情况下这些字符串都非常长而且相同产品的不同版本会返回不同的值,所以可以通过设置属性别名来使其变短。

映射器

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  	<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  	<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  	<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  	<mapper url="file:///var/mappers/AuthorMapper.xml"/>
  	<mapper url="file:///var/mappers/BlogMapper.xml"/>
  	<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  	<mapper class="org.mybatis.builder.AuthorMapper"/>
  	<mapper class="org.mybatis.builder.BlogMapper"/>
  	<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  	<package name="org.mybatis.builder"/>
</mappers>

映射文件

MyBaits的真正强大在于它的映射语句。

SQL映射文件只有很少的几个顶级元素:

  • cache – 对给定命名空间的缓存配置。
  • cache-ref – 对其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

Select

<!-- 这个语句被称作selectPerson,接受一个int(或Integer)类型的参数,并返回一个HashMap类型的对象,其中的键是列名,值便是结果行中的对应值 -->
<select id="selectPerson" parameterType="int" resultType="hashmap">
  	SELECT * FROM PERSON WHERE ID = #{id}
</select>

<!-- select元素允许你配置很多属性来配置每条语句的作用细节-->
<select
  	id="selectPerson"
  	parameterType="int"
  	parameterMap="deprecated"
  	resultType="hashmap"
  	resultMap="personResultMap"
  	flushCache="false"
  	useCache="true"
  	timeout="10"
  	fetchSize="256"
  	statementType="PREPARED"
  	resultSetType="FORWARD_ONLY">

Select元素的属性:

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
resultType从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
resultMap外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
fetchSize这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
statementTypeSTATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement。默认值:PREPARED。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个。默认值:unset (依赖驱动)。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
resultSets这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。

Insert、Update、Delete

<insert
  	id="insertAuthor"
  	parameterType="domain.blog.Author"
  	flushCache="true"
  	statementType="PREPARED"
  	keyProperty=""
  	keyColumn=""
  	useGeneratedKeys=""
  	timeout="20">

<update
  	id="updateAuthor"
  	parameterType="domain.blog.Author"
  	flushCache="true"
  	statementType="PREPARED"
  	timeout="20">

<delete
  	id="deleteAuthor"
  	parameterType="domain.blog.Author"
  	flushCache="true"
  	statementType="PREPARED"
  	timeout="20">

Insert、Update、Delete元素的属性:

属性描述
id命名空间中的唯一标识符,可被用来代表这条语句。
parameterType将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

Insert、Update、Delete元素的示例:

<insert id="insertAuthor">
    insert into Author (id,username,password,email,bio)
    values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
    update Author set
        username = #{username},
        password = #{password},
        email = #{email},
        bio = #{bio}
    where id = #{id}
</update>

<delete id="deleteAuthor">
  	delete from Author where id = #{id}
</delete>

如果你的数据库还支持多行插入, 你也可以传入一个 Author 数组或集合,并返回自动生成的主键。

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
  	insert into Author (username, password, email, bio) values
  	<foreach item="item" collection="list" separator=",">
    	(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
 	</foreach>
</insert>

对于不支持自动生成类型的数据库或可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。

<insert id="insertAuthor">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
    	select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
    </selectKey>
    insert into Author
    	(id, username, password, email,bio, favourite_section)
    values
    	(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

selectKey元素描述如下:

<selectKey
    keyProperty="id"
    resultType="int"
    order="BEFORE"
    statementType="PREPARED">

selectKey元素的属性

属性描述
keyPropertyselectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
resultType结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。
order这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先生成主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。
Sql

这个元素可以被用来定义可重用的 SQL 代码段 , 它可以(在加载的时候)被静态地设置参数。 比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password</sql>

<!-- 上面的语句可以包含在其他语句中 -->
<select id="selectUsers" resultType="map">
    select
        <include refid="userColumns"><property name="alias" value="t1"/></include>,
        <include refid="userColumns"><property name="alias" value="t2"/></include>
    from some_table t1 cross join some_table t2
</select>

属性值也可以被用在 include 元素的 refid 属性里或 include 元素的内部语句中,例如:

<sql id="sometable">
  	${prefix}Table
</sql>

<sql id="someinclude">
  	from
    	<include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
    select
    	field1, field2, field3
    <include refid="someinclude">
        <property name="prefix" value="Some"/>
        <property name="include_target" value="sometable"/>
    </include>
</select>

参数

#{height,javaType=double,jdbcType=NUMERIC,typeHandler=MyTypeHandler,numericScale=2,mode=OUT}
  • typeHandler:类型处理器。

  • numericScale:指定小数点后保留的位数

  • typeHandler:指定类型处理器类

  • modemode 属性允许你指定 INOUTINOUT 参数。

如果参数的 modeOUTINOUT,就像你在指定输出参数时所期望的行为那样,参数对象的属性实际值将会被改变。 如果 modeOUT(或 INOUT),而且 jdbcTypeCURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 引用来将结果集 ResultMap 映射到参数的类型上。要注意这里的 javaType 属性是可选的,如果留空并且 jdbcType 是 CURSOR,它会被自动地被设为 ResultMap

字符串替换
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);

@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);

@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
    
// and more "findByXxx" method
// 可以使用下面的方法来代替以上方法
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

用这种方式接受用户的输入,并将其用于语句中的参数是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

结果映射

resultMap 元素是 MyBatis 中最重要最强大的元。resultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

一个简单的映射语句示例:

<select id="selectUsers" resultType="map">
  	select id, username, hashedPassword
  	from t_user
  	where id = #{id}
</select>

指定一个JavaBean:

package com.wwj.pojo;

public class User{
	private int id;
	private String userName;
	private String hashedPassword;
	
	//Set和Get方法
}
<!-- 这里选择语句中的列名要和User类中的属性对应 -->
<select id="selectUsers" resultType="com.wwj.pojo.User">
  	select id, username, hashedPassword
  	from t_user
  	where id = #{id}
</select>

<!-- 当然也可以设置别名 -->
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.wwj.pojo.User" alias="User"/>

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
  	select id, username, hashedPassword
  	from some_table
  	where id = #{id}
</select>

<!-- 如果列名和属性名没有精确匹配,可以使用以下方式类匹配标签 -->
<select id="selectUsers" resultType="User">
    select
        user_id             as "id",
        user_name           as "userName",
        hashed_password     as "hashedPassword"
    from some_table
    where id = #{id}
</select>

使用外部的 resultMap,这也是解决列名不匹配的另外一种方式。

<select id="selectUsers" resultMap="userResultMap">
    select user_id, user_name, hashed_password
    from some_table
    where id = #{id}
</select>

<resultMap id="userResultMap" type="User">
  	<id property="id" column="user_id" />
  	<result property="username" column="user_name"/>
  	<result property="password" column="hashed_password"/>
</resultMap>
高级结果映射

举个栗子。。。先跳过,看完后面再来看这些代码。

<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
    select
        B.id as blog_id,
        B.title as blog_title,
        B.author_id as blog_author_id,
        A.id as author_id,
        A.username as author_username,
        A.password as author_password,
        A.email as author_email,
        A.bio as author_bio,
        A.favourite_section as author_favourite_section,
        P.id as post_id,
        P.blog_id as post_blog_id,
        P.author_id as post_author_id,
        P.created_on as post_created_on,
        P.section as post_section,
        P.subject as post_subject,
        P.draft as draft,
        P.body as post_body,
        C.id as comment_id,
        C.post_id as comment_post_id,
        C.name as comment_name,
        C.comment as comment_text,
        T.id as tag_id,
        T.name as tag_name
    from Blog B
        left outer join Author A on B.author_id = A.id
        left outer join Post P on B.id = P.blog_id
        left outer join Comment C on P.id = C.post_id
        left outer join Post_Tag PT on PT.post_id = P.id
        left outer join Tag T on PT.tag_id = T.id
    where B.id = #{id}
</select>

<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
    <constructor>
        <idArg column="blog_id" javaType="int"/>
    </constructor>
    <result property="title" column="blog_title"/>
    <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
        <result property="favouriteSection" column="author_favourite_section"/>
    </association>
    <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <association property="author" javaType="Author"/>
        <collection property="comments" ofType="Comment">
            <id property="id" column="comment_id"/>
        </collection>
        <collection property="tags" ofType="Tag" >
            <id property="id" column="tag_id"/>
        </collection>
        <discriminator javaType="int" column="draft">
            <case value="1" resultType="DraftPost"/>
        </discriminator>
    </collection>
</resultMap>
结果映射属性
  • constructor-用于在实例化类时,注入结果到构造方法中。
    • idArg-ID参数,标记出作为ID的结果,可以帮助提高整体性能。
    • arg-将被注入到构造方法的一个普通结果。
  • id-一个ID结果,标记出作为ID的结果可以提高整体性能。
  • result-注入到字段或JavaBean属性的普通结果。
  • association-一个复杂类型的关联,许多结果将包装成这种类型。
    • 嵌套结果映射-关联本身可以是一个resultMap元素,或者从别处引用一个。
  • collection-一个复杂类型的集合
    • 嵌套结果映射-集合本身可以是一个resultMap元素,或者从别处引用一个。
  • discriminator-
    • case- 基于某些值的结果映射
      • 嵌套结果映射 – case本身可以是一个resultMap元素,因此可以具有相同的结构和元素,或者从别处引用一个。

resultMap属性列表

属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。
id & result

idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。id 元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

Id 和 Result 的属性 :

属性描述
property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
支持的JDBC类型
BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY
构造方法
//在类中添加一个构造方法
public class User {
   	//...
   	public User(Integer id, String username, int age) {
    	//...
    }
    //...
}
<!-- 开启name属性需要添加@Param注解 -->
<!-- 如果不使用name属性,则必须保持参数顺序一致 -->
<constructor>
    <idArg column="id" javaType="int" name="id" />
    <arg column="age" javaType="_int" name="age" />
    <arg column="username" javaType="String" name="username" />
</constructor>

通过修改对象属性的方式,可以满足大多数的数据传输对象(Data Transfer Object, DTO)以及绝大部分领域模型的要求。但有些情况下你想使用不可变类。 一般来说,很少改变或基本不变的包含引用或数据的表,很适合使用不可变类。 构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入。 constructor 元素就是为此而生的。

constructor的属性:

属性描述
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。具体请参考关联元素。
resultMap结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 将会将包含重复或部分数据重复的结果集。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许你 “串联”结果映射,以便解决嵌套结果集的问题。想了解更多内容,请参考下面的关联元素。
name构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素。参看上面的解释。
关联
<association property="author" column="blog_author_id" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
</association>

关联(association)元素处理“有一个”类型的关系。关联与其他类型映射的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

associatioin的属性:

属性描述
property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
关联的嵌套Select查询
属性描述
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
fetchType可选的。有效值为 lazyeager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。

一个简单示例:

<resultMap id="blogResult" type="Blog">
  	<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  	SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  	SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

这个例子中有两个Select语句,一个用来加载blog,一个用来加载author,blog的结果映射描述了应该使用selectAuthor语句加载它的author属性,它的其他属性会被自动加载,但这样会产生**“N+1查询问题”**。

但这样会产生“N+1查询问题”。

**“N+1查询问题”**是这样的:

  • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
  • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。

这个问题会导致成百上千的 SQL 语句被执行。 为了解决这个问题,就有了下面这种方法。

关联的嵌套结果映射
属性描述
resultMap结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。
columnPrefix当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。
notNullColumn默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 selectresultMap 元素使用。默认值:未设置(unset)。

下面是一个简单的例子:

<select id="selectBlog" resultMap="blogResult">
    select
        B.id            as blog_id,
        B.title         as blog_title,
        B.author_id     as blog_author_id,
        A.id            as author_id,
        A.username      as author_username,
        A.password      as author_password,
        A.email         as author_email,
        A.bio           as author_bio
    from Blog B left outer join Author A on B.author_id = A.id
    where B.id = #{id}
</select>


<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
</resultMap>

<!-- 等同于这样 -->
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
    </association>
</resultMap>
关联的多结果集
属性描述
column当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以识别关系中的父类型与子类型。
foreignColumn指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
resultSet指定用于加载复杂类型的结果集名字。

从版本 3.2.3 开始,MyBatis 提供了另一种解决 N+1 查询问题的方法。

某些数据库允许存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 我们可以利用这个特性,在不使用连接的情况下,只访问数据库一次就能获得相关数据。

在例子中,存储过程执行下面的查询并返回两个结果集。第一个结果集会返回博客(Blog)的结果,第二个则返回作者(Author)的结果。

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM AUTHOR WHERE ID = #{id}

在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
    {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

现在我们可以指定使用 “authors” 结果集的数据来填充 “author” 关联:

<resultMap id="blogResult" type="Blog">
    <id property="id" column="id" />
    <result property="title" column="title"/>
    <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="email" column="email"/>
        <result property="bio" column="bio"/>
    </association>
</resultMap>
集合

集合元素处理“有多个”类型的关联。集合属性和关联属性几乎是一样的。建议和关联一起比较学习。

<collection property="posts" ofType="domain.blog.Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
</collection>
集合的嵌套Select查询
<resultMap id="blogResult" type="Blog">
    <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  	SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  	SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

JavaType和ofType都是用来指定对象类型的,但是JavaType是用来指定pojo中属性的类型,而ofType指定的是映射到list集合属性中pojo的类型。

集合的嵌套结果映射
<select id="selectBlog" resultMap="blogResult">
    select
        B.id as blog_id,
        B.title as blog_title,
        B.author_id as blog_author_id,
        P.id as post_id,
        P.subject as post_subject,
        P.body as post_body,
    from Blog B
    	left outer join Post P on B.id = P.blog_id
    where B.id = #{id}
</select>

<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <result property="body" column="post_body"/>
     </collection>
</resultMap>

<!-- 更详细的结果映射 -->
<resultMap id="blogResult" type="Blog">
    <id property="id" column="blog_id" />
    <result property="title" column="blog_title"/>
    <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
</resultMap>
集合的多结果集

像关联元素那样,我们可以通过执行存储过程实现,它会执行两个查询并返回两个结果集,一个是博客的结果集,另一个是文章的结果集:

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM POST WHERE BLOG_ID = #{id}

在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。

<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
  	{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>

我们指定 “posts” 集合将会使用存储在 “posts” 结果集中的数据进行填充:

<resultMap id="blogResult" type="Blog">
    <id property="id" column="id" />
    <result property="title" column="title"/>
    <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
        <id property="id" column="id"/>
        <result property="subject" column="subject"/>
        <result property="body" column="body"/>
    </collection>
</resultMap>

高级关联和集合映射是一个深度话题。文档的介绍只能到此为止。配合少许的实践,你会很快了解全部的用法。

鉴别器
<discriminator javaType="int" column="draft">
  	<case value="1" resultType="DraftPost"/>
</discriminator>

一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。

一个鉴别器的定义需要指定 column 和 javaType 属性。column 指定了 MyBatis 查询被比较值的地方。 而 javaType 用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。例如:

<resultMap id="vehicleResult" type="Vehicle">
    <id property="id" column="id" />
    <result property="vin" column="vin"/>
    <result property="year" column="year"/>
    <result property="make" column="make"/>
    <result property="model" column="model"/>
    <result property="color" column="color"/>
    <discriminator javaType="int" column="vehicle_type">
        <case value="1" resultType="carResult">
            <result property="doorCount" column="door_count" />
        </case>
        <case value="2" resultType="truckResult">
            <result property="boxSize" column="box_size" />
            <result property="extendedCab" column="extended_cab" />
        </case>
        <case value="3" resultType="vanResult">
            <result property="powerSlidingDoor" column="power_sliding_door" />
        </case>
        <case value="4" resultType="suvResult">
            <result property="allWheelDrive" column="all_wheel_drive" />
        </case>
    </discriminator>
</resultMap>

自动映射

通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

设置了结果映射后,自动映射仍能工作。如:

<select id="selectUsers" resultMap="userResultMap">
    select
        user_id             as "id",
        user_name           as "userName",
        hashed_password
    from some_table
    where id = #{id}
</select>

<resultMap id="userResultMap" type="User">
    <result property="password" column="hashed_password"/>
</resultMap>

自动映射有三种等级,默认是PARTIAL

  • NONE - 禁用自动映射。仅对手动映射的属性进行映射。
  • PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射
  • FULL - 自动映射所有属性。

无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping 属性来为指定的结果映射设置启用/禁用自动映射。

<resultMap id="userResultMap" type="User" autoMapping="false">
  	<result property="password" column="hashed_password"/>
</resultMap>

缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制 。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

eviction-清除策略,包括以下四种,默认的清除策略是 LRU:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

flushInterval - 刷新间隔,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置。

size-引用数目,可以设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly-只读,可以被设置为 true 或 false ,默认为false。

使用自定义缓存

除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。

public interface Cache {
    String getId();
    int getSize();
    void putObject(Object key, Object value);
    Object getObject(Object key);
    boolean hasKey(Object key);
    Object removeObject(Object key);
    void clear();
}
cache-ref

使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

<select id="findActiveBlogWithTitleLike" resultType="Blog">
    SELECT * FROM BLOG
    WHERE state = ‘ACTIVE’
    <if test="title != null">
        AND title like #{title}
    </if>
</select>

这条语句提供了一种可选的查找文本功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会对“title”一列进行模糊查找并返回 BLOG 结果(细心的读者可能会发现,“title”参数值是可以包含一些掩码或通配符的)。

如果希望通过“title”和“author”两个参数进行可选搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

<select id="findActiveBlogLike" resultType="Blog">
     SELECT * FROM BLOG WHERE state = ‘ACTIVE’
     <if test="title != null">
         AND title like #{title}
     </if>
     <if test="author != null and author.name != null">
         AND author_name like #{author.name}
     </if>
</select>

choose,when,otherwise

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找的情形,若两者都没有提供,就返回所有符合条件的 BLOG。

<select id="findActiveBlogLike" resultType="Blog">
    SELECT * FROM BLOG WHERE state = ‘ACTIVE’
    <choose>
        <when test="title != null">
        	AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
        	AND author_name like #{author.name}
        </when>
        <otherwise>
        	AND featured = 1
        </otherwise>
    </choose>
</select>

trim,where,set

现在回到“if”示例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发生什么。

<select id="findActiveBlogLike" resultType="Blog">
    SELECT * FROM BLOG
    WHERE
    <if test="state != null">
    	state = #{state}
    </if>
    <if test="title != null">
    	AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
    	AND author_name like #{author.name}
    </if>
</select>

如果这些条件没有一个能匹配上会发生什么?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。为了解决这个问题,可以自定义处理方式,比如这样:

<select id="findActiveBlogLike" resultType="Blog">
     SELECT * FROM BLOG
     <where>
         <if test="state != null">
         	state = #{state}
         </if>
         <if test="title != null">
         	AND title like #{title}
         </if>
         <if test="author != null and author.name != null">
         	AND author_name like #{author.name}
         </if>
     </where>
</select>

where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。通过自定义 trim 元素来定制 where 元素的功能。比如,和where 元素等价的自定义trim元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它的作用是移除所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容。

类似的用于动态更新语句的解决方案叫做 setset 元素可以用于动态包含需要更新的列,而舍去其它的。比如:

<update id="updateAuthorIfNecessary">
    update Author
    <set>
        <if test="username != null">username=#{username},</if>
        <if test="password != null">password=#{password},</if>
        <if test="email != null">email=#{email},</if>
        <if test="bio != null">bio=#{bio}</if>
    </set>
    where id=#{id}
</update>

这里,set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,因为用了条件语句之后很可能就会在生成的 SQL 语句的后面留下这些逗号。(译者注:因为用的是“if”元素,若最后一个“if”没有匹配上而前面的匹配上,SQL 语句的最后就会有一个逗号遗留)

若你对 set 元素等价的自定义 trim 元素的代码感兴趣,那这就是它的真面目:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

foreach

动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="list"
    	open="(" separator="," close=")">
    		#{item}
    </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

script

要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:

@Update({"<script>",
    "update Author",
    "<set>",
    "    <if test='username != null'>username=#{username},</if>",
    "    <if test='password != null'>password=#{password},</if>",
    "    <if test='email != null'>email=#{email},</if>",
    "    <if test='bio != null'>bio=#{bio}</if>",
    "</set>",
    "where id=#{id}",
    "</script>"})
void updateAuthorValues(Author author);

bind

bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
    <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
    SELECT * FROM BLOG
    WHERE title LIKE #{pattern}
</select>

多数据库支持

一个配置了“_databaseId”变量的 databaseIdProvider 可用于动态代码中,这样就可以根据不同的数据库厂商构建特定的语句。比如下面的例子:

<insert id="insert">
    <selectKey keyProperty="id" resultType="int" order="BEFORE">
    <if test="_databaseId == 'oracle'">
    	select seq_users.nextval from dual
    </if>
    <if test="_databaseId == 'db2'">
    	select nextval for seq_users from sysibm.sysdummy1"
    </if>
    </selectKey>
    	insert into users values (#{id}, #{name})
</insert>

动态SQL中的可拔插脚本语言

MyBatis 从 3.2 开始支持可插拔脚本语言,这允许你插入一种脚本语言驱动,并基于这种语言来编写动态 SQL 查询语句。

可以通过实现以下接口来插入一种语言:

public interface LanguageDriver {
    
    ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
    
    SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
    
    SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

一旦设定了自定义语言驱动,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

<typeAliases>
    <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
    <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

除了设置默认语言,你也可以针对特殊的语句指定特定语言,可以通过如下的 lang 属性来完成:

<select id="selectBlog" lang="myLanguage">
	SELECT * FROM BLOG
</select>

或者,如果你使用的是映射器接口类,在抽象方法上加上 @Lang 注解即可:

public interface Mapper {
    @Lang(MyLanguageDriver.class)
    @Select("SELECT * FROM BLOG")
    List<Blog> selectBlog();
}

Java API

应用目录结构

典型的应用目录结构(仅供此次学习参考):

/my_application
  /bin
  /devlib
  /lib                <-- MyBatis *.jar 文件在这里。
  /src
    /org/myapp/
      /action
      /data           <-- MyBatis 配置文件在这里, 包括映射器类, XML 配置, XML 映射文件。
        /mybatis-config.xml
        /BlogMapper.java
        /BlogMapper.xml
      /model
      /service
      /view
    /properties       <-- 在你 XML 中配置的属性文件在这里。
  /test
    /org/myapp/
      /action
      /data
      /model
      /service
      /view
    /properties
  /web
    /WEB-INF
      /web.xml

SqlSession

使用 MyBatis 的主要 Java 接口就是 SqlSessionSqlSession 是由 SqlSessionFactory 实例创建的。SqlSessionFactory对象包含创建 SqlSession实例的所有方法。而 SqlSessionFactory本身是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或手动配置 Java 代码来创建SqlSessionFactory

当 Mybatis 与一些依赖注入框架(如 Spring 或者 Guice)同时使用时,SqlSession将被依赖注入框架所创建,所以你不需要使用 SqlSessionFactoryBuilder 或者 SqlSessionFactory

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 有五个 build() 方法,每一种都允许你从不同的资源中创建一个 SqlSessionFactory实例。

SqlSessionFactory build(InputStream inputStream)
    
SqlSessionFactory build(InputStream inputStream, String environment)
    
SqlSessionFactory build(InputStream inputStream, Properties properties)
    
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
    
SqlSessionFactory build(Configuration config)

第一种方法是最常用的,它使用了一个参照了 XML 文档或上面讨论过的更特定的 mybatis-config.xml 文件的 Reader 实例。可选的参数是 environmentproperties。environment 决定加载哪种环境,包括数据源和事务管理器。比如:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC">
        ...
        <dataSource type="POOLED">
        ...
    </environment>
    <environment id="production">
        <transactionManager type="MANAGED">
        ...
        <dataSource type="JNDI">
        ...
    </environment>
</environments>

总结一下,前四个方法很大程度上是相同的,但是由于覆盖机制,便允许你可选地指定environment 和/或 properties

以下给出一个从 mybatis-config.xml 文件创建 SqlSessionFactory 的示例:

String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

最后一个 build 方法的参数为 Configuration 实例。configuration 类包含你可能需要了解 SqlSessionFactory 实例的所有内容。Configuration 类对于配置的自查很有用,它包含查找和操作 SQL 映射(当应用接收请求时便不推荐使用)。作为一个 Java API 的 configuration 类具有所有配置的开关,这些你已经了解了。这里有一个简单的示例,教你如何手动配置 configuration 实例,然后将它传递给 build() 方法来创建 SqlSessionFactory。

DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);

由此,你获得了一个可以用来创建SqlSession实例的SqlSessionFactory了。

SqlSessionFactory

SqlSessionFactory有六个方法创建SqlSession 实例。通常来说,当你选择这些方法时你需要考虑以下几点:

  • 事务处理:指定是否需要在 session 使用事务或者使用自动提交功能(auto-commit)吗?(通常意味着很多数据库和/或 JDBC 驱动没有事务)
  • 连接:指定是否需要依赖 MyBatis 获得来自数据源的配置吗?还是使用自己提供的配置?
  • 执行语句:指定是否需要 MyBatis 复用预处理语句和/或批量更新语句(包括插入和删除)吗?

基于以上需求,有下列已重载的多个 openSession() 方法供使用。

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

默认的openSession()方法没有参数,它会创建有如下特性的SqlSession

  • 会开启一个事务(也就是不自动提交)。
  • 将从由当前环境配置的 DataSource实例中获取 Connection 对象。
  • 事务隔离级别将会使用驱动或数据源的默认设置。
  • 预处理语句不会被复用,也不会批量处理更新。

这些方法大都是可读性强的。向 autoCommit 可选参数传递 true 值即可开启自动提交功能。若要使用自己的 Connection 实例,传递一个 Connection 实例给 connection 参数即可。注意并未覆写同时设置 ConnectionautoCommit 两者的方法,因为 MyBatis 会使用正在使用中的、设置了 Connection 的环境。MyBatis 为事务隔离级别调用使用了一个 Java 枚举包装器,称为 TransactionIsolationLevel,若不使用它,将使用 JDBC 所支持五个隔离级(NONEREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE),并按它们预期的方式来工作。

还有一个可能对你来说是新见到的参数,就是 ExecutorType。这个枚举类型定义了三个值:

  • ExecutorType.SIMPLE:这个执行器类型不做特殊的事情。它为每个语句的执行创建一个新的预处理语句。
  • ExecutorType.REUSE:这个执行器类型会复用预处理语句。
  • ExecutorType.BATCH:这个执行器会批量执行所有更新语句,如果 SELECT 在它们中间执行,必要时请把它们区分开来以保证行为的易读性。

getConfiguration方法会返回一个 Configuration 实例,在运行时你可以使用它来自检 MyBatis 的配置。

SqlSession

SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。在 SqlSession 类中有超过 20 个方法,所以将它们组合成易于理解的分组。

执行语句方法

这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)

int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)


<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)

void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)

selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象或 null 值。如果返回值多于一个,那么就会抛出异常。如果你不知道返回对象的数量,请使用 selectList。如果需要查看返回对象是否存在,可行的方案是返回一个值即可(0 或 1)。selectMap 稍微特殊一点,因为它会将返回的对象的其中一个属性作为 key 值,将对象作为 value 值,从而将多结果集转为 Map 类型值。因为并不是所有语句都需要参数,所以这些方法都重载成不需要参数的形式。 insert,update和delete方法返回的值表示该语句影响的行数。

RowBounds 参数会告诉 MyBatis 略过指定数量的记录,还有限制返回结果的数量。RowBounds 类有一个构造方法来接收 offset 和 limit,另外,它们是不可二次赋值的。

int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);

ResultHandler 参数允许你按你喜欢的方式处理每一行。你可以将它添加到 List 中、创建 Map 和 Set,或者丢弃每个返回值都可以,它取代了仅保留执行语句过后的总结果列表的死板结果。你可以使用 ResultHandler 做很多事,并且这是 MyBatis 自身内部会使用的方法,以创建结果集列表。

从3.4.6版本开始,存储过程的每个REFCURSOR输出参数(如果有)将使用传递给CALLABLE语句的ResultHandler 。

package org.apache.ibatis.session;
public interface ResultHandler<T> {
  	void handleResult(ResultContext<? extends T> context);
}

ResultContext 参数允许你访问结果对象本身、被创建的对象数目、以及返回值为 Boolean 的 stop 方法,你可以使用此 stop 方法来停止 MyBatis 加载更多的结果。

使用 ResultHandler 的时候需要注意以下两种限制:

  • 从被 ResultHandler 调用的方法返回的数据不会被缓存。
  • 当使用结果映射集(resultMap)时,MyBatis 大多数情况下需要数行结果来构造外键对象。如果你正在使用 ResultHandler,你可以给出外键(association)或者集合(collection)尚未赋值的对象。
批量立即更新方法

有一个方法可以刷新(执行)存储在 JDBC 驱动类中的批量更新语句。当你将 ExecutorType.BATCH 作为 ExecutorType 使用时可以采用此方法。

List<BatchResult> flushStatements()
事务控制方法

控制事务作用域有四个方法。当然,如果你已经设置了自动提交或你正在使用外部事务管理器,这就没有任何效果了。然而,如果你正在使用 JDBC 事务管理器,由Connection 实例来控制,那么这四个方法就会派上用场:

void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)

默认情况下 MyBatis 不会自动提交事务,除非它侦测到有插入、更新或删除操作改变了数据库。如果你已经做出了一些改变而没有使用这些方法,那么你可以传递 true 值到 commit 和 rollback 方法来保证事务被正常处理(注意,在自动提交模式或者使用了外部事务管理器的情况下设置 force 值对 session 无效)。很多时候你不用调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。然而,如果你需要在支持多提交和回滚的 session 中获得更多细粒度控制,你可以使用回滚操作来达到目的。

本地缓存

Mybatis有两种缓存:本体缓存和二级缓存。

每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询语句本身都会被保存在本地缓存中,那么,相同的查询语句和相同的参数所产生的更改就不会二度影响数据库了。本地缓存会被增删改、提交事务、关闭事务以及关闭 session 所清空。

默认情况下,本地缓存数据可在整个 session 的周期内使用,这一缓存需要被用来解决循环引用错误和加快重复嵌套查询的速度,所以它可以不被禁用掉,但是你可以设置 localCacheScope=STATEMENT 表示缓存仅在语句执行时有效。

如果 localCacheScope 被设置为 SESSION,那么 MyBatis 所返回的引用将传递给保存在本地缓存里的相同对象。对返回的对象(例如 list)做出任何更新将会影响本地缓存的内容,进而影响存活在 session 生命周期中的缓存所返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。

调用以下方法清空本地缓存。

void clearCache()
确保 SqlSession 被关闭
void close()

必须保证的最重要的事情是你要关闭所打开的任何 session。保证做到这点的最佳方式是这样:

try (SqlSession session = sqlSessionFactory.openSession()) {
    // following 3 lines pseudocod for "doing some work"
    session.insert(...);
    session.update(...);
    session.delete(...);
    session.commit();
}

就像 SqlSessionFactory,你可以通过调用当前使用中的 SqlSession 的 getConfiguration 方法来获得 Configuration 实例。

使用映射器
<T> T getMapper(Class<T> type)

上述的各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐,可能会产生类型安全问题并且对于你的 IDE 和单元测试也没有实质性的帮助。因此,一个更通用的方式来执行映射语句是使用映射器类。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。下面的示例展示了一些方法签名以及它们是如何映射到 SqlSession 上的。

public interface AuthorMapper {
    // (Author) selectOne("selectAuthor",5);
    Author selectAuthor(int id);
    // (List<Author>) selectList(“selectAuthors”)
    List<Author> selectAuthors();
    // (Map<Integer,Author>) selectMap("selectAuthors", "id")
    @MapKey("id")
    Map<Integer, Author> selectAuthors();
    // insert("insertAuthor", author)
    int insertAuthor(Author author);
    // updateAuthor("updateAuthor", author)
    int updateAuthor(Author author);
    // delete("deleteAuthor",5)
    int deleteAuthor(int id);
}

总之,每个映射器方法签名应该匹配相关联的 SqlSession 方法,而字符串参数 ID 无需匹配。相反,方法名必须匹配映射语句的 ID。

此外,返回类型必须匹配期望的结果类型,单返回值时为所指定类的值,多返回值时为数组或集合。所有常用的类型都是支持的,包括:原生类 型、Map、POJO 和 JavaBean。

**映射器接口不需要去实现任何接口或继承自任何类。只要方法可以被唯一标识对应的映射语句就可以了。映射器接口可以继承自其他接口。当使用 XML 来构建映射器接口时要保证语句被包含在合适的命名空间中。而且,唯一的限制就是你不能在两个继承关系的接口中拥有相同的方法签名 **

你可以传递多个参数给一个映射器方法。如果你这样做了,默认情况下它们将会以 “param” 字符串紧跟着它们在参数列表中的位置来命名,比如:#{param1}、#{param2}等。如果你想改变参数的名称(只在多参数情况下),那么你可以在参数上使用 @Param(“paramName”) 注解。

你也可以给方法传递一个 RowBounds 实例来限制查询结果。

映射器注解

MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。这个配置 API 是基于 XML 的 MyBatis 配置的基础,也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销。

不幸的是,Java 注解的的表达力和灵活性十分有限。尽管很多时间都花在调查、设计和试验上,最强大的 MyBatis 映射并不能用注解来构建——并不是在开玩笑,的确是这样。比方说,C#属性就没有这些限制,因此 MyBatis.NET 将会比 XML 有更丰富的选择。也就是说,基于 Java 注解的配置离不开它的特性。

注解如下表所示:

注解使用对象相对应的 XML描述
@CacheNamespace<cache>为给定的命名空间(比如类)配置缓存。属性有:implemetation, eviction, flushInterval, size, readWrite, blockingproperties
@PropertyN/A<property>指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:name, value。(仅在MyBatis 3.4.2以上版本生效)
@CacheNamespaceRef<cacheRef参照另外一个命名空间的缓存来使用。属性有:value, name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。
@ConstructorArgs方法<constructor>收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。
@ArgN/A<arg> <idArg>单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:id, column, javaType, jdbcType, typeHandler, selectresultMap。id 属性是布尔值,来标识用于比较的属性,和`` XML 元素相似。
@TypeDiscriminator方法<discriminator>一组实例值被用来决定结果映射的表现。属性有:column, javaType, jdbcType, typeHandlercases。cases 属性是实例数组。
@CaseN/A<case>单独实例的值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
@Results方法<resultMap>结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:value, id。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。
@ResultN/A<result> <id>在列和属性或字段之间的单独结果映射。属性有:id, column, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的相似)的属性。one 属性是单独的联系,和 相似,而 many 属性是对集合而言的,和``相似。它们这样命名是为了避免名称冲突。
@OneN/A<associaton>复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。
@ManyN/A<collection>映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用.
@MapKey方法这是一个用在返回值为 Map 的方法上的注解。它能够将存放对象的 List 转化为 key 值为对象的某一属性的 Map。属性有: value,填入的是对象的属性名,作为 Map 的 key 值。
@Options方法映射语句的属性这个注解提供访问大范围的交换和配置选项的入口,它们通常在映射语句上作为属性出现。Options 注解提供了通俗易懂的方式来访问它们,而不是让每条语句注解变复杂。属性有:useCache=true, flushCache=FlushCachePolicy.DEFAULT, resultSetType=DEFAULT, statementType=PREPARED, fetchSize=-1, timeout=-1, useGeneratedKeys=false, keyProperty="", keyColumn="", resultSets=""。值得一提的是, Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的预期以外的行为。 注意: keyColumn 属性只在某些数据库中有效(如 Oracle、PostgreSQL等)。请在插入语句一节查看更多关于 keyColumnkeyProperty 两者的有效值详情。
- @Insert - @Update - @Delete - @Select方法Insert Update Delete Select这四个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串之间先会被填充一个空格再连接成单个完整的字符串。这有效避免了以 Java 代码构建 SQL 语句时的“丢失空格”的问题。然而,你也可以提前手动连接好字符串。属性有:value,填入的值是用来组成单个 SQL 语句的字符串数组。
@InsertProvider @UpdateProvider @DeleteProvider @SelectProvider方法Insert Update Delete Select允许构建动态 SQL。这些备选的 SQL 注解允许你指定类名和返回在运行时执行的 SQL 语句的方法。(自从MyBatis 3.4.6开始,你可以用 CharSequence 代替 String 来返回类型返回值了。)当执行映射语句的时候,MyBatis 会实例化类并执行方法,类和方法就是填入了注解的值。你可以把已经传递给映射方法了的对象作为参数,“Mapper interface type” 和 “Mapper method” and “Database ID” 会经过 ProviderContext (仅在MyBatis 3.4.5及以上支持)作为参数值。(MyBatis 3.4及以上的版本,支持多参数传入) 属性有: value, type, methodvalue and type 属性需填入类(The type attribute is alias for value, you must be specify either one)。 method 需填入该类定义了的方法名 (Since 3.5.1, you can omit method attribute, the MyBatis will resolve a target method via the ProviderMethodResolver interface. If not resolve by it, the MyBatis use the reserved fallback method that named provideSql)。 注意 接下来的小节将会讨论类,能帮助你更轻松地构建动态 SQL。
@Param参数N/A如果你的映射方法的形参有多个,这个注解使用在映射方法的参数上就能为它们取自定义名字。若不给出自定义名字,多参数(不包括 RowBounds 参数)则先以 “param” 作前缀,再加上它们的参数位置作为参数别名。例如 #{param1}, #{param2},这个是默认值。如果注解是 @Param("person"),那么参数就会被命名为 #{person}
@SelectKey方法<selectKey>这个注解的功能与 `` 标签完全一致,用在已经被 @Insert@InsertProvider@Update@UpdateProvider 注解了的方法上。若在未被上述四个注解的方法上作 @SelectKey 注解则视为无效。如果你指定了 @SelectKey 注解,那么 MyBatis 就会忽略掉由 @Options 注解所设置的生成主键或设置(configuration)属性。属性有:statement 填入将会被执行的 SQL 字符串数组,keyProperty 填入将会被更新的参数对象的属性的值,before 填入 truefalse 以指明 SQL 语句应被在插入语句的之前还是之后执行。resultType 填入 keyProperty 的 Java 类型和用 StatementPreparedStatementCallableStatement 中的 STATEMENTPREPAREDCALLABLE 中任一值填入 statementType。默认值是 PREPARED
@ResultMap方法N/A这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 `` 的id。这使得注解的 select 可以复用那些定义在 XML 中的 ResultMap。如果同一 select 注解中还存在 @Results 或者 @ConstructorArgs,那么这两个注解将被此注解覆盖。
@ResultType方法N/A此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的 `` 节点中指定了,就不需要其他的注解了。其他情况下则使用此注解。比如,如果 @Select 注解在一个将使用结果处理器的方法上,那么返回类型必须是 void 并且这个注解(或者@ResultMap)必选。这个注解仅在方法返回类型是 void 的情况下生效。
@Flush方法N/A如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上)
映射器申明样例

这个例子展示了如何使用 @SelectKey 注解来在插入前读取数据库序列的值:

@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);

这个例子展示了如何使用 @SelectKey 注解来在插入后读取数据库识别列的值:

@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);

这个例子展示了如何使用 @Flush 注解去调用 SqlSession#flushStatements()

@Flush
List<BatchResult> flush();

这些例子展示了如何通过指定 @Result 的 id 属性来命名结果集:

@Results(id = "userResult", value = {
    @Result(property = "id", column = "uid", id = true),
    @Result(property = "firstName", column = "first_name"),
    @Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);

@Results(id = "companyResults")
@ConstructorArgs({
    @Arg(column = "cid", javaType = Integer.class, id = true),
    @Arg(column = "name", javaType = String.class)
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);

这个例子展示了单一参数使用 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);

class UserSqlBuilder {
    public static String buildGetUsersByName(final String name) {
    	return new SQL(){{
            SELECT("*");
            FROM("users");
            if (name != null) {
                WHERE("name like #{value} || '%'");
            }
            ORDER_BY("id");
    	}}.toString();
    }
}

这个例子展示了多参数使用 @SqlProvider 注解:

@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
    @Param("name") String name, @Param("orderByColumn") String orderByColumn);

class UserSqlBuilder {

    // If not use @Param, you should be define same arguments with mapper method
    public static String buildGetUsersByName(
        final String name, final String orderByColumn) {
        return new SQL(){{
            SELECT("*");
            FROM("users");
            WHERE("name like #{name} || '%'");
            ORDER_BY(orderByColumn);
    	}}.toString();
	}

    // If use @Param, you can define only arguments to be used
    public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
    	return new SQL(){{
            SELECT("*");
            FROM("users");
            WHERE("name like #{name} || '%'");
            ORDER_BY(orderByColumn);
    	}}.toString();
    }
}

这个例子展示了 ProviderMethodResolver的默认实现的用法(自MyBatis 3.5.1或更高版本开始可用):

@SelectProvider(UserSqlProvider.class)
List<User> getUsersByName(String name);

// Implements the ProviderMethodResolver on your provider class
class UserSqlProvider implements ProviderMethodResolver {
    // In default implementation, it will resolve a method that method name is matched with mapper method
    public static String getUsersByName(final String name) {
        return new SQL(){{
            SELECT("*");
            FROM("users");
            if (name != null) {
            	WHERE("name like #{value} || '%'");
            }
            ORDER_BY("id");
        }}.toString();
    }
}

SQL语句创建器

MyBatis 3提供了方便的工具类来帮助解决该问题。使用SQL类,简单地创建一个实例来调用方法生成SQL语句。 下面给出一些简单的例子:

// Anonymous inner class
public String deletePersonSql() {
    return new SQL() {{
        DELETE_FROM("PERSON");
        WHERE("ID = #{id}");
    }}.toString();
}

// Builder / Fluent style
public String insertPersonSql() {
    String sql = new SQL()
        .INSERT_INTO("PERSON")
        .VALUES("ID, FIRST_NAME", "#{id}, #{firstName}")
        .VALUES("LAST_NAME", "#{lastName}")
        .toString();
    return sql;
}

// With conditionals (note the final parameters, required for the anonymous inner class to access them)
public String selectPersonLike(final String id, final String firstName, final String lastName) {
    return new SQL() {{
        SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
        FROM("PERSON P");
        if (id != null) {
            WHERE("P.ID like #{id}");
        }
        if (firstName != null) {
            WHERE("P.FIRST_NAME like #{firstName}");
        }
        if (lastName != null) {
            WHERE("P.LAST_NAME like #{lastName}");
        }
        ORDER_BY("P.LAST_NAME");
    }}.toString();
}

public String deletePersonSql() {
    return new SQL() {{
        DELETE_FROM("PERSON");
        WHERE("ID = #{id}");
    }}.toString();
}

public String insertPersonSql() {
    return new SQL() {{
        INSERT_INTO("PERSON");
        VALUES("ID, FIRST_NAME", "#{id}, #{firstName}");
        VALUES("LAST_NAME", "#{lastName}");
    }}.toString();
}

public String updatePersonSql() {
    return new SQL() {{
        UPDATE("PERSON");
        SET("FIRST_NAME = #{firstName}");
        WHERE("ID = #{id}");
    }}.toString();
}
方法描述
SELECT(String) SELECT(String...)开始或插入到 SELECT子句。 可以被多次调用,参数也会添加到 SELECT子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意类型。
SELECT_DISTINCT(String) SELECT_DISTINCT(String...)开始或插入到 SELECT子句, 也可以插入 DISTINCT关键字到生成的查询语句中。 可以被多次调用,参数也会添加到 SELECT子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意类型。
FROM(String) FROM(String...)开始或插入到 FROM子句。 可以被多次调用,参数也会添加到 FROM子句。 参数通常是表名或别名,也可以是数据库驱动程序接受的任意类型。
JOIN(String) JOIN(String...) INNER_JOIN(String) INNER_JOIN(String...) LEFT_OUTER_JOIN(String) LEFT_OUTER_JOIN(String...) RIGHT_OUTER_JOIN(String) RIGHT_OUTER_JOIN(String...)基于调用的方法,添加新的合适类型的 JOIN子句。 参数可以包含由列命和join on条件组合成标准的join。
WHERE(String) WHERE(String...)插入新的 WHERE子句条件, 由AND链接。可以多次被调用,每次都由AND来链接新条件。使用 OR() 来分隔OR
OR()使用OR来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用或生成不稳定的SQL
AND()使用AND来分隔当前的 WHERE子句条件。 可以被多次调用,但在一行中多次调用或生成不稳定的SQL。因为 WHEREHAVING 二者都会自动链接 AND, 这是非常罕见的方法,只是为了完整性才被使用。
GROUP_BY(String) GROUP_BY(String...)插入新的 GROUP BY子句元素,由逗号连接。 可以被多次调用,每次都由逗号连接新的条件。
HAVING(String) HAVING(String...)插入新的 HAVING子句条件。 由AND连接。可以被多次调用,每次都由AND来连接新的条件。使用 OR() 来分隔OR.
ORDER_BY(String) ORDER_BY(String...)插入新的 ORDER BY子句元素, 由逗号连接。可以多次被调用,每次由逗号连接新的条件。
LIMIT(String) LIMIT(int)增加一个LIMIT子句。此方法与SELECT(),UPDATE()和DELETE()一起使用时有效,并且此方法设计为在使用SELECT()时与OFFSET()一起使用。(自3.5.2起可用)
OFFSET(String) OFFSET(long)增加一个 OFFSET 子句。此方法与SELECT()一同使用时生效,并且该方法设计为与LIMIT()一起使用。(自3.5.2起可用)
OFFSET_ROWS(String) OFFSET_ROWS(long)增加一个OFFSET n ROWS子句。此方法与SELECT()一起使用时有效,并且该方法设计为与FETCH_FIRST_ROWS_ONLY()一起使用。(自3.5.2起可用)
FETCH_FIRST_ROWS_ONLY(String) FETCH_FIRST_ROWS_ONLY(int)增加一个FETCH FIRST n ROWS ONLY子句。此方法与SELECT()一起使用时有效,并且该方法设计为与OFFSET_ROWS()一起使用。(自3.5.2起可用)
DELETE_FROM(String)开始一个delete语句并指定需要从哪个表删除的表名。通常它后面都会跟着WHERE语句!
INSERT_INTO(String)开始一个insert语句并指定需要插入数据的表名。后面都会跟着一个或者多个VALUES() or INTO_COLUMNS() and INTO_VALUES()。
SET(String) SET(String...)针对update语句,插入到"set"列表中
UPDATE(String)开始一个update语句并指定需要更新的表明。后面都会跟着一个或者多个SET(),通常也会有一个WHERE()。
VALUES(String, String)插入到insert语句中。第一个参数是要插入的列名,第二个参数则是该列的值。
INTO_COLUMNS(String...)增加列短语到insert语句。应该与INTO_VALUES()一起调用
INTO_VALUES(String...)增加值短语到insert语句。应该与INTO_COLUMNS()一起调用。
ADD_ROW()为批量插入添加新行。(自3.5.2起可用)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值