深入了解select元素
mybatis
提供了四种配置映射语句的元素,和C
,R
,U
,D
四种操作相对应,他们分别是insert
,select
,update
,delete
.
其中配置查询语句的元素select
是myabtis
中最常用的元素之一,他负责从数据库中读取数据.
本篇文章我们就来详细的了解一下select
元素的定义和使用.
通常来说,在使用mybatis
的过程中,一个select
元素通常会有一个与之对应的Mapper
方法配置,但是Mapper
方法配置并不是必须的,我们可以提供一个单独存在的select
语句声明,但不为其提供相应的Mapper
方法.
select
语句声明:
mapper
方法声明:
User selectUserById(Integer id);
select元素的属性定义
select
元素具有十五个属性定义,其中部分属性经过前面的学习,我们已经有所了解,但是还有部分属性我们没有接触过.
但是不要紧,现在我们来一一了解这些属性.
id属性
必填的id
属性是当前select
元素的唯一标志,在实际使用中,该属性的取值应该与其对应的mapper
方法(如果存在)定义相匹配:
部分元素可以通过id
属性来引用当前selecet
元素.
parameterMap属性
parameterMap
属性用于配置入参映射关系,在文章Mybatis源码之美:3.4.解析处理parameterMap元素中,我们详细的解析了这个属性的用法,目前该属性已经被行内参数映射和parameterType
属性所取代.
parameterType属性
parameterType
属性用于配置当前select
元素的的入参类型,他的取值是传入这条语句的参数的全限定名称或者别名.
resultMap属性
resultMap
属性用于配置结果映射,在文章Mybatis源码之美:3.5.6.resultMap元素的解析过程中,我们已经非常详细的了解了该元素.
resultType属性
resultType
属性用于配置结果映射对象的类型,他的取值是返回对象的全限定名称或者别名.
resultSetType属性
resultSetType
属性用于控制jdbc
中ResultSet
对象的行为,他的取值对应着ResultSetType
枚举对象的实例:
根据JDBC
规范,Connection
对象创建Statement
对象时允许传入一个int
类型的resultSetType
参数来控制返回的ResultSet
对象类型.
在mybatis
中,除了默认值DEFAULT
之外,resultSetType
还有三个可选的值:FORWARD_ONLY
,SCROLL_SENSITIVE
以及SCROLL_INSENSITIVE
.
他们分别对应着JDBC
规范中的TYPE_FORWARD_ONLY
,TYPE_SCROLL_SENSITIVE
和TYPE_SCROLL_INSENSITIVE
.
TYPE_FORWARD_ONLY
模式
其中,TYPE_FORWARD_ONLY
模式下的ResultSet
对象只能前进不能后退,即,在处理结果集时,我们可以由第一行滚动到第二行,但是不能从第二行滚动到第一行.
TYPE_FORWARD_ONLY
模式下的ResultSet
对象阉割了部分数据访问功能.
TYPE_SCROLL_INSENSITIVE
模式
TYPE_SCROLL_INSENSITIVE
模式下的ResultSet
对象不仅可以前进,还可以后退,甚至还能通过相对坐标或者绝对坐标跳转到指定行.
但是TYPE_SCROLL_INSENSITIVE
模式下的ResultSet
对象会将数据库查询结果缓存起来,在下次操作时,直接读取缓存中的数据,所以,该模式下的ResultSet
对象对底层数据的变化不敏感.
因此,如果在读取时,底层数据被其他线程修改,ResultSet
对象依然会读取到之前获取到的数据.
TYPE_SCROLL_SENSITIVE
模式
TYPE_SCROLL_SENSITIVE
模式下的ResultSet
对象同样可以前进后退,可以跳转到任意行.
但是和TYPE_SCROLL_INSENSITIVE
模式不同的是,TYPE_SCROLL_SENSITIVE
模式缓存的是数据记录的rowid
,在下次操作时ResultSet
会根据缓存的rowid
重新从数据库读取数据,所以TYPE_SCROLL_SENSITIVE
模式能够实时感知底层数据的变化.
这里提到的底层的数据变化是指更新操作,不包含删除和新增操作.
因为绝大多数数据库的删除操作都是标记删除,而不是物理删除,因此当数据被删除时,我们依然可以通过被缓存的rowid
来获取该条数据.
至于新增的数据,因为在查询时没有缓存对应的rowid
,所以同样不能被感知.
在JDBC
规范中,resultSetType
的默认取值为TYPE_FORWARD_ONLY
.
我们可以通过DatabaseMetaData
对象的supportsResultSetType()
方法来检索当前数据库是否支持给定的结果集类型.
/**
* Retrieves whether this database supports the given result set type.
*
* @param type defined in java.sql.ResultSet
* @return true
if so; false
otherwise
* @exception SQLException if a database access error occurs
* @see Connection
* @since 1.2
*/
boolean supportsResultSetType(int type) throws SQLException;
Connection
对象的getMetaData()
方法可以获取DatabaseMetaData
对象.
statementType属性
select
元素的statementType
属性用于控制mybatis
创建Statement
对象的行为.
public enum StatementType {
STATEMENT/*硬编码*/,
PREPARED/*预声明*/,
CALLABLE/*存储过程*/
}
statementType
属性有三个取值:STATEMENT
,PREPARED
以及CALLABLE
,默认值为PREPARED
.
这三个取值分别对应着JDBC
中Statement
的三种不同定义:
其中Statement
对象是最基础的定义,他提供了执行语句和获取结果的基本方法,它用于处理普通的静态sql
,每次执行,数据库都会重新编译对应的sql
语句,在单次查询中,效率高于PreparedStatement
对象.
PreparedStatement
对象是Statement
对象的扩展,他额外提供了处理参数的方法.他一般用于动态sql
的处理,可以有效的防止sql
注入,PreparedStatement
对象的sql
是预编译的,因此在多次执行同sql
查询时,效率高于Statement
.
CallableStatement
对象扩展了PreparedStatement
,额外添加了调用存储过程和处理出参的方法.他主要用于调用存储过程.
实际上,在mybatis
中,定义了StatementHandler
接口来接管对Statement
对象的操作:
关于StatementHandler
接口,我们会在后面的文章中进行解析.
fetchSize属性
fetchSize
属性用于控制JDBC
批量获取数据时,每次加载数据的行数,其默认值为0
.
// Statement.java
/**
* Gives the JDBC driver a hint as to the number of rows that should
* be fetched from the database when more rows are needed for
* ResultSet
objects generated by this Statement
.
* If the value specified is zero, then the hint is ignored.
* The default value is zero.
*
* @param rows the number of rows to fetch
* @exception SQLException if a database access error occurs,
* this method is called on a closed Statement
or the
* condition {@code rows >= 0} is not satisfied.
* @since 1.2
* @see #getFetchSize
*/
void setFetchSize(int rows) throws SQLException;
但是当我们查看与之相对应的getFetchSize()
方法时:
// Statement.java
/**
* Retrieves the number of result set rows that is the default
* fetch size for ResultSet
objects
* generated from this Statement
object.
* If this Statement
object has not set
* a fetch size by calling the method setFetchSize
,
* the return value is implementation-specific.
*
* @return the default fetch size for result sets generated
* from this Statement
object
* @exception SQLException if a database access error occurs or
* this method is called on a closed Statement
* @since 1.2
* @see #setFetchSize
*/
int getFetchSize() throws SQLException;
我们会发现fetchSize
取决于具体的数据库驱动,比如:oracle
数据库的默认值为10
.
我们可以通过调整fetchSize
的值来控制数据库每次加载的数据量,进而手动控制查询时间和内存空间的阈值.
timeout属性
timeout
属性用于配置JDBC
中Statement
对象的请求超时时间,单位:秒.
在未抛出异常的前提下,每次数据操作,jdbc
驱动都会等待指定timeout
时长.
flushCache和useCache属性
flushCache
和useCache
两个属性都用于控制myabtis
的缓存行为.
其中flushCache
属性用于控制清除缓存的行为,当flushCache
属性为true
时,mybatis
在执行语句之前将会清除当前语句所匹配的二级缓存和以及所有的一级缓存.
针对select
类型的语句,flushCache
属性的默认值为false
,其余类型的语句默认值为true
.
useCache
属性用于控制mybatis
将查询语句的结果写入二级缓存的行为,当useCache
属性的值为true
时,当前语句的执行结果将会被存入到二级缓存中.
databaseId属性和lang属性
databaseId
属性用于配置当前selecet
元素对应的数据库类型.
lang
属性用于指定解析当前select
元素使用的脚本驱动.
在Mybatis源码之美:3.6.解析sql代码块一文中,我们已经对databaseId
属性和lang
属性做了一个简单了解.
resultOrdered属性
在Mybatis源码之美:3.5.4.唯一标标识符--id元素一文中,我们提供了一个因为错误配置id
元素导致数据丢失的问题.
在这篇文章中,我们指出:id元素可以通过临时缓存对象来提高在嵌套结果映射中处理数据的性能.
这是一种典型的用空间换时间的解决方案,通过临时缓存,mybatis
提高了对数据的解析速度,但是势必会消耗更多的内存.
当我们执行大数量的操作时,可能就会导致OOM
(OutOfMemoryError
)的发生.
为此,mybatis
为我们提供了一个resultOrdered
属性.
resultOrdered
属性是一个标志性的属性,用户可以通过配置该属性的值为true
来告知mybatis
当前select
语句的查询结果针对于
元素的配置是有序的,即,多个相同
属性是分组且连续的.
比如:
key | value |
---|---|
A | 数据1 |
A | 数据2 |
A | 数据3 |
A | 数据4 |
B | 数据5 |
B | 数据6 |
B | 数据7 |
B | 数据8 |
这样mybatis
在解析数据时,根据
元素的配置最多只会缓存一行数据,降低了内存的使用量,因此降低了OOM
的发生几率.
针对上面的数据记录来讲,mybatis
在解析数据1
时,会将数据1
对应的记录缓存起来,之后的的数据2
,数据3
以及数据4
因为具有相同的key
值,因此都能命中缓存,避免了重复解析数据.
但是在处理数据5
时,因为key
值为B
,无法命中缓存,数据1
的缓存将会被移除,数据5
对应的记录被放入缓存中.
在这种处理方式下,因为相同Key
值的数据分组在一起,因此效率高的同时还降低了内存的使用.
但是,针对数据记录:
key | value |
---|---|
A | 数据1 |
B | 数据2 |
A | 数据3 |
B | 数据4 |
A | 数据5 |
B | 数据6 |
A | 数据7 |
B | 数据8 |
具有相同key
值的数据并没有分组在一起,而是交叉出现,因此虽然mybatis
在解析数据1
时,会将数据1
对应的记录缓存起来,但是因为数据2
的key
值为B
,不能命中缓存,因此数据1
的缓存将会被移除,数据2
对应的记录进入缓存.
依次类推,每一条数据记录都不能命中缓存,都需要重新解析.
针对这种场景,虽然降低了内存使用量,但是大大降低了数据解析的效率.
resultSets属性
在文章Mybatis源码之美:3.5.2.负责一对一映射的association元素和负责一对多映射的collection元素中我们已经对select
元素的resultSets
做了较深入的了解.
这里我们简单回顾一下其中比较重要的知识点:
通常来说,一次数据库操作只能得到一个ResultSet
对象,因此一条select
语句通常对应一个结果集.
但是因为一些额外的原因,一条select
语句是可以返回多个结果集的.
比如:
- 部分数据库支持在一次查询中返回多个结果集
- 部分数据库支持在存储过程中返回多个结果集
- 部分数据库支持一次性执行多个语句
因此,如果我们想操作不同结果集中的数据,我们就有必要区分出每个结果集对象.
mybaits
为这种场景提供了一个解决方案,它允许我们在配置select
元素的时候,通过配置其resultSets
属性来为每个结果集指定名称.
结果集的名称和resultSets
属性定义顺序对应,如果有多个结果集的名称需要配置,名称之间使用,
进行分隔.
属性总结
至此,我们算是对select
元素的所有属性都有了一定的了解.
其实,insert
,select
,update
,delete
这四个元素他们具有很多相同的属性定义,在了解了select
元素的属性之后,剩余三个元素的属性定义相对学习起来就比较简单了.
select元素的子元素定义
看完属性定义之后,让我们看一下select
元素的子元素定义.
根据DTD
定义来看,select
元素和sql
元素竟然拥有完全相同的子元素定义:
他俩拥有同样的PCDATA
标记,因此和sql
元素一样,select
元素中的文本定义,也是允许子元素和普通文本混排的.
至于剩余的子元素定义,我们将在了解了insert
,update
以及delete
元素之后,进行深入的了解.
总结
经过上面的学习之后,我按照用途对select
元素的属性做了一个简单的归类:
select
元素不算太过复杂,其中很多特性,是我们平常忽略掉,不怎么使用的,但是在深入了解了这些属性之后,相信我们对mybatis
的掌握会更深一步,以后,解决问题,优化代码也能更得心应手.
学无止境,加油~