数据迁移说白了就是把数据从一个数据仓库搬到另一个数据仓库。但是如果数据仓库之间的结构不一样的话,就会大大增加迁移的难度。一般的迁移工具只能处理表与表之间、字段之间的简单转换,如果遇上了表结构大不相同的数据迁移,那编写合适的迁移程序就相当必要了。
对于一个重构系统而言,由于业务流程改进和代码结构优化,所用及的数据结构也会有很大程度的改变。新系统有些字段可能需要查询旧系统多个表多个字段,经过复杂的逻辑运算后才能确定。如果新旧系统要并行使用一段时间有增量迁移的需求,则更增加了迁移程序的复杂度。不同的系统会有不同的迁移算法,至今也不会有哪一种迁移程序可以涵盖所有的数据迁移需求。本文所列方法只限于新旧系统都是采用hibernate框架进行数据持久化,且新旧系统数据结构有很大不同的情况下使用为佳。
采用hibernate进行数据持久化后,数据仓库之间的数据迁移就转换成了对象之间的迁移,我们所要做的无非就是产生新系统的对象然后把它保存,用对象来处理复杂逻辑一向比用单纯的表字段来得更简洁容易。我们就是要架构这样的一个框架,使开发人员只需关注新旧系统对象间的转化,其它的都交由框架去处理。
因此我们定义了这样一个接口IDataTransferService,该接口提供两个方法,add和update,add只对老系统进行新增操作,update除了新增操作外还会对同一对象进行修改操作。相对对象的确认可以通过编程来判断,默认的是根据id值判断。继承IdataTransferService后就是一个对象转换逻辑类。
IdataTransferService和一个基本实现类BaseServiceImpl,该类实现add和update方法,在这里要注意的是要做分页和transaction处理。4个protected方法,isNeedTransform指示是否要执行transform操作,在特殊情况下,系统只需要执行afterTransform的操作;afterTransform是指执行完transform操作后需要同时进行的一些后续处理;getOldDao指示对旧系统进行数据操作的DAO;getNewDao提示对新系统进行数据操作的DAO;transform是最主要的业务方法,处理新旧系统的对象转换。
HibernateDaoImpl提供了基本的数据库操作,OldDaoImpl和NewDaoImpl都继承自它。OldDaoImpl是对旧系统的数据操作DAO,通过setBaseDaoSessionFactory方法指定数据源;getEntityClass指定旧系统的对象类;getAll从start开始取count数量条记录。如果start<0则取全部记录,注意,如果实体没有名为id的属性,则需要重写这个方法,这是为性能考虑。
NewDaoImpl是对新系统的数据操作DAO,通过setBaseDaoSessionFactory方法指定数据源;getEntityClass指定旧系统的对象类;getBySrcEntity根据旧对象查找对应的新对象,有些要通过旧系统实体的联合主键来确定,一般可以用联合主键组合起来的MD5编码来作为新系统对象的主键。
TransactionSaveService是具有事务的批处理类,它会把分页内的所有对象放在一个事务内处理。
public long transData(BaseServiceImpl service, List oldList, boolean isUpdate) { long processCount = 0; // 已经处理的记录数
for (int j = 0; j < oldList.size(); j++) { IdEntity src = (IdEntity) oldList.get(j); if (service.getOldDao().getEntityClass()!=null) { src = (IdEntity) service.getOldDao().get(service.getOldDao().getEntityClass(), src.getId()); }
Object target = null; if (service.isNeedTransform()) { target = service.getNewDao().getBySrcEntity(service.getEntityClass(), src); // 如果新对象中已有记录且不是更新 if (target != null && !isUpdate) { continue; } if (target == null) { try { target = service.getEntityClass().newInstance(); } catch (Exception e) { e.printStackTrace(); } } if (target != null) { service.transform(target, src); save(service.getNewDao(), target);
processCount++; } } service.afterTransform(target, src); } return processCount; } |
BaseServiceImpl的add和update方法,以update为例
@Override public void update (int startPage, int endPage, String startDate, String endDate) throws NmoException { Logger.getLogger(this.getClass()).debug("Begin update by time transform!"); long beginTime = System.currentTimeMillis();
long processCount = 0; // 已经处理的记录数 for (int i = startPage; i <= endPage; i++) { List oldList = getOldDao().getAll(i, IDataTransferService.PageSize, startDate, endDate); if (oldList == null || oldList.isEmpty()) { break; }
Logger.getLogger(this.getClass()).debug( "Processing ... Number(" + IDataTransferService.PageSize + "): " + i);
processCount += getTransactionSaveService().transData(this, oldList, true); }
long endTime = System.currentTimeMillis(); Logger.getLogger(this.getClass()).debug( "End transform, total transform: " + processCount + " records, total cost: " + (endTime - beginTime) + "ms"); } |
至此,数据迁移框架已经成形,我们只需要为每一个新系统实体编写对应的service类,newDao类和oldDao类。