【问题标题】:Inconsistent results from java Calendar manipulationjava日历操作的结果不一致
【发布时间】:2015-04-21 19:48:13
【问题描述】:

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

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

这是我的简单日期格式:

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

这是我的方法:

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;
}

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

失败输出:

20150421-00:33:11.006 Postgres 通知 - 清除:# 保留数据的月份:1 目标日期(字符串):2015-04-21 00:00:00.001,开始月份:2,当前月份:3 成功:在 2015 年 4 月 21 日 00:00:00.001 # 行删除之前清除数据:7575704(# 行删除上次清除: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 之前清除数据 # 行已删除:12195291(# 行已删除上次清除:128570)

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

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

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

我已经阅读了 javadocs 在日历上设置字段时,必须调用 get() 才能重新计算时间。但是, add() 应该强制重新计算。

【问题讨论】:

    标签: java multithreading static calendar thread-safety


    【解决方案1】:

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

    这在标题同步下的API documentation for SimpleDateFormat 中有记录:

    日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,必须在外部同步。

    一种解决方法是让您的方法创建自己的 SimpleDateFormatter 实例。还有更多选项,在此处的相关问题中列出:Making DateFormat Threadsafe. What to use, synchronized or Thread local

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

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

    【讨论】:

    • +1。另一种解决方案是创建线程本地格式化程序,如 related question 中所述。
    • @DNA:是的,没错。我打算链接到这样的问题,但有多个问题,我一直在纠结选择哪一个。
    • 感谢您的回复!我将在我的方法中创建一个 SimpleDateFormat 实例。
    • 您可以完全摆脱 SimpleDateFormat,方法是更改​​ SQL 语句以使用绑定变量并将日期设置为从日历创建的 java.sql.Timestamp 值。
    • @user3745362:同意,格式化似乎没有必要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    相关资源
    最近更新 更多