kungcc
kungcc

Reputation: 1852

One-to-Many and One-To-One on same object

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

Answers (1)

JB Nizet
JB Nizet

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

Related Questions