Programmazione dell'estensione delle definizioni di tabella / entità in Hibernate in primavera, come?

7

Vorrei sapere se c'è un modo - forse con AOP - per estendere le definizioni di tabelle / entità in modo che io possa aggiungere funzionalità su tutta la linea che richiede persistenza.

Lascia che ti spieghi cosa voglio fare con un esempio concreto. Supponiamo di avere un semplice sistema di e-commerce:

  • Utente
    • id
    • nome
    • Password
  • Articolo
    • id
    • nome
    • prezzo
    • Immagine
  • ShoppingCart
    • id
    • user_id
  • ShoppingCartItems
    • id
    • shopping_cart_id
    • item_id
  • Ordinare
    • id
    • shopping_cart_id
  • Acquisto
    • id
    • order_id
    • INVOICE_NUMBER

Ora, supponiamo di pensare:

hey, wouldn't it be great if we could add a field to timestamp all entities?

Ora sapresti cose come:

  • Quando un utente si è registrato
  • Quando è stato creato un elemento
  • Quando è stato creato un carrello acquisti
  • Quando un articolo è stato aggiunto a un carrello
  • Quando è stato effettuato un ordine
  • Quando è stato elaborato e pagato l'ordine

Ora hai un'altra grande idea:

What if there was a status field for all entities so I could soft-delete or archive items, etc.

E ancora un'altra idea:

What if there was a text field called meta to serialize unimportant data, but that could help adding functionality without painful database modifications.

Ci sono soluzioni che posso pensare (soddisfare i requisiti di integrità relazionale):

  1. Ovviamente, devi tornare indietro, modificare le tabelle, modificare le implementazioni in modo che durante la creazione / aggiornamento tu salvi il timestamp dell'operazione allora, lo stesso per status , lo stesso per meta .

  2. E un modo un po 'contorto ma molto modulare. Crea una sorta di plugin, che crea una tabella che gestisce il campo e 1 pivot per ogni entità. E attraverso eventi, associare i dati estesi alle entità esistenti di conseguenza.

Ma se ... potresti semplicemente dire a Hibernate di estendere le definizioni della tabella in fase di programmazione e aggiungere i comportamenti di persistenza attraverso qualcosa come AOP per iniettare funzionalità.

In realtà ho fatto la prima cosa (estendendo progressivamente le definizioni delle tabelle al di fuori dell'implementazione esistente) con successo in Doctrine una volta, ma sto appena iniziando a imparare Hibernate. OTOH, sto appena iniziando ad apprendere Spring e conosco le basi su AOP, quindi almeno nella mia mente è possibile intercettare il processo di configurazione di Hibernate dove capisce le definizioni delle tabelle.

Quindi, questo è possibile con Spring e Hibernate ?, come ?, puoi fornire un esempio?

Addendum : il problema è che ho bisogno di farlo più di una volta e potrebbe provenire da qualsiasi luogo. AOP è stata la mia risposta al problema dell'ereditarietà multipla poiché estendere le lezioni sarebbe stato troppo limitato, tuttavia, in passato l'ho fatto con Events e ho funzionato altrettanto bene ... IIRC il problema è che ho dovuto hackerare Doctrine per fare questo per attivare un evento nel momento in cui la descrizione del modello veniva convertita da file di configurazione in modelli reali.

AOP non è un must, è solo un'idea per un approccio se tale meccanismo di iniezione di eventi o dipendenze non è già in atto. Un altro approccio potrebbe essere mixin, ma torno ad AOP come probabilmente la soluzione più elegante.

Il requisito principale qui è quello di non modificare il codice del modello originale.

Disclaimer : non sono un esperto di AOP, ma sono abbastanza familiare ai concetti.

    
posta dukeofgaming 07.12.2014 - 07:34
fonte

4 risposte

1

Per quello che posso dire, ciò che stai descrivendo può essere banalmente implementato con una classe Embeddable e senza bisogno di AOP.

@Embeddable
class Audit {
  Timestamp created;
  String createdBy;
  String updatedBy;
}

Quindi puoi semplicemente aggiungere il tuo oggetto incorporabile all'interno delle tue entità.

@Entity
class Order {
   @Embedded
   Audit audit;
}

Utilizzando l'ereditarietà, potresti anche renderlo disponibile per un insieme di entità:

@MappedSuperClass
class MotherOfAllEntities {
   @Embedded
   Audit audit;
}

@Entity
class ChildClass extends MotherOfAllEntities {
}

Se le informazioni aggiunte sono solo informazioni di controllo, come sembra suggerire nel tuo post potresti prendere in considerazione l'utilizzo di qualcosa come Hibernate Envers che sembra essere un'implementazione di ciò che apparentemente suggerisci con la tua domanda

@Entity
@Audited
class Order {
}

E Envers si assicurerà di archiviare le informazioni di controllo sull'entità in qualche altra struttura di dati.

Poiché Envers sembra essere un'implementazione di qualcosa di simile a quello che vuoi fare da zero, potresti anche dare un'occhiata al loro codice e vedere come lo hanno fatto, ma ancora una volta, se è quello di implementare cosa hai descritto, a me sembra di uccidere una mosca con un bazooka.

    
risposta data 08.01.2015 - 15:07
fonte
1

Hibernate ha eventi a cui puoi reagire usando intercettori . Ecco un elenco incompleto di eventi:

  • onDelete
  • onSave
  • instantiate
  • afterTransactionCompletion

Dai un'occhiata a org.hibernate.Interceptor per vedere la lista completa degli eventi a cui puoi reagire usando Interceptor.

L'esempio semplificato di utilizzo classico è l'intercettore di auditing che imposta "lastmodified":

public class AuditInterceptor extends EmptyInterceptor {
    @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException {
        if (entity instanceof AuditedEntity) {
            int index = findElementIndex(propertyNames, "dateModified");

            if (index >= 0) {
                state[index] = new Date();

                return true; // entity has been modified
            }
        }

        return false; // entity was not modified
    }

    private int findElementIndex(String[] propertyNames, String element) {
        for (int i = 0; i < propertyNames.length; i++) {
            if (element.equals(propertyNames[i])) {
                return i;
            }
        }

        return -1;
    }
}

Gli intercettori potrebbero non essere sufficienti per le tue esigenze. Fortunatamente l'architettura di Hibernate è abbastanza collegabile - la maggior parte delle funzionalità è costituita da " Servizi ", risp. implementazioni di servizi che implementano ruoli di servizio (interfacce). È possibile sostituire le implementazioni predefinite con quelle personalizzate o persino aggiungere servizi totalmente nuovi. Ma questo potrebbe essere più coinvolto e potrebbe rompersi nelle versioni future di Hibernate. Consiglierei di restare con gli intercettori il più possibile.

    
risposta data 11.05.2015 - 08:40
fonte
1

Ho qualche problema a capire qual è il tuo caso d'uso. Non ti viene in mente una domanda, ti viene in mente una soluzione.

La tua domanda sembra essere circa metadata e dove memorizzarla.

You would now know things like:

  • Quando un utente si è registrato
  • Quando è stato creato un elemento
  • Quando è stato creato un carrello acquisti
  • Quando un articolo è stato aggiunto a un carrello
  • Quando è stato effettuato un ordine
  • Quando è stato elaborato e pagato l'ordine

A seconda del caso d'uso, è perfettamente logico memorizzare i metadati accanto ai dati aziendali. Devi modificare lo schema del tuo database di conseguenza.

I) Il caso d'uso semplice e diretto

Da ciò sarebbe naturale estendere un BaseItem, che ha informazioni come created , createdby , modified , modifiedby e aggiungere alcune colonne ad altri oggetti come lastseen sull'utente. Con questo sei in grado di ricavare alcune domande da questi valori:

When an item was added to a shopping cart

Quando hai order e orderposition la created di orderposition ti dice quando la voce era added .

Non vedo un motivo per utilizzare AOP per questo caso d'uso.

II) I casi più avanzati di Datamining e Monitoring

II a) Monitoraggio

Let's suppose we have a simple ecommerce system

Supponiamo che tu abbia un sistema di eCommerce e non solo per l'esempio, potresti essere interessato al monitoraggio come AppDynamics o Nuova reliquia o qualcosa del genere.

Con una strumentazione così sofisticata, sei in grado di monitorare ogni punto della tua applicazione. Non posso parlare per New Relic, ma ho usato AppDynamics per me stesso. Con AppDynamics puoi inserire Information Points , in cui puoi tracciare ogni volta che viene chiamato un metodo, ad es. orderSubmit .

Quando il tuo caso è più di tracciare il comportamento dei sistemi in caso di miglioramento delle prestazioni o di contatori semplici, sarebbe un modo semplice per andare, senza toccare alcun codice.

Ma il punto principale di tali sistemi sta fornendo informazioni di health per la tua applicazione. e lo consiglierei comunque se gestisci un'attività di e-commerce. I tempi di fermo sono una perdita di denaro.

II b) Data mining

Se sei interessato ai metadati per il datamining puroses IIa è la strada sbagliata. A seconda della scala dei metadati che vuoi raccogliere, dovresti metterli su un datastore separato.

Come già accennato, ci sono diversi modi per collegare la tua applicazione:

  • modifica semplicemente il codice aziendale e aggiungi una semplice methodcall al sistema esterno.

Pro: è semplice e chiaro. Tutti sanno, cosa stai facendo

Contro: rifiuti la tua base di codice

  • utilizzando AOP, come suggerito

Pro: è un approccio sofisticato per affrontare problemi trasversali. Nessun codice esistente deve essere toccato

Con: Rende il codice difficile da capire per i programmatori inesperti - le cose sembrano accadere magicamente .

C'è qualcosa in mezzo.

Per semplicità e leggibilità, opterei per la prima soluzione nonostante il fatto che tu abbia un altro tipo di "registrazione"

P.S.: Niente ti impedisce di fare I) e II) tutto insieme:]

    
risposta data 04.06.2016 - 18:54
fonte
1

Non inventare più la ruota!

Hibernate ha dei plug-in, usali! Potresti essere interrotto in Enver and Auditing .

Questo è un esempio di ciò che stai cercando:

AuditReader reader = AuditReaderFactory.get(entityManager);

Person person2_rev1 = reader.find(Person.class, person2.getId(), 1); 
                            // version 1 is from archive
assert person2_rev1.getAddress().equals(new Address("Grimmauld Place", 12));

Address address1_rev1 = reader.find(Address.class, address1.getId(), 1);
assert address1_rev1.getPersons().getSize() == 1;
    
risposta data 04.06.2016 - 20:59
fonte

Leggi altre domande sui tag