映射器的配置元素有select/insert/update/delete/sql/resultMap/cache/cache-ref八个常用的,parameterMap基本不用,也不建议使用。
MyBatis3官方文档:http://www.mybatis.org/mybatis-3/zh/configuration.html
一、select元素
在工作中用的最多的是id,parameterType、resultType、resultMap。如果要设置缓存,还会使用到flushCache、useCache。其它的都不常用。
1.自动映射和驼峰映射
在mybatis-config.xml中settings元素中,autoMapping代表自动映射,默认值PARTIAL;mapUnderscoreToCamelCase代表驼峰映射,默认值false。
2.传递多个参数
(1)使用map传递参数。可读性差,维护困难,摒弃。
(2)@Param注解传递多个参数。参数小于等于5个,可使用。比用JavaBean更好。
(3)参数大于5个时,使用JavaBean方式。
(4)混合使用。
public interface RoleDao {
public List<Role> findRolesByMap(Map<String, Object> parameterMap);
public List<Role> findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
public List<Role> findRolesByBean(RoleParams roleParam);
public List<Role> findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParams pageParams);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssmr.chapter05.dao.RoleDao">
<select id="findRolesByMap" parameterType="map" resultType="role">
select id, role_name as roleName, note
from t_role
where role_name like concat('%', #{roleName}, '%')
and note like concat('%', #{note}, '%')
</select>
<select id="findRolesByAnnotation" resultType="role">
select id,role_name as roleName, note
from t_role
where role_name like concat('%', #{roleName}, '%')
and note like concat('%', #{note}, '%')
</select>
<select id="findRolesByBean" parameterType="com.ssm.chapter5.param.RoleParams"
resultType="role">
select id, role_name as roleName, note
from t_role
where role_name like concat('%', #{roleName}, '%')
and note like concat('%',#{note}, '%')
</select>
<select id="findByMix" resultType="role">
select id, role_name as roleName, note
from t_role
where role_name like concat('%',#{params.roleName}, '%')
and note like concat('%', #{params.note}, '%')
limit #{page.start}, #{page.limit}
</select>
</mapper>
3.使用resultMap映射结果集
id是标识,type代表哪个类作为其映射的类,可以是别名或全限定名。
子元素id代表resultMap的主键,而result代表其属性。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssmr.chapter05.dao.RoleDao">
<resultMap id="roleMap" type="role">
<id property="id" column="id" />
<result property="roleName" column="role_name" />
<result property="note" column="note" />
</resultMap>
<select id="getRoleUseResultMap" parameterType="long" resultMap="roleMap">
select id, role_name, note
from t_role
where id = #{id}
</select>
</mapper>
4.分页参数RowBounds
只需给接口增加一个RowBounds参数即可。
public interface RoleDao {
public List<Role> findRolesByRowBounds(@Param("roleName") String rolename, @Param("note") String note, RowBounds rowBounds);
}
<select id="findByRowBounds" resultType="role">
select id, role_name as roleName, note
from t_role
where role_name like concat('%',#{params.roleName}, '%')
and note like concat('%', #{params.note}, '%')
limit #{page.start}, #{page.limit}
</select>
测试:
public class Main {
public static void main(String[] args) {
testRowBounds();
}
public static void testRowBounds(){
SqlSession sqlSession = null;
try {
Logger log = Logger.getLogger(Main.class);
sqlSession = SqlSessionFactoryUtil.openSqlSession();
RoleDao roleDao = sqlSession.getMapper(RoleDao.class);
//offset属性是偏移量,即从第几行开始读取记录
//limit是限制条数
RowBounds rowBounds = new RowBounds(0, 20);
List<Role> roleList = roleDao.findRolesByRowBounds("role_name", "note", rowBounds);
System.out.println(roleList.size());
} catch (Exception e) {
e.printStackTrace();
}finally {
if(sqlSession != null){
sqlSession.close();
}
}
}
}
二、insert元素
<insert id="insertRole" parameterType="role">
insert into t_role(role_name, note)
values(#{roleName},#{note})
</insert>
主键回填:
useGeneratedKeys:获取数据库生成的主键,默认false。代表采用JDBC的Statement对象的getGeneratedKeys方法返回主键。
keyProperty代表将用哪个POJO的属性去匹配这个主键。
<insert id="insertRole" parameterType="role" useGeneratedKeys="true" keyProperty="id">
insert into t_role(role_name, note)
values(#{roleName},#{note})
</insert>
自定义主键:
规则:当角色表记录为空时id设置为1;不为空时,id设置为当前id加3。
keyProperty指定了采用哪个属性作为POJO的主键
resultType代表将返回一个long类型的结果集
order设置为BEFORE说明它将于当前定义的SQL前执行。
order设置为AFTER说明它会在插入语句之后执行。比如一些插入语句内部可能有嵌入索引调用。
<insert id="insertRole" parameterType="role">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
select if (max(id) = null, 1, max(id) + 3)
from t_role
</selectKey>
insert into t_role2(id, role_name, note)
values(#{id}, #{roleName},#{note})
</insert>
三、update元素和delete元素
<update id="updateRole" parameterType="role">
update t_role set role_name = #{roleName}, note = #{note}
where id = #{id}
</update>
<delete id="deleteRole" parameterType="long">
delete from t_role where id = #{id}
</delete>
四、sql元素
sql元素的作用在于可以定义一条SQL的一部分。1.基本用法:
<resultMap id="roleMap" type="role">
<id property="id" column="id" />
<result property="roleName" column="role_name" />
<result property="note" column="note" />
</resultMap>
<sql id="roleCols">
id, role_name, note
</sql>
<select id="getRoleUseResultMap" parameterType="long" resultMap="roleMap">
select <include refid="roleCols"/>
from t_role
where id = #{id}
</select>
2.支持变量传递:
在include元素中定义了一个命名为alias的变量,其值是SQL中表t_role的别名r。然后sql元素就可以使用这个变量名了。
<sql id="roleCols">
${alias}.id, ${alias}.role_name, ${alias}.note
</sql>
<select id="getRoleUseResultMap" parameterType="long" resultMap="roleMap">
select
<include refid="roleCols">
<property name="alias" value="r"/>
</include>
from t_role r
where id = #{id}
</select>
五、参数
1.概述
<insert id="insertRole" parameterType="role" useGeneratedKeys="true" keyProperty="id">
insert into t_role(role_name, note)
values(#{roleName, typeHandler=org.apache.ibatis.type.StringTypeHandler},#{note})
</insert>
事实上mybatis会根据javaType和jdbcType自动检测使用哪个typeHandler。但是我们可以自定义typeHandler。
#{age, javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
mybatis也提供了一些对控制数值的精度支持。如下只保留两位有效数字。
#{width, javaType=double,jdbcType=NUMERIC,numericScale=2}
2.存储过程参数支持
参数类型:输入参数、输出参数、输入输出参数
#{id, mode=IN}
#{roleName, mode=OUT}
#{note, mode=INOUT}
六、resultMap元素
1.resultMap元素构成
<resultMap>
<constructor>
<idArg/>
<arg/>
</constructor>
<id/>
<result/>
<association/>
<collection/>
<discriminator>
< case/>
</discriminator>
</resultMap>
constructor元素用于配置构造方法。一个POJO可能不存在没有参数的构造方法,可以使用constructor进行配置。假设Role不存在没有参数的构造方法,它的构造方法声明为public Role(Integer id, String roleName)。那么需要配置结果集,如下:
<resultMap ......>
<constructor>
<idArg column="id" javaType="int"/>
<arg column="role_name" javaType="string"/>
</constructor>
......
</resultMap>
2.使用map存储结果集
一般而言,任何select语句都可以使用map存储:
<select id="findColorByNote" parameterType="string" resultType="map">
select id, color, note
from t_color
where note like concat('%', #{note}, '%')
</select>
但是map可读性差,更多时候会使用POJO方式。
3.使用POJO存储结果集
resultMap元素的子元素id表示这个对象的主键,property代表着POJO的属性名称。
<resultMap id="roleMap" type="role">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="note" column="note"/>
</resultMap>
<select id="getRoleUseResultMap" parameterType="long" resultMap="roleMap">
select id, role_name, note
from t_role
where id = #{id}
</select>
七、级联
1.概述
association一对一级联
collection一对多级联
discriminator鉴别器级联
级联实例完整版DEMO:级联DEMO
public class Employee {
private Long id;
private String realName;
//涉及鉴别器级联
private SexEnum sex = null;
private Date birthday;
private String mobile;
private String email;
private String position;
private String note;
//工牌按一对一级联
private WorkCard workCard;
//雇员任务,一对多级联
private List<EmployeeTask> employeeTaskList = null;
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssmr.chapter05.dao.EmployeeDao">
<resultMap id="employeeMap" type="com.ssmr.chapter05.pojo.Employee">
<id column="id" property="id" />
<result column="real_name" property="realName" />
<result column="sex" property="sex"
typeHandler="com.ssmr.chapter05.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="com.ssmr.chapter05.dao.EmployeeTaskDao.getEmployeeTaskByEmpId"/>
<!--雇员和雇员任务一对多级联-->
<collection property="employeeTaskList" column="id"
fetchType="eager"
select="com.ssmr.chapter05.dao.EmployeeTaskDao.getEmployeeTaskByEmpId"/>
<!--鉴别器级联-->
<discriminator javaType="long" column="sex">
<case value="0" resultMap="maleHealthFormMapper" />
<case value="1" resultMap="femaleHealthFormMapper"/>
</discriminator>
</resultMap>
<resultMap id="femaleHealthFormMapper" type="com.ssmr.chapter05.pojo.FemaleEmployee" extends="employeeMap">
<!--女性和女性体检表一对一级联-->
<association property="femaleHealthForm" column="id"
select="com.ssmr.chapter05.dao.FemaleHealthFormDao.getFemaleHealthForm"/>
</resultMap>
<resultMap id="maleHealthFormMapper" type="com.ssmr.chapter05.pojo.MaleEmployee" extends="employeeMap">
<!--男性和男性体检表一对一级联-->
<association property="maleHealthForm" column="id"
select="com.ssmr.chapter05.dao.MaleHealthFormDao.getMaleHealthForm"/>
</resultMap>
<select id="getEmployee" parameterType="long" resultMap="employeeMap">
select id, real_name as realName, sex, birthday, mobile, email, position,note
from t_employee
where id = #{id}
</select>
</mapper>
3.N+1问题
级联完成后,只要加载主信息,一些关联信息也会同时加载,有些我们是暂时不用的,这会造成浪费,服务器压力也会增大,这就是N+1问题。解决方法就是延迟加载。mybatis提供了延迟加载的功能。
4.延迟加载
在MyBatis的配置文件mybatis-config.xml中settings配置中存在两个元素可以配置级联:
lazyLoadingEnabled 延迟加载的全局开关,默认false。
aggressiveLazyLoading 延迟加载的层级开关。版本3.4.1(包含)之前默认true,之后为false。
这两个属性都是全局配置。如果不开启的话会把级联的所有数据加载;如果都开启,就是加载层级数据,如雇员下有雇员任务和工卡,aggressiveLazyLoading为true的话就会将这两个级联数据都加载出来,为false的话就都不加载出来。现在我们只想加载雇员任务而不加载工卡。怎么办呢?
<settings>
<!--延迟加载的开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--层级延迟加载的开关。版本3.4.1(包含)之前为true,之后为false-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
fetchType属性会处理全局定义无法处理的问题。fetchType出现在级联元素(association、collection。注意:discriminator没有这个属性)中,它存在两个值:
eager:获得当前的pojo后立即加载对应的数据。
lazy:获得当前pojo后延迟加载对应的数据。
fetch属性会忽略全局配置项lazyLoadingEnabled和aggressiveLazyLoading。
5.另一种级联
这种方式会消除N+1问题,但是会引入其他问题:SQL复杂、配置复杂、一次性取出数据会浪费内存、维护困难。
这里请读者点击链接自行查看:另一种级联DEMO
6.多对多级联
往往会拆分成两个一对多的级联。
1.POJO
public class Role{
private Long id;
private String roleName;
private String note;
//关联用户信息,一对多关联
private List<User> userList;
/** getter/setter **/
}
public class User {
private Long id;
private String userName;
private String realName;
private SexEnum sex;
private String moble;
private String email;
private String note;
// 关联角色对象,一对多关联
private List<Role> roleList;
/** getter/setter **/
}
2.mapper.xml
在映射器设置fetchType为lazy,这样就不会立即加载数据进来
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssmr.chapter05.dao.RoleDao">
<resultMap type="com.ssmr.chapter05.pojo.Role" id="roleMapper">
<id column="id" property="id" />
<result column="role_name" property="roleName" />
<result column="note" property="note" />
<collection property="userList" column="id"
fetchType="lazy"
select="com.ssmr.chapter05.mapper.UserMapper.findUserByRoleId" />
</resultMap>
<select id="getRole" parameterType="long" resultMap="roleMapper">
select id, role_name, note
from t_role
where id = #{id}
</select>
<select id="findRoleByUserId" parameterType="long" resultMap="roleMapper">
select r.id, r.role_name, r.note
from t_role r, t_user_role ur
where r.id = ur.role_id and ur.user_id = #{userId}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssmr.chapter05.mapper.UserMapper">
<resultMap type="com.ssmr.chapter05.pojo.User" id="userMapper">
<id column="id" property="id" />
<result column="user_name" property="userName" />
<result column="real_name" property="realName" />
<result column="sex" property="sex"
typeHandler="com.ssmr.chapter05.typehandler.SexTypeHandler" />
<result column="mobile" property="moble" />
<result column="email" property="email" />
<result column="position" property="position" />
<result column="note" property="note" />
<collection property="roleList" column="id"
fetchType="lazy"
select="com.ssmr.chapter05.mapper.RoleMapper.findRoleByUserId" />
</resultMap>
<select id="getUser" parameterType="long" resultMap="userMapper">
select id, user_name, real_name, sex, moble, email, note
from t_user
where id =#{id}
</select>
<select id="findUserByRoleId" parameterType="long" resultMap="userMapper">
select u.id, u.user_name, u.real_name, u.sex, u.moble, u.email, u.note
from t_user u , t_user_role ur
where u.id = ur.user_id and ur.role_id =#{roleId}
</select>
</mapper>
八、缓存
1.概述
MyBatis分为一级缓存和二级缓存。
一级缓存是在SqlSession上的缓存,二级缓存是在SqlSessionFactory上的缓存。默认情况下,也就是没有任何配置的情况下,MyBatis系统会开启以及缓存,也就是对SqlSession层面的缓存,这个缓存不需要POJO对象可序列化。
2.一级缓存
对同一对象进行两次获取,如果第二次的SQL和参数都没有变化,并且缓存没有超时或者声明需要刷新时,那么它就会从缓存中取数据。
public static void testOneLevelCache() {
SqlSession sqlSession = null;
Logger logger = Logger.getLogger(Test.class);
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
RoleDao2 roleDao2 = sqlSession.getMapper(RoleDao2.class);
Role2 role = roleDao2.getRole(1L);
logger.info("再获取一次POJO......");
Role2 role2 = roleDao2.getRole(1L);
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
3.二级缓存
对于不同的SqlSession对象,一级缓存是不能共享的。如下代码会执行两次SQL:
public static void testTwoLevelCache() {
SqlSession sqlSession = null;
SqlSession sqlSession2 = null;
Logger logger = Logger.getLogger(Test.class);
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
sqlSession2 = SqlSessionFactoryUtil.openSqlSession();
RoleDao2 roleDao2 = sqlSession.getMapper(RoleDao2.class);
Role2 role2 = roleDao2.getRole(1L);
//需要提交,如果是一级缓存,MyBatis才会缓存对象到SqlSessionFactory层面
sqlSession.commit();
logger.info("不同sqlSession再获取一次POJO......");
RoleDao2 roleDao22 = sqlSession2.getMapper(RoleDao2.class);
Role2 role22 = roleDao22.getRole(1L);
//需要提交,MyBatis才缓存对象到SQLSessionFactory
sqlSession2.commit();
} catch(Exception e) {
logger.info(e.getMessage(), e);
} finally {
if (sqlSession != null) {
sqlSession.close();
}
if (sqlSession2 != null) {
sqlSession.close();
}
}
}
为了使SqlSession对象之间共享相同的缓存,有时候需要开启二级缓存。
开启二级缓存只需要在RoleMapper.xml中加入下面代码:
<cache/>
这个时候MyBatis会序列化和反序列化对应的POJO,也就要求POJO是一个可序列化的对象,那么它就必须实现java.io.Serializable接口。对角色类Role对象进行缓存,那么就需要它实现Serializable接口。
4.缓存配置项、自定义和引用
测试二级缓存,只配置cache元素,加入这个元素后,MyBatis就会将对应的命名空间内所有select元素SQL查询结果进行缓存,而其中的insert、delete、update语句在操作时会刷新缓存。
<cache
blocking=""
readOnly=""
eviction=""
flushInterval=""
type=""
size=""
/>
(1)type自定义缓存类,实现Cache接口即可。
在现实中,我们可以使用Redis、MongoDB或者其它常用的缓存,假设存在一个Redis的缓存实现类com.ssmr.chapter05.cache.RedisCache.那么可以这样配置:
<cache type="com.ssmr.chapter05.cache.RedisCache.">
<property name="host" value="localhost"/>
</cache>
这样配置后,MyBatis会启用缓存,同时调用setHost(String host)方法,去设置配置的内容。
(2)对于一些语句也需要自定义。比如对于一些查询并不想要它进行任何缓存,这时就可以通过配置改变。
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
以上是默认配置。flushCache代表是否刷新缓存。useCache属性是select特有的,代表是否需要使用缓存。
注意:这些缓存配置都是在一个映射器内配置的,如果其他映射器需要使用同样的配置,则可以引入缓存的配置:
<cache-ref namespace="com.ssmr.chapter05.dao.RoleDao"/>
九、存储过程
1.简述
存储过程是数据库预先编译好,放在数据库内存中的一个程序片段,性能高,可重复使用。
三种类型参数:输入参数IN、输出参数OUT、输入输出参数INOUT。
2.IN和OUT参数存储过程
这里使用Oracle数据库。
(1)场景:根据角色名称进行模糊查询其总数,然后把总数和查询日期返回给调用者。
(2)准备
存储过程如下:
CREATE OR REPLACE
PROCEDURE count_role(
p_role_name IN VARCHAR,
count_total OUT INT,
exec_date OUT DATE)
IS
BEGIN
SELECT COUNT(*) INTO count_total FROM t_role WHERE role_name LIKE '%' || p_role_name || '%';
SELECT SYSDATE INTO exec_date FROM DUAL;
END;
创建一个POJO:
public class PdCountRoleParams {
private String roleName;
private int total;
private Date execDate;
/** getter/setter **/
}
(3)使用
<select id="countRole" parameterType="com.ssmr.chapter05.pojo.PdCountRoleParams" statementType="CALLABLE">
{call count_role(
#{roleName, mode=IN, jdbcType=VARCHAR},
#{total, mode=OUT, jdbcType=INTEGER},
#{execDate, mode=OUT, jdbcType=DATE}
)}
</select>
statementType为CALLABLE,说明它是在使用存储过程,不这样声明就会抛异常。
在属性上通过model设置了其输入或者输出参数,指定对应jdbcType,这样mybatis就会使用对应的typeHandler去处理对应的类型转换。
3.游标的使用
如果把jdbcType声明为CURSOR,那么它就会使用ResultSet对象处理对应的结果。
(1)场景:同样根据角色名称模糊查询角色表的数据,但要求分页查询,于是存在start和end两个参数。为了知道是否存在下一页,还会要求查出总数。
(2)准备
存储过程:
CREATE OR REPLACE
PROCEDURE find_role(
p_role_name IN VARCHAR,
p_start IN INT,
p_end IN INT,
r_count OUT INT,
ref_cur OUT sys_refcursor)
AS
BEGIN
SELECT COUNT(*) INTO r_count FROM t_role WHERE role_name LIKE '%' || p_role_name || '%';
OPEN ref_cur FOR
SELECT id,role_name,note FROM
(SELECT id,role_name,note,rownum AS row1 FROM t_role a
WHERE a.role_name LIKE '%' || p_role_name || '%' AND rownum <=p_end)
WHERE row1>p_start;
END find_role;
定义存储游标的POJO:
参数是和存储过程一一对应的,而游标是由roleList去存储的,使用时只需为其提供映射关系即可。
public class PdFindRoleParams {
private String roleName;
private int start;
private int end;
private int total;
private List<Role> roleList;
/** getter/setter **/
}
(3)使用
<resultMap type="com.ssmr.chapter05.pojo.Role" id="roleMapper">
<id column="id" property="id" />
<result column="role_name" property="roleName" />
<result column="note" property="note" />
</resultMap>
<select id="findRole" parameterType="com.ssmr.chapter05.param.PdFindRoleParams" statementType="CALLABLE">
{call find_role(
#{roleName, mode=IN, jdbcType=VARCHAR},
#{start, mode=IN,jdbcType=INTEGER},
#{end, mode=IN, jdbcType=INTEGER},
#{total, mode=OUT,jdbcType=INTEGER},
#{roleList,mode=OUT,jdbcType=CURSOR,
javaType=ResultSet,resultMap=roleMap}
)}
</select>
为了使得ResultSet对应能够映射POJO,设置resultMap为roleMap。