从DuplicateKeyException中检索实际重复的特定MongoDB密钥(Java / Spring)

时间:2014-04-15 20:59:03

标签: java spring mongodb spring-data

我正在使用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

我一直在搜索失败。

2 个答案:

答案 0 :(得分:2)

不是真的,因为Mongo Java Driver已经将最后一个错误暴露为构造的String:

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"));

试过了,这个适合我。

答案 1 :(得分:0)

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

我似乎没有在接受的答案中使用这些课程,这就是我发帖的原因。

我愿意接受更好的方法来解决这个问题(在Spring数据中)/实现这个@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;
  }

}