Przeglądaj źródła

data-upgrade 支持升级锁

deason 3 miesięcy temu
rodzic
commit
c25d163a9a

+ 40 - 8
data-upgrade/src/main/java/com/qmth/boot/data/upgrade/config/DataUpgradeListener.java

@@ -7,6 +7,7 @@ import com.qmth.boot.data.upgrade.service.DataUpgradeService;
 import com.qmth.boot.data.upgrade.utils.ClassHelper;
 import com.qmth.boot.data.upgrade.utils.VersionComparator;
 import com.zaxxer.hikari.HikariDataSource;
+import org.apache.ibatis.session.TransactionIsolationLevel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.context.event.ApplicationPreparedEvent;
@@ -14,6 +15,7 @@ import org.springframework.context.ApplicationListener;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.support.TransactionCallbackWithoutResult;
 import org.springframework.transaction.support.TransactionTemplate;
@@ -113,8 +115,8 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
                 throw new RuntimeException("【应用信息表】中未找到匹配的数据!appCode:" + properties.getAppCode());
             }
 
-            log.warn("待升级应用码:{} 待升级应用版本号:{} 当前应用码:{} 当前应用版本号:{}", properties.getAppCode(),
-                    properties.getAppVersion(), appInfo.getAppCode(), appInfo.getAppVersion());
+            log.warn("待升级应用码:{} 待升级应用版本号:{} 当前应用码:{} 当前应用版本号:{} locked:{}", properties.getAppCode(),
+                    properties.getAppVersion(), appInfo.getAppCode(), appInfo.getAppVersion(), appInfo.getLocked());
 
             int compareValue = new VersionComparator().compare(appInfo.getAppVersion(), properties.getAppVersion());
             if (compareValue > 0) {
@@ -131,10 +133,19 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
             List<String> versionKeys = new ArrayList<>(serviceMaps.keySet());
             versionKeys.sort(new VersionComparator());
 
+            // 调整事务隔离级别:串行化(最严格模式加锁读,避免脏读,幻读,不可重复读等)
+            transactionTemplate.setIsolationLevel(TransactionIsolationLevel.SERIALIZABLE.getLevel());
+            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
             transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                 @Override
                 protected void doInTransactionWithoutResult(TransactionStatus status) {
                     try {
+                        // 升级锁定
+                        if (!tryLock(jdbcTemplate, appInfo.getAppCode())) {
+                            throw new RuntimeException("升级锁定中,请稍后再试!");
+                        }
+                        log.info("upgrade locking...");
+
                         for (String version : versionKeys) {
                             log.warn("*************** data upgrade version:{} start ***************", version);
                             serviceMaps.get(version).process(jdbcTemplate);
@@ -144,6 +155,10 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
                         status.setRollbackOnly();
                         // DDL-SQL语句不支持事务‌,执行失败时请采用其它补偿机制回滚!!!
                         throw new RuntimeException("SQL语句执行异常!", e);
+                    } finally {
+                        // 升级解锁
+                        unLock(jdbcTemplate, appInfo.getAppCode());
+                        log.info("upgrade unlock...");
                     }
                 }
             });
@@ -194,7 +209,7 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
                         serviceMaps.put(implVersion, (DataUpgradeService) constructor.newInstance());
                     }
                 } catch (Exception e) {
-                    throw new RuntimeException(e);
+                    throw new RuntimeException(e.getMessage(), e);
                 }
             }
         }
@@ -207,21 +222,37 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
         return serviceMaps;
     }
 
+    private boolean tryLock(JdbcTemplate jdbcTemplate, String appCode) {
+        try {
+            return jdbcTemplate.update("update boot_app_info set locked = 1 where app_code = ? and locked = 0", appCode) > 0;
+        } catch (Exception e) {
+            throw new RuntimeException("升级获取锁失败!appCode:" + appCode, e);
+        }
+    }
+
+    private void unLock(JdbcTemplate jdbcTemplate, String appCode) {
+        try {
+            jdbcTemplate.update("update boot_app_info set locked = 0 where app_code = ?", appCode);
+        } catch (Exception e) {
+            throw new RuntimeException("升级解锁失败!appCode:" + appCode, e);
+        }
+    }
+
     private void updateBootAppInfo(JdbcTemplate jdbcTemplate, String appCode, String appVersion) {
         try {
             jdbcTemplate.update("update boot_app_info set app_version = ? where app_code = ?", appVersion, appCode);
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            throw new RuntimeException("更新应用版本号失败!appCode:" + appCode, e);
         }
     }
 
     private BootAppInfo queryBootAppInfo(JdbcTemplate jdbcTemplate, String appCode) {
         try {
-            List<BootAppInfo> list = jdbcTemplate.query("select app_code,app_version from boot_app_info where app_code = ?",
+            List<BootAppInfo> list = jdbcTemplate.query("select app_code,app_version,locked from boot_app_info where app_code = ?",
                     new String[]{appCode}, new BeanPropertyRowMapper<>(BootAppInfo.class));
             return list.isEmpty() ? null : list.get(0);
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            throw new RuntimeException("获取应用信息失败!appCode:" + appCode, e);
         }
     }
 
@@ -230,13 +261,14 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
             String createTableSql = "create table if not exists boot_app_info (" +
                     " app_code varchar(20) not null," +
                     " app_version varchar(10) not null," +
+                    " locked bit(1) not null default 0," +
                     " unique key(app_code)" +
                     ")";
             jdbcTemplate.execute(createTableSql);
 
             jdbcTemplate.update("insert into boot_app_info (app_code,app_version) values(?,?)", appCode, appVersion);
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            throw new RuntimeException("初始应用信息表失败!appCode:" + appCode, e);
         }
     }
 
@@ -246,7 +278,7 @@ public class DataUpgradeListener implements ApplicationListener<ApplicationPrepa
             List<String> exists = jdbcTemplate.queryForList("SHOW TABLES LIKE 'boot_app_info'", String.class);
             return !exists.isEmpty();
         } catch (Exception e) {
-            throw new RuntimeException(e);
+            throw new RuntimeException("查询应用信息表失败!", e);
         }
     }
 

+ 10 - 0
data-upgrade/src/main/java/com/qmth/boot/data/upgrade/model/BootAppInfo.java

@@ -10,6 +10,8 @@ public class BootAppInfo implements Serializable {
 
     private String appVersion;
 
+    private Boolean locked;
+
     public String getAppCode() {
         return appCode;
     }
@@ -26,4 +28,12 @@ public class BootAppInfo implements Serializable {
         this.appVersion = appVersion;
     }
 
+    public Boolean getLocked() {
+        return locked;
+    }
+
+    public void setLocked(Boolean locked) {
+        this.locked = locked;
+    }
+
 }