Domanda

È possibile utilizzare una sequenza DB per alcune colonne che non è l'identificatore / non fa parte di un identificatore composito ?

Sto usando l'ibernazione come provider jpa e ho una tabella con alcune colonne che generano valori (usando una sequenza), sebbene non facciano parte dell'identificatore.

Quello che voglio è usare una sequenza per creare un nuovo valore per un'entità, in cui la colonna per la sequenza è NON (parte di) la chiave primaria:

@Entity
@Table(name = "MyTable")
public class MyEntity {

    //...
    @Id //... etc
    public Long getId() {
        return id;
    }

   //note NO @Id here! but this doesn't work...
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
    @SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
    @Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
    public Long getMySequencedValue(){
      return myVal;
    }

}

Quindi quando lo faccio:

em.persist(new MyEntity());

l'id verrà generato, ma la proprietà mySequenceVal verrà generata anche dal mio provider JPA.

Giusto per chiarire le cose: voglio che Sospendi generi il valore per la proprietà mySequencedValue . So che Hibernate può gestire valori generati dal database, ma non voglio usare un trigger o qualsiasi altra cosa diversa da Hibernate per generare il valore per la mia proprietà. Se Hibernate può generare valori per le chiavi primarie, perché non può generare per una proprietà semplice?

È stato utile?

Soluzione

In cerca di risposte a questo problema, mi sono imbattuto in questo link

Sembra che Hibernate / JPA non sia in grado di creare automaticamente un valore per le proprietà non id. L'annotazione @GeneratedValue viene utilizzata solo insieme a @Id per creare numeri automatici.

L'annotazione @GeneratedValue dice a Hibernate che il database sta generando questo valore stesso.

La soluzione (o soluzione alternativa) suggerita in quel forum è quella di creare un'entità separata con un ID generato, qualcosa del genere:

@Entity
public class GeneralSequenceNumber {
  @Id
  @GeneratedValue(...)
  private Long number;
}

@Entity 
public class MyEntity {
  @Id ..
  private Long id;

  @OneToOne(...)
  private GeneralSequnceNumber myVal;
}

Altri suggerimenti

Ho scoperto che @Column (columnDefinition = " serial ") funziona perfettamente ma solo per PostgreSQL. Per me questa è stata la soluzione perfetta, perché la seconda entità è "brutta" opzione.

So che questa è una domanda molto vecchia, ma è mostrata in primo luogo sui risultati e jpa è cambiata molto dalla domanda.

Il modo giusto per farlo ora è con l'annotazione @Generated . Puoi definire la sequenza, impostare il valore predefinito nella colonna su quella sequenza e quindi mappare la colonna come:

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)

Hibernate lo supporta sicuramente. Dai documenti:

" Le proprietà generate sono proprietà che hanno i loro valori generati dal database. In genere, le applicazioni di ibernazione dovevano aggiornare gli oggetti che contengono proprietà per le quali il database generava valori. Contrassegnare le proprietà come generate, tuttavia, consente all'applicazione di delegare questa responsabilità a Hibernate. In sostanza, ogni volta che Hibernate emette un INSERT SQL o un AGGIORNAMENTO per un'entità che ha definito le proprietà generate, emette immediatamente una selezione in seguito per recuperare i valori generati. & Quot;

Per le proprietà generate solo su insert, la mappatura delle proprietà (.hbm.xml) sarebbe simile a:

<property name="foo" generated="insert"/>

Per le proprietà generate al momento dell'inserimento e dell'aggiornamento della mappatura delle proprietà (.hbm.xml) sarebbe simile a:

<property name="foo" generated="always"/>

Sfortunatamente, non conosco l'APP, quindi non so se questa funzione è esposta tramite l'APP (sospetto che possibilmente no)

In alternativa, dovresti essere in grado di escludere la proprietà da inserimenti e aggiornamenti, quindi " manualmente " chiama session.refresh (obj); dopo averlo inserito / aggiornato per caricare il valore generato dal database.

Questo è il modo in cui si escluderebbe la proprietà dall'uso nelle istruzioni di inserimento e aggiornamento:

<property name="foo" update="false" insert="false"/>

Ancora una volta, non so se JPA esponga queste funzionalità di Hibernate, ma Hibernate le supporta.

Come follow-up ecco come l'ho fatto funzionare:

@Override public Long getNextExternalId() {
    BigDecimal seq =
        (BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0);
    return seq.longValue();
}

Anche se questo è un vecchio thread, voglio condividere la mia soluzione e spero di ricevere un feedback su questo. Tieni presente che ho provato questa soluzione solo con il mio database locale in alcuni testcase di JUnit. Quindi questa non è una caratteristica produttiva finora.

Ho risolto questo problema introducendo un'annotazione personalizzata chiamata Sequence senza proprietà. È solo un indicatore per i campi a cui dovrebbe essere assegnato un valore da una sequenza incrementata.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}

Usando questa annotazione ho segnato le mie entità.

public class Area extends BaseEntity implements ClientAware, IssuerAware
{
    @Column(name = "areaNumber", updatable = false)
    @Sequence
    private Integer areaNumber;
....
}

Per mantenere il database delle cose indipendente ho introdotto un'entità chiamata SequenceNumber che contiene il valore corrente della sequenza e la dimensione dell'incremento. Ho scelto className come chiave univoca in modo che ogni classe di entità ottenga la propria sequenza.

@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
    @Id
    @Column(name = "className", updatable = false)
    private String className;

    @Column(name = "nextValue")
    private Integer nextValue = 1;

    @Column(name = "incrementValue")
    private Integer incrementValue = 10;

    ... some getters and setters ....
}

L'ultimo passo e il più difficile è un PreInsertListener che gestisce l'assegnazione del numero di sequenza. Nota che ho usato la primavera come contenitore di fagioli.

@Component
public class SequenceListener implements PreInsertEventListener
{
    private static final long serialVersionUID = 7946581162328559098L;
    private final static Logger log = Logger.getLogger(SequenceListener.class);

    @Autowired
    private SessionFactoryImplementor sessionFactoryImpl;

    private final Map<String, CacheEntry> cache = new HashMap<>();

    @PostConstruct
    public void selfRegister()
    {
        // As you might expect, an EventListenerRegistry is the place with which event listeners are registered
        // It is a service so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);

        // add the listener to the end of the listener chain
        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
    }

    @Override
    public boolean onPreInsert(PreInsertEvent p_event)
    {
        updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());

        return false;
    }

    private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
    {
        try
        {
            List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);

            if (!fields.isEmpty())
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Intercepted custom sequence entity.");
                }

                for (Field field : fields)
                {
                    Integer value = getSequenceNumber(p_entity.getClass().getName());

                    field.setAccessible(true);
                    field.set(p_entity, value);
                    setPropertyState(p_state, p_propertyNames, field.getName(), value);

                    if (log.isDebugEnabled())
                    {
                        LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.error("Failed to set sequence property.", e);
        }
    }

    private Integer getSequenceNumber(String p_className)
    {
        synchronized (cache)
        {
            CacheEntry current = cache.get(p_className);

            // not in cache yet => load from database
            if ((current == null) || current.isEmpty())
            {
                boolean insert = false;
                StatelessSession session = sessionFactoryImpl.openStatelessSession();
                session.beginTransaction();

                SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);

                // not in database yet => create new sequence
                if (sequenceNumber == null)
                {
                    sequenceNumber = new SequenceNumber();
                    sequenceNumber.setClassName(p_className);
                    insert = true;
                }

                current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
                cache.put(p_className, current);
                sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());

                if (insert)
                {
                    session.insert(sequenceNumber);
                }
                else
                {
                    session.update(sequenceNumber);
                }
                session.getTransaction().commit();
                session.close();
            }

            return current.next();
        }
    }

    private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
    {
        for (int i = 0; i < propertyNames.length; i++)
        {
            if (propertyName.equals(propertyNames[i]))
            {
                propertyStates[i] = propertyState;
                return;
            }
        }
    }

    private static class CacheEntry
    {
        private int current;
        private final int limit;

        public CacheEntry(final int p_limit, final int p_current)
        {
            current = p_current;
            limit = p_limit;
        }

        public Integer next()
        {
            return current++;
        }

        public boolean isEmpty()
        {
            return current >= limit;
        }
    }
}

Come puoi vedere dal codice sopra, il listener ha usato un'istanza SequenceNumber per classe di entità e riserva un paio di numeri di sequenza definiti dal valore incrementale dell'entità SequenceNumber. Se esaurisce i numeri di sequenza, carica l'entità SequenceNumber per la classe di destinazione e riserva i valori incrementValue per le chiamate successive. In questo modo non ho bisogno di interrogare il database ogni volta che è necessario un valore di sequenza. Si noti la StatelessSession che si sta aprendo per riservare la serie successiva di numeri di sequenza. Non è possibile utilizzare la stessa sessione in cui l'entità target è attualmente persistente poiché ciò comporterebbe una ConcurrentModificationException in EntityPersister.

Spero che questo aiuti qualcuno.

Ho corretto la generazione di UUID (o sequenze) con Hibernate usando l'annotazione @PrePersist :

@PrePersist
public void initializeUUID() {
    if (uuid == null) {
        uuid = UUID.randomUUID().toString();
    }
}

Corro nella stessa situazione come te e non ho trovato alcuna risposta seria se è fondamentalmente possibile generare proprietà non id con JPA o meno.

La mia soluzione è quella di chiamare la sequenza con una query JPA nativa per impostare la proprietà a mano prima di perseguitarla.

Questo non è soddisfacente, ma per il momento funziona come una soluzione alternativa.

Mario

Ho trovato questa nota specifica nella sessione 9.1.9 GeneratedValue Annotation dalla specifica JPA: " [43] Le applicazioni portatili non devono usare l'annotazione GeneratedValue su altri campi o proprietà persistenti. " Quindi, presumo che non sia possibile generare automaticamente valore per valori di chiave non primaria almeno usando semplicemente JPA.

Se stai usando postgresql
E sto usando in avvio a molla 1.5.6

@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;

" Non voglio usare un trigger o qualsiasi altra cosa diversa da Hibernate per generare il valore per la mia proprietà "

In tal caso, che ne dici di creare un'implementazione di UserType che generi il valore richiesto e di configurare i metadati per usare quel UserType per la persistenza della proprietà mySequenceVal?

Non è lo stesso che usare una sequenza. Quando si utilizza una sequenza, non si sta inserendo o aggiornando nulla. Stai semplicemente recuperando il valore della sequenza successiva. Sembra che l'ibernazione non lo supporti.

Sembra che il thread sia vecchio, volevo solo aggiungere la mia soluzione qui (utilizzando AspectJ - AOP in primavera).

La soluzione è creare un'annotazione personalizzata @InjectSequenceValue come segue.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue {
    String sequencename();
}

Ora puoi annotare qualsiasi campo nell'entità, in modo che il valore del campo sottostante (Long / Integer) venga iniettato in fase di esecuzione usando il valore successivo della sequenza.

Annota in questo modo.

//serialNumber will be injected dynamically, with the next value of the serialnum_sequence.
 @InjectSequenceValue(sequencename = "serialnum_sequence") 
  Long serialNumber;

Finora abbiamo contrassegnato il campo di cui abbiamo bisogno per iniettare il valore della sequenza, quindi vedremo come iniettare il valore della sequenza nei campi contrassegnati, questo è fatto creando il taglio del punto in AspectJ.

Attiveremo l'iniezione poco prima dell'esecuzione del metodo save / persist , fatto nella classe seguente.

@Aspect
@Configuration
public class AspectDefinition {

    @Autowired
    JdbcTemplate jdbcTemplate;


    //@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save())
    @Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA.
    public void generateSequence(JoinPoint joinPoint){

        Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save
        for (Object arg :aragumentList ) {
            if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class

                Field[] fields = arg.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields

                        field.setAccessible(true); 
                        try {
                            if (field.get(arg) == null){ // Setting the next value
                                String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename();
                                long nextval=getNextValue(sequenceName);
                                System.out.println("Next value :"+nextval); //TODO remove sout.
                                field.set(arg, nextval);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        }
    }

    /**
     * This method fetches the next value from sequence
     * @param sequence
     * @return
     */

    public long getNextValue(String sequence){
        long sequenceNextVal=0L;

        SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL");
        while (sqlRowSet.next()){
            sequenceNextVal=sqlRowSet.getLong("value");

        }
        return  sequenceNextVal;
    }
}

Ora puoi annotare qualsiasi Entità come di seguito.

@Entity
@Table(name = "T_USER")
public class UserEntity {

    @Id
    @SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq")
    Long id;
    String userName;
    String password;

    @InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving.
    Long serialNumber;

    String name;
}

Sono stato in una situazione come te (sequenza JPA / Hibernate per campo non @Id) e ho finito per creare un trigger nel mio schema db che aggiunge un numero di sequenza univoco all'insert. Non l'ho mai fatto funzionare con JPA / Hibernate

Dopo aver trascorso ore, questo mi ha aiutato a risolvere il mio problema:

Per Oracle 12c:

ID NUMBER GENERATED as IDENTITY

Per H2:

ID BIGINT GENERATED as auto_increment

Crea anche:

@Column(insertable = false)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top