问题描述

一般公司都有DBA,DBA极有可能开启了Safe mode,也就是不支持不带索引条件过滤的update操作。

而Spring Batch /Cloud Task就有一张表 JOB_SEQ或者 TASK_SEQ的表,只有一条数据,也无法完成update操作。

Could not increment ID for BATCH_JOB_SEQ sequence table; nested exception is java.sql.SQLException:
You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

解决方案

如果是DBA提供的数据库,那么无论怎么换数据库都无法解决。除非自己提供一个开发者自建的私库(不在DBA管理中的)。

更好的解决方案是,替换Spring JDBC的MySQLMaxValueIncrementer

这个类由 DefaultDataFieldMaxValueIncrementerFactory 创建:

//DefaultDataFieldMaxValueIncrementerFactory.java
@Override
public DataFieldMaxValueIncrementer getIncrementer(String incrementerType, String incrementerName) {
DatabaseType databaseType = DatabaseType.valueOf(incrementerType.toUpperCase()); if (databaseType == DB2 || databaseType == DB2AS400) {
return new DB2SequenceMaxValueIncrementer(dataSource, incrementerName);
}
else if (databaseType == DB2ZOS) {
return new DB2MainframeSequenceMaxValueIncrementer(dataSource, incrementerName);
}
else if (databaseType == DERBY) {
return new DerbyMaxValueIncrementer(dataSource, incrementerName, incrementerColumnName);
}
else if (databaseType == HSQL) {
return new HsqlMaxValueIncrementer(dataSource, incrementerName, incrementerColumnName);
}
else if (databaseType == H2) {
return new H2SequenceMaxValueIncrementer(dataSource, incrementerName);
}
else if (databaseType == MYSQL) {
MySQLMaxValueIncrementer mySQLMaxValueIncrementer = new MySQLMaxValueIncrementer(dataSource, incrementerName, incrementerColumnName);
mySQLMaxValueIncrementer.setUseNewConnection(true);
return mySQLMaxValueIncrementer;
}
....
}

那么就要替换这个DefaultDataFieldMaxValueIncrementerFactory,代码如下:

package io.github.slankka.springbatch.safemode.patch;

import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory;
import org.springframework.batch.support.DatabaseType;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; import javax.sql.DataSource; /**
* project: springbatch safemode patch
* <br/>To prevent error: <br/>
* <code>
* Could not increment ID for BATCH_JOB_SEQ sequence table; <br/>
* nested exception is java.sql.SQLException: <br/>
* You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column <br/>
* </code>
*
* @author slankka on 2019/8/30.
*/
public class SafeModeMysqlIncreamentFactory extends DefaultDataFieldMaxValueIncrementerFactory { private DataSource dataSource;
private String incrementerColumnName = "ID"; public SafeModeMysqlIncreamentFactory(DataSource dataSource) {
super(dataSource);
this.dataSource = dataSource;
} @Override
public void setIncrementerColumnName(String incrementerColumnName) {
super.setIncrementerColumnName(incrementerColumnName);
this.incrementerColumnName = incrementerColumnName;
} @Override
public DataFieldMaxValueIncrementer getIncrementer(String incrementerType, String incrementerName) {
DatabaseType databaseType = DatabaseType.valueOf(incrementerType.toUpperCase());
if (databaseType == DatabaseType.MYSQL) {
SafeModeMysqlMaxValueIncreamenter mySQLMaxValueIncrementer = new SafeModeMysqlMaxValueIncreamenter(dataSource, incrementerName, incrementerColumnName);
mySQLMaxValueIncrementer.setUseNewConnection(true);
return mySQLMaxValueIncrementer;
}
return super.getIncrementer(incrementerType, incrementerName);
}
}

这里提供了SafeModeMysqlMaxValueIncreamenter 类,这个类就是解决问题的关键:

直接复制MySQLMaxValueIncrementer的代码,修改 stmt.executeUpdate 的SQL语句,尾部追加 where columnName > 0。

这样就能骗过 SafeMode检查。另外,如果这个字段不是主键,把他设置为主键即可。

 @Override
protected synchronized long getNextKey() throws DataAccessException {
if (this.maxId == this.nextId) {
/*
* If useNewConnection is true, then we obtain a non-managed connection so our modifications
* are handled in a separate transaction. If it is false, then we use the current transaction's
* connection relying on the use of a non-transactional storage engine like MYISAM for the
* incrementer table. We also use straight JDBC code because we need to make sure that the insert
* and select are performed on the same connection (otherwise we can't be sure that last_insert_id()
* returned the correct value).
*/
Connection con = null;
Statement stmt = null;
boolean mustRestoreAutoCommit = false;
try {
if (this.useNewConnection) {
con = getDataSource().getConnection();
if (con.getAutoCommit()) {
mustRestoreAutoCommit = true;
con.setAutoCommit(false);
}
} else {
con = DataSourceUtils.getConnection(getDataSource());
}
stmt = con.createStatement();
if (!this.useNewConnection) {
DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
}
// Increment the sequence column...
String columnName = getColumnName();
try {
stmt.executeUpdate("update " + getIncrementerName() + " set " + columnName +
" = last_insert_id(" + columnName + " + " + getCacheSize() + ") where " + columnName + " > 0");
} catch (SQLException ex) {
throw new DataAccessResourceFailureException("Could not increment " + columnName + " for " +
getIncrementerName() + " sequence table", ex);
}
// Retrieve the new max of the sequence column...
ResultSet rs = stmt.executeQuery(VALUE_SQL);
try {
if (!rs.next()) {
throw new DataAccessResourceFailureException("last_insert_id() failed after executing an update");
}
this.maxId = rs.getLong(1);
} finally {
JdbcUtils.closeResultSet(rs);
}
this.nextId = this.maxId - getCacheSize() + 1;
} catch (SQLException ex) {
throw new DataAccessResourceFailureException("Could not obtain last_insert_id()", ex);
} finally {
JdbcUtils.closeStatement(stmt);
if (con != null) {
if (this.useNewConnection) {
try {
con.commit();
if (mustRestoreAutoCommit) {
con.setAutoCommit(true);
}
} catch (SQLException ignore) {
throw new DataAccessResourceFailureException(
"Unable to commit new sequence value changes for " + getIncrementerName());
}
JdbcUtils.closeConnection(con);
} else {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}
} else {
this.nextId++;
}
return this.nextId;
}

使用方法

参见 slankka/spring-batch-safemode-patch

解决SpringBatch/Cloud Task的SafeMode下的报错问题的更多相关文章

  1. VS2010在WIN7下安装报错“下列组件安装失败”如何解决

    VS2010在WIN7下安装报错“下列组件安装失败”如何解决 http://www.111cn.net/net/42/75914.htm

  2. 解决centos7下 selenium报错--unknown error: DevToolsActivePort file doesn't exist

    解决centos7下 selenium报错--unknown error: DevToolsActivePort file doesn't exist 早上在linux下用selenium启动Chro ...

  3. Windows下nginx报错解决:CreateFile() "xxx/logs/nginx.pid" failed

    写在前面 本文给出Windows下nginx报错:CreateFile() "xxx/logs/nginx.pid" failed 的解决方法并分析了出错原因,其中 xxx 表示n ...

  4. 【转载】struts应用在断网情况下启动报错解决办法(java/net/AbstractPlainSocketImpl.java:178:-1)

    无意间struts应用在有网络的情况下启动正常,在断网的情况下启动报错,报错代码如下图所示: SEVERE: Exception starting filter struts2 Class: java ...

  5. .map文件的作用以及在chorme下会报错找不到jquery-1.10.2.min.map文件,404 的原因

    source map文件是js文件压缩后,文件的变量名替换对应.变量所在位置等元信息数据文件,一般这种文件和min.js主文件放在同一个目录下. 比如压缩后原变量是map,压缩后通过变量替换规则可能会 ...

  6. javascript的倒计时功能中newData().getTime()在iOS下会报错问题解决

    javascript的倒计时功能中newData().getTime()在iOS下会报错问题解决 在做移动端时间转化为时间戳时,遇到了一个问题,安卓手机上访问时,能拿到时间戳,从而正确转换时间,而在i ...

  7. centOS下yum报错

    CentOS下yum报错 备注:当我们在CentOS下使用yum命令的时候,会报一些错误,一下是我总结的几个解决问题的方法.(保证自己的服务器可以上网) 一.关于Loaded plugins: fas ...

  8. 解决Homestead yarn , npm run dev, 命令报错问题!

    解决Homestead yarn , npm run dev, 命令报错问题! 2018年06月01日 11:50:51 偶尔发发颠 阅读数:1654    版权声明:本文为博主原创,未经博主同意,不 ...

  9. ecstore在MySQL5.7下维护报错WARNING:512 @ ALTER IGNORE TABLE

    ecstore在MySQL5.7下维护报错WARNING:512 @ ALTER IGNORE TABLE 打开 /app/base/lib/application/dbtable.php , 替换A ...

随机推荐

  1. 建议收藏 - 专业的MySQL开发规范

    为了项目的稳定,代码的高效,管理的便捷,在开发团队内部会制定各种各样的规范 这里分享一份我们定义的MySQL开发规范,欢迎交流拍砖 数据库对象命名规范 数据库对象 命名规范的对象是指数据库SCHEMA ...

  2. 程序猿——踩bug之路

    从开始这就是一个新的坑,还好今天我们爬上了: 带着Ui界面的编程,最想感谢的是我的搭档乔美萱:此处我觉得需要掌声和尖叫,一路带我从走到飞: 一.结对编程项目:带UI的小初高数学学习软件 1.用户注册功 ...

  3. 每个人都要学的图片压缩终极奥义,有效解决 Android 程序 OOM

    # 由来 在我们编写 Android 程序的时候,几乎永远逃避不了图片压缩的难题.除了应用图标之外,我们所要显示的图片基本上只有两个来源: 来自网络下载 本地相册中加载 不管是网上下载下来的也好,还是 ...

  4. mysql操作遇到的坑(第二版)

    1.通过条件查询出上一条与下一条 sql说明:本表关联本表,然后通过其中一个表,查询出对应的条件,再用另外一个表求出上一条与下一条的数据,求出来的数据是多条的 SELECT ua.id, ua.wx_ ...

  5. Spring入门之AOP实践:@Aspect + @Pointcut + @Before / @Around / @After

    零.准备知识 1)AOP相关概念:Aspect.Advice.Join point.Pointcut.Weaving.Target等. ref: https://www.cnblogs.com/zha ...

  6. $(document).height 与$(window).height的区别

    $(document).scrollTop() 获取垂直滚动的距离 (即当前滚动的地方的窗口顶端到整个页面顶端的距离)$(document).scrollLeft() 这是获取水平滚动条的距离 要获取 ...

  7. Spring Cloud 版本控制

    ### 正常版本 ``` org.springframework.boot spring-boot-starter-parent 2.1.7.RELEASE ``` ### SpringCloud 版 ...

  8. Java8 Stream性能如何及评测工具推荐

    作为技术人员,学习新知识是基本功课.有些知识是不得不学,有些知识是学了之后如虎添翼,Java8的Stream就是兼具两者的知识.不学看不懂,学了写起代码来如虎添翼. 在上篇<Java8 Stre ...

  9. Springboot2.x + ShardingSphere 实现分库分表

    之前一篇文章中我们讲了基于Mysql8的读写分离(文末有链接),这次来说说分库分表的实现过程. 概念解析 垂直分片 按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用. 在拆分之前 ...

  10. X-扫描线算法

    多边形的扫描转换 多边形有两种重要的表示方法:顶点表示和点阵表示 顶点表示是用多边形的顶点序列来表示多边形.这种表示直观.几何意义强.占内存少,易于进行几何变换. 但由于它没有明确指出哪些象素在多边形 ...