有了接口和多个实现,那么备份和恢复的时候究竟选择哪一种数据库实现呢?BackupServiceExecuter充当工厂类(Factory),负责从多个数据库备份恢复实现类中选择一个并执行相应的备份和恢复操作,BackupServiceExecuter也实现了BackupService接口,这也是一个典型的外观(Facade)设计模式,封装了选择特定数据库的逻辑。
  定时调度器和web前端控制器也是使用BackupServiceExecuter来执行备份恢复操作,BackupServiceExecuter通过每个实现类以@Service注解指定的名称以及配置文件
db.properties或db.local.properties中jpa.database的值来做选择的依据,如下所示:
/**
*执行备份恢复的服务,自动判断使用的是什么数据库,并找到该数据库备份恢复服务的实现并执行
* @author 杨尚川
*/
@Service
public class BackupServiceExecuter extends AbstractBackupService{
private BackupService backupService=null;
@Resource(name="backupFileSenderExecuter")
private BackupFileSenderExecuter backupFileSenderExecuter;
/**
* 查找并执行正在使用的数据的备份实现实例
* @return
*/
@Override
public boolean backup() {
if(backupService==null){
backupService=SpringContextUtils.getBean(PropertyHolder.getProperty("jpa.database"));
}
boolean result = backupService.backup();
//如果备份成功,则将备份文件发往他处
if(result){
backupFileSenderExecuter.send(getNewestBackupFile());
}
return result;
}
/**
* 查找并执行正在使用的数据的恢复实现实例
* @param date
* @return
*/
@Override
public boolean restore(String date) {
if(backupService==null){
backupService=SpringContextUtils.getBean(PropertyHolder.getProperty("jpa.database"));
}
return backupService.restore(date);
}
}
  关键是这行代码backupService=SpringContextUtils.getBean(PropertyHolder.getProperty("jpa.database"));
  2、在记录备份恢复日志的时候,如果每种数据库的实现类都要粘贴复制通用的代码到备份和恢复方法的开始和结束位置,那么四处飘散着重复的代码,对易读性和可修改性都是极大的破坏。
  AOP是解决这个问题的不二之选,为了AOP能工作,良好设计的包结构、类层级,规范的命名都是非常重要的,尤其是这里的BackupServiceExecuter和真正执行备份恢复的实现类有共同的方法签名(都实现了BackupService接口),所以把他们放到不同的包里有利于AOP。
  使用AOP首先要引入依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
  其次是要在spring配置文件中指定启用自动代理:
  <aop:aspectj-autoproxy />
  后可以编写代码实现日志记录:
/**
* 备份恢复数据库日志Aspect
* org.apdplat.module.system.service.backup.impl包下面有多个数据库的备份恢复实现
* 他们实现了BackupService接口的backup方法(备份数据库)和restore(恢复数据库)方法
* @author 杨尚川
*/
@Aspect
@Service
public class BackupLogAspect {
private static final APDPlatLogger LOG = new APDPlatLogger(BackupLogAspect.class);
private static final boolean MONITOR_BACKUP = PropertyHolder.getBooleanProperty("monitor.backup");
private BackupLog backupLog = null;
static{
if(MONITOR_BACKUP){
LOG.info("启用备份恢复日志");
LOG.info("Enable backup restore log", Locale.ENGLISH);
}else{
LOG.info("禁用备份恢复日志");
LOG.info("Disable backup restore log", Locale.ENGLISH);
}
}
//拦截备份数据库操作
@Pointcut("execution( boolean org.apdplat.module.system.service.backup.impl.*.backup() )")
public void backup() {}
@Before("backup()")
public void beforeBackup(JoinPoint jp) {
if(MONITOR_BACKUP){
before(BackupLogType.BACKUP);
}
}
@AfterReturning(value="backup()", argNames="result", returning = "result")
public void afterBackup(JoinPoint jp, boolean result) {
if(MONITOR_BACKUP){
after(result);
}
}
//拦截恢复数据库操作
@Before(value="execution( boolean org.apdplat.module.system.service.backup.impl.*.restore(java.lang.String) ) && args(date)",
argNames="date")
public void beforeRestore(JoinPoint jp, String date) {
if(MONITOR_BACKUP){
before(BackupLogType.RESTORE);
}
}
@AfterReturning(pointcut="execution( boolean org.apdplat.module.system.service.backup.impl.*.restore(java.lang.String) )",
returning = "result")
public void afterRestore(JoinPoint jp, boolean result) {
if(MONITOR_BACKUP){
after(result);
}
}
private void before(String type){
LOG.info("准备记录数据库"+type+"日志");
User user=UserHolder.getCurrentLoginUser();
String ip=UserHolder.getCurrentUserLoginIp();
backupLog=new BackupLog();
if(user != null){
backupLog.setUsername(user.getUsername());
}
backupLog.setLoginIP(ip);
try {
backupLog.setServerIP(InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
LOG.error("无法获取服务器IP地址", e);
LOG.error("Can't get server's ip address", e, Locale.ENGLISH);
}
backupLog.setAppName(SystemListener.getContextPath());
backupLog.setStartTime(new Date());
backupLog.setOperatingType(type);
}
private void after(boolean result){
if(result){
backupLog.setOperatingResult(BackupLogResult.SUCCESS);
}else{
backupLog.setOperatingResult(BackupLogResult.FAIL);
}
backupLog.setEndTime(new Date());
backupLog.setProcessTime(backupLog.getEndTime().getTime()-backupLog.getStartTime().getTime());
//将日志加入内存缓冲区
BufferLogCollector.collect(backupLog);
LOG.info("记录完毕");
}
}
  3、怎么样才能异地容错呢?将备份文件保存到与服务器处于不同地理位置的机器上,好能多保存几份。除了能自动把备份文件传输到异地服务器上面,用户也可以从web界面下载。
  APDPlat使用推模型来发送备份文件,接口如下:
/**
* 备份文件发送器
* 将新的备份文件发送到其他机器,防止服务器故障丢失数据
* @author 杨尚川
*/
public interface BackupFileSender {
public void send(File file);
}