写在前面:十分感谢《深入浅出Mybatis技术原理与实战》这本书,大多数地方是书上的话,希望自己能在后面的文章中多写一些自己的理解。而且最重要的是!每次照书无脑敲的时候,都好羞愧啊(害羞脸)。后面一定注意这些问题。最后要感谢点赞、评论以及指正的朋友们,你们是坠吼的!!!
Mapper 映射器
映射器是个好东西,按照官方文档的话来说就是:
The true power of MyBatis is in the Mapped Statements. This is where the magic happens.
简单翻译就是:Mybatis真正的力量来源于它的映射语句,那里是见证奇迹的地方!
Mapper XML文件相对简单。 如果你将它们与等效的JDBC代码进行比较,你会节省95%的代码!MyBatis的目的是专注于SQL,并尽力去帮我们节省时间和精力去做其他的部分。
Mapper XML文件只有几个一级元素,介绍一下:
- cache - 给定命名空间的缓存配置。
- cache-ref - 从另一个命名空间引用缓存配置。
- resultMap - 最复杂最也是最强大的元素,描述如何从数据库结果集中加载对象
parameterMap- 已过时!- sql - 可以被其他语句引用的SQL的可重用块。
- insert - 映射INSERT语句。
- update - 映射UPDATE语句。
- delete - 映射DELETE语句。
- select - 映射SELECT语句。
我们将逐个击破!
select
select语句是我们在Mybatis中使用最多的语句。我们使用查询语句的频率高于我们使用修改语句的频率,对于插入,更新或删除,来说,他们中可能会包含着很多的选择。 直接举例子吧:
<select id="selectPerson" parameterType="int" resultType="map">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
此语句接受类型为int(或Integer)的参数,并返回Map类型的数据。
注意参数符号:#{id}
让我们看一下如果使用JDBC的话代码是什么样子的:
// JDBC代码,不是Mybatis
String sql = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,id);
可以想象,如果字段很多的话,传统方式下我们将会在书写很多重复的代码,这样不仅很枯燥,还很容易出错。例如:
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
逐个进行解释:
- id:它和Mapper命名空间组合起来是唯一的。
- parameterType:可以给出类的全名吗,也可以给类的别名,但是必须是内部定义或者自定义的。
- parameterMap:即将废弃。
- resultType:返回值的类型,也可以使用别名,不能和resultMap同时使用!
- resultMap:可以执行强大的映射功能,解决很多的映射困难,不能与resultType同时使用。resultMap给了我们自己定义映射规则的机会。
- flushCache:如果设置true将会清空本地缓存和二级缓存。默认为false。
- useCache:默认为true,是启动二级缓存的开关。
- timeout:设置超时参数,等超时抛出异常,单位是秒。默认值由厂商提供。
- fetchSize:设置获取记录的总条数。
- statementType:设置使用哪个JDBC的Statement工作。可以取值:STATEMENT、PREPARED。默认值为PREPARED。
-resultSetType:取值为FORWARD_ONLY 、SCROLL_SENSITIVE 、SCROLL_INSENSITIVE。默认值为unset(取决于驱动程序)。
给映射器传递多个参数有三种方式:
1.使用Map传参。
2.使用注解方式传参。
3.使用JavaBean传参。
首先,使用Map传参会导致业务可读性丧失,很难继续扩展和维护,这里很不推荐这种方式。
其次,使用@Param注解传递多个参数,这种方式的使用受到参数个数的影响,如果参数个数很多的话,那要写n多个@Param吗?所以一般来说,如果参数个数在五个以下的话,使用这种方式最好。
最后,如果参数多于五个,我们使用JavaBean。
insert、update、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">
具体都是干啥的,这里真的懒得写了,虽然元素很多,但是能用上的没几个。懂SQL的童鞋看了示例马上就能学会:
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
没错,官方的文档就是这么粗暴。我们主要讨论需要去重点处理的问题——主键回填和自定义
首先,我们可以使用keyProperty属性指定那个是主键字段,同时使用userGeneratedKeys属性设置这个主键是否使用数据库的内置策略生成。
举个栗子:
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
这里就是指定了id为自增字段。我们建立POJO,提供getter和setter方法,就可以使用主键回填了。
但是,有时候我们需要根据一些特殊的关系设置主键id的值,比如我们要求:如果表t_role没有记录,我们设置id=1,否则我们取最大id加2来设置新的主键,这时候我们就需要这么写:
<insert id="insertRole" parameterType="role" userGeneratedKeys="true" keyProperty="id">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select if(max(id) is null,1,max(id)+2) as newId from t_role
</selectKey>
insert into t_role
(id, role_name,note)
values
(#{id}, #{rolename}, #{note})
</insert>
sql
此元素可用于定义可以包含在其他语句中的SQL代码的可重用片段。 它可以是静态的(在加载阶段)参数化。 不同的属性值在include实例中可能不同。 例如:
<sql id="userColumns">
${alias}.id,${alias}.username,${alias}.password
</sql>
上面我们用sql元素定义了userColumns,他可以很方便的使用include元素的refid属性引用,达到重用的功能。请看:
<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 t1
cross join t2
</select>
我们也可以给refid参数值
<sql id="someinclude">
select * from <include refid="${tableName}" />
</sql>
这样实现了定义一处,很多地方也可以引用。
参数
对于储存过程来说,有三种参数:输入参数、输出参数、输入输出参数。大部分情况下Mybatis回去推断返回数据的类型,所以大部分情况下是不要去配置参数类型和结果类型的。但返回为空的字段类型是需要去设置的,因为Mybatis无法判断null是什么类型!
关于参数我想说一下特殊字符替换和处理的问题(#{}和
而不是#
3.尽量都使用#{}
resultMap
resultMap元素是MyBatis中最重要和最强大的元素。 就是有了这个东西才允许我们节省90%的代码。JDBC需要从ResultSets检索数据,也就是说,因为resultMap在某些情况下Mybatis允许你做的事情,JDBC并不支持。
resultMap定义的主要是一个结果集的映射关系。MyBatis现有的版本只支持查询。resultMap很复杂,在这只能简单列一下它的构成,具体的得我深入了解以后,再单独开一个番外去研究。
resultMap里的元素:
<resultMap>
<constructor> <!-- 配置构造方法 -->
<idArg/>
<arg/>
</constructor>
<id/> <!-- 定义主键 -->
<result/> <!-- 定义普通列的映射关系 -->
<association/> <!-- 一对一级联 -->
<collection/> <!-- 一对多级联 -->
<discriminator> <!-- 鉴别器级联 -->
<case/> <!-- 类似switch/case -->
</discriminator>
</resultMap>