[英]Inconsistent results from java Calendar manipulation
我有一個cron工作來清除超過某個月數(由用戶設置)的數據。 cron作業調用purgeData()
方法,該方法從postgres表中清除數據。 我從當前日期(通過GregorianCalendar.getInstance
)操作java Calendar
來確定在之前刪除數據的目標日期。
我的問題是日歷操作和/或將新操作的日歷轉換為字符串以便在postgres中使用偶爾會失敗,將目標日期設置為刪除當前日期之前的所有內容的當前日期,而不是早於1的數據(或#數月保持數月)。
這是我簡單的日期格式:
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-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()應該強制重新計算。
你的dateformatter不是線程安全的,將它存儲在一個類變量中,讓多個線程同時敲擊它會給你帶來無效的結果。
這在SimpleDateFormat的API文檔中記錄,標題為Synchronization:
日期格式未同步。 建議為每個線程創建單獨的格式實例。 如果多個線程同時訪問格式,則必須在外部進行同步。
一個解決方法是讓您的方法創建自己的SimpleDateFormatter實例。 還有更多選項,在相關問題中列出: 制作DateFormat Threadsafe。 使用什么,同步或線程本地 。
但是,不需要格式化日期,而是可以將日期作為參數傳遞給PreparedStatement:
sqlString = "DELETE FROM \"" + tableName + "\" WHERE datetime < ?";
pst = con.prepareStatement(sqlString);
pst.setTimestamp(1, new Timestamp(startDate.getTime()));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.