下午写了一个DO类,里面有一个枚举的对象,因此用到了ibatis的自定义handler
Account类里的status属性是枚举类型,对应的xml文件如下:
............
<typeAlias alias="account" type="test.Account"/>
<insert id="insertAccount" parameterClass="account">
insert into ACCOUNT (
ACC_ID,
ACC_FIRST_NAME,
ACC_LAST_NAME,
STATUS)
values (
#id#,
#firstName#,
#lastName#,
#status,handler=test.MyHandler#
)
</insert>
.................
上面的status是枚举类,所以在总的配置文件sqlmapconfig.xml中配置了handler
......
<typeHandler javaType="test.Account" jdbcType="VARCHAR" callback="test.MyHandler"/>
........
以上这么配置,是没有问题的,项目里面的handler也都是这么配置,以前没有仔细深究,今天仔细看了看。有个疑问:
为什么要配置2处?有这个必要么?
于是乎,回来翻了一下ibatis的源代码,释然了。
首先看看ibatis解析xml文件的代码:
com.ibatis.sqlmap.engine.mapping.parameter.InlineParameterMapParser
public SqlText parseInlineParameterMap(TypeHandlerFactory typeHandlerFactory, String sqlStatement, Class parameterClass) {
.....
StringTokenizer parser = new StringTokenizer(sqlStatement, PARAMETER_TOKEN, true);
StringBuffer newSqlBuffer = new StringBuffer();
//这个while循环把#aaa#,#bbb#,#ccc#这种东西替换成 ?,?,?,并解析##之间的内容
while (parser.hasMoreTokens()) {
token = parser.nextToken();
if (PARAMETER_TOKEN.equals(lastToken)) {
if (PARAMETER_TOKEN.equals(token)) {
newSqlBuffer.append(PARAMETER_TOKEN);
token = null;
} else {
ParameterMapping mapping = null;
//PARAM_DELIM=":"
if (token.indexOf(PARAM_DELIM) > -1) {
mapping = oldParseMapping(token, parameterClass, typeHandlerFactory);
} else {
//这里面解析##之间的内容,返回一个mapping
mapping = newParseMapping(token, parameterClass, typeHandlerFactory);
}
mappingList.add(mapping);
newSqlBuffer.append("?");
boolean hasMoreTokens = parser.hasMoreTokens();
if (hasMoreTokens)
token = parser.nextToken();
if (!hasMoreTokens || !PARAMETER_TOKEN.equals(token)) {
throw new SqlMapException(
"Unterminated inline parameter in mapped statement near '"
+ newSqlBuffer.toString() + "'");
}
token = null;
}
} else {
if (!PARAMETER_TOKEN.equals(token)) {
newSqlBuffer.append(token);
}
}
lastToken = token;
}
....
}
迫不及待的进入到newParseMapping方法中
一大堆的if..else,慢慢看来
private ParameterMapping newParseMapping(String token, Class parameterClass, TypeHandlerFactory typeHandlerFactory) {
ParameterMapping mapping = new ParameterMapping();
/*同样是用StringTokenizer,不过这里的分割符号为"="或者","
*所以如果##之间的字符串是
*propertyName,javaType=string,jdbcType=VARCHAR,mode=IN,nullValue=N/A,handler=string,numericScale=2
那么解析出来就是propertyName javaType string jdbcType ......
*/
StringTokenizer paramParser = new StringTokenizer(token, "=,", false);
//第一个显然是参数的名字
mapping.setPropertyName(paramParser.nextToken());
while (paramParser.hasMoreTokens()) {
String field = paramParser.nextToken();
if (paramParser.hasMoreTokens()) {
String value = paramParser.nextToken();
if ("javaType".equals(field)) {
value = typeHandlerFactory.resolveAlias(value);
mapping.setJavaTypeName(value);
} else if ("jdbcType".equals(field)) {
mapping.setJdbcTypeName(value);
} else if ("mode".equals(field)) {
mapping.setMode(value);
} else if ("nullValue".equals(field)) {
mapping.setNullValue(value);
} else if ("handler".equals(field)) {
try {
//到了handler这一快,在例子中,我们指定了handler
//所以,这里直接通过反射,给我们造了一个出来。
//由此可见,如果##之间如果有配handler,则会优先用这个,外面定义的handler在这里不起任何作用
//如果我们没有在这里配置handler呢?接着往下看
value = typeHandlerFactory.resolveAlias(value);
Object impl = Resources.instantiate(value);
if (impl instanceof TypeHandlerCallback) {
mapping.setTypeHandler(new CustomTypeHandler((TypeHandlerCallback) impl));
} else if (impl instanceof TypeHandler) {
mapping.setTypeHandler((TypeHandler) impl);
} else {
throw new SqlMapException ("The class " + value + " is not a valid implementation of TypeHandler or TypeHandlerCallback");
}
} catch (Exception e) {
throw new SqlMapException("Error loading class specified by handler field in " + token + ". Cause: " + e, e);
}
} else if ("numericScale".equals(field)) {
try {
Integer numericScale = Integer.valueOf(value);
if (numericScale.intValue() < 0) {
throw new SqlMapException("Value specified for numericScale must be greater than or equal to zero");
}
mapping.setNumericScale(numericScale);
} catch (NumberFormatException e) {
throw new SqlMapException("Value specified for numericScale is not a valid Integer");
}
} else {
throw new SqlMapException("Unrecognized parameter mapping field: '" + field + "' in " + token);
}
} else {
throw new SqlMapException("Incorrect inline parameter map format (missmatched name=value pairs): " + token);
}
}
//如果没有配置handler,这里会根据parameterClass给一个UnkownTypeHandler对象
//一般我们都会定义参数的parameterClass,所以关键看看那个else里面发生了什么。
if (mapping.getTypeHandler() == null) {
TypeHandler handler;
if (parameterClass == null) {
handler = typeHandlerFactory.getUnkownTypeHandler();
} else {
//又调用resolveTypeHandler
handler = resolveTypeHandler(typeHandlerFactory, parameterClass, mapping.getPropertyName(), mapping.getJavaTypeName(), mapping.getJdbcTypeName());
}
mapping.setTypeHandler(handler);
}
return mapping;
}
再看看这个方法:resolveTypeHandler
private TypeHandler resolveTypeHandler(TypeHandlerFactory typeHandlerFactory, Class clazz, String propertyName, String javaType, String jdbcType) {
TypeHandler handler = null;
if (clazz == null) {
// Unknown
handler = typeHandlerFactory.getUnkownTypeHandler();
} else if (DomTypeMarker.class.isAssignableFrom(clazz)) {
// DOM
handler = typeHandlerFactory.getTypeHandler(String.class, jdbcType);
} else if (java.util.Map.class.isAssignableFrom(clazz)) {
// Map
if (javaType == null) {
handler = typeHandlerFactory.getUnkownTypeHandler(); //BUG 1012591 - typeHandlerFactory.getTypeHandler(java.lang.Object.class, jdbcType);
} else {
try {
javaType = typeHandlerFactory.resolveAlias(javaType);
Class javaClass = Resources.classForName(javaType);
handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
} catch (Exception e) {
throw new SqlMapException("Error. Could not set TypeHandler. Cause: " + e, e);
}
}
} else if (typeHandlerFactory.getTypeHandler(clazz, jdbcType) != null) {
// Primitive
handler = typeHandlerFactory.getTypeHandler(clazz, jdbcType);
} else {
//关键在这里,根据配置,我们在##之间显然没有配置javatype,显然为空
if (javaType == null) {
//class是parameterclass,这里通过该class的get方法获取这个枚举类的class类型
Class type = PROBE.getPropertyTypeForGetter(clazz, propertyName);
//接着,根据这个class,和jdbcType去factory里面查,此时jdbcType是空的
handler = typeHandlerFactory.getTypeHandler(type, jdbcType);
} else {
try {
//如果配置了javaType,则根据class类型从factory直接获取之,factory后面会讲到
javaType = typeHandlerFactory.resolveAlias(javaType);
Class javaClass = Resources.classForName(javaType);
handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
} catch (Exception e) {
throw new SqlMapException("Error. Could not set TypeHandler. Cause: " + e, e);
}
}
}
return handler;
}
再来看看factory的代码,很简单:
public TypeHandler getTypeHandler(Class type, String jdbcType) {
/*这个typeHandlerMap里面包含了所有的typeHandler,如:
*基本对象的handler,还有我们自定义在statement外的handler
*factory其实就是一个Map<class,Map<jdbcType,Handler>>
*一个对象可以对应多种jdbcType的handler
*/
Map jdbcHandlerMap = (Map) typeHandlerMap.get(type);
TypeHandler handler = null;
//回到例子中来,我们通过class=test.MyHandler,显然能够找到一个Map<jdbcType,Handler>
if (jdbcHandlerMap != null) {
//接着,就杯具了。。。。
/*直接用jdbcType这个空的对象去map里面取,如果我们的type是ibatis内置的对象或者基本类型还好,它会再初始化的时候插入一个key=null,value=基本的handler(如:IntegerHandler,LongHandler) 的记录进去,因此,对于一些string,interger等属性,即使不写jdbctype,也是可以正常的获得handler的。而此时,我们的jdbcType是空的,由于枚举类是自定义的,故:对应的map里面没有放置key为null的默认handler,而是放置了一个key=VARCHAR,value为test.TypeHandler的东东。所以,取出来的handler为空
*/
handler = (TypeHandler) jdbcHandlerMap.get(jdbcType);
if (handler == null) {
//再取一次,也是徒劳
handler = (TypeHandler) jdbcHandlerMap.get(null);
}
}
//新版的ibatis增加了默认的枚举handler,如果是这样就没有问题了
//可惜公司用的ibatis是木有下面这几行代码的。
if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
handler = new EnumTypeHandler(type);
}
return handler;
}
提一下UnknownTypeHandler
上面提到的是非动态的sql,如果是动态的sql,则会在解析完xml文件后,所有的变量的handler都会设置成UnknownTypeHandler
它的代setParameter码如下:
public void setParameter(PreparedStatement ps, int i, Object parameter, String jdbcType)
throws SQLException {
/*
parameter是传递进来的变量,有可能是map里面的也可能是参数类里面的
jdbcType 是##之间的设置的属性,如:#status,jdbcType=VARCHAR#
*/
//直接读取参数的类型
Class searchClass = parameter.getClass();
这个默认是false
if ( usingJavaPre5 ) {
try {
searchClass = getBaseClass(searchClass);
}
catch ( Exception ex ) {
searchClass = null;
}
}
if ( searchClass == null ) {
searchClass = parameter.getClass();
}
//仍然是通过参数的class和jdbcType来获取handler
TypeHandler handler = factory.getTypeHandler(searchClass, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
总结:
- handler是通过javatype和jdbctype来寻找的,如果javatype为空,则自动通过parameterclass对象的get方法来获取。而jdbctype只能通过##之间的配置获取,如果这个为空,并且又没有在##之间指定handler,肯定找不到handler
- 在##之间指定的handler会优先使用,木有则回去factory里根据javatype和jdbctype获取。
- 在新版(相对于我的项目中的ibatis版本)的ibatis中,枚举类有默认的handler实现,可以不用写handler(这个没自己试过,只是源码里面有)
- 查询的statement根据resultmap里面的定义来调用handler
回到例子中:
如果在statement之外木有定义handler,则需要在##之间这些写
#status,handler=test.MyHandler#
如果定义了handler,则只需要增加一个jdbctype的属性,即可找到handler
显然,这种写法比上面的好,可以复用。
#status,jdbcType=VARCHAR#
注意:
对于接口类型的javatype,在配置文件里面的javatype最好写成实现类的class:
如有这么一个类
public class Account{
......
//这个字段使用自定义的handler来提交(设置返回)数据
private List<String> names;
......
//set方法
public void setNames(List<String> names){
this.names=names;
}
//get方法
public List<String> getNames(){
return names;
}
}
总的sqlmapconfig文件中,这么配置
.................
<!--这里也是用java.util.arrayList-->
<typeHandler jdbcType="VARCHAR" javaType="java.util.ArrayList" callback="aaa.bbb.cc.eee.ffff" />
.................
在对应的ibatis sql映射文件中,其javaType必须写成
<resultMap id="aaaa" class="aaa">
...........
<!-- 这里写成java.util.ArrayList 否则查询数据的时候会报错
找不到handle,导致空指针。因为对应的handlerFactory里面
class存的是ArrayList
-->
<result property="incomeTypes" column="aaa" jdbcType="VARCHAR" javaType="java.util.ArrayList" />
..........
<!-- 下面的一些statement 也配置成java.util.arrayList-->
<insert>
.......
#names,jdbcType=VARCHAR,javaType=java.util.ArrayList#,
....
</insert>
分享到:
相关推荐
此外,Ibatis还提供了TypeHandler机制,用于自定义Java类型与数据库类型的转换,解决一些特殊类型的数据处理问题。 与Hibernate相比,Ibatis的优势在于对SQL的直接控制,使得开发者可以充分利用SQL的功能,优化...
TypeHandler是Ibatis中处理Java类型和数据库类型之间转换的关键,`TypeHandlerRegistry`可以根据Java类型或JDBC类型找到对应的TypeHandler。 8. **Plugins**: 插件机制是Ibatis的一大特色,`ibatis.util.Plugins`类...
Ibatis提供了一些预定义的TypeHandler,也可以自定义。 9. **事务管理**: Ibatis支持手动和自动两种事务管理模式。手动模式下,开发者需要自己控制事务的开启、提交和回滚;自动模式下,可以通过Spring等框架进行...
12.3.2 CacheController的放入、获取以及清除操作 223 12.3.3 注册CacheController以供使用 224 12.4 配置iBATIS不支持的DataSource 224 12.5 定制事务管理 225 12.5.1 理解TransactionConfig接口 226 12.5.2 理解...
3. **TypeHandler**:自定义类型处理器,处理Java类型与数据库类型的转换。 4. **插件机制**:通过拦截器插件,可以对SqlSession、Executor等进行扩展。 5. **MyBatis Generator**:自动化工具,自动生成Mapper...
在自定义TypeHandler的过程中,我们需要创建一个新的类并实现Mybatis提供的`org.apache.ibatis.type.TypeHandler`接口。这个接口有两个核心方法:`setNonNullParameter`和`getNullableResult`,它们分别用于设置SQL...
2. **SqlSession创建**:通过SqlSessionFactory获取SqlSession实例,SqlSession是执行SQL的上下文。 3. **执行SQL**:调用SqlSession的select/insert/update/delete方法,传入SQL ID和参数。 4. **结果映射**:...
iBatis,又被称为SqlMap,是一款开源的“半自动”对象关系映射(Object Relational Mapping, ORM)框架,它通过将SQL语句与Java代码进行解耦,简化了数据库操作。与全自动ORM框架如Hibernate相比,iBatis提供了更细...
- Struts 提供了 Exception Handler 来捕获和处理运行时异常,可以自定义异常处理类,实现特定的错误页面显示。 - Spring 支持全局异常处理,通过 AOP 注解或配置,可以统一处理所有异常。 7. **测试**: - 单元...
默认情况下,MyBatis提供了内置的类型处理器(TypeHandler)来自动完成Java类型与JDBC类型之间的转换。然而,当我们遇到一些特定的需求,如数据加密、状态转换等,就需要自定义类型处理器来满足这些需求。本文将深入...
4. **参数绑定**:MyBatis支持多种参数绑定方式,如简单的类型匹配、Map参数、POJO对象以及自定义TypeHandler。它可以自动处理参数到SQL占位符的映射,避免SQL注入问题。 5. **结果映射**:结果映射用于将查询结果...
为了实现枚举的自动转换,我们需要自定义一个枚举类型处理器,继承`BaseTypeHandler`并覆盖其中的四个关键方法: 1. `setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws ...
3. 参数映射:MyBatis自动处理参数的设置,包括简单的类型、复杂的对象和自定义的TypeHandler。同时,它可以使用`@Param`注解来指定参数名,提高代码的清晰度。 4. 结果集映射:MyBatis提供了自动的结果映射功能,...
- **TypeHandler**: 自定义类型处理器,处理特定类型的转换。 ### 总结 Mybatis-jar.zip 包含了运行 Mybatis 应用所需的核心库,使得开发者可以方便地进行 SQL 查询和数据操作。通过理解 Mybatis 的核心组件、使用...
- 可以自定义类型处理器,通过实现`org.apache.ibatis.type.TypeHandler`接口。 通过这个3.2.8版本的jar包及源码,开发者不仅可以了解MyBatis的基础用法,还可以深入研究其内部实现,提升对数据库操作的优化能力,...
11.5.2. 使用SimpleJdbcInsert来获取自动生成的主键 11.5.3. 指定SimpleJdbcInsert所使用的字段 11.5.4. 使用SqlParameterSource提供参数值 11.5.5. 使用SimpleJdbcCall调用存储过程 11.5.6. 声明SimpleJdbcCall...
MyBatis提供了多种内置TypeHandler,同时也支持自定义TypeHandler。 10. **插件机制** MyBatis的插件机制允许用户在不修改源码的情况下,对Executor、StatementHandler、ParameterHandler、ResultSetHandler等关键...
11.5.2. 使用SimpleJdbcInsert来获取自动生成的主键 11.5.3. 指定SimpleJdbcInsert所使用的字段 11.5.4. 使用SqlParameterSource提供参数值 11.5.5. 使用SimpleJdbcCall调用存储过程 11.5.6. 声明SimpleJdbcCall...
5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. Spring的AOP代理 6.2. @...
5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 5.4.2.1. 注册用户自定义的PropertyEditor 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 ...