首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

MyBatis 在 CS 架構應用實戰:XML 加密、動態 SqlSessionFactory、多環境隔離

     編輯:Java 程式設計入門

場景痛點

  • CS 客戶端部署在用戶機器,配置文件易被竄改、密碼洩露
  • BS/CS 共用 Mapper XML,需統一管理
  • 多環境(開發/測試/生產)配置隔離
  • 單機版無應用服務器,需輕量級初始化

核心方案:加密配置文件 + 解密流構建 SqlSessionFactory

1. 原始 MyBatis 配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/app_db"/>
        <property name="username" value="app_user"/>
        <property name="password" value="plain_password"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/example/dao/UserMapper.xml"/>
  </mappers>
</configuration>

2. 雙向加密(AES/DES)生成 .data 文件

public class ConfigEncryptor {
    public static void main(String[] args) throws Exception {
        String xml = Files.readString(Paths.get("src/main/resources/mybatis-config.xml"));
        String encrypted = AESUtil.encrypt(xml, "strong-secret-key-16bytes!!");
        Files.write(Paths.get("src/main/resources/config.data"), encrypted.getBytes(StandardCharsets.UTF_8));
        System.out.println("加密完成: config.data");
    }
}

// AES 工具類(簡化版,生產用 GCM 模式)
public class AESUtil {
    public static String encrypt(String data, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    }
    public static String decrypt(String encrypted, String key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return new String(cipher.doFinal(Base64.getDecoder().decode(encrypted)), StandardCharsets.UTF_8);
    }
}

3. MyBatis 初始化器(單例、解密流注入)

public final class MyBatisInitializer {
    private static final String ENCRYPTED_CONFIG = "config.data";
    private static final String DECRYPT_KEY = "strong-secret-key-16bytes!!";  // 生產從環境讀取
    
    private static volatile SqlSessionFactory sqlSessionFactory;
    
    private MyBatisInitializer() {}
    
    public static SqlSessionFactory getSqlSessionFactory() {
        if (sqlSessionFactory == null) {
            synchronized (MyBatisInitializer.class) {
                if (sqlSessionFactory == null) {
                    sqlSessionFactory = buildFactory();
                }
            }
        }
        return sqlSessionFactory;
    }
    
    private static SqlSessionFactory buildFactory() {
        try (InputStream decryptedStream = getDecryptedConfigStream()) {
            return new SqlSessionFactoryBuilder().build(decryptedStream);
        } catch (IOException e) {
            throw new ExceptionInInitializerError("MyBatis 初始化失敗: " + e.getMessage());
        }
    }
    
    private static InputStream getDecryptedConfigStream() throws IOException {
        // 1. 定位加密文件(支援 classpath、文件系統、JAR 內)
        URL resource = MyBatisInitializer.class.getClassLoader().getResource(ENCRYPTED_CONFIG);
        if (resource == null) {
            // 嘗試文件系統(開發/打包後路徑差異)
            File file = new File(System.getProperty("user.dir"), "config/" + ENCRYPTED_CONFIG);
            if (file.exists()) {
                return decryptFile(file);
            }
            throw new FileNotFoundException("找不到加密配置: " + ENCRYPTED_CONFIG);
        }
        
        try (InputStream encryptedIn = resource.openStream()) {
            String encrypted = new String(encryptedIn.readAllBytes(), StandardCharsets.UTF_8);
            String decrypted = AESUtil.decrypt(encrypted, DECRYPT_KEY);
            return new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8));
        }
    }
    
    private static InputStream decryptFile(File file) throws IOException {
        String encrypted = Files.readString(file.toPath(), StandardCharsets.UTF_8);
        String decrypted = AESUtil.decrypt(encrypted, DECRYPT_KEY);
        return new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8));
    }
    
    // 關閉工廠(應用退出時調用)
    public static void shutdown() {
        if (sqlSessionFactory != null) {
            // MyBatis SqlSessionFactory 無顯式 close,但可釋放連接池
            // 若用 PooledDataSource,可反射獲取並關閉
            sqlSessionFactory = null;
        }
    }
}

4. 業務代碼使用

public class UserService {
    private final SqlSessionFactory sqlSessionFactory = MyBatisInitializer.getSqlSessionFactory();
    
    public User findById(Long id) {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            return mapper.selectById(id);
        }
    }
    
    public void insert(User user) {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {  // 自動提交
            UserMapper mapper = session.getMapper(UserMapper.class);
            mapper.insert(user);
        }
    }
}

進階:純 Java API 配置(無 XML)

public class JavaApiConfig {
    public static SqlSessionFactory build() {
        // 1. 數據源
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setDriver("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/app_db");
        dataSource.setUsername("user");
        dataSource.setPassword("pwd");
        
        // 2. 環境
        Environment env = new Environment("development", 
            new JdbcTransactionFactory(), dataSource);
        
        // 3. 配置
        Configuration config = new Configuration(env);
        config.addMapper(UserMapper.class);  // 註冊 Mapper 接口
        config.setMapUnderscoreToCamelCase(true);  // 下劃線轉駝峰
        
        // 4. 構建
        return new SqlSessionFactoryBuilder().build(config);
    }
}

多環境隔離策略

安全加固建議

  1. 密鑰不入代碼:JVM 參數 -Dapp.crypto.key=...、環境變量、密鑰管理服務
  2. 升級 AES-GCM:提供完整性驗證,防篡改
  3. 簽名驗證:配置文件簽名,啟動時驗證防替換
  4. 最小權限原則:數據庫用戶僅擁有必要表的增刪改查權限
  5. 連接池監控:定期檢查連接洩漏、異常佔用

總結

CS 架構下 MyBatis 部署的核心難點是**配置安全與輕量初始化**。本文方案通過對稱加密配置文件 + 運行時解密流構建 SqlSessionFactory,實現了密碼不落地明文、單文件多環境、BS/CS 共用 Mapper。生產環境建議進一步引入密鑰管理服務、配置簽名驗證、連接池監控,構建纵深防禦體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
策略實現方式優點缺點
多配置文件mybatis-dev.xml, mybatis-prod.xml + 系統屬性切換清晰分離需維護多份
單文件多 environment<environments default="${env}"> 多個 <environment id="dev">單文件管理密碼仍明文
外部屬性文件佔位符${db.url} 從 properties 讀取,配合加密敏感信息外部化需額外解析層
加密文件 + 環境變量密鑰本文方案:config.data + DECRYPT_KEY 從環境讀取安全、單文件、多環境共用邏輯需密鑰管理