Chociaż jest to stary wątek, chcę podzielić się moim rozwiązaniem i mam nadzieję, że otrzymam na ten temat opinię. Ostrzegam, że testowałem to rozwiązanie tylko z moją lokalną bazą danych w jakimś przypadku testowym JUnit. Jak dotąd nie jest to wydajna funkcja.
Rozwiązałem ten problem, wprowadzając niestandardową adnotację o nazwie Sekwencja bez właściwości. To tylko znacznik dla pól, którym należy przypisać wartość z sekwencji zwiększonej.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Używając tej adnotacji, oznaczyłem moje encje.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Aby zachować niezależność bazy danych rzeczy, wprowadziłem jednostkę o nazwie SequenceNumber, która przechowuje bieżącą wartość sekwencji i rozmiar przyrostu. Wybrałem className jako unikalny klucz, więc każda klasa encji otrzyma własną sekwencję.
@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 ....
}
Ostatnim krokiem i najtrudniejszym jest PreInsertListener, który obsługuje przypisanie numeru sekwencji. Zauważ, że użyłem wiosny jako pojemnika na fasolę.
@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;
}
}
}
Jak widać z powyższego kodu, odbiornik użył jednej instancji SequenceNumber na klasę jednostki i rezerwuje kilka numerów sekwencyjnych zdefiniowanych przez jednostkę IncrementValue jednostki SequenceNumber. Jeśli zabraknie numerów sekwencyjnych, ładuje jednostkę SequenceNumber dla klasy docelowej i rezerwuje wartości IncrementValue dla następnych wywołań. W ten sposób nie muszę przesyłać zapytań do bazy danych za każdym razem, gdy potrzebna jest wartość sekwencji. Zwróć uwagę na StatelessSession, który jest otwierany w celu zarezerwowania następnego zestawu numerów sekwencyjnych. Nie można użyć tej samej sesji, w której jednostka docelowa jest obecnie utrwalana, ponieważ spowodowałoby to ConcurrentModificationException w EntityPersister.
Mam nadzieję, że to komuś pomoże.