①逆向工程插件支持多数据源

②打包体积缩小
This commit is contained in:
张家训
2026-06-16 18:19:04 +08:00
parent a641796a0b
commit 711193a503
8 changed files with 338 additions and 139 deletions
@@ -132,6 +132,18 @@ public class MyBatisGeneratorPlugin implements Plugin {
public String generate(List<String> tables, String modelDir, String xmlDir, String mapperDir) throws Exception {
return generatorService.generateByTableNames(tables, true, modelDir, xmlDir, mapperDir);
}
@Override
public List<String> getDatasourceNames() {
return generatorService.getDatasourceNames();
}
@Override
public void switchDatasource(String name) {
generatorService.switchDatasource(name);
}
@Override
public String getCurrentDatasourceName() {
return generatorService.getCurrentDatasourceName();
}
};
javafx.application.Platform.runLater(() -> {
@@ -53,6 +53,9 @@ public class MyBatisGeneratorWindow {
private TextField mapperDirField;
private TreeView<String> projectTree;
private ToggleGroup fillTargetGroup;
private ComboBox<String> datasourceCmb;
private Label dbUrlLabel;
private Label dbUserLabel;
/** 表项(含复选框) */
public static class TableItem {
@@ -79,6 +82,12 @@ public class MyBatisGeneratorWindow {
String getDbUrl();
String getDbUsername();
String generate(List<String> tables, String modelDir, String xmlDir, String mapperDir) throws Exception;
/** 可用的数据源名称列表 */
List<String> getDatasourceNames();
/** 切换数据源 */
void switchDatasource(String name);
/** 当前选中的数据源名称 */
String getCurrentDatasourceName();
}
public MyBatisGeneratorWindow(GeneratorCallback callback, FileOperationHelper fileOperationHelper,
@@ -177,19 +186,69 @@ public class MyBatisGeneratorWindow {
}
private HBox createDbInfoSection() {
HBox box = new HBox(20);
HBox box = new HBox(15);
box.setPadding(new Insets(8, 12, 8, 12));
box.setStyle("-fx-background-color: #e8f0fe; -fx-background-radius: 4;");
box.setAlignment(Pos.CENTER_LEFT);
Label urlLabel = new Label("数据库: " + callback.getDbUrl());
urlLabel.setFont(Font.font("Consolas", fsBody()));
Label userLabel = new Label("用户: " + callback.getDbUsername());
userLabel.setFont(Font.font("Microsoft YaHei", fsBody()));
Label dsLabel = new Label("数据源:");
dsLabel.setFont(Font.font("Microsoft YaHei", fsBody()));
box.getChildren().addAll(urlLabel, userLabel);
datasourceCmb = new ComboBox<>();
datasourceCmb.setPrefWidth(160);
datasourceCmb.setStyle("-fx-font-size: " + fsBody() + "px;");
// 加载数据源列表
refreshDatasourceList();
datasourceCmb.setOnAction(e -> {
String selected = datasourceCmb.getValue();
if (selected != null) {
callback.switchDatasource("默认配置".equals(selected) ? null : selected);
updateDbInfo();
}
});
dbUrlLabel = new Label();
dbUrlLabel.setFont(Font.font("Consolas", fsBody() - 1));
dbUserLabel = new Label();
dbUserLabel.setFont(Font.font("Microsoft YaHei", fsBody() - 1));
updateDbInfo();
Button refreshDsBtn = new Button("刷新");
refreshDsBtn.setStyle("-fx-font-size: " + (fsBody() - 1) + "px; -fx-padding: 2 8;");
refreshDsBtn.setOnAction(e -> {
refreshDatasourceList();
updateDbInfo();
});
VBox infoBox = new VBox(3, dbUrlLabel, dbUserLabel);
box.getChildren().addAll(dsLabel, datasourceCmb, refreshDsBtn, infoBox);
return box;
}
private void refreshDatasourceList() {
String current = datasourceCmb.getValue();
datasourceCmb.getItems().clear();
datasourceCmb.getItems().add("默认配置");
datasourceCmb.getItems().addAll(callback.getDatasourceNames());
if (current != null && datasourceCmb.getItems().contains(current)) {
datasourceCmb.setValue(current);
} else {
String dsName = callback.getCurrentDatasourceName();
if (dsName != null && datasourceCmb.getItems().contains(dsName)) {
datasourceCmb.setValue(dsName);
} else {
datasourceCmb.setValue("默认配置");
}
}
}
private void updateDbInfo() {
dbUrlLabel.setText("数据库: " + callback.getDbUrl());
dbUserLabel.setText("用户: " + callback.getDbUsername());
}
// ==================== 左侧:项目路径树 ====================
private VBox createProjectTreePanel() {
@@ -1,5 +1,7 @@
package com.example.workutils.biz.mybatisgenerator.service.generator;
import com.example.workutils.system.config.DatasourceConfig;
import com.example.workutils.system.config.DatasourceConfigManager;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
@@ -25,12 +27,40 @@ public class MyBatisGeneratorService {
private static final String CONFIG_FILE = "mybatis-generator.xml";
private static final String PROPERTIES_FILE = "mybatis-generator.properties";
private final DatasourceConfigManager datasourceMgr;
/** 当前选中的数据源名称(null = 使用外部配置或内置默认配置) */
private String currentDatasourceName;
public MyBatisGeneratorService(DatasourceConfigManager datasourceMgr) {
this.datasourceMgr = datasourceMgr;
}
// ==================== 数据源切换 ====================
public List<String> getDatasourceNames() {
return datasourceMgr.getDatasourceNames();
}
public DatasourceConfig getCurrentDatasource() {
if (currentDatasourceName != null) {
return datasourceMgr.findByName(currentDatasourceName);
}
return null;
}
public String getCurrentDatasourceName() {
return currentDatasourceName;
}
public void switchDatasource(String name) {
this.currentDatasourceName = name;
log.info("已切换数据源: {}", name != null ? name : "默认配置");
}
// ==================== 生成入口 ====================
/**
* 根据表名列表生成Mapper文件
*
* @param tableNames 表名列表
* @param overwrite 是否覆盖已有文件
* @return 生成结果信息
*/
public String generateByTableNames(List<String> tableNames, boolean overwrite) throws Exception {
return generateInternal(tableNames, overwrite, null, null, null);
@@ -46,40 +76,27 @@ public class MyBatisGeneratorService {
/**
* 根据表名通配符生成Mapper文件
* 先查询数据库中匹配的表,再进行逆向工程
*
* @param pattern 表名通配符(如 user_%)
* @param overwrite 是否覆盖已有文件
* @return 生成结果信息
*/
public String generateByPattern(String pattern, boolean overwrite) throws Exception {
List<String> matchedTables = queryTablesByPattern(pattern);
if (matchedTables.isEmpty()) {
return "未找到匹配通配符 [" + pattern + "] 的表";
}
log.info("通配符 [{}] 匹配到 {} 张表: {}", pattern, matchedTables.size(), matchedTables);
return generateInternal(matchedTables, overwrite, null, null, null);
}
// ==================== 数据库查询 ====================
/**
* 根据通配符查询数据库中的表
*
* @param pattern 表名通配符(支持 % 和 _ 通配符)
* @return 匹配的表名列表
*/
public List<String> queryTablesByPattern(String pattern) throws Exception {
List<String> tables = new ArrayList<>();
Properties props = getConfigurationProperties();
String url = props.getProperty("db_url");
String username = props.getProperty("db_username");
String password = props.getProperty("db_password");
String dbName = extractDatabaseName(url);
try (Connection conn = DriverManager.getConnection(url, username, password)) {
DbConnection db = getDbConnection();
String dbName = extractDatabaseName(db.url);
try (Connection conn = DriverManager.getConnection(db.url, db.username, db.password)) {
String sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME LIKE ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, dbName);
@@ -91,26 +108,18 @@ public class MyBatisGeneratorService {
}
}
}
return tables;
}
/**
* 查询数据库中所有表
*
* @return 所有表名列表
*/
public List<String> queryAllTables() throws Exception {
List<String> tables = new ArrayList<>();
Properties props = getConfigurationProperties();
String url = props.getProperty("db_url");
String username = props.getProperty("db_username");
String password = props.getProperty("db_password");
String dbName = extractDatabaseName(url);
try (Connection conn = DriverManager.getConnection(url, username, password)) {
DbConnection db = getDbConnection();
String dbName = extractDatabaseName(db.url);
try (Connection conn = DriverManager.getConnection(db.url, db.username, db.password)) {
String sql = "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, dbName);
@@ -121,10 +130,39 @@ public class MyBatisGeneratorService {
}
}
}
return tables;
}
/**
* 获取当前生效的数据库连接信息
* 优先级: 选中的数据源 > 外部配置(workutils.config) > 内置classpath属性
*/
private DbConnection getDbConnection() {
DatasourceConfig ds = getCurrentDatasource();
if (ds != null) {
return new DbConnection(ds.getUrl(), ds.getUsername(), ds.getPassword());
}
// 回退 classpath 配置
Properties props = getClasspathProperties();
return new DbConnection(
props.getProperty("db_url", ""),
props.getProperty("db_username", ""),
props.getProperty("db_password", "")
);
}
/** 内部数据类:数据库连接信息 */
private static class DbConnection {
final String url;
final String username;
final String password;
DbConnection(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
}
}
/**
* 从JDBC URL中提取数据库名
*/
@@ -352,9 +390,22 @@ public class MyBatisGeneratorService {
}
/**
* 获取配置属性
* 获取配置属性(优先使用选中的数据源,回退到classpath)
*/
public Properties getConfigurationProperties() {
Properties props = getClasspathProperties();
// 如果选中了数据源,覆盖连接信息
DatasourceConfig ds = getCurrentDatasource();
if (ds != null) {
if (ds.getDriver() != null) props.setProperty("db_driver", ds.getDriver());
if (ds.getUrl() != null) props.setProperty("db_url", ds.getUrl());
if (ds.getUsername() != null) props.setProperty("db_username", ds.getUsername());
if (ds.getPassword() != null) props.setProperty("db_password", ds.getPassword());
}
return props;
}
private Properties getClasspathProperties() {
Properties props = new Properties();
try {
props.load(getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE));
@@ -364,38 +415,27 @@ public class MyBatisGeneratorService {
return props;
}
/**
* 获取数据库URL
*/
public String getDatabaseUrl() {
return getConfigurationProperties().getProperty("db_url", "");
DatasourceConfig ds = getCurrentDatasource();
if (ds != null) return ds.getUrl();
return getClasspathProperties().getProperty("db_url", "");
}
/**
* 获取数据库用户名
*/
public String getDatabaseUsername() {
return getConfigurationProperties().getProperty("db_username", "");
DatasourceConfig ds = getCurrentDatasource();
if (ds != null) return ds.getUsername();
return getClasspathProperties().getProperty("db_username", "");
}
/**
* 获取Model包路径
*/
public String getModelPackage() {
return getConfigurationProperties().getProperty("model_package", "");
return getClasspathProperties().getProperty("model_package", "");
}
/**
* 获取Mapper包路径
*/
public String getMapperPackage() {
return getConfigurationProperties().getProperty("dao_package", "");
return getClasspathProperties().getProperty("dao_package", "");
}
/**
* 获取SQL Mapper包路径
*/
public String getSqlMapperPackage() {
return getConfigurationProperties().getProperty("sqlmapper_package", "");
return getClasspathProperties().getProperty("sqlmapper_package", "");
}
}
@@ -0,0 +1,25 @@
package com.example.workutils.system.config;
import lombok.Data;
/**
* 单条数据源配置
*/
@Data
public class DatasourceConfig {
/** 显示名称(如"开发环境"、"SIT环境") */
private String name;
/** JDBC 驱动类 */
private String driver = "com.mysql.jdbc.Driver";
/** JDBC URL */
private String url;
/** 用户名 */
private String username;
/** 密码 */
private String password;
}
@@ -0,0 +1,98 @@
package com.example.workutils.system.config;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
/**
* 多数据源配置管理器
* <p>
* 从 data/datasources.json 加载数据源列表,供 MyBatis 逆向工程插件选择使用。
* 首次运行时自动从 classpath 模板复制到 data/ 目录。
*/
@Slf4j
@Component
public class DatasourceConfigManager {
private static final String DATASOURCE_FILE = "data/datasources.json";
private static final String TEMPLATE_PATH = "templates/datasources.json";
private final List<DatasourceConfig> datasources = new ArrayList<>();
private Path configFile;
@PostConstruct
void init() {
configFile = Paths.get(System.getProperty("user.dir"), DATASOURCE_FILE);
ensureConfigExists();
reload();
}
/** 确保数据源配置文件存在 */
private void ensureConfigExists() {
if (Files.exists(configFile)) return;
try {
Files.createDirectories(configFile.getParent());
try (InputStream is = getClass().getClassLoader().getResourceAsStream(TEMPLATE_PATH)) {
if (is != null) {
Files.copy(is, configFile);
log.info("已从模板复制数据源配置: {}", configFile.toAbsolutePath());
return;
}
}
} catch (IOException e) {
log.warn("复制数据源模板失败: {}", e.getMessage());
}
// 兜底:创建空数组
try {
Files.write(configFile, "[]".getBytes(StandardCharsets.UTF_8));
log.info("已创建空数据源配置: {}", configFile.toAbsolutePath());
} catch (IOException e) {
log.error("创建数据源配置文件失败", e);
}
}
/** 重新加载数据源配置 */
public synchronized void reload() {
datasources.clear();
if (!Files.exists(configFile)) return;
try {
String json = new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8);
List<DatasourceConfig> loaded = JSON.parseArray(json, DatasourceConfig.class);
if (loaded != null) {
datasources.addAll(loaded);
}
log.info("已加载 {} 套数据源配置", datasources.size());
} catch (IOException e) {
log.error("解析数据源配置失败: {}", e.getMessage());
}
}
/** 获取所有数据源名称列表 */
public List<String> getDatasourceNames() {
List<String> names = new ArrayList<>();
for (DatasourceConfig ds : datasources) {
names.add(ds.getName());
}
return names;
}
/** 根据名称查找数据源配置 */
public DatasourceConfig findByName(String name) {
return datasources.stream()
.filter(ds -> ds.getName().equals(name))
.findFirst().orElse(null);
}
/** 获取配置文件的路径(供设置窗口打开编辑) */
public Path getConfigFile() {
return configFile;
}
}
@@ -0,0 +1,23 @@
[
{
"name": "开发环境",
"driver": "com.mysql.jdbc.Driver",
"url": "jdbc:mysql://localhost:3306/disburse?useUnicode=true&characterEncoding=utf8&useSSL=false",
"username": "root",
"password": "1Qaz@4772"
},
{
"name": "SIT环境",
"driver": "com.mysql.jdbc.Driver",
"url": "jdbc:mysql://sit-server:3306/disburse?useUnicode=true&characterEncoding=utf8&useSSL=false",
"username": "sit_user",
"password": ""
},
{
"name": "生产环境(只读)",
"driver": "com.mysql.jdbc.Driver",
"url": "jdbc:mysql://prod-server:3306/disburse?useUnicode=true&characterEncoding=utf8&useSSL=false",
"username": "readonly",
"password": ""
}
]