OpenJPA中Calendar与MySQL DATETIME的AttributeConverter转换异常问题
我使用OpenJPA 3.2.2的AttributeConverter时,始终遇到类型转换错误:
class java.lang.String cannot be cast to class java.util.Calendar (java.lang.String and java.util.Calendar are in module java.base of loader 'bootstrap')
尝试过将转换器的数据库端类型设为String、java.sql.Timestamp、java.util.Date,但OpenJPA仍抛出上述错误。想请教:在属性转换器中,MySQL的DATETIME类型应对应哪种Java类型?
由于遗留系统限制,Java实体类必须使用Calendar字段,该代码库为多个应用共用。不使用@Convert注解时,Calendar字段可正常存入数据库,但我需要将其强制转换为UTC时区的yyyy-MM-dd HH:mm:ss格式。
相关代码
实体类
@Entity @Table(name="service") @Access(AccessType.FIELD) public class Service { ... @Column(nullable=false) @Convert(converter=JPAConverter.class) private Calendar updated; }
数据库表结构
CREATE TABLE IF NOT EXISTS service ( id bigint UNSIGNED NOT NULL default '0', updated datetime NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB;
转换器类
@Converter(autoApply=false) public class JPAConverter implements AttributeConverter<Calendar, String> { @Override public String convertToDatabaseColumn(Calendar val) { if(val==null) return null; return DateUtil.formatDateTime(val, DateUtil.TIMEZONE_UTC); } @Override public Calendar convertToEntityAttribute(String dbVal) { if(dbVal==null) return null; return DateUtil.parseDateTimeFromUTC(dbVal); } }
堆栈错误信息
Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Calendar (java.lang.String and java.util.Calendar are in module java.base of loader 'bootstrap') at org.apache.openjpa.jdbc.sql.DBDictionary.setTyped(DBDictionary.java:1551) at org.apache.openjpa.jdbc.sql.RowImpl.flush(RowImpl.java:1002) at org.apache.openjpa.jdbc.sql.RowImpl.flush(RowImpl.java:962) at org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flushAndUpdate(PreparedStatementManagerImpl.java:119) at org.apache.openjpa.jdbc.kernel.BatchingPreparedStatementManagerImpl.flushAndUpdate(BatchingPreparedStatementManagerImpl.java:80) at org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flushInternal(PreparedStatementManagerImpl.java:102) at org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flush(PreparedStatementManagerImpl.java:90) at org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager.flush(ConstraintUpdateManager.java:555) at org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager.flush(ConstraintUpdateManager.java:109) at org.apache.openjpa.jdbc.kernel.BatchingConstraintUpdateManager.flush(BatchingConstraintUpdateManager.java:61) at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:109) at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:81) at org.apache.openjpa.jdbc.kernel.JDBCStoreManager.flush(JDBCStoreManager.java:755) at org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:146) at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2310) at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:2201) at org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:2118) ... 55 more
问题分析与解决方法
核心问题
当前转换器将Calendar转为String,但数据库字段是DATETIME,OpenJPA会尝试把转换器输出的String直接当作Calendar类型适配数据库字段,导致类型不匹配。MySQL的DATETIME对应的标准Java类型应为java.sql.Timestamp(比java.util.Date更精准),而非String。
修正方案
调整转换器的数据库端类型为
Timestamp
将转换器改为AttributeConverter<Calendar, Timestamp>,让输出类型与数据库DATETIME的映射类型匹配:@Converter(autoApply=false) public class JPAConverter implements AttributeConverter<Calendar, Timestamp> { @Override public Timestamp convertToDatabaseColumn(Calendar val) { if(val == null) return null; // 将Calendar转为UTC时区的Timestamp Calendar utcCal = Calendar.getInstance(DateUtil.TIMEZONE_UTC); utcCal.setTimeInMillis(val.getTimeInMillis()); return new Timestamp(utcCal.getTimeInMillis()); } @Override public Calendar convertToEntityAttribute(Timestamp dbVal) { if(dbVal == null) return null; Calendar cal = Calendar.getInstance(DateUtil.TIMEZONE_UTC); cal.setTimeInMillis(dbVal.getTime()); return cal; } }若必须使用字符串格式写入数据库
若一定要把DATETIME存为字符串,需将数据库字段类型改为VARCHAR,同时保持转换器的String类型输出。但此方式不推荐,会丢失数据库对日期类型的原生支持(如排序、日期范围查询等)。验证时区处理
确保DateUtil工具类在格式化/解析时确实使用UTC时区,避免时区偏移导致的时间错误。使用上述Timestamp方案时,直接通过Calendar设置时区再转Timestamp即可,无需字符串格式化,可靠性更高。
额外注意
OpenJPA处理AttributeConverter时,要求转换器的数据库端类型必须与实体类字段对应的数据库列类型匹配。此前用String对应DATETIME列,类型不兼容才抛出ClassCastException。
内容的提问来源于stack exchange,提问作者Whome

