【问题标题】:Retrieving the specific MongoDB key from DuplicateKeyException that was actually duplicated (Java/Spring)从 DuplicateKeyException 中检索实际复制的特定 MongoDB 键(Java/Spring)
【发布时间】:2014-04-15 20:59:02
【问题描述】:

我正在使用 Spring-Data/MongoDB 并在保存/插入时正确捕获重复键。

例如,假设我有一个用户被保存到一个集合中。 User 对象用两个@Indexed(unique=true)(两个唯一键)注释。假设它们是“电子邮件”和“用户名”。如何检索在插入过程中实际复制的索引。

我得到的最接近的是当我执行这种类型的示例代码时:

public boolean createNewUser() {
    MongoTemplate operations = RepositoryFactory.getMongoOperationsInstance();
    try {
        log.debug("Saving new user to DB");
        operations.save(this);
        return true;
    } catch (DuplicateKeyException dke) {
        log.debug("User with same username or email found");    
        log.debug(operations.getDb().getLastError());
        return false;
    }
}

这会打印字符串:

{ "serverUsed" : "/127.0.0.1:27017" , "err" : "E11000 duplicate key error index: Collection.user.$username  dup key: { : \"user\" }" , "code" : 11000 , "n" : 0 , "connectionId" : 17 , "ok" : 1.0}

没有愚蠢的字符串操作或 Json 转换,有没有办法通过 Mongodriver API 提取Collection.user.$username

我一直在搜索不成功。

【问题讨论】:

标签: java spring mongodb spring-data


【解决方案1】:

并非如此,因为 Mongo Java 驱动程序已经将最后一个错误公开为构造字符串:

writeResult.getLastError().get("err") 返回如下内容:

insertDocument :: caused by :: 11000 E11000 duplicate key error index: test.person.$username dup key: { : "joe" }

我想这对于 shell 和每个驱动程序也是如此。

我认为一个合理的解决方案是使用自定义异常来解析这样的重复键异常:

public class DetailedDuplicateKeyException extends DuplicateKeyException {
    public DetailedDuplicateKeyException(String msg) {
        // Instead of just calling super parse the message here.
        super(msg);
    }
}

...自定义异常翻译器:

public class DetailedDuplicateKeyExceptionTransaltor extends MongoExceptionTranslator {

    @Override
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        if (ex instanceof MongoException.DuplicateKey) {
            return new DetailedDuplicateKeyException(ex.getMessage());
        }
        return super.translateExceptionIfPossible(ex);
    }
}

...并正确设置 Spring 配置:

@Bean
public MongoFactoryBean mongo() {
    MongoFactoryBean mongo = new MongoFactoryBean();
    mongo.setExceptionTranslator(new DetailedDuplicateKeyExceptionTransaltor());
    mongo.setHost("localhost");
    return mongo;
}

编辑

检查MongoTemplate 代码(1.4.1.RELEASE)后,似乎在内部使用SimpleMongoDbFactory 来检索默认MongoExceptionTranslator,因此使用MongoFactoryBean 创建的那个被隐藏了。错过了那部分。

解决方案是覆盖SimpleMongoDbFactory(忘记MongoFactoryBean,在这种情况下没用):

public class MySimpleMongoDbFactory extends SimpleMongoDbFactory {

    PersistenceExceptionTranslator translator = new       
            DetailedDuplicateKeyExceptionTransaltor();

    public MySimpleMongoDbFactory(Mongo mongo, String databaseName) {
        super(mongo, databaseName);
    }

    @Override
    public PersistenceExceptionTranslator getExceptionTranslator() {
        return translator;
    }
}

现在您可以使用自定义MongoDbFactory 构建模板:

template = new MongoTemplate (new MySimpleMongoDbFactory(new MongoClient(), "test"));

试过了,这个对我有用。

【讨论】:

  • 您好,谢谢您的回复。直到现在我还没有使用异常翻译,所以我对它比较陌生。但是,我已经做了足够的阅读,但仍然无法让翻译器真正被调用。我已按照上述步骤进行操作,您的回复范围之外还有什么遗漏吗?
  • 谢谢!那会很好的。那么 MongoDbFactory/SimpleMongoDbFactory 和 MongoFactoryBean 到底有什么区别呢?我已经将它们都用于同一目的,但不明白其中的区别。此外,如果你有时间,你认为你能解释一下“被影子”是什么意思吗?
  • 不同之处,您可以查阅参考资料或 API 文档。 “阴影”是一个不好的词选择。我没有任何与可变阴影有关的意思。我的意思是,使用 MongoFactoryBean 配置的异常翻译器永远不会传播以与模板一起使用。 MongoFactoryBean 实现了 PersistenceExceptionTranslator,但由于它只是一个创建 Mongo bean 的工厂,我看不出它背后的原因。
【解决方案2】:

如果您在使用spring-data-rest/spring-data-mongodb 时遇到此问题,我编写了一个@ControllerAdvice 类,它使用@ExceptionHandler 方法以与验证类相同的方式返回错误。

我似乎没有在接受的答案中使用类,这就是我发布这个的原因。

我愿意接受有关解决此问题的更好方法的建议(在 Spring Data 中)/实施此@ExceptionHandler

@ControllerAdvice
public class ControllerExceptionHandler {

  @ExceptionHandler(DuplicateKeyException.class)
  @ResponseStatus(value = HttpStatus.CONFLICT)
  @ResponseBody
  public Map<String, Object> handleDuplicateKeyException(DuplicateKeyException e) {
    String entity = null;
    String message = null;
    String invalidValue = null;
    String property = null;

    String errorMessage = e.getMessage();

    Pattern pattern = Pattern.compile("\\.(.*?) index: (.*?) dup key: \\{ : \\\\\"(.*?)\\\\\"");
    Matcher matcher = pattern.matcher(errorMessage);
    if (matcher.find()) {
      entity = WordUtils.capitalize(matcher.group(1));
      property = matcher.group(2);
      invalidValue = matcher.group(3);
    }

    message = WordUtils.capitalize(property) + " must be unique";

    Map<String, String> uniqueIndexViolation = new HashMap<>();
    uniqueIndexViolation.put("entity", entity);
    uniqueIndexViolation.put("message", message);
    uniqueIndexViolation.put("invalidValue", invalidValue);
    uniqueIndexViolation.put("property", property);

    List<Object> errors = new ArrayList<Object>();
    errors.add(uniqueIndexViolation);

    Map<String, Object> responseBody = new HashMap<>();
    responseBody.put("errors", errors);

    return responseBody;
  }

}

【讨论】:

  • WordUtils.capitalize 方法在哪里?
  • 在给出这个答案时它位于 org.apache.commons.lang3.text.WordUtils 中,但现在似乎处于不同的依赖关系中。见stackoverflow.com/a/44599958/2464657
猜你喜欢
  • 1970-01-01
  • 2018-03-09
  • 1970-01-01
  • 2020-08-11
  • 1970-01-01
  • 2017-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多