映射器的主要元素
select元素
概述
select元素的属性说明
元素 | 说明 | 备注 |
---|---|---|
id | 它和Mapper的命名空间组合起来是唯一的,刚好对应dao中方法的全类名+方法名 | 不唯一会抛出异常 |
parameterType | 可以是全类名,也可以是别名 | |
parameterMap | 即将废弃 | |
resultType | 可以是全类名,也可以是别名,但不与resultMap同时出现 | |
resultMap | 自定义的映射集 | 可以配置映射规则,级联,typeHandler |
flushCache | 是否清空缓存 | true/false |
useCache | 是否启用二级缓存 | true/false |
timeout | 设置超时参数,超时即抛出异常 | 默认值为数据库厂商所设 |
fetchSize | 返回条数 | 默认值为数据库厂商所设 |
statementType | 指定JDBC的statement方式,可选参数STATEMENT,PREPEARED,CALLABLESTATEMENT | |
resultSetType | 对JDBC的resultSet而言,可取值包括FORWARD_ONLY(游标允许向前访问)、SCROLL_SENSITIVE(双向滚动,但不及时更新,若数据库里的数据修改过,并不在resultSet中反映出来)、SCROLL_INSENSITIVE(双向滚动,及时更新,若数据库里的数据修改过,会在resultSet中反映出来) | |
databaseId | 参考?? | |
resultOrdered | 这个设置仅适用于嵌套结果集select语句。如果为true,就是假设包含了嵌套结果集或者是分组了,当返回一个主结果行的时候,就不能对前面结果集的引用。避免了在获取嵌套的结果集的时候内存不够。 | |
resultSets | 顾名思义,列出多个结果集,每个名称之间用逗号隔开 | 很少使用 |
简易数据类型的例子
<mapper namespace="com.yan.dao.RoleMapper" >
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from role
where id = #{id,jdbcType=INTEGER}
</select>
</mapper>
package com.yan.dao;
import com.yan.po.Role;
public interface RoleMapper {
Role selectByPrimaryKey(Integer id);
}
可以看到,xml的namespace与接口的全类名对应,id与方法名对应。
自动映射
由MyBatis-config.xml中的settings的子元素autoMappingBehavior决定,指定 MyBatis 如何自动映射列到字段/ 属性。NONE不使用自动映射,PARTIAL 只会自动映射简单, 没有嵌套的结果。FULL 会自动映射任意复杂的结果(嵌套的或其他情况) 。默认值为PARTIAL。
传递多个参数
使用Map类型传递
mapper.xml
<select id="selectByUnionKey" resultMap="BaseResultMap" parameterType="java.util.Map">
select
<include refid="Base_Column_List" />
from role
where id = #{id}
and roleName = #{roleName}
</select>
mapper.java
Role selectByUnionKey(Map<String,Object> map);
test.java
Role r =new Role();
Map<String, Object> map=new HashMap<String, Object>();
map.put("id", 1);
map.put("roleName", "超级管理员");
r=roleMapper.selectByUnionKey(map);
map传参的方式是硬编码,不推荐使用。
使用注解方式传递
直接在接口中方法参数前添加@Param(“自定义名称”),在mapper.xml中同样使用“自定义名称”接收即可,需要注意的是,同样遵循xml中id属性的限制,即它和Mapper的命名空间组合起来是唯一的,刚好对应dao中方法的全类名+方法名,不唯一会抛出异常。因为在测试中,接口中只要参数不同(map或组合参数),可以使用同样的方法名selectByUnionKey,而MyBatis不允许,因此以严格的一方为准。
// Role selectByUnionKey(Map<String,Object> map);
Role selectByUnionKey(@Param("roleName") String roleName,@Param("id") Integer id);
<!-- 注意,此时parameterType属性已删除 -->
<select id="selectByUnionKey" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from role
where id = #{id}
and roleName = #{roleName}
</select>
总结
- 使用map方式传参可读性和可维护性差,不推荐使用;
- 当参数数量<=5时,使用注解方式更有效率;
- 当参数>时,使用JavaBean更合理。
使用resultMap映射结果集
适合用于级联等复杂操作,以后讨论。基本的用法:
<resultMap id="BaseResultMap" type="com.yan.po.Role" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="rolename" property="roleName" jdbcType="VARCHAR" />
<result column="note" property="note" typeHandler="com.yan.typeHandler.IStringTypeHandler" />
</resultMap>
<sql id="Base_Column_List" >
id, rolename, note
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from role
where id = #{id,jdbcType=INTEGER}
</select>
insert元素
概述
比select元素的属性少,不同的有:
属性名称 | 描述 | 备注 |
---|---|---|
keyProperty | 指定主键,不与keyColumn同时使用 | |
keyColumn | 指定主键,不与keyProperty同时使用 | |
useGeneratedKeys | 使用JDBC的getGeneratedKeys方法获取由数据库生成的主键 | 默认为false |
主键回填和自定义
在由数据库生成主键的情况中,我们往往需要在插入数据后获取主键,以便于以后的操作 ,而MyBatis提供了实现的方法:
<!--<insert id="insert" parameterType="com.yan.po.Role" keyColumn="id" useGeneratedKeys="true">-->
<insert id="insert" parameterType="com.yan.po.Role" keyProperty="id" useGeneratedKeys="true">
insert into role (id, rolename, note
)
values (#{id,jdbcType=INTEGER}, #{roleName,jdbcType=VARCHAR}, #{note,jdbcType=VARCHAR}
)
</insert>
这样插入完成紧跟着就可以通过主键查询了。
另一个体现MyBatis灵活性的方法是通过子元素selectKey来自定义主键增长方式, 前提是去掉数据库的自增功能但保留主键:
<insert id="insert" parameterType="com.yan.po.Role">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select if(max(id) is null,1,max(id)+2) as id from role
</selectKey>
insert into role (id, rolename, note)
values (
#{id,jdbcType=INTEGER},
#{roleName,jdbcType=VARCHAR},
#{note,jdbcType=VARCHAR}
)
</insert>
总结
- selectKey中的keyProperty属性值需要对应POJO中set方法名,否则会出现No setter found for the keyProperty异常;
- selectKey中keyColumn属性值不起作用,不会用;
- 一般order属性设置为order=”BEFORE”,否则出现异常,AFTER的适用范围目前不清楚;
- 使用了selectKey后,insert中不需要再配置keyProperty和useGeneratedKeys等属性,配了也不起作用。
update元素和delete元素
sql元素
sql元素可以定义sql语句片段,使用include可以直接引用:
<sql id="Base_Column_List" >
id, rolename, note
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from role
where id = #{id,jdbcType=INTEGER}
</select>
参数
略
参数配置
略
存储过程支持
略
特殊字符串替换和处理(#和$)
代表传递普通参数,而$可代表数据列等,比如:
columns=”col1,col2,col3…”;
select ${columns} from t_tableName;
resultMap结果映射集
resultMap元素构成
<resultMap type="" id="">
<constructor>
<idArg/>
<arg/>
</constructor>
<id/>
<result/>
<association property=""></association>
<collection property=""></collection>
<discriminator javaType="">
<case value=""></case>
</discriminator>
</resultMap>
restructor元素用于配置构造方法。一个POJO可能不存在没有参数的构造方法,这个时候我们就可以使用constructor进行配置。假设角色类RoleBean不存在没有参数的构造方法,它的构造方法声明为public RoleBean(Integer id,String roleName),那么我们需要配置这个结果集,代码如下:
<resultMap type="" id="">
<constructor>
<idArg column="id" javaType="int"/>
<arg column="role_name" javaType="string"/>
</constructor>
...
</resultMap>
使用map存储结果集
一般而言,任何的select语句都可以使用map存储:
<select id="" parameterType="" resultType="">
select id,name from role where note like concat('%',#{note},'%')
</select>
使用map原则上石可以匹配所有结果集的,但是使用map接口就意味着可读性的降低,所以不推荐。
使用POJO存储结果集
<resultMap id="BaseResultMap" type="com.yan.po.Role" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="rolename" property="roleName" jdbcType="VARCHAR" />
<result column="note" property="note" typeHandler="com.yan.typeHandler.IStringTypeHandler" />
</resultMap>
<sql id="Base_Column_List" >
id, rolename, note
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select <include refid="Base_Column_List" /> from role where id = #{id,jdbcType=INTEGER}
</select>
级联
collection一对多级联
discriminator鉴别器级联
性能分析和N+1问题
级联的优势是能够方便快捷地获取数据,但是当多层级联时,会造成复杂度增加,不利于他人理解和维护的问题,而且有时候我们并不需要获取所有的数据。全取出来会造成SQL执行过多导致性能下降,因此我们要考虑延迟加载的问题。
延迟加载
主要在MyBatis的配置文件中的settings元素中设置,这意味着全局延迟加载的配置:
<settings>
<setting name="logImpl" value="LOG4J"></setting>
<setting name="lazyLoadingEnabled" value="true"></setting>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
其中lazyLoadingEnabled标志延迟加载,比如有一个父类表和一个子类表关联,当不需要子类表数据时先不加载它,aggressiveLazyLoading表示同级延迟加载,表示两个不存在父子类关系的表关联时,是否加载用不到的数据,比如A、B两表关联,但我只需要A表的数据,若aggressiveLazyLoading设置为false,则不会加载B表数据。
局部延迟加载
MyBatis也提供了局部延迟加载的功能,可以在association和collection元素上加入属性值fetchType就可以了,它有两个取值范围,即eager和lazy。它的默认值取决于在settings的配置,如果没有配置,就是eager,一旦配置了,也会被局部配置所覆盖。
其他级联
在sql语句中尽量使用左连接,但问题是resultMap得跟着变,而且复杂度呈正相关关系,不一定适用于性能要求较高的项目。
缓存
目前流行的缓存服务有MongoDB,Redis和Ehcache等,缓存是内存上保存的数据,无需再从硬盘上读取,因此具备快速读取和使用的特点,如果缓存命中率高,可以极大提高系统性能,否则没有什么意义,因此使用缓存的关键在于存储内容访问的命中率。
系统缓存
MyBatis支持缓存,在没有配置的情况下,只会开启一级缓存–就是只相对于同一个SqlSession而言。new一个就得从新读取了。
二级缓存是在SqlSessionFactory层面共享的,new一个SqlSession也可以被调用。
需要在***mapper.xml中配置,添加<cache/>
即可,但是这样太笼统,这意味着:
所有的select语句会被缓存,增删改以后还会刷新缓存,使用默认的Least Resently Used(LRU,最近最少使用的)算法来回收;根据时间表,比如No Flush Interval(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用;
缓存会被视为read/write的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
最重要的是POJO要实现serializable接口,否则会报异常。
细化cache元素
<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true"/>
属性说明:
eviction:代表缓存回收策略,目前MyBatis支持以下策略:
1.LRU:最近最少使用,移除最长时间不用的对象。
2.FIFO:先进先出,按对象进入缓存的顺序来移除。
3.SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象。
4.WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象,这里采用的是LRU,移除最长时间不用的对象。
flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
1.size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是1024个对象
2.readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,它的默认值为false,不允许我们修改。
自定义缓存
系统缓存是应用本地机器上的本地缓存,但是在大型服务器上,会使用各类不同的缓存服务器,这个时候我们可以定制缓存,比如现在十分流行的Redis缓存。我们需要实现MyBatis提供的接口org.apache.ibatis.cache.Cache,缓存接口如下:
//清空缓存
public void clear() {
}
//获取缓存编号
public String getId() {
return null;
}
//通过key获取缓存对象
public Object getObject(Object key) {
return null;
}
//获取缓存的读写锁
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
//获取缓存对象大小
@Override
public int getSize() {
return 0;
}
//保存key值缓存对象
@Override
public void putObject(Object key, Object value) {
}
//通过key删除缓存对象
@Override
public Object removeObject(Object arg0) {
return null;
}
我们需要实现这个接口并且在cache标签上添加type属性,比如我的实现类路径是com.yan.mybatis.cache.MyCache,那么这就是type的值。
另外我们可以在这个实现类中添加额外的属性,比如host,设置setHost方法,并且在cache标签中设置property子元素,这样它初始化就会被调用。
<cache type="com.yan.mybatis.cache.MyCache">
<property name="host" value="localhost"/>
</cache>
我们在映射器上可以配置insert、delete、select、update元素,对应增删改查。也可以配置SQL层面上的缓存规则,来决定他们是否需要使用或刷新缓存,我们往往是根据两个属性:useCache和flushCache来完成,其中useCache表示是否使用缓存,flushCache表示变更后是否需要刷新缓存。
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>