同一对象上的一对多和一对一

时间:2013-05-16 20:14:17

标签: jpa eclipselink

“用户”实体是“汽车”的一对多双向关系。 “Car”实体是“CarPhoto”实体(它是“Photo”实体的继承)的一对多bidir关系。 “Car”实体也与“CarPhoto”具有一对一的关系。

因此,CarPhotos是汽车的照片,汽车也指出这些照片中的任何一张是否是封面照片(想象一张专辑中正面显示一张照片的专辑)。

设置封面照片时会出现问题。

请查看下面的代码并输入错误消息:

@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 =(
}

其他信息:我正在运行eclipselink 2.4.0,mysql-connector-java 5.1.20和Java 7.

你能看出问题出在哪里吗?请帮助我,我已经挣扎了好几天了=((

最好的问候,卡尔

1 个答案:

答案 0 :(得分:3)

问题是由循环引用引起的,我认为。这张照片有一个参考(外键)到它的汽车,汽车也有一个参考其封面照片。因此EclipseLink似乎首先插入照片,但由于尚未插入汽车,插入失败。

我只是坚持并冲洗用户(以及它的汽车及其照片),然后只将照片设置为汽车的封面照片:

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

这样的循环引用一般来说是个坏主意。在这种情况下,封面照片至少可以为空。如果不是这种情况,则无法强制执行其中一个外键约束,或者其中一个必须延迟。

另一种解决方案,如果没有太多照片,可以使用布尔值将其中一张标记为封面照片。