Browse Source

数据库快照功能支持回滚操作

yingp 7 years ago
parent
commit
b6c407c542
13 changed files with 305 additions and 89 deletions
  1. 2 2
      base-servers/account/account-server/src/test/java/com/usoftchina/saas/account/api/AccountCacheTest.java
  2. 23 15
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/Snapshot.java
  3. 102 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/SnapshotOriginData.java
  4. 28 34
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/SnapshotUsage.java
  5. 1 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotDataRepository.java
  6. 31 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotOriginDataRepository.java
  7. 1 7
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotUsageRepository.java
  8. 4 0
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/SnapshotService.java
  9. 45 16
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/SnapshotLifecycle.java
  10. 63 10
      base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/support/MysqlStrategy.java
  11. 1 1
      base-servers/datacenter/datacenter-server/src/main/resources/mapper/CommonMapper.xml
  12. 2 2
      base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotServiceTest.java
  13. 2 2
      base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotUsageServiceTest.java

+ 2 - 2
base-servers/account/account-server/src/test/java/com/usoftchina/saas/account/api/AccountCacheTest.java

@@ -21,7 +21,7 @@ public class AccountCacheTest {
     public void hdel() {
         ResourceCache.of("trade-app").hdel();
         AccountCache.of(55).hdel();
-//        BaseContextHolder.setToken("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMzc5ODQ5MDU2NSIsImFwcElkIjoidHJhZGUtYXBwIiwidXNlcklkIjo2MSwiY29tcGFueUlkIjoyMSwidXNlck5hbWUiOiIxMzc5ODQ5MDU2NSIsInJlYWxOYW1lIjoi5bqU6bmPIiwiZXhwIjoxNTQzODIxMjkxfQ.DF5RT-g1Fi1czCb1-0EqI_Q9VTrwKskdzQtrpsdlcw-WyuN2xx2nqQqahLQIu836apeD3kptXxEtS2j3ArJKRzqrw4z6SFo-kFOl5cQpFLJxpVyrLl6i8ON33VO05uDRORWieNiWqSTDHFqqfOE5PaoocnCugOvweZaev7BmedQ");
-//        System.out.println(AccountCache.of(55).getAccount());
+        BaseContextHolder.setToken("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMzc5ODQ5MDU2NSIsImFwcElkIjoidHJhZGUtYXBwIiwidXNlcklkIjo2MSwiY29tcGFueUlkIjoyMSwidXNlck5hbWUiOiIxMzc5ODQ5MDU2NSIsInJlYWxOYW1lIjoi5bqU6bmPIiwiZXhwIjoxNTQ4MDkwMzE1fQ.hufMkjIMUrI4zzewYhSbmnGH3tj4CZhVHVSKZgdYk68EozKUQSnkZPEDlCotZECYabAeEVBYqWpuSssyL3y8hJ4DHtr-44l7d-htjd15nVhOgAZIvJF0h1txzn2NiJvzZlV60cbXfxE-Bw7evRoYe2wTwei5iGZk-420WhMr6ew");
+        System.out.println(AccountCache.of(55).getAccount());
     }
 }

+ 23 - 15
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/Snapshot.java

@@ -20,27 +20,23 @@ public class Snapshot {
     @Indexed(name = "snapshot_company_index")
     private Long companyId;
     /**
-     * 0 - 未开始
-     * 1 - 备份中
-     * -1 - 备份失败
-     * 2 - 完成备份
+     * @see Status
      */
-    private short status;
-    private String remark;
+    private String status;
     private String message;
 
     public Snapshot() {
         this.createTime = System.currentTimeMillis();
         this.creatorId = BaseContextHolder.getUserId();
         this.companyId = BaseContextHolder.getCompanyId();
-        this.status = Status.NOT_READY;
+        this.status = Status.NOT_READY.name();
     }
 
     public Snapshot(long companyId) {
         this.createTime = System.currentTimeMillis();
         this.creatorId = BaseContextHolder.getUserId();
         this.companyId = companyId;
-        this.status =Status.NOT_READY;
+        this.status =Status.NOT_READY.name();
     }
 
     public String get_id() {
@@ -75,11 +71,11 @@ public class Snapshot {
         this.companyId = companyId;
     }
 
-    public short getStatus() {
+    public String getStatus() {
         return status;
     }
 
-    public void setStatus(short status) {
+    public void setStatus(String status) {
         this.status = status;
     }
 
@@ -91,10 +87,22 @@ public class Snapshot {
         this.message = message;
     }
 
-    public interface Status {
-        short NOT_READY = 0;
-        short CREATING = 1;
-        short FAILED = -1;
-        short SUCCESS = 2;
+    public enum Status {
+        /**
+         * 未开始
+         */
+        NOT_READY,
+        /**
+         * 生成中
+         */
+        CREATING,
+        /**
+         * 失败
+         */
+        FAILED,
+        /**
+         * 成功
+         */
+        SUCCESS
     }
 }

+ 102 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/SnapshotOriginData.java

@@ -0,0 +1,102 @@
+package com.usoftchina.saas.dc.po;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.index.Indexed;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+/**
+ * 按公司生成数据快照 - 还原前备份的原数据
+ *
+ * @author yingp
+ * @date 2019/1/22
+ */
+@Document(collection = "snapshot_origin_data")
+public class SnapshotOriginData {
+    @Id
+    private String _id;
+    private String snapshotId;
+    @Indexed(name = "snapshot_origin_data_uid_index")
+    private String usageId;
+    private String dcName;
+    private String dbName;
+    private String tableName;
+    private String columnName;
+    private Object originData;
+
+    public SnapshotOriginData() {
+    }
+
+    public SnapshotOriginData(SnapshotUsage usage, SnapshotData data, Object originData) {
+        this.usageId = usage.get_id();
+        this.snapshotId = data.getSnapshotId();
+        this.dcName = data.getDcName();
+        this.dbName = data.getDbName();
+        this.tableName = data.getTableName();
+        this.columnName = data.getColumnName();
+        this.originData = originData;
+    }
+
+    public String get_id() {
+        return _id;
+    }
+
+    public void set_id(String _id) {
+        this._id = _id;
+    }
+
+    public String getSnapshotId() {
+        return snapshotId;
+    }
+
+    public void setSnapshotId(String snapshotId) {
+        this.snapshotId = snapshotId;
+    }
+
+    public String getDcName() {
+        return dcName;
+    }
+
+    public void setDcName(String dcName) {
+        this.dcName = dcName;
+    }
+
+    public String getDbName() {
+        return dbName;
+    }
+
+    public void setDbName(String dbName) {
+        this.dbName = dbName;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public String getColumnName() {
+        return columnName;
+    }
+
+    public void setColumnName(String columnName) {
+        this.columnName = columnName;
+    }
+
+    public String getUsageId() {
+        return usageId;
+    }
+
+    public void setUsageId(String usageId) {
+        this.usageId = usageId;
+    }
+
+    public Object getOriginData() {
+        return originData;
+    }
+
+    public void setOriginData(Object originData) {
+        this.originData = originData;
+    }
+}

+ 28 - 34
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/po/SnapshotUsage.java

@@ -19,37 +19,30 @@ import java.io.Serializable;
 public class SnapshotUsage implements Serializable {
     @Id
     private String _id;
-    /**
-     * 执行还原前,先将当前数据生成一份快照,这样在还原失败的情况下可以恢复
-     */
-    private String originSnapshotId;
     /**
      * 选择的快照文件
      */
-    private String usedSnapshotId;
+    private String snapshotId;
     private Long createTime;
     private Long creatorId;
     @Indexed(name = "snapshot_usage_company_index")
     private Long companyId;
     /**
-     * 0 - 未开始
-     * 1 - 备份中
-     * -1 - 备份失败
-     * 2 - 完成备份
+     * @see Status
      */
-    private short status;
+    private String status;
     private String message;
 
     public SnapshotUsage() {
         this.createTime = System.currentTimeMillis();
         this.creatorId = BaseContextHolder.getUserId();
         this.companyId = BaseContextHolder.getCompanyId();
-        this.status = SnapshotUsage.Status.NOT_READY;
+        this.status = Status.NOT_READY.name();
     }
 
-    public SnapshotUsage(String usedSnapshotId) {
+    public SnapshotUsage(String snapshotId) {
         this();
-        this.usedSnapshotId = usedSnapshotId;
+        this.snapshotId = snapshotId;
     }
 
     public String get_id() {
@@ -60,20 +53,12 @@ public class SnapshotUsage implements Serializable {
         this._id = _id;
     }
 
-    public String getOriginSnapshotId() {
-        return originSnapshotId;
-    }
-
-    public void setOriginSnapshotId(String originSnapshotId) {
-        this.originSnapshotId = originSnapshotId;
-    }
-
-    public String getUsedSnapshotId() {
-        return usedSnapshotId;
+    public String getSnapshotId() {
+        return snapshotId;
     }
 
-    public void setUsedSnapshotId(String usedSnapshotId) {
-        this.usedSnapshotId = usedSnapshotId;
+    public void setSnapshotId(String snapshotId) {
+        this.snapshotId = snapshotId;
     }
 
     public Long getCreateTime() {
@@ -100,11 +85,11 @@ public class SnapshotUsage implements Serializable {
         this.companyId = companyId;
     }
 
-    public short getStatus() {
+    public String getStatus() {
         return status;
     }
 
-    public void setStatus(short status) {
+    public void setStatus(String status) {
         this.status = status;
     }
 
@@ -116,17 +101,26 @@ public class SnapshotUsage implements Serializable {
         this.message = message;
     }
 
-    public interface Status {
-        short NOT_READY = 0;
+    public enum Status {
+        /**
+         * 未开始
+         */
+        NOT_READY,
         /**
          * 还原中(删除当前数据,使用快照数据写入)
          */
-        short RESTORE = 1;
+        RESTORE,
+        /**
+         * 操作失败需要使用originData回滚
+         */
+        ROLLBACK,
+        /**
+         * 失败
+         */
+        FAILED,
         /**
-         * 操作失败需要使用originData还原
+         * 成功
          */
-        short ROLLBACK = -1;
-        short FAILED = -2;
-        short SUCCESS = 2;
+        SUCCESS
     }
 }

+ 1 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotDataRepository.java

@@ -21,6 +21,7 @@ public interface SnapshotDataRepository extends MongoRepository<SnapshotData, St
 
     /**
      * 查找
+     *
      * @param snapshotId
      * @param dcName
      * @param dbName

+ 31 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotOriginDataRepository.java

@@ -0,0 +1,31 @@
+package com.usoftchina.saas.dc.repository;
+
+import com.usoftchina.saas.dc.po.SnapshotOriginData;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @date 2019/1/22
+ */
+@Repository
+public interface SnapshotOriginDataRepository extends MongoRepository<SnapshotOriginData, String> {
+    /**
+     * 删除
+     *
+     * @param snapshotId
+     */
+    void deleteBySnapshotId(String snapshotId);
+
+    /**
+     * 查找
+     *
+     * @param usageId
+     * @param dcName
+     * @param dbName
+     * @return
+     */
+    List<SnapshotOriginData> findByUsageIdAndDcNameAndDbName(String usageId, String dcName, String dbName);
+}

+ 1 - 7
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/repository/SnapshotUsageRepository.java

@@ -15,11 +15,5 @@ public interface SnapshotUsageRepository extends MongoRepository<SnapshotUsage,
      *
      * @param snapshotId
      */
-    void deleteByUsedSnapshotId(String snapshotId);
-    /**
-     * 删除
-     *
-     * @param snapshotId
-     */
-    void deleteByOriginSnapshotId(String snapshotId);
+    void deleteBySnapshotId(String snapshotId);
 }

+ 4 - 0
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/service/SnapshotService.java

@@ -11,12 +11,14 @@ import com.usoftchina.saas.page.PageRequest;
 public interface SnapshotService {
     /**
      * 创建快照
+     *
      * @return
      */
     Snapshot save();
 
     /**
      * 查找
+     *
      * @param id
      * @return
      */
@@ -24,6 +26,7 @@ public interface SnapshotService {
 
     /**
      * 查找
+     *
      * @param page
      * @return
      */
@@ -31,6 +34,7 @@ public interface SnapshotService {
 
     /**
      * 删除
+     *
      * @param id
      */
     void removeByPrimaryKey(String id);

+ 45 - 16
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/event/SnapshotLifecycle.java

@@ -7,6 +7,7 @@ import com.usoftchina.saas.dc.po.DataSourceInfo;
 import com.usoftchina.saas.dc.po.Snapshot;
 import com.usoftchina.saas.dc.po.SnapshotUsage;
 import com.usoftchina.saas.dc.repository.SnapshotDataRepository;
+import com.usoftchina.saas.dc.repository.SnapshotOriginDataRepository;
 import com.usoftchina.saas.dc.repository.SnapshotRepository;
 import com.usoftchina.saas.dc.repository.SnapshotUsageRepository;
 import com.usoftchina.saas.dc.service.DataSourceInfoService;
@@ -43,6 +44,9 @@ public class SnapshotLifecycle {
     @Autowired
     private DataSourceInfoService dataSourceInfoService;
 
+    @Autowired
+    private SnapshotOriginDataRepository snapshotOriginDataRepository;
+
     @Autowired
     private MysqlStrategy mysqlStrategy;
 
@@ -51,7 +55,7 @@ public class SnapshotLifecycle {
     public void onReadyToCreateEvent(ReadyToCreateEvent event) {
         Snapshot snapshot = event.getSnapshot();
         try {
-            snapshot.setStatus(Snapshot.Status.CREATING);
+            snapshot.setStatus(Snapshot.Status.CREATING.name());
             snapshotRepository.save(snapshot);
             // 为防止导出过程中有人操作,导致部分数据不一致,在导出前锁定该公司不允许操作
             CompanyLockCache.lock(snapshot.getCompanyId());
@@ -85,7 +89,7 @@ public class SnapshotLifecycle {
         Snapshot snapshot = event.getSnapshot();
         // 解锁
         CompanyLockCache.unlock(snapshot.getCompanyId());
-        snapshot.setStatus(Snapshot.Status.SUCCESS);
+        snapshot.setStatus(Snapshot.Status.SUCCESS.name());
         snapshotRepository.save(snapshot);
     }
 
@@ -96,7 +100,7 @@ public class SnapshotLifecycle {
         logger.error("failed to create snapshot {}", event.getException(), snapshot.get_id());
         // 解锁
         CompanyLockCache.unlock(snapshot.getCompanyId());
-        snapshot.setStatus(Snapshot.Status.FAILED);
+        snapshot.setStatus(Snapshot.Status.FAILED.name());
         snapshot.setMessage(event.getException().getMessage());
         snapshotRepository.save(snapshot);
         // 快照创建失败,数据已经无意义了,删除
@@ -108,8 +112,8 @@ public class SnapshotLifecycle {
     public void onRemovedEvent(RemovedEvent event) {
         Snapshot snapshot = event.getSnapshot();
         snapshotDataRepository.deleteBySnapshotId(snapshot.get_id());
-        snapshotUsageRepository.deleteByUsedSnapshotId(snapshot.get_id());
-        snapshotUsageRepository.deleteByOriginSnapshotId(snapshot.get_id());
+        snapshotUsageRepository.deleteBySnapshotId(snapshot.get_id());
+        snapshotOriginDataRepository.deleteBySnapshotId(snapshot.get_id());
     }
 
     @Async
@@ -118,11 +122,11 @@ public class SnapshotLifecycle {
         Snapshot snapshot = event.getSnapshot();
         SnapshotUsage usage = event.getUsage();
         try {
-            usage.setStatus(SnapshotUsage.Status.RESTORE);
+            usage.setStatus(SnapshotUsage.Status.RESTORE.name());
             snapshotUsageRepository.save(usage);
             // 为防止导入过程中有人操作,导致部分数据不一致,在导出前锁定该公司不允许操作
             CompanyLockCache.lock(usage.getCompanyId());
-            restoreSnapshotData(snapshot);
+            restoreSnapshotData(usage);
             SpringContextHolder.getContext().publishEvent(new RestoredEvent(this, snapshot, usage));
         } catch (Exception e) {
             SpringContextHolder.getContext().publishEvent(new RestoreFailedEvent(this, snapshot, usage, e));
@@ -130,17 +134,17 @@ public class SnapshotLifecycle {
     }
 
     /**
-     * 连接到不同数据中心,查找数据,生成快照数据,删除,使用快照数据写入
+     * 连接到不同数据中心,备份当前数据后删除,使用快照数据写入
      *
-     * @param snapshot
+     * @param usage
      */
-    private void restoreSnapshotData(Snapshot snapshot) {
+    private void restoreSnapshotData(SnapshotUsage usage) {
         String dcName = StringUtils.nullIf(CompanyCache.current().getCompany().getDcName(), "default");
         List<DataSourceInfo> dss = dataSourceInfoService.findByDcNameUseDefault(dcName);
         if (!CollectionUtils.isEmpty(dss)) {
             dss.parallelStream().forEach(ds -> {
                 if ("mysql".equals(ds.getDbType())) {
-                    mysqlStrategy.imp(snapshot, ds);
+                    mysqlStrategy.imp(usage, ds);
                 }
             });
         }
@@ -152,7 +156,7 @@ public class SnapshotLifecycle {
         SnapshotUsage usage = event.getUsage();
         // 解锁
         CompanyLockCache.unlock(usage.getCompanyId());
-        usage.setStatus(SnapshotUsage.Status.SUCCESS);
+        usage.setStatus(SnapshotUsage.Status.SUCCESS.name());
         snapshotUsageRepository.save(usage);
     }
 
@@ -164,10 +168,35 @@ public class SnapshotLifecycle {
         logger.error("failed to restore by snapshot {}", event.getException(), snapshot.get_id());
         // 解锁
         CompanyLockCache.unlock(usage.getCompanyId());
+        String errMsg = event.getException().getMessage();
         // 回滚
-        usage.setStatus(SnapshotUsage.Status.FAILED);
-        usage.setMessage(event.getException().getMessage());
-        snapshotUsageRepository.save(usage);
-        // TODO
+        try {
+            usage.setStatus(SnapshotUsage.Status.ROLLBACK.name());
+            snapshotUsageRepository.save(usage);
+            rollbackSnapshotData(usage);
+        } catch (Exception e) {
+            errMsg += " \n rollback error: " + e.getMessage();
+        } finally {
+            usage.setStatus(SnapshotUsage.Status.FAILED.name());
+            usage.setMessage(errMsg);
+            snapshotUsageRepository.save(usage);
+        }
+    }
+
+    /**
+     * 连接到不同数据中心,删除当前数据,使用备份数据写入
+     *
+     * @param usage
+     */
+    private void rollbackSnapshotData(SnapshotUsage usage) {
+        String dcName = StringUtils.nullIf(CompanyCache.current().getCompany().getDcName(), "default");
+        List<DataSourceInfo> dss = dataSourceInfoService.findByDcNameUseDefault(dcName);
+        if (!CollectionUtils.isEmpty(dss)) {
+            dss.parallelStream().forEach(ds -> {
+                if ("mysql".equals(ds.getDbType())) {
+                    mysqlStrategy.rollback(usage, ds);
+                }
+            });
+        }
     }
 }

+ 63 - 10
base-servers/datacenter/datacenter-server/src/main/java/com/usoftchina/saas/dc/snapshot/support/MysqlStrategy.java

@@ -2,11 +2,9 @@ package com.usoftchina.saas.dc.snapshot.support;
 
 import com.usoftchina.saas.dc.mapper.CommonMapper;
 import com.usoftchina.saas.dc.mapper.SchemaMapper;
-import com.usoftchina.saas.dc.po.DataSourceInfo;
-import com.usoftchina.saas.dc.po.SchemaInfo;
-import com.usoftchina.saas.dc.po.Snapshot;
-import com.usoftchina.saas.dc.po.SnapshotData;
+import com.usoftchina.saas.dc.po.*;
 import com.usoftchina.saas.dc.repository.SnapshotDataRepository;
+import com.usoftchina.saas.dc.repository.SnapshotOriginDataRepository;
 import com.usoftchina.saas.jdbc.DynamicDataSourceContextHolder;
 import com.usoftchina.saas.jdbc.DynamicDataSourceRegister;
 import com.usoftchina.saas.utils.CollectionUtils;
@@ -34,6 +32,15 @@ public class MysqlStrategy implements CompanyStrategy {
     @Autowired
     private SnapshotDataRepository snapshotDataRepository;
 
+    @Autowired
+    private SnapshotOriginDataRepository snapshotOriginDataRepository;
+
+    /**
+     * 指定数据源导出
+     *
+     * @param snapshot
+     * @param ds
+     */
     public void exp(final Snapshot snapshot, final DataSourceInfo ds) {
         final long companyId = snapshot.getCompanyId();
         // 如果不存在数据源则自动创建
@@ -63,19 +70,33 @@ public class MysqlStrategy implements CompanyStrategy {
         }
     }
 
-    public void imp(final Snapshot snapshot, final DataSourceInfo ds) {
+    /**
+     * 指定数据源导入
+     *
+     * @param usage
+     * @param ds
+     */
+    public void imp(final SnapshotUsage usage, final DataSourceInfo ds) {
         List<SnapshotData> snapshotDataList = snapshotDataRepository.findBySnapshotIdAndDcNameAndDbName(
-                snapshot.get_id(), ds.getDcName(), ds.getDbName());
+                usage.getSnapshotId(), ds.getDcName(), ds.getDbName());
         if (!CollectionUtils.isEmpty(snapshotDataList)) {
-            final long companyId = snapshot.getCompanyId();
+            final long companyId = usage.getCompanyId();
             // 如果不存在数据源则自动创建
             dataSourceRegister.createDataSource(ds);
             snapshotDataList.parallelStream().forEach(snapshotData -> {
                 try {
                     DynamicDataSourceContextHolder.set(ds);
-                    // 删除当前数据
-                    commonMapper.deleteByTableAndColumnAndValue(snapshotData.getTableName(),
-                            snapshotData.getColumnName(), companyId);
+                    // 查找指定表的所有数据
+                    List<LinkedHashMap<String, Object>> data = commonMapper.selectByTableAndColumnAndValue(
+                            snapshotData.getTableName(), snapshotData.getColumnName(), companyId);
+                    if (!CollectionUtils.isEmpty(data)) {
+                        // 记录当前数据,以便失败或用户选择回滚的时候用到
+                        SnapshotOriginData originData = new SnapshotOriginData(usage, snapshotData, data);
+                        snapshotOriginDataRepository.save(originData);
+                        // 删除当前数据
+                        commonMapper.deleteByTableAndColumnAndValue(snapshotData.getTableName(),
+                                snapshotData.getColumnName(), companyId);
+                    }
                     // 写入快照数据
                     List<Map<String, Object>> list = (List<Map<String, Object>>) snapshotData.getData();
                     if (!CollectionUtils.isEmpty(list)) {
@@ -88,4 +109,36 @@ public class MysqlStrategy implements CompanyStrategy {
             });
         }
     }
+
+    /**
+     * 指定数据源回滚
+     *
+     * @param usage
+     * @param ds
+     */
+    public void rollback(final SnapshotUsage usage, final DataSourceInfo ds) {
+        List<SnapshotOriginData> originDataList = snapshotOriginDataRepository.findByUsageIdAndDcNameAndDbName(
+                usage.get_id(), ds.getDcName(), ds.getDbName());
+        if (!CollectionUtils.isEmpty(originDataList)) {
+            final long companyId = usage.getCompanyId();
+            // 如果不存在数据源则自动创建
+            dataSourceRegister.createDataSource(ds);
+            originDataList.parallelStream().forEach(originData -> {
+                try {
+                    DynamicDataSourceContextHolder.set(ds);
+                    // 删除当前数据
+                    commonMapper.deleteByTableAndColumnAndValue(originData.getTableName(),
+                            originData.getColumnName(), companyId);
+                    // 写入备份数据
+                    List<Map<String, Object>> list = (List<Map<String, Object>>) originData.getOriginData();
+                    if (!CollectionUtils.isEmpty(list)) {
+                        commonMapper.insertListMap(originData.getTableName(),
+                                new ArrayList<>(list.get(0).keySet()), list);
+                    }
+                } finally {
+                    DynamicDataSourceContextHolder.clear();
+                }
+            });
+        }
+    }
 }

+ 1 - 1
base-servers/datacenter/datacenter-server/src/main/resources/mapper/CommonMapper.xml

@@ -7,7 +7,7 @@
     <delete id="deleteByTableAndColumnAndValue" statementType="STATEMENT">
         delete from ${tableName} where ${columnName}=${columnValue}
     </delete>
-    <insert id="insertMap" statementType="STATEMENT">
+    <insert id="insertMap">
         insert into ${tableName}
         <foreach collection="record.keys" item="key" open="(" close=")" separator="," >
             ${key}

+ 2 - 2
base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotServiceTest.java

@@ -36,8 +36,8 @@ public class SnapshotServiceTest {
             snapshot = snapshotService.selectByPrimaryKey(snapshot.get_id());
             System.out.println("Status: " + snapshot.getStatus());
             Thread.sleep(1000);
-            if (snapshot.getStatus() == Snapshot.Status.SUCCESS ||
-                    snapshot.getStatus() == Snapshot.Status.FAILED) {
+            if (snapshot.getStatus().equals(Snapshot.Status.SUCCESS.name()) ||
+                    snapshot.getStatus().equals(Snapshot.Status.FAILED.name())) {
                 break;
             }
         }

+ 2 - 2
base-servers/datacenter/datacenter-server/src/test/java/com/usoftchina/saas/dc/SnapshotUsageServiceTest.java

@@ -36,8 +36,8 @@ public class SnapshotUsageServiceTest {
             usage = snapshotUsageService.selectByPrimaryKey(usage.get_id());
             System.out.println("Status: " + usage.getStatus());
             Thread.sleep(1000);
-            if (usage.getStatus() == SnapshotUsage.Status.SUCCESS ||
-                    usage.getStatus() == SnapshotUsage.Status.FAILED) {
+            if (usage.getStatus().equals(SnapshotUsage.Status.SUCCESS.name()) ||
+                    usage.getStatus().equals(SnapshotUsage.Status.FAILED.name())) {
                 break;
             }
         }