แม้ว่านี่จะเป็นเธรดเก่าที่ฉันต้องการแชร์โซลูชันของฉันและหวังว่าจะได้รับคำติชมเกี่ยวกับสิ่งนี้ ถูกเตือนว่าฉันทดสอบโซลูชันนี้กับฐานข้อมูลโลคัลของฉันในบาง testcase JUnit เท่านั้น ดังนั้นนี่ไม่ใช่คุณลักษณะที่มีประสิทธิภาพจนถึงตอนนี้
ฉันแก้ไขปัญหานั้นด้วยการแนะนำบันทึกย่อที่กำหนดเองที่เรียกว่าลำดับที่ไม่มีคุณสมบัติ มันเป็นเพียงเครื่องหมายสำหรับเขตข้อมูลที่ควรกำหนดค่าจากลำดับที่เพิ่มขึ้น
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
การใช้คำอธิบายประกอบนี้ฉันทำเครื่องหมายเอนทิตีของฉัน
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
เพื่อรักษาฐานข้อมูลสิ่งต่าง ๆ ฉันแนะนำเอนทิตีที่เรียกว่า SequenceNumber ซึ่งเก็บค่าปัจจุบันตามลำดับและขนาดเพิ่มขึ้น ฉันเลือก className เป็นคีย์เฉพาะเพื่อให้แต่ละคลาสเอนทิตีจะได้รับลำดับของตนเอง
@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 ....
}
ขั้นตอนสุดท้ายและที่ยากที่สุดคือ PreInsertListener ที่จัดการการกำหนดหมายเลขลำดับ โปรดทราบว่าฉันใช้สปริงเป็นที่เก็บถั่ว
@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;
}
}
}
ดังที่คุณเห็นจากโค้ดด้านบนผู้ฟังใช้อินสแตนซ์ SequenceNumber หนึ่งรายการต่อคลาสเอนทิตี้และสำรองหมายเลขลำดับสองชุดที่กำหนดโดย incrementValue ของเอนทิตี SequenceNumber หากจำนวนซีเควนซ์นั้นหมดมันจะโหลดเอนทิตี SequenceNumber สำหรับคลาสเป้าหมายและสำรองค่า incrementValue สำหรับการโทรครั้งต่อไป วิธีนี้ฉันไม่จำเป็นต้องค้นหาฐานข้อมูลทุกครั้งที่ต้องการค่าลำดับ หมายเหตุ StatelessSession ที่กำลังเปิดเพื่อจองชุดหมายเลขลำดับถัดไป คุณไม่สามารถใช้เซสชันเดียวกันกับที่เอนทิตีเป้าหมายยังคงอยู่เนื่องจากจะทำให้ ConcurrentModificationException ใน EntityPersister
หวังว่านี่จะช่วยใครซักคน