myBatis自定义传入参数类型(TypeHandler)

本文介绍如何在MyBatis中使用自定义TypeHandler处理复杂数据类型,以实现存储过程参数传递。通过实例展示了如何定义和配置TypeHandler来转换自定义Java类型与数据库特定类型之间的映射。
为了要在myBatis查询时使用特殊的数据类型时,使用通常的jdbcType不能满足需要的时候,可以使用自定义的类型TypeHandler。

例如,有一个存储过程,我们自定义了一个类型Employee,Employee中有两个属性id(long型),name(String型),然后又要将其放在一个List中,作为参数给一个存储过程批量处理。

以下为Orcale类型:

create or replace
TYPE Employee AS OBJECT
(
ID NUMBER,
NAME VARCHAR2(100 BYTE)
)

create or replace
TYPE Employee_TABLE
AS TABLE OF Employee;


有存储过程如下:

create or replace
PROCEDURE Employee_Process
(
FK_LIST IN Employee_TABLE
) AS
BEGIN
FOR I IN 1..FK_LIST.COUNT LOOP
--操作
END LOOP;
COMMIT;
END Employee_Process;


自定义handler

import ...

//这个jdbcType对应mapper文件中对应的jdbcType
//如果此处不做配置也可以在mybatis-config.xml中配置
@MappedJdbcTypes(JdbcType.ARRAY)
public class MyHandler implements TypeHandler {

public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {

//获取OracleConnection
C3P0NativeJdbcExtractor cp30NativeJdbcExtractor = (C3P0NativeJdbcExtractor) new C3P0NativeJdbcExtractor();
OracleConnection oracleConn=(OracleConnection)cp30NativeJdbcExtractor.getNativeConnection(ps.getConnection());
//这个parameter就是我们自己在mapper中传入的参数
List<Object> dto = (ArrayList<Object>) parameter;
StructDescriptor sd = new StructDescriptor("Employee",oracleConn);
STRUCT[] result = new STRUCT[dto.size()];
for(int index = 0; index < dto.size(); index++){
AccountCommissionDetailDto d = dto.get(index);
Object[] o = new Object[3];
o[0] = new Long("1"); //id
o[1] = new String("aaa"); //name
result[index] = new STRUCT(sd,oracleConn,o);
}
ArrayDescriptor des_Employee_TABLE = ArrayDescriptor.createDescriptor("Employee_TABLE",oracleConn);
ARRAY oracle_array = new ARRAY(des_Employee_TABLE,oracleConn,result);
ps.setArray(i, oracle_array);
}

public Object getResult(ResultSet rs, String columnName) throws SQLException {
log.error("no result!");
return null;
}

public Object getResult(CallableStatement cs, int columnIndex) throws SQLException {
log.error("no result!");
return null;
}
}


mybatis-config.xml中加入如下的代码声明自定义handler
如果没有在Handler中注释jdbcType也可在这里设置

<typeHandlers>
<typeHandler javaType="list" <!--jdbcType="ARRAY"--> handler="com.package.MyHandler"/>
</typeHandlers>


最后只要在mapper中做如下设置就能使用了

#{dtos,jdbcType=ARRAY,javaType=list}

或者直接定义需要调用的Handler的类型

#{dtos,jdbcType=ARRAY,javaType=list,typeHandler=com.package.MyHandler}
<think> 我们正在解决MyBatis传入对象类型时出现"could not determine data type of parameter $2"的错误。这个错误通常发生在PostgreSQL(或Greenplum)的JDBC驱动无法推断SQL语句中参数类型时,特别是当传入参数是一个对象(如Java Bean)并且SQL语句中使用了该对象的属性时。 根据引用[1]的提示,错误信息为“could not determine data type of parameter $1”,这与我们遇到的$2类似,只是参数位置不同。因此,我们可以推断这是一个参数类型推断失败的问题。 在MyBatis中,当我们传入一个对象作为参数,并在SQL语句中使用对象的属性(如#{obj.property})时,MyBatis会尝试将每个属性值转换为对应的JDBC类型。但是,如果某个属性的值类型在SQL语句中无法被正确推断(例如,在复杂表达式或函数中),就可能出现上述错误。 解决方案: 1. **在SQL表达式中显式指定参数类型**: 在SQL语句中,对于可能引起类型推断问题的参数,使用`#{}`的`typeHandler`属性或者`jdbcType`属性来指定类型。 例如: ```xml <delete id="deleteStatisticalInfo"> DELETE FROM table_name t WHERE t.id = #{id, jdbcType=BIGINT} AND t.info LIKE CONCAT(#{prefix, jdbcType=VARCHAR}, '%') </delete> ``` 2. **在对象属性上使用@Param注解指定类型**(如果使用@Param注解): 如果我们在Mapper接口的方法中使用@Param注解传入对象,可以在注解中指定类型别名,但这通常不是必须的。更常见的是在SQL映射中指定。 3. **使用类型处理器(TypeHandler)**: 如果某个属性是自定义类型,我们可以为该类型编写一个TypeHandler,并在映射中指定它。 例如: ```xml <delete id="deleteStatisticalInfo"> DELETE FROM table_name t WHERE t.id = #{id} AND t.info LIKE CONCAT(#{prefix, typeHandler=org.apache.ibatis.type.StringTypeHandler}, '%') </delete> ``` 注意:这里我们直接使用了内置的StringTypeHandler,如果是自定义类型,则使用自定义TypeHandler。 4. **在配置中设置全局的jdbcTypeForNull**(如果问题与null值有关): 在mybatis-config.xml中设置: ```xml <configuration> <settings> <setting name="jdbcTypeForNull" value="NULL"/> </settings> </configuration> ``` 这样,当参数为null时,就会指定为JDBC的NULL类型(而不是OTHER)。 5. **检查属性类型与数据库类型的匹配**: 确保Java对象的属性类型与数据库字段类型能够正确映射。例如,数据库字段为VARCHAR,那么对应的Java属性应该是String类型。 6. **升级MyBatis和数据库驱动**: 引用[2]提到了MyBatis自动配置的条件,引用[3]提到了MapperFactoryBean的注入机制。虽然这些与当前错误没有直接关系,但我们可以考虑升级MyBatis和数据库驱动到最新版本,以修复可能存在的类型推断BUG。 7. **使用CAST或CONVERT函数强制转换**: 在SQL语句中直接对参数进行类型转换: ```sql AND t.info LIKE CONCAT(CAST(#{prefix} AS VARCHAR), '%') ``` 8. **检查参数位置**: 确保传入参数顺序与SQL中的参数位置一致。如果使用对象,那么属性映射是通过名称而非位置。 9. **使用命名参数(Named Parameters)**: 在MyBatis的Mapper接口中,使用@Param注解给参数命名,然后在SQL中引用该名称。这样可以避免位置混乱。 例如: ```java int deleteStatisticalInfo(@Param("id") Long id, @Param("prefix") String prefix); ``` 10. **调试SQL日志**: 启用MyBatis的SQL日志,查看实际发送到数据库的SQL语句和参数,有助于定位问题。 针对传入对象类型的情况,我们通常采用第一种方法(在SQL映射中指定jdbcType)或第七种方法(使用CAST函数)来解决。 示例(传入对象参数): 假设我们有一个对象StatisticalInfo,包含id(Long)和prefix(String)属性。 Mapper接口: ```java int deleteStatisticalInfo(@Param("param") StatisticalInfo param); ``` XML映射: ```xml <delete id="deleteStatisticalInfo"> DELETE FROM table_name t WHERE t.id = #{param.id, jdbcType=BIGINT} AND t.info LIKE CONCAT(#{param.prefix, jdbcType=VARCHAR}, '%') </delete> ``` 注意:这里我们给每个参数都指定了jdbcType,这样JDBC驱动就能明确知道参数类型。 如果问题仍然存在,可以考虑将参数先转换为字符串(在Java代码中)再传入,或者在SQL中使用CAST函数。 另外,引用[3]中提到了MapperFactoryBean的注入机制,这提醒我们检查MyBatis的配置是否正确,确保SqlSessionTemplate已经被正确注入。如果配置有问题,可能会导致参数处理异常,但这种情况比较少见。 因此,我们优先尝试在SQL映射中显式指定jdbcType,这是解决该问题的最直接方法。 总结步骤: 1. 在SQL映射文件的参数中明确指定jdbcType。 2. 如果使用复杂表达式,考虑在SQL中使用CAST函数转换参数类型。 3. 检查属性类型与数据库类型的匹配性。 4. 升级驱动和MyBatis版本。 相关问题: §§相关问题§§ 1. 如何在MyBatis中为复杂类型(如JSONB)编写自定义TypeHandler? 2. MyBatis的jdbcType枚举有哪些可选值?它们分别对应什么数据库类型? 3. 在MyBatis中,使用@Param注解和直接传入对象参数处理上有何区别? </think> ### 解决 MyBatis 传入对象类型时报 "could not determine data type of parameter $2" 的方法 #### 1. **Mapper XML 中显式指定参数类型** 在 `#{}` 占位符中声明 `jdbcType` 和 `javaType`: ```xml <select id="findUser" resultType="User"> SELECT * FROM users WHERE name = #{user.name, jdbcType=VARCHAR} AND age = #{user.age, jdbcType=INTEGER} <!-- 关键解决点 --> </select> ``` - **原理**:避免 JDBC 驱动自动推导类型失败[^1] - 完整类型映射表: | Java 类型 | jdbcType | 常见场景 | |-------------------|-------------------|---------------------| | String | VARCHAR/NVARCHAR | 文本字段 | | Integer/Long | INTEGER/BIGINT | 数字主键 | | java.util.Date | TIMESTAMP | 时间字段 | | Boolean | BOOLEAN | 状态标记 | #### 2. **参数对象添加类型注解** 在 DTO 类中使用 `@Param` 注解: ```java public class UserQuery { @Param(value = "name", jdbcType = JdbcType.VARCHAR) private String name; @Param(value = "age", jdbcType = JdbcType.INTEGER) private Integer age; } ``` #### 3. **全局配置 jdbcTypeForNull** 在 `mybatis-config.xml` 中配置: ```xml <configuration> <settings> <!-- 解决空值导致类型推导失败 --> <setting name="jdbcTypeForNull" value="NULL"/> </settings> </configuration> ``` #### 4. **使用类型处理器 (TypeHandler)** 为复杂类型自定义处理器: ```java @MappedJdbcTypes(JdbcType.OTHER) public class JsonTypeHandler extends BaseTypeHandler<Map<String,Object>> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Map<String,Object> map, JdbcType jdbcType) { ps.setObject(i, JSON.toJSONString(map), Types.OTHER); // 明确指定SQL类型 } } ``` 在 XML 中应用: ```xml <resultMap id="userMap" type="User"> <result column="properties" property="attributes" typeHandler="com.example.JsonTypeHandler"/> </resultMap> ``` #### 5. **SQL 层强制类型转换** 对不确定类型参数使用 CAST: ```sql SELECT * FROM orders WHERE create_time > CAST(#{filter.startDate} AS TIMESTAMP) <!-- 类型强转 --> AND amount < CAST(#{filter.maxAmount} AS NUMERIC) ``` #### 6. **检查 MyBatis 自动装配** 确保符合引用[2][3]的装配条件: 1. 验证 `SqlSessionFactoryBean` 存在: ```java @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } ``` 2. 确认 `MapperFactoryBean` 注入模式为 `AUTOWIRE_BY_TYPE`(默认) #### 错误原因分析 ```mermaid graph LR A[传入Java对象] --> B{MyBatis解析} B --> C[对象属性映射] C --> D[$1参数类型推导成功] C --> E[$2参数推导失败] E --> F[jdbcType=VARCHAR缺失] E --> G[复杂类型未处理] E --> H[空值未声明类型] ``` #### 最佳实践 ```diff - 错误写法: AND name = #{user.name} + 正确写法: AND name = #{user.name, jdbcType=VARCHAR} AND age = #{user.age, jdbcType=INTEGER} AND create_time = CAST(#{user.createTime} AS TIMESTAMP) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值