1
0
mirror of https://github.com/Rogiel/l2jserver2 synced 2025-12-05 23:22:47 +00:00

Implements better schema management

This commit is contained in:
2011-12-26 01:08:27 -02:00
parent 1a4a4b0fcf
commit d363c30c5c
14 changed files with 635 additions and 217 deletions

View File

@@ -24,7 +24,9 @@ import com.l2jserver.model.id.ID;
import com.l2jserver.service.Service; import com.l2jserver.service.Service;
import com.l2jserver.service.ServiceConfiguration; import com.l2jserver.service.ServiceConfiguration;
import com.l2jserver.service.configuration.Configuration; import com.l2jserver.service.configuration.Configuration;
import com.l2jserver.service.configuration.XMLConfigurationService.ConfigurationXPath;
import com.l2jserver.service.core.threading.AsyncFuture; import com.l2jserver.service.core.threading.AsyncFuture;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.sql.RelationalPathBase; import com.mysema.query.sql.RelationalPathBase;
/** /**
@@ -50,6 +52,20 @@ public interface DatabaseService extends Service {
* @see Configuration * @see Configuration
*/ */
public interface DatabaseConfiguration extends ServiceConfiguration { public interface DatabaseConfiguration extends ServiceConfiguration {
/**
* @return the update schema state
*/
@ConfigurationPropertyGetter(defaultValue = "true")
@ConfigurationXPath("/configuration/services/database/automaticSchemaUpdate")
boolean isAutomaticSchemaUpdateEnabled();
/**
* @param updateSchema
* the new uodate schema state
*/
@ConfigurationPropertySetter
@ConfigurationXPath("/configuration/services/database/automaticSchemaUpdate")
void setUpdateSchema(boolean updateSchema);
} }
/** /**
@@ -122,8 +138,22 @@ public interface DatabaseService extends Service {
* if any error occur while reading or parsing the file * if any error occur while reading or parsing the file
*/ */
<M extends Model<?>, T extends RelationalPathBase<?>> void importData( <M extends Model<?>, T extends RelationalPathBase<?>> void importData(
Path path, T entity) Path path, T entity) throws IOException;
throws IOException;
/**
* Updates the given <code>schema</code> in the underlying storage engine.
*
* @param schema
* the schema specification
* @return true if the schema did not existed and was created (i.e. is empty
* and ready to receive default data)
*/
boolean updateSchema(RelationalPath<?> schema);
/**
* Updates all schemas in the underlying storage engine.
*/
void updateSchemas();
/** /**
* Checks for the cached version of the object * Checks for the cached version of the object

View File

@@ -32,13 +32,13 @@ import com.l2jserver.service.database.ddl.annotation.ColumnDefault;
import com.l2jserver.service.database.ddl.annotation.ColumnNullable; import com.l2jserver.service.database.ddl.annotation.ColumnNullable;
import com.l2jserver.service.database.ddl.annotation.ColumnSize; import com.l2jserver.service.database.ddl.annotation.ColumnSize;
import com.l2jserver.service.database.ddl.struct.Column; import com.l2jserver.service.database.ddl.struct.Column;
import com.l2jserver.service.database.ddl.struct.Column.ColumnType;
import com.l2jserver.service.database.ddl.struct.ForeignKey; import com.l2jserver.service.database.ddl.struct.ForeignKey;
import com.l2jserver.service.database.ddl.struct.PrimaryKey; import com.l2jserver.service.database.ddl.struct.PrimaryKey;
import com.l2jserver.service.database.ddl.struct.Table; import com.l2jserver.service.database.ddl.struct.Table;
import com.l2jserver.service.database.ddl.struct.Column.ColumnType;
import com.l2jserver.util.ClassUtils; import com.l2jserver.util.ClassUtils;
import com.l2jserver.util.factory.CollectionFactory; import com.l2jserver.util.factory.CollectionFactory;
import com.mysema.query.sql.RelationalPathBase; import com.mysema.query.sql.RelationalPath;
import com.mysema.query.types.Path; import com.mysema.query.types.Path;
/** /**
@@ -47,15 +47,15 @@ import com.mysema.query.types.Path;
*/ */
public class TableFactory { public class TableFactory {
/** /**
* Creates an {@link Table} object from an {@link RelationalPathBase} * Creates an {@link Table} object from an {@link RelationalPath}
* *
* @param tablePath * @param tablePath
* the table path * the table path
* @return the {@link Table} object * @return the {@link Table} object
*/ */
public static Table createTable(RelationalPathBase<?> tablePath) { public static Table createTable(RelationalPath<?> tablePath) {
final Map<String, Column> columns = CollectionFactory.newMap(); final Map<String, Column> columns = CollectionFactory.newMap();
for (final Path<?> path : tablePath.all()) { for (final Path<?> path : tablePath.getColumns()) {
final Column col = createColumn(tablePath, path); final Column col = createColumn(tablePath, path);
columns.put(col.getName(), col); columns.put(col.getName(), col);
} }
@@ -72,7 +72,8 @@ public class TableFactory {
* *
* @param conn * @param conn
* the JDBC {@link Connection} * the JDBC {@link Connection}
* @param template the query template * @param template
* the query template
* @param tableName * @param tableName
* the table name * the table name
* @return the parsed table * @return the parsed table
@@ -112,7 +113,7 @@ public class TableFactory {
* the columns * the columns
* @return the foreign key list * @return the foreign key list
*/ */
private static List<ForeignKey> createFKs(RelationalPathBase<?> tablePath, private static List<ForeignKey> createFKs(RelationalPath<?> tablePath,
Map<String, Column> columns) { Map<String, Column> columns) {
final List<ForeignKey> fks = CollectionFactory.newList(); final List<ForeignKey> fks = CollectionFactory.newList();
for (final com.mysema.query.sql.ForeignKey<?> fk : tablePath for (final com.mysema.query.sql.ForeignKey<?> fk : tablePath
@@ -130,15 +131,14 @@ public class TableFactory {
return fks; return fks;
} }
private static PrimaryKey createPK(RelationalPathBase<?> tablePath, private static PrimaryKey createPK(RelationalPath<?> tablePath,
Map<String, Column> columns) { Map<String, Column> columns) {
return new PrimaryKey(columns.get(tablePath.getPrimaryKey() return new PrimaryKey(columns.get(tablePath.getPrimaryKey()
.getLocalColumns().get(0).getMetadata().getExpression() .getLocalColumns().get(0).getMetadata().getExpression()
.toString())); .toString()));
} }
private static Column createColumn(RelationalPathBase<?> tablePath, private static Column createColumn(RelationalPath<?> tablePath, Path<?> path) {
Path<?> path) {
final String columnName = path.getMetadata().getExpression().toString(); final String columnName = path.getMetadata().getExpression().toString();
final ColumnType columnType = getColumnType(path.getType()); final ColumnType columnType = getColumnType(path.getType());
final Field field = ClassUtils.getFieldWithValue(tablePath, path); final Field field = ClassUtils.getFieldWithValue(tablePath, path);

View File

@@ -16,12 +16,11 @@
*/ */
package com.l2jserver.service.database.orientdb; package com.l2jserver.service.database.orientdb;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -48,13 +47,18 @@ import com.l2jserver.service.core.threading.ScheduledAsyncFuture;
import com.l2jserver.service.core.threading.ThreadService; import com.l2jserver.service.core.threading.ThreadService;
import com.l2jserver.service.database.DAOResolver; import com.l2jserver.service.database.DAOResolver;
import com.l2jserver.service.database.DataAccessObject; import com.l2jserver.service.database.DataAccessObject;
import com.l2jserver.service.database.DatabaseException;
import com.l2jserver.service.database.DatabaseService; import com.l2jserver.service.database.DatabaseService;
import com.l2jserver.service.database.dao.DatabaseRow; import com.l2jserver.service.database.dao.DatabaseRow;
import com.l2jserver.service.database.dao.InsertMapper; import com.l2jserver.service.database.dao.InsertMapper;
import com.l2jserver.service.database.dao.SelectMapper; import com.l2jserver.service.database.dao.SelectMapper;
import com.l2jserver.service.database.dao.UpdateMapper; import com.l2jserver.service.database.dao.UpdateMapper;
import com.l2jserver.util.CSVUtils;
import com.l2jserver.util.CSVUtils.CSVMapProcessor;
import com.l2jserver.util.QPathUtils;
import com.l2jserver.util.factory.CollectionFactory; import com.l2jserver.util.factory.CollectionFactory;
import com.mysema.query.sql.ForeignKey; import com.mysema.query.sql.ForeignKey;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.sql.RelationalPathBase; import com.mysema.query.sql.RelationalPathBase;
import com.mysema.query.types.Path; import com.mysema.query.types.Path;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool; import com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool;
@@ -67,6 +71,7 @@ import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.query.nativ.ONativeSynchQuery; import com.orientechnologies.orient.core.query.nativ.ONativeSynchQuery;
import com.orientechnologies.orient.core.query.nativ.OQueryContextNative; import com.orientechnologies.orient.core.query.nativ.OQueryContextNative;
import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.tx.OTransaction;
/** /**
* This is an implementation of {@link DatabaseService} that provides an layer * This is an implementation of {@link DatabaseService} that provides an layer
@@ -122,6 +127,10 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
* every 1 minute. * every 1 minute.
*/ */
private ScheduledAsyncFuture autoSaveFuture; private ScheduledAsyncFuture autoSaveFuture;
/**
* The transactioned database connection, if any.
*/
private final ThreadLocal<ODatabaseDocumentTx> transaction = new ThreadLocal<ODatabaseDocumentTx>();
/** /**
* Configuration interface for {@link AbstractOrientDatabaseService}. * Configuration interface for {@link AbstractOrientDatabaseService}.
@@ -208,11 +217,13 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
} }
} }
ensureDatabaseSchema();
database.close(); database.close();
// check if automatic schema update is enabled
if (config.isAutomaticSchemaUpdateEnabled()) {
updateSchemas();
}
// cache must be large enough for all world objects, to avoid // cache must be large enough for all world objects, to avoid
// duplication... this would endanger non-persistent states // duplication... this would endanger non-persistent states
objectCache = cacheService.createEternalCache("database-service", objectCache = cacheService.createEternalCache("database-service",
@@ -242,7 +253,25 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
@Override @Override
public int transaction(TransactionExecutor executor) { public int transaction(TransactionExecutor executor) {
return executor.perform(); final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
.acquire(config.getUrl(), config.getUsername(),
config.getPassword());
transaction.set(database);
try {
database.begin(OTransaction.TXTYPE.OPTIMISTIC);
int returnValue = executor.perform();
database.commit();
return returnValue;
} catch (DatabaseException e) {
database.rollback();
throw e;
} catch (Exception e) {
database.rollback();
throw new DatabaseException(e);
} finally {
transaction.set(null);
database.close();
}
} }
@Override @Override
@@ -267,56 +296,62 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
*/ */
public <T> T query(Query<T> query) { public <T> T query(Query<T> query) {
Preconditions.checkNotNull(query, "query"); Preconditions.checkNotNull(query, "query");
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global() ODatabaseDocumentTx database = transaction.get();
.acquire(config.getUrl(), config.getUsername(), if (database == null)
config.getPassword()); database = ODatabaseDocumentPool.global().acquire(config.getUrl(),
config.getUsername(), config.getPassword());
log.debug("Executing query {} with {}", query, database); log.debug("Executing query {} with {}", query, database);
try { try {
return query.query(database, this); return query.query(database, this);
} finally { } finally {
database.commit(); if (transaction.get() == null)
database.close();
} }
} }
@Override @Override
public <M extends Model<?>, T extends RelationalPathBase<?>> void importData( public <M extends Model<?>, T extends RelationalPathBase<?>> void importData(
java.nio.file.Path path, T entity) throws IOException { java.nio.file.Path path, final T entity) throws IOException {
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global() final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
.acquire(config.getUrl(), config.getUsername(), .acquire(config.getUrl(), config.getUsername(),
config.getPassword()); config.getPassword());
log.info("Importing {} to {}", path, entity); log.info("Importing {} to {}", path, entity);
BufferedReader reader = Files.newBufferedReader(path, try {
Charset.defaultCharset()); database.begin(OTransaction.TXTYPE.OPTIMISTIC);
final String header[] = reader.readLine().split(","); CSVUtils.parseCSV(path, new CSVMapProcessor<Object>() {
String line; @Override
while ((line = reader.readLine()) != null) { public Object process(Map<String, String> map) {
final String data[] = line.split(","); final ODocument document = new ODocument(database, entity
final ODocument document = new ODocument(database, .getTableName());
entity.getTableName()); for (final Entry<String, String> entry : map.entrySet()) {
for (int i = 0; i < data.length; i++) { document.field(entry.getKey(), entry.getValue());
document.field(header[i], data[i]); }
} database.save(document);
database.save(document); return null;
}
});
database.commit();
} catch (IOException | RuntimeException e) {
database.rollback();
throw e;
} catch (Exception e) {
database.rollback();
throw new DatabaseException(e);
} finally {
transaction.set(null);
database.close();
} }
} }
/** @Override
* Makes sure the database schema is up-to-date with the external database public boolean updateSchema(RelationalPath<?> table) {
*/
protected abstract void ensureDatabaseSchema();
/**
* @param table
* the {@link RelationalPathBase} table
* @return true if a new schema was created
*/
protected boolean createSchema(RelationalPathBase<?> table) {
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global() final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
.acquire(config.getUrl(), config.getUsername(), .acquire(config.getUrl(), config.getUsername(),
config.getPassword()); config.getPassword());
log.info("Updating {} schema definition", table);
boolean newSchema = false; boolean newSchema = false;
try { try {
final OSchema schemas = database.getMetadata().getSchema(); final OSchema schemas = database.getMetadata().getSchema();
@@ -325,7 +360,7 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
schema = schemas.createClass(table.getTableName()); schema = schemas.createClass(table.getTableName());
newSchema = true; newSchema = true;
} }
for (final Path<?> path : table.all()) { for (final Path<?> path : table.getColumns()) {
final String name = path.getMetadata().getExpression() final String name = path.getMetadata().getExpression()
.toString(); .toString();
OProperty property = schema.getProperty(name); OProperty property = schema.getProperty(name);
@@ -335,10 +370,18 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
(path.getType().isEnum() ? OType.STRING : OType (path.getType().isEnum() ? OType.STRING : OType
.getTypeByClass(path.getType()))); .getTypeByClass(path.getType())));
if (path.getType().isEnum()) { if (path.getType().isEnum()) {
property.setType(OType.STRING); if (property.getType() != OType.STRING)
property.setType(OType.STRING);
} else { } else {
property.setType(OType.getTypeByClass(path.getType())); if (property.getType() != OType.getTypeByClass(path
.getType()))
property.setType(OType.getTypeByClass(path.getType()));
} }
final boolean nullable = QPathUtils.isNullable(path);
if (property.isNotNull() != !nullable)
property.setNotNull(!nullable);
if (property.isMandatory() != !nullable)
property.setMandatory(!nullable);
} }
for (final ForeignKey<?> fk : table.getForeignKeys()) { for (final ForeignKey<?> fk : table.getForeignKeys()) {
final String[] columns = new String[fk.getLocalColumns().size()]; final String[] columns = new String[fk.getLocalColumns().size()];
@@ -347,8 +390,9 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
columns[i++] = keyPath.getMetadata().getExpression() columns[i++] = keyPath.getMetadata().getExpression()
.toString(); .toString();
} }
schema.createIndex(StringUtils.join(columns, "-"), if (!schema.areIndexed(columns))
INDEX_TYPE.NOTUNIQUE, columns); schema.createIndex(StringUtils.join(columns, "-"),
INDEX_TYPE.NOTUNIQUE, columns);
} }
final String[] pkColumns = new String[table.getPrimaryKey() final String[] pkColumns = new String[table.getPrimaryKey()
.getLocalColumns().size()]; .getLocalColumns().size()];
@@ -358,7 +402,8 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
pkColumns[i++] = keyPath.getMetadata().getExpression() pkColumns[i++] = keyPath.getMetadata().getExpression()
.toString(); .toString();
} }
schema.createIndex("PRIMARY", INDEX_TYPE.UNIQUE, pkColumns); if (!schema.areIndexed(pkColumns))
schema.createIndex("PRIMARY", INDEX_TYPE.UNIQUE, pkColumns);
schemas.save(); schemas.save();
} finally { } finally {
database.close(); database.close();
@@ -446,7 +491,6 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
private final InsertMapper<O, RI, I, E> mapper; private final InsertMapper<O, RI, I, E> mapper;
private final Iterator<O> iterator; private final Iterator<O> iterator;
@SuppressWarnings("unused") @SuppressWarnings("unused")
// FIXME implement id generation
private final Path<RI> primaryKey; private final Path<RI> primaryKey;
protected final E e; protected final E e;
@@ -525,7 +569,7 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
mapper.insert(e, object, row); mapper.insert(e, object, row);
// TODO generate ids // TODO generate unique id
row.getDocument().save(); row.getDocument().save();
rows++; rows++;

View File

@@ -16,16 +16,14 @@
*/ */
package com.l2jserver.service.database.sql; package com.l2jserver.service.database.sql;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.sql.DataSource; import javax.sql.DataSource;
@@ -68,6 +66,9 @@ import com.l2jserver.service.database.dao.UpdateMapper;
import com.l2jserver.service.database.ddl.QueryFactory; import com.l2jserver.service.database.ddl.QueryFactory;
import com.l2jserver.service.database.ddl.TableFactory; import com.l2jserver.service.database.ddl.TableFactory;
import com.l2jserver.service.database.ddl.struct.Table; import com.l2jserver.service.database.ddl.struct.Table;
import com.l2jserver.util.CSVUtils;
import com.l2jserver.util.CSVUtils.CSVMapProcessor;
import com.l2jserver.util.QPathUtils;
import com.l2jserver.util.factory.CollectionFactory; import com.l2jserver.util.factory.CollectionFactory;
import com.mysema.query.sql.AbstractSQLQuery; import com.mysema.query.sql.AbstractSQLQuery;
import com.mysema.query.sql.RelationalPath; import com.mysema.query.sql.RelationalPath;
@@ -237,21 +238,6 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
@ConfigurationXPath("/configuration/services/database/jdbc/password") @ConfigurationXPath("/configuration/services/database/jdbc/password")
void setPassword(String password); void setPassword(String password);
/**
* @return the update schema state
*/
@ConfigurationPropertyGetter(defaultValue = "true")
@ConfigurationXPath("/configuration/services/database/jdbc/updateSchema")
boolean getUpdateSchema();
/**
* @param updateSchema
* the new uodate schema state
*/
@ConfigurationPropertySetter
@ConfigurationXPath("/configuration/services/database/jdbc/updateSchema")
void setUpdateSchema(boolean updateSchema);
/** /**
* @return the maximum number of active connections * @return the maximum number of active connections
*/ */
@@ -350,18 +336,8 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
true); true);
dataSource = new PoolingDataSource(connectionPool); dataSource = new PoolingDataSource(connectionPool);
if (config.getUpdateSchema()) { if (config.isAutomaticSchemaUpdateEnabled()) {
try { updateSchemas();
final Connection conn = dataSource.getConnection();
try {
ensureDatabaseSchema(conn);
} finally {
conn.close();
}
} catch (Exception e) {
throw new ServiceStartException(
"Couldn't update database schema", e);
}
} }
for (final Type<?> type : sqlTypes) { for (final Type<?> type : sqlTypes) {
@@ -401,51 +377,6 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
}); });
} }
/**
* Makes sure the database schema is up-to-date with the external database
*
* @param conn
* the connection to be used
* @throws SQLException
* if any {@link SQLException} occur
* @throws IOException
* if any {@link IOException} occur
*/
protected abstract void ensureDatabaseSchema(Connection conn)
throws SQLException, IOException;
/**
*
* @param conn
* the connection to be used
* @param table
* the {@link RelationalPathBase} table
* @return <code>true</code> if a new table was created, <code>false</code>
* otherwise.
* @throws SQLException
* if any {@link SQLException} occur
*/
protected boolean updateSchema(Connection conn, RelationalPathBase<?> table)
throws SQLException {
final Table expected = TableFactory.createTable(table);
String query = null;
boolean create = false;
try {
final Table current = TableFactory.createTable(conn,
engine.getTemplate(), table.getTableName());
query = QueryFactory.alterTableQueryUpdate(expected, current,
engine.getTemplate());
} catch (SQLException e) {
// table may not exist
query = QueryFactory.createTableQuery(expected,
engine.getTemplate());
create = true;
}
if ((engine.getTemplate().supportsAlterTable() && !create) || create)
executeSQL(conn, query);
return create;
}
/** /**
* Executes the SQL code in the databases * Executes the SQL code in the databases
* *
@@ -462,9 +393,6 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
final Statement st = conn.createStatement(); final Statement st = conn.createStatement();
try { try {
return st.execute(sql); return st.execute(sql);
} catch (SQLException e) {
log.warn("Error exectuing query {}", sql);
throw e;
} finally { } finally {
st.close(); st.close();
} }
@@ -472,8 +400,8 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
@Override @Override
public <M extends Model<?>, T extends RelationalPathBase<?>> void importData( public <M extends Model<?>, T extends RelationalPathBase<?>> void importData(
java.nio.file.Path path, T entity) throws IOException { java.nio.file.Path path, final T entity) throws IOException {
Connection conn; final Connection conn;
try { try {
conn = dataSource.getConnection(); conn = dataSource.getConnection();
} catch (SQLException e) { } catch (SQLException e) {
@@ -481,37 +409,30 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
} }
log.info("Importing {} to {}", path, entity); log.info("Importing {} to {}", path, entity);
try { try {
BufferedReader reader = Files.newBufferedReader(path, CSVUtils.parseCSV(path, new CSVMapProcessor<Long>() {
Charset.defaultCharset()); @Override
final String header[] = reader.readLine().split(","); public Long process(final Map<String, String> map) {
String line; SQLInsertClause insert = engine.createSQLQueryFactory(conn)
while ((line = reader.readLine()) != null) { .insert(entity);
final String data[] = line.split(","); insert.populate(map, new Mapper<Map<String, String>>() {
SQLInsertClause insert = engine.createSQLQueryFactory(conn) @Override
.insert(entity); public Map<Path<?>, Object> createMap(
insert.populate(data, new Mapper<Object[]>() { RelationalPath<?> relationalPath,
@Override Map<String, String> map) {
public Map<Path<?>, Object> createMap( final Map<Path<?>, Object> values = CollectionFactory
RelationalPath<?> relationalPath, Object[] object) { .newMap();
final Map<Path<?>, Object> values = CollectionFactory for (final Entry<String, String> entry : map
.newMap(); .entrySet()) {
pathFor: for (final Path<?> path : relationalPath final Path<?> path = QPathUtils.getPath(entity,
.getColumns()) { entry.getKey());
int i = 0; values.put(path, entry.getValue());
for (final String headerName : header) {
if (path.getMetadata().getExpression()
.toString().equals(headerName)) {
values.put(path, object[i]);
continue pathFor;
}
i++;
} }
return values;
} }
return values; });
} return insert.execute();
}); }
insert.execute(); });
}
} finally { } finally {
try { try {
conn.close(); conn.close();
@@ -520,6 +441,46 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
} }
} }
@Override
public boolean updateSchema(RelationalPath<?> table) {
final Table expected = TableFactory.createTable(table);
log.info("Updating {} schema definition", table);
try {
final Connection conn = dataSource.getConnection();
try {
String query = null;
boolean create = false;
try {
final Table current = TableFactory.createTable(conn,
engine.getTemplate(), table.getTableName());
query = QueryFactory.alterTableQueryUpdate(expected,
current, engine.getTemplate());
} catch (SQLException e) {
// table may not exist
query = QueryFactory.createTableQuery(expected,
engine.getTemplate());
create = true;
}
if ((engine.getTemplate().supportsAlterTable() && !create)
|| create)
executeSQL(conn, query);
return create;
} catch (SQLException e) {
throw new DatabaseException(e);
} finally {
try {
conn.close();
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
} catch (SQLException e) {
throw new DatabaseException(e);
}
}
@Override @Override
public int transaction(TransactionExecutor executor) { public int transaction(TransactionExecutor executor) {
Preconditions.checkNotNull(executor, "executor"); Preconditions.checkNotNull(executor, "executor");

View File

@@ -0,0 +1,202 @@
/*
* This file is part of l2jserver2 <l2jserver2.com>.
*
* l2jserver2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* l2jserver2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with l2jserver2. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import com.l2jserver.util.factory.CollectionFactory;
/**
* This class provices CSV parsing and other utilities
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class CSVUtils {
/**
* Parses an CSV files and sends every row to be processed by the
* <code>processor</code>.
* <p>
* <h1>The parsing process</h1>
* <p>
* The parser reads the first line of the CSV and interprets it as an header
* that will be subsequently be passed to the {@link CSVProcessor} as the
* <code>header</code> {@link String String[]}.
* <p>
* Following rows are data rows and are read and passed to the
* {@link CSVProcessor} one by one until the end of file.
*
* @param <R>
* the processor return type
*
* @param reader
* the CSV {@link BufferedReader}
* @param processor
* the processor
* @return all processed rows
* @throws IOException
* if any exception occur while parsing
*/
public static <R> List<R> parseCSV(BufferedReader reader,
CSVProcessor<R> processor) throws IOException {
final List<R> results = CollectionFactory.newList();
final String header[] = reader.readLine().split(",");
String line;
while ((line = reader.readLine()) != null) {
results.add(processor.process(header, line.split(",")));
}
return results;
}
/**
* Parses an CSV files and sends every row to be processed by the
* <code>processor</code>
*
* @param <R>
* the processor return type
*
* @param path
* the CSV file path
* @param processor
* the processor
* @return all processed rows
* @throws IOException
* if any exception occur while parsing
* @see #parseCSV(BufferedReader, CSVProcessor)
* <code>parseCSV(BufferedReader, CSVProcessor)</code> for more details
* about how the parsing works
*/
public static <R> List<R> parseCSV(Path path, CSVProcessor<R> processor)
throws IOException {
return parseCSV(
Files.newBufferedReader(path, Charset.defaultCharset()),
processor);
}
/**
* Parses an CSV files and sends every row to be processed by the
* <code>processor</code>
*
* @param <R>
* the processor return type
*
* @param file
* the CSV file
* @param processor
* the processor
* @return all processed rows
* @throws IOException
* if any exception occur while parsing
*/
public static <R> List<R> parseCSV(File file, CSVProcessor<R> processor)
throws IOException {
return parseCSV(file.toPath(), processor);
}
/**
* Parses an CSV files and sends every row to be processed by the
* <code>processor</code>
*
* @param <R>
* the processor return type
*
* @param in
* the CSV {@link InputStream}
* @param processor
* the processor
* @return all processed rows
* @throws IOException
* if any exception occur while parsing
*/
public static <R> List<R> parseCSV(InputStream in, CSVProcessor<R> processor)
throws IOException {
return parseCSV(new BufferedReader(new InputStreamReader(in)),
processor);
}
/**
* Parses an CSV files and sends every row to be processed by the
* <code>processor</code>
*
* @param <R>
* the processor return type
*
* @param reader
* the CSV {@link Reader}
* @param processor
* the processor
* @return all processed rows
* @throws IOException
* if any exception occur while parsing
*/
public static <R> List<R> parseCSV(Reader reader, CSVProcessor<R> processor)
throws IOException {
return parseCSV(new BufferedReader(reader), processor);
}
/**
* Processes an single row in an CSV files and returns an processed object.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
* @param <R>
* the processed object type
*/
public interface CSVProcessor<R> {
/**
* Processes the current row and returns the processed object
*
* @param header
* the CSV header (first row)
* @param data
* the CSV data
* @return the processed object
*/
R process(String[] header, String[] data);
}
/**
* This {@link CSVProcessor} implementations act as an adapter to the String
* arrays, providing access to the rows in a more object oriented fashing,
* through a {@link Map}.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*
* @param <R>
* the processed object type
*/
public static abstract class CSVMapProcessor<R> implements CSVProcessor<R> {
@Override
public final R process(String[] header, String[] data) {
final Map<String, String> map = CollectionFactory.newMap();
for (int i = 0; i < header.length; i++) {
map.put(header[i], data[i]);
}
return process(map);
}
public abstract R process(Map<String, String> map);
}
}

View File

@@ -0,0 +1,173 @@
/*
* This file is part of l2jserver2 <l2jserver2.com>.
*
* l2jserver2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* l2jserver2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with l2jserver2. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jserver.util;
import java.lang.reflect.Field;
import com.l2jserver.service.database.ddl.annotation.ColumnAutoIncrement;
import com.l2jserver.service.database.ddl.annotation.ColumnDefault;
import com.l2jserver.service.database.ddl.annotation.ColumnNullable;
import com.l2jserver.service.database.ddl.annotation.ColumnSize;
import com.l2jserver.util.transformer.Transformer;
import com.l2jserver.util.transformer.TransformerFactory;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.types.Path;
/**
* Class serving several utilities for {@link Path} and {@link RelationalPath}
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class QPathUtils {
/**
* Returns the {@link Path} represented by <code>name</code> within the
* given {@link RelationalPath}
*
* @param relationalPath
* the {@link RelationalPath}
* @param name
* the {@link Path} name
* @return the {@link Path} instance, if existent
*/
public static Path<?> getPath(RelationalPath<?> relationalPath, String name) {
for (final Path<?> path : relationalPath.getColumns()) {
if (path.getMetadata().getExpression().toString().equals(name))
return path;
}
return null;
}
/**
* @param path
* the {@link Path} to be searched in its root entity
* @return the {@link Field} holding <code>path</code>
*/
public static Field getReflectionField(Path<?> path) {
return ClassUtils.getFieldWithValue(path.getMetadata().getParent(), path);
}
/**
* @param relationalPath
* the {@link RelationalPath}
* @param pathName
* the {@link Path} name
* @return the {@link Field} holding <code>pathName</code>
* @see #getPath(RelationalPath, String)
*/
public static Field getRefelctionField(RelationalPath<?> relationalPath,
String pathName) {
return ClassUtils.getFieldWithValue(relationalPath,
getPath(relationalPath, pathName));
}
/**
* @param path
* the {@link Path}
* @return the column maximum size
*/
public static int getColumnSize(Path<?> path) {
final Field field = getReflectionField(path);
if(field == null)
return 0;
final ColumnSize size = field.getAnnotation(ColumnSize.class);
if (size != null)
return size.value();
return -1;
}
/**
* @param path
* the {@link Path}
* @return whether the column is auto incrementable or not
*/
public static boolean isAutoIncrementable(Path<?> path) {
final Field field = getReflectionField(path);
if(field == null)
return false;
final ColumnAutoIncrement autoInc = field
.getAnnotation(ColumnAutoIncrement.class);
if (autoInc != null)
return true;
return false;
}
/**
* @param path
* the {@link Path}
* @return whether the column can be <code>null</code>
*/
public static boolean isNullable(Path<?> path) {
final Field field = getReflectionField(path);
if(field == null)
return false;
final ColumnNullable nullable = field
.getAnnotation(ColumnNullable.class);
if (nullable == null) {
return true;
} else {
return false;
}
}
/**
* @param path
* the {@link Path}
* @return <code>true</code> if the column has an default value
*/
public static boolean hasDefaultValue(Path<?> path) {
final Field field = getReflectionField(path);
if(field == null)
return false;
final ColumnDefault def = field.getAnnotation(ColumnDefault.class);
if (def == null) {
return false;
} else {
return true;
}
}
/**
* @param path
* the {@link Path}
* @return the column default value
*/
public static String getDefaultUntransformedValue(Path<?> path) {
final Field field = getReflectionField(path);
if(field == null)
return null;
final ColumnDefault def = field.getAnnotation(ColumnDefault.class);
if (def != null)
return def.value();
return null;
}
/**
* @param <T>
* the column return type
* @param path
* the {@link Path}
* @return the column default value (already transformed)
*/
@SuppressWarnings("unchecked")
public static <T> T getDefaultValue(Path<T> path) {
@SuppressWarnings("rawtypes")
final Transformer transformer = TransformerFactory.getTransfromer(path
.getType());
return (T) transformer.untransform(path.getType(),
getDefaultUntransformedValue(path));
}
}

View File

@@ -19,11 +19,22 @@
not sure on the usage of any parameter, read the "Configuration" section not sure on the usage of any parameter, read the "Configuration" section
in wiki article about DatabaseService. --> in wiki article about DatabaseService. -->
<database> <database>
<!-- Whether or not the service should try to update and create missing
tables at startup. This should be disabled after the first server start as
it could cause data corruption. -->
<automaticSchemaUpdate>true</automaticSchemaUpdate>
<!-- Configures OrientDB engine - plug-and-play -->
<orientdb> <orientdb>
<!-- The OrientDB storage location -->
<url>local:data/database</url> <url>local:data/database</url>
<!-- The OrientDB username (local storage must use "admin") -->
<username>admin</username> <username>admin</username>
<!-- The OrientDB password (local storage must use "admin") -->
<password>admin</password> <password>admin</password>
</orientdb> </orientdb>
<!-- Configures JDBC engine - requires database server configuration -->
<jdbc> <jdbc>
<!-- Defines the connection URL used by JDBC to connect to the database. --> <!-- Defines the connection URL used by JDBC to connect to the database. -->
<url>jdbc:mysql://localhost/l2jserver2</url> <url>jdbc:mysql://localhost/l2jserver2</url>
@@ -32,11 +43,6 @@
<engine>com.l2jserver.service.database.sql.MySQLDatabaseEngine <engine>com.l2jserver.service.database.sql.MySQLDatabaseEngine
</engine> </engine>
<!-- Whether or not the service should try to update and create missing
tables at startup. This should be disabled after the first server start as
it could cause data corruption. -->
<updateSchema>true</updateSchema>
<!-- The username used to login into the database. NOTE: Try not use <!-- The username used to login into the database. NOTE: Try not use
"root" in production servers for security reasons. --> "root" in production servers for security reasons. -->
<username>l2j</username> <username>l2j</username>

View File

@@ -19,6 +19,22 @@
not sure on the usage of any parameter, read the "Configuration" section not sure on the usage of any parameter, read the "Configuration" section
in wiki article about DatabaseService. --> in wiki article about DatabaseService. -->
<database> <database>
<!-- Whether or not the service should try to update and create missing
tables at startup. This should be disabled after the first server start as
it could cause data corruption. -->
<automaticSchemaUpdate>true</automaticSchemaUpdate>
<!-- Configures OrientDB engine - plug-and-play -->
<orientdb>
<!-- The OrientDB storage location -->
<url>local:data/database</url>
<!-- The OrientDB username (local storage must use "admin") -->
<username>admin</username>
<!-- The OrientDB password (local storage must use "admin") -->
<password>admin</password>
</orientdb>
<!-- Configures JDBC engine - requires database server configuration -->
<jdbc> <jdbc>
<!-- Defines the connection URL used by JDBC to connect to the database. --> <!-- Defines the connection URL used by JDBC to connect to the database. -->
<url>jdbc:mysql://localhost/l2jserver2</url> <url>jdbc:mysql://localhost/l2jserver2</url>
@@ -27,11 +43,6 @@
<engine>com.l2jserver.service.database.sql.MySQLDatabaseEngine <engine>com.l2jserver.service.database.sql.MySQLDatabaseEngine
</engine> </engine>
<!-- Whether or not the service should try to update and create missing
tables at startup. This should be disabled after the first server start as
it could cause data corruption. -->
<updateSchema>true</updateSchema>
<!-- The username used to login into the database. NOTE: Try not use <!-- The username used to login into the database. NOTE: Try not use
"root" in production servers for security reasons. --> "root" in production servers for security reasons. -->
<username>l2j</username> <username>l2j</username>

View File

@@ -20,7 +20,7 @@ import com.google.inject.AbstractModule;
import com.google.inject.Module; import com.google.inject.Module;
import com.l2jserver.model.id.provider.IDProviderModule; import com.l2jserver.model.id.provider.IDProviderModule;
import com.l2jserver.service.ServiceModule; import com.l2jserver.service.ServiceModule;
import com.l2jserver.service.database.JDBCDAOModule; import com.l2jserver.service.database.OrientDBDAOModule;
/** /**
* The game server Google Guice {@link Module}. * The game server Google Guice {@link Module}.
@@ -32,6 +32,6 @@ public class GameServerModule extends AbstractModule {
protected void configure() { protected void configure() {
install(new ServiceModule()); install(new ServiceModule());
install(new IDProviderModule()); install(new IDProviderModule());
install(new JDBCDAOModule()); install(new OrientDBDAOModule());
} }
} }

View File

@@ -30,7 +30,7 @@ import com.l2jserver.service.core.threading.ThreadServiceImpl;
import com.l2jserver.service.core.vfs.Java7VFSService; import com.l2jserver.service.core.vfs.Java7VFSService;
import com.l2jserver.service.core.vfs.VFSService; import com.l2jserver.service.core.vfs.VFSService;
import com.l2jserver.service.database.DatabaseService; import com.l2jserver.service.database.DatabaseService;
import com.l2jserver.service.database.GameServerJDBCDatabaseService; import com.l2jserver.service.database.GameServerOrientDatabaseService;
import com.l2jserver.service.game.AttackService; import com.l2jserver.service.game.AttackService;
import com.l2jserver.service.game.AttackServiceImpl; import com.l2jserver.service.game.AttackServiceImpl;
import com.l2jserver.service.game.admin.AdministratorService; import com.l2jserver.service.game.admin.AdministratorService;
@@ -89,7 +89,7 @@ public class ServiceModule extends AbstractModule {
bind(CacheService.class).to(SoftCacheService.class) bind(CacheService.class).to(SoftCacheService.class)
.in(Scopes.SINGLETON); .in(Scopes.SINGLETON);
bind(DatabaseService.class).to(GameServerJDBCDatabaseService.class) bind(DatabaseService.class).to(GameServerOrientDatabaseService.class)
.in(Scopes.SINGLETON); .in(Scopes.SINGLETON);
bind(WorldIDService.class).to(CachedWorldIDService.class).in( bind(WorldIDService.class).to(CachedWorldIDService.class).in(
Scopes.SINGLETON); Scopes.SINGLETON);

View File

@@ -17,8 +17,6 @@
package com.l2jserver.service.database; package com.l2jserver.service.database;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.l2jserver.model.game.CharacterShortcut.ShortcutType; import com.l2jserver.model.game.CharacterShortcut.ShortcutType;
@@ -112,17 +110,20 @@ public class GameServerJDBCDatabaseService extends AbstractSQLDatabaseService
} }
@Override @Override
protected void ensureDatabaseSchema(Connection conn) throws SQLException, public void updateSchemas() {
IOException { updateSchema(QActorSkill.actorSkill);
updateSchema(conn, QActorSkill.actorSkill); updateSchema(QCharacter.character);
updateSchema(conn, QCharacter.character); updateSchema(QCharacterFriend.characterFriend);
updateSchema(conn, QCharacterFriend.characterFriend); updateSchema(QCharacterShortcut.characterShortcut);
updateSchema(conn, QCharacterShortcut.characterShortcut); updateSchema(QClan.clan);
updateSchema(conn, QClan.clan); updateSchema(QItem.item);
updateSchema(conn, QItem.item); updateSchema(QLogChat.logChat);
updateSchema(conn, QLogChat.logChat); if (updateSchema(QNPC.npc)) {
if (updateSchema(conn, QNPC.npc)) { try {
importData(vfsService.resolve("data/static/npc.csv"), QNPC.npc); importData(vfsService.resolve("data/static/npc.csv"), QNPC.npc);
} catch (IOException e) {
throw new DatabaseException(e);
}
} }
} }
} }

View File

@@ -82,20 +82,19 @@ public class GameServerOrientDatabaseService extends
} }
@Override @Override
protected void ensureDatabaseSchema() { public void updateSchemas() {
createSchema(QActorSkill.actorSkill); updateSchema(QActorSkill.actorSkill);
createSchema(QCharacter.character); updateSchema(QCharacter.character);
createSchema(QCharacterFriend.characterFriend); updateSchema(QCharacterFriend.characterFriend);
createSchema(QCharacterShortcut.characterShortcut); updateSchema(QCharacterShortcut.characterShortcut);
createSchema(QClan.clan); updateSchema(QClan.clan);
createSchema(QItem.item); updateSchema(QItem.item);
createSchema(QLogChat.logChat); updateSchema(QLogChat.logChat);
if (createSchema(QNPC.npc)) { if (updateSchema(QNPC.npc)) {
try { try {
importData(Paths.get("data/static/npc.csv"), QNPC.npc); importData(Paths.get("data/static/npc.csv"), QNPC.npc);
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block throw new DatabaseException(e);
e.printStackTrace();
} }
} }
} }

View File

@@ -23,15 +23,14 @@ public class QActorSkill extends RelationalPathBase<Skill> {
@ColumnSize(10) @ColumnSize(10)
public final NumberPath<Integer> actorId = createNumber("actor_id", public final NumberPath<Integer> actorId = createNumber("actor_id",
Integer.class); Integer.class);
@ColumnSize(6)
public final NumberPath<Integer> skillId = createNumber("skill_id",
Integer.class);
@ColumnSize(4) @ColumnSize(4)
public final NumberPath<Integer> level = createNumber("level", public final NumberPath<Integer> level = createNumber("level",
Integer.class); Integer.class);
@ColumnSize(6)
public final NumberPath<Integer> skillId = createNumber("skill_id",
Integer.class);
public final PrimaryKey<Skill> primary = createPrimaryKey(actorId, skillId); public final PrimaryKey<Skill> primary = createPrimaryKey(actorId, skillId);
public QActorSkill(String variable) { public QActorSkill(String variable) {

View File

@@ -16,21 +16,13 @@
*/ */
package com.l2jserver.service.database; package com.l2jserver.service.database;
import java.io.IOException;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import com.l2jserver.model.Model;
import com.l2jserver.service.AbstractService.Depends; import com.l2jserver.service.AbstractService.Depends;
import com.l2jserver.service.cache.CacheService; import com.l2jserver.service.cache.CacheService;
import com.l2jserver.service.configuration.ConfigurationService; import com.l2jserver.service.configuration.ConfigurationService;
import com.l2jserver.service.core.LoggingService; import com.l2jserver.service.core.LoggingService;
import com.l2jserver.service.core.threading.ThreadService; import com.l2jserver.service.core.threading.ThreadService;
import com.l2jserver.service.core.vfs.VFSService; import com.l2jserver.service.core.vfs.VFSService;
import com.l2jserver.service.database.dao.SelectMapper;
import com.l2jserver.service.database.sql.AbstractSQLDatabaseService; import com.l2jserver.service.database.sql.AbstractSQLDatabaseService;
import com.mysema.query.sql.RelationalPathBase;
/** /**
* This is an implementation of {@link DatabaseService} that provides an layer * This is an implementation of {@link DatabaseService} that provides an layer
@@ -80,6 +72,6 @@ public class LoginServerSQLDatabaseService extends AbstractSQLDatabaseService
} }
@Override @Override
protected void ensureDatabaseSchema(Connection conn) throws SQLException { public void updateSchemas() {
} }
} }