【MyBatis】
目录
一. 配置元素
1. select元素
2. insert元素
3. sql元素
4. resultMap元素
5. resultMap元素中的级联
(代码github)
一、映射器的配置元素
元素名称 | 描述 | 备注 |
---|---|---|
select | 查询语句 | 可以自定义参数,返回结果集 |
insert | 插入语句 | 执行后返回一个整数,代表插入的条数 |
update | 更新语句 | 执行后返回一个整数,代表更新的条数 |
delete | 删除语句 | 执行后返回一个整数,代表删除的条数 |
sql | 允许定义一部分SQL,然后在各个地方引用它 | 比如,一张表列名,一次定义,可以在多个SQL语句中使用 |
resultMap | 用来描述从数据库结果集中来加载对象 | 它提供映射规则 |
cache | 给定命名空间的缓存配置 | |
cache-ref | 其它命名空间缓存配置的引用 |
1. select元素----查询语句
元素 | 说明 | 备注 |
---|---|---|
id | 与Mapper的命名空间组合起来是唯一的,供MyBatis调用 | 如果命名空间和id结合起来不唯一,将抛出异常 |
parameterType | 可以给出类的全类名,也可以给出别名,别名必须是MyBatis内部定义或者自定义 | 可以选择Java Bean、Map等简单的参数类型传递给SQL |
resultType | 定义类的全路径,在允许自动匹配的情况下,结果集将通过Java Bean的规范映射,不能和resultMap同时使用 | 常用的参数之一,比如统计总条数时可以把它的值设置为int |
resultMap | 它是映射集的引用,能提供自定义映射规则的机会 | 可以配置映射规则、级联、typeHandler等 |
flushCache | 在调用SQL后,是否要求MyBatis清空之前查询本地缓存和二级缓存 | 取值为布尔值,true/false,默认值为false |
useCache | 启动二级缓存的开关,是否要求MyBatis将此次结果缓存 | 取值为布尔值,默认为true |
1.1 简单的select元素应用
RoleMapper接口
Integer countUserByFirstName(String firstName);
RoleMapper.xml
<select id="countUserByFirstName" parameterType="string" resultType="int">select count(*) total from t_userwhere user_name like concat(#{firstName}, '%')
</select>
1.2 传递多个参数
-
使用map接口传递参数(不推荐)
首先,map是一个键值对应的集合,使用者要通过阅读它的键,才能明了其作用
其次,使用map不能限定其传递的数据类型,业务性质不强,可读性差 -
使用注解传递多个参数
RoleMapperList<Role> findRoleByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
RoleMapper.xml
<select id="findRoleByAnnotation" resultType="role">select id,role_name as roleNam, note from t_rolewhere role_name like concat('%', #{roleName}, '%')and note like concat('%', #{note}, '%') </select>
-
通过Java Bean传递多个参数
RoleMapperList<Role> findRolesByBean(RoleParams roleParams);
RoleMapper.xml
<select id="findRolesByBean" parameterType="cn.whc.March_30.entity.RoleParams" resultType="role">select id,role_name as roleNam, note from t_rolewhere role_name like concat('%', #{roleName}, '%')and note like concat('%', #{note}, '%')</select>
-
混合使用
例子,查询一个角色,通过角色名称和备注进行查询,同此还支持分页public class PageParams {private int start;private int limit;public int getStart() {return start;}public void setStart(int start) {this.start = start;}public int getLimit() {return limit;}public void setLimit(int limit) {this.limit = limit;} }
List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParams pageParam);
<select id="findByMix" resultType="role">select id,role_name as roleName, note from t_rolewhere role_name likeconcat ('%', #{params.roleName}, '%')and note like concat('%', #{params.note}, '%')limit #{page.start}, #{page.limit}</select>
1.3 使用resultMap映射结果集
<resultMap id="roleMap" type="cn.whc.March_30.entity.Role"><!--id代表主键, property代表POJO的属性名称, column代表SQL的列名--><id property="id" column="id"/><result property="roleName" column="role_name"/><result property="note" column="note"/></resultMap>
2.insert元素----插入语句
元素 | 说明 | 备注 |
---|---|---|
id | SQL编号,用来标识这条SQL | 命名空间+id+databaseId唯一,否则将抛出异常 |
parameterType | 可以给出类的全类名,也可以给出别名,别名必须是MyBatis内部定义或者自定义 | 可以选择Java Bean、Map等简单的参数类型传递给SQL |
flushCache | 是否刷新缓存,可以配置true/false,为true时,插入时会刷新一级和二级缓存,否则不刷新 | 取值为布尔值,true/false,默认值为true |
useGeneratedKeys | 是否启动JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键。 | 默认值为false |
keyColumn | 通过生成的键值设置表中的列名,这个设置仅在某些数据库中是必须的,当主键列不是表中的第一列时需要设置.如果是复合主键,需要把每一个名称用逗号隔开 | 不能和keyProperty连用 |
keyProperty | 唯一标记一个属性,MyBatis会通过getGeneratedKeys的返回值,或者通过insert语句的selectKey子元素设置它的键值。如果是复合主键,要把每一个名称用逗号隔开 | 默认值为unset。不能和keyColumn连用 |
主键回填例子
<insert id="insertRole" parameterType="role" useGeneratedKeys="true" keyProperty="id">insert into t_role(role_name,note) values (#{roleName}, #{note})
</insert>
keyProperty代表将用哪个POJO的属性去匹配这个主键,这里是id,会用数据库生成的主键去赋值给这个POJO
3.sql元素
<sql id="roleCols">id,role_name,note
</sql><select id="getRole" parameterType="long" resultMap="roleMap">select <include refid="roleCols"/> from t_role where id = #{id}</select>
4.resultMap元素
作用: 定义映射规则、级联的更新、定制类型转换器等。resultMap定义的主要是一个结果集的映射关系,也就是SQL到Java Bean的映射关系定义,它也支持级联等特性。不支持更新或者保存
使用POJO存储结果集
<resultMap id="roleMap" type="cn.whc.March_30.entity.Role"><!--id代表主键, property代表POJO的属性名称, column代表SQL的列名--><id property="id" column="id"/><result property="roleName" column="role_name"/><result property="note" column="note"/>
</resultMap>
5.resultMap元素中的级联
级联是resultMap中的配置,分为三种- 鉴别器(discriminator):它是一个根据某些条件决定采用具体实现类级联的方案,比如体检表要根据性别去区分
- 一对一(association):比如学生证和学生就是一种一对一的级联,雇员和工牌表也是一种一对一的级联
- 一对多(collection):比如班主任和学生就是一种一对多的级联
例子
- 以雇员表为中心
- 雇员表和工牌表是一对一的级联关系
- 雇员表和员工任务表是一对多的级联关系
- 员工任务表和任务表是一对一的级联关系
- 每个雇员都会有一个体检表,随着雇员表字段性别取值的不同,会有不同的关联表
一对一级联:雇员表通过id与工牌表(emp_id)关联
<!--工卡表信息--><association property="workCard" column="id" select="cn.whc.March_30.mapper.WorkCardMapper.getWorkCardByEmpId"/>
解析:雇员表通过编号(id)和工牌表(emp_id)关联,使用association元素,然后将结果返回给雇员POJO的属性workCard
一对一级联:雇员任务表通过任务编号(task_id)和任务表示关联
雇员任务表POJO
EmployeeTaskMapper.xml(雇员任务表的映射文件)
<mapper namespace="cn.whc.March_30.mapper.EmployeeTaskMapper"><resultMap id="EmployeeTaskMap" type="cn.whc.March_30.pojo.EmployeeTask"><id column="id" property="id"/><result column="emp_id" property="empId"/><result column="task_name" property="taskName"/><result column="note" property="note"/><!--雇员任务表和任务编号(task_id)和任务表示一对一级联关联--><!--property属性代表映射到POJO属性上--><!--分步查询:select配置是命名空间+SQL id的形式,可以指向对应Mapper的SQL,MyBatis就会通过对应的SQL将数据查询回来column代表雇员任务表SQL的列,用作参数传递给select属性指定的SQL,如果是多个参数,则需要用逗号隔开--><association property="task" column="task_id" select="cn.whc.March_30.mapper.TaskMapper.getTask"/></resultMap><select id="getEmployeeTaskByEmpId" resultMap="EmployeeTaskMap">select id,emp_id,task_name,task_id,note from t_employee_task where emp_id = #{empId}</select>
</mapper>
解析:雇员任务表通过任务编号(task_id)和任务表(id)表示关联,使用association元素
一对多级联:雇员通过雇员编号(id)和雇员任务表关联
<!--雇员任务表信息--><!--一对多级联,其select元素指向SQL,将通过雇员表中column指定的SQL字段作为参数进行传递然后将结果返回给雇员POJO的属性employeeTaskList--><collection property="employeeTaskList" column="id" select="cn.whc.March_30.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" />
解析:collection元素:一对多级联,其select指向sql,通过column指定的SQL字段作为参数进行传递,然后将结果返回给雇员POJO的属性employeeTaskList
鉴别器:雇员表和体检表
SexEnum枚举类以及自定义SexTypeHandler(以后再补充,后续放在github)
<resultMap id="employee" type="cn.whc.March_30.pojo.Employee"><id column="id" property="id"/><result column="real_name" property="realName"/><result column="sex" property="sex" typeHandler="cn.whc.March_30.typeHandler.SexTypeHandler"/><result column="birthday" property="birthday"/><result column="mobile" property="mobile"/><result column="email" property="email"/><result column="position" property="position"/><result column="note" property="note"/><!--工卡表信息--><association property="workCard" column="id" select="cn.whc.March_30.mapper.WorkCardMapper.getWorkCardByEmpId"/><!--雇员任务表信息--><!--一对多级联,其select元素指向SQL,将通过column指定的SQL字段作为参数进行传递然后将结果返回给雇员POJO的属性employeeTaskList--><collection property="employeeTaskList" column="id" select="cn.whc.March_30.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" /><!--鉴别器,它的属性column代表使用哪个字段进行鉴别,这里是sex,子元素case用于区分,1则对应男,2则对应女--><discriminator javaType="long" column="sex"><case value="1" resultMap="maleHealthFormMapper"/><case value="0" resultMap="femaleHealthFormMapper"/></discriminator>
</resultMap><!--id为employee的resultMap 被femaleHealthFormMapper通过extends元素继承--><resultMap id="femaleHealthFormMapper" type="cn.whc.March_30.pojo.FemaleEmployee" extends="employee"><association property="femaleHealthForm" column="id" select="cn.whc.March_30.mapper.FemaleHealthFormMapper.getFemaleHealthForm"/></resultMap><resultMap id="maleHealthFormMapper" type="cn.whc.March_30.pojo.MaleEmployee" extends="employee"><association property="maleHealthForm" column="id" select="cn.whc.March_30.mapper.MaleHealthFormMapper.getMaleHealthForm"/></resultMap>
解析:discriminator元素:鉴别器,它的属性column代表使用哪个字段进行鉴别,这里的是sex,而它的子元素case,用于区分,类似于java的switch…case…语句。而resultMap属性表示采用哪个ResultMap去映射,比如sex=0,则使用femaleHealthFormMapper进行映射.
对于雇员体检表而言,id为employee的resultMap,被maleHealthFormMapper和femaleHealthFormMapper通过extends元素继承。
测试
N+1问题
上面的级联日志可以看出所有级联都加载出来了。但是会引发性能问题,比如作为一个雇员的管理者,只想要员工信息和员工任务信息,对于体检表和工牌的信息就是多余的。所以执行了几条毫无用处的SQL,导致数据库资源的损耗和系统性能的下降。
解决方法: Mybatis提供了延迟加载功能,一次性把常用的级联数据通过SQL直接查询出来,对于不常用的级联数据则等待要用时才取出
延迟加载
settings配置项
配置项 | 作用 | 配置选项说明 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。在特定关联关系中,可通过设置fetchType属性来覆盖该项的开关状态 | true或false | fasle |
aggressiveLazyLoading | 当启动时,对任意延迟属性的调用会时带有延迟加载属性的对象完整加载;反之,则每种属性按需加载 | true或false | 3.4.1版本后默认false |
lazyLoadingEnabled是一个开关,决定开不开启延迟加载,默认值为false,则不开启延迟加载。正如上面例子,当获取员工信息时,所有信息都被加载进来。
测试1:lazyLoadingEnabled和aggressiveLazyLoading都为true
<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="true"/>
</settings>
@Testpublic void test() {try {Logger logger = Logger.getLogger(AppTest.class);sqlSession = SqlSessionFactoryUtils.openSqlSession();EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);// 获取编号为1的雇员信息,但设置aggressiveLazyLoading为true后,由于任务表只与雇员任务表关联,没有被加载Employee employee = employeeMapper.getEmployee(1L);logger.info(employee.getBirthday());} catch (Exception e) {e.printStackTrace();} finally {if (sqlSession != null) {sqlSession.close();}}}
测试2:lazyLoadingEnabled为true;aggressiveLazyLoading为false
开启全局延迟,层级加载失效
<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>
测试3:fetchType属性
上面的两种测试都是全局性配置,并不能解决我们的需求,我们希望加载雇员信息时,只加载雇员任务信息,因为层级加载会把工牌信息也加载进来。在MyBatis中使用fetchType属性,可以处理全局定义无法处理的问题
<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/></settings>
<collection property="employeeTaskList" column="id" select="cn.whc.March_30.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" fetchType="eager" />
fetchType出现在级联元素(association、collection,注意:discriminator没有这个属性可配置)
属性值
- eager:获得当前POJO后立即加载对应的数据
- lazy:获得当前POJO后延迟加载对应的数据
另一种级联:采用连缀
SQL比较复杂;所需要的配置比之前复杂得多;一次性将所有的数据取出会造成内存的浪费。适用于比较简单且关联关系不多的场景下。
==============================================================================
TODO
这一节的内容还是挺多的,还没写完,后面的放在另一篇文章,这两天上课+晚睡觉,补午觉都到下午3 4点了,另外我的csdn网页版有时候就打不开(估计dns域名出问题了,坑了n次)
后面打算写一下idea命令行如何将项目推送到github中