Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.

Commit 1635d04

Browse files
committed
Implement database management system with support for multiple databases and entity management
1 parent 526aab8 commit 1635d04

15 files changed

Lines changed: 651 additions & 0 deletions

File tree

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ dependencies {
3737
implementation "org.mozilla:rhino:1.7.14"
3838
implementation "com.google.code.gson:gson:2.10.1"
3939
implementation "org.bstats:bstats-bukkit:3.0.2"
40+
41+
implementation 'org.redisson:redisson:3.50.0'
42+
implementation 'com.mysql:mysql-connector-j:9.3.0'
43+
implementation 'org.xerial:sqlite-jdbc:3.50.3.0'
44+
implementation 'com.h2database:h2:2.3.232'
45+
implementation 'org.mongodb:mongodb-driver-sync:5.5.1'
4046
}
4147

4248
subprojects {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package gg.nextforge.database;
2+
3+
import com.mongodb.client.MongoClient;
4+
import com.mongodb.client.MongoClients;
5+
import com.mongodb.client.MongoDatabase;
6+
import lombok.Getter;
7+
import org.redisson.Redisson;
8+
import org.redisson.api.RedissonClient;
9+
import org.redisson.config.Config;
10+
11+
import java.sql.Connection;
12+
import java.sql.DriverManager;
13+
import java.sql.SQLException;
14+
import java.util.logging.Level;
15+
import java.util.logging.Logger;
16+
17+
public class DatabaseManager {
18+
19+
private static final Logger LOGGER = Logger.getLogger("Database");
20+
21+
public enum DatabaseType {
22+
MYSQL, H2, SQLITE, MONGODB, REDISSON
23+
}
24+
25+
@Getter
26+
private final DatabaseType type;
27+
private final String connectionString;
28+
private Connection sqlConnection;
29+
private MongoClient mongoClient;
30+
private MongoDatabase mongoDatabase;
31+
private RedissonClient redissonClient;
32+
33+
public DatabaseManager(DatabaseType type, String connectionString) {
34+
this.type = type;
35+
this.connectionString = connectionString;
36+
initialize();
37+
}
38+
39+
private void initialize() {
40+
try {
41+
switch (type) {
42+
case MYSQL:
43+
case H2:
44+
case SQLITE:
45+
sqlConnection = DriverManager.getConnection(connectionString);
46+
break;
47+
case MONGODB:
48+
String uri = (connectionString);
49+
String databaseName = uri.substring(uri.lastIndexOf("/") + 1);
50+
mongoClient = MongoClients.create(uri);
51+
mongoDatabase = mongoClient.getDatabase(databaseName);
52+
break;
53+
case REDISSON:
54+
Config config = new Config();
55+
config.useSingleServer().setAddress(connectionString);
56+
redissonClient = Redisson.create(config);
57+
break;
58+
}
59+
} catch (Exception e) {
60+
LOGGER.log(Level.SEVERE, "Failed to initialize database connection", e);
61+
}
62+
}
63+
64+
public Connection getSqlConnection() {
65+
return sqlConnection;
66+
}
67+
68+
public MongoDatabase getMongoDatabase() {
69+
return mongoDatabase;
70+
}
71+
72+
public RedissonClient getRedissonClient() {
73+
return redissonClient;
74+
}
75+
76+
public void close() {
77+
try {
78+
if (sqlConnection != null && !sqlConnection.isClosed()) {
79+
sqlConnection.close();
80+
}
81+
} catch (SQLException e) {
82+
LOGGER.log(Level.WARNING, "Failed to close SQL connection", e);
83+
}
84+
85+
if (mongoClient != null) {
86+
mongoClient.close();
87+
}
88+
89+
if (redissonClient != null) {
90+
redissonClient.shutdown();
91+
}
92+
}
93+
94+
public boolean isConnected() {
95+
switch (type) {
96+
case MYSQL:
97+
case H2:
98+
case SQLITE:
99+
try {
100+
return sqlConnection != null && !sqlConnection.isClosed();
101+
} catch (SQLException e) {
102+
return false;
103+
}
104+
case MONGODB:
105+
return mongoClient != null;
106+
case REDISSON:
107+
return redissonClient != null && !redissonClient.isShutdown();
108+
default:
109+
return false;
110+
}
111+
}
112+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package gg.nextforge.database.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.FIELD)
10+
public @interface Column {
11+
String value();
12+
boolean id() default false;
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package gg.nextforge.database.annotations;
2+
3+
import java.lang.annotation.*;
4+
5+
@Retention(RetentionPolicy.RUNTIME)
6+
@Target(ElementType.FIELD)
7+
public @interface Index {
8+
boolean unique() default false;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package gg.nextforge.database.annotations;
2+
3+
import java.lang.annotation.*;
4+
5+
@Retention(RetentionPolicy.RUNTIME)
6+
@Target(ElementType.TYPE)
7+
public @interface Table {
8+
String value();
9+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package gg.nextforge.database.bootstrap;
2+
3+
import gg.nextforge.database.entity.EntityManager;
4+
import gg.nextforge.database.entity.EntityScanner;
5+
6+
import java.sql.Connection;
7+
import java.util.List;
8+
import java.util.logging.Level;
9+
import java.util.logging.Logger;
10+
11+
public class EntityBootstrapper {
12+
13+
private static final Logger LOGGER = Logger.getLogger("EntityBootstrapper");
14+
15+
private final Connection connection;
16+
private final String basePackage;
17+
18+
public EntityBootstrapper(Connection connection, String basePackage) {
19+
this.connection = connection;
20+
this.basePackage = basePackage;
21+
}
22+
23+
public void initialize() {
24+
try {
25+
EntityScanner scanner = new EntityScanner();
26+
List<Class<?>> entities = scanner.findEntities(basePackage);
27+
EntityManager manager = new EntityManager(connection);
28+
29+
for (Class<?> entity : entities) {
30+
try {
31+
manager.createSchema(entity);
32+
LOGGER.info("Schema created for entity: " + entity.getSimpleName());
33+
} catch (Exception e) {
34+
LOGGER.log(Level.SEVERE, "Failed to create schema for " + entity.getSimpleName(), e);
35+
}
36+
}
37+
38+
} catch (Exception e) {
39+
LOGGER.log(Level.SEVERE, "Failed to bootstrap entities", e);
40+
}
41+
}
42+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package gg.nextforge.database.entity;
2+
3+
import gg.nextforge.database.annotations.Column;
4+
import gg.nextforge.database.annotations.Table;
5+
6+
import java.lang.reflect.Field;
7+
import java.sql.Connection;
8+
import java.sql.SQLException;
9+
import java.sql.Statement;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
public class EntityManager {
14+
15+
private final Connection connection;
16+
17+
public EntityManager(Connection connection) {
18+
this.connection = connection;
19+
}
20+
21+
public void createSchema(Class<?> clazz) throws SQLException {
22+
if (!clazz.isAnnotationPresent(Table.class)) {
23+
throw new IllegalArgumentException("Class must be annotated with @Table");
24+
}
25+
26+
Table tableAnnotation = clazz.getAnnotation(Table.class);
27+
String tableName = tableAnnotation.value();
28+
29+
List<String> columns = new ArrayList<>();
30+
String primaryKey = null;
31+
32+
for (Field field : clazz.getDeclaredFields()) {
33+
if (!field.isAnnotationPresent(Column.class)) continue;
34+
35+
Column column = field.getAnnotation(Column.class);
36+
String name = column.value();
37+
String type = mapJavaTypeToSql(field.getType());
38+
39+
columns.add(name + " " + type);
40+
41+
if (column.id()) {
42+
primaryKey = name;
43+
}
44+
}
45+
46+
StringBuilder builder = new StringBuilder("CREATE TABLE IF NOT EXISTS ")
47+
.append(tableName)
48+
.append(" (")
49+
.append(String.join(", ", columns));
50+
51+
if (primaryKey != null) {
52+
builder.append(", PRIMARY KEY (").append(primaryKey).append(")");
53+
}
54+
55+
builder.append(")");
56+
57+
try (Statement stmt = connection.createStatement()) {
58+
stmt.executeUpdate(builder.toString());
59+
}
60+
}
61+
62+
private String mapJavaTypeToSql(Class<?> type) {
63+
if (type == String.class) return "VARCHAR(255)";
64+
if (type == int.class || type == Integer.class) return "INT";
65+
if (type == long.class || type == Long.class) return "BIGINT";
66+
if (type == boolean.class || type == Boolean.class) return "BOOLEAN";
67+
if (type == double.class || type == Double.class) return "DOUBLE";
68+
return "TEXT";
69+
}
70+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package gg.nextforge.database.entity;
2+
3+
import gg.nextforge.database.annotations.Table;
4+
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.net.URL;
8+
import java.util.ArrayList;
9+
import java.util.Enumeration;
10+
import java.util.List;
11+
12+
public class EntityScanner {
13+
14+
public List<Class<?>> findEntities(String basePackage) throws IOException, ClassNotFoundException {
15+
List<Class<?>> classes = new ArrayList<>();
16+
String path = basePackage.replace('.', '/');
17+
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(path);
18+
19+
while (resources.hasMoreElements()) {
20+
URL resource = resources.nextElement();
21+
File dir = new File(resource.getFile());
22+
for (File file : dir.listFiles()) {
23+
if (file.getName().endsWith(".class")) {
24+
String className = basePackage + '.' + file.getName().replace(".class", "");
25+
Class<?> clazz = Class.forName(className);
26+
if (clazz.isAnnotationPresent(Table.class)) {
27+
classes.add(clazz);
28+
}
29+
}
30+
}
31+
}
32+
return classes;
33+
}
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package gg.nextforge.database.query;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.StringJoiner;
6+
7+
public class QueryBuilder {
8+
9+
private final String table;
10+
private final List<String> where = new ArrayList<>();
11+
private final List<Object> params = new ArrayList<>();
12+
private String orderBy = "";
13+
private String limit = "";
14+
15+
public QueryBuilder(String table) {
16+
this.table = table;
17+
}
18+
19+
public QueryBuilder where(String column, String operator, Object value) {
20+
where.add(column + " " + operator + " ?");
21+
params.add(value);
22+
return this;
23+
}
24+
25+
public QueryBuilder orderBy(String column, boolean ascending) {
26+
this.orderBy = "ORDER BY " + column + (ascending ? " ASC" : " DESC");
27+
return this;
28+
}
29+
30+
public QueryBuilder limit(int limit) {
31+
this.limit = "LIMIT " + limit;
32+
return this;
33+
}
34+
35+
public String build() {
36+
StringBuilder sb = new StringBuilder("SELECT * FROM ").append(table);
37+
if (!where.isEmpty()) {
38+
sb.append(" WHERE ");
39+
sb.append(String.join(" AND ", where));
40+
}
41+
if (!orderBy.isEmpty()) sb.append(" ").append(orderBy);
42+
if (!limit.isEmpty()) sb.append(" ").append(limit);
43+
return sb.toString();
44+
}
45+
46+
public Object[] getParameters() {
47+
return params.toArray();
48+
}
49+
}

0 commit comments

Comments
 (0)