到目前为止我们有了一套从Object自动生成Map<String,Object>的工具,下面要作的就是把这个Map中的信息给持久化了,依照不同的持久化方式和后继对删除对象的查询方式,这又有着不同的solution。比如持久化到文件系统和数据库,显然要用不同的方式。
目前这个系统采用的是数据库,OR-Mapping用的是Toplink。对于删除的数据,在前台有能够查询的界面,可供业务人员查询。针对这样的需求,需要解决以下的几个问题。
首先,在何处获得被删除的对象?BaseDao.deleteObject(Object obj)似乎是一个不错的入口,可惜的是,Toplink对于private owner的对象会自动删除,而service developer不会向下面这样去删除一个对象
//如果想在BaseDao.deleteObject(obj)中捕获删除对象, //那就要求service这样去删除一个具有private owner 属性的Order for (Item item: order.getItems()){ getBaseDao().deleteObject(item); } getBaseDao().deleteObject(order);
所以,为了透明地得到所有Toplink实际删除了的对象,要在每个UnitOfWork生成时要注册一个Toplink的SessionEventListener
import java.util.Date; import java.util.Enumeration; import java.util.Map; import oracle.toplink.internal.helper.IdentityHashtable; import oracle.toplink.publicinterface.UnitOfWork; import oracle.toplink.sessions.SessionEvent; import oracle.toplink.sessions.SessionEventAdapter; public class RecordListener extends SessionEventAdapter { protected static final FWIIntegrator integrator = FWIntegratorFactory.getIntegrator(); @Override public void postCommitUnitOfWork(SessionEvent event) { try { UnitOfWork unitOfWork = (UnitOfWork) event.getSession(); UnitOfWork uow4DeletionRecord = null; IdentityHashtable objectsDeletedDuringCommit = unitOfWork.getObjectsDeletedDuringCommit(); for (Enumeration<?> e = objectsDeletedDuringCommit.keys(); e.hasMoreElements();) { Object key = e.nextElement(); RecordUtils recordUtils = RecordUtils.instance(); Map<String, Object> content = recordUtils.toContent(key); if (content != null) { // since deleted objects are detected in post commit callback // we have to acquire another UOW to write DeletionRecord if (uow4DeletionRecord == null) { uow4DeletionRecord = ParamUtils.getDataSource().getSession().acquireUnitOfWork(); } DeletionRecord deleteRecord = recordUtils.toDeleteRecord(content); deleteRecord.setOperatingTime(new Date()); uow4DeletionRecord.assignSequenceNumber(deleteRecord); uow4DeletionRecord.registerNewObject(deleteRecord); } } if (uow4DeletionRecord != null) { uow4DeletionRecord.commit(); uow4DeletionRecord.release(); } } catch (Exception e) { // You may log it by yourself } } }
private void initSessionAndUnitOfWork(ServerSession serverSession) { clientSession = serverSession.acquireClientSession(); uw = clientSession.acquireUnitOfWork(); addListenerToUnitOfWork(uw); } private void addListenerToUnitOfWork(UnitOfWork uw) { uw.getEventManager().addListener(new RecordListener()); }
这个Listener的作用时间是postCommitUnitOfWork,目的是保证UnitOfWork.getObjectsDeletedDuringCommit()有值。当然,由于原有的UnitOfWork已经commit了,所以得新起一个UnitOfWork来写这些DeletionRecord对象
DeletionRecord没有什么特别的东西,它包含一些查询所用到的warehouse,owner,location之类的业务字段(这些字段在数据库中会作索引,同时含有一个content字段,其中存储的是从那个Map转换过来的XML
public class DeletionRecord extends OidEnabledComponent { private static interface DeletionRecordSetterCallback { void setValue(DeletionRecord obj, Object value); } public static enum FieldType { CLASS_NAME(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setClassName((String) value); } }), WAREHOUSE_ID(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setWarehouseId((String) value); } }), OWNER_ID(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setOwnerId((String) value); } }), LOCATION_ID(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setLocationId((String) value); } }), ITEM_NO(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setItemNo((String) value); } }), ITEM_DESCRIPTION(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setItemDescription((String) value); } }), BIZ_TYPE(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setBizType((Long) value); } }), ASN_NO(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setAsnNo((String) value); } }), RECEIVE_DATE(new DeletionRecordSetterCallback() { public void setValue(DeletionRecord obj, Object value) { obj.setReceiveDate((Date) value); } }); private DeletionRecordSetterCallback setterCallback; FieldType(DeletionRecordSetterCallback setterCallback) { this.setterCallback = setterCallback; }; public void setValue(DeletionRecord obj, Object value) { this.setterCallback.setValue(obj, value); } } private String className; private String warehouseId; private String ownerId; private String locationId; private String itemNo; private String itemDescription; private Long bizType; private String asnNo; private Date receiveDate; private String operatorId; private Date operatingTime; private String content; public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getWarehouseId() { return warehouseId; } public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; } public String getOwnerId() { return ownerId; } public void setOwnerId(String ownerId) { this.ownerId = ownerId; } public String getLocationId() { return locationId; } public void setLocationId(String locationId) { this.locationId = locationId; } public String getItemNo() { return itemNo; } public void setItemNo(String id) { this.itemNo = id; } public String getItemDescription() { return itemDescription; } public void setItemDescription(String name) { this.itemDescription = name; } public Long getBizType() { return bizType; } public void setBizType(Long bizType) { this.bizType = bizType; } public String getAsnNo() { return asnNo; } public void setAsnNo(String asnNo) { this.asnNo = asnNo; } public Date getReceiveDate() { return receiveDate; } public void setReceiveDate(Date receiveDate) { this.receiveDate = receiveDate; } public String getOperatorId() { return operatorId; } public void setOperatorId(String operatorId) { this.operatorId = operatorId; } public Date getOperatingTime() { return operatingTime; } public void setOperatingTime(Date operatingTime) { this.operatingTime = operatingTime; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
需要注意的是,这个转换出来的deletionRecord不是在原有的UnitOfWork中写入的,所以那一段专门去catch了Exception。这是基于以下想法:即使自动备份失败了,也不应该影响正常的删除逻辑。如果认为自动备份失败就不能继续往下作的话,这个catch是要去掉的。
至此,一个自动备份删除对象的简易框架就搭好了。总结一下,核心思想包括以下几点
1)通过Annotation和interface,使得一个对象能够在被删除时指出自己的哪些信息需要被记录
2)通过SessionEventListen得到所有被删除的对象,保持对使用者的透明
3)把结果信息分为两部分,需要查询的用单独的字段记录做索引,其余信息用一个单层的XML保存。
再深入考虑的话,这个框架可以有以下的改进:
一是用XML指定要删除的对象和属性,作到对原始文件的完全无侵入;二是持久化到数据库外其他的媒介如文件系统、网络等,这就可以抽象出一个Appender interface,再实现FileAppender, DBApplender等对象;最后如果性能有需要,可以把持久化被删除对象和删除业务对象的逻辑做成异步的,这样删除N个对象后才持久化一次删除动作,能够减少业务逻辑的延迟。