1

I'm doing the following search with mongodb in a Spring application:

import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

private CampaignEntity incrByWithConversionRate(ObjectId campaignId, String key) {
    Query query = new Query();
    query.addCriteria(Criteria.where("id").is(campaignId));

    Update update = new Update();
    update.inc(key);
    update.set("conversionRate", new Document("$divide", Arrays.asList("$clicks", "$views")));

    return mongoTemplate.findAndModify(
        query, update,
        new FindAndModifyOptions().returnNew(true), CampaignEntity.class);
}

The problem is that it fails with the following error:

org.springframework.data.mapping.MappingException: Expected to read Document Document{{$divide=[$clicks, $views]}} into type class java.lang.Float but didn't find a PersistentEntity for the latter!

Note: The CampaignEntity class is defined as follows:

@Data
@Document(collection = "campaigns")
public abstract class CampaignEntity extends BaseEntity {
    private Integer clicks = 0;
    private Integer views = 0;
    private Float conversionRate;
}
Ariel Malka
  • 15,697
  • 6
  • 31
  • 33
  • Update: I tried to change the type of "clicks" and "views" to Float, but it didn't help. – Ariel Malka May 02 '23 at 14:04
  • 1
    $divide is an aggregation operator, not an update operator. You may be able to use [update with aggregation pipeline](https://www.mongodb.com/docs/manual/tutorial/update-documents-with-aggregation-pipeline/#updates-with-aggregation-pipeline), but I don't know how to do that with spring – Joe May 02 '23 at 16:54

1 Answers1

0

I ended up using the following solution:

MnoCampaignEntity campaign = new MnoCampaignEntity();
campaign.setClicks(29);
campaign.setViews(5);
campaignRepository.save(campaign);

String query1 = "{$match: {_id: ObjectId(\"" + campaign.getId() + "\")}}";
String query2 = "{$project: {views: 1, clicks: {$add: [\"$clicks\", 1]}}}";
String query3 = "{$project: {views: 1, clicks: 1, conversionRate: {$cond: [{$eq: [\"$views\", 0]}, 0, {$divide: [\"$clicks\", \"$views\"]}]}}}";
String query4 = "{$merge: \"campaigns\"}";

TypedAggregation<MnoCampaignEntity> aggregation = Aggregation.newAggregation(
    MnoCampaignEntity.class,
    new CustomAggregationOperation(query1),
    new CustomAggregationOperation(query2),
    new CustomAggregationOperation(query3),
    new CustomAggregationOperation(query4)
);

mongoTemplate.aggregate(aggregation.withOptions(new AggregationOptions.Builder()
    .skipOutput()
    .allowDiskUse(true)
    .build()),
    "campaigns", MnoCampaignEntity.class);

CampaignEntity updated = campaignService.findById(campaign.getId().toString());
System.out.println("CONVERSION RATE: " + updated.getConversionRate());

Note: the CustomAggregationOperation class is from this SO answer.

Ariel Malka
  • 15,697
  • 6
  • 31
  • 33