Objekt in OneToOne Beziehung über http request anzeigen lassen

tom.j85

Mitglied
Hallo liebe Community,

wir sollen eine art "tripadvisor" nachprogrammieren, ich verzeweifle gerade an der Berechnung der durchschnittlichen Ratings. Meine Architektur sieht folgendermaßen aus:

Es gibt eine Mutterklasse Root (der Name muss dringend geändert werden, wir konnten uns in der Gruppe nicht einigen worauf):

Java:
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.itf.Review.Review;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Entity_Type")
public abstract class Root {

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

    @NotBlank
    @Column(nullable = false)
    @Size(min = 1, max = 40)
    private String name;

    @NotBlank
    @Column(nullable = false)
    private String address;

    @NotBlank
    @Column(nullable = false)
    @Size(min = 1, max = 20)
    private String telephone;

    @NotBlank
    @Column(nullable = false)
    @Size(min = 1, max = 20)
    private String city;

    @NotBlank
    @Column(nullable = false)
    @Size(min = 1, max = 500)
    private String description;

    private String website;

    private Double rating;


    
    
    @JsonManagedReference
    @OneToMany(mappedBy = "rootEntity")
    private List<Review> reviewList = new ArrayList<Review>();
    
}

Dann gibt es eine Klasse Hotel, die von Root erbt

Java:
package com.itf.Entity.Hotel;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.OneToOne;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.itf.Entity.Root;
import com.itf.Review.AvgRating;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@Entity
@DiscriminatorValue("Hotel")
@AllArgsConstructor
@NoArgsConstructor
public class Hotel extends Root {

    @NotBlank
    private Double price;
    
    private Integer stars;
    
    @NotNull
    private Integer rooms;
    
    private Boolean swimmingPool;


    private Boolean restaurant;

    private String breakfast;

    private Boolean wifi;

    private String parking;
    
    private String email;
    
    @JsonBackReference
    @OneToOne(mappedBy = "matchedReview")
    private AvgRating avgRatingStats;

}

eine Klasse Review

Java:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.itf.Entity.Root;
import com.sun.istack.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name="reviews")
public class Review implements Comparable< Review > {


    
    @Id
    @NotNull
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;
    
    private Double averageRating;

    
    @Min(0)
    @Max(5)
    private Double cleanness;

    @Min(0)
    @Max(5)
    private Double friendliness;

    @Min(0)
    @Max(5)
    private Double foodQuality;

    @Min(0)
    @Max(5)
    private Double ambience;

    @Min(0)
    @Max(5)
    private Double familyFriendly;

    @Size(min= 0, max = 255)
    private String comment;
    
    @NotBlank
    private String author;
    
    
    public Double getAverageRating() {
        
        return (cleanness+friendliness+foodQuality+ambience+familyFriendly)/5;
    }

    

    @JsonBackReference
    @ManyToOne
    @JoinColumn(name = "establishment_id")
    private Root rootEntity;
    
    }
:
und eine Klasse AvgRating, die ein Objekt sein soll, dass später die durchschnittlichen Werte der einzelnen Reviews speichert:

Code:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class AvgRating {

    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;
    
    
    Double avgAmbience = 0.0;
    Double avgFriendly = 0.0;
    Double avgClean = 0.0;
    Double avgFood = 0.0;
    Double avgFamily = 0.0;
    Double avgTotalRating = 0.0;
    
    
    @OneToOne
    @JoinColumn(name = "review_match")
    Review matchedReview;
    
}

Das Repository ist relativ straighfoward:

Code:
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface HotelRepository extends JpaRepository<Hotel, Integer> {

}

und der Knackpunkt ist der Controller. Ich würde gerne das Objekt private AvgRating avgRatingStats in der Klasse Hotel mit den Durchschnittswerten der einzelnen Reviews versehen, sobald es aus der Datenbank gezogen wird...


Java:
import java.util.List;
import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.itf.Review.AvgRating;
import com.itf.Review.Review;




@RestController
@RequestMapping("/smart-trip/hotels/")
public class HotelController {

    @Autowired
    HotelRepository hotelRepository;

    
    Hotel hotel;
    List <Hotel> hotelList;
    List <Review> reviews;
    AvgRating avgRating = new AvgRating();
    
    
    
    
    @GetMapping
    public List<Hotel> fetchAll(){
        
        hotelList = hotelRepository.findAll();
        

        for (Hotel hotel : hotelList) {
            reviews = hotel.getReviewList();
            
            
            for (Review review : reviews) {
                avgRating.setAvgAmbience(+review.getAmbience());
                avgRating.setAvgClean(+review.getCleanness());
                avgRating.setAvgFamily(+review.getFamilyFriendly());
                avgRating.setAvgFood(+review.getFoodQuality());
                avgRating.setAvgFriendly(+review.getFriendliness());
                avgRating.setAvgTotalRating(+review.getAverageRating());
                
            }
            hotel.setAvgRatingStats(avgRating);
            
        }
        
        
        return hotelList;
        
    }
    
    }

Um die ganze Sache noch zu verkomplizieren gibt es auch Mock-Daten:

SQL:
--Add Hotels
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Hotel am Main','Olof-Staße 19','017630222','Frankfurt','Gemütliche Hotel am Main','www.hotelmain.com',4,20.0,4,90,true,'false',false,true,'true','hotelmain@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Wald Hotel','Waldstr.13','017645665','Frankfurt','Günstiges Hotel am Wald','www.hotelWald.com',3,30.0,2,40,true,'false',true,true,'false','waldhotel@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Hotel Blankenese','Hauptstraße 19','017630222','Hamburg','Gemütliches Hotel an der Elbe','www.hotelblankenese.com',3.0,40.0,3,90,false,'false',false,true,'false','hotelmain@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Kreuzberg Inn','Wiesenstr.13','017645665','Berlin','Hotel im Szeneviertel','www.Kreuzberginn.com',4.5,60.0,5,40,true,'false',true,true,'false','waldhotel@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Schlosshotel Dresden','Hauptstaße 19','017630222','Dresden','Gemütliche Hotel an der Elbe','www.hotelmain.com',3.4,70.0,5,90,false,'false',false,true,'false','schlosshotel@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Berghotel Frankfurt','Waldstr.13','017645665','Frankfurt','Günstiges Hotel am Wald','www.hotelWald.com',2.7,40.0,3,40,false,'true',false,false,'true','waldhotel@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Gala Hotel','Hauptstraße 19','017630222','Frankfurt','Hochklassiges Domizil der Elbe','www.hotel-am-berg.com',3.0,100.0,5,90,false,'false',true,true,'false','hotelmain@mail.de');
INSERT INTO root (entity_type,name,address,telephone,city,description,website,rating,price,stars,rooms,swimming_pool,restaurant,breakfast,wifi,parking,email)VALUES('Hotel', 'Stadthotel Frankfurt','Wiesenstr.13','017645665','Frankfurt','Hotel im Szeneviertel','www.stadthotel.com',4.5,60.0,5,40,true,'true',true,true,'true','waldhotel@mail.de');


--Add Reviews
insert into reviews( cleanness, friendliness, food_quality, ambience, family_friendly, comment, author, establishment_id ) values
(3,5,4,1,5,'Tolles Ding', 'das Bo', 1);
insert into reviews( cleanness, friendliness, food_quality, ambience, family_friendly, comment, author, establishment_id ) values
(3,5,2,3,1,'Tolles Ding', 'der letzte Mohikaner',2);
insert into reviews( cleanness, friendliness, food_quality, ambience, family_friendly, comment, author, establishment_id ) values
(3,4,1,1,4,'Tolles Ding', 'Janette',3);
insert into reviews( cleanness, friendliness, food_quality, ambience, family_friendly, comment, author, establishment_id ) values
(5,2,2,1,2,'Tolles Ding', 'biba-mausi', 4);
insert into reviews( cleanness, friendliness, food_quality, ambience, family_friendly, comment, author, establishment_id ) values
(1,5,2,1,3,'Tolles Ding','Kalle',1);
insert into reviews( cleanness, friendliness, food_quality, ambience, family_friendly, comment, author, establishment_id ) values
(4,5,2,2,1,'Tolles Ding','Mareike E.',3);

Das Problem scheint, dass das AvgRating Objekt nicht weitergegeben wird....wenn es vom Frontend abgerufen wird.
Zur Info noch den http-Service in Angular:

Javascript:
const apiHotelRoot = "http://localhost:8080/smart-trip/hotels/";


@Injectable({
  providedIn: 'root'
})
export class HotelService {


public getHotels(): Observable<Hotel[]> {
    return this.http.get<Hotel[]>(apiHotelRoot);

  }

und in der entsprechenden hotel-list.ts datei steht dann

Javascript:
import { Component, OnInit, Input, SimpleChange } from '@angular/core';
import { Hotel } from '../hotel/model/hotel';
import { HotelService } from '../hotel/hotel.service';
import { Pipe, PipeTransform } from "@angular/core";
import { map } from 'rxjs/operators';
import { Observable, of } from 'rxjs';


@Component({
  selector: 'app-hotel-list',
  templateUrl: './hotel-list.component.html',
  styleUrls: ['./hotel-list.component.css']
})
export class HotelListComponent implements OnInit {

  constructor(private hotelService: HotelService) { }

  ngOnInit(): void {

    this.hotelService.getHotels()
      .subscribe(fetchedHotels => (this.filteredHotelList = fetchedHotels));
      
  }
  }

Wie gesagt, das funktioniert auch alles, die filteredHotelList gibt per json-pipe auch jede menge Infos (die Mock Daten eben) ....aaaber: Das im Controller in Java generierte AvgRating Objekt, das eigentlich per oneToOne in jedem Hotel stecken sollte kommt nicht an! Direkt hinter der reviewList müsste es eigetlich stehen:

JSON:
 [ { "id": 3, "name": "Hotel am Main", "address": "Olof-Staße 19", "telephone": "017630222", "city": "Frankfurt", "description": "Gemütliche Hotel am Main", "website": "www.hotelmain.com", "rating": 4, "reviewList": [ { "id": 3, "averageRating": 2.6, "cleanness": 3, "friendliness": 4, "foodQuality": 1, "ambience": 1, "familyFriendly": 4, "comment": "Tolles Ding", "author": "Janette" }, { "id": 6, "averageRating": 2.8, "cleanness": 4, "friendliness": 5, "foodQuality": 2, "ambience": 2, "familyFriendly": 1, "comment": "Tolles Ding", "author": "Mareike E." } ], "price": 20, "stars": 4, "rooms": 90, "swimmingPool": true, "restaurant": false, "breakfast": "FALSE", "wifi": true, "parking": "true", "email": "hotelmain@mail.de" }

Ich vermute irgendwo habe ich einen Denkfehler..ist mein erstes Projekt mit Datenbanken..bin für jeden Tipp und jede Hilfe dankbar!
 

looparda

Top Contributor
Die eine Seite der Relation wird mit @JsonManagedReference , die andere mit @JsonBackReference annotiert.
An
Java:
    @OneToOne
    @JoinColumn(name = "review_match")
    Review matchedReview;
fehlt eine Annotation.
Statt dem Frontend Code hättest du einen Test zeigen können, der das Mapping zu JSON testet und fehlschlägt.
 

tom.j85

Mitglied
Jaaa, das war das Problem! Drei Stunden Suche und da wars. Vielen Dank! Jetzt muss ich nur noch die Berechnung vernünftig hinbekommen.

Besten Dank! Ich werde mich auch mal mit JSON tests beschäftigen. JSONAssert habe ich gerade gefunden. Denke das ist was Du meinst.

Beste Grüße
Tom
 

looparda

Top Contributor
So in etwa (ungetestet):
Java:
HotelTest

@Test
public void givenBidirectionRelation_whenSerializingAsJSON_hasAvgRatingStats() throws JsonProcessingException, IOException {
    Hotel hotel = new Hotel(...);

    String result = new ObjectMapper().writeValueAsString(hotel);

    assertThat(result, containsString("\"AvgRatingStats\":"));
}
Geht mit JSONAssert und anderen Libs wohl auch sicher filigraner, wenn man das möchte.
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
C Objekte aus DB in Strings umwandeln also von List<Objekt> in String Datenbankprogrammierung 6
S Mit 2 Queries ein Objekt erstellen Datenbankprogrammierung 6
I SQLite Objekt speichern einer Serialisierter Klasse Datenbankprogrammierung 1
P MySQL Historie für ein Objekt anlegen Datenbankprogrammierung 5
H JDBCODBC - Connection-Objekt Datenbankprogrammierung 3
N Java-Objekt dynamisch aus Datenbanktabelle erzeugen? Datenbankprogrammierung 4
W MySQL-Connection-Objekt übergeben Datenbankprogrammierung 2
G MySQL Zugriff auf Datenbank Objekt Datenbankprogrammierung 19
M JPA-Query - nicht das komplette Objekt Datenbankprogrammierung 4
J Derby/JavaDB NullPointerException bei VORHANDENEM EntityManager-Objekt!!! Datenbankprogrammierung 3
kirchrath Hibernate Join - angejointes Objekt wählen Datenbankprogrammierung 2
P MySQL Objekt aus BLOB auslesen Datenbankprogrammierung 3
S JPA: Objekt in Tabelle mit Composite Keys mappen (Embeddable Annotation) Datenbankprogrammierung 2
Y JPQL WHERE != Objekt Datenbankprogrammierung 10
J Datenbank: Record-Objekt Datenbankprogrammierung 2
M \t in List-Objekt Datenbankprogrammierung 5
S Java objekt in MYSQL auslesen Datenbankprogrammierung 32
M Java Objekt in Datenbank schreiben :( Datenbankprogrammierung 8
G objekt zurück setzen Datenbankprogrammierung 2
sparrow Objekt mit lazy-verbindung komplettieren Datenbankprogrammierung 13
A Fehlermeldung "Objekt bereits geschlossen" Datenbankprogrammierung 3
A JDBC-Fehler "Objekt bereits geschlossen" Datenbankprogrammierung 4
Y Hibernate - 1:1 Beziehung liefert leeres Objekt Datenbankprogrammierung 19
S Hibernate - Ein Referenziertes Objekt mit Sprichern Datenbankprogrammierung 5
P [Hibernate] Objekt laden, Id ändern und speichern? Datenbankprogrammierung 2
R Hibernate speichert nur das Basis-Objekt Datenbankprogrammierung 4
M Objekt in Datenbank speichern Datenbankprogrammierung 2
J Wie kann ich in ein OLE-Objekt reinschreiben ? Datenbankprogrammierung 2
K relationale vs. objekt-orientierte Datenbank Datenbankprogrammierung 4
L Objekt in die DB speichern und die Id zurückgeben Datenbankprogrammierung 2
Psypsy Hibernate / JPA OneToOne MappedBy Frage Datenbankprogrammierung 2
B Hibernate Annotation @OneToOne Fremdschlüssel Datenbankprogrammierung 3
turmaline [Hibernate] @OneToOne: Löschen einer referenzierten Instanz Datenbankprogrammierung 2
turmaline H2 [Hibernate] @OneToOne-Beziehung Datenbankprogrammierung 5
Maxim6394 JPA / EclipseLink - n:m Beziehung wird nicht aktualisiert Datenbankprogrammierung 0
G Oracle Hibernate M:N-Beziehung mit nur einer vorhandenen Tabelle Datenbankprogrammierung 5
S Hibernate - N:1 Beziehung Datenbankprogrammierung 8
Y ManyToOne Beziehung persistieren Datenbankprogrammierung 10
G 1:n Beziehung Datenbankprogrammierung 11
M Hibernate - Welche Beziehung? Datenbankprogrammierung 3
S Hibernate - spezielle Frage, n zu n Beziehung Datenbankprogrammierung 11
G m:n Beziehung - Tabelle richtig erzeugen Datenbankprogrammierung 3
R [Hibernate] 0..1 Beziehung mit XDoclet abbilden Datenbankprogrammierung 3
G Frage zu Hibernate und M:N Beziehung Datenbankprogrammierung 9
Y Hibernate - Mapping der Beziehung zwischen 2 Tabellen Datenbankprogrammierung 2

Ähnliche Java Themen


Oben