0

I'm using org.mongodb:bson:4.1.2 with org.springframework.boot:spring-boot-starter-data-mongodb:2.4.7.

My entity looks like:

@Entity
@Table(name = "fire_alert")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Document(collection = "alert_<dynamic>")
@Data
public class AlertPO implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "owner_id")
    private Long ownerId;

    @Column(name = "alert_type")
    private Long alertType;
}

Cause there will be millions of alerts, so I need to save records into different mongodb collections based on AlertPO.alertType.

After digging into org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity, I found the field collection of annotation @Document support SpEL expression. This kind of expressions will be evaluated in org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity#getCollection and it is obviously that current entity won't be added into the EvaluationContext.

According to this question:

How to Map a Java Entity to Multiple MongoDB Collections in Spring Data?

We can overwrite repositories to use MongoTemplate to persistent data into proper collection programmatically. But we're going to using JPA and we do not want to using MongoTemplate directly. How to do this?

Horsing
  • 1,070
  • 7
  • 22

1 Answers1

0

Although the SpEL expression's EvaluationContext contains no instances of AlertPO, but we can make it possible through ThreadLocal instances registered in BeanFactory.

Such as:

@Component(value = "collection")
public class CollectionHolder {
    private static final ThreadLocal<Stack<String>> COLLECTION = ThreadLocal.withInitial(Stack::new);

    public void push(Alert alert) {
        COLLECTION.get().push(typeToCollection(alert.getAlertType()));
    }

    public void push(Long type) {
        COLLECTION.get().push(typeToCollection(type));
    }

    public String top() {
        return COLLECTION.get().peek();
    }

    public void pop() {
        COLLECTION.get().pop();
    }
}

Now we can push collection name:

@Service
public class MongoAlertStorage implements IAlertStorage {
    @Autowired
    private CollectionHolder holder;
    @Autowired
    private AlertRepository alertRepository;
    @Autowired
    private Converters converters;

    @Override
    public Alert save(Alert alert) throws Exception {
        try {
            holder.push(alert);
            return converters.of(alertRepository.save(converters.of(alert)));
        } finally {
            holder.pop();
        }
    }
}

It is time to specify dynamic collection in expression:

@Document(collection = "alert_#{@collection.top()}")
public class AlertPO implements Serializable {
    // ...
}

Now, you can debug at org.springframework.expression.common.CompositeStringExpression#getValue(org.springframework.expression.EvaluationContext) to check the value evaluated, it should be what you want.

Horsing
  • 1,070
  • 7
  • 22