由于有多个数据源,所以我们首先需要对数据源进行一个管理。
同时由于我们也是多数据导入,我们也需要一个进度条那么就需要作业调度。那么多线程并发访问问题就会出现。(LocalThread,并对资源进行一个上锁)
关于总部的数据源我们使用阿里的Druid进行管理,这就是一个数据源连接池,附加了很多功能(sql监控等),并对性能进行优化。
我们会把所有的数据源进行管理,放入map然后获取
private Map<String, DataSource> dataSourceCache;
之后我们需要对每个数据源添加事务,之后,进行管理
DataSourceTransactionManager tm = new DataSourceTransactionManager(ds);
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
td.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
dataSourceCache.put(ds, new TransactionTemplate(tm, td));
这是获取数据源的问题解决了。
那么接下来就是对资源进行一个上锁,资源就是我们要执行的job
我们可以自定义上锁资源。
我们可以创建一个表,用来存储资源id等数据,设置资源id为主键,只要我们save这个表对象的时候,如果被锁,那么insert就会报错,那么证明该资源已被锁。
if (lock == null) {
lock = new PDataSyncLock();
lock.setExpiredTime(DateUtils.addSeconds(now, continuingSeconds));
try {
em.persist(lock);
em.flush();
} catch (PersistenceException e) {
throw new LockException("资源“{0}”已被锁定。", resource);
}
} else if (lock.getExpiredTime().compareTo(now) <= 0) {
em.merge(lock);
em.flush();
} else {
throw new LockException("资源“{0}”已被锁定。", resource);
}
将资源锁定之后,那么开始执行数据同步.
pentaho的Kettle的开源ETL工具
进行数据同步我们使用的是pentaho的Kettle的开源ETL工具,允许你管理来自不同数据库的数据。拥有可视化工具。
主要有2个脚本文件,transformation(对数据的基础转换)和job(整个工作流的控制)
public KettleTransBuilder(String name) {
try {
String kettleHome = KettleTransBuilder.class.getResource("/").getFile() + "../../kettleLog"; //之后创建转换对象,这里指定转换文件的位置
// 获得执行类的当前路径
String user_dir = System.getProperty("user.dir");
// Kettle初始化需要修改相应的配置路径
System.setProperty("KETTLE_HOME", kettleHome);
System.setProperty("user.dir", kettleHome);
// 运行环境初始化(设置主目录、注册必须的插件等) 初始化kettle环境
KettleEnvironment.init();
// Kettle初始化完毕,还原执行类的当前路径 对于web项目要这样换一下路径,否则第一次加载要好久
System.setProperty("user.dir", user_dir);
//创建ktr元对象
transMeta = new TransMeta();
transMeta.setName(name);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Trans trans = new Trans(transMeta); 之后调用trans.setParameterValue("FILE_PATH",读入文件的路径比如excel文件路径)
该类是我们封装的一个类。其中包含很多外观方法,将很多kettle繁琐的步骤,组合在一起(外观模式)。
比如:我们同步数据的时候,是从总部数据库,直接全部拉下来。 所以需要在子库,全部删除。我们就需要直接执行sql语句。
通过ExecSQLMeta这个类,它被提供就是用来执行sql的。
// 构造执行脚本步骤
ExecSQLMeta execMeta = new ExecSQLMeta();
String pluginId = PluginRegistry.getInstance().getPluginId(StepPluginType.class, execMeta);
execMeta.setDatabaseMeta(transMeta.findDatabase(dsName));
execMeta.setSql(sql);
execMeta.setSingleStatement(singleStatement);
execMeta.setVariableReplacementActive(true);
之后在执行的时候
StepMeta execStep = new StepMeta(pluginId, STEP_EXECSQL + "_" + index, execMeta);
transMeta.addStep(execStep);
这就相当于一个作业的监控,按步执行,进行一个进度监控。将每一步,传入job
但是同步数据,也就是insert,和select数据。
insert,通过调用TableInputMeta类。select同理 使用 TableOutputMeta。删除,DeleteMeta类
public KettleTransBuilder inputData(String dsName, String sql) {
Assert.assertArgumentNotNull(dsName, "dsName");
Assert.assertArgumentNotNull(sql, "sql");
TableInputMeta tableInput = new TableInputMeta();
String pluginId = PluginRegistry.getInstance().getPluginId(StepPluginType.class, tableInput);
DatabaseMeta dsMeta = transMeta.findDatabase(dsName);
tableInput.setDatabaseMeta(dsMeta);
tableInput.setSQL(sql);
tableInput.setVariableReplacementActive(true);
if (!transMeta.getSteps().isEmpty()) {
StepMeta preStep = transMeta.getSteps().get(transMeta.getSteps().size() - 1);
if (preStep.getName().startsWith(STEP_INPUTTIMESTAMP)) {
// 时间戳不为空
tableInput.setLookupFromStep(preStep);
}
}
List<StepMeta> inputSteps = getStepMega(transMeta.getSteps(), STEP_INPUTDATA);
String index = String.valueOf(inputSteps.size() + 1);
StepMeta inputDataStep = new StepMeta(pluginId, STEP_INPUTDATA + "_" + index, tableInput);
transMeta.addStep(inputDataStep);
return this;
}