从昨晚开始,到今天中午之前,一直在纠结时间存储问题,昨晚是纠结时间取出来的问题。
其实我的想法很简单,我就想java.util.Date 存储到 Elasticsearch ,然后从 Elasticsearch 中再取出来的时候,它是个Date ,不需要我任何转换。
但是发现好像不行。
我开始在创建 Mapping 的时候,就是为:
//...省略部分代码.startObject("create_date").field("type","date").field("format","yyyy-MM-dd HH:mm:ss").endObject()//...省略部分代码
指定了Type 为Date ,并且format 为yyyy-MM-dd HH:mm:ss ,然后new Date(); 插入后报错:
message [MapperParsingException[failed to parse [create_date]]; nested: IllegalArgumentException[Invalid format: "2016-07-04T03:03:12.616Z" is malformed at "T03:03:12.616Z"];]
根据错误提示,我先把时间格式化,然后插入:
result.put("create_date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(create_date));
然后插入OK。后来我看了源码,才恍然大悟。新版本(我不知道从什么版本开始,我以前最开始用的是0.9)值是根据value 的类型来判断。我贴一下。
org.elasticsearch.common.xcontent.XContentBuilder 中 1248 行。
private void writeValue(Object value) throws IOException { if (value == null) { generator.writeNull(); return; } Class<?> type = value.getClass(); if (type == String.class) { generator.writeString((String) value); } else if (type == Integer.class) { generator.writeNumber(((Integer) value).intValue()); } else if (type == Long.class) { generator.writeNumber(((Long) value).longValue()); } else if (type == Float.class) { generator.writeNumber(((Float) value).floatValue()); } else if (type == Double.class) { generator.writeNumber(((Double) value).doubleValue()); } else if (type == Byte.class) { generator.writeNumber(((Byte)value).byteValue()); } else if (type == Short.class) { generator.writeNumber(((Short) value).shortValue()); } else if (type == Boolean.class) { generator.writeBoolean(((Boolean) value).booleanValue()); } else if (type == GeoPoint.class) { generator.writeStartObject(); generator.writeNumberField("lat", ((GeoPoint) value).lat()); generator.writeNumberField("lon", ((GeoPoint) value).lon()); generator.writeEndObject(); } else if (value instanceof Map) { writeMap((Map) value); } else if (value instanceof Path) { //Path implements Iterable<Path> and causes endless recursion and a StackOverFlow if treated as an Iterable here generator.writeString(value.toString()); } else if (value instanceof Iterable) { generator.writeStartArray(); for (Object v : (Iterable<?>) value) { writeValue(v); } generator.writeEndArray(); } else if (value instanceof Object[]) { generator.writeStartArray(); for (Object v : (Object[]) value) { writeValue(v); } generator.writeEndArray(); } else if (type == byte[].class) { generator.writeBinary((byte[]) value); /* 注意这里:如果是Date类型,就是以字符串输出。 如果你跟进去看。代码在下个片段。 */ } else if (value instanceof Date) { generator.writeString(XContentBuilder.defaultDatePrinter.print(((Date) value).getTime())); } else if (value instanceof Calendar) { generator.writeString(XContentBuilder.defaultDatePrinter.print((((Calendar) value)).getTimeInMillis())); } else if (value instanceof ReadableInstant) { generator.writeString(XContentBuilder.defaultDatePrinter.print((((ReadableInstant) value)).getMillis())); } else if (value instanceof BytesReference) { BytesReference bytes = (BytesReference) value; if (!bytes.hasArray()) { bytes = bytes.toBytesArray(); } generator.writeBinary(bytes.array(), bytes.arrayOffset(), bytes.length()); } else if (value instanceof BytesRef) { BytesRef bytes = (BytesRef) value; generator.writeBinary(bytes.bytes, bytes.offset, bytes.length); } else if (value instanceof Text) { Text text = (Text) value; if (text.hasBytes() && text.bytes().hasArray()) { generator.writeUTF8String(text.bytes().array(), text.bytes().arrayOffset(), text.bytes().length()); } else if (text.hasString()) { generator.writeString(text.string()); } else { BytesArray bytesArray = text.bytes().toBytesArray(); generator.writeUTF8String(bytesArray.array(), bytesArray.arrayOffset(), bytesArray.length()); } } else if (value instanceof ToXContent) { ((ToXContent) value).toXContent(this, ToXContent.EMPTY_PARAMS); } else if (value instanceof double[]) { generator.writeStartArray(); for (double v : (double[]) value) { generator.writeNumber(v); } generator.writeEndArray(); } else if (value instanceof long[]) { generator.writeStartArray(); for (long v : (long[]) value) { generator.writeNumber(v); } generator.writeEndArray(); } else if (value instanceof int[]) { generator.writeStartArray(); for (int v : (int[]) value) { generator.writeNumber(v); } generator.writeEndArray(); } else if (value instanceof float[]) { generator.writeStartArray(); for (float v : (float[]) value) { generator.writeNumber(v); } generator.writeEndArray(); } else if (value instanceof short[]) { generator.writeStartArray(); for (short v : (short[]) value) { generator.writeNumber(v); } generator.writeEndArray(); } else { // if this is a "value" object, like enum, DistanceUnit, ..., just toString it // yea, it can be misleading when toString a Java class, but really, jackson should be used in that case generator.writeString(value.toString()); //throw new ElasticsearchIllegalArgumentException("type not supported for generic value conversion: " + type); }}
我们看下这部分:XContentBuilder.defaultDatePrinter.print(((Date) value).getTime()) 进去后。看到如下:
/** * Prints a millisecond instant to a String. * <p> * This method will use the override zone and the override chronology if * they are set. Otherwise it will use the ISO chronology and default zone. * * @param instant millis since 1970-01-01T00:00:00Z * @return the printed result */public String print(long instant) { StringBuilder buf = new StringBuilder(requirePrinter().estimatePrintedLength()); try { printTo((Appendable) buf, instant); } catch (IOException ex) { // StringBuilder does not throw IOException } return buf.toString();}
看到这里就明白了吧。他最终的输出方式都是以字符串输出,只是默认的格式是:1970-01-01T00:00:00Z ,也就是默认的 UTC 格式。我的时间转换结果成:2016-07-04T03:03:12.616Z 这里并且有时区的概念,东八区,这里输出的时间少了8 个小时。这个得注意。
总结了下。最终输出都是String 类型。感觉不友好。我本想的是,我不管存入是怎么样,我取出来得是Date 对象就可以了。
官网时间(Date)格式说明
关于时间类型说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/date.html
JSON doesn’t have a date datatype, so dates in Elasticsearch can either be:
- strings containing formatted dates, e.g. "2015-01-01" or "2015/01/01 12:10:30".
- a long number representing milliseconds-since-the-epoch.
- an integer representing seconds-since-the-epoch.
Internally, dates are converted to UTC (if the time-zone is specified) and stored as a long number representing milliseconds-since-the-epoch.
Date formats can be customised, but if no format is specified then it uses the default:
- "strict_date_optional_time||epoch_millis"
This means that it will accept dates with optional timestamps, which conform to the formats supported by strict_date_optional_time or milliseconds-since-the-epoch.
解决方法及问题:
1.时间输出格式,如果是默认 UTC 格式,时间不是我们常用的格式,而且时区问题,少了8个小时。
解决方案:
-
直接用毫秒值,缺点为不直观。
- 直接设置format为你想要的格式,比如
“yyyy-MM-dd HH:mm:ss”然后存储的时候,指定格式,并且 Mapping 也是指定相同的format。
2.存储Date,和取出来也是Dete?
解决方案:
- 存储的时候利用各种JSON对象,比如 json-lib , fastjson , Jackson , gson 等等。存储的时候就可以用JSON Format一下再存储,然后取出来后,在用
JSON.toBean(json,POJO.class),就解决了,这里利用的是相同 JSON 包转成 JSON ,然后又toBean回来,是没问题的,然后 Elasticsearch 也支持存储 JSON 。
好了上面观点纯属个人观点。可能存在错误和参杂个人色彩。请勿作为直接参考。错误的地方,请在下面留言。
Elasticsearch时间存储技巧
874

被折叠的 条评论
为什么被折叠?



