Date: 你不爱我了, 竟然要用jdk8的时间表示 🚠
一旦圆满了,马上就要亏厌,树上的果子,一旦熟透了,马上就要坠落
java8中, 时间使用上可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat, 为什么要这么替代呢, 原来的时间api有什么问题呢?
jdk8前的时间api有什么问题
在Java 8之前,Java中的时间API存在一些问题,主要有以下几点:
-
线程不安全:Date和Calendar都是可变对象,并且不是线程安全的。如果多个线程同时使用同一个Date或Calendar实例进行操作,可能会导致数据出错。
-
可读性差:在Date和Calendar中,大量的方法名都不够清晰,使用起来不够直观,容易出现误操作。
-
不够灵活:Date和Calendar中的很多操作都比较麻烦,比如要获取某一天的前一天,需要进行多次操作才能实现。
为了解决这些问题,Java 8引入了新的时间API,即java.time包,其中包含了一系列新的日期和时间类。这些类的主要优点包括:
-
线程安全:新的日期和时间类都是不可变对象,并且线程安全,可以在多线程环境下安全使用。
-
可读性高:新的日期和时间类使用了更清晰的方法名,并且提供了更直观的操作方式,易于理解和使用。
-
更灵活:新的日期和时间类提供了更多的操作方法,比如获取某一天的前一天只需要一步操作即可。
在Java 8中,Instant类用于表示时间戳,相当于旧的Date类;LocalDateTime类用于表示日期和时间,相当于旧的Calendar类;DateTimeFormatter类用于格式化日期和时间,相当于旧的SimpleDateFormat类。
综上所述,Java 8中的新日期和时间API更加方便、灵活和易于使用,是对旧的日期和时间API的一次重要升级和改进。
mysql的时间相关字段对应
java8中的时间类如何跟mysql的时间相关字段对应起来呢?
Java 8中的时间类与MySQL的时间相关字段可以使用以下方式进行对应:
-
Instant类对应MySQL的TIMESTAMP字段。在Java中,可以使用Instant.ofEpochSecond()方法将秒数转换为Instant对象,然后将其转换为Timestamp对象,再将Timestamp对象插入到MySQL的TIMESTAMP字段中。
-
LocalDateTime类对应MySQL的DATETIME字段。在Java中,可以使用LocalDateTime.parse()方法将字符串解析为LocalDateTime对象,然后将其转换为Timestamp对象,再将Timestamp对象插入到MySQL的DATETIME字段中。
-
LocalDate类对应MySQL的DATE字段。在Java中,可以使用LocalDate.parse()方法将字符串解析为LocalDate对象,然后将其转换为java.sql.Date对象,再将java.sql.Date对象插入到MySQL的DATE字段中。
需要注意的是,Java 8中的日期和时间类与MySQL的时间相关字段之间的转换需要考虑时区问题,确保时区的一致性。另外,在将Java 8中的日期和时间对象转换为MySQL的时间相关字段时,也需要进行格式化和解析等操作,确保数据的正确性和完整性。
mysql中timestamp和datetime, timestamp是带时区, 而datetime是不带时区,除了这点区别, 还有其他区别吗?
除了时区的差异,MySQL中的timestamp和datetime类型还有以下几个区别:
-
存储范围:MySQL中的timestamp类型可以存储的范围比datetime类型小。timestamp类型可以存储的时间范围为1970-01-01 00:00:01到2038-01-19 03:14:07,而datetime类型可以存储的时间范围为1000-01-01 00:00:00到9999-12-31 23:59:59。
-
存储空间:MySQL中的timestamp类型占用4个字节的存储空间,而datetime类型占用8个字节的存储空间。
-
存储精度:MySQL中的timestamp类型存储精度为秒级,而datetime类型存储精度为微秒级。
-
默认值:MySQL中的timestamp类型默认值为CURRENT_TIMESTAMP,表示当前时间戳,而datetime类型默认值为NULL。
需要注意的是,由于timestamp类型存储的是UTC时间戳,因此在将timestamp类型转换为本地时间时,需要考虑时区的差异。如果在Java中使用Instant类型表示MySQL中的timestamp类型,可以使用JDBC驱动提供的方法直接将timestamp类型转换为Instant类型,从而避免时区转换的问题。而对于datetime类型,由于不包含时区信息,需要在程序中手动进行时区转换。
springboot中, 我要如何考虑时区问题
Java 8中的日期和时间类与MySQL的时间相关字段之间的转换需要考虑时区问题,确保时区的一致性, 如果是在springboot中, 我要如何考虑时区问题?
在Spring Boot中,可以通过配置文件中的属性来设置时区,从而确保Java 8中的日期和时间类与MySQL的时间相关字段之间的转换时区一致。
具体来说,可以在application.properties或application.yml中添加以下属性:
- 对于Spring Boot 2.x及以上版本:
spring:
jackson:
time-zone: Asia/Shanghai
这里以设置时区为"Asia/Shanghai"为例。通过设置这个属性,可以确保Spring Boot应用程序的默认时区为"Asia/Shanghai",从而在处理Java 8中的日期和时间类时,能够正确地进行时区转换,与MySQL的时间相关字段之间保持一致。
需要注意的是,如果在Spring Boot中使用了JPA等ORM框架来操作数据库,还需要确保ORM框架的配置中也设置了正确的时区。一般来说,ORM框架会自动采用Spring Boot的默认时区,因此不需要额外配置。但是,如果ORM框架的默认时区与Spring Boot的默认时区不一致,需要手动进行配置,以确保时区的一致性。
mybatis和hibernate这两个orm框架来说吧, 默认时区跟springboot中一致吗
MyBatis和Hibernate这两个ORM框架默认时区与Spring Boot中的默认时区是一致的,都是使用JVM的默认时区。
具体来说,MyBatis使用JDBC连接数据库,而JDBC的默认时区就是JVM的默认时区,因此在使用MyBatis操作数据库时,其默认时区就是JVM的默认时区。Hibernate也类似,它会自动根据JVM的默认时区来进行时区转换。
需要注意的是,如果需要在MyBatis或Hibernate中手动设置时区,可以通过设置JDBC连接的时区来实现。对于MyBatis来说,可以在mybatis-config.xml In spring中添加如下配置:
<configuration>
<settings>
<setting name="jdbcTypeForNull" value="NULL" />
<setting name="defaultTimeZone" value="Asia/Shanghai" />
</settings>
</configuration>
对于Hibernate来说,可以在application.properties或application.yml中添加如下配置:
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai
这里以设置时区为"Asia/Shanghai"为例。通过这些配置,可以确保在使用MyBatis和Hibernate操作数据库时,其默认时区与Spring Boot中的默认时区一致,避免时区转换带来的问题。
现在我使用的是mybatis框架, 有一个类A, 有四个字段String id, Instant birthday, LocalDateTime createTime, LocalDate updateTime, 我要如何配置我的SQL呢?
如何配置我的SQL | 梦回 spring 配置时代
如果需要对birthday、createTime和updateTime进行范围查询,可以使用如下的SQL语句:
SELECT id, birthday, create_time, update_time FROM table_a WHERE birthday >= #{startDate} AND birthday <= #{endDate} AND create_time >= #{startTime} AND create_time <= #{endTime} AND update_time >= #{updateDate} AND update_time <= #{updateDateEnd} ORDER BY id
这里的"startDate"、“endDate”、“startTime”、“endTime”、"updateDate"和"updateDateEnd"是查询条件,需要在Java代码中动态设置它们的值。
如果需要插入A类的一条数据,可以使用如下的SQL语句:
INSERT INTO table_a (id, birthday, create_time, update_time) VALUES (#{id}, #{birthday}, #{createTime}, #{updateTime})
这里的"#{id}“、”#{birthday}“、”#{createTime}“和”#{updateTime}"是A类的四个字段,需要在Java代码中设置它们的值。
需要注意的是,在处理Instant、LocalDateTime和LocalDate等Java 8中的日期和时间类型时,需要配置对应的TypeHandler来实现与数据库的转换。可以在mybatis-config.xml中添加如下的TypeHandler配置:
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.InstantTypeHandler" javaType="java.time.Instant"/>
<typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler" javaType="java.time.LocalDateTime"/>
<typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler" javaType="java.time.LocalDate"/>
</typeHandlers>
这里的"org.apache.ibatis.type.InstantTypeHandler"、"org.apache.ibatis.type.LocalDateTimeTypeHandler"和"org.apache.ibatis.type.LocalDateTypeHandler"是MyBatis提供的处理Instant、LocalDateTime和LocalDate等Java 8中的日期和时间类型的TypeHandler,可以直接使用。
如果有些Mapper使用的TypeHandler不同,可以分别为它们设置不同的TypeHandler
如果有些Mapper使用的TypeHandler不同,可以分别为它们设置不同的TypeHandler。具体来说,有两种方式可以实现:
在Mapper接口中使用@TypeHandler注解
- 在Mapper接口中使用@TypeHandler注解
可以在Mapper接口的方法上使用@TypeHandler注解来指定要使用的TypeHandler。例如:
@Select("SELECT * FROM table_a WHERE birthday = #{birthday, typeHandler=org.apache.ibatis.type.InstantTypeHandler}")
A findByBirthday(@Param("birthday") Instant birthday);
这里的"org.apache.ibatis.type.InstantTypeHandler"是要使用的TypeHandler类的全限定名。在执行查询时,MyBatis会自动将Instant类型的birthday参数转换为对应的数据库类型,并使用指定的TypeHandler来完成转换。
在Mapper XML文件中为每个参数和结果集设置对应的TypeHandler
- 在Mapper XML文件中为每个参数和结果集设置对应的TypeHandler
可以在Mapper XML文件中为每个参数和结果集设置对应的TypeHandler。例如:
<select id="findByBirthday" resultType="A">
<parameterMap type="java.util.Map" id="param">
<parameter property="birthday" javaType="java.time.Instant" typeHandler="org.apache.ibatis.type.InstantTypeHandler"/>
</parameterMap>
SELECT * FROM table_a WHERE birthday = #{birthday, typeHandler=org.apache.ibatis.type.InstantTypeHandler}
</select>
这里的"“标签用于指定参数类型和TypeHandler,”"标签用于指定参数名、Java类型和TypeHandler。在执行查询时,MyBatis会自动将Instant类型的birthday参数转换为对应的数据库类型,并使用指定的TypeHandler来完成转换。
类似地,如果需要设置结果集的TypeHandler,可以在标签上使用resultMap属性,指定结果集的TypeHandler。例如:
<select id="findAll" resultMap="result" >
SELECT * FROM table_a
</select>
<resultMap id="result" type="A">
<result property="birthday" column="birthday" javaType="java.time.Instant" typeHandler="org.apache.ibatis.type.InstantTypeHandler" />
<result property="createTime" column="create_time" javaType="java.time.LocalDateTime" typeHandler="org.apache.ibatis.type.LocalDateTimeTypeHandler" />
<result property="updateTime" column="update_time" javaType="java.time.LocalDate" typeHandler="org.apache.ibatis.type.LocalDateTypeHandler" />
</resultMap>
这里的"“标签用于指定结果集的TypeHandler和映射关系,”"标签用于指定每个属性对应的列名、Java类型和TypeHandler。在查询结果转换时,MyBatis会自动将数据库类型转换为Java类型,并使用指定的TypeHandler来完成转换。
示例
mysql中timestamp和datetime, timestamp是带时区, 而datetime是不带时区, 如果使用的是datetime, java中接受的instant类型, 这样会有什么问题
在MySQL中,datetime类型表示一个不带时区的日期和时间值,而timestamp类型则包含时区信息。如果在MySQL中使用datetime类型,而在Java中使用Instant类型,可能会遇到时区转换问题,因为Instant是基于UTC时间的,表示一个时间戳。
使用Java中的Instant类型来表示MySQL中的datetime类型可能会导致以下问题:
- 数据转换问题:由于datetime类型不包含时区信息,所以在将MySQL中的datetime值转换为Java的Instant类型时,可能会导致错误的时区转换,从而产生不准确的时间值。
假设你有一个MySQL数据库,其中有一个包含datetime类型的列,记录了用户的注册时间。你的应用程序在美国东部时区运行,而数据库服务器在UTC时区。
+----+---------------------+
| id | registration_time |
+----+---------------------+
| 1 | 2023-04-06 10:00:00 |
+----+---------------------+
在这种情况下,如果你直接将MySQL中的datetime值转换为Java的Instant类型,可能会导致错误的时区转换。例如:
import java.sql.*;
import java.time.Instant;
public class Main {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
String user = "username";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
String query = "SELECT id, registration_time FROM users WHERE id = 1";
PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
Instant registrationTime = rs.getTimestamp("registration_time").toInstant();
System.out.println("Registration time (Instant): " + registrationTime);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们将MySQL中的datetime值转换为了Java的Instant类型。但由于datetime类型不包含时区信息,所以这将导致错误的时区转换。实际上,返回的时间值将被错误地解释为UTC时间,即2023-04-06T10:00:00Z
,而不是正确的美国东部时区时间2023-04-06T06:00:00-04:00
- 时区不一致问题:在不同地区运行程序时,由于时区的差异,可能会导致程序处理MySQL中的datetime类型时出现问题。
假设你的应用程序需要支持多个时区的用户,并且你的数据库服务器仍然在UTC时区。在这种情况下,由于MySQL中的datetime类型不包含时区信息,所以在不同地区运行程序时,可能会导致程序处理MySQL中的datetime类型时出现问题。
例如,一个用户在美国东部时区创建了一个事件,另一个用户在欧洲柏林时区查看该事件。由于数据库中存储的时间值不包含时区信息,所以在将MySQL中的datetime值转换为Java的Instant类型时,你需要首先将其转换为正确的时区,然后再进行进一步的处理。
在这种情况下,你需要确保在处理MySQL中的datetime类型时,能够根据每个用户所在的时区进行正确的时区转换。这将使应用程序的逻辑更加复杂,因为你需要在应用程序中手动处理时区转换。
为了避免这些问题,建议将MySQL中的datetime类型转换为Java中的LocalDateTime类型,而不是Instant类型。LocalDateTime是不带时区的日期和时间,类似于MySQL中的datetime。
示例代码:
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.Instant;
import java.sql.Timestamp;
public class Main {
public static void main(String[] args) {
// 假设从MySQL数据库中获取的datetime值为2023-04-06 12:00:00
Timestamp mysqlDatetime = Timestamp.valueOf("2023-04-06 12:00:00");
// 将Timestamp转换为LocalDateTime
LocalDateTime localDateTime = mysqlDatetime.toLocalDateTime();
// 如果需要将LocalDateTime转换为Instant,需要指定时区,例如系统默认时区
ZoneId zoneId = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
Instant instant = zonedDateTime.toInstant();
}
}
在此示例中,我们首先将MySQL中的datetime(Timestamp)转换为Java中的LocalDateTime。如果确实需要将其转换为Instant类型,我们可以通过指定时区(如系统默认时区)将LocalDateTime转换为ZonedDateTime,然后将ZonedDateTime转换为Instant。这样,我们可以确保在转换过程中正确处理时区问题。
这里当然是使用我们的 typehandler 进行转换啦
public class InstantTypeHandler extends BaseTypeHandler<Instant> {
private final DateTimeTypeHandler dateTimeTypeHandler = new DateTimeTypeHandler();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {
ps.setObject(i, Timestamp.from(parameter));
}
@Override
public Instant getNullableResult(ResultSet rs, String columnName) throws SQLException {
LocalDateTime localDateTime = dateTimeTypeHandler.getNullableResult(rs, columnName);
return localDateTime != null ? localDateTime.toInstant(ZoneOffset.UTC) : null;
}
@Override
public Instant getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
LocalDateTime localDateTime = dateTimeTypeHandler.getNullableResult(rs, columnIndex);
return localDateTime != null ? localDateTime.toInstant(ZoneOffset.UTC) : null;
}
@Override
public Instant getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
LocalDateTime localDateTime = dateTimeTypeHandler.getNullableResult(cs, columnIndex);
return localDateTime != null ? localDateTime.toInstant(ZoneOffset.UTC) : null;
}
}
这里其实还有一个神奇的时区问题
如果没有设置serverTimezone参数,MySQL服务器会使用系统时区来解析datetime类型的值。因此,如果在MySQL中使用NOW()函数获取当前时间,返回的就是系统时区的当地时间。例如,在中国使用的MySQL服务器上执行NOW()函数,返回的就是中国时区的当地时间。
需要注意的是,如果MySQL服务器和应用程序运行在不同的时区,那么从MySQL中查询到的datetime类型的值可能与应用程序期望的本地时间不一致。在这种情况下,需要手动进行时区转换,以保证查询结果的正确性。
如果在连接MySQL数据库时没有指定serverTimezone参数,就会使用MySQL服务器的系统时区来解析datetime类型的值。在这种情况下,如果MySQL服务器和应用程序运行在不同的时区,就会导致datetime类型的值被错误地转换为本地时间
建议在链接数据库URL中配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
即使在连接MySQL数据库时指定了serverTimezone参数,也不会对MySQL服务器上存储的datetime类型的值产生影响。因此,在查询MySQL数据库时,仍然需要手动进行时区转换,以保证查询结果的正确性。
假设应用程序在运行UTC+8,而数据库服务器在UTC-5时区, 如果我使用 now()插入datetime类型的字段, 这个时候存储的是UTC-5时区时区的datetime, 这个时候读到应用程序中的LocalDateTime, 这个时候LocalDateTime比我要的时候是少了13个小时的是吗? 如果不使用now(), 我在程序中创建LocalDateTime,然后插入到数据库就不会有这个问题?
就是这样的。如果应用程序运行在UTC+8时区,而数据库服务器在UTC-5时区,当你在MySQL中使用now()
函数插入datetime类型的字段时,存储的是UTC-5时区的时间。当你从数据库中读取这个datetime类型字段到应用程序中的LocalDateTime时,它会比你期望的时间少了13个小时。
为了避免这个问题,你可以在应用程序中创建一个LocalDateTime对象,将其转换为UTC-5时区的时间,然后插入到数据库。这样,在插入和读取时,都会使用相同的时区,从而避免时区转换的问题。以下是一个简单的示例:
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Main {
public static void main(String[] args) {
// 在应用程序中创建一个LocalDateTime对象
LocalDateTime localDateTime = LocalDateTime.now();
// 获取数据库服务器所在的时区(以UTC-5为例)
ZoneId databaseTimeZone = ZoneId.of("UTC-5");
// 将应用程序中的LocalDateTime转换为数据库服务器所在时区的时间
ZonedDateTime databaseTime = localDateTime.atZone(ZoneId.systemDefault()).withZoneSameInstant(databaseTimeZone);
// 现在,你可以将databaseTime插入到数据库的datetime类型字段中
// ...
}
}
通过这种方式,你可以确保插入和读取datetime类型字段时使用相同的时区,从而避免时区转换的问题。当然,这仅适用于单一时区的情况。如果你需要支持多个时区的用户,你可能需要在应用程序中进行更复杂的时区处理。
springboot中,当然starter已经帮我们做好了这些类型转换的注册
当然这里权当看下, 你可以怎么配置类型转换器
你可以在@Configuration类中配置MyBatis的TypeHandler。具体来说,可以在@Configuration类中创建一个SqlSessionFactoryBean,并设置其typeHandlers属性为要使用的TypeHandler列表。示例如下:
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
TypeHandler<?>[] typeHandlers = new TypeHandler<?>[] {
new InstantTypeHandler(),
new LocalDateTimeTypeHandler(),
new LocalDateTypeHandler()
};
sqlSessionFactoryBean.setTypeHandlers(typeHandlers);
return sqlSessionFactoryBean;
}
}
这里的InstantTypeHandler、LocalDateTimeTypeHandler和LocalDateTypeHandler都是MyBatis提供的处理Java 8中的日期和时间类型的TypeHandler。如果你有自定义的TypeHandler,也可以添加到typeHandlers列表中。
需要注意的是,如果你在@Configuration类中配置了SqlSessionFactoryBean的typeHandlers属性,那么所有使用该SqlSessionFactoryBean的Mapper都会共享这些TypeHandler,因此应该确保这些TypeHandler适用于所有Mapper。如果有些Mapper使用的TypeHandler不同,可以在Mapper接口中使用@TypeHandler注解来指定要使用的TypeHandler,或者在Mapper XML文件中为每个参数和结果集设置对应的TypeHandler。
如果在Spring Boot项目中使用了mybatis-spring-boot-starter依赖,那么MyBatis的TypeHandler会自动注册。具体来说,MyBatis会扫描项目中所有的TypeHandler实现类,并自动注册到MyBatis的TypeHandlerRegistry中。因此,你不需要手动在mybatis-config.xml中配置TypeHandler,也不需要在@Configuration类中配置SqlSessionFactoryBean的typeHandlers属性。
如果需要自定义TypeHandler,只需要实现TypeHandler接口,并在实现类上添加@MappedTypes和@MappedJdbcTypes注解,MyBatis就会自动将其注册为TypeHandler。例如:
@MappedTypes({Instant.class})
@MappedJdbcTypes({JdbcType.TIMESTAMP, JdbcType.DATE})
public class InstantTypeHandler implements TypeHandler<Instant> {
@Override
public void setParameter(PreparedStatement ps, int i, Instant parameter, JdbcType jdbcType) throws SQLException {
if (parameter != null) {
ps.setTimestamp(i, Timestamp.from(parameter));
} else {
ps.setTimestamp(i, null);
}
}
@Override
public Instant getResult(ResultSet rs, String columnName) throws SQLException {
return rs.getTimestamp(columnName).toInstant();
}
@Override
public Instant getResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getTimestamp(columnIndex).toInstant();
}
@Override
public Instant getResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getTimestamp(columnIndex).toInstant();
}
}
这里的@MappedTypes和@MappedJdbcTypes注解分别用于指定Java类型和数据库类型。在使用Instant类型时,MyBatis会自动使用InstantTypeHandler进行转换。