# self-manytoone



## Schuriko (12. Jun 2019)

Ich habe eine Personen klasse, wobei created_by und updated_by ein self manytoone ist:


```
@Entity(name="persons")
@Table(indexes = { @Index(name = "IDX_EMAIL_PASSWORD", columnList = "email, password") })
public class Person {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @CreationTimestamp
        private LocalDateTime    created_at;
    
        @ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
        @JoinColumn(name = "created_by")
        private Person    created_by;
/*       
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "createdBy")
        @JoinColumn(name = "created_by")
        private List<Person> created;
*/       
        @Null
        @UpdateTimestamp
        private LocalDateTime    updated_at;
        
        @ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
        @JoinColumn(name = "updated_by")
        private Person    updated_by;
/*       
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "updatedBy")
        @JoinColumn(name = "updated_by")
        private List<Person> updated;
*/       
        @javax.validation.constraints.Pattern(regexp = "(?=.*\\S.*.*).{3,255}")
        private String firstname;   
            
        @javax.validation.constraints.Pattern(regexp = "(?=.*\\S.*.*).{3,255}")
        private String lastname;   
            
        @Column(unique=true)
        @Email(message = "Email should be valid")
        private String email;   
            
        @javax.validation.constraints.Pattern(regexp = "(?=.*\\S.*.*).{6,255}")
        private String password;   
        
        public Person() {
        }
        
        public Person(Person person) {
            super();

            this.firstname = person.firstname;
            this.lastname = person.lastname;
            this.email = person.email;
            this.password = person.password;
        }
        
        public Person(@Pattern(regexp = "(?=.*\\S.*.*).{3,255}") String firstname,
                @Pattern(regexp = "(?=.*\\S.*.*).{3,255}") String lastname,
                @Email(message = "Email should be valid") String email,
                @Pattern(regexp = "(?=.*\\S.*.*).{6,255}") String password) {
            super();
            this.firstname = firstname;
            this.lastname = lastname;
            this.email = email;
            this.password = password;
        }


        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public LocalDateTime getCreatedAt() {
            return created_at;
        }

        public void setCreatedAt(LocalDateTime created_at) {
            this.created_at = created_at;
        }

        public Person getCreatedBy() {
            return created_by;
        }

        public void setCreatedBy(Person created_by) {
            this.created_by = created_by;
        }

        public LocalDateTime getUpdatedAt() {
            return updated_at;
        }

        public void setUpdatedAt(LocalDateTime updated_at) {
            this.updated_at = updated_at;
        }

        public Person getUpdatedBy() {
            return updated_by;
        }

        public void setUpdatedBy(Person updated_by) {
            this.updated_by = updated_by;
        }

        public String getFirstname() {
            return firstname;
        }

        public void setFirstname(String firstname) {
            this.firstname = firstname;
        }

        public String getLastname() {
            return lastname;
        }

        public void setLastname(String lastname) {
            this.lastname = lastname;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            
            result = prime * result + ((email == null) ? 0 : email.hashCode());
            result = prime * result + ((id == null) ? 0 : id.hashCode());
            
            return result;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            
            if (obj == null)
                return false;
            
            if (getClass() != obj.getClass())
                return false;
            
            Person other = (Person) obj;
            
            if (email == null) {
                if (other.email != null)
                    return false;
            } else if (!email.equals(other.email))
                return false;
            
            if (id == null) {
                if (other.id != null)
                    return false;
            } else if (!id.equals(other.id))
                return false;
            
            return true;
        }

        public Person clone() {
            return new Person(this);
        }
        
        @Override
        public String toString() {
            return "Person [id=" + id + ", created_at=" + created_at + ", created_by=" + created_by + ", updated_at="
                    + updated_at + ", updated_by=" + updated_by + ", firstname=" + firstname + ", lastname=" + lastname
                    + ", email=" + email + ", password=" + password + "]";
        }
}
```

Hierbei können "Parent Persons" beliebig viele "Child Persons" erstellen. Wird jetzt "Parent Person" gelöscht, dann sollen alle von dieser Instanz erstellten "Child Persons" auf null gesetzt werden.

Hierzu habe ich eine Test Klasse geschrieben:

```
@Test
        public void testDeleteChildPersonForCreatedBy() {
            // initialize
            String firstnameValue1 = "firstname1";
            String lastnameValue1 = "lastname1";
            String emailValue1 = "email1@email.de";
            String passwordValue1 = "password1";           
            
            Person parentPerson = new Person();
            parentPerson.setFirstname(firstnameValue1);
            parentPerson.setLastname(lastnameValue1);
            parentPerson.setEmail(emailValue1);
            parentPerson.setPassword(passwordValue1);
            
            personRepository.save(parentPerson);
            assertTrue(parentPerson.getId() > 0);
            
            String firstnameValue2 = "firstname2";
            String lastnameValue2 = "lastname2";
            String emailValue2 = "email2@email.de";
            String passwordValue2 = "password2";           
            
            Person childPerson = new Person();
            childPerson.setCreatedBy(parentPerson);
            childPerson.setFirstname(firstnameValue2);
            childPerson.setLastname(lastnameValue2);
            childPerson.setEmail(emailValue2);
            childPerson.setPassword(passwordValue2);

            personRepository.save(childPerson);
            assertTrue(childPerson.getId() > 0);
            
            // test
            personRepository.delete(childPerson);
            
            long counts = personRepository.count();
            assertEquals(1L, counts);
            
            // clean up
            personRepository.delete(parentPerson);
        }
```

Aber irgendwie bekomme ich es nicht hin. Die Test-Funktion stößt immer wieder auf eine AsserationError.
Wie müsste die Entity "Person" korrekt aussehen?
Hierbei möchte ich das, wenn die Parent Person


----------



## mrBrown (12. Jun 2019)

Schuriko hat gesagt.:


> Hierbei möchte ich das, wenn die Parent Person


Da fehlt ein Teil des Satzes?


----------



## Schuriko (12. Jun 2019)

mrBrown hat gesagt.:


> Da fehlt ein Teil des Satzes?


Da hast du recht. War noch ein Überbleibsel von


> Hierbei können "Parent Persons" beliebig viele "Child Persons" erstellen. Wird jetzt "Parent Person" gelöscht, dann sollen alle von dieser Instanz erstellten "Child Persons" auf null gesetzt werden.


Ich meine damit dann soll created_by vom "Child Person" auf Null gesetzt werden.

Deshalb bitte den letzten Satz ignorieren.


----------



## mrBrown (12. Jun 2019)

Okay.
Welche Assertions in deinem Test schlagen denn fehl?


----------



## Schuriko (12. Jun 2019)

mrBrown hat gesagt.:


> Okay.
> Welche Assertions in deinem Test schlagen denn fehl?





> assertEquals(1L, counts);


----------



## mrBrown (12. Jun 2019)

counts ist zwei?
Hast du an den Anfang des Tests mal eine Assumption gesetzt und sichergestellt, dass dort 0 vorhanden sind?


----------



## Schuriko (12. Jun 2019)

mrBrown hat gesagt.:


> counts ist zwei?
> Hast du an den Anfang des Tests mal eine Assumption gesetzt und sichergestellt, dass dort 0 vorhanden sind?


Wieso zwei????? Durch

```
// test
            personRepository.delete(childPerson);
```
wird doch childPerson gelöscht. Also sollte doch

```
assertEquals(1L, counts);
```
korrekt sein. Sprich nur noch 1 Datensatz vorhanden sein.


----------



## mrBrown (12. Jun 2019)

Schuriko hat gesagt.:


> Wieso zwei????? Durch
> 
> ```
> // test
> ...



Das war eine Frage von mir, keine Antwort.

*Du* sagtest, dass die Assertion fehl schlägt, also ist es schon mal nicht 1. Wenn man alles völlig unwahrscheinliche weg lässt, bleiben 0 und 2 übrig - da war 2 kein so unabwegiger Verdacht...
Aber anstatt mich raten zu lassen (und dich dann über meinen Tipp zu beschweren), könntest du auch einfach sagen, was genau der Fehler ist


----------



## Schuriko (12. Jun 2019)

Ich habe die Test - Funktion nochmal abgeändert:

```
@Test
        public void testDeleteChildPersonForCreatedBy() {
            // initialize
            String firstnameValue1 = "firstname1";
            String lastnameValue1 = "lastname1";
            String emailValue1 = "email1@email.de";
            String passwordValue1 = "password1";           
            
            Person parentPerson = new Person();
            parentPerson.setFirstname(firstnameValue1);
            parentPerson.setLastname(lastnameValue1);
            parentPerson.setEmail(emailValue1);
            parentPerson.setPassword(passwordValue1);
            
            personRepository.save(parentPerson);
            assertTrue(parentPerson.getId() > 0);
            
            String firstnameValue2 = "firstname2";
            String lastnameValue2 = "lastname2";
            String emailValue2 = "email2@email.de";
            String passwordValue2 = "password2";           
            
            Person childPerson = new Person();
            childPerson.setCreatedBy(parentPerson);
            childPerson.setFirstname(firstnameValue2);
            childPerson.setLastname(lastnameValue2);
            childPerson.setEmail(emailValue2);
            childPerson.setPassword(passwordValue2);

            assertTrue(childPerson.getId() > 0);
            
            long counts = personRepository.count();
            assertEquals(2L, counts);
            
            // test
            personRepository.delete(childPerson);
            
            counts = personRepository.count();
            assertEquals(1L, counts);
            
            // clean up
            personRepository.delete(parentPerson);
        }
```
sprich den Teil

```
long counts = personRepository.count();
            assertEquals(2L, counts);
```
hinzugefügt. Diese Assert ist ok. Die Test - Funktion mosert immer noch  den obigen, besagten Teil an.


----------



## Schuriko (12. Jun 2019)

mrBrown hat gesagt.:


> Das war eine Frage von mir, keine Antwort.
> 
> *Du* sagtest, dass die Assertion fehl schlägt, also ist es schon mal nicht 1. Wenn man alles völlig unwahrscheinliche weg lässt, bleiben 0 und 2 übrig - da war 2 kein so unabwegiger Verdacht...
> Aber anstatt mich raten zu lassen (und dich dann über meinen Tipp zu beschweren), könntest du auch einfach sagen, was genau der Fehler ist


Du warst etwas schneller, war gerad noch am abändern


----------



## mrBrown (12. Jun 2019)

Schuriko hat gesagt.:


> Du warst etwas schneller, war gerad noch am abändern


Allerdings weiß ich jetzt immer noch nicht, was da jetzt genau falsch ist...


----------



## mihe7 (12. Jun 2019)

Auf den Code blickend würde ich erwarten, dass 0 übrig bleiben.


----------



## mrBrown (12. Jun 2019)

Das ergibt in Verbindung mit dem cascade = REMOVE sogar Sinn...


----------



## mihe7 (12. Jun 2019)

Nur, das was er will, passt a) nicht zum Test und b) geht IMO nicht out of the box automatisch.


----------



## Schuriko (12. Jun 2019)

Also liegt es an der Cascade? Wie müsste ich es denn dann schreiben, um zu erreichen, dass, wie oben beschrieben, wenn die "Parent Person" gelöscht wird, alle Eigenschaften createdBy der "Child Person" auf null gesetzt wird, die von "Parent Person" beinhalten, wenn "Parent Person" gelöscht wird?


----------



## Schuriko (12. Jun 2019)

mihe7 hat gesagt.:


> Nur, das was er will, passt a) nicht zum Test und b) geht IMO nicht out of the box automatisch.


Ups da hast du Recht Mihe7 XD


----------



## mihe7 (12. Jun 2019)

Schuriko hat gesagt.:


> Also liegt es an der Cascade?


Sollte. Du hast eine Referenz auf eine andere Person. Zu dieser Referenz hast Du angegeben, dass ein Remove einer Entity kaskadierend weitergegeben werden soll. D. h. löscht Du eine Person, löschst Du automatisch die referenzierte Person mit (sofern das überhaupt möglich ist, denn es könnte ja andere Personen geben, die diese Person ebenfalls referenzieren).



Schuriko hat gesagt.:


> Wie müsste ich es denn dann schreiben, um zu erreichen, dass, wie oben beschrieben, wenn die "Parent Person" gelöscht wird, alle Eigenschaften createdBy der "Child Person" auf null gesetzt wird, die von "Parent Person" beinhalten, wenn "Parent Person" gelöscht wird?


Ich denke, Du musst die Beziehungen vor dem Entfernen des Parents "manuell" entfernen. D. h. Du fügst in der delete-Methode des Repositories vorher ein DELETE FROM ... (EDIT: Käse, ein UPDATE natürlich) ein.


----------



## Schuriko (12. Jun 2019)

Jau habs hinbekommen. Danke an euch beiden.


----------



## mihe7 (12. Jun 2019)

Wenn Du der Nachwelt nun auch noch Deine Lösung zeigst


----------



## Schuriko (12. Jun 2019)

Hab doch noch ein Problem:


```
@Entity
@Table(name="persons", indexes = { @Index(name = "IDX_EMAIL_PASSWORD", columnList = "email, password") })
public class Person {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @CreationTimestamp
        private LocalDateTime    created_at;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "created_by")
        private Person    created_by;
/*       
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "createdBy")
        @JoinColumn(name = "created_by")
        private List<Person> created;
*/       
        @Null
        @UpdateTimestamp
        private LocalDateTime    updated_at;
        
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "updated_by")
        private Person    updated_by;
/*       
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "updatedBy")
        @JoinColumn(name = "updated_by")
        private List<Person> updated;
*/       
        @javax.validation.constraints.Pattern(regexp = "(?=.*\\S.*.*).{3,255}")
        private String firstname;   
            
        @javax.validation.constraints.Pattern(regexp = "(?=.*\\S.*.*).{3,255}")
        private String lastname;   
            
        @Column(unique=true)
        @Email(message = "Email should be valid")
        private String email;   
            
        @javax.validation.constraints.Pattern(regexp = "(?=.*\\S.*.*).{6,255}")
        private String password;   
        
        public Person() {
        }
        
        public Person(Person person) {
            super();

            this.firstname = person.firstname;
            this.lastname = person.lastname;
            this.email = person.email;
            this.password = person.password;
        }
        
        public Person(@Pattern(regexp = "(?=.*\\S.*.*).{3,255}") String firstname,
                @Pattern(regexp = "(?=.*\\S.*.*).{3,255}") String lastname,
                @Email(message = "Email should be valid") String email,
                @Pattern(regexp = "(?=.*\\S.*.*).{6,255}") String password) {
            super();
            this.firstname = firstname;
            this.lastname = lastname;
            this.email = email;
            this.password = password;
        }


        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public LocalDateTime getCreatedAt() {
            return created_at;
        }

        public void setCreatedAt(LocalDateTime created_at) {
            this.created_at = created_at;
        }

        public Person getCreatedBy() {
            return created_by;
        }

        public void setCreatedBy(Person created_by) {
            this.created_by = created_by;
        }

        public LocalDateTime getUpdatedAt() {
            return updated_at;
        }

        public void setUpdatedAt(LocalDateTime updated_at) {
            this.updated_at = updated_at;
        }

        public Person getUpdatedBy() {
            return updated_by;
        }

        public void setUpdatedBy(Person updated_by) {
            this.updated_by = updated_by;
        }

        public String getFirstname() {
            return firstname;
        }

        public void setFirstname(String firstname) {
            this.firstname = firstname;
        }

        public String getLastname() {
            return lastname;
        }

        public void setLastname(String lastname) {
            this.lastname = lastname;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            
            result = prime * result + ((email == null) ? 0 : email.hashCode());
            result = prime * result + ((id == null) ? 0 : id.hashCode());
            
            return result;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            
            if (obj == null)
                return false;
            
            if (getClass() != obj.getClass())
                return false;
            
            Person other = (Person) obj;
            
            if (email == null) {
                if (other.email != null)
                    return false;
            } else if (!email.equals(other.email))
                return false;
            
            if (id == null) {
                if (other.id != null)
                    return false;
            } else if (!id.equals(other.id))
                return false;
            
            return true;
        }

        public Person clone() {
            return new Person(this);
        }
        
        @Override
        public String toString() {
            return "Person [id=" + id + ", created_at=" + created_at + ", created_by=" + created_by + ", updated_at="
                    + updated_at + ", updated_by=" + updated_by + ", firstname=" + firstname + ", lastname=" + lastname
                    + ", email=" + email + ", password=" + password + "]";
        }
```

mit der Repository

```
public interface PersonRepository extends CrudRepository<Person, Long> {

        Person findByEmail(String email);
        
        @Modifying
        @Query("update from Person p SET p.created_by = null where p.created_by = ?1")
        void setCreatedByToNull(Long id);
        
        @Modifying
        @Query("update from Person p SET p.updated_by = null where p.updated_by = ?1")
        void setUpdatedByToNull(Long id);
        
}
```

und der Test Funktion

```
@Test
        public void testSetCreatedByToNull() {
            long countPersons = personRepository.count();
            assertEquals(0, countPersons);
            
            // initialize
            String firstnameValue1 = "firstname1";
            String lastnameValue1 = "lastname1";
            String emailValue1 = "email1@email.de";
            String passwordValue1 = "password1";           
            
            Person parentPerson = new Person();
            parentPerson.setFirstname(firstnameValue1);
            parentPerson.setLastname(lastnameValue1);
            parentPerson.setEmail(emailValue1);
            parentPerson.setPassword(passwordValue1);
            
            personRepository.save(parentPerson);
            assertTrue(parentPerson.getId() > 0);

            Optional<Person> foundParentPerson = personRepository.findById(parentPerson.getId());
            assertTrue(foundParentPerson.isPresent());
            assertEquals(foundParentPerson.get().getId(), parentPerson.getId());
            
            String firstnameValue2 = "firstname2";
            String lastnameValue2 = "lastname2";
            String emailValue2 = "email2@email.de";
            String passwordValue2 = "password2";           
            
            Person childPerson1 = new Person();
            childPerson1.setCreatedBy(parentPerson);
            childPerson1.setFirstname(firstnameValue2);
            childPerson1.setLastname(lastnameValue2);
            childPerson1.setEmail(emailValue2);
            childPerson1.setPassword(passwordValue2);

            personRepository.save(childPerson1);
            assertTrue(childPerson1.getId() > 0);
            
            Optional<Person> foundChildPerson1 = personRepository.findById(childPerson1.getId());
            assertTrue(foundChildPerson1.isPresent());
            assertEquals(foundChildPerson1.get().getId(), childPerson1.getId());

            String firstnameValue3 = "firstname2";
            String lastnameValue3 = "lastname2";
            String emailValue3 = "email3@email.de";
            String passwordValue3 = "password2";           
            
            Person childPerson2 = new Person();
            childPerson2.setCreatedBy(parentPerson);
            childPerson2.setFirstname(firstnameValue3);
            childPerson2.setLastname(lastnameValue3);
            childPerson2.setEmail(emailValue3);
            childPerson2.setPassword(passwordValue3);

            personRepository.save(childPerson2);
            assertTrue(childPerson2.getId() > 0);
            
            Optional<Person> foundChildPerson2 = personRepository.findById(childPerson2.getId());
            assertTrue(foundChildPerson2.isPresent());
            assertEquals(foundChildPerson2.get().getId(), childPerson2.getId());
            
            long counts = personRepository.count();
            assertEquals(3L, counts);
            
            // test
            personRepository.setCreatedByToNull(parentPerson.getId());
            
            counts = personRepository.count();
            assertEquals(3L, counts);
            
            Optional<Person> foundChildPerson1Try2 = personRepository.findById(childPerson1.getId());
            assertFalse(foundChildPerson1Try2.isPresent());
            assertNull(foundChildPerson1Try2.get().getCreatedBy());
            
            Optional<Person> foundChildPerson2Try2 = personRepository.findById(childPerson2.getId());
            assertFalse(foundChildPerson2Try2.isPresent());
            assertNull(foundChildPerson1Try2.get().getCreatedBy());
            
            // clean up
            personRepository.delete(foundParentPerson.get());
            personRepository.delete(foundChildPerson1.get());
            personRepository.delete(foundChildPerson2.get());
        }
```

führt beim Testen der Funktion zu der Meldung


> org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [1] did not match expected type [com.project.entities.Person (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [1] did not match expected type [com.project.entities.Person (n/a)]
> at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:373)
> at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
> at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
> ...



Wobei ich jetzt nicht so ganz verstehe was hier schief läuft.


----------



## mrBrown (12. Jun 2019)

created_by ist eine Person, kein Long - musst also auch eine Person übergeben.


----------



## Schuriko (19. Jun 2019)

Danke @All


----------

