Проблема сопоставления для приложения Spring Boot при объединении таблиц для сущностей

#spring #spring-boot #hibernate #spring-data-jpa #spring-data

Вопрос:

У меня в базе данных есть две связанные таблицы, которые я пытаюсь объединить с их соответствующими сущностями, используя отношения @ManyToMany в приложении Spring (фотографии см. Ниже).

таблица базы данных

К сожалению, когда я пытаюсь это сделать, картографы идут по бесконечному циклу. Это, безусловно, должно быть связано с тем, как я присоединяюсь к своим сущностям, но я не могу понять, как это сделать, читая онлайн. Кто-нибудь знает, где я ошибаюсь? Ниже приведен мой код. Обратите внимание, что уровни контроллера, Сервиса и Репозитория опущены для краткости.

Сущности

 @Data
@Entity
@Table(name = "M_Advisor")
public class AdvisorEntity {

        @Id
        @Column(name = "ADVISOR_ID")
        private Long advisorId;

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

        @ManyToMany(fetch = FetchType.EAGER,
                        targetEntity = org.example.entities.contracts.ContractEntity.class)
        @JoinTable(name = "M_CONTRACT", joinColumns = @JoinColumn(name = "ADVISOR_ID"),
                        inverseJoinColumns = @JoinColumn(name = "CONTRACT_ID"))      
        private Collection<ContractEntity> contracts;
}
 
 @Entity
@Data
@Table(name = "M_Contract")
public class ContractEntity {

    @Id
    @Column(name = "CONTRACT_ID")
    private Long contractId;

    @Column(name = "ADVISOR_ID")
    private Long advisorId;

    @ManyToMany(mappedBy = "contracts",
            targetEntity = org.example.entities.advisors.AdvisorEntity.class,
            fetch = FetchType.EAGER)
    private Collection<AdvisorEntity> advisors;
}
 

Картографы

 @Mapper
public interface AdvisorMapper {
    AdvisorDTO advisorEntityToAdvisorDTO(AdvisorEntity advisorEntity);

    AdvisorEntity advisorDTOToAdvisorEntity(AdvisorDTO advisorDto);
}
 
 @Mapper
public interface ContractMapper {
    ContractDTO contractEntityToContractDTO(ContractEntity contractEntity);

    ContractEntity contractDTOToContractEntity(ContractDTO contractDto);

}
 

AdvisorMapperImpl
Note: This is auto generated but I have removed some attributes. Correct line numbers are noted where needed.

 @Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-08-19T11:13:35-0400",
    comments = "version: 1.4.1.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-6.8.jar, environment: Java 11.0.9.1 (AdoptOpenJDK)"
)
@Component
public class AdvisorMapperImpl implements AdvisorMapper {

    @Override
    public AdvisorDTO advisorEntityToAdvisorDTO(AdvisorEntity advisorEntity) {
        if ( advisorEntity == null ) {
            return null;
        }

        AdvisorDTO advisorDTO = new AdvisorDTO();

        advisorDTO.setAdvisorId( advisorEntity.getAdvisorId() );
        advisorDTO.setFirstName( advisorEntity.getFirstName() );

// Line 44
        advisorDTO.setContracts( contractEntityCollectionToContractDTOCollection( advisorEntity.getContracts() ) );

        return advisorDTO;
    }

    @Override
    public AdvisorEntity advisorDTOToAdvisorEntity(AdvisorDTO advisorDto) {
        if ( advisorDto == null ) {
            return null;
        }

        AdvisorEntity advisorEntity = new AdvisorEntity();

        advisorEntity.advisorId( advisorDto.getAdvisorId() );
        advisorEntity.firstName( advisorDto.getFirstName() );
        advisorEntity.contracts( contractDTOCollectionToContractEntityCollection( advisorDto.getContracts() ) );

        return advisorEntity;
    }

    protected Collection<AdvisorDTO> advisorEntityCollectionToAdvisorDTOCollection(Collection<AdvisorEntity> collection) {
        if ( collection == null ) {
            return null;
        }

        Collection<AdvisorDTO> collection1 = new ArrayList<AdvisorDTO>( collection.size() );
        for ( AdvisorEntity advisorEntity : collection ) {

// Line 87
            collection1.add( advisorEntityToAdvisorDTO( advisorEntity ) );
        }

        return collection1;
    }

    protected ContractDTO contractEntityToContractDTO(ContractEntity contractEntity) {
        if ( contractEntity == null ) {
            return null;
        }

        ContractDTO contractDTO = new ContractDTO();

        contractDTO.setContractId( contractEntity.getContractId() );
        contractDTO.setAdvisorId( contractEntity.getAdvisorId() );
        contractDTO.setDetails( contractEntity.getDetails() );

//Line 139
        contractDTO.setAdvisors( advisorEntityCollectionToAdvisorDTOCollection( contractEntity.getAdvisors() ) );

        return contractDTO;
    }

    protected Collection<ContractDTO> contractEntityCollectionToContractDTOCollection(Collection<ContractEntity> collection) {
        if ( collection == null ) {
            return null;
        }

        Collection<ContractDTO> collection1 = new ArrayList<ContractDTO>( collection.size() );
        for ( ContractEntity contractEntity : collection ) {

// Line 151
            collection1.add( contractEntityToContractDTO( contractEntity ) );
        }

        return collection1;
    }

    protected Collection<AdvisorEntity> advisorDTOCollectionToAdvisorEntityCollection(Collection<AdvisorDTO> collection) {
        if ( collection == null ) {
            return null;
        }

        Collection<AdvisorEntity> collection1 = new ArrayList<AdvisorEntity>( collection.size() );
        for ( AdvisorDTO advisorDTO : collection ) {
            collection1.add( advisorDTOToAdvisorEntity( advisorDTO ) );
        }

        return collection1;
    }

    protected ContractEntity contractDTOToContractEntity(ContractDTO contractDTO) {
        if ( contractDTO == null ) {
            return null;
        }

        ContractEntity contractEntity = new ContractEntity();

        contractEntity.contractId( contractDTO.getContractId() );
        contractEntity.advisorId( contractDTO.getAdvisorId() );
        contractEntity.advisors( advisorDTOCollectionToAdvisorEntityCollection( contractDTO.getAdvisors() ) );
        contractEntity.details( contractDTO.getDetails() );


        return contractEntity;
    }

    protected Collection<ContractEntity> contractDTOCollectionToContractEntityCollection(Collection<ContractDTO> collection) {
        if ( collection == null ) {
            return null;
        }

        Collection<ContractEntity> collection1 = new ArrayList<ContractEntity>( collection.size() );
        for ( ContractDTO contractDTO : collection ) {
            collection1.add( contractDTOToContractEntity( contractDTO ) );
        }

        return collection1;
    }
}

 

Stackoverflow Error (no pun intended)

 java.lang.StackOverflowError: null
        at org.example.entities.advisors.AdvisorMapperImpl.contractEntityCollectionToContractDTOCollection(AdvisorMapperImpl.java:149) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.advisorEntityToAdvisorDTO(AdvisorMapperImpl.java:44) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.advisorEntityCollectionToAdvisorDTOCollection(AdvisorMapperImpl.java:87) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.contractEntityToContractDTO(AdvisorMapperImpl.java:139) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.contractEntityCollectionToContractDTOCollection(AdvisorMapperImpl.java:151) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.advisorEntityToAdvisorDTO(AdvisorMapperImpl.java:44) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.advisorEntityCollectionToAdvisorDTOCollection(AdvisorMapperImpl.java:87) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.contractEntityToContractDTO(AdvisorMapperImpl.java:139) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.contractEntityCollectionToContractDTOCollection(AdvisorMapperImpl.java:151) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.advisorEntityToAdvisorDTO(AdvisorMapperImpl.java:44) ~[main/:na]
        at org.example.entities.advisors.AdvisorMapperImpl.advisorEntityCollectionToAdvisorDTOCollection(AdvisorMapperImpl.java:87) ~[main/:na]

etc etc...
 

Update 1.0:
I realized that perhaps a @ManyToMany relationship was not appropriate, and instead should be @OneToOne. Changing this seems to solve the mapping problem, however I now I have a different issue.

Here’s an update on my entities:

 @Entity
@Table(name = "M_Advisor")
public class AdvisorEntity {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "ADVISOR_ID")
        private Long advisorId;

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

        @OneToOne(mappedBy = "advisor", cascade = CascadeType.ALL)
        @PrimaryKeyJoinColumn
        ContractEntity contract;

//getters and setters
}
 
 @Entity
@Table(name = "M_Contract")
public class ContractEntity {

    @Id
    @Column(name = "CONTRACT_ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long contractId;

    @Column(name = "ADVISOR_ID")
    private Long advisorId;

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

    @OneToOne
    @MapsId
    @JoinColumn(name = "ADVISOR_ID", referencedColumnName = "ADVISOR_ID")
    private ContractEntity advisor;

//getters and setters
 

Я попытался следовать этому пошаговому руководству Baeldung, но получаю ошибку. Вот полный след:

 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Repeated column in mapping for entity: org.example.entities.contracts.ContractEntity column: advisor_id (should be mapped with insert="false" update="false")
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154) ~[spring-context-5.3.7.jar:5.3.7]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908) ~[spring-context-5.3.7.jar:5.3.7]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.7.jar:5.3.7]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.0.jar:2.5.0]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.5.0.jar:2.5.0]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:438) ~[spring-boot-2.5.0.jar:2.5.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:337) ~[spring-boot-2.5.0.jar:2.5.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1336) ~[spring-boot-2.5.0.jar:2.5.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1325) ~[spring-boot-2.5.0.jar:2.5.0]
        at org.example.Application.main(Application.java:10) ~[main/:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.5.0.jar:2.5.0]
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Repeated column in mapping for entity: org.example.entities.contracts.ContractEntity column: advisor_id (should be mapped with insert="false" update="false")
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:421) ~[spring-orm-5.3.7.jar:5.3.7]
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-5.3.7.jar:5.3.7]
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845) ~[spring-beans-5.3.7.jar:5.3.7]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782) ~[spring-beans-5.3.7.jar:5.3.7]
        ... 21 common frames omitted
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: org.example.entities.contracts.ContractEntity column: advisor_id (should be mapped with insert="false" update="false")
        at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:862) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:880) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:902) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:634) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.mapping.RootClass.validate(RootClass.java:267) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:354) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:298) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:468) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1259) ~[hibernate-core-5.4.31.Final.jar:5.4.31.Final]
        at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.3.7.jar:5.3.7]
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.3.7.jar:5.3.7]
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-5.3.7.jar:5.3.7]
        ... 25 common frames omitted
 

Ответ №1:

Не используйте @Data аннотации Ломбока для объектов!

Особенно когда у вас двунаправленные отношения.

Генерация equals hashCode и toString методы приведут к StackOverflowError

Вам лучше использовать только @Getter и @Setter с сущностями.

Для проблемы картографирования вам следует рассмотреть возможность использования только однонаправленной связи. Это также разорвет петлю.

Комментарии:

1. Спасибо вам за обратную связь. Однако это не решает проблему.

2. Пожалуйста, покажите код AdvisorMapperImpl. Особенно строки 149, 87, 151

3. Обновил свой пост с помощью AdvisorMapperImpl.

4. Я в том случае, если вы создаете цикл в impl картографа

5. Я понимаю, что AdvisorMapperImpl является причиной этого цикла, как показано в ошибке. Опять же, используя mapstruct. Mapper @Mapper, эта реализация генерируется автоматически. Я использовал это раньше до подключения этих таблиц без каких-либо проблем, поэтому я чувствую, что что-то не так с тем, как я использую javax.persistence.