sql setup
This commit is contained in:
@@ -32,6 +32,20 @@
|
|||||||
},
|
},
|
||||||
"rpc": {
|
"rpc": {
|
||||||
"timeout_ms": 5000
|
"timeout_ms": 5000
|
||||||
|
},
|
||||||
|
"mysql": {
|
||||||
|
"enabled": false,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 3306,
|
||||||
|
"database": "kweebec",
|
||||||
|
"user": "kweebec",
|
||||||
|
"password": null,
|
||||||
|
"max_pool_size": 10,
|
||||||
|
"min_idle": 2,
|
||||||
|
"connection_timeout_ms": 5000,
|
||||||
|
"idle_timeout_ms": 600000,
|
||||||
|
"max_lifetime_ms": 1800000,
|
||||||
|
"use_ssl": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -78,6 +92,23 @@
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `timeout_ms` | `5000` | How long an RPC request waits for a response before completing the future exceptionally with `TimeoutException`. |
|
| `timeout_ms` | `5000` | How long an RPC request waits for a response before completing the future exceptionally with `TimeoutException`. |
|
||||||
|
|
||||||
|
### mysql
|
||||||
|
|
||||||
|
| Field | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `enabled` | `false` | Master switch. When false, `getDatabase()` returns null and no pool is created. Set to true once you've put real credentials in place. |
|
||||||
|
| `host` | `"127.0.0.1"` | MySQL host. |
|
||||||
|
| `port` | `3306` | MySQL port. |
|
||||||
|
| `database` | `"kweebec"` | Database (schema) name. Must exist before NetworkCore boots; we don't auto-create databases. |
|
||||||
|
| `user` | `"kweebec"` | MySQL user. |
|
||||||
|
| `password` | `null` | MySQL password. Prefer the env var. |
|
||||||
|
| `max_pool_size` | `10` | Hikari `maximumPoolSize`. |
|
||||||
|
| `min_idle` | `2` | Hikari `minimumIdle`. |
|
||||||
|
| `connection_timeout_ms` | `5000` | Hikari `connectionTimeout`. |
|
||||||
|
| `idle_timeout_ms` | `600000` (10 min) | Hikari `idleTimeout`. |
|
||||||
|
| `max_lifetime_ms` | `1800000` (30 min) | Hikari `maxLifetime`. Keep below MySQL's `wait_timeout`. |
|
||||||
|
| `use_ssl` | `false` | Adds `useSSL=true` to the JDBC URL. Turn on whenever MySQL is on a different host than the Hytale server. |
|
||||||
|
|
||||||
## Environment variable overrides
|
## Environment variable overrides
|
||||||
|
|
||||||
These take precedence over the config file at boot time. Use them for secrets so the config file stays safe to check into version control.
|
These take precedence over the config file at boot time. Use them for secrets so the config file stays safe to check into version control.
|
||||||
@@ -86,6 +117,7 @@ These take precedence over the config file at boot time. Use them for secrets so
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `REDIS_PASSWORD` | `redis.password` |
|
| `REDIS_PASSWORD` | `redis.password` |
|
||||||
| `NETWORK_AUTH_TOKEN` | `network.auth_token` |
|
| `NETWORK_AUTH_TOKEN` | `network.auth_token` |
|
||||||
|
| `MYSQL_PASSWORD` | `mysql.password` |
|
||||||
|
|
||||||
## Recommended setup per environment
|
## Recommended setup per environment
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# Database
|
||||||
|
|
||||||
|
Shared MySQL pool, exposed to all plugins via `DatabaseService` from NetworkCore. Use this for durable state that needs to survive restarts and be queried later: player profiles, punishment history, currency balances, leaderboards, anything you'd run a SQL query against.
|
||||||
|
|
||||||
|
For ephemeral observability data (counters, latency), use the metrics layer instead. See [04_Metrics.md](04_Metrics.md).
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface DatabaseService {
|
||||||
|
DataSource getDataSource();
|
||||||
|
boolean isHealthy();
|
||||||
|
int getActiveConnections();
|
||||||
|
int getIdleConnections();
|
||||||
|
int getTotalConnections();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it. We expose the `DataSource` and stay out of your way. Use JDBC directly, jOOQ, MyBatis, JdbcTemplate, whatever you like.
|
||||||
|
|
||||||
|
## Getting a connection
|
||||||
|
|
||||||
|
Always use try-with-resources so connections return to the pool:
|
||||||
|
|
||||||
|
```java
|
||||||
|
DatabaseService db = NetworkCore.getInstance().getDatabase();
|
||||||
|
if (db == null) {
|
||||||
|
// MySQL disabled in NetworkCore config; degrade gracefully
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection c = db.getDataSource().getConnection();
|
||||||
|
PreparedStatement ps = c.prepareStatement("SELECT name, level FROM players WHERE uuid = ?")) {
|
||||||
|
ps.setString(1, uuid.toString());
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return new PlayerRecord(rs.getString("name"), rs.getInt("level"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Never hold a connection across an RPC call, a thread hop, or a wait. Pool exhaustion is the most common way Hikari-backed plugins fall over.
|
||||||
|
|
||||||
|
## Threading
|
||||||
|
|
||||||
|
The pool is fully thread-safe. Get a connection from whichever thread needs it. JDBC calls themselves block, so:
|
||||||
|
|
||||||
|
- Don't run heavy queries on the Hytale world thread - it stops world ticking.
|
||||||
|
- Don't run heavy queries on the MessageBus worker pool - it backs up the bus.
|
||||||
|
- Use a dedicated executor or `CompletableFuture.supplyAsync` for I/O-heavy work.
|
||||||
|
|
||||||
|
## Schema management
|
||||||
|
|
||||||
|
NetworkCore owns the *pool*, not your schema. Each plugin is responsible for creating and migrating its own tables.
|
||||||
|
|
||||||
|
Conventions:
|
||||||
|
- **Table prefix per plugin.** The Staff plugin prefixes everything with `staff_`. Pick something unique to your plugin so two plugins don't fight over `players`.
|
||||||
|
- **Bootstrap on startup.** In your plugin's `start()`, run a `CREATE TABLE IF NOT EXISTS ...` for every table you need. This makes a fresh install just-work without manual setup.
|
||||||
|
- **Migrations as numbered scripts.** When you change a schema, add a `migrations/002_add_email_column.sql` and a tiny runner that tracks applied migrations in a `<plugin>_schema_version` table. Don't reach for Flyway/Liquibase until you've added a third migration - those frameworks earn their weight on real projects, not on the second migration.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
See the [mysql section in 03_Configuration.md](03_Configuration.md#mysql). Default is `enabled: false` so a server that doesn't have MySQL configured doesn't crash on boot. Flip to `true` once you've put credentials in place via `MYSQL_PASSWORD` env var.
|
||||||
|
|
||||||
|
## What you see in /networkcore status
|
||||||
|
|
||||||
|
When MySQL is healthy:
|
||||||
|
```
|
||||||
|
Database: ok (active=2 idle=8 total=10)
|
||||||
|
```
|
||||||
|
|
||||||
|
When disabled:
|
||||||
|
```
|
||||||
|
Database: disabled
|
||||||
|
```
|
||||||
|
|
||||||
|
When configured but failing:
|
||||||
|
```
|
||||||
|
Database: unhealthy
|
||||||
|
```
|
||||||
|
|
||||||
|
If you ever see "unhealthy", check the boot log for the connection error and verify host/port/credentials.
|
||||||
|
|
||||||
|
## What is *not* in the box
|
||||||
|
|
||||||
|
- **No ORM.** Plain `DataSource` only. Pick your own.
|
||||||
|
- **No migration framework.** Bootstrap with `CREATE TABLE IF NOT EXISTS` per plugin.
|
||||||
|
- **No automatic encryption at rest.** That's MySQL's job, not the plugin's. Use MySQL Enterprise's TDE or AWS RDS encryption.
|
||||||
|
- **No read replicas.** Single pool to a single MySQL endpoint. Read replicas are an MySQL Router or ProxySQL concern, not a Hytale plugin concern.
|
||||||
|
|
||||||
|
## Sizing the pool
|
||||||
|
|
||||||
|
`max_pool_size = 10` is fine for a single server with a handful of plugins. Rules of thumb:
|
||||||
|
|
||||||
|
- Each plugin doing heavy queries needs maybe 2-4 connections worth of headroom.
|
||||||
|
- The pool is per Hytale-server-instance, not network-wide. If you have 5 servers each with pool=10, MySQL needs to handle 50 concurrent connections.
|
||||||
|
- Make sure MySQL's `max_connections` is comfortably above the *sum* of all pools that point at it.
|
||||||
|
|
||||||
|
Don't crank `max_pool_size` to "fix" pool exhaustion. Pool exhaustion is almost always a leaked connection or a query that's holding the connection too long. Find the leak, then re-evaluate sizing.
|
||||||
@@ -15,6 +15,7 @@ If you're building a plugin that needs to talk to other servers, record stats, o
|
|||||||
7. [07_RPC.md](07_RPC.md) - request/response between servers
|
7. [07_RPC.md](07_RPC.md) - request/response between servers
|
||||||
8. [08_Commands.md](08_Commands.md) - the /networkcore admin command
|
8. [08_Commands.md](08_Commands.md) - the /networkcore admin command
|
||||||
9. [09_Operations_And_Security.md](09_Operations_And_Security.md) - auth token, what's not secured, ops checks
|
9. [09_Operations_And_Security.md](09_Operations_And_Security.md) - auth token, what's not secured, ops checks
|
||||||
|
10. [10_Database.md](10_Database.md) - shared MySQL pool (HikariCP)
|
||||||
|
|
||||||
## Quick reference
|
## Quick reference
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,18 @@
|
|||||||
<version>2.11.0</version>
|
<version>2.11.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.zaxxer</groupId>
|
||||||
|
<artifactId>HikariCP</artifactId>
|
||||||
|
<version>5.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>8.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.findbugs</groupId>
|
<groupId>com.google.code.findbugs</groupId>
|
||||||
<artifactId>jsr305</artifactId>
|
<artifactId>jsr305</artifactId>
|
||||||
@@ -99,6 +111,10 @@
|
|||||||
<pattern>org.reactivestreams</pattern>
|
<pattern>org.reactivestreams</pattern>
|
||||||
<shadedPattern>${shade.base}.reactivestreams</shadedPattern>
|
<shadedPattern>${shade.base}.reactivestreams</shadedPattern>
|
||||||
</relocation>
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>com.zaxxer.hikari</pattern>
|
||||||
|
<shadedPattern>${shade.base}.hikari</shadedPattern>
|
||||||
|
</relocation>
|
||||||
</relocations>
|
</relocations>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.kewwbec.networkcore;
|
|||||||
import com.hypixel.hytale.logger.HytaleLogger;
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
|
import net.kewwbec.networkcore.api.DatabaseService;
|
||||||
import net.kewwbec.networkcore.api.MessageBus;
|
import net.kewwbec.networkcore.api.MessageBus;
|
||||||
import net.kewwbec.networkcore.api.MetricsService;
|
import net.kewwbec.networkcore.api.MetricsService;
|
||||||
import net.kewwbec.networkcore.api.RpcClient;
|
import net.kewwbec.networkcore.api.RpcClient;
|
||||||
@@ -11,6 +12,7 @@ import net.kewwbec.networkcore.bus.RedisMessageBus;
|
|||||||
import net.kewwbec.networkcore.command.NetworkCoreCommand;
|
import net.kewwbec.networkcore.command.NetworkCoreCommand;
|
||||||
import net.kewwbec.networkcore.config.ConfigLoader;
|
import net.kewwbec.networkcore.config.ConfigLoader;
|
||||||
import net.kewwbec.networkcore.config.CoreConfig;
|
import net.kewwbec.networkcore.config.CoreConfig;
|
||||||
|
import net.kewwbec.networkcore.db.MySqlDatabaseService;
|
||||||
import net.kewwbec.networkcore.internal.ServerIdGenerator;
|
import net.kewwbec.networkcore.internal.ServerIdGenerator;
|
||||||
import net.kewwbec.networkcore.internal.ServiceRegistry;
|
import net.kewwbec.networkcore.internal.ServiceRegistry;
|
||||||
import net.kewwbec.networkcore.metrics.BuiltInMetrics;
|
import net.kewwbec.networkcore.metrics.BuiltInMetrics;
|
||||||
@@ -39,6 +41,7 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
@Nullable private RedisMessageBus messageBus;
|
@Nullable private RedisMessageBus messageBus;
|
||||||
@Nullable private RedisServerRegistry serverRegistry;
|
@Nullable private RedisServerRegistry serverRegistry;
|
||||||
@Nullable private RpcClientImpl rpcClient;
|
@Nullable private RpcClientImpl rpcClient;
|
||||||
|
@Nullable private MySqlDatabaseService database;
|
||||||
|
|
||||||
public NetworkCore(@Nonnull JavaPluginInit init) {
|
public NetworkCore(@Nonnull JavaPluginInit init) {
|
||||||
super(init);
|
super(init);
|
||||||
@@ -101,6 +104,22 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
this.rpcClient = new RpcClientImpl(messageBus, serverId, config.rpc.timeout_ms, metricsImpl);
|
this.rpcClient = new RpcClientImpl(messageBus, serverId, config.rpc.timeout_ms, metricsImpl);
|
||||||
services.register(RpcClient.class, rpcClient);
|
services.register(RpcClient.class, rpcClient);
|
||||||
|
|
||||||
|
if (config.mysql.enabled) {
|
||||||
|
try {
|
||||||
|
this.database = new MySqlDatabaseService(config.mysql);
|
||||||
|
if (database.isHealthy()) {
|
||||||
|
services.register(DatabaseService.class, database);
|
||||||
|
} else {
|
||||||
|
LOGGER.at(Level.WARNING).log("MySQL DatabaseService initialized but unhealthy; not registered");
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
((HytaleLogger.Api) LOGGER.at(Level.SEVERE).withCause(e)).log("Failed to initialize MySQL DatabaseService");
|
||||||
|
this.database = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.at(Level.INFO).log("MySQL disabled in config (mysql.enabled=false); no DatabaseService registered");
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.at(Level.INFO).log("NetworkCore started");
|
LOGGER.at(Level.INFO).log("NetworkCore started");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +137,10 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
try { messageBus.close(); } catch (RuntimeException ignored) {}
|
try { messageBus.close(); } catch (RuntimeException ignored) {}
|
||||||
messageBus = null;
|
messageBus = null;
|
||||||
}
|
}
|
||||||
|
if (database != null) {
|
||||||
|
try { database.close(); } catch (RuntimeException ignored) {}
|
||||||
|
database = null;
|
||||||
|
}
|
||||||
if (exporter != null) {
|
if (exporter != null) {
|
||||||
try { exporter.stop(); } catch (RuntimeException ignored) {}
|
try { exporter.stop(); } catch (RuntimeException ignored) {}
|
||||||
exporter = null;
|
exporter = null;
|
||||||
@@ -144,6 +167,8 @@ public final class NetworkCore extends JavaPlugin {
|
|||||||
public ServerRegistry getServerRegistry() { return services.get(ServerRegistry.class); }
|
public ServerRegistry getServerRegistry() { return services.get(ServerRegistry.class); }
|
||||||
@Nullable
|
@Nullable
|
||||||
public RpcClient getRpc() { return services.get(RpcClient.class); }
|
public RpcClient getRpc() { return services.get(RpcClient.class); }
|
||||||
|
@Nullable
|
||||||
|
public DatabaseService getDatabase() { return services.get(DatabaseService.class); }
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public CoreConfig getConfigSnapshot() {
|
public CoreConfig getConfigSnapshot() {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package net.kewwbec.networkcore.api;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
public interface DatabaseService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pooled DataSource. Callers should always use try-with-resources:
|
||||||
|
*
|
||||||
|
* try (Connection c = service.getDataSource().getConnection()) {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
DataSource getDataSource();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the pool is open and at least one connection has been established successfully.
|
||||||
|
*/
|
||||||
|
boolean isHealthy();
|
||||||
|
|
||||||
|
int getActiveConnections();
|
||||||
|
int getIdleConnections();
|
||||||
|
int getTotalConnections();
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef;
|
|||||||
import com.hypixel.hytale.server.core.universe.world.World;
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
import net.kewwbec.networkcore.NetworkCore;
|
import net.kewwbec.networkcore.NetworkCore;
|
||||||
|
import net.kewwbec.networkcore.api.DatabaseService;
|
||||||
import net.kewwbec.networkcore.api.MessageBus;
|
import net.kewwbec.networkcore.api.MessageBus;
|
||||||
import net.kewwbec.networkcore.api.ServerInfo;
|
import net.kewwbec.networkcore.api.ServerInfo;
|
||||||
import net.kewwbec.networkcore.api.ServerRegistry;
|
import net.kewwbec.networkcore.api.ServerRegistry;
|
||||||
@@ -65,6 +66,19 @@ public final class NetworkCoreCommand extends AbstractCommandCollection {
|
|||||||
} else {
|
} else {
|
||||||
context.sendMessage(Message.raw("Metrics: disabled").color(Color.WHITE));
|
context.sendMessage(Message.raw("Metrics: disabled").color(Color.WHITE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatabaseService db = plugin.getDatabase();
|
||||||
|
if (db == null) {
|
||||||
|
context.sendMessage(Message.raw("Database: disabled").color(Color.WHITE));
|
||||||
|
} else if (!db.isHealthy()) {
|
||||||
|
context.sendMessage(Message.raw("Database: unhealthy").color(Color.RED));
|
||||||
|
} else {
|
||||||
|
context.sendMessage(Message.raw(
|
||||||
|
"Database: ok (active=" + db.getActiveConnections() +
|
||||||
|
" idle=" + db.getIdleConnections() +
|
||||||
|
" total=" + db.getTotalConnections() + ")"
|
||||||
|
).color(Color.WHITE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public final class ConfigLoader {
|
|||||||
if (parsed.network == null) parsed.network = new CoreConfig.NetworkSection();
|
if (parsed.network == null) parsed.network = new CoreConfig.NetworkSection();
|
||||||
if (parsed.metrics == null) parsed.metrics = new CoreConfig.MetricsSection();
|
if (parsed.metrics == null) parsed.metrics = new CoreConfig.MetricsSection();
|
||||||
if (parsed.rpc == null) parsed.rpc = new CoreConfig.RpcSection();
|
if (parsed.rpc == null) parsed.rpc = new CoreConfig.RpcSection();
|
||||||
|
if (parsed.mysql == null) parsed.mysql = new CoreConfig.MysqlSection();
|
||||||
return applyEnvOverrides(parsed);
|
return applyEnvOverrides(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +49,10 @@ public final class ConfigLoader {
|
|||||||
if (envToken != null && !envToken.isEmpty()) {
|
if (envToken != null && !envToken.isEmpty()) {
|
||||||
config.network.auth_token = envToken;
|
config.network.auth_token = envToken;
|
||||||
}
|
}
|
||||||
|
String envMysqlPw = System.getenv("MYSQL_PASSWORD");
|
||||||
|
if (envMysqlPw != null && !envMysqlPw.isEmpty()) {
|
||||||
|
config.mysql.password = envMysqlPw;
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public final class CoreConfig {
|
|||||||
public NetworkSection network = new NetworkSection();
|
public NetworkSection network = new NetworkSection();
|
||||||
public MetricsSection metrics = new MetricsSection();
|
public MetricsSection metrics = new MetricsSection();
|
||||||
public RpcSection rpc = new RpcSection();
|
public RpcSection rpc = new RpcSection();
|
||||||
|
public MysqlSection mysql = new MysqlSection();
|
||||||
|
|
||||||
public static final class ServerSection {
|
public static final class ServerSection {
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -42,4 +43,20 @@ public final class CoreConfig {
|
|||||||
public static final class RpcSection {
|
public static final class RpcSection {
|
||||||
public long timeout_ms = 5000L;
|
public long timeout_ms = 5000L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class MysqlSection {
|
||||||
|
public boolean enabled = false;
|
||||||
|
public String host = "127.0.0.1";
|
||||||
|
public int port = 3306;
|
||||||
|
public String database = "kweebec";
|
||||||
|
public String user = "kweebec";
|
||||||
|
@Nullable
|
||||||
|
public String password = null;
|
||||||
|
public int max_pool_size = 10;
|
||||||
|
public int min_idle = 2;
|
||||||
|
public long connection_timeout_ms = 5_000L;
|
||||||
|
public long idle_timeout_ms = 600_000L;
|
||||||
|
public long max_lifetime_ms = 1_800_000L;
|
||||||
|
public boolean use_ssl = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package net.kewwbec.networkcore.db;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import net.kewwbec.networkcore.api.DatabaseService;
|
||||||
|
import net.kewwbec.networkcore.config.CoreConfig;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public final class MySqlDatabaseService implements DatabaseService {
|
||||||
|
|
||||||
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
private volatile boolean healthy;
|
||||||
|
|
||||||
|
public MySqlDatabaseService(@Nonnull CoreConfig.MysqlSection cfg) {
|
||||||
|
HikariConfig hc = new HikariConfig();
|
||||||
|
String jdbcUrl = String.format(
|
||||||
|
"jdbc:mysql://%s:%d/%s?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=%s&allowPublicKeyRetrieval=true",
|
||||||
|
cfg.host, cfg.port, cfg.database, cfg.use_ssl
|
||||||
|
);
|
||||||
|
hc.setJdbcUrl(jdbcUrl);
|
||||||
|
hc.setUsername(cfg.user);
|
||||||
|
if (cfg.password != null) {
|
||||||
|
hc.setPassword(cfg.password);
|
||||||
|
}
|
||||||
|
hc.setDriverClassName("com.mysql.cj.jdbc.Driver");
|
||||||
|
hc.setMaximumPoolSize(cfg.max_pool_size);
|
||||||
|
hc.setMinimumIdle(cfg.min_idle);
|
||||||
|
hc.setConnectionTimeout(cfg.connection_timeout_ms);
|
||||||
|
hc.setIdleTimeout(cfg.idle_timeout_ms);
|
||||||
|
hc.setMaxLifetime(cfg.max_lifetime_ms);
|
||||||
|
hc.setPoolName("networkcore-mysql");
|
||||||
|
hc.setAutoCommit(true);
|
||||||
|
|
||||||
|
this.dataSource = new HikariDataSource(hc);
|
||||||
|
|
||||||
|
try (Connection c = dataSource.getConnection()) {
|
||||||
|
if (!c.isValid(2)) {
|
||||||
|
throw new SQLException("Connection is not valid");
|
||||||
|
}
|
||||||
|
this.healthy = true;
|
||||||
|
LOGGER.at(Level.INFO).log("Connected to MySQL at %s:%d/%s (pool=%d)",
|
||||||
|
cfg.host, cfg.port, cfg.database, cfg.max_pool_size);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
this.healthy = false;
|
||||||
|
((HytaleLogger.Api) LOGGER.at(Level.SEVERE).withCause(e)).log(
|
||||||
|
"Failed initial connectivity check to MySQL at %s:%d/%s", cfg.host, cfg.port, cfg.database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHealthy() {
|
||||||
|
return healthy && !dataSource.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getActiveConnections() {
|
||||||
|
return dataSource.getHikariPoolMXBean() == null ? 0 : dataSource.getHikariPoolMXBean().getActiveConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIdleConnections() {
|
||||||
|
return dataSource.getHikariPoolMXBean() == null ? 0 : dataSource.getHikariPoolMXBean().getIdleConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalConnections() {
|
||||||
|
return dataSource.getHikariPoolMXBean() == null ? 0 : dataSource.getHikariPoolMXBean().getTotalConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (!dataSource.isClosed()) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user