select
在 MyBatis 中,查询语句是最常用的语句之一。向数据库中放入数据并没有多大价值,直到你再将它取出,因此大多数的应用中查询比修改数据多的多。
属性
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
1、id:该命名空间中可以用来引用该语句的的唯一标识
2、parameterType:传入这条语句的参数的完全限定类名或别名。这个属性是可选的,因为MyBatis 可以自己推断出传入这条语句的具体参数对应的 TypeHandler。默认是未设置。
3、resultType:语句返回的期望类型的完全限定类名或者别名。注意如果返回的是集合,那么这个类型应该是集合包含的类型而不是集合本身。使用 resultType 或 ResultMap,二者取其一。
4、resultMap:外部 resultMap 的名称引用。结果映射是 MyBatis 最强大的特性之一,在对它们有了很好的理解之后,很多困难的映射问题就能迎刃而解。使用 resultMap 或 resultType,二者取其一。
<resultMap type="order" id="orderResultMap">
<id property="id" column="id" />
<result property="userId" column="user_id" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
</resultMap>
5、flushCache:设置该属性为 true 会使本地缓存和二级缓存被清空,无论何时该语句被调用。默认:false。
6、useCache:设置该属性为 true 将会使该条语句的执行结果被缓存到二级缓存中。默认:true。
7、timeout:该设置是在抛出异常之前,驱动等待数据库返回请求结果的秒数。默认未设置(依赖驱动)。
8、fetchSize:这个设置是尝试使驱动批量返回的结果行数。默认未设置(依赖驱动)。
9、statementType:值为 STATEMENT, PREPARED 或 CALLABLE 其中的一个,这个设置使 MyBatis 使用 Statement, PreparedStatement or CallableStatement。默认:PREPARED。
9-1、STATEMENT:直接操作sql,不进行预编译,获取数据:$—Statement
9-2、PREPARED:预处理,参数,进行预编译,获取数据:#—–PreparedStatement:默认
9-3、CALLABLE:执行存储过程————CallableStatement
10、resultSetType:值为 FORWARD_ONLY、SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 其中的一个。默认未设置(赖驱动)。
11、databaseId:如果配置了一个 databaseIdProvider,MyBatis 将会加载所有不带 databaseId 或者匹配当前数据库的 databaseId 的语句。如果带或者不带 databaseId 的相同语句都有,那么不带的会被忽略。
mybatis实现分页、批量插入等操作时,mysql跟oracle的sql写法不同,可以通过databaseId来识别不同数据库,写不同的sql
<bean id="vendorProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
<prop key="Oracle">oracle</prop> <!-- 取别名,名称可任意,并不是上面配置的oralce数据源bean的id -->
<prop key="MySQL">mysql</prop> <!-- 取别名,名称可任意,并不是上面配置的mysql数据源的bean的id-->
</props>
</property>
</bean>
<bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
<property name="properties" ref="vendorProperties" />
</bean>
sqlSessionFactory中增加<property name="databaseIdProvider" ref="databaseIdProvider" />
mybatis mapper中增加databaseId="oracle"或者databaseId="mysql"
12、resultOrdered:仅适用于嵌套结果查询语句:如果设置为 true,它会假定包含了嵌套结果集或分组,这样当一个新的主结果行被返回时,就不会发生对前面结果集的引用。这就使得嵌套结果集很友好地存入更多内存(即不会内存不够用)。默认:false。
13、仅适用于多个结果集。它列出了语句返回的结果集并为每个结果集命名。多个名称用逗号分隔。
insert, update and 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">
特有属性
useGeneratedKeys:(仅针对 insert 和 update)这个设置告诉 MyBatis 使用 JDBC getGeneratedKeys 方法来获取数据库内部生成的主键(例如 MySQL 或 SQL Server 这样的关系型数据库的自增字段)。默认:false
keyProperty:(仅针对 insert 和 update)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 方法的返回值或者通过 insert 语句的 selectKey 子元素来设置它的键值。默认未设置。如果期望得到多个生成的列,可以是使用逗号分隔的属性名称列表。
keyColumn:(仅针对 insert 和 update)通过生成的键值设置表中的列名。这个设置仅在某些数据库(如 PostgreSQL ),当主键的列不是表中的第一列的时候是必须设置的。如果期望得到多个生成的列,可以是使用逗号分隔的属性名称列表。
selectKey:对于不支持自动生成主键的数据库或者目前不支持自动生成主键的 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 语句会被先运行,Author 的 id 属性会被设置,之后 insert 语句才会被调用。这提供给你一个不使 Java 代码变复杂的同时与数据库自动生成主键功能类似的方式。
<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">
keyProperty:selectKey 语句要设置的目标属性。如果期望得到多个生成的列,可以是使用逗号分隔的属性名称列表。
resultType:返回结果的类型。MyBatis 通常可以推算出来,但为了更加明确写上也没什么问题。MyBatis 允许任何简单类型作为主键类型,包含字符串。如果你期望作用于多个生成的列,那么你可以使用包含所有期望属性的一个 Object 或一个 Map。
order:该属性值可设置为 BEFORE 或者 AFTER 。如果设置为 BEFORE ,它会首先选择主键,设置 keyProperty ,然后执行插入语句;如果设置为 AFTER ,它会首先运行插入语句,然后才运行 selectKey 语句——这和 Oracle 很像,在插入语句内部可能会有嵌入索引调用。
sql:可以被用来定义可重用 SQL 代码块,可以包含在其他语句中。
它可以被静态地(在加载阶段)参数化。不同的属性值根据包含的实例而不同。
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
属性值也可以用在 include 元素的 refid 属性或者 include 元素的内部语句中
<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>
Parameters:是 MyBatis 中非常强大的元素。在简单使用中,90%的情况下参数都很少。
简单参数
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上面的例子描述了一个非常简单的命名参数映射。parameterType 被设置为 int,因此参数可以随意命名。
原生类型或简单的数据类型,比如 Integer 和 String,是没有关联属性的,因此它会完全用参数值来替代。但是,如果你传入的是一个复杂的对象,那么情况就不太一样。
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果一个 User 类型的对象参数被传入这个语句,那么 id、username 和 password 属性会被查找并且它们的值会被传入 PreparedStatement 参数中。
复杂参数
javaType,jdbcType
保留两位小数
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
像 MyBatis 的其他部分一样,除了 HashMap 对象,javaType 通常可以由参数对象确定。这时 javaType 应该被指定以确保使用正确的 TypeHandler。如果传入的是 null 值,那么对于所有可空的列,就要指定 JDBC 类型
TypeHandler
为了更加定制化地使用类型处理,你也可以指定一个具体的 TypeHandler 类(或别名)。
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
尽管看起来变的越来越繁琐,但事实上你很少会设置这些。
numericScale
对于 numeric 类型,有一个 numericScale 属性来确定相关的小数位数。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
mode:允许你指定 IN, OUT 或 INOUT 参数
如果一个参数是 OUT 或 INOUT ,那么参数对象属性的具体值就会被改变,就像你在获取输出参数时所期望的那样。如果 mode=OUT (或 INOUT) 并且 jdbcType=CURSOR (即 Oracle REFCURSOR),那么你必须指定一个 resultMap 来映射 ResultSet 到对应的参数类型上。注意 javaType 属性在这里是可选的,如果不设置并且 jdbcType=CURSOR,它将会被自动设置为 ResultSet
MyBatis 也支持更高级的数据类型,比如结构体,但是你必须在外部参数注册时告诉其语句类型名称。 例如(在实际使用中要像这样不能换行):
尽管有这么多强大的选项可供使用,但大多数时候你只需要简单地指定属性名称,MyBatis 会自己推断出其他的。顶多要为可空的列指定 jdbcType。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
Result Maps:
resultMap 元素是 MyBatis 最重要也是最强大的元素。它可以使你从 90% 的获取 JDBC 结果集数据的代码中解放出来,甚至在某些情形下允许你做一些 JDBC 不支持的事。事实上,编写一个复杂语句的关联映射的相同功能可能需要几千行的代码。
ResultMap 的设计思想是,简单的语句根本不需要显式的结果映射,更复杂一点的语句只要求描述它们的关系就可以。
<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>
在这些情况下,MyBatis 会自动创建一个 ResultMap 来基于属性名将列自动映射到 JavaBean 的属性上。
<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" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
MyBatis 创建的一个想法是:数据库并不总是你所想要或者需要的样子。我们希望每个数据库都符合第三范式或 BCNF 范式,但它们并不总是这样。如果有一个数据库映射模式可以完美地映射到所有使用它的应用中那就再好不过了,可惜并没有。结果映射就是 MyBatis 提供的用来解决这个问题的方案。
<!-- 非常复杂的语句 -->
<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 的完整的例子(假设 Author、Blog、Post、Comments 和 Tags 都是类型别名)。
<!-- Very Complex Result Map -->
<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 :实例化类时,注入结果到构造方法中。
1、idArg :标记 ID 参数,有助于提高整体性能。、
2、arg:要注入到构造方法的一个普通结果(除 idArg 标记的主键外的属性字段)。
id:标记一个 ID 结果,有助于提高整体性能。
result:要注入到字段或者 JavaBean 属性的普通结果(除 id 标记的主键外的字段)
association:一个复杂类型关联;多个结果的会包装到这个类型。
collection:复杂类型的集合。
discriminator :使用一个结果值来决定使用哪个 resultMap。
autoMapping:如果设置该属性,MyBatis 将会打开或关闭该 ResultMap 的自动映射。这个属性会覆盖全局自动映射行为。默认未设置。
id & result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
这些是结果映射最基本的内容。id 和 result 都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。两者唯一的区别在于:id 标识的结果是用来在比较对象实例时区分对象的唯一标识属性。这样可以帮助提高整体性能,尤其是缓存和关联结果映射(即 连接查询映射)的性能提升。
property
用来映射列的结果的字段或属性。如果匹配的 JavaBean 存在一个给定名字的属性,那么它将被使用。否则,MyBatis 将会查找一个给定名字的字段。无论哪种情况,你都可以使用点式分隔的复杂属性导航。例如,你可以映射一个简单的属性:username,或者更加复杂的属性:address.street.number。
column
数据库中的列名,或者列的别名。这跟传递到 resultSet.getString(columnName) 的参数一致。
javaType
一个完全限定 Java 类名,或类型别名(内置的别名请参考文章 Mybatis Doc 2.3:Configuration 之 TypeAliases)。如果你映射到一个 JavaBean,MyBatis 通常可以推断出类型。但是,如果你映射到一个 HashMap,那么你应该显式地指定 JavaType 以确保期望的映射行为。
jdbcType
所支持的 JDBC 类型参见之后的表格。仅在执行 insert、update 、delete 操作时的可空列上需要设置。这是 JDBC 要求的,而非 MyBatis。所以即使你直接使用 JDBC 编程,你也需要为可空字段设置这个类型。
typeHandler
我们之前讨论了默认的类型处理器(Mybatis Doc 2.4:Configuration 之 TypeHandlers)。在基于映射的映射原则上,使用这个属性你可以覆盖默认的类型处理器。值为一个 TypeHandler 实现的完全限定类名或类型别名。
支持的 JDBC 类型
为了之后的引用,MyBatis 通过内置的 JdbcType 的枚举类型,支持以下 JDBC 类型。
constructor
虽然属性可以满足大多数的数据传输对象(DTO)类型的类和大多数的域模型,但可能还有一些情况你想使用不可变类。通常包含引用或查询数据的表很少变甚至不变,适合不可变类;构造器注入允许你在初始化时为类设置值,而不会暴露公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来达到这个目的,但一些人还是更喜欢构造器注入的方式。constructor 元素就是来做这个的。为了注入结果到这个构造器,MyBatis 需要用某种方式标识这个构造器。
在下面这个例子中,MyBatis 会查找一个声明了三个参数的构造器: java.lang.Integer, java.lang.String 和 int ,并以其顺序来查找。
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>
当你正在处理一个包含多个参数的构造器,很容易在 arg 元素顺序上出错。从 3.4.3 开始,通过为每个参数指定名称,你可以以任意顺序编写 arg 元素。
为了通过 name 来引用构造方法的参数,你可以添加 @Param 注解或者用 ‘-parameters’ 编译选项来编译项目并且开启 useActualParamName (这个选项默认是开启的)。
association
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
association 元素处理“有一个”类型的关系。例如,在我们的例子中,一个博客有一个作者。
关联映射和其他任意的结果映射工作几乎很像。你要指定一个目标属性、 javaType(大多数时候 MyBatis 可以推断出来)、jdbcType (如果需要的话)和 一个TypeHandler(如果你想要覆盖获取的结果值)。
Auto-mapping
1、在简单的场景下,MyBatis 可以为你自动映射结果。其他情况下,你需要构建 resultMap。但是在本节中你也可以混合使用这两种策略。让我们来看看自动映射是如何工作的。
2、当自动映射结果时,MyBatis 将获取列名并查找有相同名称的属性(忽略大小写)。这意味着如果一个列名为 ID,同时有一个名为 id 的属性被找到,MyBatis 会将 id 属性设置为 ID 列对应的值。
3、通常数据库的列被命名为大写字母并用下划线分隔两个单词,而 java 属性经常遵循驼峰命名规则。为了在这两者之间开启自动映射,设置 mapUnderscoreToCamelCase 为 true(Configuration XML 中)。
4、即使指定了一个 resultMap,自动映射依然能正常工作。这种情况下,对于每一个 resultMap,结果集中所有没有手动匹配的列都会被自动映射,然后手动映射才会被处理。
<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>
自动映射级别:
1、NONE:禁用自动映射。只有手动映射属性才会被设置。
2、PARTIAL:将会自动映射除了内部定义的包含嵌套结果映射的结果(join)。
3、FULL:自动映射所有。
默认值为 PARTIAL,这是有原因的。当设置为 FULL 时,自动映射会在处理连接查询结果时执行,并且连接获取同一行的几个不同实体的数据,因此这将会导致不可预期的映射。为了理解这个风险我们可以看下面这个例子:
<select id="selectBlog" resultMap="blogResult">
select
B.id,
B.title,
A.username,
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<association property="author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="Author">
<result property="username" column="author_username"/>
</resultMap>
在这个结果映射中,Blog 和 Author 都会被自动映射。但是要注意 Author 有一个 id 属性,而结果集中也有一个 id 属性,因此 Author 的 id 将会被填充为 Blog 的 id。这是你所不希望看到的。所以使用 FULL 选项要当心。
通过添加 autoMapping 属性,你可以忽略自动映射级别配置并开启或关闭一个指定 ResultMap 的自动映射:
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>
Cache
MyBatis 包含一个强大的事务性查询缓存特性,它是可配置和可自定义的。MyBatis 3 中的缓存实现有了很多改进以使它更强大的同时也更加易于配置。
默认情况下,只有本地会话缓存是开启的,它仅仅被用来为一个会话期间的数据进行缓存。
想要开启全局二级缓存你只需要简单地在你的 SQL 映射文件中添加一行:
<cache/>
缓存将被视为 read/write (可读/可写),意味着对象获取不会被共享,可以安全地被调用者修改,而不干扰其他调用者或线程潜在的修改,注意:缓存将只作用于在使用 cache 标签声明它的映射文件中的语句。如果你一起使用 Java API 和 XML 映射文件,声明在对应接口中的语句默认将不会被缓存。你将需要使用 @CacheNamespaceRef 注解来引用缓存域。
cache 属性
所有这些属性都可以通过 cache 元素的属性来修改。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这一更高级的配置创建了一个 FIFO 缓存,它会每隔 60 秒刷新一次,最多存储 512 个结果列表或对象的引用,返回对象是只读的,因此修改它们可能导致不同线程中的调用者之间的冲突。
readOnly 属性可以被设置为 true 或 false。一个只读的缓存将会给所有的调用者返回缓存对象的相同实例。因此这些对象不能被修改,这提供了一个很重要的性能优势。一个可读写缓存将会返回缓存对象的一个副本(通过序列化)。这会慢一些,但是更加安全,因此 readOnly 的默认值为 false。
注意:二级缓存是事务性的。这意味着当一个 SqlSession 提交完成时,或者回滚完成且没有执行设置了 flushCache=true 的 insert/delete/update 语句时,它才会被修改。
cache-ref
除了用这些方式定制缓存外,你也可以通过实现你自己的缓存,或为其他第三方缓存解决方案创建一个适配器来完全覆盖缓存的行为。
也许在将来的某个时候你会想要在命名空间之间共享相同的缓存配置和缓存实例。在这些情况下,你可以通过使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>