前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JSR310新日期API(五)-在主流框架中使用新日期时间类

JSR310新日期API(五)-在主流框架中使用新日期时间类

作者头像
Throwable
发布2020-06-23 16:02:50
2.2K0
发布2020-06-23 16:02:50
举报
文章被收录于专栏:Throwable's BlogThrowable's Blog

前提

前面的几篇文章已经基本介绍完了JSR-310日期时间类库的基本使用,这篇文章主要介绍在主流的框架中如何使用这些类库。因为涉及到数据库操作,先准备好一张表和对应的实体。

代码语言:javascript
复制
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 '用户表';
代码语言:javascript
复制
@Data
public class User{

   private Long id;
   private String name;
   private LocalDate birthday;
   private OffsetDateTime createTime;
}

这里如果不考虑时区的影响,createTime也可以使用LocalDateTime类型。另外,为了连接测试数据库,这里引入’光’连接池的依赖:

代码语言:javascript
复制
<dependency>
	<groupId>com.zaxxer</groupId>
	<artifactId>HikariCP</artifactId>
	<version>3.2.0</version>
</dependency>

JDBC中使用JSR-310日期时间类库

说实话,由于JDBC类库在方法参数或者返回值类型很久没更新,对于带日期时间的属性,统一使用java.sql.Timestamp类型,对于日期类型的属性则统一使用java.sql.Date,因此需要进行类型转换。代码如下:

代码语言:javascript
复制
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()));
		//LocalDateTime -> p.setTimestamp(4, Timestamp.from(LocalDateTime.now()));
		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()));
			//LocalDateTime -> user.setCreateTime(resultSet.getTimestamp("create_time").toLocalDateTime());
		}
		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的所有依赖类,所以不需要额外引入):

代码语言:javascript
复制
<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

代码语言:javascript
复制
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);
}

核心代码如下:

代码语言:javascript
复制
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-namesjackson-datatype-jdk8jackson-datatype-jsr310,这三个模块是独立打包的,可以按需引入。这里做的实例需要引入下面的依赖:

代码语言:javascript
复制
<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>

在官方文档中已经很详细给出具体的使用例子,这里也简单做一个例子:

代码语言:javascript
复制
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默认的序列化和反序列化器配置如下:

代码语言:javascript
复制
    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应用做示例,引入依赖:

代码语言:javascript
复制
<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即可。

代码语言:javascript
复制
// 配置类
@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类型的BeanJSR-310日期时间类型的序列化和反序列化生效即可,因为默认配置的MappingJackson2HttpMessageConverterHTTP消息转换器就是使用内置的ObjectMapper类型的BeanJSON的序列化和反序列化。

小结

实战层面来看,使用的框架都是基于JDK类库的实现,只要JDK类库的功能可以实现,那么在应用的时候要有信心主流的框架一定会支持对应的特性。

参考资料:

(本文完 e-a-201818 c-2-d)

本文是Throwable的原创文章,转载请提前告知作者并且标明出处。 博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议 本文永久链接是:https://cloud.tencent.com/developer/article/1649980

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年1月8日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前提
  • JDBC中使用JSR-310日期时间类库
  • Mybatis中使用JSR-310日期时间类库
  • Jackson中使用JSR-310日期时间类库
  • SpringMVC中使用JSR-310日期时间类库
  • 小结
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档