简体   繁体   English

JPA ManyToMany映射问题(无法将同一实体映射到另一个实体)

[英]JPA ManyToMany mapping issue(unable to map same entity to another)

I'm new to jpa and spring-data , so please forgive my ignorance on the matter. 我是jpaspring-data新手,所以请原谅我对此事的无知。

I have two Entities, and I'm having trouble mapping them to each other with ManyToMany relationship: 我有两个实体,但在通过ManyToMany关系将它们相互映射时遇到了麻烦:

Department: 部:

@Entity
@Table(name = "Department")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Integer         id;

    @Column(name = "name")
    private String          name;

    @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
    @JoinTable(name = "department_week_days", joinColumns = @JoinColumn(name = "department_id"), inverseJoinColumns = @JoinColumn(name = "week_day_id"))
    private List<WeekDay>   weekDays;

    public Department(String name) {
        this.name = name;
        weekDays = new ArrayList<WeekDay>();
    }

    protected Department() {
    }

    public void addWeekDay(WeekDay day) {
        weekDays.add(day);
        day.getDepartments().add(this);
    }

    // omitted setters and getters 

}

and

WeekDay: 工作日:

@Entity
public class WeekDay {

    public static enum WeekDays {

        MO("Monday", 1), TU("Tuesday", 2), WE("Wednesday", 3), TH("Thursday ", 4), FR("Friday", 5), SA("Saturday", 6), SU("Sunday", 7);

        private String  name;
        private int     dayNum;

        private WeekDays(String name, int dayNum) {
            this.name = name;
            this.dayNum = dayNum;
        }

        public int asInt() {
            return dayNum;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Integer             id;

    @Column(name = "name")
    private String              name;

    @Transient
    private int                 numericOrder;

    @Embedded
    private WorkingHours        workingHours;

    @Column(name = "minimumEmployees")
    private Integer             minimumEmployees;

    @Column(name = "maximumEmployees")
    private Integer             maximumEmployees;

    @ManyToMany(mappedBy = "weekDays")
    private List<Department>    departments;

    public WeekDay(WeekDays day, WorkingHours workingHours, Integer minimumEmployees, Integer maximumEmployees) {

        this.name = day.toString();
        this.numericOrder = day.asInt();
        this.workingHours = workingHours;
        this.minimumEmployees = minimumEmployees;
        this.maximumEmployees = maximumEmployees;

        departments = new ArrayList<Department>();

    }

    protected WeekDay() {
    }

    // omitted setters and getters

}

My problem is that I can't add the same WeekDay to two different Department 's for example: 我的问题是我不能将相同的WeekDay添加到两个不同的Department ,例如:

WeekDay day = WeekDayFactory.create(WeekDayFactory.SA, WeekDayFactory.DAY_SHORT, 1, 2);

Department d1 = new Department("d1");
d1.addWeekDay(day);
repository.save(d1);

Department d2 = new Department("d2");
d2.addWeekDay(day);
repository.save(d2);

the above wont let me add day into d2 why? 上面不会让我在d2添加day 为什么?

If I create a new WeekDay with the same values as day it works: 如果我创建一个具有与day相同值的new WeekDay ,则它可以工作:

WeekDay day = WeekDayFactory.create(WeekDayFactory.SA, WeekDayFactory.DAY_SHORT, 1, 2);
WeekDay sameValuesAsDay = WeekDayFactory.create(WeekDayFactory.SA, WeekDayFactory.DAY_SHORT, 1, 2);

Department d1 = new Department("d1");
d1.addWeekDay(day);
repository.save(d1);

Department d2 = new Department("d2");
d2.addWeekDay(sameValuesAsDay);
repository.save(d2);

obviously this creates a duplicate row in the WeekDay table which defeats the purpose of ManyToMany mapping since I can just add the data into the department table it self for each department . 显然,这会在WeekDay表中创建重复行,这WeekDayManyToMany映射的目的,因为我可以将数据添加到它自己为每个departmentdepartment表中。

As I said at the beginning, I have no real idea on how to do this correctly so please excuse my ignorance and point me in the right direction to solve my problem. 正如我在一开始所说的那样,我对如何正确执行此操作没有真正的想法,因此请原谅我的无知,并指出正确的方向来解决我的问题。

EDIT 编辑

stack trace: 堆栈跟踪:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:735) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:716) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:703) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:304) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    at com.kendaya.holidaysplaning.HolidaysPlaningApplication.main(HolidaysPlaningApplication.java:21) [classes/:na]
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.kendaya.holidaysplaning.entities.WeekDay; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.kendaya.holidaysplaning.entities.WeekDay
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:488) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.11.7.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) ~[spring-data-commons-1.13.7.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at com.sun.proxy.$Proxy66.save(Unknown Source) ~[na:na]
    at com.kendaya.holidaysplaning.HolidaysPlaningApplication.lambda$0(HolidaysPlaningApplication.java:39) [classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:732) [spring-boot-1.5.7.RELEASE.jar:1.5.7.RELEASE]
    ... 6 common frames omitted
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.kendaya.holidaysplaning.entities.WeekDay
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:765) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:398) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:431) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:363) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:456) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:278) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:178) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:109) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_91]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_91]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_91]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) ~[spring-orm-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at com.sun.proxy.$Proxy64.persist(Unknown Source) ~[na:na]
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508) ~[spring-data-jpa-1.11.7.RELEASE.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_91]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_91]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_91]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504) ~[spring-data-commons-1.13.7.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489) ~[spring-data-commons-1.13.7.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.7.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56) ~[spring-data-commons-1.13.7.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.11.RELEASE.jar:4.3.11.RELEASE]
    ... 17 common frames omitted

As far as I can see, the mapping is correct and everything should work as expected. 据我所知,映射是正确的,一切都应按预期工作。 But the following error message 但是以下错误信息

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.kendaya.holidaysplaning.entities.WeekDay 引起原因:org.hibernate.PersistentObjectException:传递给持久对象的分离实体:com.kendaya.holidaysplaning.entities.WeekDay

is telling you that your day instance is detached. 告诉您您的day实例已分离。 Probably the first repository.save() call is executed in a separate transaction so that after save() call the persistence context is cleared. 可能在单独的事务中执行第一个repository.save()调用,以便在save()调用后清除持久性上下文。 You have two options to solve the problem: 您有两种选择来解决此问题:

  • Either reread the day record after the first save call before persisting the second department entity, or 在保留第二个部门实体之前重新读取第一个save呼叫之后的day记录,或者

     Department d2 = new Department("d2"); WeekDay day1 = repository.find(day.getId()); //!!! pseodocode d2.addWeekDay(day1); repository.save(d2); 
  • execute both the repository.save() calls in the same transaction, in which case the day instance is still managed. 在同一事务中执行两个repository.save()调用,在这种情况下,仍然管理day实例。

Department d1 = new Department("d1");
d1.addWeekDay(day);
repository.save(d1);

Department d2 = new Department("d2");
d2.addWeekDay(sameValuesAsDay);
repository.save(d2);

replace the code with 将代码替换为

Department d1 = new Department("d1");
d1.addWeekDay(day);
d1 =repository.save(d1);

Department d2 = new Department("d2");
d2.addWeekDay(sameValuesAsDay);
d2 =repository.save(d2);

I think the accepted answer for this questions, Detached entity passed to persist error , will help you to understand what is the root cause. 我认为针对此问题的公认答案是:“ 分离的实体传递给持久性错误” ,它将帮助您了解根本原因。

In short, hibernate's save method accepts only transient object(ie object created with new operator). 简而言之,hibernate的save方法仅接受瞬时对象(即使用new运算符创建的对象)。 When you persist the object using save, that object is no more in the hibernate session (in your case day object) so it throws the error. 当您使用save持久化对象时,该对象在休眠会话中不再存在(在您的情况下为day对象),因此会引发错误。 Try using saveOrUpdate method or as suggested by ujulu, retrieve the day object and then persist. 尝试使用saveOrUpdate方法或按照ujulu的建议,检索day对象,然后将其保留。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM