前提 前面的几篇文章已经基本介绍完了JSR-310日期时间类库的基本使用,这篇文章主要介绍在主流的框架中如何使用这些类库。因为涉及到数据库操作,先准备好一张表和对应的实体。
CREATE TABLE `t_user`( id BIGINT PRIMARY KEY COMMENT '主键' , username VARCHAR (10 ) COMMENT '姓名' , birthday DATE COMMENT '生日' , create_time DATETIME COMMENT '创建时间' , KEY idx_name(`username`), KEY idx_create_time(`create_time`) )COMMENT '用户表' ;
@Data public class User { private Long id; private String name; private LocalDate birthday; private OffsetDateTime createTime; }
这里如果不考虑时区的影响,createTime
也可以使用LocalDateTime
类型。另外,为了连接测试数据库,这里引入’光’连接池的依赖:
<dependency > <groupId > com.zaxxer</groupId > <artifactId > HikariCP</artifactId > <version > 3.2.0</version > </dependency >
JDBC中使用JSR-310日期时间类库 说实话,由于JDBC
类库在方法参数或者返回值类型很久没更新,对于带日期时间的属性,统一使用java.sql.Timestamp
类型,对于日期类型的属性则统一使用java.sql.Date
,因此需要进行类型转换。代码如下:
public class JdbcSample { public static void main (String[] args) throws Exception { HikariConfig config = new HikariConfig(); config.setMaximumPoolSize(10 ); config.setJdbcUrl("jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8" ); config.setUsername("root" ); config.setPassword("root" ); config.setDriverClassName("com.mysql.jdbc.Driver" ); DataSource dataSource = new HikariDataSource(config); Connection connection = dataSource.getConnection(); connection.setAutoCommit(false ); PreparedStatement p = connection.prepareStatement("INSERT INTO t_user(id,username,birthday,create_time) VALUES (?,?,?,?)" ); p.setLong(1 , 1L ); p.setString(2 , "Throwable" ); p.setDate(3 , Date.valueOf(LocalDate.of(1993 , 3 , 10 ))); p.setTimestamp(4 , Timestamp.from(OffsetDateTime.now().toInstant())); int updateCount = p.executeUpdate(); connection.commit(); System.out.println(String.format("更新数据%d条" , updateCount)); p = connection.prepareStatement("SELECT * FROM t_user WHERE id = ?" ); p.setLong(1 , 1L ); ResultSet resultSet = p.executeQuery(); User user = null ; if (resultSet.next()) { user = new User(); user.setId(resultSet.getLong("id" )); user.setName(resultSet.getString("username" )); user.setBirthday(resultSet.getDate("birthday" ).toLocalDate()); user.setCreateTime(OffsetDateTime.ofInstant(resultSet.getTimestamp("create_time" ).toInstant(), ZoneId.systemDefault())); } System.out.println(user); } } 更新数据1 条 User(id=1 , name=Throwable, birthday=1993 -03 -10 , createTime=2019 -01 -06T23:09:01 +08:00 )
除了需要做少量类型转换,没有其他的兼容性问题。
Mybatis中使用JSR-310日期时间类库 既然JDBC
已经可以使用JSR-310
的日期时间类库,那么基于JDBC
封装的ORM
框架必定也可以支持。除了需要引入Mybatis
本身的依赖,还需要引入mybatis-typehandlers-jsr310
依赖(这里注意一点,Mybatis
某个版本之后已经内置了mybatis-typehandlers-jsr310
的所有依赖类,所以不需要额外引入):
<dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.4.6</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-typehandlers-jsr310</artifactId > <version > 1.0.2</version > </dependency >
新建一个Mapper接口类UserMapper
:
public interface UserMapper { @Insert("INSERT INTO t_user(id,username,birthday,create_time) VALUES (#{id},#{name},#{birthday},#{createTime})") int insert (User user) ; @Select("SELECT id,username as name,birthday,create_time as createTime FROM t_user WHERE id = #{id}") User selectById (Long id) ; }
核心代码如下:
public class MybatisSample { public static void main (String[] args) throws Exception { HikariConfig config = new HikariConfig(); config.setMaximumPoolSize(10 ); config.setJdbcUrl("jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8" ); config.setUsername("root" ); config.setPassword("root" ); config.setDriverClassName("com.mysql.jdbc.Driver" ); DataSource dataSource = new HikariDataSource(config); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development" , transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(UserMapper.class); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(1L ); user.setCreateTime(OffsetDateTime.now()); user.setBirthday(LocalDate.of(1993 , 3 , 10 )); user.setName("Throwable" ); int updateCount = userMapper.insert(user); System.out.println(String.format("更新数据%d条" , updateCount)); sqlSession.commit(); User result = userMapper.selectById(1L ); System.out.println(result); sqlSession.close(); } } 更新数据1 条 User(id=1 , name=Throwable, birthday=1993 -03 -10 , createTime=2019 -01 -06T23:30 :09+08:00 )
虽然多引入了一个依赖,但是使用起来十分简单,甚至可以做到开发态无感知,Mybatis这一点做得比较完善。
Jackson中使用JSR-310日期时间类库 Jackson从2.x
某个版本中,官方就基于JDK8
的新特性开发了第三方类库jackson-modules-java8 ,这个第三方类库包括三个模块jackson-module-parameter-names
、jackson-datatype-jdk8
和jackson-datatype-jsr310
,这三个模块是独立打包的,可以按需引入。这里做的实例需要引入下面的依赖:
<dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.8</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.datatype</groupId > <artifactId > jackson-datatype-jsr310</artifactId > <version > 2.9.8</version > </dependency >
在官方文档中已经很详细给出具体的使用例子,这里也简单做一个例子:
public class JacksonMain { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd" ); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); public static void main (String[] args) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER)); javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)); objectMapper.registerModule(javaTimeModule); Sample sample = new Sample(); sample.setLocalDate(LocalDate.now()); sample.setLocalDateTime(LocalDateTime.now()); System.out.println(objectMapper.writeValueAsString(sample)); } @Data public static class Sample { private LocalDate localDate; private LocalDateTime localDateTime; } } {"localDate" :"2019-01-07" ,"localDateTime" :"2019-01-07 23:40:12" }
ObjectMapper
实例中可以注册自定义的JavaTimeModule
模块,JavaTimeModule
模块中已经存在了不少默认的日期时间类的序列化和反序列化器,必要时可以像上面的例子一样重写对应的日期时间类型的序列化和反序列化器并且覆盖已经配置的默认实现,这样子就能实现我们想要的格式化输出。JavaTimeModule
默认的序列化和反序列化器配置如下:
public JavaTimeModule () { super (PackageVersion.VERSION); this .addDeserializer(Instant.class, InstantDeserializer.INSTANT); this .addDeserializer(OffsetDateTime.class, InstantDeserializer.OFFSET_DATE_TIME); this .addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME); this .addDeserializer(Duration.class, DurationDeserializer.INSTANCE); this .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE); this .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE); this .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE); this .addDeserializer(MonthDay.class, MonthDayDeserializer.INSTANCE); this .addDeserializer(OffsetTime.class, OffsetTimeDeserializer.INSTANCE); this .addDeserializer(Period.class, JSR310StringParsableDeserializer.PERIOD); this .addDeserializer(Year.class, YearDeserializer.INSTANCE); this .addDeserializer(YearMonth.class, YearMonthDeserializer.INSTANCE); this .addDeserializer(ZoneId.class, JSR310StringParsableDeserializer.ZONE_ID); this .addDeserializer(ZoneOffset.class, JSR310StringParsableDeserializer.ZONE_OFFSET); this .addSerializer(Duration.class, DurationSerializer.INSTANCE); this .addSerializer(Instant.class, InstantSerializer.INSTANCE); this .addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE); this .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE); this .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE); this .addSerializer(MonthDay.class, MonthDaySerializer.INSTANCE); this .addSerializer(OffsetDateTime.class, OffsetDateTimeSerializer.INSTANCE); this .addSerializer(OffsetTime.class, OffsetTimeSerializer.INSTANCE); this .addSerializer(Period.class, new ToStringSerializer(Period.class)); this .addSerializer(Year.class, YearSerializer.INSTANCE); this .addSerializer(YearMonth.class, YearMonthSerializer.INSTANCE); this .addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer.INSTANCE); this .addSerializer(ZoneId.class, new ToStringSerializer(ZoneId.class)); this .addSerializer(ZoneOffset.class, new ToStringSerializer(ZoneOffset.class)); this .addKeySerializer(ZonedDateTime.class, ZonedDateTimeKeySerializer.INSTANCE); this .addKeyDeserializer(Duration.class, DurationKeyDeserializer.INSTANCE); this .addKeyDeserializer(Instant.class, InstantKeyDeserializer.INSTANCE); this .addKeyDeserializer(LocalDateTime.class, LocalDateTimeKeyDeserializer.INSTANCE); this .addKeyDeserializer(LocalDate.class, LocalDateKeyDeserializer.INSTANCE); this .addKeyDeserializer(LocalTime.class, LocalTimeKeyDeserializer.INSTANCE); this .addKeyDeserializer(MonthDay.class, MonthDayKeyDeserializer.INSTANCE); this .addKeyDeserializer(OffsetDateTime.class, OffsetDateTimeKeyDeserializer.INSTANCE); this .addKeyDeserializer(OffsetTime.class, OffsetTimeKeyDeserializer.INSTANCE); this .addKeyDeserializer(Period.class, PeriodKeyDeserializer.INSTANCE); this .addKeyDeserializer(Year.class, YearKeyDeserializer.INSTANCE); this .addKeyDeserializer(YearMonth.class, YearMothKeyDeserializer.INSTANCE); this .addKeyDeserializer(ZonedDateTime.class, ZonedDateTimeKeyDeserializer.INSTANCE); this .addKeyDeserializer(ZoneId.class, ZoneIdKeyDeserializer.INSTANCE); this .addKeyDeserializer(ZoneOffset.class, ZoneOffsetKeyDeserializer.INSTANCE); }
如果熟练掌握Jackson的解析原理和源码,可以尝试继承JSR310FormattedSerializerBase
或者JSR310DateTimeDeserializerBase
实现自定义序列化或反序列化器,从更底层控制日期时间类的序列化和反序列化。
SpringMVC中使用JSR-310日期时间类库 SpringMVC中默认的HTTP消息转换器就是使用Jackson实现的,前面已经提到了Jackson可以完美支持JSR-310,那么SpringMVC也必定可以支持,只需要对ObjectMapper
做一些额外的配置即可。这里简单以SpringBoot应用做示例,引入依赖:
<dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.9.8</version > </dependency > <dependency > <groupId > com.fasterxml.jackson.datatype</groupId > <artifactId > jackson-datatype-jsr310</artifactId > <version > 2.9.8</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <version > 2.1.0.RELEASE</version > </dependency >
由于org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
中会通过Jackson2ObjectMapperBuilder
去构造内部使用的ObjectMapper
,我们只需要提供一个自定义的Jackson2ObjectMapperBuilder
类型的Bean即可。
@Configuration public class JacksonBuilderAutoConfiguration { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd" ); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); @Bean public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder () { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER)); javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)); builder.modules(javaTimeModule); return builder; } } @SpringBootApplication public class Application implements CommandLineRunner { @Autowired private ObjectMapper objectMapper; public static void main (String[] args) { SpringApplication.run(Application.class, args); } @Override public void run (String... args) throws Exception { Sample sample = new Sample(); sample.setLocalDate(LocalDate.now()); sample.setLocalDateTime(LocalDateTime.now()); System.out.println(objectMapper.writeValueAsString(sample)); } @Data public static class Sample { private LocalDate localDate; private LocalDateTime localDateTime; } } {"localDate" :"2019-01-07" ,"localDateTime" :"2019-01-07 23:58:08" }
这里只要保证SpringMVC
内部使用的ObjectMapper
类型的Bean
对JSR-310
日期时间类型的序列化和反序列化生效即可,因为默认配置的MappingJackson2HttpMessageConverter
HTTP消息转换器就是使用内置的ObjectMapper
类型的Bean
做JSON
的序列化和反序列化。
小结 实战层面来看,使用的框架都是基于JDK类库的实现,只要JDK类库的功能可以实现,那么在应用的时候要有信心主流的框架一定会支持对应的特性。
参考资料:
(本文完 e-a-201818 c-2-d)