Skip to content

Document how to set an auto increment id? #469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
djyde opened this issue Oct 12, 2014 · 48 comments
Closed

Document how to set an auto increment id? #469

djyde opened this issue Oct 12, 2014 · 48 comments
Assignees
Labels
Milestone

Comments

@djyde
Copy link

djyde commented Oct 12, 2014

No description provided.

@bmunkholm
Copy link
Contributor

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.

@mpost
Copy link

mpost commented Jan 25, 2015

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

@vuhung3990
Copy link

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

@cmelchior cmelchior added P1 and removed P1 labels Apr 15, 2015
@timanglade timanglade added P2 and removed backlog labels May 6, 2015
@bobbyflowstate
Copy link

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.

@markini
Copy link

markini commented Aug 20, 2015

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?

@hajoxx
Copy link

hajoxx commented Sep 5, 2015

@markini Collision of UUID is very little chance.

@markini
Copy link

markini commented Sep 5, 2015

@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.

@AlokBansal8
Copy link

@bmunkholm any update on it?

@Arthez
Copy link

Arthez commented Feb 11, 2016

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)

@cmelchior
Copy link
Contributor

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());

@Arthez
Copy link

Arthez commented Feb 11, 2016

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.

@cmelchior
Copy link
Contributor

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

@AlokBansal8
Copy link

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

@fawaad
Copy link

fawaad commented Feb 22, 2016

@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?

@MichaelObi
Copy link

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

@joxad
Copy link

joxad commented Mar 9, 2016

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 .

@cmelchior
Copy link
Contributor

cmelchior commented Mar 15, 2016

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 Document how to set an auto increment id? #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();
    }
}

@bmunkholm bmunkholm changed the title how to set an auto increment id? Document how to set an auto increment id? Mar 15, 2016
@kejith
Copy link

kejith commented Oct 21, 2016

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.

@Zhuinden
Copy link
Contributor

@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.

@Zhuinden
Copy link
Contributor

Zhuinden commented Oct 21, 2016

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
     }
 }

@kejith
Copy link

kejith commented Oct 21, 2016

@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.

@Zhuinden
Copy link
Contributor

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.

@kejith
Copy link

kejith commented Oct 21, 2016

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?

@Zhuinden
Copy link
Contributor

Zhuinden commented Oct 21, 2016

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
} 

@Zhuinden Zhuinden mentioned this issue Nov 6, 2016
@kinsleykajiva
Copy link

@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

@iamtodor
Copy link

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

@beeender
Copy link
Contributor

@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.

@Zhuinden
Copy link
Contributor

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.

@cmelchior
Copy link
Contributor

cmelchior commented Mar 29, 2017

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.

@cmelchior cmelchior self-assigned this Apr 7, 2017
@monajafi
Copy link

monajafi commented Apr 10, 2017

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)

@monajafi
Copy link

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

@cmelchior
Copy link
Contributor

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

@kinsleykajiva
Copy link

@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.

@mrasif
Copy link

mrasif commented Dec 14, 2017

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.

@adonyas
Copy link

adonyas commented Feb 19, 2018

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

@kinsleykajiva
Copy link

Hey try this:
https://stackoverflow.com/a/39137326/6334851

@MkazemAkhgary
Copy link

@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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests