对于springframework的mongoTemplate扩展自定义的分享

Posted Derrick_gu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对于springframework的mongoTemplate扩展自定义的分享相关的知识,希望对你有一定的参考价值。

之前对于spring的mongoTemplate真的是有点又爱又恨,由于它对mongodb的驱动做了一层封装,使得在开发的时候方便了许多,但是它的语法和mongo的原生js有很大不同,有时候在mongo官方文档里的API接口很多时候在mongoTemplate中的使用完全不一样,导致有些时候用的很别扭,而且一些语句完全不知道怎么去转换为template的语法。不过最近的两次使用经历使得我对mongoTemplate有了一些改观。

第一个就是mongoTemplate自身的criteria.where没有>和<的操作,也就是对mongo的一条记录自身的两个字段进行比较。mongo语句如下:

db.whereColl.find($where: "this.b > this.a")

当时研究和许久,最后实在没招了,扒源码找出了它的Criteria实现,下面是它的源码实现:

/**
 * Creates a criterion using the @literal $elemMatch operator
 * 
 * @see http://docs.mongodb.org/manual/reference/operator/query/elemMatch/
 * @param c
 * @return
 */
public Criteria elemMatch(Criteria c) 
   criteria.put("$elemMatch", c.getCriteriaObject());
   return this;
/**
 * Creates an 'and' criteria using the $and operator for all of the provided criteria.
 * <p>
 * Note that mongodb doesn't support an $and operator to be wrapped in a $not operator.
 * <p>
 * 
 * @throws IllegalArgumentException if @link #andOperator(Criteria...) follows a not() call directly.
 * @param criteria
 */
public Criteria andOperator(Criteria... criteria) 
   BasicDBList bsonList = createCriteriaList(criteria);
   return registerCriteriaChainElement(new Criteria("$and").is(bsonList));
private BasicDBList createCriteriaList(Criteria[] criteria) 
   BasicDBList bsonList = new BasicDBList();
   for (Criteria c : criteria) 
      bsonList.add(c.getCriteriaObject());
   
   return bsonList;
public DBObject getCriteriaObject() 

   if (this.criteriaChain.size() == 1) 
      return criteriaChain.get(0).getSingleCriteriaObject();
    else if (CollectionUtils.isEmpty(this.criteriaChain) && !CollectionUtils.isEmpty(this.criteria)) 
      return getSingleCriteriaObject();
    else 
      DBObject criteriaObject = new BasicDBObject();
      for (Criteria c : this.criteriaChain) 
         DBObject dbo = c.getSingleCriteriaObject();
         for (String k : dbo.keySet()) 
            setValue(criteriaObject, k, dbo.get(k));
         
      
      return criteriaObject;
   
protected DBObject getSingleCriteriaObject() 

   DBObject dbo = new BasicDBObject();
   boolean not = false;

   for (String k : this.criteria.keySet()) 
      Object value = this.criteria.get(k);
      if (not) 
         DBObject notDbo = new BasicDBObject();
         notDbo.put(k, value);
         dbo.put("$not", notDbo);
         not = false;
       else 
         if ("$not".equals(k) && value == null) 
            not = true;
          else 
            dbo.put(k, value);
         
      
   

   if (!StringUtils.hasText(this.key)) 
      if (not) 
         return new BasicDBObject("$not", dbo);
      
      return dbo;
   

   DBObject queryCriteria = new BasicDBObject();

   if (!NOT_SET.equals(isValue)) 
      queryCriteria.put(this.key, this.isValue);
      queryCriteria.putAll(dbo);
    else 
      queryCriteria.put(this.key, dbo);
   

   return queryCriteria;
private void setValue(DBObject dbo, String key, Object value) 
   Object existing = dbo.get(key);
   if (existing == null) 
      dbo.put(key, value);
    else 
      throw new InvalidMongoDbApiUsageException("Due to limitations of the com.mongodb.BasicDBObject, "
            + "you can't add a second '" + key + "' expression specified as '" + key + " : " + value + "'. "
            + "Criteria already contains '" + key + " : " + existing + "'.");
   
通过上面的源码可以知道,mongoTemplate的语法转换是通过构建一个DBObject,然后将查询语法转换为mongo的Java驱动的语法,也是接近于原生的语法。

知道了这个,稍微对com.mongodb包有些经验的都应该知道要怎么去操作了,我们通过复写getCriteriaObject方法来实现自定义查询语句,突破mongoTemplate的限制,我的实现如下:

    public ListResponse<AObject> loadAObjectList(String aId, String bId, int start, int limit) 
        ListResponse<AObject> AObjectListResp = new ListResponse<AObject>();
        Criteria criteria = new Criteria() 
            @Override
            public DBObject getCriteriaObject() 
                DBObject obj = new BasicDBObject();
                obj.put("$where", "this.groupNum > this.joinedNum");
                return obj;
            
        ;
        Query query = Query.query(criteria);
        query.addCriteria(Criteria.where("members").nin(bId).and("aId").is(aId)).with(new Sort(Sort.Direction.DESC, "gmtCreated")).skip((start-1) * limit).limit(limit);
        List<AObject> AObjectList = template.find(query, AObject.class);
        long count = template.count(new Query(criteria).addCriteria(.where("members").nin(bId).and("aId").is(aId)), AObject.class);
        return AObjectListResp.fill(ResponseCode.SUCCESS, "success", AObjectList, count, count > start * limit);
    

上面我们通过复写相关方法来自定义Criteria语法,而另一个事件是今天的一个项目,需要从mongo中随机取出一定数量的文档,我们知道mongo在3.2版本之后对此加入了一个官方的api,我们可以使用aggregate管道的$sample来读取文件,mongo语法如下:

db.users.aggregate(
   [  $sample:  size: 3   ]
)
我看了mongotemple的aggregate相关API,发现它的API只有以下几个,即便是升级到最新的1.10.0-RELEASE版本也是如此,最后我想起上面的自定义扩展,于是,又去吭哧吭哧地翻看源代码,首先:
template.aggregate(Aggregation.newAggregation(Aggregation.match(Criteria),.....),....);
我发现它的语法条件都是通过Aggregation.XXX来操作的,于是,我打开Aggregation.XXX的源码:

/**
 * Creates a new @link MatchOperation using the given @link Criteria.
 * 
 * @param criteria must not be @literal null.
 * @return
 */
public static MatchOperation match(Criteria criteria) 
   return new MatchOperation(criteria);
/**
 * Creates a new @link LimitOperation limiting the result to the given number of elements.
 * 
 * @param maxElements must not be less than zero.
 * @return
 */
public static LimitOperation limit(long maxElements) 
   return new LimitOperation(maxElements);
我发现它是构建不同的XXXOperation,于是,我打开MatchOperation和LimitOperation:

public class MatchOperation implements AggregationOperation 

   private final CriteriaDefinition criteriaDefinition;

   /**
    * Creates a new @link MatchOperation for the given @link CriteriaDefinition.
    * 
    * @param criteriaDefinition must not be @literal null.
    */
   public MatchOperation(CriteriaDefinition criteriaDefinition) 

      Assert.notNull(criteriaDefinition, "Criteria must not be null!");
      this.criteriaDefinition = criteriaDefinition;
   

   /* 
    * (non-Javadoc)
    * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
    */
   @Override
   public DBObject toDBObject(AggregationOperationContext context) 
      return new BasicDBObject("$match", context.getMappedObject(criteriaDefinition.getCriteriaObject()));
   
public class LimitOperation implements AggregationOperation 

   private final long maxElements;

   /**
    * @param maxElements Number of documents to consider.
    */
   public LimitOperation(long maxElements) 

      Assert.isTrue(maxElements >= 0, "Maximum number of elements must be greater or equal to zero!");
      this.maxElements = maxElements;
   

   /* 
    * (non-Javadoc)
    * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
    */
   @Override
   public DBObject toDBObject(AggregationOperationContext context) 
      return new BasicDBObject("$limit", maxElements);
   
到这儿,我发现最终,它的操作也是落到了BasicDBObject上面,并且都是通过继承AggregationOperation来实现的,于是我照着葫芦画瓢,自己自定义了一个SampleOperation类:

public class SampleOperation implements AggregationOperation 


    private final long maxElements;

    /**
     * @param maxElements Number of documents to consider.
     */
    public SampleOperation(long maxElements) 

        Assert.isTrue(maxElements >= 0, "Maximum number of elements must be greater or equal to zero!");
        this.maxElements = maxElements;
    

    /*
    * (non-Javadoc)
    * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
    */
    @Override
    public DBObject toDBObject(AggregationOperationContext context) 
        return new BasicDBObject("$sample",  new BasicDBObject("size", maxElements));
    
通过这两次经历,我发现mongoTemplate虽然语法上和原生的语法有些不同之处,但是它在设计之初充分地考虑到了开发的自定义扩展。

上面是我个人的一些小小的心(惨)得(通)经(教)验(训),希望能够对大家有些帮助。

以上是关于对于springframework的mongoTemplate扩展自定义的分享的主要内容,如果未能解决你的问题,请参考以下文章

org.springframework.web.client.HttpClientErrorException$BadRequest: 400 错误请求

IDEA中程序包Org.Springframework.Boot不存在

springframework开源代码导入eclipse

断代式升级!Spring Framework 6.0 正式发布!!

断代式升级!Spring Framework 6.0 正式发布!!

使用ControllerAdvice注意事项,Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.