前言
使用MyBatis开发过程中sql语句都需要参数或返回值,而且大部分都是自定义对象。参数或返回值可以使用parameterType和resultType设置,正常情况下是需要使用包名加类名来确定对象的,也可以使用别名来简化。接下来详细讲述别名。
一、如何使用?
别名的配置分为两种,一种是通过包名配置,获取包下类信息根据类名简写作为别名。另一种直接根据类信息配置别名。接下来说明下mybatis中别名是如何使用的。(会使用的可以直接看第二节源码分析)
项目结构如下:
mybatis-config.xml中的代码(后面简写成config.xml或主配置),以package和typeAlias设置了别名信息。
<configuration>
<!-- 别名设置 -->
<typeAliases>
<!-- 根据类信息配置别名 -->
<!-- 别名定义为uur -->
<typeAlias alias="uur" type="com.learn.mybatis.entity.param.UmsUserParam" />
<!-- 根据包名配置别名 -->
<package name="com.learn.mybatis.entity" />
</typeAliases>
</configuration>
也可以使用@Alias对类进行别名设置,前提是需要先设置package或typeAlias,如果未设置package或typeAlias,@Alias注解将不会生效(后面对注解进行分析)。
使用@Alias注解如下:
@Data
// 别名定位为uup
@Alias("uup")
public class UmsUserParam{
/**
* 系统生成的客户编号
*/
private String no;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;
}
对自定义别名进行使用。
<!-- 使用别名uup或uur -->
<select id="select" parameterType="uup" resultMap="BaseResultMap" >
select
<include refid="column_info"/>
from ums_user
<where>
<if test="no!=null">
and `no` = like concat(concat('%',#{no}),'%')
</if>
<if test="name!=null">
and `name` like concat(concat('%',#{name}),'%')
</if>
<if test="age!=null">
and `age` = {age}
</if>
<if test="sex!=null">
and `sex` = {sex}
</if>
</where>
</select>
config.xml与注解分别配置了不同的别名。
加载自定义别名如下:解析typeAlias
加载自定义别名如下:解析package
解析mapper.xml时使用别名如下:
可以看到别名信息中存在uup和uur两个而且对应的类是相同的。uur是通过typeAlias配置添加到别名信息中的,uup则是通过package扫描下面的类然后通过Alias注解注册的别名。
二、源码分析
TypeAliasRegistry类使用map保存别名与类的关系,初始化时添加了常用的别名信息。TypeAliasRegistry类中包含通过参数获取类信息和注册别名的方法。
下面是TypeAliasRegistry类信息:
public class TypeAliasRegistry {
// map对象用来维护别名与类的关系
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
// 构造方法 注册默认的别名信息
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
// 根据参数获取类信息(存在别名则直接从别名map中获取,否则根据参数转换成对应的类)
@SuppressWarnings("unchecked")
// throws class cast exception as well if types cannot be assigned
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// 将key统一转换成小写(保存别名时是小写)
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
// 别名map中是否包含该key
if (TYPE_ALIASES.containsKey(key)) {
// 包含则直接从map中获取类信息
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
// 不包含则直接转换成类信息
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
// 根据包名注册别名
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
// 根据包名和父类注册别名
public void registerAliases(String packageName, Class<?> superType){
// 解析工具
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
// 扫描包下的类并且是superType的子类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取符合条件的类
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// 跳过内部类、接口
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
// 将类注册到别名map中
registerAlias(type);
}
}
}
// 将类信息注册到别名中(package解析和typeAlias只设置类信息时会调用该方法,所以Alias需要在package或typeAlias设置后使用)
public void registerAlias(Class<?> type) {
// 获取类名(不包含包名,简单类名)
String alias = type.getSimpleName();
// 获取类上注解
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
// 存在注解则获取注解配置的别名
alias = aliasAnnotation.value();
}
// 将别名和类信息注册到别名map中
registerAlias(alias, type);
}
// 将别名和类信息添加到别名map中(package和typeAlias最终都要调用该方法)
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
// 别名统一转换成小写(获取时传入的key同样操作)
String key = alias.toLowerCase(Locale.ENGLISH);
// 别名map中存在相同的key 且 对应的value不为null而且与存在的类信息不同时抛出异常
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
// 将别名与类信息添加到别名map中
TYPE_ALIASES.put(key, value);
}
// 添加别名的重载方法 通过stirng转换成对应的类信息在进行添加
public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
}
}
// 获取别名map 只读
public Map<String, Class<?>> getTypeAliases() {
return Collections.unmodifiableMap(TYPE_ALIASES);
}
}
TypeAliasRegistry是如何创建的?Mybatis的主配置类Configuration中包含TypeAliasRegistry属性。mybatis启动时创建Configuration对象,Configuration构造方法里创建TypeAliasRegistry对象。
代码信息如下:
// Configuration无参构造,其他构造都会调用无参构造。(与别名无关的代码都删掉了)
public Configuration() {
// 创建TypeAliasRegistry
this.typeAliasRegistry = new TypeAliasRegistry();
// 创建默认的别名信息
this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
}
用户自定义的别名是如何注册的呢?这里需要说明Configuration的解析了,以Xml形式为例,XMLConfigBuilder的parseConfiguration方法是对config.xml进行解析的,里面包含对别名的解析。
parseConfiguration方法如下:
// 对Config.xml文件进行解析(这里只看别名)
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 对别名进行解析
typeAliasesElement(root.evalNode("typeAliases"));
// mybatis插件解析
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 自定义的typeHandler进行解析
typeHandlerElement(root.evalNode("typeHandlers"));
// 对配置的mapper进行解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// 对Config.xml文件中配置的别名进行解析
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 遍历typeAliases下子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// package解析
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// typeAlias进行解析
// 获取定义的别名
String alias = child.getStringAttribute("alias");
// 获取类信息
String type = child.getStringAttribute("type");
try {
// 转换成类信息
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 别名为null则通过类进行别名注册(如果存在注解则使用注解配置的别名,不存在则使用类简单名称TypeAliasRegistry类中有说明)
typeAliasRegistry.registerAlias(clazz);
} else {
// 别名不为null则直接注册
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
别名的注册已经了解了,mapper.xml配置的sql中使用的parameterType和resultType是如何解析的呢?mapper.xml中<select|insert|update|delete>最终都会转换成MappedStatement。MappedStatement是由XMLStatementBuilder的parseStatementNode方法进行解析的。
parseStatementNode代码如下:
// 解析<select|insert|update|delete>语句
public void parseStatementNode() {
// context为解析当前语句的信息(select标签内包含的信息)。
// 获取parameterType的值
String parameterType = context.getStringAttribute("parameterType");
// 根据parameterType的值通过TypeAliasRegistry获取对应的类。如何是null则直接返回null,如果是别名则根据别名获取类信息,不是别名则根据获取的值转换成对应的类。
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取resultType的值
String resultType = context.getStringAttribute("resultType");
// 与parameterType相同
Class<?> resultTypeClass = resolveClass(resultType);
}
总结
mybatis别名是通过TypeAliasRegistry进行存储的,TypeAliasRegistry通过内部的Map存储别名与类的关系。创建Configuration时会创建TypeAliasRegistry对象,并初始化默认的别名信息,解析Configuration的过程中会对自定的别名进行解析。自定义别名的方式分为两种,一种是通过package对包下的类定义别名,一种是根据typeAlias定义单个类。可以通过Alias注解定义单个类别名,使用注解前需要使用package或typeAlias,否则注解不生效。同一个类可以定义多个别名,不同的类不能定义相同的别名。