本篇介绍MyBatis,内容皆总结摘抄自waschool,仅作笔记。
MyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
MyBatis功能架构分为三层,如下:
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它的主要目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些共用的东西抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。
优缺点
优点
- 提供xml标签,支持编写动态SQL;
- 提供对象关系映射标签,支持对象关系组件维护;
- 提供映射标签,支持对象与数据库的字段关系映射;
- 解除sql与程序代码的耦合;
- 灵活,MyBatis不会对应用程序或数据库的现有设计强加任何影响。
缺点:
- 编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此;
- SQL语句依赖于数据库,导致数据库移植性差;
- 二级缓存机制不佳。
MyBatis XML映射文件
SQL映射文件有几个顶级元素,按照它们应该被定义的顺序如下表。
元素 | 描述 |
---|---|
cache | 给定命名空间的缓存配置 |
cache-ref | 其他命名空间缓存配置的引用 |
resultMap | 用来描述如何从数据库结果集中来加载对象 |
sql | 可被其他语句引用的可重用语句块 |
insert | 映射插入语句 |
update | 映射更新语句 |
delete | 映射删除语句 |
select | 映射查询语句 |
select
一个简单查询的select元素如下:
<select id="selectById" parameterType="int" resultMap="PermissionResult">
SELECT * from sy_permission t where t.id=#{id}
</select>
这个语句id为selectById,接受一个int类型的参数,并返回一个HashMap类型的对象,其中键是列名,值说结果行中的对应值。而参数符号#{id}告诉MyBatis创建一个预处理语句参数,通过JDBC,这样的一个参数在SQL中会由一个?来标识,并被传递到一个新的预处理语句中。
select元素有很多属性来决定每条语句的作用细节,如下表。
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句 |
parameterType | 传入这条语句的参数类的玩全限定名或别名。这个属性是可选的,因为MyBatis可以通过TypeHandler判断出具体传入语句的参数 |
resultType | 从这条语句返回的期望类型的类的完全限定名或别名。如果是集合类型,则返回的是集合包含的类型而不是集合本身。resultType与resultMap同时只能使用其中一个 |
resultMap | 外部resultMap的命名引用。 |
flushCache | 将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值是false |
useCache | 将其设置为true,将会导致本条语句的结果被二级缓存,对select元素默认值为true |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数 |
fetchSize | 影响驱动程序每次批量返回的结果行数 |
statementType | STATEMENT,PREPARED或CALLABLE的一个,这会让MyBatis分别使用Statement,PrepareStatement或CallableStatement,默认值:PREPARED |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE或SCROLL_INSENSITIVE中的一个,默认值:unset(依赖驱动) |
databaseId | 如果配置了databaseIdProvider,MyBatis会加载所有的不带databaseId或匹配当前databaseId的语句;如果带或者不带的语句都有,则不带的会被忽略 |
resultOrdered | 此设置仅针对嵌套结果select语句适用。如果为true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 |
resultSets | 仅针对多结果集的情况使用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的 |
insert、update和delete
一个简单的insert、update和delete语句如下:
<insert id="insertBatchRoles" parameterType="User">
insert into sy_user_role_ref(user_id,role_id) values (#{id},#{roleId})
</insert>
<update id="disabled">
update sy_car_line
<set>
isDisabled = #{disabled}
</set>
where id=#{id}
</update>
<delete id="deleteById" parameterType="int">
delete from sy_department where id=#{id}
</delete>
insert,update和delete的属性类似,如下表。
属性 | 描述 |
---|---|
id | 命名空间的唯一标识符,可被用来代表这条语句 |
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句)。 |
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 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
sql
这个元素可以用来定义可重用的SQL代码段,可以包含在其他语句中。例如以下代码段:
<sql id="selectFields">
select t.addTime,t.id,t.name,t.remark,t.isDefault,t.targetOrgType,t.creatorOrg_id,t.creator_id,t.modifierOrg_id,t.modifier_id,t.modifiedTime
</sql>
可以重用在这张表的任何查询语句中,例如:
<select id="selectById" resultMap="RoleResult">
<include refid="selectFields" />
from sy_role t where t.id=#{id}
</select>
result maps
resultMap元素是MyBatis中最重要最强大的元素。使用resultMap不需要明确的结果映射。例如使用resultType元素如下:
<select id="selectById" resultType="map">
SELECT id,username
FROM user
WHERE id = #{id}
</select>
上述语句简单作用于所有列被自动映射到HashMap的键上,这由resultType属性指定。但我们想要查询的结果直接转换为POJO,在MyBatis配置文件中加入以下语句并修改上述语句如下就可以实现。
<!-- 放在MyBatis配置文件中 -->
<typeAlias type="com.platformmanager.User" alias="User"/>
<select id="selectById" resultType="User">
SELECT id,username
FROM user
WHERE id = #{id}
</select>
我们还可以使用resultMap属性来达到以上目的,并且还可以解决数据库字段与POJO属性不匹配的问题。
<resultMap type="User" id="UserResult">
<id property="id" column="user_id" />
<result property="username" column="username" />
</resultMap>
<select id="selectById" resultMap="UserResult">
SELECT user_id,username
FROM user
WHERE user_id = #{id}
</select>
以上只是使用resultMap的一个简单示例,事实上有些业务逻辑较复杂的情况下,resultMap也会略显复杂。resultMap元素有很多子元素,其子元素和结构如下:
resultMap
constructor ---类在实例化时, 用来注入结果到构造方法中
idArg ---ID参数,标记结果作为id,可以提高整体效能
arg ---注入到构造方法的一个普通结果
id ---一个id结果,标记结果作为id,可以帮助提高整体效能
result ---注入到字段或JavaBean属性的普通结果
association ---一个复杂的类型关联
嵌入结果映射 ---结果映射自身的关联
discriminator ---使用结果值来决定使用哪个结果映射
case ---基于某些值的结果映射
我们重点介绍association,在某些略复杂的业务场景,我们查询某个POJO时,这个POJO可能依赖另一个POJO。这时我们可以选择通过执行另外一个SQL映射,但对于大型数据集合和列表来说会导致很大的问题。此时我们可以使用association元素使用嵌套结果映射。例如以下是一个名称为ConsignerPrice的POJO(为了方便,get和set方法略去):
public class ConsignerPrice extends BaseEntity {
private static final long serialVersionUID = 1L;
private Integer consignerId;
private Integer creatorId;
private Integer modifierId;
private Consigner consigner;
}
在查询ConsignerPrice时想要将Consigner一起查出来,使用resultMap嵌套关联如下:
<resultMap type="ConsignerPrice" id="ConsignerPriceResult">
<id property="id" column="id" />
<result property="id" column="id" />
<result property="consignerId" column="consigner_id" />
<result property="creatorId" column="creator_id" />
<result property="modifierId" column="modifier_id" />
<result property="addTime" column="addTime" />
<result property="modifiedTime" column="modifiedTime" />
<association property="consigner" javaType="com.tms.pojo.Consigner" resultMap="ConsignerResult"/>
</resultMap>
<resultMap type="Consigner" id="ConsignerResult">
<id property="id" column="c_id" />
<result property="id" column="c_id" />
<result property="name" column="name" />
</resultMap>
动态SQL
if
if语句可以使用在where子句中,使用方法如下:
<select id="findByCarNo"resultType="com.tms.pojo.Car">
SELECT * from sy_car t where carNo = #{carNo}
<if test="carId != null">
AND car_id = carId
</if>
</select>
在此处if语句的功能是:如果没有传入car_id,则只根据carNo查找;如果传入了carId,则根据carNo与carId两个条件查找。
choose when otherwise
有时我们需要用到类似Java语言中switch case语句的控制语句,例如查找车辆信息时如果提供了carId则根据carId查找,如果提供了carNo则根据carNo查找,如果两者都没有提供则返回全部结果。
<select id="findCar" retultType="Car">
SELECT * FROM sy_car WHERE
<choose>
<when test="carId != null">
AND car_id = #{carId}
</when>
<when test="carNo != null">
AND car_no = #{carNo}
</when>
<otherwise>
and 1=1
</otherwise>
</choose>
</select>
但上述语句在没有匹配到任何一个条件的SQL语句如下:
SELECT * FROM sy_car WHERE
AND 1=1;
很明显是错误的,要解决以上问题我们可以使用where元素,where元素只有在一个以上的if条件,满足的情况下才会去插入WHERE子句,而且如果最后的内容是以AND或OR开头,where元素也会将其去除。修改上述映射语句如下:
<select id="findCar" retultType="Car">
SELECT * FROM sy_car
<where>
<when test="carId != null">
AND car_id = #{carId}
</when>
<when test="carNo != null">
AND car_no = #{carNo}
</when>
</where>
</select>
foreach
动态SQL的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建IN条件语句的时候,使用方式如下:
<update id="disabledList" parameterType="java.util.List">
update sy_line set isDisabled = #{disabled}
where id in
<foreach collection="lineIdList" item="lineIdList" index="index" separator="," open="(" close=")">
#{lineIdList}
</foreach>
</update>
为方便理解,上述映射语句对应的Mapper接口方法如下:
int disabledList(@Param("lineIdList") List<Integer> consignerLineIdList, @Param("disabled") boolean disabled);
foreach元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。也允许指定开闭匹配的字符串以及在迭代中间放置分隔符。