我在使用Java计算数据库中的值时遇到了一些麻烦。
我试图按照日期从我的数据库计算一些值,但它们有小时,分钟和秒。如果我使用java.sql.Date
进行搜索,则只允许我输入年,月和日,因此不会计算日期为year"-"month"-31 (hour:minutes:seconds != 00:00:00)"
的值。
有没有办法为java.sql.Date
添加小时,分钟和秒?如果没有,那么它会有什么更好的方法呢?
我的代码:
public static int getAmountPos(Connection con, Integer month, Integer year) {
java.sql.Date date1 = java.sql.Date.valueOf(year+"-"+month+"-01");
java.sql.Date date2 = java.sql.Date.valueOf(year+"-"+month+"-31");
Integer amount = null;
String sql = "SELECT COUNT(*) AS 'cantidad' "
+ " FROM table.point_of_service "
+ " WHERE registration_date between ? AND ?";
PreparedStatement ps = con.getPreparedStatement(sql);
ps.setDate(1, date1);
ps.setDate(2, date2);
ResultSet rs = ps.executeQuery();
if(rs.next()) {
amount = rs.getInt("cantidad");
}
return amount;
}
答案 0 :(得分:5)
java.sql.Date
课程不打算保存有关时间的信息。它只包含日期(年,月,日)。您应该根据自己的需要使用java.sql.Timestamp
。
答案 1 :(得分:2)
更改代码以使用(a)java.time类型和(b)半开放方法来延长时间。
String sql = "SELECT COUNT(*) AS 'count_' "
+ "FROM table_.point_of_service_ "
+ "WHERE registration_date_ >= ? " // Greater-than-or-equal.
+ "AND WHERE registration_date_ < ? ; "; // Less-than (not 'or-equal') is Half-Open approach.
PreparedStatement ps = con.getPreparedStatement(sql);
ps.setObject( 1, YearMonth.of( 2016 , Month.FEBRUARY ).atDay( 1 ) ); // Calling `setObject` to handle `LocalDate` in JDBC 4.2 and later.
ps.setObject( 2, YearMonth.of( 2016 , Month.FEBRUARY ).plusMonths( 1 ).atDay( 1 ) );
java.sql类型仅用于将数据移入和移出数据库。通常不应该用于业务逻辑或操纵日期时间值。
java.sql.Date
类表示没有时间且没有时区的仅限日期的值。 java.sql.Time
相反,这是一个没有日期和时区的时间。 java.sql.Timestamp
。此值始终为UTC。对于日期时间,请致电getTimestamp
上的ResultSet
。
java.sql.Timestamp tsWhenRegistered = myResultSet.getTimestamp( "when_registered_" );
当然,您必须在数据库中调用具有匹配数据类型的列。
在Java 8及更高版本中,将java.time框架用于业务逻辑。
避免使用旧的java.util.Date/.Calendar类,因为它们已被证明是麻烦,混乱和有缺陷的。它们已被java.time类取代。
一旦从数据库中获取它们,就从java.sql类型转换为java.time类型。希望JDBC驱动程序将被更新以直接处理java.time类型,但在此之前自己进行转换。
Instant
Instant
是UTC时间轴上的一个时刻。
Instant instant = tsWhenRegistered.toInstant(); // From java.sql to java.time.
在您的上下文中应用时区(ZoneId
)。假设您的公司位于魁北克省。
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( zoneId );
YearMonth
现在,从您的问题中看到的java.time到java.sql的另一种方式。不要传递Integer
年份和月份的对象,而是使用YearMonth
类。请记住,这些java.time类现在已内置到Java中。因此,您可以在整个代码中使用它们。这为您提供了类型安全和编译器检查,并保证了有效值。
YearMonth yearMonth = YearMonth.of( 2016 , Month.FEBRUARY ); // Pass Month enum for type-safety and range-checking.
YearMonth yearMonth = YearMonth.of( 2016 , 2 ); // Or, pass month number 1-12.
重新执行问题中显示的方法,假设“when_registered_”是日期 - 时间值而不是日期,因为您似乎在您的问题中建议(不太清楚)。
public static int getAmountPos( Connection con, YearMonth yearMonth ) {
…
顺便提一下,提示:在数据库名称后附加下划线可以防止与SQL中的关键字发生冲突。 SQL标准明确承诺永远不会使用尾随下划线。
不要使用SQL BETWEEN
。在日期时间工作中,最佳实践是“半开放”方法,其中开头是包含,而结尾是独占。搜索逻辑开头为greater-than-or-equal
,结尾为less-than
(不小于或等于结尾)。这避免了确定分秒的结束日期的问题。
YearMonth
→LocalDate
从YearMonth
开始,我们可以将该月的第一天和下个月的第一天作为仅限日期(LocalDate
)值。
LocalDate localDateStart = yearMonthArg.atDay( 1 ); // Get first-of-month.
LocalDate localDateStop = yearMonthArg.plusMonths( 1 ).atDay( 1 ); // Get first of *next* month.
如果您的驱动程序符合JDBC 4.2或更高版本,您应该能够通过LocalDate
方法将这些PreparedStatement
对象传递给setObject
,并使用getObject
来检索数据
如果您的数据库列被定义为标准SQL DATE
类型或类似的仅限日期类型(没有时间,没有时区),则以下是示例代码。
…
String sql = "SELECT COUNT(*) AS 'count_' "
+ "FROM table_.point_of_service_ "
+ "WHERE registration_date_ >= ? " // Greater-than-or-equal.
+ "AND WHERE registration_date_ < ?"; // Less-than (not 'or-equal') is Half-Open approach.
PreparedStatement ps = con.getPreparedStatement(sql);
ps.setObject( 1, localDateStart ); // Calling `setObject` to handle `LocalDate` in JDBC 4.2 and later.
ps.setObject( 2, localDateStop );
…
如果您的驱动程序无法执行此操作,请使用java.sql.Date
对象替换。旧的日期时间类有新的方法来促进这种转换。在这里,我们需要java.sql.Date.valueOf
方法。从数据库中检索数据时,转向另一个方向,请致电toLocalDate
。
…
ps.setDate( 1, java.sql.Date.valueOf( localDateStart ) ); // Converting `LocalDate` to `java.sql.Date`.
ps.setDate( 2, java.sql.Date.valueOf( localDateStop ) );
…
LocalDate
+ ZoneId
→ZonedDateTime
→java.sql.Timestamp
如果您的数据库列不定义为仅日期类型,而是日期时间类型,那么我们需要使用日期时间对象而不是LocalDate
对象进行查询见上文。如果您的日期是针对UTC的话,则表示OffsetDateTime
个对象;如果您的日期在某个其他时区有意义,则表示ZonedDateTime
。
请记住,LocalDate
个对象没有任何实际意义,因为在任何特定时刻,日期都会因世界而异。我们必须应用时区来将每天的第一个时刻作为ZonedDateTime
个对象。请注意,由于夏令时和其他异常情况,一天中的第一个时刻并非总是00:00:00.0
。要从java.time转换为java.sql,我们从Instant
对象中提取ZonedDateTime
个对象,最后转换为java.sql.Timestamp
。
public Integer countForYearMonth ( Connection connArg , YearMonth yearMonthArg ) {
// CAUTION: Pseudo-code. Ignoring real-world issues such as closing resources and handling exceptions. Never run, so never tested.
LocalDate localDateStart = yearMonthArg.atDay( 1 ); // Get first-of-month.
LocalDate localDateStop = yearMonthArg.plusMonths( 1 ).atDay( 1 ); // Get first of *next* month.
// Give those LocalDate objects real meaning by applying a time zone.
ZoneId zoneId = ZoneId.of( "America/Montreal" ); // Perhaps pass as argument rather than hard-code a particular time zone.
ZonedDateTime zdtStart = localDateStart.atStartOfDay( zoneId ); // Inclusive of first moment of February… 2016-02-01T00:00:00.0-05:00[America/Montreal]
ZonedDateTime zdtStop = localDateStop.atStartOfDay( zoneId ); // Inclusive of first moment of March… 2016-03-01T00:00:00.0-05:00[America/Montreal]
// Business logic is complete. So convert to java.sql for database access.
java.sql.Timestamp tsStart = java.sql.Timestamp.from ( zdtStart.toInstant() ); // February 1st 2016 at 5 AM UTC.
java.sql.Timestamp tsStop = java.sql.Timestamp.from ( zdtStop.toInstant() ); // March 1st 2016 at 5 AM UTC.
Integer amount = null;
String sql = "SELECT COUNT(*) AS 'count_' "
+ "FROM some_table_ "
+ "WHERE when_registered_ >= ? " // Greater-than-or-equal.
+ "AND when_registered_ < ? " // Less-than (not 'or-equal') is Half-Open approach.
+ "; ";
PreparedStatement ps = connArg.getPreparedStatement(sql);
ps.setDate( 1, tsStart );
ps.setDate( 2, tsStop );
ResultSet rs = ps.executeQuery();
if( rs.next() ) {
amount = rs.getInt( "count_" );
}
return amount;
}
答案 2 :(得分:0)
我不同意那些说java.sql.date
没有时间价值的人。当您在构造函数中传递长时间值时,它将保存该时间信息。您不能操纵那些时间信息,因为这些方法已被弃用,但时间信息将持续存在且不会丢失或截断。
根据java documentation:
使用给定的毫秒时间值构造Date对象。如果给定的毫秒值包含时间信息,则驱动程序将时间组件设置为默认时区(运行应用程序的Java虚拟机的时区)中与零GMT对应的时间。
现在回答这个问题,你转换为Timestamp或Instant或者LocalDate来创建将在sql字符串中使用的完整日期。见StackOverflow for conversion details。 David Keen有一个很好的cheat sheet that is useful和Michael Sharhag has java 8 date and time conversion details。
这样的事情应该有效:
String dateString = year.toString() + "-" + month.toString() + "-01T00:00:00.0Z";
Instant dtgInstant = Instant.parse(dateString);
java.sql.Date timestamp = new java.sql.Date(dtgInstant.toEpochMilli());