映射器
由接口+XML/注解组成;可配置参数、各类SQL语句、存储过程、缓存、级联等,并通过简易的映射规则映射到指定POJO或者其他对象上,能有效消除JDBC底层代码;由于注解对于复杂SQL处理麻烦,自身可读性差,丢失了XML上下文相互引用的实际情况,一般推荐使用XML文件实现;
(1)select
常用元素配置:
–MyBatis自带映射:自动映射(POJO的属性名与SQL列名一致或查询时使用别名as使其一致)和驼峰映射(SQL是xxx_yyy,对应POJO是xxxYyy),由setting元素中的配置autoMappingBehavior和mapUnderscoreToCamelCase控制;一般推荐使用自动映射,因为驼峰映射要求会比较严苛;autoMappingBehavior取值有:
NONE:不进行自动映射;
PARTIAL:默认值,只对没有嵌套结果集进行自动映射;
FULL:对所有结果集进行自动映射;
–resultMap:对于复杂的映射,如主从表格的级联,字段的转换等;
<resultMap type="cn.infocore.pojo.MyStreamer" id="streamerResultMap">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
</resultMap>
<select id="list" resultMap="streamerResultMap">
select * from Streamer
</select>
传递参数:
–单个:#{参数名}
–多个:
—map:业务可读性差,一般不用;
—@Param注解:参数小于等于5个时,推荐使用;
—Java Bean:参数大于5个时,推荐使用;
—混合使用:参数中多个对象,支持EL;
分页参数RowRounds:MyBatis内置了一个专门处理分页的类RowRounds,适用于小数据量的查询,大量数据查询建议使用分页插件,源码如下:
public class RowBounds {
public static final int NO_ROW_OFFSET = 0; //偏移量,从第几行开始读
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE; //限制条数
public static final RowBounds DEFAULT = new RowBounds();
private final int offset;
private final int limit;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
/****getter setter*****/
}
使用:
public List<MyStreamer> list(RowBounds rowBounds);
SQL:没有RowBounds信息,因为它是MyBatis的一个附加参数,会被自动识别从而进行分页;
<select id="list" resultMap="streamer">
select * from Streamer
</select>
(2)insert
MyBatis执行完一条insert语句后,会返回一个整数表示其影响的记录数;
<insert id="insertDiskGroup" parameterType="diskgroup" useGeneratedKeys="true" keyProperty="group.id">
insert into DiskGroup(name,size,clientId) values('${group.name}','${group.size}','${group.clientId}')
</insert>
useGeneratedKeys="true"表示获取自动生成的主键,同时配置keyProperty或keyColumn,表示把主键赋值到POJO的哪个属性中,如存在多个主键,则用逗号“,”隔开;实现自动生成的主键回填的功能;
<insert id="insertDiskGroup" parameterType="diskgroup">
<selectKey keyProperty="group.id" resultType="int" order="BEFORE">
select if (max(group.id)==null,1,max(group.id)+3) from DiskGroup
</selectKey>
insert into DiskGroup(name,size,clientId) values('${group.name}','${group.size}','${group.clientId}')
</insert>
自定义主键增长规则,上例为表记录为空时,id设置为1,否则id设置为当前id+3;返回一个int类型的结果集,order="BEFORE"表示该语句将在定义的SQL之前执行,还可以使用AFTER;
(3)update、delete
update和delete与insert类似,执行完也会返回一个表示影响数据库记录数的整数;
<update id="renameClient">
update Client set name=#{name} where id=#{cId}
</update>
<delete id="removeClient" parameterType="int">
delete from Client where id=#{cId}
</delete>
(4)sql元素
在MyBatis的xml文件里,sql元素作为定义的SQL的一部分,可以实现编写一次,而在其他元素中多次引用的功能;
<sql id="diskGroupCols">name,size,clientId</sql>
<insert id="insertDiskGroup" parameterType="diskgroup" useGeneratedKeys="true" keyProperty="group.id">
insert into DiskGroup(<include refid="diskGroupCols"/>) values('${group.name}','${group.size}','${group.clientId}')
</insert>
sql元素还支持变量传递:在include元素下命名了一个alias的变量,其值是表DiskGroup的别名g,这样sql元素中就可以使用这个变量名了;
<sql id="diskGroupCols">${alias}.name,${alias}.size,${alias}.clientId</sql>
<insert id="insertDiskGroup" parameterType="diskgroup" useGeneratedKeys="true" keyProperty="group.id">
insert into DiskGroup g(<include refid="diskGroupCols"><property name="alias" value="g"/></include>) values('${group.name}','${group.size}','${group.clientId}')
</insert>
(5)参数
-当一些数据库字段返回为null,而MyBatis系统又检测不到使用何种jdbcType进行处理时,会发生异常,这个时候就需要指定typeHandler:
update User set mode=#{mode} where username=#{username,typeHandler=org.apache.ibatis.type.StringTypeHandler}
-如果username是一个没有注册的类型,那么需要自定义typeHandler,然后再指定即可;
-指定jdbcType、javaType、typeHandler:可以支持精度;
#{username,javaType=int,jdbcType=NUMERIC,numericScale=2,typeHandler=MyTypeHandler}
存储过程参数支持:
#{id,mode=IN}:输入参数;
#{id,mode=OUT}:输出参数;
#{id,mode=INOUT}:输入输出参数;
(6)resultMap
定义映射规则、级联的更新、定义类型转换器等;结果集的映射关系;现有MyBatis只支持resultMap的查询,不支持更新或者保存;
resultMap元素构成如下:
<resultMap>
<constructor> //配置构造方法,MyBatis会使用此构造方法构造POJO
<idArg property="" column="" javaType="" jdbcType="" typeHandler=""/>
<arg/>
</constructor>
<id/> //主键,允许多个主键,称为联合主键
//配置POJO到SQL列名的映射关系
<result property="" column="" javaType="" jdbcType="" typeHandler=""/>
<association/>
<collection/>
<discriminator>
<case/>
</discriminator>
</resultMap>
–存储结果集
可以使用map和POJO存储查询结果集,但由于map的可读性差,因此推荐POJO;使用方法,前文可见,就不再重复了;
(7)级联
级联可以十分快速的获取关联数据,但过多也会增加系统的复杂度和耦合度,从而使系统性能降低;因此当级联超过3层时,不建议使用,适用于对象关系比较简单,级联不多的场景;
MyBatis级联分3种:
–鉴别器(discriminator):根据某些条件采用具体实现类级联的方案;
–一对一(association):两个对象的关系是一对一;
–一对多(collection):两个对象的关系是一对多;
不支持多对多级联,因为过于复杂,可以通过两个一对多级联实现;
<resultMap type="cloudaccount" id="CloudAccountMap">
<association property="cloudserver" column="acId" select="cn.infocore.pojo.MyCloudServerDao.getServer" />
</resultMap>
一对一,property属性代表映射到指定POJO属性上;select配置命名空间+SQL id;column代表SQL的列,会作为参数传递给select指定的SQL,如果多个参数,用逗号隔开;
<resultMap type="cloudaccount" id="CloudAccountMap">
<collection property="servers" column="acId" select="cn.infocore.pojo.MyCloudServerDao.listByAcId" />
</resultMap>
一对多,select配置命名空间+SQL id;column代表SQL的列,会作为参数传递给select指定的SQL,如果多个参数,用逗号隔开;结果返回给property指定的POJO属性servers;
<discriminator javaType="long" cloumn="sex">
<case value="1" resultMap="maleMapper"/>
<case value="2" resultMap="femaleMapper"/>
</discriminator>
鉴别器,column代表使用哪个字段进行鉴别,子元素case用于区分,resultMap属性表示采用哪个ResultMap去映射;
通过级联设置,默认情况下会执行所有关联SQL,包括获取当前信息不需要的SQL,导致数据库资源的损耗和系统性能的下降;这个就出现了N+1问题,即当前N个关联关系完成级联,再加入一个级联关系,就变成N+1个级联,所有的级联SQL都会执行;针对这个问题,就提出了延迟加载的功能;
延迟加载配置:settings
-lazyLoadingEnabled :默认false,延迟加载的全局开关,true表示所有关联对象都会延迟加载,在特定关联关系中,可通过fetchType覆盖;实体的关系对象属于另一个层级;
-aggressiveLazyLoading:在3.4.1版本后,默认false;按需加载,true表示开启了层级开关的延迟加载;鉴别器与实体在同一个层级;
-fetchType:适用于association和collection,eager值表示获得当前POJO后立即加载对应的数据;lazy值表示获得当前POJO后延迟加载对应的数据;fetchType会忽略上述两个全局配置项;
SQL表连接基础上的级联:
-每一个级联元素中的主键与POJO实体配置的主键一一对应,形成级联;
-association通过javaType的定义声明实体映射,而collection是使用ofType声明;
-discriminator元素定义使用何种具体的resultMap进行级联;
<collection property="employeeTaskList" ofType="cn.infocore.pojo.EmployeeTask" column="id">
<id column="et_id" property="id" />
<result column="id" property="empId" />
<result column="task_name" property="taskName" />
<result column="note" property="note" />
<association property="task" javaType="cn.infocore.pojo.Task" column="et_task_id">
<id column="t_id" property="id" /> //实体id与et_task_id一一对应
<result column="t_title" property="title" />
<result column="t_context" property="context" />
<result column="t_note" property="note" />
</association>
</collection>
<discriminator javaType="int" column="sex"> //通过sex列判断
<case value="1" resultMap="maleHealthFormMapper" />
<case value="2" resultMap="femaleHealthFormMapper" />
</discriminator>
(8)缓存
MyBatis默认开启一级缓存,这个缓存不需要POJO对象可序列化(实现Serializable接口);
1)一级缓存:SqlSession上的缓存,通过同一个SqlSession对象进行数据获取,第一次查询后会缓存数据,第二次只要SQL与参数没变,且缓存未超时和未声明需要刷新时,就会从缓存读取数据;不同SqlSession对象中的缓存是不能共享的;
2)二级缓存:SqlSessionFactory上的缓存,即对<mapper>
上定义的namespace命名空间内的所有select元素SQL查询的结果进行缓存,而其中的insert、delete、update语句在操作时会刷新缓存;可以共享不同SqlSession对象上的缓存,二级缓存需要手动开启;在映射文件里加上<cache/>
即可;POJO对象需要可序列化;
Cache元素配置项:
public interface Cache {
//获取缓存ID
String getId();
//保存对象,key为键,value为值
void putObject(Object key, Object value);
//获取缓存数据,key为键
Object getObject(Object key);
//删除缓存,key为键
Object removeObject(Object key);
//清除缓存
void clear();
//获取缓存大小
int getSize();
//获取读写锁,需要考虑多线程的场景
ReadWriteLock getReadWriteLock();
}
默认缓存配置:flushCache表示是否刷新缓存,useCache是否使用缓存;
<select ... flushCache="false" useCache="true"/>
<update... flushCache="true"/>
<insert... flushCache="true"/>
<delete... flushCache="true"/>
引用其他映射器中cache元素的配置:
<cache-ref namespace="cn.infocore.mappser.RoleMapper"/>
重点再讲一下sqlSession.close()和sqlSession.commit():
1)执行select操作,会保存一级缓存,再调用close,会将其一级缓存的数据放进二级缓存中,此时一级缓存随着SqlSession的关闭也就不存在了;
2)执行select缓存,会保存一级缓存,再调用commit,会将其中的一级缓存的数据放进二级缓存中,并清空一级缓存;
3)执行更新操作(update、delete、insert)时,同时不调用commit或close,这时只会清空其自身的一级缓存,对二级缓存没有影响;
4)执行更新操作(update、delete、insert)时,调用commit,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果);
5)执行更新操作(update、delete、insert)时,调用close,只会清空其自身的一级缓存(执行更新操作的效果),对二级缓存没影响了;当autoCommit为true时,会清空二级缓存;
(9)存储过程
数据库预先编译好,放在数据库内存中的一个程序片段,性能高,可重复使用;
参数类型:输入参数、输出参数、输入输出参数;
返回结果:整形、字符型OUT、INOUT参数等简易类型、游标类型;