Mybatis访问PostgreSql异常:PSQLException: 错误: 无法确定参数 $1 的数据类型

遇到的问题

java 复制代码
List<Order> selectByCriteria(@Param("orderDto") OrderDto orderDto, @Param("pageSize")Integer pageSize, @Param("offset")Integer offset);

xml文件的SQL语句已经指定jdbcType=VARCHAR:

xml 复制代码
	<if test="orderDto.cityName != null and orderDto.cityName != ''">
	  and f_sa_name like concat('%', #{orderDto.saName,javaType=String,jdbcType=VARCHAR}, '%')
	</if>,

查询PostgreSQL时依然报错:

nested exception is org.postgresql.util.PSQLException: 错误: 无法确定参数 $1 的数据类型"

错误原因分析

分析错误发生的位置和时序

bash 复制代码
// 1. MyBatis 准备参数(这一步正常)
MyBatis: 将 #{saName, jdbcType=VARCHAR} 转换为 JDBC 参数
MyBatis: 设置参数值 "淄博市" 到 PreparedStatement 的第1个位置
		↓ 成功

// 2. PostgreSQL 执行 SQL(这一步出错)
PostgreSQL: 收到 SQL: SELECT * FROM t_order WHERE f_sa_name like concat('%s', $1, '%s')
PostgreSQL: 尝试推断 $1 的类型
PostgreSQL: ❌ 错误!无法确定 $1 的数据类型

jdbcType是MyBatis、JDBC驱动和数据库之间的"翻译官",确保三方对数据类型有一致的理解。更准确的讲,jdbcType是给JDBC的,不是给PostgreSQL 解析器的。PostgreSQL在解析SQL时,按顺序处理参数。如果参数的第一次出现是在无法推断类型的位置(如OR条件左侧、函数参数、CASE语句等),即使指定了jdbcType,PostgreSQL也无法确定类型。

验证错误归属的测试

java 复制代码
public class ErrorAttributionTest {
    
    public static void main(String[] args) throws SQLException {
	
        String url = "jdbc:postgresql://localhost:5432/test";
        String user = "postgres";
        String password = "******";
        
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            
            // 测试1:使用明确的类型 - 成功
            String sql1 = "SELECT * FROM users WHERE name = ?::text";
            try (PreparedStatement pstmt = conn.prepareStatement(sql1)) {
                pstmt.setString(1, "test");  // 明确设置类型
                pstmt.executeQuery();
                System.out.println("测试1成功:明确类型");
            }
            
            // 测试2:类型不明确 - 失败
            String sql2 = "SELECT * FROM users WHERE (? IS NULL OR name = ?)";
            try (PreparedStatement pstmt = conn.prepareStatement(sql2)) {
                pstmt.setString(1, "test");
                pstmt.setString(2, "test");
                
                try {
                    pstmt.executeQuery();
                } catch (PSQLException e) {
                    System.out.println("测试2失败:" + e.getMessage());
                    System.out.println("这是 PostgreSQL 错误,不是 MyBatis 错误");
                }
            }
            
            // 测试3:使用 JDBC 直接设置参数类型
            String sql3 = "SELECT * FROM users WHERE name = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(sql3)) {
                // JDBC 明确设置类型
                pstmt.setObject(1, "test", Types.VARCHAR);
                pstmt.executeQuery();
                System.out.println("测试3成功:JDBC 明确类型");
            }
        }
    }
}

常见问题场景和修复方案

场景1:参数在 OR 条件的左侧

xml 复制代码
<!-- ❌ 错误示例:$1 在 OR 左侧,无法推断类型 -->
<select id="findOrders" resultType="Order">
	SELECT * FROM orders 
	<where>
		<if test="saName != null">
			AND (#{saName, jdbcType=VARCHAR} IS NULL OR sa_name = #{saName, jdbcType=VARCHAR})
		</if>
	</where>
</select>

<!-- ✅ 修复:交换顺序,让有类型的字段在左侧 -->
<select id="findOrders" resultType="Order">
	SELECT * FROM orders 
	<where>
		<if test="saName != null">
			AND (sa_name = #{saName, jdbcType=VARCHAR} OR #{saName, jdbcType=VARCHAR} IS NULL)
		</if>
	</where>
</select>

场景2:参数在函数内部

xml 复制代码
<!-- ❌ 错误示例:参数在 CONCAT 函数内部 -->
<select id="searchOrders" resultType="Order">
	SELECT * FROM orders 
	WHERE order_no LIKE CONCAT('%', #{orderNo, jdbcType=VARCHAR}, '%')
</select>

<!-- ✅ 修复1:使用类型转换 -->
<select id="searchOrders" resultType="Order">
	SELECT * FROM orders 
	WHERE order_no LIKE CONCAT('%', #{orderNo, jdbcType=VARCHAR}::text, '%')
</select>

<!-- ✅ 修复2:使用 || 操作符(PostgreSQL 字符串连接) -->
<select id="searchOrders" resultType="Order">
	SELECT * FROM orders 
	WHERE order_no LIKE '%' || #{orderNo, jdbcType=VARCHAR} || '%'
</select>

<!-- ✅ 修复3:使用位置参数 -->
<select id="searchOrders" resultType="Order">
	SELECT * FROM orders 
	WHERE order_no LIKE '%' || ? || '%'
</select>

场景3:参数在 CASE 语句中

xml 复制代码
<!-- ❌ 错误示例:参数在 CASE 的 WHEN 部分 -->
<select id="getOrderStatus" resultType="String">
	SELECT 
		CASE 
			WHEN #{status, jdbcType=INTEGER} = 1 THEN '待处理'
			WHEN #{status, jdbcType=INTEGER} = 2 THEN '处理中'
			WHEN #{status, jdbcType=INTEGER} = 3 THEN '已完成'
			ELSE '未知'
		END as status_name
	FROM orders
</select>

<!-- ✅ 修复:使用子查询或临时表 -->
<select id="getOrderStatus" resultType="String">
	WITH status_param AS (
		SELECT #{status, jdbcType=INTEGER}::integer as status_val
	)
	SELECT 
		CASE 
			WHEN status_val = 1 THEN '待处理'
			WHEN status_val = 2 THEN '处理中'
			WHEN status_val = 3 THEN '已完成'
			ELSE '未知'
		END as status_name
	FROM status_param
</select>

场景4:参数在子查询中

xml 复制代码
<!-- ❌ 错误示例:参数在子查询中且位置不佳 -->
<select id="findByDept" resultType="Order">
	SELECT * FROM orders 
	WHERE dept_id IN (
		SELECT id FROM department 
		WHERE name LIKE CONCAT('%', #{deptName, jdbcType=VARCHAR}, '%')
	)
</select>

<!-- ✅ 修复:使用 JOIN 代替子查询 -->
<select id="findByDept" resultType="Order">
	SELECT o.* FROM orders o
	JOIN department d ON o.dept_id = d.id
	WHERE d.name LIKE '%' || #{deptName, jdbcType=VARCHAR} || '%'
</select>

场景5:多个参数在复杂表达式中

xml 复制代码
<!-- ❌ 错误示例:多个参数相互影响 -->
<select id="findByDateRange" resultType="Order">
	SELECT * FROM orders 
	WHERE 
		(#{startDate, jdbcType=TIMESTAMP} IS NULL OR create_time >= #{startDate, jdbcType=TIMESTAMP})
		AND (#{endDate, jdbcType=TIMESTAMP} IS NULL OR create_time <= #{endDate, jdbcType=TIMESTAMP})
</select>

<!-- ✅ 修复:使用 COALESCE 或 CASE -->
<select id="findByDateRange" resultType="Order">
	SELECT * FROM orders 
	WHERE 
		create_time >= COALESCE(#{startDate, jdbcType=TIMESTAMP}::timestamp, '1970-01-01')
		AND create_time <= COALESCE(#{endDate, jdbcType=TIMESTAMP}::timestamp, '9999-12-31')
</select>

终极解决方案:使用类型包装器

复制代码
如果上述方法都不行,可以创建一个专门处理 PostgreSQL 类型问题的包装器:
java 复制代码
public class PostgreSQLTypeHelper {

	/**
	 * 包装参数,确保 PostgreSQL 能推断类型
	 */
	public static String wrapParam(String paramName, String jdbcType) {
		return "#{" + paramName + ", jdbcType=" + jdbcType + "}::" + 
			   mapJdbcTypeToPostgresType(jdbcType);
	}
	
	private static String mapJdbcTypeToPostgresType(String jdbcType) {
		switch (jdbcType) {
			case "VARCHAR": return "text";
			case "INTEGER": return "integer";
			case "TIMESTAMP": return "timestamp";
			case "DATE": return "date";
			case "BOOLEAN": return "boolean";
			case "BIGINT": return "bigint";
			case "DECIMAL": return "numeric";
			default: return "text";
		}
	}
}

在 XML 中使用:

xml 复制代码
<select id="safeQuery" resultType="Order">
	SELECT * FROM orders 
	WHERE 
		-- 强制类型转换
		sa_name = #{saName, jdbcType=VARCHAR}::text
		AND status = #{status, jdbcType=INTEGER}::integer
		AND create_time >= #{startDate, jdbcType=TIMESTAMP}::timestamp
</select>

调试技巧

1、查看实际发送的 SQL:

yaml 复制代码
logging:
  level:
	org.postgresql: DEBUG
	com.your.mapper: TRACE

2、使用 PostgreSQL 的 PREPARE 语句测试:

sql 复制代码
-- 在 PostgreSQL 中手动测试
PREPARE test_stmt(text) AS 
SELECT * FROM orders WHERE sa_name = $1;

EXECUTE test_stmt('test');

DEALLOCATE test_stmt;

同样的 MyBatis 配置在不同数据库的表现:

xml 复制代码
<select id="test" resultType="map">
	SELECT * FROM users WHERE (? IS NULL OR name = ?)
</select>
复制代码
数据库		结果		原因
PostgreSQL	❌ 报错		需要明确类型
MySQL		✅ 成功		弱类型,自动转换
Oracle		✅ 成功		有默认类型推断机制
相关推荐
莫寒清3 小时前
MyBatis 与 MyBatis-Plus 的区别
面试·mybatis
亓才孓3 小时前
【MyBatis Plus】@Service标签应该放在ServiceImpl上(接口不可以实例化)
mybatis
笑我归无处15 小时前
Springboot+mybatisplus配置多数据源+分页
spring boot·后端·mybatis
海边的Kurisu16 小时前
Mybatis-Plus | 只做增强不做改变——为简化开发而生
java·开发语言·mybatis
zihan032118 小时前
若依(RuoYi)框架核心升级:全面适配 SpringData JPA,替换 MyBatis 持久层方案
java·开发语言·前端框架·mybatis·若依升级springboot
莫寒清19 小时前
MyBatis 中 ${} 和 #{} 有什么区别?
java·面试·mybatis
2301_780669861 天前
MyBatis(配置,增删改查,注解与XML两种开发方式)、SpringBoot配置文件(yml简化properties)
xml·spring boot·mybatis·javaweb
莫寒清1 天前
MyBatis 的缓存机制
面试·mybatis
Hx_Ma161 天前
mybatis练习2
java·数据库·mybatis