2017-04-18 7 views
2

Um mein Problem zu demonstrieren, habe ich eine einfache Spring Boot-Anwendung erstellt. Es hat eine Entität, die ID, zwei String Eigenschaften und zwei Sets<String> Sets hat.So optimieren Sie Carstesian Produkt beim Laden Hibernate Entity

package com.mk.cat.domain; 

import javax.persistence.*; 
import java.util.Set; 

@Entity 
@Table(name = "cat") 
public class Cat { 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id") 
    private Long id; 

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

    @Column(name = "sex") 
    private String sex; 

    @ElementCollection(fetch = FetchType.EAGER) 
    @Column(name = "color") 
    @CollectionTable(
      name = "cat_color", 
      joinColumns = @JoinColumn(name = "cat_id")) 
    private Set<String> colors; 

    @ElementCollection(fetch = FetchType.EAGER) 
    @Column(name = "nickname") 
    @CollectionTable(
      name = "cat_nickname", 
      joinColumns = @JoinColumn(name = "cat_id")) 
    private Set<String> nicknames; 

    public Long getId() { 
     return id; 
    } 

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

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public String getSex() { 
     return sex; 
    } 

    public void setSex(String sex) { 
     this.sex = sex; 
    } 

    public Set<String> getColors() { 
     return colors; 
    } 

    public void setColors(Set<String> colors) { 
     this.colors = colors; 
    } 

    public Set<String> getNicknames() { 
     return nicknames; 
    } 

    public void setNicknames(Set<String> nicknames) { 
     this.nicknames = nicknames; 
    } 
} 

Es gibt auch einen einfachen Code, der persistent ist und die Cat-Entity von DB lädt.

package com.mk.cat; 

import com.google.common.collect.Sets; 
import com.mk.cat.domain.Cat; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.boot.CommandLineRunner; 
import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 

@SpringBootApplication 
public class CatApplication implements CommandLineRunner { 

    private final CatRepository catRepository; 

    private static final Logger LOGGER = LoggerFactory.getLogger(CatApplication.class); 

    @Autowired 
    public CatApplication(CatRepository catRepository) { 
     this.catRepository = catRepository; 
    } 

    public static void main(String[] args) { 
     SpringApplication.run(CatApplication.class, args); 
    } 

    @Override 
    public void run(String... args) throws Exception { 
     Cat cat = new Cat(); 
     cat.setName("Ben"); 
     cat.setSex("Male"); 
     cat.setNicknames(Sets.newHashSet("Fluffy", "Mr. Tomcat", "Catburger")); 
     cat.setColors(Sets.newHashSet("Black", "White")); 

     final Cat saved = catRepository.save(cat); 
     LOGGER.info("Cat saved={}", cat); 

     catRepository.findOne(saved.getId()); 
    } 
} 

Ich verfolgen Hibernate und ich fand, dass die Cat von DB von diesem SQL geladen wird.

select cat0_.id as id1_0_0_, 
    cat0_.name as name2_0_0_, 
    cat0_.sex as sex3_0_0_, 
    colors1_.cat_id as cat_id1_1_1_, 
    colors1_.color as color2_1_1_, 
    nicknames2_.cat_id as cat_id1_2_2_, 
    nicknames2_.nickname as nickname2_2_2_ 
    from cat cat0_ 
    left outer join cat_color colors1_ on cat0_.id=colors1_.cat_id 
    left outer join cat_nickname nicknames2_ on cat0_.id=nicknames2_.cat_id 
where cat0_.id=1 

Der Ruhezustand wird dann dieses kartesische Produkt aus den Zeilen der Tabelle cat und zwei Tabellen, welche die Cat#colors und Cat#nicknames Sätze darstellen.

id1_0_0_ name2_0_0_ sex3_0_0_ cat_id1_1_1_ color2_1_1_ cat_id1_2_2_ nickname2_2_2_ 
1 Ben Male 1 Black 1 Fluffy 
1 Ben Male 1 Black 1 Catburger 
1 Ben Male 1 Black 1 Mr. Tomcat 
1 Ben Male 1 White 1 Fluffy 
1 Ben Male 1 White 1 Catburger 
1 Ben Male 1 White 1 Mr. Tomcat 

Hibernate geht dann durch jede und jeder Zeile analysiert, jedes einzelne Element des ResultSet und schafft die Entity. Ist es irgendwie möglich, diesen Ansatz zu optimieren? Ich möchte die Cat#colors und Cat#nicknames Sätze von einem Subselect auswählen, wegen schwerwiegenden Leistungsproblemen. Im realen Fall, hole ich 1500 Entitäten, die eine komplexe Struktur haben und es ist nicht ungewöhnlich, dass eine abgerufene Entität 25.000 Zeilen im entsprechenden ResultSet erzeugt, was eine sehr lange Parsingzeit verursacht.

Das Lazy Loading in meinem Fall ist nicht die Option, die ich verwenden möchte, weil es Unordnung in den Code bringt. Soweit ich weiß, muss die lazy loaded Collection beim ersten Aufruf initialisiert werden und das ist ein ziemlich großer Usability-Preis, den ich in meiner realen Anwendung bezahlen muss.

Ich würde schätzen 3 separate wählt, eine aus der cat Tabelle, eine aus der cat_color Tabelle und eine aus der cat_nickname Tabelle.

+1

Die Anforderung, die Sie verfolgt haben, sieht für mich gut aus und ist nicht das, was ich ein "kartesisches Produkt" nennen würde. Welche andere SQL-Anforderung würde die Daten einer einzelnen Katze abrufen? Wenn Sie für einen bestimmten Anwendungsfall keine peripheren Daten benötigen, können Sie jedoch einige Verbindungen trennen (FetchType.LAZY). – Tristan

+0

Das Lazy-Laden in meinem Fall ist nicht die Option, die ich verwenden möchte, weil es den Code überflutet. Soweit ich weiß, muss die lazy loaded Collection beim ersten Aufruf initialisiert werden und das ist ein ziemlich großer Usability-Preis, den ich in meiner realen Anwendung bezahlen muss. – Michal

+0

Welcher andere SQL-Request würde die Daten einer einzelnen Katze abrufen? – Tristan

Antwort

0

Ich fand die Lösung für Hibernate, die @Fetch(FetchMode.SELECT) hat die Arbeit getan, weil es Hibernate die Spitznamen durch eine separate Auswahl anstelle von Join auswählen.

@Fetch(FetchMode.SELECT) 
@ElementCollection(fetch = FetchType.EAGER) 
@Column(name = "nickname") 
@CollectionTable(
     name = "cat_nickname", 
     joinColumns = @JoinColumn(name = "cat_id")) 
private Set<String> nicknames; 
Verwandte Themen