Spring Data中如何保留Solr返回的UTC DateTime原始字符串格式?
当前环境:
- Solr版本:4.4.0(无法升级)
- Spring Data Solr版本:2.0.5.RELEASE
对接Solr的Java客户端执行查询后,字段会映射到POJO中。其中Solr的accessDate字段是datetime类型(配置如下),映射到POJO的String类型字段时,发现原本Solr存储的UTC格式字符串2023-06-06T17:52:17.139Z,在Java中调用getter得到的是Tue Jun 06 13:52:17 EDT 2023,看起来是先转成了java.util.Date再转为字符串。
客户端代码
private Cursor<SolrMetricRecord> querySolr(Query query) { Cursor<SolrMetricRecord> cur = null; try { cur = solrTemplate.queryForCursor(query, SolrMetricRecord.class); } catch (Exception e) { logger.error("Exception thrown querying Solr",e); } return cur; } @Override public List<SolrMetricRecord> getSolrMetricsRecordsStartingAtAccessDate(String startingAccessDate) { if (startingAccessDate==null) throw new IllegalArgumentException("Starting accessDate must be specified."); Query query = new SimpleQuery(); query.addCriteria(new Criteria(SOLR_FIELD_NAME_ACCESS_DATE).greaterThan(startingAccessDate)); query.addSort(new Sort(SOLR_FIELD_NAME_ACCESS_DATE)); query.setRows(1000); Cursor<SolrMetricRecord> cur = querySolr(query); return populateListFromCursor(cur); } private List<SolrMetricRecord> populateListFromCursor(Cursor<SolrMetricRecord> cur) { if (cur==null) { logger.info("Null cursor returned from Solr query"); return null; } if (cur.isClosed()) logger.info("Cursor is closed... Call open()?"); List<SolrMetricRecord> list = new ArrayList<SolrMetricRecord>(); try { while (cur.hasNext()) { list.add(cur.next()); } } catch (Exception e) { logger.debug("Exception thrown calling hasNext()",e); } return list; }
POJO代码
@SolrDocument(solrCoreName = "metrics") public class SolrMetricRecord { @Id @Indexed(name = "id", type = "String") private String id; @Field("accessDate") private String accessDate; // Other fields }
Solr字段配置
<field name="accessDate" type="datetime" indexed="true" stored="true" multiValued="false" /> ... <fieldtype name="datetime" class="solr.DateField" sortMissingLast="true" omitNorms="true"/>
疑问:
- 这种转换行为是否正常?
- 如何保留Solr返回的UTC时间原始字符串格式,避免繁琐的格式转换?
1. 转换行为是否正常?
这是正常行为。Spring Data Solr在处理Solr的DateField类型字段时,默认会将其解析为java.util.Date对象。当你的POJO字段类型是String时,框架会调用Date.toString()方法将其转为字符串,而Date.toString()会使用当前JVM的时区(这里是EDT)来格式化日期,所以得到的是带时区的本地时间字符串,而非Solr存储的UTC原始格式。
2. 如何保留UTC原始字符串格式?
有两种可行的方案:
方案一:自定义字段转换器
实现Converter<Date, String>,指定将Date对象按Solr的UTC格式转换为字符串,然后注册到Spring Data Solr的转换上下文里。
import org.springframework.core.convert.converter.Converter; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; public class SolrDateToStringConverter implements Converter<Date, String> { private static final String SOLR_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; @Override public String convert(Date source) { if (source == null) { return null; } SimpleDateFormat sdf = new SimpleDateFormat(SOLR_DATE_FORMAT); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); return sdf.format(source); } }
然后在配置类中注册这个转换器:
import org.springframework.context.annotation.Configuration; import org.springframework.data.solr.core.convert.MappingSolrConverter; import org.springframework.data.solr.core.convert.SolrCustomConversions; import org.springframework.data.solr.core.mapping.SolrMappingContext; import org.springframework.data.solr.repository.config.EnableSolrRepositories; import java.util.Arrays; @Configuration @EnableSolrRepositories(basePackages = "your.package.path") public class SolrConfig { public MappingSolrConverter solrConverter(SolrMappingContext solrMappingContext) { MappingSolrConverter converter = new MappingSolrConverter(solrMappingContext); SolrCustomConversions conversions = new SolrCustomConversions(Arrays.asList(new SolrDateToStringConverter())); converter.setCustomConversions(conversions); return converter; } // 其他Solr相关配置(如SolrTemplate、SolrMappingContext等)... }
方案二:直接获取Solr返回的原始字段值
绕过Spring Data Solr的自动映射,直接使用SolrJ的QueryResponse来获取原始的字段字符串。修改查询逻辑,手动执行查询并解析结果:
import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; private List<SolrMetricRecord> querySolrRaw(String startingAccessDate) { List<SolrMetricRecord> records = new ArrayList<>(); try { SolrQuery query = new SolrQuery(); query.setQuery("accessDate:[" + startingAccessDate + " TO *]"); query.setSort("accessDate", SolrQuery.ORDER.asc); query.setRows(1000); QueryResponse response = solrTemplate.getSolrClient().query("metrics", query); SolrDocumentList docs = response.getResults(); for (SolrDocument doc : docs) { SolrMetricRecord record = new SolrMetricRecord(); record.setId((String) doc.getFieldValue("id")); // 直接获取原始的UTC格式字符串 record.setAccessDate((String) doc.getFieldValue("accessDate")); // 手动设置其他字段... records.add(record); } } catch (Exception e) { logger.error("Exception thrown querying Solr", e); } return records; }
这样就能直接拿到Solr存储的2023-06-06T17:52:17.139Z格式字符串,无需额外转换。
注意:方案二需要手动处理所有字段的映射,适合字段不多的场景;方案一通过转换器实现自动映射,更符合Spring Data的使用习惯,但需要确保转换器被正确注册。
内容的提问来源于stack exchange,提问作者robhyx

