I had the following error while testing a simple piece of code in JUnit that creates a User object (Objectify Entity) and then tries to bind it as a parent to another Objectify 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 essentially the code looks:
public class DownloadTaskRepositoryImplTest {
Here is the corresponding 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 the problem was that I did not configure LocalServiceTestHelper . But I got an error, even after I added it.
Then I thought the problem was that I used 100% possible consistency, but I set it to 0% possible sequence, and I still had the problem.
Then I thought that this happened because I did not complete the transaction by calling closeable.close(); before creating the dependent object (i.e. DownloadTask ). So I tried to combine the creation of an object into my own transaction; it did not work. I tried PLUS to decrease to 0% in the end, and that didn't work either.
For testing purposes, I can simply force User.id to 1L and satisfy Objecitfy, thereby circumventing the problem.
But why not install @Id from User as soon as I save User ?
** EDIT **
Here is my User class; note that id is Long , 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; } }