来自Java Calendar操作的结果不一致

Ren*_*nee 3 java static multithreading calendar thread-safety

我有一个cron工作来清除超过某个月数(由用户设置)的数据.cron作业调用一个purgeData()方法来清除postgres表中的数据.我Calendar从当前日期(via GregorianCalendar.getInstance)操作java 来确定删除之前数据的目标日期.

我的问题是日历操作和/或将新操作的日历转换为字符串以便在postgres中使用偶尔会失败,将目标日期设置为删除当前日期之前的所有内容的当前日期,而不是早于1的数据(或#数月保持数月).

这是我简单的日期格式:

public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss.SSS");
Run Code Online (Sandbox Code Playgroud)

这是我的方法:

public String purgeData() throws ParseException {
    Connection con = null;
    String sqlString = "";
    PreparedStatement pst = null;
    String returnString = "";

    Calendar startDate = GregorianCalendar.getInstance();
    returnString += "# Months to keep data: " + getNumMonthsKeepData();
    startDate.add(Calendar.MONTH, getNumMonthsKeepData() * -1);
    String targetDate = dateFormatter.format(startDate.getTime());
    Calendar today = GregorianCalendar.getInstance();

    returnString +=" Target date (string): " + targetDate + " start date (Calendar): " + startDate.toString() + ", Start month: " + startDate.get(Calendar.MONTH) + ", Current month: " + today.get(Calendar.MONTH);

    if (startDate.get(Calendar.MONTH)!= today.get(Calendar.MONTH)) {

        String tableName = getPreviousMonthlyTable();
        try {
            con = getDBConnection();

            try {
                // Delete old data
                sqlString = "DELETE FROM \"" + tableName
                        + "\" WHERE  datetime < '" + targetDate + "'";

                pst = con.prepareStatement(sqlString);
                int rowsDeleted = pst.executeUpdate();
                returnString += "SUCCESS: Purged data prior to " + targetDate
                        + " # rows deleted: " + rowsDeleted
                        + "( # rows deleted last purge: "
                        + numRowsDeletedPreviously + " )\n";

            } catch (SQLException ex) {
                returnString += "FAILED to execute: " + sqlString;
            }

            try {
                if (pst != null) {
                    pst.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {
                return null;
            }
        } catch (SQLException ex) {
            returnString += "Delete from table fail: " + ex.getMessage();
        }
    } else {
        returnString += "FAIL:  Fail to delete data prior to: " + targetDate + ". Start month: " + startDate.get(Calendar.MONTH)
                + " equals current month: " + today.get(Calendar.MONTH);
    }
    return returnString;
}
Run Code Online (Sandbox Code Playgroud)

日期失败似乎是随机的,因为在一次部署成功时,它会在另一次部署失败.

失败输出:

20150421-00:33:11.006 Postgres通知 - 清除:#保存数据的月份:1目标日期(字符串):2015-04-21 00:00:00.001,开始月份:2,当前月份:3成功:先前清除数据至2015-04-21 00:00:00.001#rows deleted:7575704(#rows deleted last purge:26608)

注意:目标日期应为2015-03-21 00:00:30.000(请注意,由于cron作业从00:30开始每4小时运行一次,因此也是30分钟)

较旧的故障输出(在添加更多日志之前): 20150414-20:37:53.347 Postgres通知 - 清除:成功:2015-04-14之前的清除数据19:00:00.004#rows deleted:12195291(#rows删除最后净化:128570)

注意:之前的清除数据应该是2015-03-14 20:30:00.000(请注意,由于cron作业从00:30开始每4小时运行一次,因此也是1小时30分钟)

成功输出: 20150421-00:30:02.559 Postgres通知 - 清除:#个月保存数据:1目标日期(字符串):2015-03-21 00:30:00.003,开始月份:2,当前月份:3成功: 2015-03-21 00:30:00.003之前的清除数据#rows删除:139757(#rows删除最后清除:33344)

似乎日期操作实际上有效,如开始月份和当前月份的输出所示.在两种故障情况下,整数值都不同.但是,将字符串转换为SimpleDateFormat似乎是错误的.

我已经阅读了javadocs,因为在Calendar上设置字段时,必须调用get()来重新计算时间.但是,add()应该强制重新计算.

Nat*_*hes 5

你的dateformatter不是线程安全的,将它存储在一个类变量中,让多个线程同时敲击它会给你带来无效的结果.

这在SimpleDateFormatAPI文档中记录,标题为Synchronization:

日期格式未同步.建议为每个线程创建单独的格式实例.如果多个线程同时访问格式,则必须在外部进行同步.

一个解决方法是让您的方法创建自己的SimpleDateFormatter实例.还有更多选项,在相关问题中列出:制作DateFormat Threadsafe.使用什么,同步或线程本地.

但是,不需要格式化日期,而是可以将日期作为参数传递给PreparedStatement:

sqlString = "DELETE FROM \"" + tableName + "\" WHERE datetime < ?";
pst = con.prepareStatement(sqlString);
pst.setTimestamp(1, new Timestamp(startDate.getTime()));
Run Code Online (Sandbox Code Playgroud)