typeAliases
导学
本节学习目标:
- 理解
typeAliases解决的核心痛点:全限定类名冗长、易错、可读性差 - 掌握单个别名注册(
typeAlias)与包扫描注册(package)两种方式 - 熟记 MyBatis 内置常用别名,避免重复注册
- 能够在项目中合理规划别名命名,提升 Mapper XML 的可维护性
定义
在 Mapper XML 中,每次引用 Java 类型都需要写全限定名:
<select id="findById" resultType="com.feixiang.entity.Employee">
当包层次深、类名长时,这种写法不仅繁琐,还容易因拼写错误导致启动失败。typeAliases 元素的作用就是为 Java 类设置一个简短、易记的别名,让 XML 配置更加简洁直观。
适用位置与核心属性
typeAliases 位于 mybatis-config.xml 中 settings 之后、typeHandlers 之前。
单个别名注册
<typeAliases>
<typeAlias type="com.feixiang.entity.Employee" alias="Employee"/>
</typeAliases>
| 属性 | 是否必填 | 说明 |
|---|---|---|
type | 必填 | 需要注册别名的全限定类名 |
alias | 可选 | 指定的别名。若省略,MyBatis 会自动将类名的首字母小写作为别名(Employee → employee) |
包扫描注册
<typeAliases>
<package name="com.feixiang.entity"/>
</typeAliases>
| 属性 | 是否必填 | 说明 |
|---|---|---|
name | 必填 | 需要扫描的包路径。该包下所有类(不含内部类、接口)都会被自动注册别名 |
包扫描时,若类上有
@Alias("Emp")注解,则使用注解值作为别名;否则默认首字母小写的类名。
核心原理
别名解析与注册流程
核心存储结构:TypeAliasRegistry 内部维护一个 HashMap<String, Class<?>>,key 为别名,value 为对应的 Java 类。MyBatis 内置别名在 TypeAliasRegistry 构造时就被预先注册。
完整示例
场景说明
乐途公司员工管理系统包含多个实体类:Employee、Department、Position、SalaryRecord。为了避免在 Mapper XML 中反复书写冗长的全限定名,需要为实体包配置别名扫描,同时为一个常用的自定义类型单独注册别名。
操作前的状态
实体类目录:
com.feixiang.entity/
├── Employee.java
├── Department.java
├── Position.java
├── SalaryRecord.java
未配置别名前的 Mapper XML:
<select id="selectById" resultType="com.feixiang.entity.Employee">
完整配置代码
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 别名配置 -->
<typeAliases>
<!-- 方式一:单个别名注册(适合少量类或需要自定义别名时) -->
<typeAlias type="com.feixiang.entity.Employee" alias="Emp"/>
<!-- 方式二:包扫描注册(推荐,批量处理实体类) -->
<package name="com.feixiang.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
<mapper resource="mapper/DepartmentMapper.xml"/>
</mappers>
</configuration>
配置别名后的 Mapper XML:
<?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.feixiang.mapper.EmployeeMapper">
<!-- 使用自定义别名 Emp -->
<select id="selectById" resultType="Emp">
SELECT employee_id, employee_name, department_code, create_time
FROM employee
WHERE employee_id = #{id}
</select>
<!-- 使用包扫描自动生成的别名 employee(Employee 首字母小写) -->
<select id="selectAll" resultType="employee">
SELECT * FROM employee
</select>
</mapper>
DepartmentMapper.xml:
<mapper namespace="com.feixiang.mapper.DepartmentMapper">
<!-- 使用包扫描别名 department -->
<select id="selectByCode" resultType="department">
SELECT department_code, department_name
FROM department
WHERE department_code = #{code}
</select>
</mapper>
实际效果/结果
启动日志(开启 DEBUG 级别):
DEBUG org.apache.ibatis.io.ResolverUtil - Checking to see if class com.feixiang.entity.Employee matches criteria [is assignable to Object]
DEBUG org.apache.ibatis.type.TypeAliasRegistry - Registered type alias 'Emp' to 'com.feixiang.entity.Employee'
DEBUG org.apache.ibatis.type.TypeAliasRegistry - Registered type alias 'employee' to 'com.feixiang.entity.Employee'
DEBUG org.apache.ibatis.type.TypeAliasRegistry - Registered type alias 'department' to 'com.feixiang.entity.Department'
DEBUG org.apache.ibatis.type.TypeAliasRegistry - Registered type alias 'position' to 'com.feixiang.entity.Position'
DEBUG org.apache.ibatis.type.TypeAliasRegistry - Registered type alias 'salaryRecord' to 'com.feixiang.entity.SalaryRecord'
查询执行成功,结果正确映射到实体对象。
分析
Emp是显式注册的自定义别名,优先级与自动生成的employee并存,两者指向同一类- 包扫描会递归扫描指定包下的所有类,但不会扫描子包(如需扫描子包需单独配置
<package name="com.feixiang.entity.sub"/>) - 若包扫描生成的别名与自定义别名冲突,后注册的会覆盖先注册的(取决于 XML 中的书写顺序)
易错场景/常见误区
| 误区 | 错误表现 | 正解 |
|---|---|---|
| 别名大小写不敏感导致误解 | 认为 Employee 和 employee 是同一个别名 | MyBatis 别名区分大小写,Emp、emp、EMP 是三个不同的 key |
| 包扫描时期望扫描子包 | 子包下的类未被注册别名 | package 标签不递归扫描子包,需要为每个子包单独配置 |
| 对已注册的内置别名重复注册 | 试图注册 string 为自定义类,导致类型混乱 | 避免使用 MyBatis 内置别名作为自定义别名 |
省略 alias 属性时期望原类名 | 以为 Employee 注册后别名还是 Employee | 省略 alias 时,默认别名为首字母小写的类名(employee) |
在 typeAlias 的 type 属性写别名 | 启动报 ClassNotFoundException | type 属性必须写全限定类名,不能写别名 |
面试考点
Q1:MyBatis 有哪些常用的内置别名?
A:MyBatis 预先注册了大量常用类型的别名,例如:
- 基本类型:
_int→int,_long→long,_boolean→boolean,_double→double- 包装类型:
int→Integer,long→Long,string→String,boolean→Boolean- 集合类型:
map→Map,hashmap→HashMap,list→List,arraylist→ArrayList- 其他:
date→Date,decimal→BigDecimal,object→Object注意基本类型别名带下划线前缀(_int),以区别于包装类型(int)。
Q2:typeAlias 和 package 两种方式如何选择?
A:如果实体类数量少(少于 5 个)或需要自定义别名(如
Emp代替Employee),使用typeAlias;如果实体类数量多且命名规范(遵循驼峰命名),使用package扫描更高效。实际项目中通常两者结合:对大部分实体用package扫描,对个别需要特殊别名的类用typeAlias补充。
Q3:包扫描时如何自定义某个类的别名而不修改全局配置?
A:在实体类上添加
@Alias("自定义名")注解。例如@Alias("Dept")会让TypeAliasRegistry使用注解值而非首字母小写的类名。这种方式的好处是别名与代码在一起,修改时无需打开 XML 配置文件。
小结
typeAliases 是提升 Mapper XML 可读性的利器。通过 typeAlias 的精确控制和 package 的批量扫描,可以在简洁性与灵活性之间找到平衡。熟记内置别名清单能避免不必要的重复注册。建议团队约定实体包扫描路径,并在实体类上通过 @Alias 注解统一管理别名。
下一章引子
别名让 XML 变简洁了,但 Java 类型与 JDBC 类型之间的转换问题尚未触及。例如 Java 的 Date 如何存入 MySQL 的 DATETIME?自定义枚举如何映射到数据库的 TINYINT?这些转换工作由 typeHandlers 负责,它是 MyBatis 类型系统的"翻译官"。