MyBatis是什么?
MyBatis是一款一流的支持自定义SQL、存储过程和高级映射的持久化框架。MyBatis几乎消除了所有的JDBC代码,也基本不需要手工去设置参数和获取检索结果。MyBatis能够使用简单的XML格式或者注解进行来配置,能够映射基本数据元素、Map接口和POJOs(普通java对象)到数据库中的记录。
简介
MyBatis工作流程
功能架构
1、API接口层:
2、数据处理层:
3、基础支撑层:
从XML中构建SqlSessionFactory
- String resource = "com/accp/mybatis/data/Configuration.xml";
- Reader reader = Resources.getResourceAsReader(resource);
- sqlMapper = new SqlSessionFactoryBuilder().build(reader);
MyBatis主配置文件
MyBatis配置文件中大标签configuration下子标签包括:
configuration
|--- properties
|--- settings
|--- typeAliases
|--- typeHandlers
|--- objectFactory
|--- plugins
|--- environments
|--- |--- environment
|--- |--- |--- transactionManager
|--- |--- |__ dataSource
|__ mappers
- <?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>
- <!--在下面的datasource中可以使用占位符来引用属性文件中的值-->
- <properties resource="properties/datasource.properties" />
- <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="defaultExecutorType" value="SIMPLE" />
- <setting name="defaultStatementTimeout" value="25000" />
- </settings>
- <!-- 配置别名 类型别名是Java 类型的简称。它仅仅只是关联到XML 配置,简写冗长的JAVA 类名。-->
- <typeAliases>
- <typeAlias type="com.accp.mybatis.model.Blog" alias="Blog"/>
- </typeAliases>
- <!-- 使用这个配置,"Blog"就能在任何地方代替"com.accp.mybatis.model.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
- float Float
- boolean Boolean
- date Date
- decimal BigDecimal
- bigdecimal BigDecimal
- object Object
- map Map
- hashmap HashMap
- list List
- arraylist ArrayList
- collection Collection
- iterator Iterator
- -->
- <!-- 每个数据库(environment)对应一个SqlSessionFactory 实例-->
- <environments default="accp">
- <!--
- environments对于一个应用可能连接多个数据库,那就需要配置不同的环境来连接不同的数据库,
- 每一个SqlSessionFactory对应一个environments也可以设置不同的环境应用于开发或测试的环境如果环境被忽略,
- 那么默认环境将会被加载,也就是default="development"的作用了
- -->
- <environment id="accp">
- <transactionManager type="JDBC" />
- <!--
- transactionManager设置事物的管理类型是 type=”[JDBC|MANAGED]”
- JDBC使用datasource的连接来管理事物范围。
- MANAGED自己不进行事物的提交和回滚,依靠容器来管理事物,设置closeConnection为false,取消自动关闭连接
- -->
- <dataSource type="POOLED">
- <!--
- dataSource设置数据源[UNPOOLED|POOLED|JNDI]
- POOLED:每次被请求时简单打开和关闭连接
- POOLED:JDBC 连接对象的数据源连接池的实现,用来避免创建新的连接实例时必要的初始连接和认证时间。
- poolMaximumActiveConnections – 在任意时间存在的活动(也就是正在使用)连接的数量。默认值:10
- poolMaximumIdleConnections – 任意时间存在的空闲连接数。
- oolMaximumCheckoutTime – 在被强制返回之前,池中连接被检查的时间。默认值:20000 毫秒(也就是 20 秒)
- poolTimeToWait – 这是给连接池一个打印日志状态机会的低层次设置,还有重新尝试获得连接,这些情况下往往需要很长时间(为了避免连接池没有配置时静默失败)。 默认值:20000 毫秒(也就是 20 秒)
- poolPingQuery – 发送到数据的侦测查询,用来验证连接是否正常工作,并且准备接受请求。默认是“NO PING QUERY SET”,这会引起许多数据库驱动连接由一个错误信 息而导致失败。
- poolPingEnabled – 这是开启或禁用侦测查询。如果开启,你必须用一个合法的SQL语句(最好是很快速的)设置 poolPingQuery 属性。默认值:false。
- poolPingConnectionsNotUsedFor – 这是用来配置 poolPingQuery 多次时间被用一次。这可以被设置匹配标准的数据库连接超时时间,来避免不必要的侦测。默认值:0 (也就是所有连接每一时刻都被侦测-但仅仅当 poolPingEnabled 为 true 时适用)。
- JNDI – 这个数据源的实现是为了使用如 Spring 或应用服务器这类的容器,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
- initial_context – 这个属性用来从初始上下文中寻找环境(也就是initialContext.lookup(init ial——context))。这是个可选属性,如果被忽略,那么data_source 属性将 会直接以 init ialContext 为背景再次寻找。
- data_source – 这是引用数据源实例位置的上下文的路径。它会以由 init ial_context查询返回的环境为背景来查找,如果 init ial_context 没有返回结果时,直接以初始上下 文为环境来查找。
- -->
- <property name="driver" value="${driver}" />
- <property name="url" value="${url}" />
- <property name="username" value="${username}" />
- <property name="password" value="${password}" />
- <property name="driver.encoding" value="${encoding}" />
- </dataSource>
- </environment>
- </environments>
- <!--mappers是告诉MyBatis 去哪寻找映射SQL 的语句。可以使用类路径中的资源引用,或者使用字符,输入确切的URL 引用。-->
- <mappers>
- <mapper resource="com/accp/mybatis/data/BlogMapper.xml" />
- </mappers>
- </configuration>
从SqlSessionFactory中获取SqlSession
- SqlSession session = sqlMapper.openSession();
MyBatis 真正强大之处就在这些映射语句,也就是它的魔力所在。对于它的强大功能,SQL 映射文件的配置却非常简单。
如果您比较SQL 映射文件配置与JDBC 代码,您很快可以发现,使用SQL 映射文件配置可以节省95%的代码量。MyBatis 被创建来专注于SQL,但又给您自己的实现极大的空间。
需要配置的基本元素
1. cache – 配置给定模式的缓存
2. cache-ref – 从别的模式中引用一个缓存
3. resultMap – 这是最复杂而却强大的一个元素了,它描述如何从结果集中加载对象
4. sql – 一个可以被其他语句复用的SQL 块
5. insert – 映射INSERT 语句
6. update – 映射UPDATE 语句
7. delete – 映射DELEETE 语句
8. select - 映射SELECT语句
配置注意
- <!--mappers是告诉MyBatis 去哪寻找映射SQL 的语句。可以使用类路径中的资源引用,或者使用字符,输入确切的URL 引用。-->
- <mappers>
- <mapper resource="com/accp/mybatis/data/BlogMapper.xml" />
- </mappers>
当Java接口与XML文件在一个相对路径下时,可以不在myBatis配置文件的mappers中声明。
SQL
例子请看下面select中例子
SELECT
mybatis select是mybatis 中最常用的元素之一。- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <!--可重用的SQL代码段-->
- <sql id="blog_column">id,title,author_id as authorId</sql>
- <select id="selectBlog_as_map" parameterType="int" resultType="hashmap">
- select <include refid="blog_column"/> from Blog where id = #{id}
- </select>
- </mapper>
这个语句被称作selectBlog_as_map,使用一个int (或Integer)类型的参数,并返回一个HashMap类型的对象
#{id}告诉mybatis创建了一个PreparedStatement(预处理语句)参数。
在JDBC中,类似的代码如下:
- String selectBlog_as_map = “select * from Blog where id =?”;
- PreparedStatement ps = conn.prepareStatement(selectBlog_as_map);
- ps.setInt(1,id);
- public static void selectBlogAsMap(int id) {
- SqlSession session = sqlMapper.openSession();
- Map<String, Object> map = session.selectOne("selectBlog_as_map",id);
- System.out.println(map);
- session.close();
- }
属性 | 描述 | 取值 | 默认 |
---|---|---|---|
id | 在这个模式下唯一的标识符,可被其它语句引用 | ||
parameterType | 传给此语句的参数的完整类名或别名 | ||
resultType | 语句返回值类型的整类名或别名。注意,如果是集合, 那么这里填写的是集合的项的整类名或别名,而不是集合本身的类名。 (resultType 与resultMap 不能并用) | ||
resultMap | 引用的外部resultMap 名。结果集映射是MyBatis 中最强大的特性。 许多复杂的映射都可以轻松解决。(resultType 与resultMap 不能并用) | ||
flushCache | 如果设为true,则会在每次语句调用的时候就会清空缓存。select 语句默认设为false | true|false | false |
useCache | 如果设为true,则语句的结果集将被缓存。select 语句默认设为false true|false false timeout 设置驱动器在抛出异常前等待回应的最长时间,默认为不设值,由驱动器自己决定 | true|false | false |
timeout | 设置驱动器在抛出异常前等待回应的最长时间,默认为不设值,由驱动器自己决定 | 正整数 | 未设置 |
fetchSize | 设置一个值后,驱动器会在结果集数目达到此数值后, 激发返回,默认为不设值,由驱动器自己决定 | 正整数 | 驱动器决定 |
statementType | statement,preparedstatement,callablestatement。 预准备语句、可调用语句 | STATEMENT PREPARED CALLABLE | PREPARED |
resultSetType | forward_only,scroll_sensitive,scroll_insensitive 只转发,滚动敏感,不区分大小写的滚动 | FORWARD_ONLY SCROLL_SENSITIVE SCROLL_INSENSITIVE | 驱动器决定 |
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <insert id="insertBlog" parameterType="Blog">
- insert into Blog
- (id,title,author_id)
- values
- (#{id},#{title},#{authorId})
- </insert>
- </mapper>
对于insert如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后把keyProperty 设成对应的列,就搞定了。
例如Blog表已经对 id 使用了自动生成的列类型,那么语句如下:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <insert id="insertBlog" parameterType="Blog" useGeneratedKeys=”true” keyProperty=”id”>
- insert into Blog
- (title,author_id)
- values
- (#{title},#{authorId})
- </insert>
- </mapper>
还可以使用selectKey元素。下面例子,使用mysql数据库nextval('student')为自定义函数,用来生成一个key。
例如:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <insert id="insertBlog" parameterType="Blog" >
- <selectKey keyProperty="studentID" resultType="String" order="BEFORE">
- select nextval('id')
- </selectKey>
- insert into Blog
- (id,title,author_id)
- values
- (#{id},#{title},#{authorId})
- </insert>
- </mapper>
insert语句属性配置细节:
属性 | 描述 | 取值 | 默认 |
id | 在这个模式下唯一的标识符,可被其它语句引用 | ||
parameterType | 传给此语句的参数的完整类名或别名 | ||
flushCache | 如果设为true,则会在每次语句调用的时候就会清空缓存。select 语句默认设为false | true|false | false |
useCache | 如果设为true,则语句的结果集将被缓存。select 语句默认设为false true|false false timeout 设置驱动器在抛出异常前等待回应的最长时间,默认为不设值,由驱动器自己决定 | true|false | false |
timeout | 设置驱动器在抛出异常前等待回应的最长时间,默认为不设值,由驱动器自己决定 | 正整数 | 未设置 |
fetchSize | 设置一个值后,驱动器会在结果集数目达到此数值后,激发返回,默认为不设值,由驱动器自己决定 | 正整数 | 驱动器决定 |
statementType | statement,preparedstatement,callablestatement。 预准备语句、可调用语句 | STATEMENT PREPARED CALLABLE | PREPARED |
useGeneratedKeys | 告诉MyBatis 使用JDBC 的getGeneratedKeys 方法来获取数据库自己生成的主键(MySQL、SQLSERVER 等 关系型数据库会有自动生成的字段)。默认:false | true|false | false |
keyProperty | 标识一个将要被MyBatis 设置进getGeneratedKeys 的key 所返回的值,或者为insert 语句使用一个selectKey 子元素。 |
selectKey语句属性配置细节:
属性 | 描述 | 取值 |
keyProperty | selectKey 语句生成结果需要设置的属性。 | |
resultType | 生成结果类型,MyBatis 允许使用基本的数据类型,包括String 、int类型。 | |
order | 可以设成BEFORE 或者AFTER,如果设为BEFORE,那它会先选择主键, 然后设置keyProperty,再执行insert语句;如果设为AFTER,它就先运行insert 语句再运行selectKey 语句, 通常是insert 语句中内部调用数据库(像Oracle)内嵌的序列机制。 | BEFORE AFTER |
statementType | 像上面的那样, MyBatis 支持STATEMENT,PREPARED和CALLABLE 的语句形式, 对应Statement ,PreparedStatement 和CallableStatement 响应 | STATEMENT PREPARED CALLABLE |
UPDATE,DELETE
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <update id="updateBlog" parameterType="Blog">
- UPDATE Blog
- SET title = #{title},
- author_id = #{author.id},
- WHERE id = #{id};
- </update>
- </mapper>
delete 简单例子:
- <delete id="deleteBlog" parameterType="Blog">
- DELETE FROM BLOG WHERE ID = #{id}
- </delete>
update、delete语句属性配置细节:
属性 | 描述 | 取值 | 默认 |
id | 在这个模式下唯一的标识符,可被其它语句引用 | ||
parameterType | 传给此语句的参数的完整类名或别名 | ||
flushCache | 如果设为true,则会在每次语句调用的时候就会清空缓存。select 语句默认设为false | true|false | false |
useCache | 如果设为true,则语句的结果集将被缓存。select 语句默认设为false true|false false timeout 设置驱动器在抛出异常前等待回应的最长时间,默认为不设值,由驱动器自己决定 | true|false | false |
timeout | 设置驱动器在抛出异常前等待回应的最长时间,默认为不设值,由驱动器自己决定 | 正整数 | 未设置 |
fetchSize | 设置一个值后,驱动器会在结果集数目达到此数值后,激发返回,默认为不设值,由驱动器自己决定 | 正整数 | 驱动器决定 |
statementType | statement,preparedstatement,callablestatement。 预准备语句、可调用语句 | STATEMENT PREPARED CALLABLE | PREPARED |
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <insert id="insertBlog" parameterType="Blog">
- insert into Blog
- (id,title,author_id)
- values
- (#{id},#{title},#{author.id})
- </insert>
- </mapper>
首先,像MyBatis的其他部分,参数可以指定一个确定的数据类型。
- #{property,javaType=int,jdbcType=NUMERIC}
注意:如果null被当作值来传递,对于所有可能为空的列,JDBC Type是需要的。也可以通过阅读PreparedStatement. setNull()方法的JavaDocs文档来研究它。
为了以后自定义类型处理器,你可以指定一个确定的类型处理器类(或别名),比如:
- #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
对于数值类型,对于决定有多少数字是相关的,有一个数值范围。
- #{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
- #{department,
- mode=OUT,
- jdbcType=CURSOR,
- javaType=ResultSet,
- resultMap=departmentResultMap}
默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置安全的值(比如?)。这样做很安全,很迅速,也是首选的做法,有时你只是想直接在SQL语句中插入一个不改变的字符串。比如,像ORDER BY,你可以这样来使用:
ORDER BY ${columnName}
这里MyBatis不会修改或转义字符串。
重要:接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的。这会导致潜在的SQL注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检查。
resultMap
- package com.accp.mybatis.model;
- public class Blog {
- private Integer id;
- private String title;
- private Integer authorId;
- //省略get和set方法
- }
基于JavaBean的规范,上面这个类有3个属性:id,title和authorId。这些在select语句中会精确匹配到列名。
这样的一个JavaBean可以被映射到结果集,就像映射到HashMap一样简单。
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <select id="selectBlog_by_id" parameterType="int" resultType="Blog">
- select * from Blog where id = #{id}
- </select>
- </mapper>
这些情况下,MyBatis会在幕后自动创建一个ResultMap,基于属性名来映射列到JavaBean的属性上。如果列名没有精确匹配,你可以在列名上使用select字句的别名(一个标准的SQL特性)来匹配标签。
ResultMap最优秀的地方你已经了解了很多了,但是你还没有真正的看到一个。只是出于示例的原因,让我们来看看最后一个示例中外部的resultMap是什么样子的,这也是解决列名不匹配的另外一种方式。
- <resultMap id="Blog_result" type="Blog" >
- <id column="id" property="id" />
- <result column="title" property="title"/>
- <result column="author_id" property="authorId"/>
- </resultMap>
引用它的语句使用resultMap属性就行了(注意我们去掉了resultType属性)。比如:
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.accp.mybatis.model.Blog">
- <resultMap id="Blog_result" type="Blog" >
- <id column="id" property="id" />
- <result column="title" property="title"/>
- <result column="author_id" property="authorId"/>
- </resultMap>
- <!-- resultType与resultMap不能同时使用 -->
- <select id="selectBlog_by_id" parameterType="int" resultMap="Blog_result">
- select * from Blog where id = #{id}
- </select>
- </mapper>
MyBatis的创建基于这样一个思想:数据库并不是您想怎样就怎样的。虽然我们希望所有的数据库遵守第三范式或BCNF(修正的第三范式),但它们不是。如果有一个数据库能够完美映射到所有应用程序,也将是非常棒的,但也没有。结果集映射就是MyBatis为解决这些问题而提供的解决方案。例如,我们如何映射下面这条语句?
- <select id="selectBlog_by_id" parameterType="int" resultMap="Blog_result">
- select
- b.id,
- b.title,
- b.author_id,
- a.id,
- a.username,
- a.password,
- a.email,
- a.bio
- from
- Blog b left join Author a
- on
- b.author_id = a.id
- where
- b.id = #{id}
- </select>
- <resultMap type="Blog" id="Blog_result">
- <id column="id" property="id" />
- <result column="title" property="title"/>
- <!-- 映射关联的对象 -->
- <association property="author" javaType="Author">
- <id column="author_id" property="id"/>
- <result column="username" property="username"/>
- <result column="password" property="password"/>
- <result column="email" property="email"/>
- <result column="bio" property="bio"/>
- </association>
- </resultMap>
resultMap
resultMap属性:type为java实体类;id为此resultMap的标识。
resultMap可以设置的映射:
1. constructor – 用来将结果反射给一个实例化好的类的构造器
a) idArg – ID 参数;将结果集标记为ID,以方便全局调用
b) arg –反射到构造器的通常结果
2. id – ID 结果,将结果集标记为ID,以方便全局调用
3. result – 反射到JavaBean 属性的普通结果
4. association – 复杂类型的结合;多个结果合成的类型
a) nested result mappings – 几resultMap 自身嵌套关联,也可以引用到一个其它上
5. collection –复杂类型集合a collection of complex types
6. nested result mappings – resultMap 的集合,也可以引用到一个其它上
7. discriminator – 使用一个结果值以决定使用哪个resultMap
a) case – 基本一些值的结果映射的case 情形
i. nested result mappings –一个case 情形本身就是一个结果映射,因此也可以包括一些相同的元素,也可以引用一个外部resultMap。
下面一部分将详细说明每个元素。
id、result
- <resultMap type="Blog" id="Blog_result">
- <id column="id" property="id" />
- <result column="title" property="title"/>
- </resultMap>
这些是结果映射最基本内容。id和result都映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的单独属性或字段。
这两者之间的唯一不同是id表示的结果将是当比较对象实例时用到的标识属性。这帮助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射)。
id、result语句属性配置细节:
属性 | 描述 |
property | 映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同的JavaBeans的属性,那么就会使用。 否则MyBatis将会寻找给定名称的字段。这两种情形你可以使用通常点式的复杂属性导航。 比如,你可以这样映射一些东西:“username”,或者映射到一些复杂的东西:“address.street.number”。 |
column | 从数据库中得到的列名,或者是列名的重命名标签。 这也是通常和会传递给resultSet.getString(columnName)方法参数中相同的字符串。 |
javaType | 一个Java类的完全限定名,或一个类型别名(参加上面内建类型别名的列表)。 如果你映射到一个JavaBean,MyBatis通常可以断定类型。 然而,如果你映射到的是HashMap,那么你应该明确地指定javaType来保证所需的行为。 |
jdbcType | 在这个表格之后的所支持的JDBC类型列表中的类型。JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理。 这是JDBC的需要,而不是MyBatis的。如果你直接使用JDBC编程,你需要指定这个类型-但仅仅对可能为空的值。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名。 |
支持的JDBC类型
MyBatis支持如下的JDBC类型:
BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
TINYINT | REAL | VARCHAR | BINARY | BLOB | NVARCHAR |
SMALLINT | DOUBLE | LONGVARCHAR | VARBINARY | CLOB | NCHAR |
INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
BIGINT | DECIMAL | TIME | NULL | CURSOR |
constructor <构造方法>
- <resultMap type="Blog" id="Blog_result_cs">
- <constructor>
- <idArg column="id" javaType="int"/>
- <arg column="title" javaType="String"/>
- </constructor>
- </resultMap>
对于大多数数据传输对象(Data Transfer Object,DTO)类型,属性可以起作用,而且像绝大多数的领域模型,指令也许是你想使用一成不变的类的地方。通常包含引用或查询数据的表很少或基本不变的话对一成不变的类来说是合适的。构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis也支持私有属性和私有JavaBeans属性来达到这个目的,但是一些人更青睐构造方法注入。Constructor(构造方法)元素支持这个。
- package com.accp.mybatis.model;
- public class Blog {
- private Integer id;
- private String title;
- private Integer authorId;
- public Blog() {
- }
- public Blog(Integer id, String title, Integer authorId) {
- super();
- System.out.println("使用有参Blog");
- this.id = id;
- this.title = title;
- this.authorId = authorId;
- }
- //........
- }
为了向这个构造方法中注入结果,MyBatis需要通过它的参数的类型来标识构造方法。Java没有自查(或反射)参数名的方法。所以当创建一个构造方法元素时,保证参数是按顺序排列的,而且数据类型也是确定的。
association<关联映射>
- <!-- 映射关联的对象 -->
- <association property="author" javaType="Author">
- <id column="author_id" property="id"/>
- <result column="username" property="username"/>
- <result column="password" property="password"/>
- <result column="email" property="email"/>
- <result column="bio" property="bio"/>
- </association>
关联元素处理“有一个”类型的关系。比如,在我们的示例中,一个博客有一个用户。关联映射就工作于这种结果之上。你指定了目标属性,来获取值的列,属性的java类型(很多情况下MyBatis可以自己算出来),如果需要的话还有jdbc类型,如果你想覆盖或获取的结果值还需要类型控制器。
关联中不同的是你需要告诉MyBatis如何加载关联。MyBatis在这方面会有两种不同的方式:
(1) 嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型。
(2) 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。
首先,然让我们来查看这个元素的属性。
属性 | 描述 |
property | 映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同的JavaBeans的属性,那么就会使用。否则MyBatis将会寻找给定名称的字段。这两种情形你可以使用通常点式的复杂属性导航。比如,你可以这样映射一些东西:“username”,或者映射到一些复杂的东西:“address.street.number”。 |
column | 来自数据库的列名,或重命名的列标签。这和通常传递给resultSet.getString(columnName)方法的字符串是相同的。 注意:要处理复合主键,你可以指定多个列名通过column=”{prop1=col1,prop2=col2}”这种语法来传递给嵌套查询语句。这会引起prop1和prop2以参数对象形式来设置给目标嵌套查询语句。 |
javaType | 一个Java类的完全限定名,或一个类型别名(参加上面内建类型别名的列表)。如果你映射到一个JavaBean,MyBatis通常可以断定类型。然而,如果你映射到的是HashMap,那么你应该明确地指定javaType来保证所需的行为。 |
jdbcType | 在这个表格之前的所支持的JDBC类型列表中的类型。JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理。这是JDBC的需要,而不是MyBatis的。如果你直接使用JDBC编程,你需要指定这个类型-但仅仅对可能为空的值。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名。 |
联合嵌套选择(Nested Select for Association)
select | 通过这个属性,通过ID引用另一个加载复杂类型的映射语句。从指定列属性中返回的值,将作为参数设置给目标select 语句。表格下方将有一个例子。注意:在处理组合键时,您可以使用column=”{prop1=col1,prop2=col2}”这样的语法,设置多个列名传入到嵌套语句。这就会把prop1和prop2设置到目标嵌套语句的参数对象中。 |
SELECT<联合查询>
- <resultMap type="Blog" id="Blog_result">
- <association property="author" column="author_id"
- javaType="Author" select="selectAuthorById" />
- </resultMap>
- <select id="selectAuthorById" parameterType="int" resultType="Author">
- select * from Author where id = #{id}
- </select>
- <!--
- select关联,建议在一对一的情况下使用。
- 在此处,如果selectBlogById返回多个Blog,将会带来N+1问题
- -->
- <select id="selectBlogById" parameterType="int" resultMap="Blog_result">
- select * from Blog where id = #{id}
- </select>
我们有两个查询语句:一个来加载博客,另外一个来加载作者,而且博客的结果映射描述了“selectAuthorById”语句应该被用来加载它的author属性。
其他所有的属性将会被自动加载,假设它们的列和属性名相匹配。
这种方式很简单,但是对于大型数据集合和列表将不会表现很好。问题就是我们熟知的“N+1查询问题”。概括地讲,N+1查询问题可以是这样引起的:
(1)你执行了一个单独的SQL语句来获取结果列表(就是“+1”)。
(2)对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)。
关联的嵌套结果
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>
非常重要:在嵌套结果映射中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<集合>
- <collection property="posts" ofType="Post">
- <id property="id" column="id"/>
- <result property="subject" column="subject"/>
- <result property="body" column="body"/>
- </collection>
集合元素的作用几乎和关联是相同的。实际上,它们也很相似。
我们来继续上面的示例,一个博客只有一个作者。但是博客有很多文章。在博客类中,这可以由下面这样的写法来表示:
private List<Post> posts;
要映射嵌套结果集合到List中,我们使用集合元素。就像关联元素一样,我们可以从连接中使用嵌套查询,或者嵌套结果。
集合的嵌套查询
首先,让我们看看使用嵌套查询来为博客加载文章。
- <resultMap type="Blog" id="Blog_result">
- <association property="author" column="author_id"
- javaType="Author" select="selectAuthorById" />
- <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectBlogPost" />
- </resultMap>
- <select id="selectBlogPost" resultType="Post" parameterType="int">
- select * from Post where blog_id=#{id}
- </select>
- <select id="selectAuthorById" parameterType="int" resultType="Author">
- select * from Author where id = #{id}
- </select>
- <!--
- select关联,建议在一对一的情况下使用。
- 在此处,如果selectBlogById返回多个Blog,将会带来N+1问题
- -->
- <select id="selectBlogById" parameterType="int" resultMap="Blog_result">
- select * from Blog where id = #{id}
- </select>
首先,你应该注意我们使用的是集合元素。然后要注意那个新的“ofType”属性。这个属性用来区分JavaBean(或字段)属性类型和集合包含的类型来说是很重要的。所以你可以读出下面这个映射:
- <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectBlogPost" />
读作:“在Post类型的ArrayList中的posts的集合。”
集合的嵌套结果
- <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>
discriminator鉴别器
- <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>
- <resultMap id="carResult" type="Car">
- <result property=”doorCount” column="door_count" />
- </resultMap>
上面示例中,MyBatis会从结果集中得到每条记录,然后比较它的vehicle类型的值。如果它匹配任何一个鉴别器的实例,那么就使用这个实例指定的结果映射。
还有另外一种语法来做简洁的映射风格。比如:
- <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>