升级JPA后,如何将存储LocalDateTime的VARBINARY字段转为TIMESTAMP?
解决JPA升级后LocalDateTime字段从VARBINARY转TIMESTAMP的问题
首先得搞清楚为什么之前的LocalDateTime会存在tinyblob里:旧版JPA不支持Java 8的时间API,Hibernate默认用Java对象序列化把LocalDateTime转成字节数组,存在Blob类型的字段里。你看到的乱码字符串就是序列化后的字节转成字符串的结果,本质是java.time.Ser(Java时间类的序列化包装类)的字节数据。
要把这些数据转成TIMESTAMP类型,纯SQL很难处理Java序列化的数据,推荐用FlyWay的Java迁移脚本来完成,步骤如下:
1. 准备FlyWay环境
如果你的项目还没引入FlyWay,先在pom.xml(或build.gradle)里加依赖:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency>
2. 编写Java迁移脚本
在src/main/resources/db/migration目录下创建一个Java迁移类,命名要符合FlyWay的规范:V<版本号>__<描述>.java,比如V1__Convert_AnswerTime_From_Blob_To_Timestamp.java:
import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.Context; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.time.LocalDateTime; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; public class V1__Convert_AnswerTime_From_Blob_To_Timestamp extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { // 第一步:新增临时TIMESTAMP字段,用来存转换后的数据 context.getConnection().createStatement().execute( "ALTER TABLE user_answer ADD COLUMN answer_time_temp TIMESTAMP NULL" ); // 第二步:读取旧的blob数据,反序列化后写入临时字段 String selectSql = "SELECT id, answer_time FROM user_answer"; PreparedStatement selectStmt = context.getConnection().prepareStatement(selectSql); ResultSet rs = selectStmt.executeQuery(); String updateSql = "UPDATE user_answer SET answer_time_temp = ? WHERE id = ?"; PreparedStatement updateStmt = context.getConnection().prepareStatement(updateSql); while (rs.next()) { Long id = rs.getLong("id"); byte[] blobBytes = rs.getBytes("answer_time"); if (blobBytes != null) { // 反序列化字节数组得到LocalDateTime try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(blobBytes))) { Object serializedObj = ois.readObject(); // 确认是LocalDateTime对象后写入临时字段 if (serializedObj instanceof LocalDateTime) { LocalDateTime answerTime = (LocalDateTime) serializedObj; updateStmt.setObject(1, answerTime); updateStmt.setLong(2, id); updateStmt.executeUpdate(); } } } } // 第三步:删除旧的blob字段,把临时字段重命名为原字段名 context.getConnection().createStatement().execute( "ALTER TABLE user_answer DROP COLUMN answer_time" ); context.getConnection().createStatement().execute( "ALTER TABLE user_answer CHANGE COLUMN answer_time_temp answer_time TIMESTAMP NOT NULL" ); } }
3. 配置应用启动参数
在application.properties里做如下配置:
# 启用FlyWay spring.flyway.enabled=true spring.flyway.locations=classpath:db/migration # 迁移期间把JPA的DDL自动处理设为none,避免冲突 spring.jpa.hibernate.ddl-auto=none
4. 执行迁移并验证
- 启动应用,FlyWay会自动执行这个Java迁移脚本,完成数据转换。
- 迁移完成后,把
spring.jpa.hibernate.ddl-auto改回validate,再次启动应用,就不会再出现字段类型不匹配的错误了。
重要提醒
- 迁移前一定要备份数据库,避免数据丢失!
- 如果表数据量很大,建议在业务低峰期执行迁移,避免影响正常业务。
- 新版本JPA已经原生支持
LocalDateTime,不需要再给字段加@Type注解了。
内容的提问来源于stack exchange,提问作者moffeltje
相关产品推荐
相关产品推荐

