You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data中如何保留Solr返回的UTC DateTime原始字符串格式?

问题:Solr datetime字段映射到POJO时的格式转换问题

当前环境:

  • 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"/>

疑问:

  1. 这种转换行为是否正常?
  2. 如何保留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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 02:27:02