You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Play Framework动态数据库连接实现方案咨询

动态创建账户专属数据库连接(Play Framework)

要实现按账户动态创建数据库连接并缓存,确保其行为与默认数据库一致,可按以下步骤操作:

1. 定义账户数据库配置模型

首先在默认数据库中存储账户的数据库连接信息,创建对应的实体类:

import io.ebean.Model;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Account extends Model {
    @Id
    public Long id;
    public String dbDriver; // 如org.postgresql.Driver
    public String dbUrl;    // 如jdbc:postgresql://host:port/dbname
    public String dbUser;
    public String dbPassword;
}

2. 实现动态数据库管理类

创建一个单例类,负责缓存连接、创建动态数据库及Ebean实例:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.config.ServerConfig;
import play.db.Database;
import play.db.DefaultDatabase;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class DynamicDatabaseManager {
    private static final DynamicDatabaseManager INSTANCE = new DynamicDatabaseManager();
    private final Map<Long, Database> dbCache = new ConcurrentHashMap<>();
    private final Map<Long, EbeanServer> ebeanCache = new ConcurrentHashMap<>();

    private DynamicDatabaseManager() {}

    public static DynamicDatabaseManager getInstance() {
        return INSTANCE;
    }

    // 获取或创建账户专属数据库连接
    public Database getDatabase(Long accountId) {
        return dbCache.computeIfAbsent(accountId, this::buildDatabase);
    }

    // 获取或创建账户专属Ebean服务器
    public EbeanServer getEbeanServer(Long accountId) {
        return ebeanCache.computeIfAbsent(accountId, this::buildEbeanServer);
    }

    // 构建数据库连接(基于HikariCP,与Play默认池一致)
    private Database buildDatabase(Long accountId) {
        Account account = Account.find.byId(accountId);
        if (account == null) {
            throw new IllegalArgumentException("账户不存在:" + accountId);
        }

        HikariConfig poolConfig = new HikariConfig();
        poolConfig.setDriverClassName(account.dbDriver);
        poolConfig.setJdbcUrl(account.dbUrl);
        poolConfig.setUsername(account.dbUser);
        poolConfig.setPassword(account.dbPassword);
        // 配置连接池参数(与application.conf中默认配置对齐)
        poolConfig.setMaximumPoolSize(10);
        poolConfig.setMinimumIdle(2);
        poolConfig.setConnectionTimeout(30000);

        HikariDataSource dataSource = new HikariDataSource(poolConfig);
        String dbName = "database" + accountId;
        return new DefaultDatabase(dbName, dataSource, null, null);
    }

    // 构建Ebean服务器(与默认Ebean配置行为一致)
    private EbeanServer buildEbeanServer(Long accountId) {
        Database db = getDatabase(accountId);
        String serverName = "database" + accountId;

        ServerConfig serverConfig = new ServerConfig();
        serverConfig.setName(serverName);
        serverConfig.setDataSource(db.getDataSource());
        serverConfig.addPackage("models"); // 扫描模型类,与默认配置一致
        serverConfig.setDdlGenerate(false); // 按需开启DDL生成
        serverConfig.setDdlRun(false);

        return EbeanServerFactory.create(serverConfig);
    }

    // 清理账户连接(删除账户时调用,避免资源泄漏)
    public void cleanup(Long accountId) {
        Database db = dbCache.remove(accountId);
        if (db != null) db.shutdown();

        EbeanServer server = ebeanCache.remove(accountId);
        if (server != null) server.shutdown();
    }
}

3. 在控制器中使用动态连接

在业务代码中调用管理类获取连接,支持JDBC或Ebean ORM操作:

import io.ebean.EbeanServer;
import play.db.Database;
import play.mvc.Controller;
import play.mvc.Result;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class AccountDataController extends Controller {

    public Result fetchAccountData(Long accountId) {
        // 获取动态数据库连接(JDBC方式)
        Database db = DynamicDatabaseManager.getInstance().getDatabase(accountId);
        try (Connection conn = db.getConnection()) {
            String sql = "SELECT * FROM user_data WHERE account_id = ?";
            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
                stmt.setLong(1, accountId);
                try (ResultSet rs = stmt.executeQuery()) {
                    // 处理查询结果
                    return ok("获取数据成功");
                }
            }
        } catch (SQLException e) {
            return internalServerError("数据库错误:" + e.getMessage());
        }

        // 或使用Ebean ORM方式
        // EbeanServer ebean = DynamicDatabaseManager.getInstance().getEbeanServer(accountId);
        // List<UserData> dataList = ebean.find(UserData.class).where().eq("account_id", accountId).findList();
        // return ok(Json.toJson(dataList));
    }
}

关键注意事项

  • 缓存机制:使用ConcurrentHashMap实现线程安全的连接缓存,避免重复创建同名连接。
  • 连接池一致性:采用Play默认的HikariCP连接池,确保动态连接与默认数据库的池行为一致。
  • 资源清理:删除账户时务必调用cleanup方法关闭连接池和Ebean服务器,防止资源泄漏。
  • 错误处理:添加账户不存在、连接失败等场景的异常处理,提升系统稳定性。
  • 配置对齐:动态连接池的参数(如最大连接数)应与application.conf中默认数据库的配置保持一致。

内容的提问来源于stack exchange,提问作者Philips

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 12:15:42