Hibernate one to one - attempted to assign id from null one-to-one property

0

Cześć, czy ktoś mógłby mi pomóc z mapowaniem one to one w MySQL, proszę?
Korzystałem z tutoriali:
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
https://www.baeldung.com/jpa-one-to-one

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.time.LocalDate;
import java.util.Collection;

@Entity
@Table(name = "users")
public class User implements UserDetails
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String username;
    private byte age;
    private byte sex;
    private String country;
    private String city;
    private String about;
    private boolean isTosAndPrivacyAccepted;
    private int points;
    private int views;
    private int reports;
    private int verifies;
    private String status;
    private String authority;

    @Column(name = "created_date")
    private LocalDate date;

    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
            fetch = FetchType.LAZY, optional = false)
    private Photo photo;

    konstruktor...
gettery i settery...
}
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import javax.persistence.*;

@Entity
@Table(name = "photo")
@JsonIgnoreProperties({
        "fileName",
        "fileType",
        "dataByte"
})
public class Photo
{
    @Id
    private int id;

    private String fileName;
    private String fileType;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private User user;

    private String data;

    konstruktor...
gettery i settery...
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class WebappApplicationTests
{
    @Autowired
    UserRepository repository;

    @Test
    @Order(1)
    public void start() {
        Photo photo = new Photo("photo111");
        User user = new User(
                "user1",
                "1",
                (byte)19,
                (byte)1,
                "coun US",
                "city Los",
                "about",
                101,
                101,
                1,
                101,
                true,
                "ROLE_USER",
                photo
        );
        repository.save(user);
    }
}

Dostaję:

org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [com.webapp.user.Photo.user]; nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.webapp.user.Photo.user]

	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:351)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:253)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:144)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$ExposeRepositoryInvocationInterceptor.invoke(CrudMethodMetadataPostProcessor.java:364)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
	at com.sun.proxy.$Proxy105.save(Unknown Source)
	at com.webapp.WebappApplicationTests.start(WebappApplicationTests.java:45)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.webapp.user.Photo.user]
	at org.hibernate.id.ForeignGenerator.generate(ForeignGenerator.java:90)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:119)
	at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
	at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
	at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:824)
	at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:791)
	at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
	at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490)
	at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415)
	at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216)
	at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149)
	at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:459)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:295)
	at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
	at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:139)
	at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
	at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
	at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
	at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:800)
	at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:785)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:309)
	at com.sun.proxy.$Proxy98.persist(Unknown Source)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:535)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
	at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
	... 42 more

1

Kto ma wygenerować idka dla Photo? :)

0

@Charles_Ray: to jest ta rzecz, której nie rozumiem. Korzystam z implementacji shared primary key i z tego co rozumiem Photo powinno przyjąć Id od User. Wpadłem w pułapkę magii frameworków

1

Udało mi się odczarować magię frameworków, natomiast jest to nauczka, aby omijać takie mapowania szerokim łukiem. Dlaczego Photo nie może mieć własnej sekwencji. Poniżej działające mapowania.

@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @OneToOne(cascade = CascadeType.PERSIST,mappedBy = "user")
    Phone phone;

    public void setPhone(Phone phone) {
        this.phone = phone;
    }
}

oraz

@Entity
public class Phone {

    @Id
    Long userId;

    @MapsId
    @OneToOne
    Person user;

    String number;

    public Phone(Person user, String number) {
        this.user = user;
        this.number = number;
    }
}

przykład użycia:

Person p = new Person();
Phone phone = new Phone(p,"1234");
p.setPhone(phone);
em.persist(p);

Wyplute SQL-ki:

Hibernate: create table Person (id bigint generated by default as identity, primary key (id))
Hibernate: create table Phone (number varchar(255), user_id bigint not null, primary key (user_id))
Hibernate: alter table Phone add constraint FKqo4acnm5pi5n1itt7jmnkl934 foreign key (user_id) references Person
Hibernate: insert into Person (id) values (null)
Hibernate: insert into Phone (number, user_id) values (?, ?)
0

Dzięki za objaśnienie. Wychodzi na to jeden z moich poprzednich prawdopodobnie zadziałał, ale natknąłem się na JsonMappingException: Infinite recursion (StackOverflowError) kiedy próbowałem odczytać listę userów z bazy i myślałem, że coś jest nie tak z mappingiem. Znowu dostaję ten exception, ale teraz rozumiem, że jest to związane z Jacksonem.

1

Cykl referencji między obiektami i Jackson wpada w nieskończona pętlę

0

@Charles_Ray: to chyba właśnie się dzieje kiedy wykonamy:|

Person p = new Person();
Phone phone = new Phone(p,"1234");
p.setPhone(phone);

https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/
Tutaj na przykład zapisywanie one to one wygląda tak (nie ma tego momentu kiedy przekazujemy referencje Person p do Phone phone):

Post post = new Post();
post.setTitle("High-Performance Java Persistence");
 
PostDetails details = new PostDetails();
details.setCreatedBy("Vlad Mihalcea");
 
post.setDetails(details);
 
entityManager.persist(post);
1

Zgadzam się. Tak jak pisałem w kilku innych wątkach - relacje dwustronne to zło. Już nie mówię nawet o tym, że pchanie encji po kablu jest jeszcze większym złem - serializowany powinien być DTO, a nie encja.

1

@Charles_Ray - zauważ jak jest zamplementowany ten setter na tym blogu. Tutaj ustawiają się pola po obu stronach relacji jednym setterem. Ten setter też ma trochę wspólnego z tą magią

public void setDetails(PostDetails details) {
        if (details == null) {
            if (this.details != null) {
                this.details.setPost(null);
            }
        }
        else {
            details.setPost(this);
        }
        this.details = details;
    }
1

Jeśli masz dwustronna relację, to musisz ręcznie ustawić obie strony. Dlatego czasem robi się takie settery, które robią trochę więcej niż setter :)

0

Dziękuję wszystkim za pomoc.
Dla potomnych :) takie mapowanie unidirectional załatwiło sprawę:

@Entity
@Table(name = "users")
public class User implements UserDetails
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "photo_id")
    private Photo photo;
}
@Entity
@Table(name = "photo")
public class Photo
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
}
@Test
@Order(1)
public void start() {
   Photo photo = new Photo("photo");
   User user = new User(
          photo
    );
    repository.save(user);
}

0 użytkowników online, w tym zalogowanych: 0, gości: 0