Bien que ce soit un vieux thread, je veux partager ma solution et espère avoir quelques commentaires sur ce. Soyez averti que je l'ai testé cette solution avec ma base de données locale dans certains cas de test JUnit. Ce n'est donc pas une fonction productive jusqu'à présent.
J'ai résolu le problème pour ma par l'introduction d'une annotation personnalisée appelée Séquence ayant aucun bien. C'est juste un repère pour les champs qui doivent être affectés d'une valeur d'une incrémenté de la séquence.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
À l'aide de cette annotation j'ai marqué mes entités.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Pour garder les choses de base de données indépendant, j'ai introduit une entité appelée SequenceNumber qui détient la séquence actuelle de la valeur et de la taille de l'incrément. J'ai choisi celui de la classe unique de la clé, de sorte que chaque classe d'entité recevra sa propre séquence.
@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 ....
}
La dernière étape et la plus difficile, c'est un PreInsertListener qui gère le numéro d'ordre d'affectation. Notez que j'ai utilisé le printemps comme récipient à grains.
@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;
}
}
}
Comme vous pouvez le voir dans le code ci-dessus le port d'écoute utilisé un SequenceNumber instance par classe d'entité et se réserve un couple de numéros de séquence définie par le incrementValue de la SequenceNumber entité. Si il manque des numéros de séquence, il charge le SequenceNumber entité de la classe cible et se réserve incrementValue les valeurs pour les appels. De cette façon, je n'ai pas besoin d'interroger la base de données chaque fois qu'une séquence de valeur est nécessaire.
Remarque le statelesssession n'est ouvert pour la réservation de la prochaine série de numéros de séquence. Vous ne pouvez pas utiliser la même session de l'entité cible est actuellement persisté car cela conduirait à une ConcurrentModificationException dans le EntityPersister.
Espérons que cela aide quelqu'un.