搭一个新项目,从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 基本文档查询