JPA e select dentro le insert (atomicità)

31 views
Skip to first unread message

Tiziano Lattisi

unread,
May 23, 2012, 3:40:08 AM5/23/12
to jug...@googlegroups.com
Ciao a tutti,
cerco di spiegarmi meglio con un esempio:

insert into miatabella (miocampo) values ((select max(miocampo) from
miatabella)+1);

In prosa quello che si vuole ottenere è che il nuovo record valorizzi
il campo con il valore
massimo dello stesso campo nella tabella, incrementato di uno.

Lasciando stare il fatto che una cosa del genere magari sarebbe da
implementare con
un contatore, lo si consideri come un esempio generico di inserimento
di un valore dipendente
dallo stato precedente dei record.

In linguaggio sql vengo garantito dell'atomicità dell'operazione, ma
in JPA come si potrebbe
affrontare il problema?

ciao
Tiziano

Simone Bordet

unread,
May 23, 2012, 4:05:35 AM5/23/12
to jug...@googlegroups.com
Ciao,

2012/5/23 Tiziano Lattisi <tiziano...@gmail.com>:
> Ciao a tutti,
> cerco di spiegarmi meglio con un esempio:
>
> insert into miatabella (miocampo) values ((select max(miocampo) from
> miatabella)+1);
>
> In prosa quello che si vuole ottenere è che il nuovo record valorizzi
> il campo con il valore
> massimo dello stesso campo nella tabella, incrementato di uno.
>
> Lasciando stare il fatto che una cosa del genere magari sarebbe da
> implementare con
> un contatore, lo si consideri come un esempio generico di inserimento
> di un valore dipendente
> dallo stato precedente dei record.
>
> In linguaggio sql vengo garantito dell'atomicità dell'operazione

Non è vero.
Con SQL (in read committed) puoi avere 2 righe con lo stesso valore, a
meno che ci sia uno unique constraint sulla colonna.

> ma in JPA come si potrebbe affrontare il problema?

Nello stesso modo in cui devi risolverlo in SQL (perché non è atomico).

Con JPA vedo queste soluzioni:
1. Metti uno unique constraint sulla colonna, e quando ottieni una
eccezione fai il rollback della transazione, ne fai partire un'altra e
riprovi.
2. Acquisisci un lock (per esempio su una riga speciale con primary
key = 0), e poi fai l'operazione di cui sopra.

Per 2 il codice è qualcosa come:

Entity zero = entityManager.find(Entity.class, 0);
entityManager.lock(zero, LockModeType.WRITE);
Entity max = entityManager.createQuery("from Entity e where e.miocampo
= max(e.miocampo)").getSingleResult();
Entity newMax = new Entity(max.miocampo+1);
entityManager.persist(newMax);

Ovviamente devi acquisire il lock in tutti i posti nel codice dove
scrivi sulla tabella, ma se scrivi bene il codice dovrebbe essere solo
1.

Devi fare delle prove però, perché il locking dipende dal DB che usi e
dal supporto che ha per le transazioni (p.e. MySQL con ISAM non ce
l'ha ecc.).
Usa PostgreSQL e non ti sbagli mai.

Simon
--
http://cometd.org
http://intalio.com
http://bordet.blogspot.com
----
Finally, no matter how good the architecture and design are,
to deliver bug-free software with optimal performance and reliability,
the implementation technique must be flawless.   Victoria Livschitz

Tiziano Lattisi

unread,
May 23, 2012, 4:41:32 AM5/23/12
to jug...@googlegroups.com
Il 23 maggio 2012 10:05, Simone Bordet <sbo...@intalio.com> ha scritto:
> Non è vero.
> Con SQL (in read committed) puoi avere 2 righe con lo stesso valore, a
> meno che ci sia uno unique constraint sulla colonna.

Ciao Simone,
davvero?
Ero convinto che questo problema affliggesse le transazioni, ma non un singolo
comando. :-(

Grazie del chiarimento.

> Con JPA vedo queste soluzioni:
> 1. Metti uno unique constraint sulla colonna, e quando ottieni una
> eccezione fai il rollback della transazione, ne fai partire un'altra e
> riprovi.
> 2. Acquisisci un lock (per esempio su una riga speciale con primary
> key = 0), e poi fai l'operazione di cui sopra.

Per la tipologia di cosa che sto facendo, penso che la strada più adeguata
possa essere la prima.

> Usa PostgreSQL e non ti sbagli mai.
>
> Simon

Oh, concordo. :-)

Grazie ancora!
Tiziano

Simone Bordet

unread,
May 23, 2012, 4:57:14 AM5/23/12
to jug...@googlegroups.com
Ciao,

2012/5/23 Tiziano Lattisi <tiziano...@gmail.com>:
> Il 23 maggio 2012 10:05, Simone Bordet <sbo...@intalio.com> ha scritto:
>> Non è vero.
>> Con SQL (in read committed) puoi avere 2 righe con lo stesso valore, a
>> meno che ci sia uno unique constraint sulla colonna.
>
> Ciao Simone,
> davvero?
> Ero convinto che questo problema affliggesse le transazioni, ma non un singolo
> comando. :-(

*Tutti* i comandi SQL vengono eseguiti all'interno di una transazione.
Se non è l'applicazione a farla partire, ci pensa o il driver JDBC,
oppure direttamente il DB.

>> Con JPA vedo queste soluzioni:
>> 1. Metti uno unique constraint sulla colonna, e quando ottieni una
>> eccezione fai il rollback della transazione, ne fai partire un'altra e
>> riprovi.
>> 2. Acquisisci un lock (per esempio su una riga speciale con primary
>> key = 0), e poi fai l'operazione di cui sopra.
>
> Per la tipologia di cosa che sto facendo, penso che la strada più adeguata
> possa essere la prima.

Non è esente da problemi però.
Se il DB è giù, ti becchi lo stesso una eccezione, al che riprovi la
transazione e vai in loop.
Si dovrebbe distinguere quale eccezione è stata sollevata, in questo
caso una violazione di unique constraint.
Purtroppo JPA non ti permette di distinguere il tipo di eccezione,
però il provider JPA può (Hibernate di sicuro, e Spring anche), quindi
devi provare e analizzare che eccezione ti viene ritornata e agire di
conseguenza.

La soluzione 2 è portabile per ogni database fornito di transazioni.

Chris Mair

unread,
May 23, 2012, 4:45:10 PM5/23/12
to jug...@googlegroups.com
>
>> Con JPA vedo queste soluzioni:
>> 1. Metti uno unique constraint sulla colonna, e quando ottieni una
>> eccezione fai il rollback della transazione, ne fai partire un'altra e
>> riprovi.
>> 2. Acquisisci un lock (per esempio su una riga speciale con primary
>> key = 0), e poi fai l'operazione di cui sopra.
>
> Per la tipologia di cosa che sto facendo, penso che la strada più adeguata
> possa essere la prima.
>
>> Usa PostgreSQL e non ti sbagli mai.
>>
>> Simon
>
> Oh, concordo. :-)


Fermi tutti. :)

Cos'e` che l'OP vuole ottenere? Semplicemente generare degli id
artificiali?

La soluzione corretta in SQL standard e` allora quella di creare una
SEQUENCE e fare assegnare in default all'id il prossimo valore della
sequenza.

Bye,
Chris.



Tiziano Lattisi

unread,
May 23, 2012, 5:02:50 PM5/23/12
to jug...@googlegroups.com
Il 23 maggio 2012 22:45, Chris Mair <ch...@1006.org> ha scritto:
> Fermi tutti. :)
>
> Cos'e` che l'OP vuole ottenere? Semplicemente generare degli id
> artificiali?
>
> La soluzione corretta in SQL standard e` allora quella di creare una
> SEQUENCE e fare assegnare in default all'id il prossimo valore della
> sequenza.
>
> Bye,
> Chris.

Ciao Chris,
leggi la prima email, vedrai che è solo a titolo di esempio. ;-)

ciao
t.
Reply all
Reply to author
Forward
0 new messages