MyBatis 理论篇
[TOC]
什么是MyBatis
MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBitis消除了几乎所有的JDBC代码和参数的手工设置及对结果的检索.MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Object ,普通的Java对象)映射成数据库中的记录.
入门
每个MyBatis的应用程序都以一个SQLSessionFactory对象的实例为核心.SQLSessionFactory对象的实例可以通过SqlSessionFactory对象来获得.SqlSessionFactory对象可以通过XML培训文件,或从以往使用惯例中准备好的Configuration类实例中来构建SQLSessionFactory对象.
从XML中构建SQLSessionFactory
从XML文件中构建SQLSessionFactory的实例非常简单.这里建议你使用类路径下的资源文件来配置,但是你可以使用任意的Reader实例,这个实例包括由文字形式的文件路径或URL形式的文件路径file://来创建.MyBatis包含了一个工具类,称作为资源,这些工具类包含一些方法,这些方法使得从类路径或其他位置加载资源文件变得更简单.
String resource = "org/mybatis/example/Configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlMapper = new SqlSessionFactoryBuilder().build(reader);
XML配置文件包含对MyBatis系统的核心配置,包含获取数据库连接实例的数据源和决定事务范围和控制事务管理器,关于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>
<environments default="development">
<!-- environment 元素体中包含对事务管理和连接池的环境配置 -->
<environment id="development">
<transactionManager type="JDBC" />
<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>
<!-- mappers 元素是包含所有mapper(映射器)的列表,这些mapper的XML文件包含SQL代码和映射定义信息 -->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml" />
</mappers>
</configuration>
当然,在XML配置文件中还有很多可以配置的,上面的示例支出的则是最关键的部分.要注意XML头部的声明,需要用来验证XML文档正确性.environment元素体中包含对事务管理和连接池的环境配置,mappers元素是包含所有mapper(映射器)的列表,这些mapper的XML文件包含SQL代码和映射定义信息
不使用XML构建SQLSessionFactory
如果你喜欢从java程序而不是从XML文件中直接创建配置实例,或创建你自己的配置构建器,MyBatis也提供完整的配置类,提供所有从XML文件中加载配置信息的选项.
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development",
transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(configuration);
注意,这种清空下配置是天价映射器类.映射器类是java类,这些类包含SQL映射语句的注解从而避免了XML文件的依赖,XML映射仍然是在大多数高级映射(比如:嵌套Join映射)时需要.处于这样的原因,如果存在XML配置文件的话,MyBatis将会自动查找和加载一个对等的XML文件(这种情况下,基于类路径下的BlogMapper.class类的类名,那么BlogMapper.xml 将会被加载).
从SqlSessionFactory中获取SqlSession
现在我们已经知道如何获取SQLSessionFactory对象了,基于同样的启示,我们就可以获得SqlSession的实例了.SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,你可以用SqlSession.实例来直接执行已映射的SQL语句,例如:
SqlSession session = sqlMapper.openSession();
try {
Blog blog = (Blog) session.selectOne(
"org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
这种方法起到的作用,和我们使用之前的MyBatis版本是相似的,现在有一种更简洁的方法.使用合理描述参数和SQL语句返回值的接口(比如BlogMapper.class),这样现在就可以执行更简单,更安全的代码.没有容易发生的字符串文字和转换的错误. 例如:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
探究已映射的SQL语句
这里你也许可以知道通过SqlSession和Mapper对象到底执行了什么操作.已映射的SQL语句是一个很大的主题,而且这个主题会贯穿本文档中的大部分内容,为了宏观的概念,这里有一些示例.
上面提到的任何一个示例,语句是通过XML或注解定义的.我们先来看看XML.使用基于XML的映射语言,在过去的几年中使得MyBatis非常流行,它为MyBatis提供所有的特性设置.如果你以前用过MyBatis,这个概念你就应该很熟悉了,但是XML映射文件也有很多的改进,后面我们详细再说.这里给出一个基于XML映射语句的示例,这些语句应该可以满足上述示例中的SqlSession对象的调用.
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" parameterType="int" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
这个简单的例子看起来很多额外的东西,但是也相当简洁了.如果你喜好,你可以在一个单独的XML映射文件中定义很多的映射语句,除XML头部和文档声明之外,你可以得到很多方便之处.在文件中剩余部分是很好的自我解释.在命名空间"com.mybatis.example.BlogMapper"中,它定义了一个名为"selsect Blog"映射的语句,这样它允许你使用完全限定名“org.mybatis.example.BlogMapper.selectBlog”来调用映射语句,我们下面的实例中所有的写法也是这样的.
Blog blog = (Blog) session.selectOne(
"org.mybatis.example.BlogMapper.selectBlog", 101);
要注意这个使用完全是限定名调用Java对象的方法是相似的,这样做是由原因的.这个命名可以直接映射相同命名空间下的映射器类,使用一个名称,参数和返回值已映射的查询语句都一样的方法既可以.这就允许你非常容易地调用映射器接口中的方法,这和你前面看到的是i一样的,下面这个示例中它有出现了.
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
第二种方式有很多的优点,首先它不是基于文字的,那就更安全了.第二,如果你的IDE有代码补全功能,那么你可以利用它来操作已映射的SQL语句.第三,不需要强制类型转换,同时BlogMapper接口可以保持简洁,返回值类型很安全(参数类型也很安全.)
命名空间的一点注释
命名空间在之前版本MyBatis中是可选项,非常混乱在使用上没有帮助,现在命名空间是必须的,而且有要给目的,它是用更长的完全限定名来隔离语句.
命名空间使得接口绑定称为肯,就像你看到的那样,如果之前不了解,那么现在你就会使用他们了,你应该按照下面给出的示例来练习,以免改变自己的想法.使用命名空间,并将它放在适合的Java包空间之下,这样使你的代码变得简洁,在更长的时间内提供MyBatis的作用.
命名解析:为了减少输入量,MyBatis对所有的命名配置元素使用如下的命名解析规则,包括语句,结果映射,缓存等.
- 直接查询完全限定名(比如:“com.mypackage.MyMapper.selectAllThings”),如果发现就使用.
- 短名称(比如:selectAllThings”)可以用来引用在任意含糊的对象.而如果有两个或两个以上的(比如:“com.foo.selectAllThings”和“com.bar.selectAllThings”),那么就会得到错误报告,说命名称是含糊的,因此就必须使用完全限定名.
对BlogMapper这样的映射器来说,还有一个妙招.它们中间映射的语句可以不需要再XML中来写,而可以使用java注解来替换,比如,上面的XML实例可以如下来替换:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
对于简单语句来说,使用注解代码会更加清晰,然而java注解对于复杂语句来说就会混乱,应该限制使用.因此,如果你布局不做复杂的事情,那么最好使用XML来映射语句.
当然这也屈居于你的项目团队的决定,看哪种更适合你来使用,还有以长久以来使用映射语句的重要性.也就是说,不要将自己局限在一种方式,你可以轻松地将注解转换成XML映射语句,反之亦然.
范围和生命周期
理解我们目前已经讨论过的不同范围和声明周期类是很重要的,不正确的使用他们会导致严重的并发问题.
SqlSessionFactoryBuilder
这个类可以被实例化,但是已丢弃.一旦你创建了SqlSessionFactory后,这个类就不需要存在了.因此SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是本地方法变量).你可以重用SqlSessionFactoryBuilder来创建多个SqlSessionFactory实例,但是最好的方式就是不需要保持它一直存在来保证所有XML解析资源,因为还有更重要的事情要做.
SqlSessionFactory
一旦被创建,SqlSessionFactory实例应该在你的应用程序执行期间都存在.没有理由来处理或重新创建它.使用SQLSessionFactory的最佳实践是在应用程序运行期间不要重复创建多次.这样的操作将被视为非常糟糕的.因此SqlSessionFactory的最佳范围是应用范围.有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式.然而这两种方法都不认为是最佳实践.这样的话,你可以考虑依赖注入容器,比如Google Guice 或 Spring .这样的框允许你创建支持程序来管理单例SqlSessionFactory的生命周期.
SqlSession
每个线程都应该有它自己的SqlSession实例.SqlSession的实例不能共享使用,它也是线程不安全的.因此最佳的范围是请求或方法范围.绝对不能将SqlSession实例的引用放在一个类的静态字段甚至是实例字段中.也绝不能将SqlSession实例的引用放在任何类型的挂了你范围中,比如Servlet框架中HttpSession.如果你现在正在用任意的web框架,要考虑SqlSession放在一个Http请求对象相似的范围内,就可以关闭它,关闭Session很重要,你应该确保finally块来关闭它.下面的示例就是一个确保SqlSession关闭的基本模式.
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
在你的代码中一贯的使用这种模式,将会保证所有数据库资源都正确的关闭(假设没有通过你自己的连接关闭,这会给MyBatis造成一种机箱表明你自己管理连接资源).
映射器实例
&ems;映射器是你创建绑定语句的接口.映射器接口的实例可以从SqlSession中获得,那么从技术上来说,当被请求时,任意映射器实例的最宽范围和SqlSession是相同的.然而,映射器实例的最佳范围是方法范围.也就是说,他们应该在使用他们的方法中被请求,然后就抛弃掉.他们不需要明确地关闭.那么在请求对象中保留它们也就不是什么问题了,这和SqlSession相似,你也许会发现,在这个水平上管理太多的资源的话会失控.保持简单,将映射器放在方法范围内,下面的示例展示了这个实例:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// do work
} finally {
session.close();
}
XML映射配置文件
MyBatis的XML配置文件包含了影响MyBatis行为甚深的设置和属性信息.XML文档的高层级结构如下.
- configuration 配置
- properties :属性
- setting 设置
- typeAliases 类型命名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- 映射器
- environment 环境变量
properties
这些都是外部化的,可替代的属性,这些属性也可以配置在典型的Java属性文件中,或者通过properties元素的子元素来传递.例如:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="myuser" />
<property name="password" value="123456" />
</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属性将会从包含进来的cofig.properties文件中的值替换.这里提供很多配置选项.
属性也可以被传递到SqlSessionBuilder.builder()方法中.例如:
SqlSessionFactory factory =
sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory =
sqlSessionFactoryBuilder.build(reader, environment, props);
如果在这些地方,属性多于一个的话,MyBatis安装如下属性加载他们:
- 在properties元素内指定的属性首先被读取
- 从类路径下资源或properties元素的url属性中加载的属性第二被读取,她会覆盖已存在的完全一样的属性
- 作为方法参数传递的属性最后被读取,它也会覆盖任一已存在的完全一样的属性,这些属性可能是从properties元素内和资源/url属性中加载的.
因此,最高优先级属性是那些作为方法参数的,然后是资源/url属性,最后是properties元素中指定的属性.
settings
这些是极其重要的调整,它们会修改MyBatis在运行时的行为方式.见如下表
参数设置 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 这个配置使用全局的映射器启用或禁用缓存. | true | false | true |
lazyLoadingEnabled | 全局启用或禁用延迟加载.当禁用时,所有关联对象都会即时加载. | true | false | true |
aggressiveLazyLoading | 当启用时,有延迟加载属性的对象在被调用时将会完成加载任意属性.否则,每种属性将会按需要加载. | true | false | true |
multpleResultSetsEnabled | 允许或不允许多种结果从一个单独的语句中返回(需要适合的驱动) | true | false | true |
useColumnLable | 使用列标签代替列名.不同的却动在这方面表现不同.参考驱动文档或充分测试两种方法来决定所使用的驱动 | true | false | true |
useGenerateKeys | 允许JDBC支持生成的键.需要适合的驱动.如果设置为true则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍有效. | true | false | false |
autoMappingBehavoir | 指定MyBatis如何自动映射列到字段/属性.PARTIAL只会自动映射简单,没有嵌套的结果.FULL会自动映射任意复杂的结果(嵌套的或其他情况) | NONE , PARTIAL , FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器,SIMPLE执行器没有什么特别支出.REUSE执行器重用预处理语句.BATCH执行器重用语句和批量更新. | SIMPLE , REUSE , BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待一个数据库的响应时间 | 任何正整数 | Not Set (null) |
一个设置信息元素的实例,如下:
<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="enhancementEnabled" value="false" />
<setting name="defaultExecutorType" value="SIMPLE" />
<setting name="defaultStatementTimeout" value="25000" />
</settings>
typeAliases
类型别名为Java类型命名一个短的名字.它只和XML配置有关,用来减少类完全限定的多余部分.例如:
<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>
这个配置,"Blog"可以任意来代替"domain.blog.Blog"所使用的地方.
对于普通的Java类型,有许多内建的类型别名.它们都是大小写不敏感的,由于重载的名字,要注意原生类型的特殊处理.
别名 | 映射类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
typeHandlers
无论是MyBatis在预处理语句中设置一个参数,还是从结果中取出一个值时,类型处理器被用来将获取的值以合适的方式转换成Java类型,如下表格所示
类型处理器 | Java类型 | JDBC类型 |
---|---|---|
BooleanTypeHandler | Boolean ,boolean | 任何兼容的布尔值 |
ByteTypeHandler | Byte , byte | 任何兼容的数字或短整型 |
ShortTypeHandler | Short , short | 任何兼容的数字或短整型 |
IntegerTypeHandler | Integer , int | 任何兼容的数字和整型 |
LongTypeHandler | Long,long | 任何兼容的数字或长整型 |
FloatTypeHandler | Float,float | 任何兼容的数字或单精度浮点型 |
DoubleTypeHandler | Double,double | 任何兼容的数字或双精度浮点型 |
BigDecimalTypeHandler | BigDecimal | 任何兼容的数字或十进制小数类型 |
StringTypeHandler | String | CHAR 和 VARCHAR 类型 |
ClobTypeHandler | String | CLOB和 LONGVARCHAR 类型 |
NStringTypeHandler | String | NVARCHAR 和 NCHAR 类型 |
NClobTypeHandler | String | NCLOB类型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB和 LONGVARBINARY 类型 |
DateTypeHandler | Date(java.util) | TIMESTAMP 类型 |
DateOnlyTypeHandler | Date(java.util) | DATE 类型 |
TimeOnlyTypeHandler | Date(java.util) | TIME 类型 |
SqlTimestampTypeHandler | Timestamp(java.sql) | TIMESTAMP 类型 |
SqlDateTypeHandler | Date(java.sql) | DATE 类型 |
SqlTimeTypeHandler | Time(java.sql) | TIME 类型 |
ObjectTypeHandler | 任意 | 其他或未指定类型 |
EnumTypeHandler | Enumeration 类型 | VARCHAR-任何兼容的字符串类型, |
你可以重写类型处理器或创建你自己的累成处理器来处理,不支持的或非标准的类型.要这样做的话,简单实现TypeHandler接口(org.mybatis.type),然后映射新的类型处理器类到Java类型,还有可选的一个JDBC类型.例如:
// ExampleTypeHandler.java
public class ExampleTypeHandler implements TypeHandler {
public void setParameter(PreparedStatement ps, int i, Object parameter,JdbcType jdbcType) throws SQLException {
ps.setString(i, (String) parameter);
}
public Object getResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
public Object getResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
// MapperConfig.xml
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR"
handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
使用这样的类型处理器将会覆盖已经存在的处理Java的String类型属性和VARCHAR参数及结果的类型处理器.要注意MyBatis不会审视数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指定那是VARCHAR类型的字段,来绑定到正确的类型处理器上.这是因为MyBatis直到语句被执行都不知道数据类型的这个实现导致的.
objectFactory
MyBatis每次创建结果对象新的实例时,它使用一个ObjectFactory实例来完成.如果参数映射存在,默认的ObjectFactory不比使用默认构造方法或带参数的构造方法实例化模板类的工作多.如果你项重写默认的ObjectFactory,你可以创建你自己的.比如.
// ExampleObjectFactory.java
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);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
}
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100" />
</objectFactory>
ObjectFactory接口非常简单.它包含两个用于创建的方法,一个默认构造方法,另外一个是处理带参数的构造方法.最终.setProperties方法可以被用来配置ObjectFactory.在初始化你懂ObjectFactory实例后,objectFactory元素体中定义的属性会被传递给setProperties方法.
plugins
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的核心.这是低层的类和方法.
实现插件的力量也比较简单,例如:
// ExamplePlugin.java
@Intercepts({@Signature(type= Executor.class,method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable
{
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
// MapperConfig.xml
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
上面的插件将会拦截在Excutor实例中所有的"update"方法调用,它也是负责低层映射语句执行的内部对象
覆盖配置类
除了用插件来修改MyBatis核心行为之外,你也可以完全覆盖配置类.简单扩展它,然后覆盖其中任意方法,之后传递它到sqlSessionBuilder.builder(myConfig)方法的调用.这可能会严重影响MyBatis的行为,所以要小心.
environments
MyBatis可以撇嘴多种环境.这会帮助你将SQL映射应用与多种数据库之中.例如,你也许为开发要设置不同的配置,测试和生产环境.或者你可能有多种生产级数据库却相同共享模式,所以你会想对不同数据库使用相同的SQL映射,这种用例是很多的.
要记得一个很重要的问题:你可以配置多种环境,但你只能为每个SqlSessionFactory实例选择一个.
所以,如果你想连接两个数据库,你需要创建两个SqlSessionFactory实例,每个数据库对应一个.而如果是三个数据库你就需要三个实例,以此类推.记忆起来很简单:
每个数据库对应一个SqlSessionFactory
-
为了明确创建哪种环境,你可以将它作为可选的参数传递给SqlSessionFactoryBuilder.可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);
-
如果环境被忽略,那么默认环境将会被加载,如下进行:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);
环境元素定义了如何配置环境.
<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>
注意这里的关键部分:
- 默认的环境ID(比如:default="development")
- 每个environment元素定义的环境ID(比如:id="development").
- 事务管理器的配置(比如:type="JDBC").
- 数据以的配置(比如:type="POOLED").
默认的环境和环境ID是自我解释的.你可以使用你喜好的名称来命名,只要确定默认的要匹配其中之一.
transactionManager
在MyBatis中有两种事务管理器类型(也是是type="[JDBC | MANAGED]");
JDBC -- 这个配置直接简单使用了JDBC的提交和回滚设置.它依赖于从数据源得到的连接来管理事务范围.
-
MANAGED -- 这个配置几乎没做什么.它从来不提交或回滚一个连接.而它会让容器来管理事务的整个声明周期(比如Spring 或JEEP应用服务器的上下文).默认情况下它会关闭连接.然而一些容器并不希望这样,因此如果你需要从连接中停止它,将closeConnection属性设置为false.例如:
<transactionManager type="MANAGED"> <property name="closeConnection" value="false" /> </transactionManager>
这两种事务管理器都不需要任何属性.然而它们都是类型别名,要替换使用它们,你需要放置将你自己的类的完全限定名或类型别名,它们引用了你对TransacFactory接口的实现类.
public interface TransactionFactory { void setProperties(Properties props); Transaction newTransaction(Connection conn, boolean autoCommit); }
任何在XML中配置的属性在实例化后将会被传递给setProperties()方法,你的事先类需要创建一个事务接口的实现,这个接口也很简单:
public interface Transaction { Connection getConnection(); void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
使用这两个接口,你可以完全自定义MyBatis对事务的管理.
dataSource
dataSource元素使用基本的JDBC数据源接口来配置JDBC连接对象的资源.
许多MyBitis的应用程序将会按示例中的例子来配置数据源.然而它并不是必须的.
要知道为了方便,使用延迟加载,数据源才是必须的.
有三种内建的数据源类型(也就是type="???");
UNPOOLED - 这个数据源的实现是每次被请求时简单打开和关闭的连接.它有一点慢,这是对简单应用程序的一个很好的选择,因为它不需要即使的可用连接.不同的数据库对这个表现也是不一样的,所以对某些数据来说配置数据源并不重要,这个配置也是闲置的.
UNPOOLED类型的数据源仅仅用来配置一下5中属性:
driver -- 这是JDBC驱动的Java类的完全限定名(如果你的驱动包含的有,它也不是数据源类)
url -- 这是户口的JDBC URL 地址
username -- 登陆数据库的用户名
password -- 登陆数据库的密码
-
defaultTransactionIsoIationLevel -- 默认的连接事务隔离级别
作为可选项,你可以传递数据库驱动的属性.要这样做,属性的前缀是"driver",开头的.例如:
-
driver.encoding=UTF8
这样就会传递以"UTF8"来传递,"encoding"属性,它是通过DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动.
-
POOLED -- 这是JDBC连接对象的数据源连接池的实现,用来米面创建新的连接实例时必要的初始连接和认证事件.这是一种当前Web应用程序来快速响应请求很流行的方法.
除了上述(UNPOOLED)属性之外,还有很多属性可以用来配置POOLED数据源
poolMaximumActiveConnections -- 在任意时间存在的空闲连接数.
poolMaximumIdeConnections -- 任意时间存在的空闲连接数
poolMaximumCheckoutTime -- 在被强制返回之前,池中连接被检查的时间,默认值2000毫秒(20秒)
poolPingQuery -- 发送到数的侦测查询,用来验证连接是否正常工作,并且准备接收请求.默认是NO PING QUERY SET,这会引起许多数据库驱动连接由一个错误信息而导致失败
poolPingEnabled -- 这是开启或禁用侦测查询.如果开启,你必须用一个合法的SQL语句(最好是很快速的),设置poolPingQuery属性.默认值:false.
poolPingConnectionsNotUsedFor -- 这是用来配置poolPingQuery 多次时间被用一次.这可以被设置匹配标准的数据连接超时时间,来避免不必要的侦测.默认值:0(也就是所有连接每一时刻都被侦测-- 但仅仅当poolPingEnabled 为 true 时适用).
-
JNDI -- 这个数据源的实现是为了使用如Spring或应用服务器这个类的容器,容器可以集中或外部配置数据源,然后放置一个JNDI上下文的引用.这个数据源配置只需要两个属性
- initial_context -- 这个属性用来初始化上下文中寻找环境(也就是initialContext.lookup(initial-context)).这是个可选属性,如果被忽略,那么data_source属性将会直接一initialContext为背景在此寻找.
- data_source -- 这是引用数据源实例位置上的上下文路径.它会以由initial_context查询返回的环境为背景来查找,如果initial_context没有返回结果时,直接以初始上下文为环境来查找.
和其他数据源配置相似,它也可以通过"env."的前缀直接项初始化上下文发送属性.
-
env.encoding=UTF8
在初始化之后,这就会以值"UTF8"向初始化上下文的构造方法传递名为"encoding"的属性
mappers
既然MyBatis的行为已经由上述元素配置完了,我们现在就要定义SQL映射语句了.但是,首先我们需要告诉MyBatis到哪里去兆这个语句.Java在这方面没有提供一个很好的方法,所以最佳的方式是告诉MyBatis到哪里去兆映射文件.你可以使用相对与类路径的资源引用,或者字符表示,或URL引用的完全限定名(包括:file:///urls) , 例如:
// 使用相对于类路径的资源
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml" />
<mapper resource="org/mybatis/builder/BlogMapper.xml" />
<mapper resource="org/mybatis/builder/PostMapper.xml" />
</mappers>
// 使用完全限定路径
<mappers>
<mapper url="file:///var/sqlmaps/AuthorMapper.xml" />
<mapper url="file:///var/sqlmaps/BlogMapper.xml" />
<mapper url="file:///var/sqlmaps/PostMapper.xml" />
</mappers>
这些语句简单告诉了MyBitis去哪里找映射文件.其余的细节就是在每个SQL映射文件中了,下面的部分我们来讨论SQL映射文件.
SQL映射文件的XML文件
&ems;MyBatis真正的力量是在映射语句中.这里是奇迹发生的地方.对于所有的力量,SQL映射的XML文件是相当简单.当然如果你将它们和对等功能的JDBC代码来比较,你会发现映射文件节省了大约95%的代码量.MyBatis的构建是聚焦与SQL的,使其远离于普通的方式.
SQL映射文件有很少的几个顶级元素(按照它们应该被定义的书序):
名称 | 释义 |
---|---|
cache | 配置给定命名空间的缓存 |
cache-ref | 从其他命名空间引用缓存配置 |
resultMap | 最复杂,也是最有力量的元素,用来描述如何从数据库结果集中来加载你的对象. |
sql | 可以重用的SQL块,也可以被其他语句引用. |
insert | 映射插入语句 |
update | 映射更新语句 |
delete | 映射删除语句 |
select | 映射查询语句 |
下一部分将从语句本身开始来描述每个元素的细节.
select
查询语句是使用MyBatis时最常用的元素之一.直到你从数据库去除数据时才会发现将数据存在数据库中是多么有价值,所以许多应用程序的查询操作要比更改数据操作多得多.对于每次插入,更新或删除,那也会有很多的查询.这是MyBatis的一个基本原则,也是讲中心和努力放到查询和结果映射的原因,对于简单类别的查询元素是非常简单的.比如:
<select id=”selectPerson” parameterType=”int” resultType=”hashmap”>
SELECT * FROM PERSON WHERE ID = #{id}
</select>
这个语句被称作selectPerson,使用一个int(或Integer)类型的参数,并返回一个HashMap类型的对象,其中的键是列名,值是对应的值.
注意参数注释:
#{id}
这就告诉MyBatis创建一个PreparedStatement(预处理语句)参数.使用JDBC,这样的一个参数在SQL中会由一个"?"来标识,并被传递到一个新的预处理语句中,就像这样:
// 相似的JDBC代码,不是MyBatis的
String selectPerson = “SELECT * FROM PERSON WHERE ID=?”;
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
当然,这需要很多单独的JDBC的代码来提取结果并将它们映射到对象实例中.这就是MyBatis节省你时间的地方.我们需要深入了解参数和结果映射.哪些细节部分我们西门来俩句.
select元素有很多属性允许你配置,来决定每条语句的作用细节.
<select
id=”selectPerson”
parameterType=”int”
parameterMap=”deprecated”
resultType=”hashmap”
resultMap=”personResultMap”
flushCache=”false”
useCache=”true”
timeout=”10000”
fetchSize=”256”
statementType=”PREPARED”
resultSetType=”FORWARD_ONLY”
>
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句. |
parameterType | 将会传入这条语句的参数类的完全限定名或别名 |
resultType | 从这条语句中返回的期望类型的完全限定名或别名.注意集和情形.那应该是集合可以包含的类型,而不能是集合本身.使用resultType或resultMap,但不能同时使用. |
resultMap | 命名引用外部的resultMap.返回map是MyBatis最具力量的特性,对其一个很好的理解的话,许多复杂映射的情形就能被解决了.使用resultMap或resultType,但不能同时使用. |
flushCache | 将其设置为true , 无论语句什么时候被调用,都会导致缓存被清空.默认值为false. |
userCache | 将其设置为true , 将会导致本条语句的结果被缓存,默认值为true. |
timeout | 这个设置驱动程序等等数据库返回请求结果,并抛出异常时间的最大等待值.默认不设置(驱动自行处理) |
fetchSize | 这时暗示驱动程序每次批量返回的结果行数.默认不设置(驱动自行处理) |
statementType | STATEMENT,PREPARED或CALLABLE的一种.这会让MyBatis使用选择使用Statement,PreparedStatement或CallableStatement.. 默认值:PREPARED. |
resultSetType | FORWARD_ONLY SCROLL_SENSITIVE SCROLL_INSENSITIVE中的一种.默认是不设置(驱动自行处理) |
**input , update , delete **
数据修改语句insert update delete 在他们的实现中非常相似.
<insert id="insertAuthor" parameterType="domain.blog.Author"
flushCache="true" statementType="PREPARED" keyProperty=""
useGeneratedKeys="" timeout="20000" />
<update id="insertAuthor" parameterType="domain.blog.Author"
flushCache="true" statementType="PREPARED" timeout="20000" />
<delete id="insertAuthor" parameterType="domain.blog.Author"
flushCache="true" statementType="PREPARED" timeout="20000" />
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句 |
parameterType | 将会传入这条语句的参数类的完全限定或别名 |
flushCache | 将其设置为true,不论语句什么时候被调用,都会导致缓存被清空,默认值为:false. |
timeout | 这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的最大等待值.默认不设置(驱动自行处理.) |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一种。这会让 MyBatis使用选择使用 Statement,PreparedStatement 或 CallableStatement。默认值:PREPARED。 |
useGeneratedKeys | (进队insert有用)这会告诉MyBatis使用JDBC的getGeneratedKeys方法来去除由数据(比如:像MySQL和SQLServer这样的数据库管理系统的自动递增字段)内部生成的主键.默认值:false. |
keyProperty | (仅对insert有用)标记一个属性,MyBatis会通过getGeneratedKeys或者通过insert语句的selectKey子元素设置它的值.默认:不设置 |
下面就是 insert ,update 和delete 语句的示例:
<insert id="insertAuthor" parameterType="domain.blog.Author">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor" parameterType="domain.blog.Author">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor” parameterType=" int ">
delete from Author where id = #{id}
</delete>
如前所属,插入语句有一点多,它有一些属性和子元素用来处理主键的生成.
首先,如果你的数据库支持自动生成主键的字段(比如MySQL和SQLServer数据库),那么你可以设置userGeneratedKeys="true",而且设置keyProperty到你已经做好的模板属性上.例如,如果上面的Author表已经对id使用了自动生成的列类型,那么语句可以修改为:
<insert id="insertAuthor" parameterType="domain.blog.Author"
useGeneratedKeys=”true” keyProperty=”id”>
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
MyBatis有灵娲一种方法来处理数据库不支持自动生成类型,或者肯JDBC驱动不支持自动生成主键的主键生成问题.
这里有一个简单(甚至很傻)的示例,它可以生成一个随机ID(可能你不会这么做,但是这样展示了MyBatis处理问题的灵活性,因为它并不真的关心ID的生成);
<insert id="insertAuthor" parameterType="domain.blog.Author">
<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元素将会首先运行,Author的id会被设置,然后插入语句会被调用.这给你一个简单的行为在你的数据库中来处理自动生成的主键,而不需要使你的Java代码变得复杂.
selectKey 元素描述如下:
<selectKey keyProperty="id" resultType="int" order="BEFORE"
statementType="PREPARED">
属性 | 描述 |
---|---|
keyProperty | selectKey语句结果应该被设置的目标属性 |
resultType | 结果的类型,MyBatis通常可以算出来,但是写上也没有问题.MyBatis允许任何简单类型用作主键的类型,包括字符串. |
order | 这可以被设置为BEFORE或AFTER.如果设置为BEFORE,那么它首先选择主键,设置keyProperty然后执行插入语句.如果设置为AFTER,那么先执行插入语句,然后是selectKey元素.这和Oracle数据库相似,可以在插入语句中嵌入序列调用. |
statementType | 和前面的相同,MyBatis支持STATEMENT,PREPARED和CALLABLE语句的映射类型,分别代表PreparedStatement和CallableStaement类型 |
sql
这个元素可以被用来定义可重用的SQL代码段,可以包含在其他语句中.比如:<sql id=”userColumns”> id,username,password </sql>
这个SQL片段可以被包含在其他语句中,例如
<select id=”selectUsers” parameterType=”int” resultType=”hashmap”>
select
<include refid=”userColumns” />
from some_table
where id = #{id}
</select>
Parameters
在之前的语句中,你已经看到了一些简单参数的示例,在MyBatis中参数是非常强大的元素.对于简单的做法,大概90%的情况,是不用太多的,比如:
<select id=”selectUsers” parameterType=”int” resultType=”User”>
select id, username, password
from users
where id = #{id}
</select>
上面这个示例说明了一个非常简单的命名参数映射.参数类型被设置为"int",因此这个参数可以被设置成任何内容.原生的类型或简单数据类型,比如整型和没有相关属性的字符串,因此它会完全用参数来替代.然而,如果你传递了一个复杂的对象,那么MyBatis的处理方式就会有一点不同.比如:
<insert id=”insertUser” parameterType=”User”>
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果User类型的参数对象传递到了语句中id,username,和password属性将会被查到,然后他们的值就被传递到预处理语句的参数中.
这点对于传递参数到语句中非常好,但是对于参数映射也有一些其他的特性.
首先,像MyBatis的其他部分,参数可以指定一个确定的数据类型.
#{property,javaType=int,jdbcType=NUMERIC}
像MyBatis的剩余部分,javaType通常可以从参数对象中来确定,除非对象是一个HashMap.那么javaType应该被确定来保证使用正确类型处理器.
注意:如果null被当作值来传递,对于所有可能为空的列,JDBCType是需要的.也可以通过阅读PreparedStatement.setNull();方法的JavaDocs文档来研究它.
为了以后自定义类型处理器,你可以指定一个确定的类型处理器类(或别名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
尽管它看起来繁琐,但是实际上是你很少设置他们其中之一.
对于数据类型,对于决定有多少数字是相关的,有一个数值范围.
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后mode属性允许你指定in,out或inout参数.如果参数为out或inout.参数对象属性的真实值将会被改变,就像你期望你需要输出一个参数.如果mode为OUT(或INOUT),而且jdbcType为CURSOR(也就是Oracle的REFCURSOR),你必须指定一个resultMap来映射结果集到参数类型.要注意这里的javaType属性是可选的,如果左边是空白是jdbcType的CURSOR类型,它会自动被设置为结果集.
#{department,
mode=OUT,
jdbcType=CURSOR,
javaType=ResultSet,
resultMap=departmentResultMap
MyBatis也支持很多高级的数据类型,比如机构体,但是当注册out参数时你必须告诉语句类型名称.比如(在此提示,在实际中不要像这样换行);
#{middleInitial,
mode=OUT,
jdbcType=STRUCT,
jdbcTypeName=MY_TYPE,
resultMap=departmentResultMap}
景观所有这些请打的选项很多时候你值简单指定属性名,MyBatis会自己计算剩余的.最多的清空是你为jdbcType指定可能为空的列名.
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
字符串替换
默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置安全的值(比如?).这样做很安全,很迅速,也是首选的做法,有时你只是项直接在SQL语句中插入一个不改变的字符串.比如,项ORDER BY , 你可以这样来使用:
ORDER BY ${columnName}
这里MyBatis不会修改或转义字符串
重要:接收用户输出的内容并提供给语句中不变的字符串,这样做是不安全的.这会导致潜在的SQL注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检查.
resultMap
resultMap元素是MyBatis中最重要最强大的元素.它就是让你远离90%的需要从结果集中去除数据的JDBC代码的哪个东西,而且在一些情形下允许你做一些JDBC不支持的事情.事实上,编写相似于对复杂语句联合映射这些等同的代码,也需可以跨过上千行的代码.ResultMap的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系.
你已经看到简单的映射语句的示例了,但没有明确的resultMap.比如:
<select id=”selectUsers” parameterType=”int” resultType=”hashmap”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
这样一个语句简单作用域所有列被自动映射到HashMap的键上,这由resultType属性指定.这在很多情况下是由用的.但是HashMap 不能很好描述一个领域模型,那样你的应用程序将会使用JavaBean或POJOs或POJOs来作为领域模型,MyBatis对两者都支持.看看下面的JavaBean:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
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 getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
基于JavaBean的规范,上面这个类有三个属性:id,username和hashedPassword.这些在select语句中会精确匹配到列名.
这样的一个JavaBean可以被映射到结果集,就像映射到HashMap一样简单.
<select id=”selectUsers” parameterType=”int” resultType=”com.someapp.model.User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
要记住类型别名是你懂 伙伴.使用他们你可以不用输入类的全路径.比如:
<!-- 在XML配置文件中 -->
<typeAlias type=”com.someapp.model.User” alias=”User” />
<!-- 在SQL映射的XML文件中 -->
<select id=”selectUsers” parameterType=”int” resultType=”User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
这些情况下,MyBatis会在幕后自动创建一个ResultMap,基于属性名来映射到JavaBean的属性上.如果列名没有精确匹配,你可以在列名上使用select子句别名(一个标准的SQL特性)来匹配标签.比如:
<select id=”selectUsers” parameterType=”int” resultType=”User”>
select
user_id as “id”,
user_name as “userName”,
hashed_password as “hashedPassword”
from some_table
where id = #{id}
</select>
ResultMap最优秀的地方你已经了解了很多了,但是你还没有真正的看到一个.这些简单示例不需要比你看到的更多东西.只是处于示例的原因,让我们来看看最后一个实例中外部的resultMap是什么样子的,这也是解决列名不匹配的另外一种方式.
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name" />
<result property="password" column="hashed_password" />
</resultMap>
引用它的语句使用resultMap属性就行了(注意我们去掉resultType属性)比如:
<select id=”selectUsers” parameterType=”int” resultMap=”userResultMap”>
select
user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
高级结果映射
MyBatis创建的一个想法:数据库不用永远是你想要的或需要他们是什么样的.而我们最喜欢的数据库最好是第三范式或BCNF范式,但是它们有时不是.如果可能有一个单独的数据库映射,所有应用程序都可以使用它,这非常好,但有时也不是.结果映射就是MyBatis提供处理这个问题的答案:
比如,我们如何映射下面的这个语句?
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" parameterType="int"
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" column="blog_author_id"
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" column="post_author_id"
javaType="Author" />
<collection property="comments" column="post_id" ofType=" Comment">
<id property="id" column="comment_id" />
</collection>
<collection property="tags" column="post_id" ofType=" Tag">
<id property="id" column="tag_id" />
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost" />
</discriminator>
</collection>
</resultMap>
resultMap元素有很多子元素和一个质的讨论的结构,下面是resultMap元素的概念图.
-
resultMap
- constructor:类在实例化时,用来注入结果到构造方法中.
- idArg :id参数,标记接轨哦作为ID可以帮助提高整体效能
- arg:注入到构造方法的一个普通结果
- id: 一个ID结果:标记结果作为ID可以帮助提高整体效能
- result: 注入到字段或JavaBean属性的普通结果
- association:一个复杂的类型关联:许多结果将包成这种类型
- 嵌入结果映射:结果映射自身的关联,或参考一个
- collection : 复杂类型的集合
- 嵌入结果映射: 结果映射自身的 集合,或者参考一个.
- discriminator:使用结果值来决定使用哪个结果映射
- case:基于某些值的结果映射
- 嵌入结果映射,这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射.
- case:基于某些值的结果映射
最佳实践:通常逐步建立结果映射.单元测试的真正帮助在这里,如果你尝试创建一次创建一个向上面示例示例那样巨大的结果映射,那可能会有错误而且很那去控制它来工作.开始简单一些,异步异步的发展.而且要进行单元测试!使用该框架的缺点是它们优势是黑盒.你确定你显示想要的行为的最好选择是编写单元测试,它也可以你帮助得到提交的错误.
id,result
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
这些是结果映射最基本内容.id和result都映射一个单独列的值到简单数据类型(字符串,整列,双精度浮点数,日期等)的单独属性或字段.
这两者之间的唯一不同是id表示的结果将是比当较对象实例时用到的标识符属性.这帮助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射).
每个映射属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性.如果匹配的是存在的,和给定名称相同的JavaBean的属性,那么就会使用.否则MyBatis将会寻找给定名称的字段.这两种情形你可以使用通常点式的复杂属性导航.比如,你可以这样映射一些东西:"username",或者映射到一些复杂的东西:"address.street.number." |
column | 从数据库中得到的列名,或者是列名的重命名标签.这也是通常会传递给resultSet.getString(columnName)方法参数中相同的字符串 |
javaType | 一个Java类的完全限定名,或一个类型别名(参数上面内建类型别名的列表).如果你映射到一个JavaBean , MyBatis 通常可以断定类型.然而,如果你映射到的是HashMap , 那么你应该明确地指定javaType来保证所需的行为. |
jdbcType | 在这个表格之后的所有支持的JDBC类型列表中的类型.JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理.这是JDBC的需要,而不是MyBatis的.如果你直接使用JDBC编程,你需要指定这个类型,但不仅仅对可能为空的值. |
typeHandler | 我们在前面讨论过默认的类型处理器.使用这个属性,你可以覆盖默认的类型处理器,这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类别名. |
支持的JDBC类型
为了参考,MyBatis通过包含的jdbcType的枚举类型,支持下面的JDBC类型.
BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
---|---|---|---|---|---|
TINYNT | REAL | VARCHER | BINARY | BLOB | NVARCHAR |
SMALLINT | DOUBLE | LONGVARCHAR | VARVINARY | CLOB | NCHAR |
INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
BIGINT | DECIMAL | TIME | NULL | CURSOR |
构造方法
<constructor>
<idArg column="id" javaType="int" />
<arg column=”username” javaType=”String” />
</constructor>
对于大多数数据传输对象(Data Transfer Object ,DTO )类型,属性可以起作用,而且像绝大多数的领域模型,指令也许是你想使用一成不变的类的地方.通常包含引用或查询数据的表很少或基本不变的话对一成不变的类来说是合适的.构造方法注入允许你再初始化时为类设置属性的值,而不用暴露共有方法.MyBatis也支持私有属性和私有JavaBean属性来达到这个目的的,但是一些人更青睐构造方法注入.Constructor(构造方法),元素支持这个.
例子:
public class User {
//…
public User(int id, String username) {
//…
}
//…
}
为了向这个构造方法中注入结果,MyBatis需要通过它的参数的类型来标识构造方法.java没有自查(或反射)参数的方法.所以当创建一个构造方法元素时,保证参数是按顺序排列的,而且数据类型也是确定的.
<constructor>
<idArg column="id" javaType="int" />
<arg column=”username” javaType=”String” />
</constructor>
剩余的属性和规则和固定的id和result元素是相同的
属性 | 描述 |
---|---|
column | 来自数据库的类名,或重命名的列标签.这和通常传递给resultSet.getString(columnName);方法的字符串是相同的. |
javaType | 一个Java类的完全限定名称,或一个类型别名(参见上面内建类型的别名的列表)如果你映射到一个JavaBean,MyBatis通常可以断定类型.然而,如果你映射到的是HashMap,那么你应该明确地指定javaType来保证所需的行为 |
jdbcType | 在这个表格之前的所支持的JDBC类型列表中的类型.JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理.这是JDBC的需要,而不是MyBatis的.如果你直接使用JDBC 编程,你需要指定这个类型,但仅仅对可能为空的值. |
typeHandler | 我们在掐灭讨论够默认的类型处理器.使用这个属性,你可以覆盖默认的类型处理器.这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名. |
关联
<association property="author" column="blog_author_id"
javaType=" Author">
<id property="id" column="author_id" />
<result property="username" column="author_username" />
</association>
关联元素处理"有一个"类型的关系.比如,我们在示例中,一个博客有一个用户.关联映射就工作于这种结果之上.你指定了目标属性,来获取值的列.属性的Java类型(很多情况下MyBatis可以自己算出来),如果需要的话还有JDBC类型,如果你项覆盖或获取的结果值还需要类型控制器.
关联中不同的是你需要告诉MyBatis如何加载关联.MyBatis在这方面会有两种不同的方式:
- 嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型.
- 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集.
首先,让我们来查看这个元素的属性.所有的你都会看到,它和普通的只由select和resultMap属性的结果映射不同.
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性.如果匹配的是存在的,和给定的名称相同的JavaBean的属性,那么就会使用.否则MyBatis将会寻找给定名称的字段.这两中情形你可以使用通常点式的复杂属性导航.比如,你可以这样映射一些东西:"username",或者映射到一些复杂的东西"address.street.number". |
column | 来自数据库的列名,或重命名的列标签.者和通常床底给resultSet.getString(columnName)方法的字符串是相同的. 注意:要处理符合主键,你可以指定多个列名通过 column="{prop1=coll,prop2=col2}"这种语法来传递给嵌套查询语句.这会引起prop1和prop2以参数对象形式来设置给目标嵌套查询语句. |
javaType | 一个java类的完全限定名,或一个类型别名(参加上面内建类型别名的列表),如果你映射到一个javaBean,MyBatis通常可以断定类型.然而,如果你映射到的是HashMap,那么你应该明确地指定javaType来保证所需的行为. |
jdbcType | 在这个表格之前的所支持的JDBC类型列表中的类型,JDBC类型是仅仅需要对插入,更新,和删除操作可能为空的列进行处理,这是JDBC的需要,而不是MyBatis的,如果你直接使用JDBC编程,你需要指定这个类型,但仅仅对可能为空的值. |
typeHandler | 我们在前面讨论过默认的类型处理器.使用这个属性,你可以覆盖默认的类型处理器,这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名. |
关联嵌套查询
属性 | 描述 |
---|---|
select | 另外一个映射语句的ID,可以加载这个属性映射需要的复杂类型.获取的在列属性中指定的列的值将被传递给目标select语句作为参数,表格后面有一个详细的示例.注意:要处理符合主键,你可以指定多个列名通过 column="{prop1=col1,prop2=col2}",这种语法来传递给嵌套查询语句.这会引起prop1和prop2参数对象形式来设置目标乔涛查询语句 |
示例:
<resultMap id=”blogResult” type=”Blog”>
<association property="author" column="blog_author_id"
javaType="Author" select=”selectAuthor” />
</resultMap>
<select id=”selectBlog” parameterType=”int” resultMap=”blogResult”>
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id=”selectAuthor” parameterType=”int” resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
我们有两个查询语句:一个加载博客,另外一个加载作者,而且博客的结果映射描述了"selectAuthor"语句应该被用来加载它的author属性.
其他所有的属性将会被自动加载,假设他们的列和属性名相匹配.
这种这种方式很简单,但是对于大型数据库集合和列表将不会表现很好.问题就是我们熟知的"N+1"查询问题.概括地将,N+1查询问题可以是这样引起的:
- 你执行了一个单独的SQL语句来获取结果列表(就是"+1")
- 对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是"N")
这个问题会导致陈白上千的SQL语句被执行.这通常是不期望的.
MyBatis能延迟加载这样的查询就是一个好处,因此你可以分散这些语句同时运行的消耗.然而,如果你加载一个列表,之后迅速迭代来访问嵌套的数据,你会调用所有的延迟加载,这样的行为可能是很糟糕的.
所以还有另外一种办法.
关联的嵌套结果
属性 | 描述 |
---|---|
resultMap | 这种结果映射的ID,可以映射关联的嵌套结果到一个适合的对象图中.这是一种替代方法来调用另外一个查询语句.这允许你联合多个表来合成到一个单独的结果集.这样的结果集可能包含重复,数据的重复组组员被分解,合理映射到一个嵌套的对象图.为了使它变得容易,MyBatis让你"链接"结果映射,来处理嵌套结果.例子会很容易来仿照,示例如下: |
在上面你已经看到了一个非常复杂的嵌套关联的示例.下面这个是一个非常简单是示例来说明他们如何工作.代替了执行一个分离的语句,我们联合博客表和作者表在一起,就像:
<select id="selectBlog" parameterType="int" 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=”blog_id” column="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>
在上面的示例中你可以看到博客的作者关联代表这"authorResult",结果映射来加载作者实例.
非常重要:在嵌套结果映射中id元素扮演了非常重要的角色.应该通常指定一个或多个属性.它们可以用来唯一标识结果.实际上就是如果你不使用它(id元素),但是会产生一个严重的性能问题,不过MyBatis仍然可以正常工作,选择的属性越少越好,它们可以唯一地标识结果.主键就是一个显而易见的选择(几遍是联合主键).
现在,上面的示例用了外部的结果映射元素来映射关联.这使得Author结果映射可以重用.然而,如果你不需要重用它的话,或者你仅仅引用你所有的结果映射合到一个单独描述的结果映射中.你可以嵌套结果映射.这里给出使用这种方式的相同示例:
<resultMap id="blogResult" type="Blog">
<id property=”blog_id” column="id" />
<result property="title" column="blog_title" />
<association property="author" column="blog_author_id"
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>
上面你已经看到了如何处理"有一个"类型关联,但是"有很多个"是怎样的?下面这个部分就是来讨论这个主题的.
集合
<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>
集合元素的作用几乎和关联是相同的.实际上,他们也很相似,文档的异同是多余的.所以我们更关注于他们的不同.
我们来继续上面的示例,一个博客只有一个作者,但是博客有很多文章.在博客类中,这个可以由下面这样的写法来表示:
private List<Post> posts;
要映射嵌套结果到集合List中,我们使用集合元素.就像关联元素一样,我们可以从连接中使用嵌套查询,或者嵌套结果.
集合嵌套查询
首先,让我们看看使用嵌套查询来为博客加载文章.
<resultMap id=”blogResult” type=”Blog”>
<collection property="posts" javaType=”ArrayList” column="blog_id"
ofType="Post" select=”selectPostsForBlog” />
</resultMap>
<select id=”selectBlog” parameterType=”int” resultMap=”blogResult”>
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id=”selectPostsForBlog” parameterType=”int” resultType="Author">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
这里你应该注意很多东西,但大部分代码和上面的关联是非常相似的.首先,你应该注意我们使用的是集合元素.然后要注意那个心底 ofType属性.这个属性用来区分JavaBean(或字段)属性类型和集合包含的类型来说是很重要的.所以你可以读出下面这个映射.
<collection property="posts" javaType=”ArrayList” column="blog_id"
ofType="Post" select=”selectPostsForBlog”/>
读作:"在Post类型的ArrayList中的post的集合."
javaType属性是不需要的,因为MyBatis在很多情况下会为你算出来.所以你可以缩短写法.
<collection property="posts" column="blog_id" ofType="Post" select=”selectPostsForBlog”/>
集合的嵌套结果
至此,你可以猜测集合的嵌套结果是如何来工作的,因为它和关联完全相同,除了它应用了一个"ofType"属性.
<select id="selectBlog" parameterType="int" 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>
同样,要记得id元素的重要性,如果你不记得了,请你阅读上面的关联部分.
同样,如果你引用更长形式允许你的结果映射的更多重用,你可以使用下面这个代替的映射.
<resultMap id="blogResult" type="Blog">
<id property=”id” column="blog_id" />
<result property="title" column="blog_title" />
<collection property="posts" ofType="Post" resultMap=”blogPostResult” />
</resultMap>
<resultMap id="blogPostResult" type="Post">
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
<result property="body" column="post_body" />
</resultMap>
注意:这个对你所映射的内容没有深度,广度或关联和集合相联合的限制.当映射他们时你应该在大脑中保留它们的表现.你的应用在找到最佳方法前要一致进行的单元测试和性能测试.好在MyBatis让你后来可以改变想法,而不是对你的代码造成很小(或任何)影响.
鉴别器
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost" />
</discriminator>
有时一个单独的数据库查询也许返回很多不同(但是希望有些关联)数据类型的结果集.鉴别器元素就是被设计来处理这个情况的,还有包括类的继承层次结构.鉴别器非常容易理解,因为它的表现很像Java语言中的switch语句.
定义鉴别器指定了column和javaType属性.列是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" resultMap="carResult" />
<case value="2" resultMap="truckResult" />
<case value="3" resultMap="vanResult" />
<case value="4" resultMap="suvResult" />
</discriminator>
</resultMap>
在这个示例中,MyBatis会从结果集中得到每条记录,然后比较它的vehicle类型的值.如果它匹配任何一个鉴别器示例,那么就使用这个实例指定的结果映射.换句话说,这样做完全是剩余的结果映射被忽略(除非它被扩展,这在第二个示例中讨论).如果没有任何一个实例相匹配,那么MyBatis仅仅使用鉴别器块外定义的结果映射.所以,如果carResult按如下声明:
<resultMap id="carResult" type="Car">
<result property=”doorCount” column="door_count" />
</resultMap>
那么只有doorCount属性会被加载.这步完成后完整地允许鉴别器示例的独立组,景观和父结果映射可能没有什么关系,这种情况下,我们当然知道cars和vehicles之间有关系;如Car是Vehicle示例.因此,我们想要剩余的属性也被加载.我们设置的结果映射的简单改变如下.
<resultMap id="carResult" type="Car" extends=”vehicleResult”>
<result property=”doorCount” column="door_count" />
</resultMap>
现在vehicleResult和carResult的属性都会被加载了.
尽管层级有些人会发现这个外部映射定义会多少有一些令人厌烦之处.因此还有另外一种语法来做简介的映射风格:例如:
<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>
要记得这些都是结果映射,如果你不指定任何结果,那么MyBatis将会为你自动匹配列和属性,所以这些例子中的大部分是很冗长的.而起始是不需要的.
缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制.MyBatis3中的缓存实现的很多改进都已经实现了,使得它更加强大,而且易于配置.
默认情况下是没有开启缓存的,除了局部的session缓存,可以增强变现而且处理循环依赖也是必须的.要开启二级缓存.
参数介绍
- 映射语句稳重所有select语句将会被缓存
- 映射语句文件中所有inert , update 和delete 语句会刷新缓存.
- 缓存会使用Least Recently Use算法来收回
- 根据时间表,缓存不会以任何时间顺序来属性
- 缓存会存储到列表集合或对象的1024个引用.
- 缓存会被视为一个read/write的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改.
所有的这些属性都可以通过缓存元素的属性来修改.比如:
<cache eviction="FIFO" flushInterval="60000" size="512"
readOnly="true" />
这个更高级的配置穿甲了一个FIFO缓存,并每个60秒刷新,存数据结果对象或列表的512个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改他们会导致冲突.
可用的收回策略有:
属性 | 描述 |
---|---|
LRU | 最近最少使用的,移除最常事件不被使用的对象 |
FIFO | 先进先出,安装对象进入缓存的顺序来移除他们. |
SOFT | 软引用,移除基于垃圾回收器状态和软引用规划的对象. |
WEAK | 弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象.默认是LRU |
flushInterval(刷新间隔) | 可以被设置为任意的正整数,而且他们代表一个合理的毫秒形式的时间段.默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新. |
size(引用数目) | 可以被设置为任意正整数,要记住你缓存的悐数目和你运行环境的可用内存资源数目.默认是1024. |
readOnly(只读) | 只读属性可以被设置为true或false.只读的缓存会给所有调用者返回缓存对象的相同实例.因此这些对象不能被修改,这提供了很重要的性能优势.可读写的缓存会返回缓存对象的拷贝(通过序列号).这会慢一些,但是安全,因此默认是false. |
使用自定义缓存
除了这些自定义缓存的方式,你也可以通过实现你自己的缓存或非其他第三方缓存方案,穿甲适配器来完成全覆缓存行为.
<cache type=”com.domain.something.MyCustomCache”/>
这个示例展示了如何使用一个自定义的缓存试下.type属性指定的类必须实现org.mybatis.cache.Cache接口.这个接口是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();
ReadWriteLock getReadWriteLock();
}
要配置你的缓存,简单和共有的JavaBean属性来配置你的缓存实现,而是通过cache元素来传递属性,比如,下面代码会在你的缓存是现在中调用一个称为"setCacheFile(String file)"的方法:
<cache type=”com.domain.something.MyCustomCache”>
<property name=”cacheFile” value=”/tmp/my-custom-cache.tmp”/>
</cache>
你可以使用所有简单类型作为JavaBean的属性,MyBatis会进行转换.
记得缓存配置和缓存实例是绑定在SQL映射文件的命名空间是很重要的.因此,所有在相同命名空间的语句正如绑定的缓存一样.语句可以修改和缓存交互的方式,或在语句的语句基础上使用两种简单的属性来完成配出他们.默认情况下,语句可以这样来配置.
<select . flushCache=”false” useCache=”true” />
<insert . flushCache=”true” />
<update . flushCache=”true” />
<delete . flushCache=”true” />
因为哪些是默认的,你明显不能明确地以这种方式来配置一条语句.相反,如果你项改变默认的行为,只能设置flushCache和useCache属性.比如:在一些情况下你也许项配出从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存.相似的,你也许有一些更新语句一款执行二日不需要刷新缓存.
参照缓存
回想一下上一节内容,这个热书命名空间的唯一缓存会被使用或者刷新相同命名空间内的语句.也许将来的某个时候,你会想在命名空间中共享相同的缓存配置和实例.在这样的情况下你可以使用cache-ref元素来引用另外一个缓存.
<cache-ref namespace=”com.someone.application.data.SomeMapper”/>
动态SQL
MyBatis的一个强大的特征之一通常是它的动态SQL能力.如果你又使用JDBC或其他相似框架经验,你就明白条件的串联SQL字符串在一起是多么的痛苦,确保不能忘了空格或在列表的最后省略逗号.动态SQL可以彻底处理这种痛苦.
通常使用动态SQL不可能是独立的一部分,MyBAT死当然使用一种强大的动态SQL语言来改进这种情形,这种语言可以被用在任意映射的SQL语句中.
动态SQL元素和使用JSTL或其他相似的基于xml的文件处理器相似.在MyBatis之前的版本中,有很多的元素需要来了解.MyBatis3大大提升了他们,现在用不到原先一般的呀US怒就能工作了,MyBatis采用功能强大的基于ONGL的表达式来消除其他元素.
- if
- choose(when,otherwise)
- trim(where)
- foreach
if
在动态SQL中做所做的最通用的事情是包含部分where字句的条件.比如:
<select id=”findActiveBlogWithTitleLike” parameterType=”Blog”
resultType=”Blog”>
SELECT * FROM BLOG
WHERE state = „ACTIVE‟
<if test=”title ! null ”>
AND title like #{title}
</if>
</select>
这条语句会提供一个可选的文本查找功能.如果你没有传递title,那么所有激活的博客都会被返回.但是如果你传递了title.那么就会查找相近的title(对于敏锐的检索,这种情况下你的参数值需要包含任意的遮掩或通配符)
假若我们项可选的搜索title和author呢?首先,要改变语句的名称让它有意义,然后简单加入另外一个条件.
<select id=”findActiveBlogLike” parameterType=”Blog”
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 title like #{author.name}
</if>
</select>
choose,whenotherwise
有时我们不想应用所有的条件,相反我们项选择很多情况下的一种.和Java中switch语句相似,MyBatis提供choose元素.
我们使用上面的示例,但是现在我们来搜索当title提供时仅有title条件,当author提供时仅有author条件.如果二者都没有提供,只返回featured blogs(也许是由管理员策略地选择的结果列表,而不是返回大量没有意义的,随机的博客列表).
<select id=”findActiveBlogLike” parameterType=”Blog”
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 title like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim,where ,set
前面的例子已经方便处理了一个臭名昭著的动态SQL问题,要考虑我们回到"if"示例后会发生什么?但这次我们将"ACTIVE=1"也设置成动态的条件.
<select id=”findActiveBlogLike” parameterType=”Blog”
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 title like #{author.name}
</if>
</select>
如果这些条件都没有匹配上将会发生什么?这条SQL结束时就会成这样
SELECT * FROM BLOG
WHERE
这会导致查询失败,如果仅仅第二个条件匹配时什么样?这条SQL结束时就会时这样:
SELECT * FROM BLOG
WHERE
AND title like „someTitle‟
这个查询也会失败,这个问题不能简单的用条件来解决,如果你从来没有这样写过,那么你以后也不会这样写.
MyBatis有一个简单的处理方式,这个90%的情况下都会有用,而在不能使用的地方,你可以自定义处理方式.加上一个简单的改变,所有事情都会顺利进行:
<select id=”findActiveBlogLike” parameterType=”Blog”
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 title like #{author.name}
</if>
</where>
</select>
where元素知道如果由被包含的标记返回任意内容,就仅仅插入"WHERE".而且,如果以"AND"或"OR"开头的内容,那么就会跳过WHERE不插入.
如果where元素没有做出你想要的,你可以使用trim元素来定义.比如,和where元素相等的trim元素是:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
…
</trim>
overrides属性采用管道文本分隔符来覆盖,这里的空白也是重要的.它的结果就是移除在overrides属性中指定的内容,插入在with属性中的内容.
和动态更新语句相似的解决方案是set.set元素可以被用于动态包含更新列,而不包含不需要更新的.比如:
<update id="updateAuthorIfNecessary" parameterType="domain.blog.Author">
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关键字,而且也会消除任意无关的逗号,那也许在应用条件之后来跟踪定义的值.
如果你对和这相等的trim元素好奇,它看起来就是这样的.
<trim prefix="SET" suffixOverrides=",">
…
</trim>
注意这种情况下我们覆盖一个后缀,而同时也附加前缀.
foreache
另外一个动态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元素非常强大,它允许你指定一个集合,声明结合项和索引变量,它们可以用在元素体内,它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符,这个元素是很智能的,它不会偶然地附加多于的分隔符.
注意:你可以传递一个List实例或者数组作为参数对象传给MyBatis.当你这么做的时候,MyBatis会自动将它们包装在一个Map中,用名称作为键,List实例将会以:"list"作为键,而数组是丽江会以"array"作为键.