Hallo, wie mir freundlicherweise schon im anderen Thread der Hinweis gegeben wurde, das "vor" einer Datenbank eine REST-API muss.
Ich habe mich schon ein bisschen schlau gemacht (die REST-API greift auf die Datenbank zu und holt sich so die Daten), aber bevor ich in falsche Richtungen mich orientiere, habe ich mal 2 Fragen.
Kann man eine REST-API selbst bauen (Java, Python)? Und wenn ja gibt es (vielleicht deutsche) Anleitungen dazu?
Ziel ist so etwas auf einen eigenen Linux-Server bzw einen Linux-Server VPS-Server zu installieren.
Ich kenne vor allem Englische Anleitungen. Man kann sich da z.B. Spring Boot oder Quarkus ansehen. (Und da auch mal nach Deutschen Anleitungen suchen. Sollten aber nicht zu alt sein, da sich beide Frameworks zügig weiter entwickeln.)
und habe darüber hinaus noch weitere Video-Tutorial angeschaut.
Jetzt bin ich soweit, das ich zu erste Test's komme, doch leider gibt er ein Fehler aus.
Code:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Das heißt ich muss eine Datenbanktabelle anlegen.
Aber zum generellen testen möchte ich es ohne Datenbank machen. Geht das überhaupt?
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
Ich glaube zu wissen diesen bei spring boot initializr mit angegeben zu haben.
Wie kann man so etwas prüfen?
Edit: so sieht meine pom.xml aus:
XML:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.0</version><relativePath/><!-- lookup parent from repository --></parent><groupId>de.resttest</groupId><artifactId>matze</artifactId><version>0.0.1-SNAPSHOT</version><name>matze</name><description>das ist ein test fuer rest</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
Nach dem Ausführen wird in der Zeile 5 (<parent>) eine Fehlermeldung angezeigt mit unzähligen Einträgen.
Hast du eine Abhängigkeit zu MySQL im Ptojekt? In Maven wäre es etwas wie:
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.31</version>
</dependency>
Jetzt habe ich viele Anleitungen sorgfältig durchgearbeitet,z.B. auch diese, Das mit den Ausgaben von allen Daten und eins hinzufügen klappt schon mal ohne Probleme.
Jetzt wollte ich in der Klasse Controller eine Methode hinzufügen, welche ein bestimmter Name oder nur eine bestimmte id ausgibt.
Leider klappt das noch nicht so wie ich mir das vorstelle.
Ich habe eine Klasse "User" erstellt, was name und ort beinhaltet, inklusive den Methoden set- und getter.
Dann eine Klasse UserRebo, die als interface von CrudRepository dient (was kann man noch hier eintragen?).
Und eine Klasse ControllerUser, welche beim Aufruf über eine url angesteuert wird und die "GetMapping" - Methoden ausgewertet werden.
Nun möchte ich alles erweitern, so das man entweder nach einer id oder einen Namen sucht und ausgeben lässt.
Aufrufen möchte ich die letzte Methode im controller "getID"
Postman gibt mir einen Fehler wenn ich "localhost:8082/api/user/2" eingebe.
Die id 2 gibt es wenn ich "all" ausgebe sehe ich es.
Hier mal die Klassen damit ihr wisst, was ich bis jetzt gemacht habe.
@Controller@RequestMapping("api")//der Pfad nach der url:port also url:port/api -- name kann beliebeig seinpublicclassControllerUser{// Dies bedeutet, die Bean namens userRepository zu erhalten// Wird von Spring automatisch generiert, wir werden es verwenden, um die Daten zu verarbeiten @AutowiredprivateUserRepo userRepository;@PostMapping("/add")//nur an post -Anfragen also url:port/api/add// @ResponseBody bedeutet, dass der zurückgegebene String die Antwort ist, kein Ansichtsname// @RequestParam bedeutet, dass es sich um einen Parameter aus der GET- oder POST-Anfrage handeltpublic@ResponseBodyString addNewUser (@RequestParamString name,@RequestParamString ort){User n =newUser();
n.setName(name);
n.setOrt(ort);
userRepository.save(n);return"Saved";}@GetMapping("/all")//nur anzeigen -Anfragen also url:port/api/allpublic@ResponseBodyIterable<User>getAllUsers(){// Dies gibt ein JSON oder XML mit den Benutzern zurückreturn userRepository.findAll();}//die besagt Methode funktionier so leider nicht,@GetMapping("user/{id}")publicOptional<User>getID(@PathVariableLong id){return userRepository.findById(id);}}
Wie war das mit meinem Auto? Mein Auto gibt mir einen Fehler. Kannst Du mir sagen, was kaputt ist?
Was ist denn so schwer daran, einfach einmal genau zu sagen, was Du zurück bekommst? Die Response Codes haben alle eine Bedeutung und die kann man durchaus mit angeben.
Ansonsten fällt mir auf, dass Du in den Mappings nicht einheitlich mit den / arbeitest. Ich habe eigentlich immer, dass ich mit / starte. Sprich: @GetMapping("/user/{id}")
Ob das weggelassen werden kann, kann ich im Augenblick nicht sagen, da ich mir das im Detail so nicht angesehen habe. (Für mich startet man mit einem / und gut ist es - damit bin ich bisher sehr gut gefahren. Und ich werde es jetzt nicht ausprobieren, ob es auch anders geht.)
Und bitte: Fang nicht mit Abkürzungen an! Das ist ein UserRepository und nicht UserRepo oder so! Und gibt Variablen vernünftige Bezeichner! n ist kein vernünftiger Bezeichner. Zumal n für einen User? Das kann der createdUser sein - dann ist klar: In der Variable ist der User drin, der erzeugt wurde!
So ich habe jetzt nochmal alles "durchstudiert", jetzt habe ich die Methoden "put", "delete" und "get" erstellt bzw. überarbeitet.
Jetzt funktioniert es wie es soll.
Alle Methoden funktionieren. Die Frage ist ob es vom Code sauber ist.
Hier erstmal der Controller:
Java:
@Controller@RequestMapping("/api")//der Pfad nach der url:port also url:port/api -- name kann beliebeig seinpublicclassControllerUser{@AutowiredprivateUserRepo userRepository;@PostMapping("/add")public@ResponseBodyString addNewUser (@RequestParamString name,@RequestParamString ort){User user =newUser();
user.setName(name);
user.setOrt(ort);
userRepository.save(user);return"Saved";}@GetMapping("/all")public@ResponseBodyIterable<User> getAllUsers (){return userRepository.findAll();}//hier wird explizit die ID als Parameter angegeben@GetMapping("/user")public@ResponseBodyOptional<User>nameSearch(@RequestParamLong id){return userRepository.findById(id);}//die ID wird an die URL gehängt@GetMapping("/user/{id}")public@ResponseBodyOptional<User>idSearch(@PathVariableLong id){return userRepository.findById(id);}//ein Eintrag löschen mit dem Parameter ID@DeleteMapping("/del")public@ResponseBodyStringloeschen(@RequestParamLong id){
userRepository.deleteById(id);return"gelöscht";}//einen Eintrag verändern@PutMapping("/put")public@ResponseBodyStringput(@RequestParamLong id,@RequestParamString ort){User user =newUser();//gesetzt wird die übergebene ID und den Ort
user.setID(id);
user.setOrt(ort);//hier wird der Name zurückgegebenString name = userRepository.findById(id).get().getName();//den Namen setzen
user.setName(name);//und speichern
userRepository.save(user);return"erfolgreich";}//hier wird explizit nach dem Namen gesuch und wiedergegeben@GetMapping("/einzelausgabe")public@ResponseBodyList<User>einzel(@RequestParamString name){return userRepository.findByName(name);}}
Was mir noch für Fragen aufwerfen ist, das es viele Tutorial's gibt und alle unterschiedliche Controller mit verschiedenen Methoden haben wie z.B. ResponseEntity.
Mir stellt sich die Frage was braucht man wann und was wirklich?
Oder kann man mit meinen Controller "leben" der alles beinhaltet?
Sauber ist es eigentlich nicht. User ist eine Entity, es ist ein Antipattern eine Entity aus einem Rest-Endpunkt zurückzugeben. Man würde ein UserDTO erstellen und dieses zurückgeben.
Der Hintergrund ist: Stell dir vor jemand erweitert User und schreibt da ein Feld rein, welches schutzwürdig ist, z.B.: sexuelle Orientierung. Jetzt gibt dein Rest-Endpunkt automatische diese Daten weiter. Das sollte nicht passieren, auch wenn die Daten nicht direkt in deiner Programmoberfläche oder Webseite sichtbar sind, können sie trotzdem ausgelesen werden. Daher erstellt man ein DTO-Objekt, welches nur die Daten enthält, welche der Endpunkt zurückgeben soll. Auch wenn die Daten im Moment identisch sind. Programme werden sich aber mit der Zeit verändern.
Eine vernünftige IDE(-Einstellung) würde dich auch automatisch auf das Problem hinweisen. Es empfiehlt sich immer die statische Codeanalyse einzuschalten/installieren, z.B.: Sonarlint finde ich ganz gut für Einsteiger.
//einen Eintrag verändern@PutMapping("/put")public@ResponseBodyStringput(@RequestParamLong id,@RequestParamString ort){User user =newUser();//gesetzt wird die übergebene ID und den Ort
user.setID(id);
user.setOrt(ort);//hier wird der Name zurückgegebenString name = userRepository.findById(id).get().getName();//den Namen setzen
user.setName(name);//und speichern
userRepository.save(user);return"erfolgreich";}
Ich würde es eher so schreiben:
Java:
//einen Eintrag verändern@PutMapping("/put")@Transactionalpublic@ResponseBodyStringput(@RequestParamLong id,@RequestParamString ort){User user = userRepository.findById(id).orElseThrow(...);
user.setOrt(ort);//und speichern
userRepository.save(user);// das sollte gar nicht nötig sein, weglassenreturn"erfolgreich";}
Statt "erfolgreich" geben wir eigentlich immer das vollständige Object zurück. Es können sich zwischenzeitlich noch weiter Felder geändert haben. So kann man sein Programm aktualisieren.
Sauber ist es eigentlich nicht. User ist eine Entity, es ist ein Antipattern eine Entity aus einem Rest-Endpunkt zurückzugeben. Man würde ein UserDTO erstellen und dieses zurückgeben.
Der Hintergrund ist: Stell dir vor jemand erweitert User und schreibt da ein Feld rein, welches schutzwürdig ist, z.B.: sexuelle Orientierung. Jetzt gibt dein Rest-Endpunkt automatische diese Daten weiter. Das sollte nicht passieren, auch wenn die Daten nicht direkt in deiner Programmoberfläche oder Webseite sichtbar sind, können sie trotzdem ausgelesen werden. Daher erstellt man ein DTO-Objekt, welches nur die Daten enthält, welche der Endpunkt zurückgeben soll. Auch wenn die Daten im Moment identisch sind. Programme werden sich aber mit der Zeit verändern.
Eine vernünftige IDE(-Einstellung) würde dich auch automatisch auf das Problem hinweisen. Es empfiehlt sich immer die statische Codeanalyse einzuschalten/installieren, z.B.: Sonarlint finde ich ganz gut für Einsteiger.
Was heißt das jetzt genau? Wie sollte ich vorgehen? Was ist ein DTO-Objekt? Vielleicht kannst du dazu ein Schema verlinken was aufzeigt wie man es richtig macht.
Ich habe mir mal die Seite angeschaut, versucht es zu verstehen. Aber leider kommen mehr Fragen auf als Antworten.
Ich weiß nicht mal wie ich den Code verstehen soll, wo soll er hin, wie binde ich mein vorhandenes Rest-Projekt ein?
Was macht die Klasse
Java:
PostDto
u.s.w. Leider habe ich im Netz auch nicht so viel gesehen, was mich weiter bringt.
Ich habe mir mal die Seite angeschaut, versucht es zu verstehen. Aber leider kommen mehr Fragen auf als Antworten.
Ich weiß nicht mal wie ich den Code verstehen soll, wo soll er hin, wie binde ich mein vorhandenes Rest-Projekt ein?
Was macht die Klasse
Java:
PostDto
u.s.w. Leider habe ich im Netz auch nicht so viel gesehen, was mich weiter bringt.
Also das Beispiel hat eine Entity Post und man will nun nicht dieses Entity raus geben sondern nur ein modifiziertes PostDTO.
Daher braucht man nun eunen Mapper, der aus einer Post Instanz eine PostDTO Instanz macht.
Das ist alles. Und das kannst Du 1:1 umwandeln. Dazu "Post" durch den Namen deiner Entity ersetzen.
Alles gut und schön, aber ich würde gerne wissen wie das ganze im Prinzip arbeitet, also auch die einzelnen Methoden etc.
Ich wünschte mir in den Anleitungen steht über jede Method und Klasse eine kleine Beschreibung. Es wird leider immer alles im Groben beschrieben.
aber ich würde gerne wissen wie das ganze im Prinzip arbeitet, also auch die einzelnen Methoden etc.
Ich wünschte mir in den Anleitungen steht über jede Method und Klasse eine kleine Beschreibung.
Im Artikel findest Du einen Link auf http://modelmapper.org/getting-started/ - dort steht beschrieben, wie der ModelMapper funktioniert inkl. Benutzerhandbuch.
Ansonsten besteht ja kein Zwang, diese Bibliothek - oder überhaupt eine - für das Mapping zu verwenden. Wir erzeugen z. T. direkt JsonObject-Instanzen, die wir zurückgeben oder haben DTO-Klassen, die wir manuell füllen bzw. als Adapter fungieren.
Ansonsten besteht ja kein Zwang, diese Bibliothek - oder überhaupt eine - für das Mapping zu verwenden. Wir erzeugen z. T. direkt JsonObject-Instanzen, die wir zurückgeben oder haben DTO-Klassen, die wir manuell füllen bzw. als Adapter fungieren.
Genau, das meine ich ja, ich habe aktuell kein grundsätzlichen Überblick wie so etwas generell funktionieren muss.
Aber ich werde mich nach und nach mal durcharbeiten, inkl. mit den hilfreichen Tipps hier im Forum.
Wenn ich mal noch eine Frage melde ich mich zu gegebener Zeit nochmal.
Ganz grundsätzlich ist es einfach so, dass über die Leitung keine Objekte sondern eine Serialisierung der Objekte bzw. der Objektdaten gehen können. Damit Sender und Empfänger Daten austauschen können, müssen sie die "gleiche Sprache" sprechen. In Webservices wird oft JSON verwendet, weil es a) weit weniger geschwätzig als XML ist und b) von JavaScript im Browser sowieso verstanden wird.
Jetzt hast Du also ein Person-Objekt und das muss irgendwie, sagen wir mal in JSON, serialisiert werden. SpringBoot/Jakarta EE können das automatisch: man gibt als Rückgabetyp z. B. Person an, liefert ein Person-Objekt zurück und das Framework erzeugt daraus einen JSON-Text.
Statt nun direkt ein Person-Objekt zurückzuliefern, kann man auch eine "Sicht" auf eine Person (oder auf Person und Objekte weiterer Klassen) zurückliefern, dazu kann man ein DTO verwenden (s. dazu Kommentar #21 von @Oneixee5).
Ein DTO ist einfach ein Objekt, das eben gerade die Daten enthält, die über die Leitung gehen sollen. Erstellt werden kann dieses wie jedes andere Objekt auch: man schreibt eine Klasse, erzeugt eine Instanz, setzt die Properties.
Nehmen wir mal an, wir hätten eine Person-Klasse, die Name, Vorname und Geburtsdatum darstellt. Aus Gründen des Datenschutzes sollen aber nur Name und Vorname über den REST-Endpoint geliefert werden.
Java:
classPersonDTO{publicString name;publicString vorname;publicPersonDTO(Perosn perosn){
name = person.getName();
vorname = person.getVorname();}}
Gibst Du jetzt in Deiner REST-Methode statt Person PersonDTO zurück, wird niemals das Geburtsdatum über die Leitung gehen. Die Person-Klasse kann später auch weitere Attribute erhalten, die die Außenwelt nichts angehen: das DTO stellt sicher, dass diese nicht übermittelt wreden.
Das Prinzip ist, soweit ich es verstanden habe, ich habe wie in mein Post 17 und 20 eine normale User-Klasse, eine User-Repository-Klasse, eine Controller-Klasse.
Hinzu füge ich in folgendes: in der UserdtoApplication Klasse
Das Spring-Framework muss für die Injection einer ModelMapper-Instanz via @Autowired wissen, wie es eine Instanz von ModelMapper erzeugen kann. Das wird hier mit einer Factory-Method erledigt, die mit @Bean annotiert werden muss.
Die Annotation dient m. W. schlicht dazu, eine Spring-Komponente im Code explizit als Service (i. S. des Domain-Driven-Design) und nicht einfach als gewöhnliche Komponente darzustellen.
Ich bin gerade dabei vom UserDTO zum User einen Eintrag zu verändern.
Dabei legte ich die Methode "convertToEntity" an und die Methode die ein Name verändert.
Im Beispiel wurde gezeigt, dass man auch "UserDTO" mit als Parameter übergeben soll.
Jetzt stellt sich mir die Frage wie ich das zum testen in Postman eingeben soll und ob das so überhaupt funktioniert, denn ich möchte ja den Namen von UserDTO (der nur von außerhalb sichtbar ist) ändern, das User den Namen ändert.
Vielleicht kann mir jemand einen kleinen Tipp geben, ich habe vom vielen lesen leider den durchblick verloren.
Java:
//einen Eintrag verändern@PutMapping("/put")@ResponseBodypublicUserDTOput(@RequestParamLong id,@RequestBodyUserDTO userDto,@RequestParamString name)throwsParseException{User user =convertToEntity(userDto);
user.setName(name);
userRepository.save(user);returnconvertToDto(userRepository.findById(id).orElseThrow(()->newUserNotFoundException("Unavailable")));}//von UserTDO zum User, hier uebergebe ich den modelMapper den UserDTO und soll ihn in User "umwandeln"privateUserconvertToEntity(UserDTO userDTO)throwsParseException{User user = modelMapper.map(userDTO,User.class);return user;}
@RestController@RequestMapping("/api")publicclassControllerUser{@AutowiredprivateUserRepo userRepository;@AutowiredprivateUserConverter converter;//Alles ausgeben@GetMapping("/all")publicList<UserDTO>findAll(){List<User> findAll = userRepository.findAll();return converter.entityToDTO(findAll);}//Ausgabe Objekt mit bestimmter ID@GetMapping("/user/{id}")publicUserDTOfindById(@PathVariableLong id){User orElse = userRepository.findById(id).orElse(null);return converter.entityToDTOo(orElse);}//ein kompletes neues Objekt hinzufuegen@PostMapping("/neu")publicUserDTOsave(@RequestParamString name){UserDTO dto =newUserDTO();
dto.setName(name);User user = converter.dtoToEntity(dto);
user = userRepository.save(user);return converter.entityToDTOo(user);}//einen Eintrag verändern//und dien gesamten eintrag zurueck geben@PutMapping("/put")publicUserDTOput(@RequestParamLong id,@RequestParamString name){UserDTO dto =newUserDTO();
dto.setName(name);
dto.setID(id);User user = converter.dtoToEntity(dto);
user = userRepository.save(user);return converter.entityToDTOo(user);}//ein Eintrag löschen mit dem Parameter ID@DeleteMapping("/del")publicList<UserDTO>loeschen(@RequestParamLong id){
userRepository.deleteById(id);return converter.entityToDTO(userRepository.findAll());}//hier wird explizit nach dem Namen gesuch und wiedergegeben@GetMapping("/einzelausgabe")publicList<UserDTO>einzel(@RequestParamString name){List<User> user = userRepository.findByName(name);return converter.entityToDTO(user);}}
Das einzige Problem, das ich hier sehe ist, dass das DTO ggf. den User überschreibt. Wir würden den User aus dem Repo holen und die Properties des DTO auf den persistenten User mappen.
Es geht einfach darum, dass Du aus dem DTO direkt einen User erstellst. Diese Entity kann aber nur über die Informationen verfügen, die im DTO existieren, der Rest wäre "leer". Würdest Du nun das so erzeugte User-Objekt im Repo speichern, wären die ggf. im existierenden User bereits vorhandenen Informationen verloren.
Spendier der User-Entity einfach mal ein Geburtsdatum. Jetzt bekommst Du von außen eine Änderung via UserDTO, das neben der ID nur den Namen enthält. Du erzeugst nun ein neues User-Objekt mit ID und Namen und speicherst den Spaß im Repository ab. Damit überschreibst Du Dir das Geburtsdatum des bereits vorhandenen Users.
Deswegen machen wir etwas wie
Java:
User user = userRepository.findById(dto.getId());
user.setName(dto.getName());// mappen der DTO-Properties auf den User
userRepository.save(user);
Das ist ja jetzt vom bisherigen Thema losgelöst. Generell ist das der Weg, den ich empfehlen würde. Also ein separates Identity Management (z.B. Keycloak - das ist einfach aufzusetzen auf Entwickler Systemen) und dann wird das lediglich genutzt.
Damit umgehst Du viele kritische Dinge wie z.B, die sicherer Verwahrung von Daten. (Du hattest das speichern von Passwörtern erwähnt. Wenn, dann wird nur ein Hash - oder besser: "seeded hash" - gespeichert, aber niemals die Passwörter.) Etwas, das Du nicht speicherst, das kann nicht böswillig gelesen werden.
Und es bietet Firmen die Möglichkeit, da eigene identity Management Lösungen zu nutzen. (Kennt evtl. der eine oder andere: man will sich irgendwo anmelden wie z.B. Microsoft und nach Eingabe der Email Adresse erscheint dann ein Login des Firmen Systems)
Spendier der User-Entity einfach mal ein Geburtsdatum. Jetzt bekommst Du von außen eine Änderung via UserDTO, das neben der ID nur den Namen enthält. Du erzeugst nun ein neues User-Objekt mit ID und Namen und speicherst den Spaß im Repository ab. Damit überschreibst Du Dir das Geburtsdatum des bereits vorhandenen Users.
Aber was ich mich gerade frage, das geht nur bei Objekten die schon existieren.
Das geht aber nicht wenn man ein neues Objekt erstellt, denn der DTO User hat ja nur "Rechte" auf name und id.
Da macht ja das erstellen keinen Sinn über DTO User. Oder wie ist das in der Prxis?
Das geht aber nicht wenn man ein neues Objekt erstellt, denn der DTO User hat ja nur "Rechte" auf name und id.
Da macht ja das erstellen keinen Sinn über DTO User. Oder wie ist das in der Prxis?
Prinzipiell geht es genau so beim Erstellem von neuen Usern.
Aber die fachlichen Fragen kannst nur Du klären. Generell kann man mehrere DTO Klassen haben. Dann ahst Du halt noch eine UserCreateDTO Klasse, die nur Felder hat, die beim Erzeugen Sinn machen.
Genau, die habe ich jetzt erstellt und funktioniert wie es funktionieren soll.
Bei der Put-Methode hatte ich einen Denkfehler, da wurde der ort z.b. immer gelöscht.
Jetzt sieht es so aus, und es funktioniert auch.
Vielen dank euch allen.
a) Warum erzeugst Du erst ein DTO, wenn Du es eh nicht brauchst? DTO sind Data Transfer Objects - also zur Weitergabe von Daten. Aber in Zeile 3 des Codes erzeugst Du eine Instanz, die Du nicht weiter gibst. Das ist so also schlicht sinnlos. In Zeile 6 kannst Du direkt die id verwenden und in Zeile 7 den Namen. Die DTO Klasse kommt also wirklich erst am Ende ins Spiel.
b) Was soll dieses orElse(null)? Damit kann user null sein und Du rennst in der folgenden Zeile in eine NPE. Und die NPE ist eine typische "EntwicklerIstZuBlödException" - das sollte dir bewusst sein!