MyBatis的真正强大在于它的映射语句,这也是它的魔力所在。
sql映射文件包含的顶级元素有:
- cache 对给定命名空间的缓存配置
- cache-ref 对其他命名空间缓存配置的引用
- resultMap 用来描述如何从数据库结果集中来加载对象
- sql 可被其他语句引用的可重用代码块
- insert 映射插入语句
- update 映射更新语句
- delete 映射删除语句
- select 映射查询语句
select
使用xml
查询语句是MyBatis中最常用的元素之一:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
此时这条查询语句被称作selectPerson,接收一个int(或integer)类型的参数,并返回一个HashMap类型的对象,其中的键是列名,值便是结果行中的对应值。
select元素中的属性
属性 | 描述 |
---|---|
id | 命名空间中唯一的标识符,可以被用来引用这条语句 |
parameterType | 传入这条语句的参数的完全限定名和别名。可选,MyBatis可以通过类型处理器(TypeHandler)判断出具体传入语句的参数,默认值为未设置(unset) |
resultType | 返回的期望类型的类的完全限定名或别名。注意如果返回的是集合,应该设置为集合包含的类型,而不是集合本身。不可与resultMap同时使用 |
resultMap | 外部resultMap的命名引用。结果集的映射是MyBatis对强大的特性,不可与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中的一个,默认未DEFAULT(等价于unset)依赖驱动 |
databaseId | 配置数据库厂商标识,MyBatis会加载所有的不带databaseId或配置当前databaseId的语句;如果带或者不带的语句都有,则不带的会被忽略 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 |
resultSets | 这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。 |
使用注解
首先我们需要在配置文件中开启注解;之后我们可以在dao接口中使用注解
@Select("select * from user where id = #{id}")
User findById(@Param("id") String id);
@Select 表示查询映射
@Param 表示要使用的参数
insert,update和delete
使用xml
数据变更语句insert,update和delete的实现非常接近
<insert id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
delete from Author where id = #{id}
</delete>
属性 | 描述 |
---|---|
id | 命名空间中的唯一标识符,可被用来代表这条语句。 |
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 |
keyColumn | (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
使用注解
@Insert("insert into user (name, password) values (#{name}, #{password})")
pubilc int insertUser(@Param("name") String name, @Param("password") String password);
@Update("update user set password=#{password} where name=#{name}")
pubilc int updateUser(@Param("name") String name, @Param("password") String password)
@Delete("delete from user where name=#{name}")
pubilc int deleteUser(@Param("name") String name)
参数
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上述示例展示了一个非常简单的命名参数映射。参数类型被设置为int,使用#{xx}相当于使用了一个?占位符。
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果使用User类型的参数对象传递到语句中,User类中定义的id、username、password等属性都将会被查找到,然后将它们的值传入预处理语句的参数中。
对向语句中传递参数来说,这样既简单又有效。
默认情况下,使用#{}格式的语法可以在MyBatis创建PreparedStatement时使用参数占位符并安全的设置参数(就像使用?一样)。这样更安全,迅速,通常也是首选做法,不过有时可能需要直接在sql语句中插入一个不转义的字符串。比如,像ORDER BY,我们可以这样做:
ORDER BY ${columnName}
此时MyBatis不会修改或转义字符串。当sql语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。比如:想要通过一个条件获取参数时
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value)
此时${column}会被直接替换,而#{value}会被使用?预处理。这样就可以不同的条件及条件值进行查询数据。
使用这种方式接收用户的输入,并将其用于语句中的参数是不安全的,会导致潜在的sql注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验
结果映射
简单结果映射
resultMap元素是MyBatis中最重要最强大的元素。ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系即可。
一般情况下使用resultType=“map”,将所有的列映射到HashMap的键上,是可以满足开发需求的。但是有时我们可能会使用到javaBean或POJO作为模型。
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
此时我们可以将resultType指定为我们提前写好的pojo类,这种情况下MyBatis会在幕后自动创建一个ResultMap,再基于属性名来映射列到JavaBean的属性上。如果列名和属性名没有精确匹配,可以在SELECT语句中对列使用别名来匹配标签;或者使用外部的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>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
高级结果映射
在我们实际开发过程中,我们可能会遇到一些复杂的查询和结果映射,MyBatis创建时的一个思想就是:数据库不可能永远是你所想或所需要的那个样子。
此时我们就需要用的一对一关联,一对多关联,甚至于多对多的映射。
一对一
1-首先需要在pojo类中创建一个属性,将关联查询的信息映射到这个属性中
public class Orders{
private Integer id;
private Integer userId;
private Integer number;
private User user;
}
2-在xml中编写reslutMap
<resultMap type="orders" id="ordersUserResultMap">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<association property="user" javaType="user">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
</association>
</resultMap>
在进行关联时MyBatis有两种不同的方式加载关联:
嵌套Select查询:通过执行另外一个sql映射语句来加载期望的复杂类型。
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_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>
不推荐使用Select嵌套关联,相对来说这种方法虽然简单,但是性能表现不佳。
一对多
一对多的使用其实与一对一非常相似,区别在于它关联的是一个集合元素
private List<Post> posts
<collection property="posts" javaType="ArrayList" ofType="domain.blog.Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
一般情况下,MyBatis可以推断javaType属性,因此可以省略该属性;如此我们可以看出除了新增的ofType属性之外,它和一对一时完全相同的;