Spring JdbcTemplate解决MySQL的check约束无效

本文介绍如何在MySQL中通过业务逻辑实现字段值的有效性检查,包括表设计、JdbcTemplate配置、DAO层实现及异常处理等步骤。

场景分析

在MySQL中如果要检查某个字段的值是否在指定区间,或许有些伙伴会想到check约束,但是在MySQL中check约束是无效的,所以该篇博客将要解决在MySQL中check无效的问题

解决方案

因为MySQL无法使用check约束,那么只能在业务逻辑中自己编码来实现约束

1、首先模拟出一张数据表,方便测试
mysql> use sms;
Database changed
mysql> select * from student;
+----+------+----------+--------+------+-------+
| id | num  | password | name   | age  | score |
+----+------+----------+--------+------+-------+
|  1 | 0001 | 111      | pink   |   21 |    98 |
|  2 | 11   | 11       | ee     |   12 |   -45 |
|  3 | 0004 | 123      | linjie |   20 |    99 |
|  4 | 11   | 11       | ww     |   11 |   -89 |
|  5 | 007  | 123      | pojp   |   23 |    99 |
|  6 | 008  | 123      | trtp   |   23 |    99 |
|  7 | 009  | 123      | QQQp   |   23 |    99 |
|  8 | 007  | 123      | pojp   |   23 |    99 |
|  9 | 008  | 123      | trtp   |   23 |    99 |
| 10 | 009  | 123      | QQQp   |   23 |    99 |
| 11 | 007  | 123      | pojp   |   23 |    99 |
| 12 | 008  | 123      | trtp   |   23 |    99 |
| 13 | 009  | 123      | QQQp   |   23 |    99 |
| 14 | 007  | 123      | pojp   |   23 |    99 |
| 15 | 008  | 123      | trtp   |   23 |    99 |
| 16 | 009  | 123      | QQQp   |   23 |    99 |
+----+------+----------+--------+------+-------+
2、配置JdbcTemplate

至于你用什么来操作数据库,可自行试验,笔者这里采用JdbcTemplate


.

db.properties

jdbc.user=root
jdbc.password=1111
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///sms

jdbc.initPoolSize=5
jdbc.maxPoolSize=10

applicationContext

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
        <context:component-scan base-package="com.linjie"></context:component-scan>

        <!-- 导入资源文件
            读取db.properties文件中的数据 -->
        <context:property-placeholder location="classpath:db.properties"/>

        <!-- 配置C3P0数据源 -->
        <bean id="dataSource"
            class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="user" value="${jdbc.user}"></property>
            <property name="password" value="${jdbc.password}"></property>
            <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
            <property name="driverClass" value="${jdbc.driverClass}"></property>

            <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
            <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
        </bean>

        <!-- 配置Spring的jdbcTemplate 
            并注入一个dataSource数据源-->
        <bean id="jdbcTemplate"
            class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>    
        </bean>
    </beans>
3、定义Dao接口和Dao实现类

IUserDao接口

package com.linjie.tx;
/**
* @author LinJie E-mail:xulinjie0105@gmail.com
* @version 创建时间:2018年5月12日 下午12:11:28
* Dao接口
*/
public interface IUserDao {
    //修改用户年龄
    public void changeAge(int score,int id) ;
}

UserDaoImpl实现类

这里就是解决check无效的核心业务逻辑(笔者对于重要的代码都采用图片贴出)
这里写图片描述

4、定义分数异常类,实现RuntimeException
package com.linjie.tx;
/**
* @author LinJie E-mail:xulinjie0105@gmail.com
* @version 创建时间:2018年5月12日 下午1:32:55
* 异常类
*/
public class ScoreException extends RuntimeException {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public ScoreException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public ScoreException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public ScoreException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public ScoreException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public ScoreException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}
5、测试(测试数据是将成绩减少200)
package com.linjie.tx;
/**
* @author LinJie E-mail:xulinjie0105@gmail.com
* @version 创建时间:2018年5月12日 下午12:26:53
* 测试
*/

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class txTest {
    private ApplicationContext context = null;
    private IUserDao iUserDao;

    {
        context = new ClassPathXmlApplicationContext("applicationContext.xml");
        iUserDao = context.getBean(IUserDao.class);
    }

    @Test
    public void test() {
        iUserDao.changeAge(200, 1);
    }
}
6、结果

这里写图片描述

可以看出减去200在核心检查逻辑中是不行的,ok,解决check在mysql的无效问题完成

if(selectScore < score) {
        throw new ScoreException("分数异常");
    }
Exception in thread "main" org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [insert into user values(null, ?, ?)]; Parameter index out of range (3 > number of parameters, which is 2).; nested exception is java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2). at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:110) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1443) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633) at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:862) at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:917) at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:927) at dao.TestDaoImpl.update(TestDaoImpl.java:21) at service.TestServiceImpl.testJDBC(TestServiceImpl.java:24) at config.TestJDBC.main(TestJDBC.java:14) Caused by: java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2). at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:64) at com.mysql.cj.jdbc.ClientPreparedStatement.checkBounds(ClientPreparedStatement.java:1345) at com.mysql.cj.jdbc.ClientPreparedStatement.getCoreParameterIndex(Cli
最新发布
10-19
<think>我们正在处理一个Spring框架中的异常:TransientDataAccessResourceException,具体错误信息是“Parameter index out of range (3 > number of parameters, which is 2)”。这个异常通常发生在使用SpringJdbcTemplate执行SQL语句时,设置的参数个数与SQL语句中的参数占位符(通常是问号`?`)数量不匹配。 根据引用[1]、[2]、[3]中的错误信息,我们可以看到类似的错误都是由于参数占位符和实际提供的参数数量不一致造成的。例如: - 引用[1]:参数索引超出范围(1>0),即SQL语句中没有参数占位符,但代码中提供了1个参数。 - 引用[2]:参数索引超出范围(2>1),即SQL语句中只有1个占位符,但代码中提供了2个参数。 - 引用[3]:参数索引超出范围(1>0),即SQL语句中没有占位符,但代码中尝试设置1个参数。 现在用户遇到的错误是:参数索引3超出了范围,因为实际参数个数只有2。这意味着在SQL语句中,我们试图设置第3个参数(索引从1开始),但SQL语句中只有2个占位符。 解决步骤: 1. **检查SQL语句中的参数占位符数量**:数一下SQL语句中问号(`?`)的数量。例如,如果SQL语句是`update table set col1=?, col2=? where id=?`,那么应该有3个占位符。 2. **检查代码中提供的参数数量**:在调用JdbcTemplate方法(如`update`, `query`等)时,传递的参数数组或参数列表的大小必须与占位符数量一致。 3. **注意:参数索引从1开始**:错误信息中的索引是从1开始的,所以第3个参数对应的索引是3,而实际提供的参数只有2个,所以会报错。 4. **常见原因**: - SQL语句中的占位符数量与传递的参数数量不一致(通常是占位符数量多于参数数量)。 - 在SQL语句中使用了中文问号或者其他全角问号(应该使用英文半角问号`?`)。 - 在SQL语句中,问号被包含在字符串常量中(例如,在SQL字符串中写了一个问号作为字符串内容,而不是占位符),导致占位符计数错误。 - 在SQL语句中使用了存储过程调用(call语句)并且参数占位符使用不正确。 5. **解决方法**: - 仔细核对SQL字符串中的占位符个数和传递的参数个数。 - 如果使用的是命名参数(如`NamedParameterJdbcTemplate`),则要确保每个命名参数都有对应的值,且没有多余的参数。 6. **调试技巧**: - 打印出要执行的SQL语句,检查问号的数量。 - 打印出传递的参数数组的长度和内容。 - 如果使用IDE,可以在执行SQL语句的代码行设置断点,查看SQL字符串和参数数组。 根据引用[4]:`JdbcTemplate.query(String sql, Object[] args, RowMapper<T> rowMapper)`方法中,`args`参数数组的长度必须与SQL语句中的参数占位符数量一致。 示例:如果SQL语句有两个占位符,那么`args`数组的长度必须是2。如果出现3>2的错误,说明数组长度是2,但代码中试图设置第3个参数,这通常是因为SQL语句中出现了3个占位符,而实际上只提供了2个参数?不对,错误信息是索引3大于参数总数2,即SQL中只有2个占位符,但代码中试图设置第3个参数。所以,应该是代码中传递的参数数组长度大于占位符数量?或者代码中错误地多次设置了参数? 但是,在JdbcTemplate中,我们是通过数组一次传入所有参数的,所以更可能的原因是SQL语句中只有2个占位符,但代码中传递的参数数组长度却是3?不对,错误信息说参数数量是2(即占位符数量是2),但试图设置第3个参数(索引3),这种情况不太可能由参数数组长度大于占位符数量引起,因为JdbcTemplate内部会按参数数组长度去设置对应索引的参数(从1到数组长度),如果数组长度大于占位符数量,那么在设置第3个参数时发现占位符只有2个,就会报错。 因此,我们得出结论:SQL语句中只有2个占位符,但传递的参数数组却包含3个元素。所以,解决方法就是使两者数量相等。 另一种可能是:在同一个PreparedStatement中,多次设置参数而没有清除之前的设置?但通常我们不会直接操作PreparedStatement,而是通过JdbcTemplate来设置参数数组。 还有一种情况:动态生成SQL语句时,可能因为条件拼接导致占位符数量变化,而参数数组没有相应调整。 因此,我们需要: - 检查SQL语句字符串,确认其中的占位符(问号)数量。 - 检查传递给JdbcTemplate方法的参数数组(或可变参数)的长度。 例如,假设SQL语句如下: String sql = "UPDATE table SET col1=?, col2=? WHERE id=?"; // 这里有3个占位符 然后调用: jdbcTemplate.update(sql, new Object[]{value1, value2}); // 只提供了2个参数,但需要3个 这样就会报错,因为占位符有3个,但只提供了2个参数。 但是,用户当前的错误是“3>2”,即占位符只有2个,但试图设置第3个参数。所以,可能是代码中传递了3个参数,但SQL中只有2个占位符。 例如: String sql = "UPDATE table SET col1=?, col2=?"; // 2个占位符 jdbcTemplate.update(sql, value1, value2, value3); // 传递3个参数 或者: jdbcTemplate.update(sql, new Object[]{value1, value2, value3}); // 数组长度为3 这样,当JdbcTemplate尝试设置第3个参数时,发现SQL中只有2个占位符,就会抛出异常。 因此,请用户检查这两方面,确保数量一致。 另外,还要注意SQL语句中是否在注释或字符串中包含了问号?因为注释中的问号不会被当作占位符,但是字符串中的问号(在单引号内)会被当作普通字符,也不会被解析为占位符。所以,如果SQL语句中包含了字符串常量,而字符串常量中又有问号,那么这些问号不会被算作占位符。因此,需要确保占位符都是在需要绑定参数的位置。 最后,建议使用命名参数(NamedParameterJdbcTemplate)来避免这种位置绑定带来的问题。使用命名参数时,参数通过Map传递,占位符使用冒号加参数名(如`:param`),这样就不需要担心参数的位置和个数严格匹配的问题,只需要保证每个命名参数都有对应的值即可。 但是,如果仍然想使用基于位置的参数,那么请确保占位符数量和参数数组长度严格一致。 总结步骤: 1. 检查SQL语句,数一下问号(`?`)的数量。 2. 检查传递的参数数组(或参数列表)的长度。 3. 调整两者,使其数量相等。 如果错误仍然存在,检查SQL语句字符串是否在运行时被修改(比如动态拼接SQL),导致占位符数量变化。 另外,引用中的例子表明,有时候错误是因为调试好的SQL没有放到代码里(引用[1]),所以也要确保代码中运行的SQL是期望的SQL。 因此,请用户按照以下步骤操作: - 在代码中打印出要执行的SQL语句(注意:不要打印出包含敏感信息的日志,如果涉及敏感信息,可以在测试环境中使用测试数据)。 - 在代码中打印出参数数组的长度和每个参数的值(同样注意敏感信息)。 - 核对打印的SQL语句中的问号数量与参数数组长度。 示例代码: System.out.println("SQL: " + sql); System.out.println("Parameters count: " + args.length); for (int i=0; i<args.length; i++) { System.out.println("Parameter " + (i+1) + ": " + args[i]); } 然后观察日志,看是否一致。 如果使用IDE调试,可以直接查看这些值。 如果发现不一致,修改SQL语句或参数数组,使其数量一致。 如果使用动态SQL拼接(比如根据条件添加不同的筛选条件),那么需要动态地构建参数数组,确保每增加一个占位符就增加一个参数。 如果问题复杂,建议重构为使用NamedParameterJdbcTemplate。 根据引用[4],我们还可以考虑使用`SqlParameterValue`来显式指定参数类型,但这不会影响参数个数,所以不是解决个数不一致的办法。 综上所述,解决这个异常的关键是确保SQL语句中的参数占位符数量与传递的参数数量严格一致。 </think>### Spring框架中解决`TransientDataAccessResourceException`异常的方法 当出现`PreparedStatementCallback`的`TransientDataAccessResourceException`异常,且错误信息为 **"Parameter index out of range (3 > number of parameters, which is 2)"** 时,表示SQL语句中的参数占位符数量与代码传入的参数数量不匹配。以下是系统化的解决步骤: --- #### **1. 检查SQL语句中的占位符数量** 错误明确表示:**实际需要3个参数,但SQL语句仅声明了2个占位符**(`?`)。 - **验证SQL字符串**:检查执行的SQL语句(如`update account set col1=?,col2=? where id=?`),确认`?`的数量是否为**3个**。 - **常见错误**: - 占位符被遗漏(如`WHERE id=?`漏写`?`)。 - 使用中文问号(`?`)而非英文问号(`?`),导致占位符未被识别[^1][^3]。 - 在字符串文本中误用`?`(如`VALUES('?')`),此类`?`不会被解析为占位符。 --- #### **2. 检查代码传入的参数数量** 根据引用[4],Spring的`JdbcTemplate`要求传入的参数数组长度必须与SQL占位符数量严格匹配: ```java jdbcTemplate.update( "UPDATE table SET col1=?, col2=? WHERE id=?", // 3个占位符 new Object[]{value1, value2, value3} // 必须传入3个参数 ); ``` - **错误示例**: ```java // SQL有3个占位符,但只传入2个参数 → 触发异常 jdbcTemplate.update("UPDATE... WHERE id=?", new Object[]{value1, value2}); ``` --- #### **3. 验证参数绑定方式** - **避免索引错误**:确保参数索引从`1`开始,依次递增绑定。 - **动态SQL问题**:若SQL通过字符串拼接动态生成(如根据条件添加`AND`子句),需同步调整参数数组长度: ```java String sql = "SELECT * FROM table WHERE 1=1"; List<Object> params = new ArrayList<>(); if (condition) { sql += " AND col = ?"; // 动态添加占位符 params.add(value); // 同步添加参数 } jdbcTemplate.query(sql, params.toArray(), rowMapper); ``` --- #### **4. 使用`NamedParameterJdbcTemplate`替代(推荐)** 改用命名参数可避免位置索引错误(Spring官方文档推荐): ```java NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(dataSource); Map<String, Object> params = new HashMap<>(); params.put("id", 1001); params.put("name", "test"); params.put("email", "test@example.com"); namedTemplate.update( "UPDATE user SET name=:name, email=:email WHERE id=:id", params // 参数名与占位符匹配即可,顺序无关 ); ``` --- #### **5. 调试与验证** 1. **打印SQL和参数**:在DAO层日志输出实际执行的SQL和参数数组长度。 ```java logger.debug("SQL: {}, Params: {}", sql, Arrays.toString(args)); ``` 2. **检查SQL注入点**:确认SQL语句未被其他组件(如AOP、拦截器)意外修改。 3. **数据库兼容性**:某些数据库驱动对存储过程调用(`{call ...}`)的占位符解析存在差异,需按文档规范编写。 --- ### 相关问题 1. 如何动态构建安全的SQL语句以防止参数绑定错误? 2. `NamedParameterJdbcTemplate`在处理批量更新时有哪些注意事项? 3. Spring Data JDBC与MyBatis在参数绑定机制上有何区别? [^1]: 引用自调试场景中SQL与代码不匹配的典型错误 [^2]: 参数索引超出范围的实际案例 [^3]: 占位符数量为0但试图设置参数的异常堆栈 [^4]: Spring官方关于`JdbcTemplate.query()`的参数规范说明
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值