Ленивая загрузка не работает в простом примере загрузки Hibernate / Spring после получения объекта из JpaRepository - PullRequest
0 голосов
/ 19 апреля 2020

Я разработал загрузочное приложение Spring, которое использовало аннотацию fetch = EAGER для всех отношений между сущностями. Я думаю, что это вызывает серьезные проблемы с производительностью, и с тех пор я узнал, что это, по-видимому, анти-паттерн (https://vladmihalcea.com/the-open-session-in-view-anti-pattern/ & https://vladmihalcea.com/eager-fetching-is-a-code-smell/).

Я пытался понять, как правильно использовать отложенную загрузку. Я создал минимальный пример, который позволяет мне воспроизводить его.

TestJpaApplication

package com.myproject.testJpa;

import com.myproject.testJpa.entity.Host;
import com.myproject.testJpa.entity.HostSet;
import com.myproject.testJpa.entity.repository.HostRepository;
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;
import org.springframework.context.annotation.Bean;
import com.myproject.testJpa.entity.repository.HostSetRepository;
import org.springframework.transaction.annotation.Transactional;

@SpringBootApplication
public class TestJpaApplication {
    private final Logger logger = LoggerFactory.getLogger(TestJpaApplication.class);

    @Autowired
    private HostRepository hostRepository;

    @Autowired
    private HostSetRepository hostSetRepository;

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

    @Bean
    public CommandLineRunner demo() {
        return (args) -> {
            init();
            fetch();
        };
    }

    private void init() {
        Host host1 = findOrCreateHost("HOST 1");
        Host host2 = findOrCreateHost("HOST 2");
        Host host3 = findOrCreateHost("HOST 3");

        HostSet hostSet = findOrCreateHostSet("HOST SET 1");

        hostSet.addHost(host1);

        hostSetRepository.save(hostSet);

        hostRepository.save(host1);
        hostRepository.save(host2);
        hostRepository.save(host3);
    }

    @Transactional
    private void fetch() {
        HostSet hostSet = hostSetRepository.findOneByNameIgnoreCase("HOST SET 1");

        for(Host host : hostSet.getHosts()) {
            logger.debug("Host: {}", host);
        }
    }

    public Host findOrCreateHost(String name) {
        Host host = hostRepository.findOneByNameIgnoreCase(name);

        if(host == null) {
            host = new Host(name);

            hostRepository.save(host);
        }
        return host;
    }

    public HostSet findOrCreateHostSet(String name) {
        HostSet hostSet = hostSetRepository.findOneByNameIgnoreCase(name);

        if (hostSet == null) {
            hostSet = new HostSet(name);
            hostSetRepository.save(hostSet);
        }

        logger.debug("Host: {}", hostSet.getHosts());

        return hostSet;
    }
}

Host

package com.myproject.testJpa.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

@Entity
public class Host {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "host__id")
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "hosts")
    private Set<HostSet> hostSets = new HashSet<>();

    public Host() {

    }

    public Host(Long id) {
        this.id = id;
    }

    public Host(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

    public Set<HostSet> getHostSets() {
        return hostSets;
    }

    public void setHostSets(Set<HostSet> hostSets) {
        this.hostSets = hostSets;

        hostSets.forEach(hs -> addToHostSet(hs));
    }

    public Host addToHostSet(HostSet hostSet) {
        if (!hostSets.contains(hostSet)) {
            hostSets.add(hostSet);
            hostSet.getHosts().add(this);
        }

        return this;
    }

    public Host removeFromHostSet(HostSet hostSet) {
        if (hostSets.contains(hostSet)) {
            hostSets.remove(hostSet);
            hostSet.getHosts().remove(this);
        }

        return this;
    }
}

HostSet

package com.myproject.testJpa.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;



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

    private String name;

    @ManyToMany
    @JoinTable(
        name = "host_set__host",
        joinColumns = @JoinColumn(name = "host_set__id"),
        inverseJoinColumns = @JoinColumn(name = "host__id")
    )
    private Set<Host> hosts = new HashSet<>();

    public HostSet() {
    }

    public HostSet(Long id) {
        this.id = id;
    }

    public HostSet(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

    public Set<Host> getHosts() {
        return hosts;
    }

    public void setHosts(Set<Host> hosts) {
        this.hosts = hosts;
    }

    public HostSet addHost(Host host) {
        if(!hosts.contains(host)) {
            hosts.add(host);
            host.addToHostSet(this);
        }

        return this;
    }

    public HostSet removeHost(Host host) {
        if(hosts.contains(host)) {
            hosts.remove(host);
            host.removeFromHostSet(this);
        }

        return this;
    }
}

HostRepository

package com.myproject.testJpa.entity.repository;

import com.myproject.testJpa.entity.Host;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface HostRepository extends JpaRepository<Host, Long> {
    public Host findOneByNameIgnoreCase(String name);
}

HostSetRepository

package com.myproject.testJpa.entity.repository;

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

import com.myproject.testJpa.entity.HostSet;

@Repository
public interface HostSetRepository extends JpaRepository<HostSet, Long> {
    public HostSet findOneByNameIgnoreCase(String name);
}

Когда я запускаю приложение, оно выдает следующую ошибку при зацикливании хостов полученного hostSet в методе fetch ().

Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myproject.testJpa.entity.HostSet.hosts, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
    at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
    at com.myproject.testJpa.TestJpaApplication.fetch(TestJpaApplication.java:58)
    at com.myproject.testJpa.TestJpaApplication.lambda$demo$0(TestJpaApplication.java:34)
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:784)

Я пытался добавить аннотацию @Transactional в нескольких местах, но безрезультатно. Это сводит меня с ума, потому что я не вижу, что я делаю неправильно.

Спасибо за вашу помощь!

1 Ответ

0 голосов
/ 23 апреля 2020

Оказывается, что @Transactional не работал в классе TestJpaApplication (я не установил его для анонимного метода, не знаю, как или если это возможно).

Я переместил содержимое в отдельный сервис и все заработало.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...