Reputation: 1852
The "User" entity is a one-to-many bidir relationship of "Car". The "Car" entity is a one-to-many bidir relationship of the "CarPhoto" entity (which is a inheritance of "Photo" entity). The "Car" entity also has a one-to-one relationship to "CarPhoto".
Hence, the CarPhotos are photos to a car and the car also points out if any of these photos shall be a cover photo (think like an album where one picture is shown at the front on the album).
The problem occur when a cover photo is set.
Please look at my code below and error message:
@Entity
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "USERS_SEQ")
@TableGenerator(name = "USERS_SEQ", table = "SEQUENCE", pkColumnName = "SEQ_NAME", pkColumnValue = "USERS_SEQ", valueColumnName = "SEQ_COUNT", allocationSize = 1)
@Column(nullable = false)
private long id;
@OneToMany(mappedBy = "user", cascade = { CascadeType.ALL }, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Car> cars;
//...
}
@Entity
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "CARS_SEQ")
@TableGenerator(name = "CARS_SEQ", table = "SEQUENCE", pkColumnName = "SEQ_NAME", pkColumnValue = "CARS_SEQ", valueColumnName = "SEQ_COUNT", allocationSize = 1)
@Column(nullable = false)
private long id;
@OneToMany(mappedBy = "car", cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<CarPhoto> photos;
// coverPhoto shall be NULL or a object in the Set<CarPhoto> photos
@OneToOne
@JoinColumn(name = "COVER_PHOTO", referencedColumnName = "ID")
private CarPhoto coverPhoto;
//...
}
@Entity
@DiscriminatorValue("C")
public class CarPhoto extends Photo {
@ManyToOne(cascade = { CascadeType.DETACH })
@JoinColumn(name = "CARID", nullable = false)
@NotNull
private Car car;
//...
}
@Entity
@Inheritance
@DiscriminatorColumn(name = "DESCRIMINATOR")
public abstract class Photo {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "PHOTOS_SEQ")
@TableGenerator(name = "PHOTOS_SEQ", table = "SEQUENCE", pkColumnName = "SEQ_NAME", pkColumnValue = "PHOTOS_SEQ", valueColumnName = "SEQ_COUNT", allocationSize = 50)
@Column(nullable = false)
private long id;
//...
}
@Test
public void testCarAddCarPhotos() throws Exception {
User user = dg.getTestUser(); // Get a user object
Car car = dg.getTestCar(); // Get a car object
CarPhoto photo = dg.getTestCarPhoto(); // Get a car photo object
user.addToCars(car); // Add car to user cars list
car.setUser(user); // ^^^ To keep bi-directional relationship^^^
photo.setCar(car); // Add car as owner to this photo
car.addToPhotos(photo); // ^^^ To keep bi-directional relationship^^^
em.persist(user);
em.flush();
em.clear();
// This tests works perfectly, the user has a car, and the car has a photo
}
@Test
public void testCarAddCarPhotosAddCoverPhoto() throws Exception {
User user = dg.getTestUser(); // Get a user object
Car car = dg.getTestCar(); // Get a car object
CarPhoto photo = dg.getTestCarPhoto(); // Get a car photo object
user.addToCars(car); // Add car to user cars list
car.setUser(user); // ^^^ To keep bi-directional relationship^^^
photo.setCar(car); // Add car as owner to this photo
car.addToPhotos(photo); // ^^^ To keep bi-directional relationship^^^
// NOW TEST TO A THE COVERPHOTO
car.setCoverPhoto(photo);
em.persist(user);
em.flush();
// ERROR:
// javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.DatabaseException
// Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`vehicledb_test`.`PHO
// TOS`, CONSTRAINT `FK_PHOTOS_CARID` FOREIGN KEY (`CARID`) REFERENCES `CARS` (`ID`))
// Error Code: 1452
// Call: INSERT INTO PHOTOS (ID, DESCRIPTION, FILENAME, TITLE, UPLOADTIME, CARID, DESCRIMINATOR) VALUES (?, ?, ?, ?, ?, ?, ?)
// bind => [1, jequejnnzkxhzahaimg, rabbit, computer, 2013-05-16 21:59:42.524, 3, C]
// Query: WriteObjectQuery(Photo [id=1, description=jequejnnzkxhzahaimg, fileName=rabbit, title=computer, uploadTime=Thu May 16 21:59:42 CEST 2013])
// at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:804)
// at se.while_se.service.jpa.UserRepositoryTest.testModifyUserRemoveCarWithPhoto(UserRepositoryTest.java:251)
// 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:601)
// at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
// at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
// at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
// at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
// at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
// at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
// at org.junit.rules.TestWatchman$1.evaluate(TestWatchman.java:48)
// at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
// at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
// at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
// at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
// at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
// at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
// at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
// at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
// at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
// at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
// at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
// at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35)
// at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:146)
// at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97)
// 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:601)
// at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
// at $Proxy0.invoke(Unknown Source)
// at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:145)
// at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:87)
// at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
// Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.DatabaseException
em.clear();
// This test does not works =(
}
Additional information: I am running eclipselink 2.4.0, mysql-connector-java 5.1.20 and Java 7.
Can you see where the problem is? Please help me, I have struggled with this for days now =(((
Best regards, Carl
Upvotes: 0
Views: 2210
Reputation: 691893
The problem is caused by the circular reference, I think. The photo has a reference (foreign key) to its car, and the car also has a reference to its cover photo. So EclipseLink seems to insert the photo first, but since the car has not been inserted yet, the insert fails.
I would simply persist and flush the user (and thus its cars and its photos), and then only, set the photo as the cover photo of the car:
user.addToCars(car); // Add car to user cars list
car.setUser(user); // ^^^ To keep bi-directional relationship^^^
photo.setCar(car); // Add car as owner to this photo
car.addToPhotos(photo); // ^^^ To keep bi-directional relationship^^^
em.persist(user);
em.flush();
// NOW TEST TO A THE COVERPHOTO
car.setCoverPhoto(photo);
Such circular references are a bad idea in general. In this case, the cover photo can at least be null. If it was not the case, you couldn't enforce one of the foreign key constraints, or one of them would have to be deferred.
An alternative solution, if there aren't too many photos, would be to flag one of them as the cover photo using a boolean.
Upvotes: 3