示例:构造函数 + 枚举类型处理器
注意事项:
查询结果存储:Map vs POJO,何时选谁?MyBatis 支持直接以
✅ 优势:零 POJO 开发成本,适合临时查询、动态字段、快速原型。
级联:一对多、一对一与鉴别器(discriminator)实战场景建模:员工体检按性别拆表为适配男女体检项目差异,采用「单表继承」+ 「鉴别器」方案:
POJO 设计:继承体系支撑鉴别映射
XML 映射:
|
| 配置项 | 说明 | 默认值(3.4.1+) |
|---|---|---|
lazyLoadingEnabled |
是否启用延迟加载 | false |
aggressiveLazyLoading |
是否“激进”:任意属性访问即加载全部延迟对象 | false |
开启后,employee.getWordCard() 第一次调用才触发 WorkCardDao.getWorkCardByEmployeeId 查询;后续调用直接返回缓存对象。
✅ 推荐在 mybatis-config.xml 中全局开启:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
作用域:单个 SqlSession 生命周期内。
行为:同一 SqlSession 中,相同 SQL + 相同参数 → 第二次查询直接命中缓存,不发 SQL。
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
Employee employee1 = employeeDao.getEmployeeById(1); // 执行 SQL
Employee employee2 = employeeDao.getEmployeeById(1); // 直接返回缓存
✅ 无需 POJO 实现 Serializable;
❌ 不同 SqlSession 间不共享。
作用域:整个 Mapper 命名空间(如 com.ssm.Dao.EmployeeDao)。
前提:POJO 必须实现 java.io.Serializable(因需序列化跨 Session 传输):
public class Employee implements Serializable {
private static final long serialVersionUID = 1L; // 显式声明
// ...
}

全局开关(mybatis-config.xml):
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
Mapper 文件启用(如 EmployeeDao.xml):
<mapper namespace="com.ssm.Dao.EmployeeDao">
<cache/> <!-- 启用默认二级缓存 -->
<!-- 或自定义配置 -->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"
type="com.example.RedisCache">
<property name="redisHost" value="localhost"/>
<property name="redisPort" value="6379"/>
</cache>
</mapper>
SQL 级控制(可选):
<select id="selectById" useCache="true">...</select>
<insert flushCache="true">...</insert> <!-- 默认 true,自动清缓存 -->
| 属性 | 可选值 | 说明 |
|---|---|---|
eviction |
LRU, FIFO, SOFT, WEAK |
缓存淘汰策略,默认 LRU |
flushInterval |
毫秒数(如 60000) |
定时刷新间隔,0 表示不刷新 |
size |
正整数(如 512) |
最大缓存条目数 |
readOnly |
true/false |
true:返回只读对象(性能优);false:返回可修改副本(线程安全) |
type |
自定义 Cache 实现类全限定名 |
如集成 Redis、Caffeine 等 |
🔒 重要提醒:二级缓存在分布式环境下不保证强一致性。高并发更新场景下,建议结合业务容忍度评估是否启用;若需跨节点同步,应选用 Redis 等外部缓存并配置
type。
以统计角色数量为例(Oracle):
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;
对应 Java POJO 封装参数:
public class PdCountRoleParams {
private String roleName;
private int total;
private Date execDate;
// getter/setter...
}
Mapper XML 调用:
<select id="countRole" statementType="CALLABLE" parameterType="com.ssm.pojo.param.PdCountRoleParams">
{call count_role(
#{roleName, mode=IN, jdbcType=VARCHAR},
#{total, mode=OUT, jdbcType=INTEGER},
#{execDate, mode=OUT, jdbcType=DATE}
)}
</select>
存储过程返回 SYS_REFCURSOR,MyBatis 通过 jdbcType=CURSOR + resultMap 解析:
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;
Mapper XML:
<resultMap type="role" id="roleMap2">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="note" column="note"/>
</resultMap>
<select id="findRole" statementType="CALLABLE" parameterType="com.ssm.pojo.param.PdFindRoleParams">
{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=roleMap2}
)}
</select>
✅
roleList字段将被自动填充为List<Role>,total字段接收计数结果。
以下 SQL 按依赖顺序执行,确保外键引用有效:
-- 删除旧表(按依赖逆序)
DROP TABLE IF EXISTS t_female_health_form;
DROP TABLE IF EXISTS t_male_health_form;
DROP TABLE IF EXISTS t_task;
DROP TABLE IF EXISTS t_work_card;
DROP TABLE IF EXISTS t_employee;
DROP TABLE IF EXISTS t_employee_task;
-- 创建员工主表
CREATE TABLE t_employee (
id int(12) NOT NULL AUTO_INCREMENT,
real_name varchar(60) NOT NULL,
sex int(2) NOT NULL COMMENT '1:男 2:女',
birthday date NOT NULL,
mobile varchar(20) NOT NULL,
email varchar(60) NOT NULL,
position varchar(60) NOT NULL,
note varchar(256) DEFAULT NULL,
PRIMARY KEY (id)
);
-- 创建员工任务关联表
CREATE TABLE t_employee_task (
id int(12) NOT NULL,
employee_id int(12) NOT NULL,
task_id int(12) NOT NULL,
task_name VARCHAR(60) NOT NULL,
note varchar(256) DEFAULT NULL,
PRIMARY KEY (id),
KEY FK_employee_id (employee_id),
CONSTRAINT FK_employee_id FOREIGN KEY (employee_id) REFERENCES t_employee (id),
KEY FK_task_id (task_id),
CONSTRAINT FK_task_id FOREIGN KEY (task_id) REFERENCES t_task (id)
);
-- 创建男性健康表
CREATE TABLE t_male_health_form (
id int(12) NOT NULL AUTO_INCREMENT,
employee_id int(12) DEFAULT NULL,
heart varchar(64) NOT NULL,
liver varchar(64) NOT NULL,
spleen varchar(64) NOT NULL,
lung varchar(64) NOT NULL,
kidney varchar(64) NOT NULL,
prostate varchar(64) NOT NULL,
note varchar(256) NOT NULL,
PRIMARY KEY (id),
KEY FK_employee_id_1 (employee_id),
CONSTRAINT FK_employee_id_1 FOREIGN KEY (employee_id) REFERENCES t_employee (id)
);
-- 创建女性健康表
CREATE TABLE t_female_health_form (
id int(12) NOT NULL AUTO_INCREMENT,
employee_id int(12) NOT NULL,
heart varchar(64) NOT NULL,
liver varchar(64) NOT NULL,
spleen varchar(64) NOT NULL,
lung varchar(64) NOT NULL,
kidney varchar(64) NOT NULL,
uterus varchar(64) NOT NULL,
note varchar(255) NOT NULL,
PRIMARY KEY (id),
KEY FK_employee_id_2 (employee_id),
CONSTRAINT FK_employee_id_2 FOREIGN KEY (employee_id) REFERENCES t_employee (id)
);
-- 创建工作卡表
CREATE TABLE t_work_card (
id int(12) NOT NULL AUTO_INCREMENT,
employee_id int(12) NOT NULL,
real_name varchar(60) NOT NULL,
department varchar(20) NOT NULL,
mobile varchar(20) NOT NULL,
position varchar(20) NOT NULL,
note varchar(256) DEFAULT NULL,
PRIMARY KEY (id),
KEY FK_employee_id (employee_id),
CONSTRAINT FK_employee_id FOREIGN KEY (employee_id) REFERENCES t_employee (id)
);
-- 创建任务表
CREATE TABLE t_task (
id int(12) NOT NULL,
title varchar(256) NOT NULL,
context varchar(256) NOT NULL,
note varchar(256) DEFAULT NULL,
PRIMARY KEY (id)
);
-- 为已存在表添加外键约束
ALTER TABLE t_employee_task ADD CONSTRAINT FK_REFERENCE_1 FOREIGN KEY (employee_id) REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_employee_task ADD CONSTRAINT FK_REFERENCE_2 FOREIGN KEY (task_id) REFERENCES t_task (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_female_health_form ADD CONSTRAINT FK_REFERENCE_3 FOREIGN KEY (employee_id) REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_male_health_form ADD CONSTRAINT FK_REFERENCE_4 FOREIGN KEY (employee_id) REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
ALTER TABLE t_work_card ADD CONSTRAINT FK_REFERENCE_5 FOREIGN KEY (employee_id) REFERENCES t_employee (id) ON DELETE RESTRICT ON UPDATE RESTRICT;
掌握 MyBatis 映射器高级特性,是脱离模板化开发、迈向高可用架构的必经之路:
typeHandler 是类型安全的基石,尤其在微服务间 JSON 交互、枚举标准化场景中不可或缺; ${} / #{})赋予 SQL 构建灵活性,但务必严守注入防线; <resultMap> + <discriminator> 让复杂领域模型与数据库物理设计解耦,支撑业务演进; 💡 若你正在构建企业级 Java 应用,建议将本文实践与 后端 & 架构 板块中的分布式事务、读写分离方案结合,形成完整的数据访问层技术栈。