I got the following error while testing a simple piece of code in JUnit that creates a User
object (an Objectify Entity) and then tries to attach it as a Parent to another Objectify Entity called DownloadTask
:
java.lang.IllegalArgumentException: You cannot create a Key for an object with a null @Id. Object was com.netbase.followerdownloader.model.User@57fcbecc
at com.googlecode.objectify.impl.KeyMetadata.getRawKey(KeyMetadata.java:185)
at com.googlecode.objectify.impl.Keys.rawKeyOf(Keys.java:35)
at com.googlecode.objectify.impl.Keys.keyOf(Keys.java:28)
at com.googlecode.objectify.Key.create(Key.java:62)
at com.googlecode.objectify.Ref.create(Ref.java:31)
at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImpl.create(DownloadTaskRepositoryImpl.java:35)
at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImplTest.setUp(DownloadTaskRepositoryImplTest.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Here's essentially what the code looks like:
public class DownloadTaskRepositoryImplTest {
// maximum eventual consistency (see https://cloud.google.com/appengine/docs/java/tools/localunittesting)
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
.setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
private DownloadTaskRepositoryImpl downloadTaskRepositoryImpl;
@Before
public void setUp() {
helper.setUp();
DatastoreServiceFactory.getDatastoreService();
(new ObjectifyRegistrar()).registerDataModel();
UserRepositoryImpl userRepositoryImpl = new UserRepositoryImpl();
User user = userRepositoryImpl.create("user1");
downloadTaskRepositoryImpl = new DownloadTaskRepositoryImpl(userRepositoryImpl);
downloadTaskRepositoryImpl.create(user, DownloadTask.DownloadType.Followers, "tess__terr"); // THIS LINE GETS THE EXCEPTION
}
Here is the relevant bit from DownloadTaskRepositoryImpl:
@Override
public DownloadTask create(User user, DownloadTask.DownloadType downloadType, String screenname) {
DownloadTask downloadTask = new DownloadTask(downloadType, screenname);
downloadTask.owner = Ref.create(user);
save(downloadTask);
return downloadTask;
}
private void save(DownloadTask downloadTask) {
Closeable closeable = begin();
ofy().save().entity(downloadTask);
closeable.close();
}
Initially I thought that the problem was I hadn't set up the LocalServiceTestHelper
. But I got the error still even after I added it.
Then I thought the problem was that I was using 100% eventual consistency but I set it to 0% eventual consistency and I still had the problem.
Then I thought maybe it was because I hadn't committed the transaction by calling closeable.close();
before I created the dependent object (i.e. DownloadTask
). So I tried wrapping the creation of the object in its own transaction; that didn't work. I tried that PLUS reducing to 0% eventual consistency and that didn't work either.
For testing purposes, I can simply force the User.id
to 1L
and that satisfies Objecitfy, thus working around the problem.
But why wouldn't the @Id of User
be set once I've saved the User
?
** EDIT **
Here is my User class; note that the type of id
is Long
and not long
:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
@Entity
public class User {
@Id public Long id;
public String twitterScreenName;
public String email;
public User () {
}
public User (String twitterScreenName) {
this.twitterScreenName = twitterScreenName;
}
}
This is most likely caused by saving asynchronously instead of synchronously:
Source: https://code.google.com/p/objectify-appengine/wiki/BasicOperations#Saving
It should fix the problem to change the
save()
methodFrom this:
To this:
This fixed the problem for me in a similar situation where I was testing with Junit on a data model involving a linked object model.
What's the type of 'id' in User class? If it's 'Long', keeping id=null should work. From what you explained, looks like it's 'long' (primitive). if that's the case, change it to 'Long' and try again.