搭一個新項目,從mongo數據庫中查詢數據,我直接使用的spring-data-mongodb模塊。直接創建Repository接口,繼承MongoRepository
public interface ResourceRepository extends MongoRepository<Resource, Long> {
}
使用MongoRepository的findById方法,查詢"_id"爲某個值,程序中查詢不出來,但是數據庫中有值。並且使用db.getCollection(‘resource’).find({"_id":})驗證過。
然後查詢的時候debug了下,走到了MongoTemplate的findById方法中,它將_id替換爲了id:
public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
Assert.notNull(id, "Id must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityClass);
String idKey = ID_FIELD;//_id
if (persistentEntity != null) {
if (persistentEntity.getIdProperty() != null) {
idKey = persistentEntity.getIdProperty().getName();//賦值爲id
}
}
return doFindOne(collectionName, new Document(idKey, id), new Document(), entityClass);
}
idKey = persistentEntity.getIdProperty().getName();這句代碼把“_id”的key換爲了“id”,這樣如果mongo集合中只有“_id”字段,就查詢不出來了。
解決方案
繼承MongoTemplate類,重寫它的findById方法,我Debug後MongoTemplate執行的是public MongoTemplate(MongoDbFactory mongoDbFactory, @Nullable MongoConverter mongoConverter) 的構造方法:
我們也創建類似的構造方法:
@Component
public class PMongoTemplate extends MongoTemplate{
private static final String ID_FIELD = "_id";
@Autowired
public PMongoTemplate(MongoDbFactory mongoDbFactory, @Nullable MongoConverter mongoConverter) {
super(mongoDbFactory,mongoConverter);
}
@Override
public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
Assert.notNull(id, "Id must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
String idKey = ID_FIELD;
return doFindOne(collectionName, new Document(idKey, id), new Document(), entityClass);
}
}
啓動之後報如下錯誤,沒有創建Bean ‘mongoTemplate’:
因爲我們自定了一個MongoTemplate類型的Bean,所以Spring在初始化的時候,不不再創建同一類型的了。所以我們之前創建的ResourceRepository等接口都無法使用了,這些接口繼承MongoRepository,啓動後創建對應的SimpleMongoRepository,SimpleMongoRepository間接依賴了mongoTemplate。
這樣走不通,那我們就用MongoTemplate自帶的方法:
protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
Document mappedFields = queryMapper.getMappedObject(fields, entity);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query),
mappedFields, entityClass, collectionName);
}
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
new ReadDocumentCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
}
@Nullable
@Override
public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
if (ObjectUtils.isEmpty(query.getSortObject()) && !query.getCollation().isPresent()) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
} else {
query.limit(1);
List<T> results = find(query, entityClass, collectionName);
return results.isEmpty() ? null : results.get(0);
}
}
doFindOne是protected修飾的我們無法直接調用,findOne可以調用,我們只需要創建Query對象,將條件傳遞進去即可,下面是簡單的實現:
@Component
public class MongoQLService {
@Autowired
MongoTemplate mongoTemplate;
public <T> T findById(Object id,Class<T> entityClass,String collectionName) {
Query query = new Query();
query.addCriteria( new Criteria("_id").is(id));
return mongoTemplate.findOne(query, entityClass, collectionName);
}
}
關於Query的構建,參考Spring Data MongoDB 基本文檔查詢