只为一个字段编写一个Mongo Converter

时间:2014-02-18 09:39:24

标签: java mongodb spring-data

我的情况是我有一个简单的POJO,里面有一个长字段,实际上是一个时间戳。

此字段必须位于由ISODate表示的mongo数据库中。

我可以在整个Pojo中编写一个转换器,但由于它只有25个中的一个字段没有多大意义,并且当字段发生变化或者另外一个字段发生变化时,它是另一个错误点。

有没有办法在默认转换服务之后调用默认转换器服务并更改另外两个服务,这将对性能产生很大影响。 或者是否有一个默认的Converter接口来覆盖?

这里最好的做法是什么?

也许有一个我可以应用于这个领域的注释?

是的,我也可以写一个LongToDate转换器,但这会影响所有POJO,而不仅仅是这一个。

这里有样本POJO:

public class Person {
   private long someTimestamp;
//getters and setters
}

如果没有转换器,这将是这样的:

{
    "_id" : ObjectId("52ae8eede4b0249cde22059e"),
    "someTimestamp" : NumberLong(1392714950940)
}

该结果应如何:

{
    "_id" : ObjectId("52ae8eede4b0249cde22059e"),
    "someTimestamp" : ISODate("2013-12-23T23:00:00.000Z")
}

当这样的时间戳值在嵌套文档中时,所描述的问题变得更加复杂:

{
    "_id" : ObjectId("52ae8eede4b0249cde22059e"),
    "items" : [
        "someTimestamp" : NumberLong(1392714950940)
    ]
}

POJO:

public class Person {
   private Collection<OtherPojoThatHoldsTimestamps> items;
//getters and setters
}

也许我应该提到我使用Spring来实现这一目标。 (http://projects.spring.io/spring-data-mongodb/

4 个答案:

答案 0 :(得分:5)

听起来你需要注释驱动的转换。 reference manual中的第6.6.2节讨论了格式化程序的这一点,甚至还有一个带时间戳的例子。

一般的想法是,您要标记需要使用注释进行特殊处理的字段(它很好地使用了注释,您毕竟指定了元数据)。然后,您为具有该注释的字段注册转换器。

问题是我刚试过这个并且无法让它发挥作用,因为元数据正在迷失。我已经提交JIRA ticket来了解是否可以解决这个问题。

答案 1 :(得分:2)

解决方案之一是使用spring data mongodb listener:

package com.rc.user.auth.model.listener;

import com.mongodb.DBObject;
import com.nimbusds.jwt.JWTParser;
import com.rc.user.auth.model.OAuth2AccessTokenEntity;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
import org.springframework.stereotype.Component;

import java.text.ParseException;

@Component
public class OAuth2AccessTokenEntityListener extends AbstractMongoEventListener<OAuth2AccessTokenEntity> {

    @Override
    public void onBeforeSave(BeforeSaveEvent<OAuth2AccessTokenEntity> event) {
        OAuth2AccessTokenEntity oat = event.getSource();
        DBObject db = event.getDBObject();

        db.put("tokenValue", oat.getJwt().serialize());
    }

    @Override
    public void onAfterConvert(AfterConvertEvent<OAuth2AccessTokenEntity> event) {

        OAuth2AccessTokenEntity oat = event.getSource();
        DBObject db = event.getDBObject();

       try {
          oat.setJwt(JWTParser.parse(db.get("tokenValue").toString()));
      } catch (ParseException e) {
          e.printStackTrace();
        }
    }
}

解决方案二是使用转换器:

package com.rc.user.auth.model.convert;

import com.nimbusds.jwt.JWT;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;


@WritingConverter
public class JWTToStringConverter implements Converter<JWT, String> {

    @Override
    public String convert(JWT jwt) {
        return jwt.serialize();
    }
}

答案 2 :(得分:0)

您无法仅为一个班级定义Converter。但是,在morphia将其重新映射到您的班级之前,您可以使用@PreLoad来按摩该数据:

    @PreLoad
    void convertDate(final DBObject dbObject) {
        dbObject.put("someTimestamp", new Date(dbObject.get("someTimestamp")));
    }

有效的方法可以满足您的需求。这种方法有一些缺点。

  1. 你必须在你发现自己处于这种情况的每个领域都这样做。因此,上面的“项目”示例需要@PreLoad方法中的类似代码。希望这种情况不会经常发生,因此不应过于繁琐。
  2. 这是一个懒惰的转换。如果您的查询依赖于属性为日期,则他们将找不到尚未转换的记录。一种解决方案是通过morphia简单地加载/保存每个项目。除非您对对象进行版本控制,否则这可能会破坏对这些文档的其他写入。或者你可以编写一些javascript来在shell中运行来加载这些文档,创建一个新日期并简单地$ set回到那个字段。 (当然,你也可以用morphia和一些低级java驱动程序代码做类似的事情)。任何未启用版本控制的解决方案都可能需要一些停机时间,具体取决于您的应用程序及其使用情况。
  3. 希望这能让你开始朝着正确的方向前进。祝你好运。

答案 3 :(得分:0)

从 Spring Data MongoDB 3.2 开始,似乎无法轻松定义特定于字段的(非全局)类型转换器。

配置 bean 中的

The type converters registered through the overriden configureConverters() 方法影响所有域对象。

但是您可以使用不同类型的字段和访问器方法(getter/setter)使用简单的解决方法来完成您想要的。

public class Person{
  
  @AccessType(AccessType.Type.FIELD)
  private Instant timestamp;
  
  public long getTimestamp() {
    return this.timestamp.getEpochSecond();
  }
  
  public void setTimestamp(final long ts) {
    this.timestamp = Instant.ofEpochSecond(ts);
  }
}

在上面的代码中,到 MongoDB 的持久化映射器将看到 java.time.Instant 类型 字段和 timestamp 字段将映射到 MongoDB 中的 Date(ISODate) 类型。

但是,其他 Java 程序将通过 Person.timestamp 类型访问 long 字段,因为 getter 和 setter 使用 long 类型。

虽然这种方法使用字段级注释(如果存在)不是那么花哨,但它简单直观。

java.time.Instant 是 Java 8 之后日期/时间必不可少的类之一。它似乎最适合 Java 中的时间戳。 Spring Data MongoDB 为 java.util.Datejava.time.Instant 到/从 MongoDB 的 date(Date, ISODate) 类型提供了内置类型映射。

以下是我程序中的示例域对象。应用龙目岛。在域对象中,java.math.BigInteger 类型映射到 MongoDB 的 decimal(Decimal128) type

@Getter
@Setter
@RequiredArgsConstructor
@Accessors(chain = true)
@Document(collection = "transactions")
public class Transaction implements java.io.Serializable{

  @Id
  private final String hash;

  @Field(name = "block_no")
  private long blockNo;

  private long index;

  private String from;

  // denormalized field from account
  @Field("from_is_contr")
  private boolean fromIsContract;

  private String to;

  // denormalized field from account
  @Field("to_is_contr")
  private boolean toIsContract;

  @Getter(AccessLevel.NONE)
  @Setter(AccessLevel.NONE)
  @AccessType(AccessType.Type.FIELD)
  @Field(targetType = DECIMAL128)
  private BigDecimal value;

  public BigInteger getValue() {
    return this.value.toBigInteger();
  }

  public Transaction setValue(BigInteger val) {
    this.value = new BigDecimal(val);
    return this;
  }

  @Indexed
  private Instant at;

}