diff --git a/l2jserver2-common/src/main/java/com/l2jserver/model/AbstractModel.java b/l2jserver2-common/src/main/java/com/l2jserver/model/AbstractModel.java index 53b477e5a..10fe2d2d5 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/model/AbstractModel.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/model/AbstractModel.java @@ -77,18 +77,21 @@ public abstract class AbstractModel> implements Model { */ protected void desireUpdate() { if (this.desire != ObjectDesire.INSERT - && this.desire != ObjectDesire.DELETE) { + && this.desire != ObjectDesire.DELETE + && this.desire != ObjectDesire.TRANSIENT) { log.debug("{} desires an update", this); this.desire = ObjectDesire.UPDATE; } } /** - * Set this object desire to {@link Model.ObjectDesire#INSERT}. If the desire is - * {@link Model.ObjectDesire#DELETE} the desire will not be changed. + * Set this object desire to {@link Model.ObjectDesire#INSERT}. If the + * desire is {@link Model.ObjectDesire#DELETE} the desire will not be + * changed. */ protected void desireInsert() { - if (this.desire != ObjectDesire.DELETE) { + if (this.desire != ObjectDesire.DELETE + && this.desire != ObjectDesire.TRANSIENT) { log.debug("{} desires an insert", this); this.desire = ObjectDesire.INSERT; } diff --git a/l2jserver2-common/src/main/java/com/l2jserver/model/Model.java b/l2jserver2-common/src/main/java/com/l2jserver/model/Model.java index 62fb20ee9..20194252e 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/model/Model.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/model/Model.java @@ -99,6 +99,11 @@ public interface Model> { *

* If tge object is not in the database nothing will happen. */ - DELETE; + DELETE, + /** + * The object is transient and is not meant to be inserted into the + * database + */ + TRANSIENT; } } diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/AbstractDAO.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/AbstractDAO.java index 69b50ca1e..522594998 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/AbstractDAO.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/AbstractDAO.java @@ -24,7 +24,6 @@ import com.l2jserver.model.id.ID; import com.l2jserver.service.core.threading.AbstractTask; import com.l2jserver.service.core.threading.AsyncFuture; import com.l2jserver.service.core.threading.ThreadService; -import com.l2jserver.service.database.DatabaseService.TransactionExecutor; /** * Abstract DAO implementations. Store an instance of {@link DatabaseService}. @@ -94,16 +93,11 @@ public abstract class AbstractDAO, I extends ID> @Override @SafeVarargs public final int saveObjects(final T... objects) { - return database.transaction(new TransactionExecutor() { - @Override - public int perform() { - int rows = 0; - for (final T object : objects) { - rows += save(object); - } - return rows; - } - }); + int rows = 0; + for (final T object : objects) { + rows += save(object); + } + return rows; } @Override diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/DataAccessObject.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/DataAccessObject.java index c8e07fd2c..8c71c60b6 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/DataAccessObject.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/DataAccessObject.java @@ -142,8 +142,12 @@ public interface DataAccessObject, I extends ID> extends int save(O object); /** - * Save several instances to the database using a transaction (if possible). - * This method will only save if the object has changed. + * Save several instances to the database. This method will only save if the + * object has changed. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. * * @param objects * the objects @@ -152,8 +156,14 @@ public interface DataAccessObject, I extends ID> extends int saveObjects(@SuppressWarnings("unchecked") O... objects); /** - * Asynchronously save several instances to the database using a transaction - * (if possible). This method will only save if the object has changed. + * Asynchronously save several instances to the database. This method will + * only save if the object has changed. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. + * Also note that this method should not be used inside transactions, + * instead use {@link #saveObjects(Model...)}. * * @param objects * the objects @@ -185,8 +195,11 @@ public interface DataAccessObject, I extends ID> extends int insert(O object); /** - * Inserts several instances in the database using a transaction (if - * possible). + * Inserts several instances in the database. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. * * @param objects * the objects @@ -195,8 +208,13 @@ public interface DataAccessObject, I extends ID> extends int insertObjects(@SuppressWarnings("unchecked") O... objects); /** - * Asynchronously insert several instances in the database using a - * transaction (if possible). + * Asynchronously insert several instances in the database. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. + * Also note that this method should not be used inside transactions, + * instead use {@link #insertObjects(Model...)}. * * @param objects * the objects @@ -216,8 +234,11 @@ public interface DataAccessObject, I extends ID> extends int update(O object); /** - * Updates several instances in the database using a transaction (if - * possible). + * Updates several instances in the database. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. * * @param objects * the objects @@ -226,8 +247,13 @@ public interface DataAccessObject, I extends ID> extends int updateObjects(@SuppressWarnings("unchecked") O... objects); /** - * Asynchronously update several instances in the database using a - * transaction (if possible). + * Asynchronously update several instances in the database. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. + * Also note that this method should not be used inside transactions, + * instead use {@link #updateObjects(Model...)}. * * @param objects * the objects @@ -246,8 +272,11 @@ public interface DataAccessObject, I extends ID> extends void delete(O object); /** - * Deletes several instances in the database using an transaction (if - * possible). + * Deletes several instances in the database. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. * * @param objects * the objects @@ -256,8 +285,13 @@ public interface DataAccessObject, I extends ID> extends int deleteObjects(@SuppressWarnings("unchecked") O... objects); /** - * Asynchronously delete several instances in the database using a - * transaction (if possible). + * Asynchronously delete several instances in the database. + *

+ * Important note: operations are not performed inside an + * transaction. If transactions are desired, + * {@link DatabaseService#transaction(TransactionExecutor)} should be used. + * Also note that this method should not be used inside transactions, + * instead use {@link #deleteObjects(Model...)}. * * @param objects * the objects diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java index 9d68967fa..3555f0b8a 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java @@ -139,9 +139,8 @@ public abstract class AbstractOrientDatabaseService extends * the {@link DataAccessObject DAO} resolver */ @Inject - public AbstractOrientDatabaseService( - CacheService cacheService, ThreadService threadService, - DAOResolver daoResolver) { + public AbstractOrientDatabaseService(CacheService cacheService, + ThreadService threadService, DAOResolver daoResolver) { super(OrientDatabaseConfiguration.class); this.cacheService = cacheService; this.threadService = threadService; @@ -457,11 +456,29 @@ public abstract class AbstractOrientDatabaseService extends */ protected void updateDesire(Object object, ObjectDesire expected) { if (object instanceof Model) { - if (((Model) object).getObjectDesire() == expected) { - ((Model) object).setObjectDesire(ObjectDesire.NONE); - } + if (((Model) object).getObjectDesire() == ObjectDesire.TRANSIENT) + return; + if (((Model) object).getObjectDesire() != expected) + return; + ((Model) object).setObjectDesire(ObjectDesire.NONE); } } + + /** + * Tests if the object desire is not {@link ObjectDesire#TRANSIENT} + * + * @param object + * the object + * @return true if the object desire is {@link ObjectDesire#TRANSIENT} + */ + protected boolean testDesire(Object object) { + if (object instanceof Model) { + if (((Model) object).getObjectDesire() == ObjectDesire.TRANSIENT) + return true; + return false; + } + return false; + } /** * Returns the parameter name for the given path @@ -582,6 +599,9 @@ public abstract class AbstractOrientDatabaseService extends final DocumentDatabaseRow row = new DocumentDatabaseRow(); while (iterator.hasNext()) { final O object = iterator.next(); + if(testDesire(object)) + continue; + row.setDocument(new ODocument(database, entity.getTableName())); mapper.insert(entity, object, row); @@ -667,6 +687,8 @@ public abstract class AbstractOrientDatabaseService extends final DocumentDatabaseRow row = new DocumentDatabaseRow(); while (iterator.hasNext()) { final O object = iterator.next(); + if(testDesire(object)) + continue; List documents = database .query(new ONativeSynchQuery( @@ -755,6 +777,8 @@ public abstract class AbstractOrientDatabaseService extends int rows = 0; while (iterator.hasNext()) { final O object = iterator.next(); + if(testDesire(object)) + continue; List documents = database .query(new ONativeSynchQuery( @@ -953,9 +977,7 @@ public abstract class AbstractOrientDatabaseService extends if (object == null) { object = mapper.select(entity, row); updateCache(object, service); - if (object instanceof Model) { - ((Model) object).setObjectDesire(ObjectDesire.NONE); - } + updateDesire(object, ObjectDesire.INSERT); } return object; } @@ -999,9 +1021,7 @@ public abstract class AbstractOrientDatabaseService extends if (object == null) { object = mapper.select(entity, row); updateCache(object, service); - if (object instanceof Model) { - ((Model) object).setObjectDesire(ObjectDesire.NONE); - } + updateDesire(object, ObjectDesire.INSERT); } if (object != null) results.add(object); diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java index b2ba35620..6395b8f4f 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java @@ -161,7 +161,7 @@ public abstract class AbstractSQLDatabaseService extends /** * The connection used inside a transaction from multiple DAOs. */ - private ThreadLocal transactionalConnection = new ThreadLocal<>(); + private ThreadLocal transaction = new ThreadLocal<>(); /** * The {@link Type} that will be mapped by the querydsl. */ @@ -281,7 +281,7 @@ public abstract class AbstractSQLDatabaseService extends @Override public , T extends RelationalPathBase> void importData( - java.nio.file.Path path, final T entity) throws IOException { + final java.nio.file.Path path, final T entity) throws IOException { final Connection conn; try { conn = dataSource.getConnection(); @@ -379,7 +379,7 @@ public abstract class AbstractSQLDatabaseService extends try { conn.setAutoCommit(false); - transactionalConnection.set(conn); + transaction.set(new TransactionIsolatedConnection(conn)); final int rows = executor.perform(); conn.commit(); @@ -388,8 +388,8 @@ public abstract class AbstractSQLDatabaseService extends conn.rollback(); throw e; } finally { - transactionalConnection.set(null); - transactionalConnection.remove(); + transaction.set(null); + transaction.remove(); conn.setAutoCommit(true); conn.close(); } @@ -425,38 +425,20 @@ public abstract class AbstractSQLDatabaseService extends public T query(Query query) throws DatabaseException { Preconditions.checkNotNull(query, "query"); try { - boolean inTransaction = true; - Connection conn = transactionalConnection.get(); + Connection conn = transaction.get(); if (conn == null) { log.debug( "Transactional connection for {} is not set, creating new connection", query); - inTransaction = false; conn = dataSource.getConnection(); } log.debug("Executing query {} with {}", query, conn); try { - if (!inTransaction) { - conn.setAutoCommit(false); - } - try { - return query - .query(engine.createSQLQueryFactory(conn), this); - } finally { - if (!inTransaction) { - conn.commit(); - } - } - } catch (Exception e) { - if (!inTransaction) { - conn.rollback(); - } - throw e; + return query.query(engine.createSQLQueryFactory(conn), this); } finally { - if (!inTransaction) { - conn.setAutoCommit(true); - conn.close(); - } + // transaction wrappers does not allow closing, so this is safe + // to do + conn.close(); } } catch (Throwable e) { log.error("Could not open database connection", e); @@ -558,11 +540,29 @@ public abstract class AbstractSQLDatabaseService extends */ protected void updateDesire(Object object, ObjectDesire expected) { if (object instanceof Model) { - if (((Model) object).getObjectDesire() == expected) { - ((Model) object).setObjectDesire(ObjectDesire.NONE); - } + if (((Model) object).getObjectDesire() == ObjectDesire.TRANSIENT) + return; + if (((Model) object).getObjectDesire() != expected) + return; + ((Model) object).setObjectDesire(ObjectDesire.NONE); } } + + /** + * Tests if the object desire is not {@link ObjectDesire#TRANSIENT} + * + * @param object + * the object + * @return true if the object desire is {@link ObjectDesire#TRANSIENT} + */ + protected boolean testDesire(Object object) { + if (object instanceof Model) { + if (((Model) object).getObjectDesire() == ObjectDesire.TRANSIENT) + return true; + return false; + } + return false; + } } /** @@ -670,6 +670,9 @@ public abstract class AbstractSQLDatabaseService extends int rows = 0; while (iterator.hasNext()) { final O object = iterator.next(); + if (testDesire(object)) + continue; + final SQLInsertWritableDatabaseRow row = new SQLInsertWritableDatabaseRow( factory.insert(entity)); mapper.insert(entity, object, row); @@ -751,6 +754,9 @@ public abstract class AbstractSQLDatabaseService extends int rows = 0; while (iterator.hasNext()) { final O object = iterator.next(); + if (testDesire(object)) + continue; + final SQLUpdateWritableDatabaseRow row = new SQLUpdateWritableDatabaseRow( factory.update(entity)); // maps query to the values @@ -825,6 +831,9 @@ public abstract class AbstractSQLDatabaseService extends int rows = 0; while (iterator.hasNext()) { final O object = iterator.next(); + if (testDesire(object)) + continue; + final SQLDeleteClause delete = factory.delete(entity); // maps query to the values query(delete, object); diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/TransactionIsolatedConnection.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/TransactionIsolatedConnection.java new file mode 100644 index 000000000..d5eedcd67 --- /dev/null +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/TransactionIsolatedConnection.java @@ -0,0 +1,337 @@ +/* + * This file is part of l2jserver2 . + * + * 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 . + */ +package com.l2jserver.service.database.sql; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +/** + * Wraps an {@link Connection} into as an unclosable and transaction-disabled + * {@link Connection}s + * + * @author Rogiel + */ +public class TransactionIsolatedConnection implements Connection { + /** + * The wrapped connection + */ + private final Connection connection; + + /** + * @param connection + * the connection + */ + public TransactionIsolatedConnection(Connection connection) { + super(); + this.connection = connection; + } + + @Override + public void abort(Executor arg0) throws SQLException { + connection.abort(arg0); + } + + @Override + public void clearWarnings() throws SQLException { + connection.clearWarnings(); + } + + @Override + public void close() throws SQLException { + // DO NOTHING + } + + @Override + public void commit() throws SQLException { + // DO NOTHING + } + + @Override + public Array createArrayOf(String arg0, Object[] arg1) throws SQLException { + return connection.createArrayOf(arg0, arg1); + } + + @Override + public Blob createBlob() throws SQLException { + return connection.createBlob(); + } + + @Override + public Clob createClob() throws SQLException { + return connection.createClob(); + } + + @Override + public NClob createNClob() throws SQLException { + return connection.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return connection.createSQLXML(); + } + + @Override + public Statement createStatement() throws SQLException { + return connection.createStatement(); + } + + @Override + public Statement createStatement(int arg0, int arg1, int arg2) + throws SQLException { + return connection.createStatement(arg0, arg1, arg2); + } + + @Override + public Statement createStatement(int arg0, int arg1) throws SQLException { + return connection.createStatement(arg0, arg1); + } + + @Override + public Struct createStruct(String arg0, Object[] arg1) throws SQLException { + return connection.createStruct(arg0, arg1); + } + + @Override + public boolean getAutoCommit() throws SQLException { + return connection.getAutoCommit(); + } + + @Override + public String getCatalog() throws SQLException { + return connection.getCatalog(); + } + + @Override + public Properties getClientInfo() throws SQLException { + return connection.getClientInfo(); + } + + @Override + public String getClientInfo(String arg0) throws SQLException { + return connection.getClientInfo(arg0); + } + + @Override + public int getHoldability() throws SQLException { + return connection.getHoldability(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return connection.getMetaData(); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return connection.getNetworkTimeout(); + } + + @Override + public String getSchema() throws SQLException { + return connection.getSchema(); + } + + @Override + public int getTransactionIsolation() throws SQLException { + return connection.getTransactionIsolation(); + } + + @Override + public Map> getTypeMap() throws SQLException { + return connection.getTypeMap(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return connection.getWarnings(); + } + + @Override + public boolean isClosed() throws SQLException { + return connection.isClosed(); + } + + @Override + public boolean isReadOnly() throws SQLException { + return connection.isReadOnly(); + } + + @Override + public boolean isValid(int arg0) throws SQLException { + return connection.isValid(arg0); + } + + @Override + public boolean isWrapperFor(Class arg0) throws SQLException { + return connection.isWrapperFor(arg0); + } + + @Override + public String nativeSQL(String arg0) throws SQLException { + return connection.nativeSQL(arg0); + } + + @Override + public CallableStatement prepareCall(String arg0, int arg1, int arg2, + int arg3) throws SQLException { + return connection.prepareCall(arg0, arg1, arg2, arg3); + } + + @Override + public CallableStatement prepareCall(String arg0, int arg1, int arg2) + throws SQLException { + return connection.prepareCall(arg0, arg1, arg2); + } + + @Override + public CallableStatement prepareCall(String arg0) throws SQLException { + return connection.prepareCall(arg0); + } + + @Override + public PreparedStatement prepareStatement(String arg0, int arg1, int arg2, + int arg3) throws SQLException { + return connection.prepareStatement(arg0, arg1, arg2, arg3); + } + + @Override + public PreparedStatement prepareStatement(String arg0, int arg1, int arg2) + throws SQLException { + return connection.prepareStatement(arg0, arg1, arg2); + } + + @Override + public PreparedStatement prepareStatement(String arg0, int arg1) + throws SQLException { + return connection.prepareStatement(arg0, arg1); + } + + @Override + public PreparedStatement prepareStatement(String arg0, int[] arg1) + throws SQLException { + return connection.prepareStatement(arg0, arg1); + } + + @Override + public PreparedStatement prepareStatement(String arg0, String[] arg1) + throws SQLException { + return connection.prepareStatement(arg0, arg1); + } + + @Override + public PreparedStatement prepareStatement(String arg0) throws SQLException { + return connection.prepareStatement(arg0); + } + + @Override + public void releaseSavepoint(Savepoint arg0) throws SQLException { + // DO NOTHING + } + + @Override + public void rollback() throws SQLException { + // DO NOTHING + } + + @Override + public void rollback(Savepoint arg0) throws SQLException { + // DO NOTHING + } + + @Override + public void setAutoCommit(boolean arg0) throws SQLException { + // DO NOTHING + } + + @Override + public void setCatalog(String arg0) throws SQLException { + connection.setCatalog(arg0); + } + + @Override + public void setClientInfo(Properties arg0) throws SQLClientInfoException { + connection.setClientInfo(arg0); + } + + @Override + public void setClientInfo(String arg0, String arg1) + throws SQLClientInfoException { + connection.setClientInfo(arg0, arg1); + } + + @Override + public void setHoldability(int arg0) throws SQLException { + connection.setHoldability(arg0); + } + + @Override + public void setNetworkTimeout(Executor arg0, int arg1) throws SQLException { + connection.setNetworkTimeout(arg0, arg1); + } + + @Override + public void setReadOnly(boolean arg0) throws SQLException { + connection.setReadOnly(arg0); + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return connection.setSavepoint(); + } + + @Override + public Savepoint setSavepoint(String arg0) throws SQLException { + return connection.setSavepoint(arg0); + } + + @Override + public void setSchema(String arg0) throws SQLException { + connection.setSchema(arg0); + } + + @Override + public void setTransactionIsolation(int arg0) throws SQLException { + connection.setTransactionIsolation(arg0); + } + + @Override + public void setTypeMap(Map> arg0) throws SQLException { + connection.setTypeMap(arg0); + } + + @Override + public T unwrap(Class arg0) throws SQLException { + return connection.unwrap(arg0); + } +}