highlight: a11y-dark
1. SQL 语句
SQL
标签的属性列表:
TypeHandler
) 推断出具体传入语句的参数,默认值为未设置(unset
)。该参数一般不需要写。
parameterMap
这是引用外部parameterMap
的已经被废弃的方法。请使用内联参数映射和 parameterType
属性。
resultType
从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用resultType
或resultMap
,但不能同时使用。
resultMap
外部resultMap
的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用resultMap
或resultType
,但不能同时使用。
flushCache
将其设置为true
后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false
。
useCache
将其设置为true
后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对select
元素为true
。
timeout
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset
)(依赖驱动)。
fetchSize
这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset
)(依赖驱动)。
statementType
值为STATEMENT
,PREPARED
或CALLABLE
中的一个。这会让 MyBatis 分别使用 Statement
,PreparedStatement
或CallableStatement
,默认值:PREPARED
。
resultSetType
值为FORWARD_ONLY
,SCROLL_SENSITIVE
,SCROLL_INSENSITIVE
或DEFAULT
(等价于unset
) 中的一个,默认值为未设置(unset
)(依赖驱动)。
databaseId
如果配置了数据库厂商标识(databaseIdProvider
),MyBatis 会加载所有的不带 databaseId
或匹配当前databaseId
的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered
这个设置仅针对嵌套结果select
语句适用:如果为true
,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
。
resultSets
这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。
useGeneratedKeys
(仅对insert
和update
有用)这会令 MyBatis 使用JDBC
的getGeneratedKeys
方法来取出由数据库内部生成的主键(比如:像MySQL
和SQL Server
这样的关系数据库管理系统的自动递增字段),默认值:false
。
keyProperty
(仅对insert
和update
有用)唯一标记一个属性,MyBatis 会通过getGeneratedKeys
的返回值或者通过insert
语句的selectKey
子元素设置它的键值,默认值为未设置(unset
)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn
(仅对insert
和update
有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像PostgreSQL
)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
1.1. SELECT
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
<select id="selectPerson" resultType="hashmap">
SELECT * FROM Person WHERE id = #{id}
</select>
1.2. INSERT
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<insert id="insertAuthor">
INSERT INTO Author (id, username, password, email, bio)
VALUES (#{id}, #{username}, #{password}, #{email}, #{bio})
</insert>
1.2.1. 返回自增主键
如果数据库支持自动生成主键(比如MySQL
和SQL Server
),可以设置useGeneratedKeys="true"
,然后再把 keyProperty
设置到目标属性上,就可以实现将新增记录的主键值设置到参数对象的对应属性上。假设Author
表已经对id
列设置了自增主键。
单条插入:
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
INSERT INTO Author (username, password, email, bio)
VALUES (#{username}, #{password}, #{email}, #{bio})
</insert>
批量插入:
<insert id="batchInsertAuthor" useGeneratedKeys="true" keyProperty="id">
INSERT INTO Author (username, password, email, bio) VALUES
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
</foreach>
</insert>
Mapper 层代码:
@Mapper
public interface AuthorMapper {
int insertAuthor(Author author);
int batchInsertAuthor(List<Author> authList);
}
返回的int
为插入的记录条数,成功后会自动为参数的author
对象的id
属性赋值。(注意:即使不写useGeneratedKeys="true" keyProperty="id"
,数据库记录及其主键也能正常生成,只不过不会回设主键值到 Java 参数对象上而已。)
1.2.2. 提前生成主键
对于不支持自动生成类型的数据库或可能不支持自动生成主键的JDBC
驱动,MyBatis 有另外一种方法来生成主键。这里有一个简单的示例,它可以生成一个随机id
(开发中最好不要这么做,这里只是为了展示 MyBatis 处理问题的灵活性及其所关心的广度。Oracle
中一般是通过查询该表对应的序列获取主键值):
<insert id="insertAuthor">
<selectKey keyProperty="id" order="BEFORE">
SELECT CAST(RANDOM()*1000000 as INTEGER) a FROM SYSIBM.SYSDUMMY1
</selectKey>
INSERT INTO Author (id, username, password, email, bio)
VALUES (#{id}, #{username}, #{password}, #{email}, #{bio})
</insert>
在上面的示例中,selectKey
元素将会首先运行,Author
的id
会被设置,然后插入语句会被调用。这给你了一个和数据库中来处理自动生成的主键类似的行为,避免了使 Java 代码变得复杂。
selectKey 标签的属性:
<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">
属性
描述
keyProperty
selectKey
语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn
匹配属性的返回结果集中的列名称。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
resultType
结果的类型。MyBatis 通常可以推断出来,但是为了更加精确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的Object
或一个Map
。
order
这可以被设置为BEFORE
或AFTER
。如果设置为BEFORE
,那么它会首先生成主键,设置 keyProperty
然后执行插入语句。如果设置为AFTER
,那么先执行插入语句,然后是selectKey
中的语句 - 这和Oracle
数据库的行为相似,在插入语句内部可能有嵌入索引调用。
statementType
与前面相同,MyBatis 支持STATEMENT
,PREPARED
和CALLABLE
语句的映射类型,分别代表Statement
、PreparedStatement
和CallableStatement
类型。
1.3. UPDATE
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<update id="updateAuthor">
UPDATE Author SET
username = #{username}, password = #{password}, email = #{email}, bio = #{bio}
WHERE id = #{id}
</update>
1.4. DELETE
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete id="deleteAuthor">
DELETE FROM Author WHERE id = #{id}
</delete>
2. SQL 片段
sql
这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。它可以(在加载的时候)被静态地设置参数。在不同的包含语句中可以设置不同的值到参数占位符上。比如:
<sql id="userColumns">
${alias}.id, ${alias}.username, ${alias}.password
</sql>
这个 SQL 片段可以被包含在其他语句中,例如:
<select id="selectUsers" resultType="map">
SELECT
<include refid="userColumns">
<property name="alias" value="t1"/>
</include>,
<include refid="userColumns">
<property name="alias" value="t2"/>
</include>
FROM some_table t1 CROSS JOIN some_table t2
</select>
property
标签的属性值可以直接用在include
元素包含的sql
标签的内部语句中,也可以被用在include
元素的refid
属性里,例如:
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
FROM <include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
SELECT field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
平时工作中不需要像上面示例那样嵌套好几层,这里写一个常用的简单示例,查询字段和查询条件封装到sql
片段中,所有的查询语句都能复用:
<sql id="basicColumns">
id,
username,
password,
email,
bio
</sql>
<sql id="conditionWhere">
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="username != null and username != ''">
AND username = #{username}
</if>
</where>
</sql>
<select id="findById" resultType="author">
SELECT
<include refid="basicColumns" />
FROM Author
<include refid="conditionWhere" />
</select>
3. 参数使用
3.1. 参数传递
3.1.1. 单个简单类型参数
可以接受基本类型以及对应的包装类型,集合类型的值。这种情况 MyBatis 可直接使用这个参数,不需要经过任何处理。
User selectUsers(Long id);
<select id="selectUsers" resultType="User">
SELECT id, username, password FROM users WHERE id = #{id}
</select>
基本类型的话直接使用#{}
,变量名可以随意,一般直接写接口中的形式参数变量名即可。如果使用${}
,变量名必须为value
,也就是必须写为${value}
。
集合类型的话一般用在foreach
标签中,和接口中的形参变量名也无关,collection
里需要对应着写。List
类型参数就写list
;Set
类型参数就写collection
;数组类型参数就写array
;
源码在org.apache.ibatis.session.defaults.DefaultSqlSession
中:
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
3.1.2. 单个 JavaBean 参数
传入一个对象的情况,User
类型的参数对象传递到了语句中,id
、username
和password
属性将会被查找,然后将它们的值传入预处理语句的参数中。
int insertUser(User user);
和接口形参中变量名称无关,获取的变量名都是JavaBean
中属性名。
<insert id="insertUser">
INSERT INTO users (id, username, password)
VALUES(#{id}, #{username}, #{password})
</insert>
3.1.3. 单个 Map 参数
可以封装多个参数为map
,直接传递。
int insertUser(Map<String, Object> map);
和接口形参中变量名称无关,获取的变量名都是Map
中的key
名。
<insert id="insertUser">
INSERT INTO users (id, username, password)
VALUES(#{id}, #{userName}, #{password})
</insert>
3.1.4. 多个参数
任意多个参数,都会被 MyBatis 重新包装成一个Map
传入。
int insertUser(Long id, String username, String password);
和接口形参中变量名称无关,获取的变量名是按照参数顺序取的#{param1}, #{param2}...
,或者#{0}, #{1}...
。
<insert id="insertUser">
INSERT INTO users (id, username, password)
VALUES(#{0}, #{1}, #{2})
</insert>
3.1.5. 注解参数(推荐使用)
使用@Param
注解为接口的形参起一个名字(一般名字都和形参变量名相同),MyBatis 就会将这些参数封装进Map
中,key
就是我们自己指定的名字。该注解全限定类名为org.apache.ibatis.annotations.Param;
。
int insertUser(@Param("id")Long id, @Param("username")String username, @Param("password")String password);
<insert id="insertUser">
INSERT INTO users (id, username, password)
VALUES(#{id}, #{userName}, #{password})
</insert>
3.2. 参数属性
下面介绍几种参数的属性设置,但大多时候只须简单地指定属性名,其他的事情 MyBatis 会自己去推断,顶多要为可能为NULL
的列指定jdbcType
而已。
3.2.1. javaType
#{property, javaType=int, jdbcType=NUMERIC}
javaType
通常可以根据参数对象的类型确定下来,所以一般可以省略。除非该参数对象是一个HashMap
。如果所使用的参数是个HashMap
,需要显式指定 javaType
来确保 MyBatis 会使用正确的类型处理器(TypeHandler
)。
3.2.2. jdbcType
#{property, javaType=int, jdbcType=NUMERIC}
如果一个列允许null
值,并且会传递值为null
的参数,就必须要指定 jdbcType
。
3.2.3. numericScale
#{height, javaType=double, jdbcType=NUMERIC, numericScale=2}
对于数值类型,有一个小数保留位数的设置属性numericScale
,用来指定小数点后保留的位数。
3.2.4. typeHandler
由于 Java 代码中的String
类型对应的默认typeHandler
为StringTypeHandler
,当用String
类型处理时,如果String
长度超过一定长度,就会报如下错误:
setString can only process strings of less than 32766 chararacters
解决办法是指定该属性的typeHandler
,如下:
#{message, typeHandler=org.apache.ibatis.type.ClobTypeHandler}
我们也可以自定义typeHandler
来处理需要的数据。
3.2.5. mode
这个属性在写存储过程中会用到。所以一般我们不需要配置。
mode
属性允许你指定IN
,OUT
或INOUT
参数。如果参数的mode
为OUT
或INOUT
,就像你在指定输出参数时所期望的行为那样,参数对象的属性实际值将会被改变。
如果mode
为OUT
(或INOUT
),而且jdbcType
为CURSOR
(也就是 Oracle 的REFCURSOR
),你必须指定一个resultMap
引用来将结果集ResultMap
映射到参数的类型上。要注意这里的javaType
属性是可选的,如果留空并且jdbcType
是CURSOR
,它会被自动地被设为 ResultMap
。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高级的数据类型,比如结构体(structs
),但是当使用OUT
参数时,你必须显式设置类型的名称jdbcTypeName
。
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
3.3. 参数获取
3.3.1. ${} 和 #{}
默认情况下,使用#{}
格式的语法会导致 MyBatis 创建PreparedStatement
参数占位符并安全地设置参数(就像使用?
一样)。 有时需要直接在SQL
语句中插入一个不转义的字符串。 比如,像ORDER BY
,你可以这样来使用:
ORDER BY ${columnName}
${}
常用于表名和字段名的替换。
@Select("SELECT * FROM user WHERE ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
其中${column}
会被直接替换,而#{value}
会被使用?
预处理。
用${}
方式接受用户的输入,并将其用于语句中的参数是不安全的,会导致潜在的SQL
注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。
3.3.2. LIKE 写法
使用${}
实现:
<select id="selectPerson" resultType="hashmap">
SELECT * FROM Person WHERE name LIKE '%${name}%'
</select>
这种写法可能会受到SQL
注入的攻击,尽量避免使用。
使用#{}
实现:
<select id="selectPerson" resultType="hashmap">
SELECT * FROM Person WHERE name LIKE "%"#{name}"%"
</select>
因为#{}
解析成SQL
语句时候,会在变量外侧自动加单引号''
,所以这里%
需要使用双引号""
,不能使用单引号''
,不然会查不到任何结果。
使用CONCAT()
实现:
<select id="selectPerson" resultType="hashmap">
SELECT * FROM Person WHERE name LIKE CONCAT('%', #{name}, '%')
</select>
这是 SQL 中自带的一个字符串拼接函数,直接使用即可。