5

I am trying to have a consistent db where the username and email are unique.

http://www.mongodb.org/display/DOCS/Indexes#Indexes-unique%3Atrue

http://code.google.com/p/morphia/wiki/EntityAnnotation

My user class looks like this:

public class User {
    @Indexed(unique = true)
    @Required
    @MinLength(4)
    public String username;

    @Indexed(unique = true)
    @Required
    @Email
    public String email;

    @Required
    @MinLength(6)
    public String password;

    @Valid
    public Profile profile;

    public User() {
...

I used the @Indexed(unique=true) annotation but it does not work. There are still duplicates in my db.

Any ideas how I can fix this?

Edit:

I read about ensureIndexes but this seems like a wrong approach, I don't want to upload duplicate data, just to see that its really a duplicate.

I want to block it right away.

somthing like

try{

ds.save(user);
}
catch(UniqueException e){
...
}
Maik Klein
  • 15,548
  • 27
  • 101
  • 197

5 Answers5

5

A unique index cannot be created if there are already duplicates in the column you are trying to index.

I would try running your ensureIndex commands from the mongo shell:

db.user.ensureIndex({'username':1},{unique:true})
db.user.ensureIndex({'email':1},{unique:true})

.. and also check that the indexes are set:

db.user.getIndexes()

Morphia should have WriteConcern.SAFE set by default, which will throw an exception when trying to insert documents that would violate a unique index.

Stennie
  • 63,885
  • 14
  • 149
  • 175
1

There is good explanation about unique constraint right here Unique constraint with JPA and Bean Validation , does this help you at all? So what I would do is just to validate your data at controller level (or bean validate()) when checking other errors as well. That will do the job, but its not as cool than it would be with annotation.

Edit

Alternatively see this post Inserting data to MongoDB - no error, no insert which clearly describes that mongodb doesn't raise error by default of unique indexes if you don't tell it so, try configuring your mongodb to throw those errors too and see if you can work on with solution :(

Edit 2

It also crossed my mind that play 2 has a start up global class where you could try to access your database and run your indexed column commands like this db.things.ensureIndex({email:1},{unique:true}); ?? see more at http://www.playframework.org/documentation/2.0/JavaGlobal

Community
  • 1
  • 1
Mauno Vähä
  • 9,688
  • 3
  • 33
  • 54
  • 1
    hm, I don't get it. Sure i can check it, but what if two users would register with the same username in the same time? I would need something that is atomic. – Maik Klein Aug 04 '12 at 22:24
  • Databases has their own build in locking systems (read, write locks etc.), so i believe that if there is unique constraints at database level - saving duplicate data at the same time would not be possible? – Mauno Vähä Aug 04 '12 at 22:26
  • Maybe I am wrong but I thought you meant something like this. `if(user.not_in_db) ds.save(user);` And if I have no constraint at the db level there will be duplicated data. – Maik Klein Aug 04 '12 at 22:35
  • 1
    Yes, keep your constraint at db level no matter what, its easier to make sure your db will at least block the data if something at bean level fails like annotations. With play 2 you can create separate Login class which has validate() method to be runned when data is coming from template. Start by looking zentasks project which can be found from your play installation samples folder. Login class what i meant its located at play/samples/java/zentasks/app/controllers/Application.java there is example how to validate null cases of username/password, extend it to validate unique users also. – Mauno Vähä Aug 04 '12 at 22:48
  • Plus, if you dont want to use separate Login class but User bean instead all the time (at template when asking values etc.) You can add that validate() method to your User class (it will be automatically called by play when form send to corresponding controller which handles form submission). – Mauno Vähä Aug 04 '12 at 22:52
  • Yes thanks I am already using this, but can you point me to the constraints at the db level? I can't find anything about it. – Maik Klein Aug 04 '12 at 22:53
  • @MaikKlein: Uniqueness constraints at the DB level are enforced by [indexes](http://www.mongodb.org/display/DOCS/Indexes#Indexes-unique%3Atrue). – Stennie Aug 05 '12 at 07:33
0

I had the same issue, with play framework 1.2.6 and morphia 1.2.12.

The solution for the @Indexed(unique = true) annotation, is to let morpha to re create the collection. So if I already had the "Account" collection in mongo, and annotated the email column, and re started the play app, nothing changed in the Account indexes.

If I dropped the Account ollection, morphia re crated it, and now the email column is unique:

> db.Account.drop()
true

After play restart: (I have a job to create initial accounts...)

> db.Account.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "ns" : "something.Account",
                "name" : "_id_"
        },
        {
                "v" : 1,
                "key" : {
                        "email" : 1
                },
                "unique" : true,
                "ns" : "something.Account",
                "name" : "email_1"
        }
]

Now, after an insert with an already existing email, I get a MongoException.DuplicateKey exception.

Kristóf Dombi
  • 3,927
  • 3
  • 18
  • 14
0

To create indexes, the Datastore.ensureIndexes() method needs to be called to apply the indexes to MongoDB. The method should be called after you have registered your entities with Morphia. It will then synchronously create your indexes. This should probably be done each time you start your application.

Morphia m = ...
Datastore ds = ...

m.map(Product.class);
ds.ensureIndexes(); //creates all defined with @Indexed
0

Morphia will create indexes for the collection with either the class name or with the @Entity annotation value.

For example if your class name is Author:

  • Please make sure you have @Indexed annotation in you Entity class and you have done these two steps:

    m.map(Author.class);

    ds.ensureIndexes();

  • Check indexes on mongo db

    b.Author.getIndexes()

I am adding this answer, to emphasize that you can not create indexes with a custom collection name(Entity class is Author, but your collection name is different)

This scenario is obvious in many cases, where you want to reuse the Entity class if the schema is same