Remplacement d'un ORM complet (JPA / Hibernate) par une solution plus légère: modèles recommandés pour charger/enregistrer?

je développe une nouvelle application Web Java et j'explore de nouvelles voies (nouveau pour moi!) pour conserver les données. J'ai surtout de l'expérience avec JPA & Hibernate mais, sauf pour les cas simples, je pense que ce genre d'ORMS complet peut devenir assez complexe. En plus, je n'aime pas trop travailler avec eux. Je cherche une nouvelle solution, probablement plus proche de SQL.

Les solutions je suis à la recherche actuellement :

mais il y a deux cas d'utilisation qui me préoccupent avec ces solutions, comparativement à L'hibernation. J'aimerais savoir quels sont les modèles recommandés pour ces cas d'utilisation.


"1519500920 Cas d'Utilisation" 1 - Extraction d'un entité et accès à certaines de ses entités associées d'enfants et de petits-enfants.
  • disons que j'ai une entité Person .
    • cette Person a une entité associée Address .
      • cette Address a une entité associée City .
        • cette City entité a un bien name .

le chemin complet pour accéder au nom de la ville, à partir de l'entité personne, serait:

person.address.city.name

maintenant, disons que je charge l'entité de personne d'un PersonService , avec cette méthode:

public Person findPersonById(long id)
{
    // ...
}

en utilisant Hibernate, les entités associées au Person pourraient être chargées paresseusement, sur demande, donc il serait possible d'accéder à person.address.city.name et être sûr que j'ai accès à ce propriété (aussi longtemps que toutes les entités de cette chaîne ne sont pas nulles).

mais utiliser l'une des 3 solutions que j'étudie, c'est plus compliqué. Avec ces solutions, quels sont les modèles recommandés pour prendre soin de ce cas d'utilisation? À l'avance, je vois 3 modèles possibles:

  1. " tous les enfants et petits-enfants associés requis entités pourraient être chargés avidement par la requête SQL utilisée.

    Mais la question que je vois avec cette solution est qu'il peut y avoir un autre code qui doit accéder à autres entités/propriétés chemins de l'entité Person . Par exemple, peut-être qu'un code aura besoin d'accéder à person.job.salary.currency . Si je veux réutiliser la méthode findPersonById() que j'ai déjà, la requête SQL devra alors charger plus d'informations! Non seulement l'entité address->city associée, mais aussi l'entité job->salary associée.

    maintenant qu'arrive-t-il s'il y a 10 d'autres endroits qui ont besoin d'accéder à d'autres renseignements à partir de l'entité personne? Dois-je toujours charger avec empressement tous les informations potentiellement nécessaires? Ou peut-être avoir 12 méthodes de service différentes pour charger une entité de personne? :

    findPersonById_simple(long id)
    
    findPersonById_withAdressCity(long id)
    
    findPersonById_withJob(long id)
    
    findPersonById_withAdressCityAndJob(long id)
    
    ...
    

    mais à chaque fois que j'utilisais une entité Person , je devais savoir ce qui en avait été chargé et ce qui ne l'avait pas été... Il pourrait être assez lourd, pas vrai?

  2. dans la méthode getAddress() getter de l'entité Person , pourrait-il y avoir une vérification pour voir si l'adresse a déjà été chargée et, si non, la charger paresseusement? Il s'agit d'un modèle fréquemment utilisé dans les applications de la vie réelle?

  3. y a-t-il d'autres modèles qui peuvent être utilisés pour m'assurer que je peux accéder aux entités/propriétés dont j'ai besoin à partir d'un modèle chargé?


utiliser le cas 2-sauvegarder une entité et s'assurer que ses entités associées et modifiées sont aussi sauvegardées.

je veux pouvoir sauvegarder une Person entité en utilisant cette PersonService 's méthode:

public void savePerson(Person person)
{
    // ...
}

si j'ai une entité Person et que je change person.address.city.name en quelque chose d'autre, Comment puis-je m'assurer que les modifications de l'entité City persisteront lorsque je sauvegarderai le Person ? En utilisant Hibernate, il peut être facile de cascade l'opération de sauvegarde aux entités associées. Qu'en est-il des solutions que j'étudie?

  1. " devrais-je utiliser une sorte de drapeau dirty pour savoir quelles entités associées doivent aussi être sauvegardées lorsque je sauve la personne?

  2. Existe-t-il d'autres modèles connus utiles pour traiter ce cas d'utilisation?


mise à Jour : Il est une discussion à propos de cette question sur la JOOQ forum.

34
demandé sur ROMANIA_engineer 2013-07-25 18:00:29

7 réponses

ce genre de problème est typique quand on n'utilise pas un vrai ORM, et il n'y a pas de balle en argent. Une approche de conception simple qui a fonctionné pour moi pour un (pas très grand ) webapp avec iBatis (myBatis), est d'utiliser deux couches pour la persistance:

  • une couche de bas niveau muette: chaque table a sa classe Java (POJO ou DTO), avec des champs qui correspondent directement aux colonnes . Disons que nous avons une table PERSON avec un ADDRESS_ID zone pointant vers une table ADRESS ; ensuite, nous aurions une classe PersonDb , avec juste un champ addressId (entier); nous n'avons pas de méthode personDb.getAdress() , juste la méthode simple personDb.getAdressId() . Ces classes Java sont donc assez stupides (elles ne connaissent pas la persistance ou les classes apparentées). Une classe PersonDao correspondante sait comment charger/persister cet objet. Cette couche est facile à créer et à entretenir avec des outils comme iBatis + iBator (ou MyBatis + MYBatisGenerator ).

  • une couche de niveau supérieur qui contient objets de domaine riches : chacun de ceux-ci est typiquement un graphe des POJOs ci-dessus. Ces classes ont aussi l'intelligence pour charger/sauvegarder le graphe (peut-être paresseusement, peut-être avec quelques drapeaux sales), en appelant les DAOs respectifs. L'important, cependant, est que ces objets de domaine riches ne sont pas une carte un-à-un à les objets POJO (ou tables DB), mais plutôt avec domain use cases . La" taille " de chaque graphe est déterminée (elle ne croît pas indéfiniment), et est utilisée de l'extérieur comme une classe particulière. Donc, ce n'est pas que vous ayez une classe riche Person (avec un graphe indéterminé d'objets liés) qui est utilisé est plusieurs cas d'utilisation ou des méthodes de service; à la place, vous avez plusieurs classes riches, PersonWithAddreses , PersonWithAllData ... chaque enveloppe un particulier bien limitée graphique, avec sa propre logique de persistance. Cela peut sembler inefficace ou maladroit, et dans un certain contexte, cela peut l'être, mais il arrive souvent que les cas d'utilisation lorsque vous avez besoin de sauvegarder un graphique complet d'objets sont en fait limités.

  • de plus, pour des choses comme les rapports tabulaires, (sélections spécifiques qui renvoient un tas de colonnes à afficher) vous n'utiliseriez pas le POJO ci-dessus, mais Droit et muet (peut-être même des cartes)

voir ma réponse connexe ici

27
répondu leonbloy 2017-05-23 12:10:30

La réponse à vos nombreuses questions est simple. Vous avez trois choix.

  1. utilisez l'un des trois outils SQL-centric que vous avez mentionnés ( MyBatis , jOOQ , DbUtils ). Cela signifie que vous devez arrêter de penser en termes de votre modèle de domaine OO et de mappage Objet-Relationnel (i.e. entities et lazy loading). SQL est sur les données relationnelles et les RBDMS sont assez bons pour calculer plans d'exécution pour "chercher impatiemment" le résultat de plusieurs jointures. Habituellement, il n'y a même pas beaucoup de besoin pour la mise en cache prématurée, et si vous avez besoin de mettre en cache l'élément de données occasionnelle, vous pouvez toujours utiliser quelque chose comme EhCache

  2. N'utilisez aucun de ces outils SQL-centric et collez avec Hibernate / JPA. Parce que même si tu dis que tu n'aimes pas hiberner, tu "penses hiberner". Hibernation est très bonne à persister graphiques d'objets vers la base de données. Aucun de ces outils ne peut être forcé à travailler comme hibernation, parce que leur mission est autre chose. Leur mission est d'opérer sur SQL.

  3. allez d'une manière entièrement différente et choisissez de ne pas utiliser un modèle de données relationnelles. D'autres modèles de données (graphiques, par exemple) peut mieux vous convenir. Je mets cela comme une troisième option, parce que vous pourriez ne pas réellement avoir ce choix, et je n'ai pas beaucoup d'expérience personnelle avec d'autres modèles.

notez que votre question ne concernait pas spécifiquement jOOQ. Néanmoins, avec jOOQ, vous pouvez externaliser la cartographie des résultats de requêtes plates (produites à partir de sources de table jointes) pour objecter des graphiques à travers des outils externes tels que ModelMapper . Il y a un fil de discussion intéressant sur une telle intégration sur le groupe D'utilisateurs ModelMapper .

(clause de non-responsabilité: je travaille pour la société derrière jOOQ)

28
répondu Lukas Eder 2015-12-06 00:01:44

La Persistance Des Approches

le spectre des solutions simples / de base à sophistiquées / riches est:

  • SQL/JDBC - dur-code SQL à l'intérieur des objets
  • SQL-Base de Cadre (par exemple, jOOQ, MyBatis) - Modèle d'Enregistrement Active (séparée objet général représente les données de lignes et poignées SQL)
  • ORM-Framework (e.g. Hibernate, EclipseLink, Datanucléus) - Data Mapper Pattern (Objet par entité) plus Unité de modèle de travail (contexte de persistance / Gestionnaire D'entité)

vous cherchez à mettre en œuvre l'un des deux premiers niveaux. Cela signifie déplacer la mise au point loin du modèle d'objet vers SQL. Mais votre question demande des cas d'utilisation impliquant le modèle d'objet étant mappé en SQL (c'est-à-dire le comportement ORM). Vous souhaitez ajouter des fonctionnalités à partir du troisième niveau contre des fonctionnalités à partir de l'un des deux premiers niveaux.

nous pourrions essayez de mettre en œuvre ce comportement dans un dossier actif. Mais cela nécessiterait des métadonnées riches à attacher à chaque instance Active D'enregistrement - l'entité réelle impliquée, ses relations avec d'autres entités, les paramètres de chargement paresseux, les paramètres de mise à jour en cascade. Cela en ferait effectivement un objet d'entité cartographié caché. De plus, jOOQ et MyBatis ne font pas cela pour les cas D'utilisation 1 et 2.

Comment Réaliser Vos Demandes?

implémenter un comportement ORM étroit directement dans vos objets, comme une petite couche personnalisée sur le dessus de votre cadre ou SQL/JDBC brut.

Cas d'Utilisation 1: Stocker les métadonnées pour chaque entité relation de l'objet: (i) si la relation doit être paresseux-chargé (niveau de classe) et (ii) si lazy-load a eu lieu (au niveau de l'objet). Ensuite, dans la méthode getter, utilisez ces options pour déterminer s'il faut faire paresseux-load et le faire réellement.

cas D'utilisation 2: similaire à Utilisez le cas 1-Faites-le vous-même. Stockez un drapeau sale dans chaque entité. Pour chaque relation d'objet d'entité, stockez un drapeau décrivant si la sauvegarde doit être en cascade. Puis quand une entité est sauvée, visitez récursivement chaque relation "save cascade". Ecrivez toutes les entités sales découvertes.

Modèles

Pros

  • les appels à SQL framework sont simples.

Cons

  • vos objets deviennent plus compliqués. Jetez un coup d'oeil au code D'utilisation des cas 1 et 2 un produit open source. Ce n'est pas anodin
  • manque de support pour le modèle D'objet. Si vous utilisez object model en java pour votre domaine, il aura moins de support pour les opérations de données.
  • risque de fluage et d'anti-patterns de scope: la fonctionnalité manquante ci-dessus est la pointe de l'iceberg. Peut finir par faire quelque réinventer la roue et L'engrenage de L'Infrastructure dans la logique des Affaires.
  • l'Éducation et l'Entretien de la non-solution standard. JPA, JDBC et SQL sont des standards. D'autres cadres ou solutions personnalisées ne le sont pas.

ça vaut le coup???

Cette solution fonctionne bien si vous avez des exigences assez simples de traitement de données et un modèle de données avec un plus petit nombre d'entités:

  • Si oui, génial! Ne ci-dessus.
  • si non, Cette solution est un mauvais ajustement et représente de fausses économies dans l'effort - c.-à-d. finira par ça prend plus de temps et c'est plus compliqué que d'utiliser un ORM. Dans ce cas, jetez un autre coup d'oeil à JPA - il pourrait être plus simple que vous le pensez et il soutient ORM pour CRUD plus SQL brut pour les requêtes compliquées :-).
11
répondu Glen Best 2013-07-30 01:10:22

les dix dernières années, j'ai utilisé JDBC, EJB entité beans, Hibernate, GORM et enfin JPA (dans cet ordre). Pour mon projet actuel, je suis retourné à l'utilisation JDBC simple, parce que l'accent est mis sur la performance. C'est pourquoi je voulais

  • contrôle total sur la génération D'énoncés SQL: pour être en mesure de passer une déclaration à des accordeurs de performance DB, et de mettre la version optimisée dans le programme
  • Full contrôle sur le nombre de déclarations SQL qui sont envoyés à la base de données
  • procédures stockées (triggers), fonctions stockées (pour les calculs complexes dans les requêtes SQL)
  • pour pouvoir utiliser toutes les fonctionnalités SQL disponibles sans restrictions (requêtes récursives avec CTEs, fonctions d'agrégation de fenêtres, ...)

le modèle de données est défini dans un dictionnaire de données; à l'aide d'une approche axée sur le modèle a generator crée des classes d'aide, des scripts DDL, etc. La plupart des opérations sur la base de données sont en lecture seule; seulement quelques cas d'utilisation écrire.

Question 1: aller chercher les enfants

Le système est construit sur un cas d'utilisation, et que nous avons dédiée instruction SQL pour obtenir toutes les données pour un cas d'utilisation/demande. Certains des États SQL sont plus grands que 20kb, ils rejoignent, calculent en utilisant des fonctions stockées écrites en Java/Scala, trier, paginer etc. de manière à ce que le résultat soit directement mappé dans un objet de transfert de données qui, à son tour, est introduit dans la vue (pas de traitement supplémentaire dans la couche application). En conséquence, l'objet de transfert de données est en cas d'utilisation spécifiques. Il ne contient que les données pour le cas d'utilisation (rien de plus, rien de moins).

comme le jeu de résultats est déjà" pleinement joint " il n'y a pas besoin de paresseux/impatient fetching etc. L'objet de transfert de données est terminé. Un cache n'est pas nécessaire (le le cache de la base de données est correct); l'exception: si le jeu de résultats est grand (environ 50 000 lignes), l'objet de transfert de données est utilisé comme valeur de cache.

Question 2: Épargner

après que le contrôleur a fusionné en arrière les changements de L'interface graphique, encore une fois il y a un objet spécifique qui tient les données: fondamentalement les lignes avec un État (Nouveau, supprimé, modifié, ...) dans une hiérarchie. C'est une itération manuelle pour sauvegarder les données dans la hiérarchie: les données nouvelles ou modifiées sont a persisté en utilisant certaines classes d'aide avec les commandes generate SQL insert ou update . Comme pour les éléments supprimés, ceci est optimisé dans les deletes en cascade (PostgreSql). Si plusieurs lignes doivent être supprimées, cela est optimisé en une seule déclaration delete ... where id in ... ainsi.

encore une fois, il s'agit d'un cas d'utilisation spécifique, donc il ne s'agit pas d'une approche générale. Il a besoin de plus de lignes de code, mais ce sont les lignes qui contiennent les optimisations.

le l'expérience

  • il ne faut pas sous-estimer l'effort pour apprendre L'hibernation ou le JPA. Il faut prendre en compte le temps passé à configurer les caches, l'invalidation du cache dans un cluster, la recherche avide/paresseuse, et le réglage ainsi. La migration vers une autre version majeure D'hibernation n'est pas seulement une recompilation.
  • il ne faut pas surestimer l'effort pour construire une application sans ORM.
  • il est plus simple D'utiliser SQL directement-être fermer à SQL (comme HQL, JPQL) est pas le même, surtout si vous parlez à votre accordeur de performance DB
  • les serveurs SQL sont incroyablement rapides lors de l'exécution de requêtes longues et complexes, surtout si elles sont combinées avec des fonctions stockées écrites en Scala
  • même avec les instructions SQL spécifiques au cas d'utilisation, la taille du code est plus petite: les ensembles de résultats" Fully joined " sauvegardent beaucoup de lignes dans le la couche application.

informations connexes:

Mise À Jour: Interfaces

S'il y a une entité Person dans le modèle de données logique, il y a une classe pour persister une entité Person (pour les opérations CRUD), juste comme une entité JPA.

mais le clou est qu'il n'y a pas d'entité unique Person du point de vue de l'utilisation / requête / logique commerciale: chaque méthode de service définit son propre notion d'une Person , et il ne contient que exactement les valeurs exigées par ce cas d'utilisation. Rien de plus, rien de moins. Nous utilisons Scala, de sorte que la définition et l'utilisation de nombreuses petites classes est très efficace (pas de code de chaudière setter/getter requis.)

exemple:

class GlobalPersonUtils {
  public X doSomeProcessingOnAPerson(
    Person person, PersonAddress personAddress, PersonJob personJob, 
    Set<Person> personFriends, ...)
}

est remplacé par

class Person {
  List addresses = ...
  public X doSomeProcessingOnAPerson(...)
}

class Dto {
  List persons = ...
  public X doSomeProcessingOnAllPersons()
  public List getPersons()
}

utilisant le cas d'utilisation spécifique Persons , Adresses etc: dans ce cas, le Person regroupe déjà toutes les données pertinentes. Nécessite plus de classes, mais il n'est pas nécessaire de passer autour des entités JPA.

Notez que ce traitement est en lecture seule, les résultats sont utilisés par la vue. Exemple: obtenir distinct City cas d'une personne de la liste.

si les données sont modifiées, il s'agit d'un autre cas d'utilisation: si la ville d'une personne est modifiée, cela est traité par une méthode de service différente, et la personne est récupérée à nouveau à partir de la base de données.

7
répondu Beryllium 2017-05-23 11:54:40

Avertissement: Ceci est un autre plug sans vergogne à partir d'un projet d'auteur.

Check out JSimpleDB et voir si elle répond à vos critères de simplicité / puissance. Il s'agit d'un nouveau projet né de plus de 10 ans de frustration essayant de traiter la persistance Java via SQL et ORM.

il fonctionne sur le dessus de n'importe quelle base de données qui peut fonctionner comme un magasin de clé/valeur, par exemple, FoundationDB (ou N'importe quelle base de données SQL, bien que tout sera bloqué dans une seule table clé/valeur).

2
répondu Archie 2014-04-22 15:57:31

Il semble que la question centrale dans la question, avec le modèle relationnel lui-même. À partir de ce qui a été décrit, une base de données de graphiques va cartographier le domaine de problème très clairement. Comme alternative, les magasins de documents sont une autre façon d'aborder le problème parce que, alors que l'impédance est encore là, les documents en général sont plus simples à raisonner que les ensembles. Bien sûr, toute approche va avoir ses propres particularités.

1
répondu CurtainDog 2013-08-03 04:36:44

puisque vous voulez une bibliothèque simple et légère et D'utiliser SQL, je peux vous suggérer de jeter un oeil à fjorm . Il vous permet D'utiliser POJOs et CRUD opérations sans beaucoup d'effort.

avertissement: je suis un auteur du projet.

0
répondu Mladen Adamovic 2013-09-13 20:24:06