I'm implementing the "auto-increment" id using strategy described here: http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/
Basically the value of the seqId
field is set by calling an utility function that updates the counter on an auxiliary collection and returns the incremented value. Sounds great.
My issue is in mapping this to be used with Morphia. The tutorial suggests performing the insert (such as in the shell) like so:
db.users.insert(
{
seqId: getNextSequence("userid"),
name: "Sarah C."
}
I'm basically looking to do something like setting the POJO seqId
field to something that Morphia will translate into an insert like the one above when I invoke save()
.
My POJO looks like this:
@Entity
public class User {
@Id
private Long id;
// THIS IS THE FIELD I WANT TO AUTO-INCREMENT
private Long seqId;
private String name;
...
}
The question is: How to make Morphia set the value of a field as the value returned by a function call?
I looked into using the @PrePresist
annotation to perform this function call and getting the value, then setting it in the +_id field. That has several drawbacks such as making multiple calls to MongoDB instead of just one, and also the fact that my model objects don't have a reference to the datastore and I'd rather not mix up the concerns.
Is this possible? Any suggestions?
I'm on MongoDB 2.6.6 using the latest Java drivers.
Thanks!
PS: I'm aware that auto-increment is not recommended in large environments. I need it anyways for this specific scenario.
I'll describe the solution that's working for us quite well. Note that this supports auto increments on the class level and a subset of it — so you can count users or admin-users (user with an admin enum or whatever).
This contains the current value for each auto increment field, it's basically a reference:
@Entity(noClassnameStored = true)
public class AutoIncrementEntity {
@Id
protected String key;
protected Long value = 1L;
protected AutoIncrementEntity() {
super();
}
/**
* Set the key name — class or class with some other attribute(s).
*/
public AutoIncrementEntity(final String key) {
this.key = key;
}
/**
* Set the key name and initialize the value so it won't start at 1.
*/
public AutoIncrementEntity(final String key, final Long startValue) {
this(key);
value = startValue;
}
public Long getValue() {
return value;
}
}
In your persistence service, you could use the following to set / create the auto increment automatically:
public <E extends BaseEntity> ObjectId persist(E entity) {
// If it's a user and doesn't yet have an ID, set one; start counting from 1000.
if ((entity instanceof UserEntity) && (((UserEntity) entity).getUserId() == null)) {
((UserEntity) entity).setUserId(
generateAutoIncrement(entity.getClass().getName(), 1000L));
}
// Additionally, set an ID within each user group; start counting from 1.
if ((entity instanceof UserEntity) && (((UserEntity) entity).getRoleId() == null)) {
((UserEntity) entity).setRoleId(
generateAutoIncrement(entity.getClass().getName() + "-" + entity.getRole(), 1L));
}
mongoDataStore.save(entity);
return entity.getId();
}
/**
* Return a unique numeric value for the given key.
* The minimum value, set to 1 if nothing specific is required.
*/
protected long generateAutoIncrement(final String key, final long minimumValue){
// Get the given key from the auto increment entity and try to increment it.
final Query<AutoIncrementEntity> query = mongoDataStore.find(
AutoIncrementEntity.class).field("_id").equal(key);
final UpdateOperations<AutoIncrementEntity> update = mongoDataStore
.createUpdateOperations(AutoIncrementEntity.class).inc("value");
AutoIncrementEntity autoIncrement = mongoDataStore.findAndModify(query, update);
// If none is found, we need to create one for the given key.
if (autoIncrement == null) {
autoIncrement = new AutoIncrementEntity(key, minimumValue);
mongoDataStore.save(autoIncrement);
}
return autoIncrement.getValue();
}
And finally your entity:
@Entity(value = "user", noClassnameStored = true)
public class UserEntity extends BaseEntity {
public static enum Role {
ADMIN, USER,
}
private Role role;
@Indexed(unique = true)
private Long userId;
private Long roleId;
// Role setter and getter
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
There's nothing specific going on in the entity. All the logic is handled by the persistence service. I'm not using the @PrePersist
, because you'd then need to put the persistence service into the entity, which doesn't sound like a good idea.