Obwohl dies ein alter Thread ist, möchte ich meine Lösung teilen und hoffentlich ein Feedback dazu erhalten. Seien Sie gewarnt, dass ich diese Lösung nur mit meiner lokalen Datenbank in einem JUnit-Testfall getestet habe. Dies ist also bisher keine produktive Funktion.
Ich habe dieses Problem für mich gelöst, indem ich eine benutzerdefinierte Annotation namens Sequence ohne Eigenschaft eingeführt habe. Es ist nur eine Markierung für Felder, denen ein Wert aus einer inkrementierten Sequenz zugewiesen werden soll.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Mit dieser Anmerkung habe ich meine Entitäten markiert.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Um die Datenbank unabhängig zu halten, habe ich eine Entität namens SequenceNumber eingeführt, die den aktuellen Sequenzwert und die Inkrementgröße enthält. Ich habe den Klassennamen als eindeutigen Schlüssel ausgewählt, damit jede Entitätsklasse ihre eigene Sequenz erhält.
@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 ....
}
Der letzte und schwierigste Schritt ist ein PreInsertListener, der die Zuweisung der Sequenznummer übernimmt. Beachten Sie, dass ich Feder als Bohnenbehälter verwendet habe.
@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;
}
}
}
Wie Sie dem obigen Code entnehmen können, hat der Listener eine SequenceNumber-Instanz pro Entitätsklasse verwendet und reserviert einige Sequenznummern, die durch den incrementValue der SequenceNumber-Entität definiert sind. Wenn die Sequenznummern ausgehen, wird die SequenceNumber-Entität für die Zielklasse geladen und inkrementelle Werte für die nächsten Aufrufe reserviert. Auf diese Weise muss ich die Datenbank nicht jedes Mal abfragen, wenn ein Sequenzwert benötigt wird. Beachten Sie die StatelessSession, die geöffnet wird, um den nächsten Satz von Sequenznummern zu reservieren. Sie können nicht dieselbe Sitzung verwenden, in der die Zielentität derzeit beibehalten wird, da dies zu einer ConcurrentModificationException im EntityPersister führen würde.
Hoffe das hilft jemandem.