Projeto de aplicativo de carregamento lento do Hibernate

87

Costumo usar o Hibernate em combinação com o framework Spring e seus recursos de demarcação de transação declarativa (por exemplo, @Transactional ).

Como todos sabemos, o Hibernate tenta ser o mais não invasivo e transparente possível, mas isso se mostra um pouco mais desafiador quando se trata de lazy-loadedrelacionamentos.


Vejo uma série de alternativas de design com diferentes níveis de transparência.

  1. Faça relacionamentos não carregados lentamente (por exemplo, fetchType=FetchType.EAGER)
    • Isso viola toda a ideia de carregamento lento.
  2. Inicializar coleções usando Hibernate.initialize(proxyObj);
    • Isso implica um acoplamento relativamente alto ao DAO
    • Embora possamos definir uma interface com initialize, outras implementações não têm garantia de fornecer qualquer equivalente.
  3. Adicione o comportamento da transação aos Modelpróprios objetos persistentes (usando proxy dinâmico ou @Transactional)
    • Não tentei a abordagem de proxy dinâmico, embora nunca tenha parecido fazer o @Transactional funcionar nos próprios objetos persistentes. Provavelmente devido a essa hibernação é uma operação em um proxy para estar.
    • Perda de controle quando as transações estão realmente ocorrendo
  4. Fornece API preguiçoso / não preguiçoso, por exemplo, loadData()eloadDataWithDeps()
    • Força o aplicativo a saber quando empregar qual rotina, novamente o acoplamento forte
    • Estouro de método,, loadDataWithA()....,loadDataWithX()
  5. Força a procura de dependências, por exemplo, fornecendo apenas byId()operações
    • Requer muitas rotinas não orientadas a objetos, por exemplo,, findZzzById(zid)e então em getYyyIds(zid)vez dez.getY()
    • Pode ser útil buscar cada objeto em uma coleção, um por um, se houver uma grande sobrecarga de processamento entre as transações.
  6. Faça parte da aplicação @Transactional em vez de apenas DAO
    • Possíveis considerações de transações aninhadas
    • Requer rotinas adaptadas para gerenciamento de transações (por exemplo, suficientemente pequeno)
    • Pequeno impacto programático, embora possa resultar em grandes transações
  7. Fornecer ao DAO perfis de busca dinâmica , por exemplo,loadData(id, fetchProfile);
    • Os aplicativos devem saber qual perfil usar quando
  8. Tipo de transação AoP, por exemplo, interceptar operações e realizar transações quando necessário
    • Requer manipulação de código de byte ou uso de proxy
    • Perda de controle quando as transações são realizadas
    • Magia negra, como sempre :)

Eu perdi alguma opção?


Qual é a sua abordagem preferida ao tentar minimizar o impacto dos lazy-loadedrelacionamentos no design de seu aplicativo?

(Oh, e desculpe por WoT )

Johan Sjöberg
fonte
exemplo para as opções 2 e 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be
Você poderia fornecer um exemplo para a opção 4?
grausightdc

Respostas:

26

Como todos sabemos, o Hibernate tenta ser o mais não invasivo e transparente possível

Eu diria que a suposição inicial está errada. A persistência transaparente é um mito, pois a aplicação deve sempre cuidar do ciclo de vida da entidade e do tamanho do gráfico do objeto que está sendo carregado.

Note que o Hibernate não pode ler pensamentos, portanto, se você sabe que precisa de um conjunto particular de dependências para uma operação particular, você precisa expressar suas intenções de Hibernar de alguma forma.

Deste ponto de vista, as soluções que expressam essas intenções explicitamente (nomeadamente, 2, 4 e 7) parecem razoáveis ​​e não sofrem de falta de transparência.

axtavt
fonte
Você está certo, é claro, o mais transparente possível só funciona até agora. Essas são algumas escolhas boas que você escolheu.
Johan Sjöberg
IMHO: resposta perfeitamente correta. Na verdade, é um mito. BTW: meu voto seria para as opções 4 e 7 (ou me afastar do ORM)
G. Demecki
7

Não tenho certeza de qual problema (causado pela preguiça) você está sugerindo, mas para mim o maior problema é evitar a perda do contexto da sessão em meus próprios caches de aplicativo. Caso típico:

  • o objeto fooé carregado e colocado em um mapa;
  • outra thread pega esse objeto do mapa e chama foo.getBar()(algo que nunca foi chamado antes e é avaliado lentamente);
  • estrondo!

Portanto, para resolver isso, temos uma série de regras:

  • envolva as sessões da forma mais transparente possível (por exemplo, OpenSessionInViewFilterpara aplicativos da web);
  • têm API comum para threads / pools de threads em que a vinculação / desconexão da sessão do banco de dados é feita em algum lugar alto na hierarquia (envolvida try/finally) para que as subclasses não precisem pensar nisso;
  • ao passar objetos entre threads, passe IDs em vez dos próprios objetos. O encadeamento de recebimento pode carregar o objeto, se necessário;
  • ao armazenar objetos em cache, nunca armazene objetos em cache, mas seus ids. Tenha um método abstrato em seu DAO ou classe de gerenciador para carregar o objeto do cache de 2º nível do Hibernate quando você souber o ID. O custo de recuperar objetos do cache do Hibernate de segundo nível ainda é muito mais barato do que ir para o banco de dados.

Como você pode ver, isso está longe de ser não invasivo e transparente . Mas o custo ainda é suportável, para comparar com o preço que eu pagaria para carregar antecipadamente. O problema com o último é que às vezes leva ao efeito borboleta ao carregar um único objeto referenciado, quanto mais uma coleção de entidades. Consumo de memória, uso de CPU e latência para mencionar o mínimo também são muito piores, então acho que posso viver com isso.

mindas
fonte
Obrigado pela sua resposta. A perda de transparencyé forçar o aplicativo a se preocupar com o carregamento de objetos preguiçosos. Se tudo fosse buscado avidamente, o aplicativo poderia estar completamente inconsciente se os objetos são persistidos em um banco de dados ou não, já Foo.getBar()que sempre terá sucesso. > when passing objects between threads, pass IDs, sim, isso corresponderia a # 5.
Johan Sjöberg
3

Um padrão muito comum é usar OpenEntityManagerInViewFilter se você estiver construindo um aplicativo da web.

Se você estiver construindo um serviço, eu abriria o TX no método público do serviço, em vez de nos DAOs, como muitas vezes um método requer para obter ou atualizar várias entidades.

Isso resolverá qualquer "exceção de carregamento lento". Se você precisa de algo mais avançado para ajuste de desempenho, acho que buscar perfis é o caminho a percorrer.

Augusto
fonte
1
Eu acho que você queria dizer: A antipattern muito comum ... . Embora eu concorde em abrir o TX no nível de serviço, o uso do OSIVainda é um antipadrão e leva a problemas muito sérios, como incapacidade de lidar com exceções ou degradação de desempenho. Resumindo: IMHO OSIV é uma solução fácil de usar, mas boa apenas para projetos de brinquedo.
G. Demecki