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.
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
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
Welcher andere SQL-Request würde die Daten einer einzelnen Katze abrufen? – Tristan