realm/realm-java

Document how to set an auto increment id?

djyde opened this issue ยท 48 comments

Document how to set an auto increment id?

Hi Randy,
We currently don't support that, but plan to do so.
Until then, you would have to create a field yourself.
Could you please expand on your needs for it?

There was also some related discussion here:
https://groups.google.com/forum/#!topic/realm-java/6hFqdyoH67w

Thanks!

On Sun, Oct 12, 2014 at 9:55 AM, Randy notifications@github.com wrote:

โ€”
Reply to this email directly or view it on GitHub
#469.

I would also be interested in an auto id feature. Maybe not even an increment but a UUID.

i use this on android

                realm.beginTransaction();
                dbObj obj = realm.createObject(dbObj.class);

                // increatement index
                int nextID = (int) (realm.where(dbObj.class).maximumInt("id") + 1);

                // insert new value
                obj.setId(nextID);
                obj.setName("thang");
                obj.setAge(10);

                realm.commitTransaction();

id always max+1, but it isn't good solution

When I build a Messaging application I would like to create local fake message(s) with unique IDs and then update them with the actual unique key I get from the server after successful post. The way I do this in SQL is by setting an int id field to Primary Key and then setting String uniqueKey as part of my Unique Key. I've just started experimenting with Realm today, but some of these limitations have really made it difficult for me to migrate to Realm.

I have a lot of local models which needs an unique identifier.
I pass the id to fragments and threads and fetch them then from realm.

I'm doing something like this for now: model.setId(UUID.randomUUID().toString());
I'm still not sure what I should do when a collision occurs.

  • Overriding is no option in my case
  • Letting the app crash could lead to unpredictable states.

I'm thinking about either query every id before adding the model to realm or implement a rollback strategy (not sure how, or if possible).
Has anyone similar use cases?

@markini Collision of UUID is very little chance.

@Rexota yeah, we decided to just go without collision detection.
I looked a little bit into the UUID documentation and I guess collisions of said ids are indeed the least of my worries.

@bmunkholm any update on it?

I to solve this issue with timestamp. What you do think about this? Int(NSDate().timeIntervalSince1970)

@ronanrodrigo Im afraid that this is bad way, I insert list of 8 and pairs of 3 have same ID (I use getNanos, miliseconds doesnt work for sure)

Timestamps could work, but they only have millisecond precision which means that there is a small chance of two timestamps being created at the same time. Using an AtomicInteger would be safer:

e.g.

// Find max value when opening the DB
RealmConfiguration config = new RealmConfiguration.Builder(context).build();
Realm realm = Realm.getInstance(config);
AtomicLong primaryKeyValue = realm.where(Foo.class).max("id").longValue();
realm.close();

// ....

// Create new object
new Foo(primaryKeyValue.incrementAndGet());

For now, I use something like this (For inserting List of RealmObjects):

int listSize = list.size();
for (int index = 0; index < listSize; index++) {
     list.get(index).setId(setUniqueId() + index);
}
// insert List to database

public static long setUniqueId() {
        Number num = realm.where(RealmObject.class).max(COLUMN_ID_NAME);
        if (num == null) return 1;
        else return ((long) num + 1);
    }

Miliseconds as id fail me because I get same id for 3 objects. My temp method lower performance quite a bit, inserting Lists of 8x RealmObjects (object has: long, String, String, int), 5000 times without uniqueId on average takes 3.755sec, with 5.312sec, but every next insertion (when database increase by 5000x8 objects) it adds up about 6.5sec every time, due to checking max id in the database.

EDIT: this can be done better, just save maxId in onCreate and then use it till you are done with Realm, and when you wanna use Realm again just get maxId again, not every time like in code above.

You don't have to run that query every time, just once when the app start:

public class MyApplication extends Application {

  public static AtomicLong primaryKeyValue;

  public void onCreate() {
    RealmConfiguration config = new RealmConfiguration.Builder(context).build();
    Realm realm = Realm.getInstance(config);
    primaryKeyValue = new AtomicLong(realm.where(Foo.class).max("id").longValue());
    realm.close();
  }
}

public class MyActivity extends Activity {

  protected void onCreate() {
    long nextKey = MyApplication.primaryKeyValue.incrementAndGet();
  }
}

I would probably encapsulate it in something like a PrimaryKeyFactory class, but I hope the idea is clear. Just get the maximum key when starting the app, and then it is super cheap to generate the next value with an AtomicLong

@cmelchior great work around. ๐Ÿ‘
But would love to have annotation like @AutoIncrement which I can use in my RealmObjects.

@cmelchior, I think this is a good workaround as well. The problem lies in usage in an app that uses multiple Model classes. The Application's onCreate() could get very bloated fairly quickly.

This is something usually handled below the surface in most databases (I'm thinking MySQL and SQLite). Is this something you guys are trying to target for the official 1.0.0 release?

@fawaad I think the next release that's due (hopefully) next week should address this.

joxad commented

I just migrate from Active Android to Realm because I saw a lot of people talking about this lib, as far as I want to test Reactive as well. But the lack of AutoIncrement is very hard.
I 'll try the workaround for a time
RealmConfiguration config = new RealmConfiguration.Builder(context).build();
Realm realm = Realm.getInstance(config);
AtomicLong primaryKeyValue = realm.where(Foo.class).max("id").longValue();
realm.close();

// ....

// Create new object
new Foo(primaryKeyValue.incrementAndGet());

But It will be great to have this method done each time we make a new XXXRealmObject .

Until we can implement support for auto-generating primary keys in the bindings we should document the work-arounds. There is 4 cases:

  1. The data contains natural keys, e.g JSON with some kind of identifier -> Just add @PrimaryKey to that field in the Realm Model

  2. We just need a unique identifier in order to identify the object. UUID.randomUUID().toString() should be used either in the constructor if creating un-managed objects or through a setId(UUID) method if using realm.createObject().

  3. Be able to sort by insertion. Just add a createdAt = new Date() field.

  4. Need an unique identifier that is sortable: This is probably the most tricky one as timestamps are easily sortable but not guaranteed to be unique. In that case something like #469 (comment) probably wrapped in nice helper class like PrimaryKeyFactory or similar. We are just about to implement getPrimaryKey for RealmObjectSchema, once that is done we could actually create a nice drop-in helper class for this as we could dynamically determine the primary key field on startup.

Something like:


public class PrimaryKeyFactory {

    private static Map<Class, AtomicLong> keys = new HashMap<Class, AtomicLong>();

    public static void initialize(Realm realm) {
        // 1. Loop through all classes using RealmSchema / RealmObjectSchema / RealmConfiguration.getRealmObjectClassees()
        // 2. Determine the maximum value for each primary key field and save it in the `keys` map.
    }

    // Automitically create next key
    public synchronized long nextKey(Class clazz) {
        return keys.get(clazz).incrementAndGet();
    }
}

this is what I use (based on @cmelchior concept):

import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmObject;
import io.realm.RealmObjectSchema;
import io.realm.RealmSchema;
import static java.lang.String.format;

/**
 * Created by zacheusz on 16/03/16.
 * https://github.com/realm/realm-java/issues/469#issuecomment-196798253
 */
public class PrimaryKeyFactory {

   /** primary key field name */
    private static final String PRIMARY_KEY_FIELD = "id";

    /**
     * Singleton instance.
     */
    private final static PrimaryKeyFactory instance = new PrimaryKeyFactory();

    /**
     * Maximum primary key values.
     */
    private Map<Class<? extends RealmObject>, AtomicLong> keys;

    /**
     * get the singleton instance
     * @return singleton instance
     */
    public static PrimaryKeyFactory getInstance() {
        return instance;
    }

    /**
     * Initialize the factory. Must be called before any primary key is generated
    * - preferably from application class.
    */
    public synchronized void initialize(final Realm realm) {
        if (keys != null) {
            throw new IllegalStateException("already initialized");
        }
        // keys field is used as an initialization flag at the same time
        keys = new HashMap<>();
        final RealmConfiguration configuration = realm.getConfiguration();
        final RealmSchema realmSchema = realm.getSchema();
        //using RealmConfiguration#getRealmObjectClasses because
        // RealmSchema#getAll() returns RealmObjectSchema with simple class names only
        for (final Class<? extends RealmObject> c : configuration.getRealmObjectClasses()) {

            final RealmObjectSchema objectSchema = realmSchema.get(c.getSimpleName());
            Log.i(getClass().getSimpleName(), format("schema for class %s : %s", c.getName(), objectSchema));
            if (objectSchema != null && objectSchema.hasPrimaryKey()) {
                Number keyValue = null;
                try {
                    keyValue = realm.where(c).max(PRIMARY_KEY_FIELD);
                } catch (ArrayIndexOutOfBoundsException ex) {
                    Log.d(getClass().getSimpleName(), format("error while getting number primary key %s " +
                            " for %s",PRIMARY_KEY_FIELD, c.getName()), ex);
                }
                if (keyValue == null) {
                    Log.w(getClass().getSimpleName(), format("can't find number primary key %s " +
                            " for %s.",PRIMARY_KEY_FIELD, c.getName()));
                } else {
                    keys.put(c, new AtomicLong(keyValue.longValue()));
                }
            }
        }
    }

    /**
     *  Automatically create next key for a given class.
     */
    public synchronized long nextKey(final Class<? extends RealmObject> clazz) {
        if (keys == null) {
            throw new IllegalStateException("not initialized yet");
        }
        AtomicLong l = keys.get(clazz);
        if (l == null) {
            Log.i(getClass().getSimpleName(), "There was no primary keys for " + clazz.getName());
            //RealmConfiguration#getRealmObjectClasses() returns only classes with existing instances
            //so we need to store value for the first instance created
            l = new AtomicLong(0);
            keys.put(clazz, l);
        }
        return l.incrementAndGet();
    }
}

Gist URL: https://gist.github.com/zacheusz/66c79df90b22a581d6f0

Wouldn't be better to have a global unique identifier instead of multiple object-dependant unique identifiers??

I mean, if you have multiple Model classes you will need to go though all of them any way, so you could just take the biggest one and be done with it... Not having to store a Map or use a Class as parameter in nextKey.

Something like:

public class PrimaryKeyFactory {

    // Model classes
    private static Class[] model = {ABCD.class, BCDA.class, CDAB.class, DABC.class};
    private static AtomicLong unique;

    public static void initialize(Realm realm) {
        // If unique already calculated, skip
        if (unique != null)
            return;

        // Start the DB in id = 2, id = 1 is in use...???
        unique = new AtomicLong(1);

        // Loop through all classes and determine the global maximum value
        for (Class clazz : model) {
            Number num = realm.where(clazz).max("id");

            // If there are no ids, skip
            if(num != null) {
                AtomicLong maxKey = new AtomicLong(num.longValue());
                if (maxKey.longValue() > unique.longValue())
                    unique = maxKey;
            }
        }
    }

    // Automatically create next key
    public static long nextKey() {
        return unique.incrementAndGet();
    }
}

This way I can call initialize once or as many times as I deem and only do the calculations once.

Is there something I'm missing with this approach?? Is the best solution I have think of.

Ps: I allow calling initialize multiple times as I don't plan to use it onCreate

@cmelchior

AtomicLong primaryKeyValue = realm.where(Foo.class).max("id").longValue();

Hmm, compiler complains that longValue doesn't return an AtomicLong, also this causes a NullPointerException when there are no records.?

@behelit
Yes, that was just bad pseudo code.

Something like the below should be better:

AtomicLong primaryKeyValue; 

public void inititialize() {
  Number currentMax = realm.where(Foo.class).max("id");
  long nextId = 0
  if (currentMax != null) {
    nextId = currentMax.longValue() + 1;
  }
  primaryKeyValue = new AtomicLong(nextId);
}

public long getNextId() {
  return primaryKeyValue.getAndIncrement();
}

@cmelchior @behelit I would advise catching ArrayIndexOutOfBoundsException here - look at my code above.

@rsd-raul with your code:

  1. you will not have continuous PKs for particular table (class)
  2. you have to modify PrimaryKeyFactory each time you add a new model class
  3. catch ArrayIndexOutOfBoundsException from realm.where(clazz).max("id") - will be thrown for empty table

@zacheusz

1- I'm not particularly worried about the continuous PKs as the most I need of them is to be correlative (order by "id") and I'm used to universal PKs.

eg: 1, 82, 23, 45 will be ordered 1, 23, 45 and 82 despite not being continuous, that should do the job for establishing a timeline for a model class.

2- Indeed, I've looked trough your previous answer and I've updated the model declaration to:

// Retrieve the model
Set<Class<? extends RealmObject>> model = realm.getConfiguration().getRealmObjectClasses();

Much much better now, thanks.

3- In my case I'm using realm 0.88.2, and the output for an empty table is null, which I test for in the model loop, lucky enough I don't have to catch the exception.

I was create a implementation of Auto Increment feature for each Model from Realm Database
See: Sample of how to use RealAutoIncrement. Is pretty simple ๐Ÿ˜„

https://gist.github.com/carloseduardosx/a7bd88d7337660cd10a2c5dcc580ebd0

daolq commented

@carloseduardosx
Thank you!
I mix your suggest to resolve this problem:

https://gist.github.com/at-daolq/dfe77581294fd070eba0ad7369ce0c89

I was update the implementation of Auto Increment feature for Realm. Now is much more robust with support for large operations of insert and delete at same time. Everyone can see the new implementation here:

https://gist.github.com/carloseduardosx/6c3a3dfc3a9cb467aa464884f39544c2

See: Sample of how to use RealAutoIncrement. Is pretty simple too ๐Ÿ˜„

I just wanted to share my attempt on solving this Problem, because i don't want to pass a primary Key value all the time. First i created a Database-Class to handle the storage of a RealmObject.

public class RealmDatabase {
Realm realm;

public RealmDatabase() {
    RealmConfiguration realmConfiguration =  new RealmConfiguration.Builder()
            .name("test").schemaVersion(1).build();

    try {
        realm = Realm.getInstance(realmConfiguration);
    } catch (Exception e) {
        Log.e("Realm init failed", e.getMessage());
    }
}

protected void saveObjectIntoDatabase(final RealmObject object) {
    realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm bgRealm) {
            bgRealm.copyToRealm(object);    
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            // onSuccess
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            // onError
        }
    });
}

After creating the database-class i created an Interface to distinguish between Objects with a Primary Key and without.

public interface AutoIncrementable {
    public void setPrimaryKey(int primaryKey);
    public int getNextPrimaryKey(Realm realm);
}

Now you will just need to edit this code of the previous execute method

public void execute(Realm bgRealm) {
                if(object instanceof AutoIncrementable){
                    AutoIncrementable autoIncrementable = (AutoIncrementable) object;
                    autoIncrementable.setPrimaryKey(autoIncrementable.getNextPrimaryKey(bgRealm));
                    bgRealm.copyToRealm((RealmObject)autoIncrementable);
                } else {
                    bgRealm.copyToRealm(object);
                }
}

With this solution the database logic will still be in one class and this class can passed down to every class which needs to write into the database.

public class Person extends RealmObject implements AutoIncrementable{

    @PrimaryKey
    public int id;
    public String name;

    @Override
    public void setPrimaryKey(int primaryKey) {
        this.id = primaryKey;
    }

    @Override
    public int getNextPrimaryKey(Realm realm) {
        return realm.where(Person.class).max("id").intValue() + 1;
    }
}

Please give me some feedback. I am new to Realm and i thought it would be a good idea to handle it like this because i dont need to handle the primary key anymore because i only need to know the primary key when inserting into the database.

@kejith I think max returns null on first attempt, otherwise this works perfectly fine inside a transaction.

You should only do saveObjectIntoDatabase for one element at a time though, if you want to save a list, you should save the list in one transaction.

This is how I'd do it:

 realm.executeTransaction(new Realm.Transaction() { // must be in transaction for this to work
     @Override
     public void execute(Realm realm) {
         Number currentIdNum = realm.where(User.class).max(UserFields.ID);
         int nextId;
         if(currentIdNum == null) {
            nextId = 1;
         } else {
            nextId = currentIdNum.intValue() + 1;
         }
         User user = new User(); // unmanaged
         user.setId(nextId);
         //...
         realm.insertOrUpdate(user); // using insert API
     }
 }

@Zhuinden thanks for your advise. I fixed the problem with the null pointer.

Number currentIdNum = realm.where(User.class).max(UserFields.ID);
I wanted to decouple Model-Classes from the execute-Method so i can reuse the execute for every RealmObject.

Ah. Yeah, I've done that before, http://stackoverflow.com/a/31560557/2413303
If you add a getIdField() method, you can hide it per type.

Thanks for your quick answer. I've watched your code (the code u linked) and i think that

public interface RealmRepository<T extends RealmObject, ID extends Serializable> {
    T findOne(Realm realm, ID id);

    RealmResults<T> findAll(Realm realm);

    T saveOrUpdate(Realm realm, T t);

    RealmList<T> saveOrUpdate(Realm realm, RealmList<T> tList);

    RealmQuery<T> query(Realm realm);

    void delete(Realm realm, ID id);

    void delete(Realm realm, T t);

    void delete(Realm realm, RealmResults<T> realmResults);

    void deleteAll(Realm realm);

    long count(Realm realm);
}

Could just be a static class to take a RealmObject and a Realm as parameters for every Method so it isnt needed to implement the Interface on every object?

Well I had a ___RepositoryImpl class for each class.

So it was like

public class DogRepositoryImpl 
      extends LongRealmRepositoryImpl<Dog> 
      implements DogRepository {
    public DogRepositoryImpl() {
         super(Dog.class);
    } 

    // getId, setId
} 

@kejith i have noticed that you offered a solution to this problem above you gave us a snippet:

public interface AutoIncrementable {
    public void setPrimaryKey(int primaryKey);
    public int getNextPrimaryKey(Realm realm);
}
public void execute(Realm bgRealm) {
                if(object instanceof AutoIncrementable){
                    AutoIncrementable autoIncrementable = (AutoIncrementable) object;
                    autoIncrementable.setPrimaryKey(autoIncrementable.getNextPrimaryKey(bgRealm));
                    bgRealm.copyToRealm((RealmObject)autoIncrementable);
                } else {
                    bgRealm.copyToRealm(object);
                }
}
public class Person extends RealmObject implements AutoIncrementable{

    @PrimaryKey
    public int id;
    public String name;

    @Override
    public void setPrimaryKey(int primaryKey) {
        this.id = primaryKey;
    }

    @Override
    public int getNextPrimaryKey(Realm realm) {
        return realm.where(Person.class).max("id").intValue() + 1;
    }
}

but you highlighted that the problem of null pointer at initial use.
*My advise or view * i think if you use the new Realm version at 2.1,1 in android it allows the use of default value so i think if you would set id default value the null pointer can be resolved i havent tried it yet but i will make use of it though... tell me what you think about the apporoach

@beeender does your last action - adding the issue to 3.2 milestone mean we can expect to get the feature pretty soon?

@iamtodor we will document it in 3.2 ... probably something like #469 (comment) Realm won't support native auto incremental id in a short term.

AtomicLong sounds like a bad idea in a synchronized scenario, also you need to initialize the atomic long on start-up. I've done that and it is a hassle.

None of the strategies in this issue will work in a sync scenario. Only something like UUID.getRandom().toString() would. So this issue is purely about documenting how to generate local keys under the premise that the Realm will never be synced.

As noted in #469 (comment) Asking for auto-incremented ID's is usually the wrong question.

Here is my strategy for auto increment in sync scenario. but did not implement completely yet:
1- Cache RealmObject's ids which is created using UUID.randomUUId in separate Realm Object wether device is online or offline
2- Query cached ids then save the Realmresults in temp list and finally clear cached ids after device get online.
3- Query RealmObjects using their cached ids
4- Assign second id by incrementing 1 to max second id ( In my case second id is barcode number which needs to be autoincremented in sync condition).
In this method I have to wait until cached id's or RealmObjects to get synced completely before quering and deleting them. this is crucial for offline conflict resolution. As a workaround to this
I do the query and deletion after delay to give enough time to realm to get synced. this also will be solved using sync progress feature(requires both download and upload completion callback simultaneously not separately)

By the way, RealmInteger( #4266) type can be used for id auto increment too.

The website docs have been updated with a section on how to work with auto-incrementing IDs. This should hopefully provide insight into why they in most cases are not needed, and how to get similar semantics if needed.

If you feel that anything is missing or is unclear in that section, feel free to create a new issue with the details and I'll update it as fast as possible.

https://realm.io/docs/java/latest/#auto-incrementing-ids

@cmelchior Thanks for the tip, i have gone through the documentation though its almost similar to how i was doing it, but still they (Realm) could have published this a long time ago.

In case of ActiveAndroid ORM you do not need to write id column in model, It will automatic generate auto incremented value and you can simply use it.
I am giving a sample model below-

@Table(name="Items")
public class Item extends Model{
    @Column(name="name")
    public String name;
}

Instead of

@Table(name="Items")
public class Item extends Model{
    @Column(name="Id")
    public long id;
    @Column(name="name")
    public String name;
}

If item is an object of Item then you can simply get id by using

item.getId();
So, the correct model is first one. For reference you can click here.

hello every one my class looks like this i used it to set random id for book id but i want it to be sequential and increasing plz help

public void Random(){
Random rd=new Random();
txtbook1.setText(""+rd.nextInt(1000+1));

}

harry

@c

Timestamps could work, but they only have millisecond precision which means that there is a small chance of two timestamps being created at the same time. Using an AtomicInteger would be safer:

e.g.

// Find max value when opening the DB
RealmConfiguration config = new RealmConfiguration.Builder(context).build();
Realm realm = Realm.getInstance(config);
AtomicLong primaryKeyValue = realm.where(Foo.class).max("id").longValue();
realm.close();

// ....

// Create new object
new Foo(primaryKeyValue.incrementAndGet());

that is risky, if you have two id's 9223372036854775807 and -9223372036854775808
it will fail. rare but its possible. its not fool proof. so to speak.