mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Fix for #232 - New ebean leaves connections with active transactions
This commit is contained in:
@@ -1,388 +1,427 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Database platform specific settings.
|
||||
*/
|
||||
public class DatabasePlatform {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
*/
|
||||
protected String openQuote = "\"";
|
||||
|
||||
/**
|
||||
* The close quote used by quoted identifiers.
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
|
||||
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* DB specific DDL syntax.
|
||||
*/
|
||||
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
protected int booleanDbType = Types.BOOLEAN;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
*/
|
||||
protected int blobDbType = Types.BLOB;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
*/
|
||||
protected int clobDbType = Types.CLOB;
|
||||
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
*/
|
||||
protected boolean treatEmptyStringsAsNull;
|
||||
|
||||
/**
|
||||
* The database platform name.
|
||||
*/
|
||||
protected String name = "generic";
|
||||
|
||||
protected String columnAliasPrefix = "c";
|
||||
|
||||
protected String tableAliasPlaceHolder = "${ta}";
|
||||
|
||||
/**
|
||||
* Use a BackTick ` at the beginning and end of table or column names that you
|
||||
* want to use quoted identifiers for. The backticks get converted to the
|
||||
* appropriate characters in convertQuotedIdentifiers
|
||||
*/
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
protected boolean idInExpandedForm;
|
||||
|
||||
protected boolean selectCountWithAlias;
|
||||
|
||||
/**
|
||||
* If set then use the FORWARD ONLY hint when creating ResultSets for
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
|
||||
/**
|
||||
* Flag set for SQL Server due to lack of support of getGeneratedKeys in
|
||||
* batch mode (meaning for batch inserts you should explicitly turn off
|
||||
* getGeneratedKeys - joy).
|
||||
*/
|
||||
protected boolean disallowBatchOnCascade;
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
public DatabasePlatform() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the DatabasePlatform.
|
||||
* <p>
|
||||
* "generic" is returned when no specific database platform has been set or
|
||||
* found.
|
||||
* </p>
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DB Sequence based IdGenerator.
|
||||
*
|
||||
* @param be
|
||||
* the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds
|
||||
* the DataSource
|
||||
* @param seqName
|
||||
* the name of the sequence
|
||||
* @param batchSize
|
||||
* the number of sequences that should be loaded
|
||||
*/
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public DbEncrypt getDbEncrypt() {
|
||||
return dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public void setDbEncrypt(DbEncrypt dbEncrypt) {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
* @return the db type map
|
||||
*/
|
||||
public DbTypeMap getDbTypeMap() {
|
||||
return dbTypeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL syntax for this platform.
|
||||
*
|
||||
* @return the db ddl syntax
|
||||
*/
|
||||
public DbDdlSyntax getDbDdlSyntax() {
|
||||
return dbDdlSyntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column alias prefix.
|
||||
*/
|
||||
public String getColumnAliasPrefix() {
|
||||
return columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column alias prefix.
|
||||
*/
|
||||
public void setColumnAliasPrefix(String columnAliasPrefix) {
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table alias placeholder.
|
||||
*/
|
||||
public String getTableAliasPlaceHolder() {
|
||||
return tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table alias placeholder.
|
||||
*/
|
||||
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
return booleanDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Blob.
|
||||
* <p>
|
||||
* This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for
|
||||
* example.
|
||||
* </p>
|
||||
*/
|
||||
public int getBlobDbType() {
|
||||
return blobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Clob.
|
||||
* <p>
|
||||
* This is typically Types.CLOB but for Postgres is Types.VARCHAR.
|
||||
* </p>
|
||||
*/
|
||||
public int getClobDbType() {
|
||||
return clobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if empty strings should be treated as null.
|
||||
*
|
||||
* @return true, if checks if is treat empty strings as null
|
||||
*/
|
||||
public boolean isTreatEmptyStringsAsNull() {
|
||||
return treatEmptyStringsAsNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a compound ID in (...) type expression needs to be in
|
||||
* expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in
|
||||
* ((?,?),(?,?),...);
|
||||
*/
|
||||
public boolean isIdInExpandedForm() {
|
||||
return idInExpandedForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on
|
||||
* findIterate() and findVisit() PreparedStatements.
|
||||
* <p>
|
||||
* This specifically is required for MySql when processing large results.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isForwardOnlyHintOnFindIterate() {
|
||||
return forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements.
|
||||
*/
|
||||
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
* @return the db identity
|
||||
*/
|
||||
public DbIdentity getDbIdentity() {
|
||||
return dbIdentity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SqlLimiter used to apply additional sql around a query to limit
|
||||
* its results.
|
||||
* <p>
|
||||
* Basically add the clauses for limit/offset, rownum, row_number().
|
||||
* </p>
|
||||
*
|
||||
* @return the sql limiter
|
||||
*/
|
||||
public SqlLimiter getSqlLimiter() {
|
||||
return sqlLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert backticks to the platform specific open quote and close quote
|
||||
*
|
||||
* <p>
|
||||
* Specific plugins may implement this method to cater for platform specific
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName
|
||||
* the db name
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
// Ignore null values e.g. schema name or catalog
|
||||
if (dbName != null && dbName.length() > 0) {
|
||||
if (dbName.charAt(0) == BACK_TICK) {
|
||||
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
|
||||
|
||||
String quotedName = getOpenQuote();
|
||||
quotedName += dbName.substring(1, dbName.length() - 1);
|
||||
quotedName += getCloseQuote();
|
||||
|
||||
return quotedName;
|
||||
|
||||
} else {
|
||||
logger.error("Missing backquote on [" + dbName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if select count against anonymous view requires an alias.
|
||||
*/
|
||||
public boolean isSelectCountWithAlias() {
|
||||
return selectCountWithAlias;
|
||||
}
|
||||
|
||||
public String completeSql(String sql, Query<?> query) {
|
||||
if (Boolean.TRUE.equals(query.isForUpdate())) {
|
||||
sql = withForUpdate(sql);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected String withForUpdate(String sql) {
|
||||
// silently assume the database does not support the "for update" clause.
|
||||
logger.info("it seems your database does not support the 'for update' clause");
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the like clause used by this database platform.
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the persistBatchOnCascade setting should be ignored.
|
||||
* <p>
|
||||
* This is primarily for SQL Server which does not support getGeneratedKeys with jdbc batch mode
|
||||
* so can't really be transparently used.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDisallowBatchOnCascade() {
|
||||
return disallowBatchOnCascade;
|
||||
}
|
||||
}
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Database platform specific settings.
|
||||
*/
|
||||
public class DatabasePlatform {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
|
||||
|
||||
/**
|
||||
* Behavior used when ending a query only transaction (at read committed isolation level).
|
||||
*/
|
||||
public enum OnQueryOnly {
|
||||
|
||||
/**
|
||||
* Rollback the transaction.
|
||||
*/
|
||||
ROLLBACK,
|
||||
|
||||
/**
|
||||
* Just close the transaction. Valid at READ_COMMITTED isolation and preferred on some Databases
|
||||
* as a performance optimisation.
|
||||
*/
|
||||
CLOSE,
|
||||
|
||||
/**
|
||||
* Commit the transaction
|
||||
*/
|
||||
COMMIT
|
||||
}
|
||||
|
||||
/**
|
||||
* The behaviour used when ending a read only transaction at read committed isolation level.
|
||||
*/
|
||||
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
*/
|
||||
protected String openQuote = "\"";
|
||||
|
||||
/**
|
||||
* The close quote used by quoted identifiers.
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
|
||||
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* DB specific DDL syntax.
|
||||
*/
|
||||
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
protected int booleanDbType = Types.BOOLEAN;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
*/
|
||||
protected int blobDbType = Types.BLOB;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
*/
|
||||
protected int clobDbType = Types.CLOB;
|
||||
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
*/
|
||||
protected boolean treatEmptyStringsAsNull;
|
||||
|
||||
/**
|
||||
* The database platform name.
|
||||
*/
|
||||
protected String name = "generic";
|
||||
|
||||
protected String columnAliasPrefix = "c";
|
||||
|
||||
protected String tableAliasPlaceHolder = "${ta}";
|
||||
|
||||
/**
|
||||
* Use a BackTick ` at the beginning and end of table or column names that you
|
||||
* want to use quoted identifiers for. The backticks get converted to the
|
||||
* appropriate characters in convertQuotedIdentifiers
|
||||
*/
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
protected boolean idInExpandedForm;
|
||||
|
||||
protected boolean selectCountWithAlias;
|
||||
|
||||
/**
|
||||
* If set then use the FORWARD ONLY hint when creating ResultSets for
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
|
||||
/**
|
||||
* Flag set for SQL Server due to lack of support of getGeneratedKeys in
|
||||
* batch mode (meaning for batch inserts you should explicitly turn off
|
||||
* getGeneratedKeys - joy).
|
||||
*/
|
||||
protected boolean disallowBatchOnCascade;
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
public DatabasePlatform() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the DatabasePlatform.
|
||||
* <p>
|
||||
* "generic" is returned when no specific database platform has been set or
|
||||
* found.
|
||||
* </p>
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DB Sequence based IdGenerator.
|
||||
*
|
||||
* @param be
|
||||
* the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds
|
||||
* the DataSource
|
||||
* @param seqName
|
||||
* the name of the sequence
|
||||
* @param batchSize
|
||||
* the number of sequences that should be loaded
|
||||
*/
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the behaviour to use when ending a read only transaction.
|
||||
*/
|
||||
public OnQueryOnly getOnQueryOnly() {
|
||||
return onQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the behaviour to use when ending a read only transaction.
|
||||
*/
|
||||
public void setOnQueryOnly(OnQueryOnly onQueryOnly) {
|
||||
this.onQueryOnly = onQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public DbEncrypt getDbEncrypt() {
|
||||
return dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public void setDbEncrypt(DbEncrypt dbEncrypt) {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
* @return the db type map
|
||||
*/
|
||||
public DbTypeMap getDbTypeMap() {
|
||||
return dbTypeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL syntax for this platform.
|
||||
*
|
||||
* @return the db ddl syntax
|
||||
*/
|
||||
public DbDdlSyntax getDbDdlSyntax() {
|
||||
return dbDdlSyntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column alias prefix.
|
||||
*/
|
||||
public String getColumnAliasPrefix() {
|
||||
return columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column alias prefix.
|
||||
*/
|
||||
public void setColumnAliasPrefix(String columnAliasPrefix) {
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table alias placeholder.
|
||||
*/
|
||||
public String getTableAliasPlaceHolder() {
|
||||
return tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table alias placeholder.
|
||||
*/
|
||||
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
return booleanDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Blob.
|
||||
* <p>
|
||||
* This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for
|
||||
* example.
|
||||
* </p>
|
||||
*/
|
||||
public int getBlobDbType() {
|
||||
return blobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Clob.
|
||||
* <p>
|
||||
* This is typically Types.CLOB but for Postgres is Types.VARCHAR.
|
||||
* </p>
|
||||
*/
|
||||
public int getClobDbType() {
|
||||
return clobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if empty strings should be treated as null.
|
||||
*
|
||||
* @return true, if checks if is treat empty strings as null
|
||||
*/
|
||||
public boolean isTreatEmptyStringsAsNull() {
|
||||
return treatEmptyStringsAsNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a compound ID in (...) type expression needs to be in
|
||||
* expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in
|
||||
* ((?,?),(?,?),...);
|
||||
*/
|
||||
public boolean isIdInExpandedForm() {
|
||||
return idInExpandedForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on
|
||||
* findIterate() and findVisit() PreparedStatements.
|
||||
* <p>
|
||||
* This specifically is required for MySql when processing large results.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isForwardOnlyHintOnFindIterate() {
|
||||
return forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements.
|
||||
*/
|
||||
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
* @return the db identity
|
||||
*/
|
||||
public DbIdentity getDbIdentity() {
|
||||
return dbIdentity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SqlLimiter used to apply additional sql around a query to limit
|
||||
* its results.
|
||||
* <p>
|
||||
* Basically add the clauses for limit/offset, rownum, row_number().
|
||||
* </p>
|
||||
*
|
||||
* @return the sql limiter
|
||||
*/
|
||||
public SqlLimiter getSqlLimiter() {
|
||||
return sqlLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert backticks to the platform specific open quote and close quote
|
||||
*
|
||||
* <p>
|
||||
* Specific plugins may implement this method to cater for platform specific
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName
|
||||
* the db name
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
// Ignore null values e.g. schema name or catalog
|
||||
if (dbName != null && dbName.length() > 0) {
|
||||
if (dbName.charAt(0) == BACK_TICK) {
|
||||
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
|
||||
|
||||
String quotedName = getOpenQuote();
|
||||
quotedName += dbName.substring(1, dbName.length() - 1);
|
||||
quotedName += getCloseQuote();
|
||||
|
||||
return quotedName;
|
||||
|
||||
} else {
|
||||
logger.error("Missing backquote on [" + dbName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if select count against anonymous view requires an alias.
|
||||
*/
|
||||
public boolean isSelectCountWithAlias() {
|
||||
return selectCountWithAlias;
|
||||
}
|
||||
|
||||
public String completeSql(String sql, Query<?> query) {
|
||||
if (Boolean.TRUE.equals(query.isForUpdate())) {
|
||||
sql = withForUpdate(sql);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected String withForUpdate(String sql) {
|
||||
// silently assume the database does not support the "for update" clause.
|
||||
logger.info("it seems your database does not support the 'for update' clause");
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the like clause used by this database platform.
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the persistBatchOnCascade setting should be ignored.
|
||||
* <p>
|
||||
* This is primarily for SQL Server which does not support getGeneratedKeys with jdbc batch mode
|
||||
* so can't really be transparently used.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDisallowBatchOnCascade() {
|
||||
return disallowBatchOnCascade;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,68 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Oracle10 and greater specific platform.
|
||||
*/
|
||||
public class Oracle10Platform extends DatabasePlatform {
|
||||
|
||||
public Oracle10Platform() {
|
||||
super();
|
||||
this.name = "oracle";
|
||||
this.dbEncrypt = new Oracle10DbEncrypt();
|
||||
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("number(1) default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("number", 10));
|
||||
dbTypeMap.put(Types.BIGINT, new DbType("number", 19));
|
||||
dbTypeMap.put(Types.REAL, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.SMALLINT, new DbType("number", 5));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("number", 3));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("number", 38));
|
||||
|
||||
dbTypeMap.put(Types.VARCHAR, new DbType("varchar2", 255));
|
||||
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("blob"));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("clob"));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("raw", 255));
|
||||
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
|
||||
|
||||
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
|
||||
dbDdlSyntax.setIdentity(null);
|
||||
dbDdlSyntax.setMaxConstraintNameLength(30);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Oracle10 and greater specific platform.
|
||||
*/
|
||||
public class Oracle10Platform extends DatabasePlatform {
|
||||
|
||||
public Oracle10Platform() {
|
||||
super();
|
||||
this.name = "oracle";
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Oracle
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.dbEncrypt = new Oracle10DbEncrypt();
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("number(1) default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("number", 10));
|
||||
dbTypeMap.put(Types.BIGINT, new DbType("number", 19));
|
||||
dbTypeMap.put(Types.REAL, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.SMALLINT, new DbType("number", 5));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("number", 3));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("number", 38));
|
||||
|
||||
dbTypeMap.put(Types.VARCHAR, new DbType("varchar2", 255));
|
||||
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("blob"));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("clob"));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("raw", 255));
|
||||
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
|
||||
|
||||
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
|
||||
dbDdlSyntax.setIdentity(null);
|
||||
dbDdlSyntax.setMaxConstraintNameLength(30);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,80 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v9 specific platform.
|
||||
* <p>
|
||||
* Uses serial types and getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Unique jdbc type id defined for hstore type.
|
||||
*/
|
||||
public static final int TYPE_HSTORE = 4001;
|
||||
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.columnAliasPrefix = "as c";
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
|
||||
|
||||
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v9 specific platform.
|
||||
* <p>
|
||||
* Uses serial types and getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Unique jdbc type id defined for hstore type.
|
||||
*/
|
||||
public static final int TYPE_HSTORE = 4001;
|
||||
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.columnAliasPrefix = "as c";
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
|
||||
|
||||
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+436
-451
@@ -1,451 +1,436 @@
|
||||
package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.PersistBatch;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.event.TransactionEventListener;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Manages transactions.
|
||||
* <p>
|
||||
* Keeps the Cache and Cluster in synch when transactions are committed.
|
||||
* </p>
|
||||
*/
|
||||
public class TransactionManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class);
|
||||
|
||||
public static final Logger SQL_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SQL");
|
||||
|
||||
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SUM");
|
||||
|
||||
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.TXN");
|
||||
|
||||
/**
|
||||
* The behavior desired when ending a query only transaction.
|
||||
*/
|
||||
public enum OnQueryOnly {
|
||||
|
||||
/**
|
||||
* Rollback the transaction.
|
||||
*/
|
||||
ROLLBACK,
|
||||
|
||||
/**
|
||||
* Just close the transaction.
|
||||
*/
|
||||
CLOSE_ON_READCOMMITTED,
|
||||
|
||||
/**
|
||||
* Commit the transaction
|
||||
*/
|
||||
COMMIT
|
||||
}
|
||||
|
||||
protected final BeanDescriptorManager beanDescriptorManager;
|
||||
|
||||
/**
|
||||
* Prefix for transaction id's (logging).
|
||||
*/
|
||||
protected final String prefix;
|
||||
|
||||
protected final String externalTransPrefix;
|
||||
|
||||
/**
|
||||
* The dataSource of connections.
|
||||
*/
|
||||
protected final DataSource dataSource;
|
||||
|
||||
/**
|
||||
* Flag to indicate the default Isolation is READ COMMITTED. This enables us
|
||||
* to close queryOnly transactions rather than commit or rollback them.
|
||||
*/
|
||||
protected final OnQueryOnly onQueryOnly;
|
||||
|
||||
protected final BackgroundExecutor backgroundExecutor;
|
||||
|
||||
protected final ClusterManager clusterManager;
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
protected final PersistBatch persistBatch;
|
||||
|
||||
protected final PersistBatch persistBatchOnCascade;
|
||||
|
||||
/**
|
||||
* Id's for transaction logging.
|
||||
*/
|
||||
protected final AtomicLong transactionCounter = new AtomicLong(1000);
|
||||
|
||||
protected final BulkEventListenerMap bulkEventListenerMap;
|
||||
|
||||
protected final TransactionEventListener[] transactionEventListeners;
|
||||
|
||||
/**
|
||||
* Create the TransactionManager
|
||||
*/
|
||||
public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config,
|
||||
BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
|
||||
|
||||
this.persistBatch = config.getPersistBatch();
|
||||
this.persistBatchOnCascade = config.appliedPersistBatchOnCascade();
|
||||
this.beanDescriptorManager = descMgr;
|
||||
this.clusterManager = clusterManager;
|
||||
this.serverName = config.getName();
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
this.dataSource = config.getDataSource();
|
||||
this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners());
|
||||
|
||||
List<TransactionEventListener> transactionEventListeners = bootupClasses.getTransactionEventListeners();
|
||||
this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]);
|
||||
|
||||
this.prefix = "";
|
||||
this.externalTransPrefix = "e";
|
||||
|
||||
String value = System.getProperty("ebean.transaction.onqueryonly", "CLOSE").toUpperCase().trim();
|
||||
this.onQueryOnly = getOnQueryOnly(value, dataSource);
|
||||
|
||||
initialiseHeartbeat();
|
||||
}
|
||||
|
||||
private void initialiseHeartbeat() {
|
||||
if (dataSource instanceof DataSourcePool) {
|
||||
DataSourcePool ds = (DataSourcePool)dataSource;
|
||||
backgroundExecutor.executePeriodically(ds.getHeartbeatRunnable(), ds.getHeartbeatFreqSecs(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) {
|
||||
if (shutdownDataSource && (dataSource instanceof DataSourcePool)) {
|
||||
((DataSourcePool)dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
}
|
||||
|
||||
public BeanDescriptorManager getBeanDescriptorManager() {
|
||||
return beanDescriptorManager;
|
||||
}
|
||||
|
||||
public BulkEventListenerMap getBulkEventListenerMap() {
|
||||
return bulkEventListenerMap;
|
||||
}
|
||||
|
||||
public PersistBatch getPersistBatch() {
|
||||
return persistBatch;
|
||||
}
|
||||
|
||||
public PersistBatch getPersistBatchOnCascade() {
|
||||
return persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the behaviour to use when a query only transaction is committed.
|
||||
* <p>
|
||||
* There is a potential optimisation available when read committed is the default
|
||||
* isolation level. If it is, then Connections used only for queries do not require
|
||||
* commit or rollback but instead can just be put back into the pool via close().
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Isolation level is higher (say SERIALIZABLE) then Connections used
|
||||
* just for queries do need to be committed or rollback after the query.
|
||||
* </p>
|
||||
*/
|
||||
private OnQueryOnly getOnQueryOnly(String onQueryOnly, DataSource ds) {
|
||||
|
||||
if (onQueryOnly.equals("COMMIT")){
|
||||
return OnQueryOnly.COMMIT;
|
||||
}
|
||||
if (onQueryOnly.startsWith("CLOSE")){
|
||||
if (!isReadCommitedIsolation(ds)){
|
||||
String m = "transaction.queryonlyclose is true but the transaction Isolation Level is not READ_COMMITTED";
|
||||
throw new PersistenceException(m);
|
||||
} else {
|
||||
return OnQueryOnly.CLOSE_ON_READCOMMITTED;
|
||||
}
|
||||
}
|
||||
// default to rollback
|
||||
return OnQueryOnly.ROLLBACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the isolation level is read committed.
|
||||
*/
|
||||
private boolean isReadCommitedIsolation(DataSource ds) {
|
||||
|
||||
Connection c = null;
|
||||
try {
|
||||
c = ds.getConnection();
|
||||
|
||||
int isolationLevel = c.getTransactionIsolation();
|
||||
return (isolationLevel == Connection.TRANSACTION_READ_COMMITTED);
|
||||
|
||||
} catch (SQLException ex) {
|
||||
String m = "Errored trying to determine the default Isolation Level";
|
||||
throw new PersistenceException(m, ex);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.error("closing connection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the type of behavior to use when closing a transaction that was used to query data only.
|
||||
*/
|
||||
public OnQueryOnly getOnQueryOnly() {
|
||||
return onQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the externally supplied Connection.
|
||||
*/
|
||||
public SpiTransaction wrapExternalConnection(Connection c) {
|
||||
|
||||
return wrapExternalConnection(externalTransPrefix + c.hashCode(), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an externally supplied Connection with a known transaction id.
|
||||
*/
|
||||
public SpiTransaction wrapExternalConnection(String id, Connection c) {
|
||||
|
||||
ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this);
|
||||
|
||||
// set the default batch mode
|
||||
t.setBatch(persistBatch);
|
||||
t.setBatchOnCascade(persistBatchOnCascade);
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Transaction.
|
||||
*/
|
||||
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
|
||||
Connection c = null;
|
||||
try {
|
||||
c = dataSource.getConnection();
|
||||
long id = transactionCounter.incrementAndGet();
|
||||
|
||||
SpiTransaction t = createTransaction(explicit, c, id);
|
||||
if (isolationLevel > -1) {
|
||||
c.setTransactionIsolation(isolationLevel);
|
||||
}
|
||||
|
||||
if (explicit && TXN_LOGGER.isTraceEnabled()) {
|
||||
TXN_LOGGER.trace(t.getLogPrefix()+"Begin");
|
||||
}
|
||||
|
||||
return t;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
// close connection on failed creation
|
||||
try {
|
||||
if (c != null){
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing failed connection", e);
|
||||
}
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public SpiTransaction createQueryTransaction() {
|
||||
Connection c = null;
|
||||
try {
|
||||
c = dataSource.getConnection();
|
||||
long id = transactionCounter.incrementAndGet();
|
||||
|
||||
return createTransaction(false, c, id);
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
// close the connection and re-throw the exception
|
||||
try {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing failed connection", e);
|
||||
}
|
||||
throw ex;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
// don't need to close connection in this case
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new transaction.
|
||||
*/
|
||||
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
|
||||
return new JdbcTransaction(prefix + id, explicit, c, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a local rolled back transaction.
|
||||
*/
|
||||
public void notifyOfRollback(SpiTransaction transaction, Throwable cause) {
|
||||
|
||||
try {
|
||||
if (TXN_LOGGER.isInfoEnabled()) {
|
||||
String msg = transaction.getLogPrefix()+"Rollback";
|
||||
if (cause != null){
|
||||
msg += " error: "+formatThrowable(cause);
|
||||
}
|
||||
TXN_LOGGER.info(msg);
|
||||
}
|
||||
|
||||
for (TransactionEventListener listener : transactionEventListeners) {
|
||||
listener.postTransactionRollback(transaction, cause);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error while notifying TransactionEventListener of rollback event", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query only transaction in read committed isolation.
|
||||
*/
|
||||
public void notifyOfQueryOnly(boolean onCommit, SpiTransaction transaction, Throwable cause) {
|
||||
|
||||
// Nothing that interesting here
|
||||
if (TXN_LOGGER.isTraceEnabled()) {
|
||||
TXN_LOGGER.trace(transaction.getLogPrefix()+"Commit - query only");
|
||||
}
|
||||
}
|
||||
|
||||
private String formatThrowable(Throwable e){
|
||||
if (e == null){
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
formatThrowable(e, sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void formatThrowable(Throwable e, StringBuilder sb){
|
||||
|
||||
sb.append(e.toString());
|
||||
StackTraceElement[] stackTrace = e.getStackTrace();
|
||||
if (stackTrace.length > 0){
|
||||
sb.append(" stack0: ");
|
||||
sb.append(stackTrace[0]);
|
||||
}
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null){
|
||||
sb.append(" cause: ");
|
||||
formatThrowable(cause, sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a local committed transaction.
|
||||
*/
|
||||
public void notifyOfCommit(SpiTransaction transaction) {
|
||||
|
||||
try {
|
||||
|
||||
if (transaction.isExplicit()) {
|
||||
if (TXN_LOGGER.isInfoEnabled()) {
|
||||
TXN_LOGGER.info(transaction.getLogPrefix()+"Commit");
|
||||
}
|
||||
} else if (TXN_LOGGER.isDebugEnabled()) {
|
||||
TXN_LOGGER.debug(transaction.getLogPrefix()+"Commit");
|
||||
}
|
||||
|
||||
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction.getEvent());
|
||||
|
||||
postCommit.notifyLocalCacheIndex();
|
||||
postCommit.notifyCluster();
|
||||
|
||||
// cluster and text indexing
|
||||
backgroundExecutor.execute(postCommit.notifyPersistListeners());
|
||||
|
||||
for (TransactionEventListener listener : transactionEventListeners) {
|
||||
listener.postTransactionCommit(transaction);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Transaction that comes from another framework or local code.
|
||||
* <p>
|
||||
* For cases where raw SQL/JDBC or other frameworks are used this can
|
||||
* invalidate the appropriate parts of the cache.
|
||||
* </p>
|
||||
*/
|
||||
public void externalModification(TransactionEventTable tableEvents) {
|
||||
|
||||
TransactionEvent event = new TransactionEvent();
|
||||
event.add(tableEvents);
|
||||
|
||||
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event);
|
||||
|
||||
// invalidate parts of local cache and index
|
||||
postCommit.notifyLocalCacheIndex();
|
||||
|
||||
backgroundExecutor.execute(postCommit.notifyPersistListeners());
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify local BeanPersistListeners etc of events from another server in the cluster.
|
||||
*/
|
||||
public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cluster Received: " + remoteEvent.toString());
|
||||
}
|
||||
|
||||
List<TableIUD> tableIUDList = remoteEvent.getTableIUDList();
|
||||
if (tableIUDList != null) {
|
||||
for (int i = 0; i < tableIUDList.size(); i++) {
|
||||
TableIUD tableIUD = tableIUDList.get(i);
|
||||
beanDescriptorManager.cacheNotify(tableIUD);
|
||||
}
|
||||
}
|
||||
|
||||
List<BeanPersistIds> beanPersistList = remoteEvent.getBeanPersistList();
|
||||
if (beanPersistList != null) {
|
||||
for (int i = 0; i < beanPersistList.size(); i++) {
|
||||
BeanPersistIds beanPersist = beanPersistList.get(i);
|
||||
beanPersist.notifyCacheAndListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.PersistBatch;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
|
||||
import com.avaje.ebean.event.TransactionEventListener;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Manages transactions.
|
||||
* <p>
|
||||
* Keeps the Cache and Cluster in synch when transactions are committed.
|
||||
* </p>
|
||||
*/
|
||||
public class TransactionManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class);
|
||||
|
||||
public static final Logger SQL_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SQL");
|
||||
|
||||
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SUM");
|
||||
|
||||
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.TXN");
|
||||
|
||||
protected final BeanDescriptorManager beanDescriptorManager;
|
||||
|
||||
/**
|
||||
* Prefix for transaction id's (logging).
|
||||
*/
|
||||
protected final String prefix;
|
||||
|
||||
protected final String externalTransPrefix;
|
||||
|
||||
/**
|
||||
* The dataSource of connections.
|
||||
*/
|
||||
protected final DataSource dataSource;
|
||||
|
||||
/**
|
||||
* Flag to indicate the default Isolation is READ COMMITTED. This enables us
|
||||
* to close queryOnly transactions rather than commit or rollback them.
|
||||
*/
|
||||
protected final OnQueryOnly onQueryOnly;
|
||||
|
||||
protected final BackgroundExecutor backgroundExecutor;
|
||||
|
||||
protected final ClusterManager clusterManager;
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
protected final PersistBatch persistBatch;
|
||||
|
||||
protected final PersistBatch persistBatchOnCascade;
|
||||
|
||||
/**
|
||||
* Id's for transaction logging.
|
||||
*/
|
||||
protected final AtomicLong transactionCounter = new AtomicLong(1000);
|
||||
|
||||
protected final BulkEventListenerMap bulkEventListenerMap;
|
||||
|
||||
protected final TransactionEventListener[] transactionEventListeners;
|
||||
|
||||
/**
|
||||
* Create the TransactionManager
|
||||
*/
|
||||
public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config,
|
||||
BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
|
||||
|
||||
this.persistBatch = config.getPersistBatch();
|
||||
this.persistBatchOnCascade = config.appliedPersistBatchOnCascade();
|
||||
this.beanDescriptorManager = descMgr;
|
||||
this.clusterManager = clusterManager;
|
||||
this.serverName = config.getName();
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
this.dataSource = config.getDataSource();
|
||||
this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners());
|
||||
|
||||
List<TransactionEventListener> transactionEventListeners = bootupClasses.getTransactionEventListeners();
|
||||
this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]);
|
||||
|
||||
this.prefix = "";
|
||||
this.externalTransPrefix = "e";
|
||||
|
||||
this.onQueryOnly = initOnQueryOnly(config.getDatabasePlatform().getOnQueryOnly(), dataSource);
|
||||
|
||||
initialiseHeartbeat();
|
||||
}
|
||||
|
||||
private void initialiseHeartbeat() {
|
||||
if (dataSource instanceof DataSourcePool) {
|
||||
DataSourcePool ds = (DataSourcePool)dataSource;
|
||||
backgroundExecutor.executePeriodically(ds.getHeartbeatRunnable(), ds.getHeartbeatFreqSecs(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) {
|
||||
if (shutdownDataSource && (dataSource instanceof DataSourcePool)) {
|
||||
((DataSourcePool)dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
}
|
||||
|
||||
public BeanDescriptorManager getBeanDescriptorManager() {
|
||||
return beanDescriptorManager;
|
||||
}
|
||||
|
||||
public BulkEventListenerMap getBulkEventListenerMap() {
|
||||
return bulkEventListenerMap;
|
||||
}
|
||||
|
||||
public PersistBatch getPersistBatch() {
|
||||
return persistBatch;
|
||||
}
|
||||
|
||||
public PersistBatch getPersistBatchOnCascade() {
|
||||
return persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the behaviour to use when a query only transaction is committed.
|
||||
* <p>
|
||||
* There is a potential optimisation available when read committed is the default
|
||||
* isolation level. If it is, then Connections used only for queries do not require
|
||||
* commit or rollback but instead can just be put back into the pool via close().
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Isolation level is higher (say SERIALIZABLE) then Connections used
|
||||
* just for queries do need to be committed or rollback after the query.
|
||||
* </p>
|
||||
*/
|
||||
private OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
|
||||
// first check for a system property 'override'
|
||||
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
|
||||
if (systemPropertyValue != null) {
|
||||
return OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase());
|
||||
}
|
||||
|
||||
if (OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) {
|
||||
// check for read committed isolation level
|
||||
if (!isReadCommitedIsolation(ds)){
|
||||
logger.warn("Ignoring DatabasePlatform.OnQueryOnly.CLOSE as the transaction Isolation Level is not READ_COMMITTED");
|
||||
// we will just use ROLLBACK and ignore the desired optimisation
|
||||
return OnQueryOnly.ROLLBACK;
|
||||
} else {
|
||||
// will use the OnQueryOnly.CLOSE optimisation
|
||||
return OnQueryOnly.CLOSE;
|
||||
}
|
||||
}
|
||||
// default to rollback if not defined on the platform
|
||||
return dbPlatformOnQueryOnly == null ? OnQueryOnly.ROLLBACK : dbPlatformOnQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the isolation level is read committed.
|
||||
*/
|
||||
private boolean isReadCommitedIsolation(DataSource ds) {
|
||||
|
||||
Connection c = null;
|
||||
try {
|
||||
c = ds.getConnection();
|
||||
|
||||
int isolationLevel = c.getTransactionIsolation();
|
||||
return (isolationLevel == Connection.TRANSACTION_READ_COMMITTED);
|
||||
|
||||
} catch (SQLException ex) {
|
||||
String m = "Errored trying to determine the default Isolation Level";
|
||||
throw new PersistenceException(m, ex);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.error("closing connection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the type of behavior to use when closing a transaction that was used to query data only.
|
||||
*/
|
||||
public OnQueryOnly getOnQueryOnly() {
|
||||
return onQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the externally supplied Connection.
|
||||
*/
|
||||
public SpiTransaction wrapExternalConnection(Connection c) {
|
||||
|
||||
return wrapExternalConnection(externalTransPrefix + c.hashCode(), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an externally supplied Connection with a known transaction id.
|
||||
*/
|
||||
public SpiTransaction wrapExternalConnection(String id, Connection c) {
|
||||
|
||||
ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this);
|
||||
|
||||
// set the default batch mode
|
||||
t.setBatch(persistBatch);
|
||||
t.setBatchOnCascade(persistBatchOnCascade);
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Transaction.
|
||||
*/
|
||||
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
|
||||
Connection c = null;
|
||||
try {
|
||||
c = dataSource.getConnection();
|
||||
long id = transactionCounter.incrementAndGet();
|
||||
|
||||
SpiTransaction t = createTransaction(explicit, c, id);
|
||||
if (isolationLevel > -1) {
|
||||
c.setTransactionIsolation(isolationLevel);
|
||||
}
|
||||
|
||||
if (explicit && TXN_LOGGER.isTraceEnabled()) {
|
||||
TXN_LOGGER.trace(t.getLogPrefix()+"Begin");
|
||||
}
|
||||
|
||||
return t;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
// close connection on failed creation
|
||||
try {
|
||||
if (c != null){
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing failed connection", e);
|
||||
}
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public SpiTransaction createQueryTransaction() {
|
||||
Connection c = null;
|
||||
try {
|
||||
c = dataSource.getConnection();
|
||||
long id = transactionCounter.incrementAndGet();
|
||||
|
||||
return createTransaction(false, c, id);
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
// close the connection and re-throw the exception
|
||||
try {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing failed connection", e);
|
||||
}
|
||||
throw ex;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
// don't need to close connection in this case
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new transaction.
|
||||
*/
|
||||
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
|
||||
return new JdbcTransaction(prefix + id, explicit, c, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a local rolled back transaction.
|
||||
*/
|
||||
public void notifyOfRollback(SpiTransaction transaction, Throwable cause) {
|
||||
|
||||
try {
|
||||
if (TXN_LOGGER.isInfoEnabled()) {
|
||||
String msg = transaction.getLogPrefix()+"Rollback";
|
||||
if (cause != null){
|
||||
msg += " error: "+formatThrowable(cause);
|
||||
}
|
||||
TXN_LOGGER.info(msg);
|
||||
}
|
||||
|
||||
for (TransactionEventListener listener : transactionEventListeners) {
|
||||
listener.postTransactionRollback(transaction, cause);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error while notifying TransactionEventListener of rollback event", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query only transaction in read committed isolation.
|
||||
*/
|
||||
public void notifyOfQueryOnly(boolean onCommit, SpiTransaction transaction, Throwable cause) {
|
||||
|
||||
// Nothing that interesting here
|
||||
if (TXN_LOGGER.isTraceEnabled()) {
|
||||
TXN_LOGGER.trace(transaction.getLogPrefix()+"Commit - query only");
|
||||
}
|
||||
}
|
||||
|
||||
private String formatThrowable(Throwable e){
|
||||
if (e == null){
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
formatThrowable(e, sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void formatThrowable(Throwable e, StringBuilder sb){
|
||||
|
||||
sb.append(e.toString());
|
||||
StackTraceElement[] stackTrace = e.getStackTrace();
|
||||
if (stackTrace.length > 0){
|
||||
sb.append(" stack0: ");
|
||||
sb.append(stackTrace[0]);
|
||||
}
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null){
|
||||
sb.append(" cause: ");
|
||||
formatThrowable(cause, sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a local committed transaction.
|
||||
*/
|
||||
public void notifyOfCommit(SpiTransaction transaction) {
|
||||
|
||||
try {
|
||||
|
||||
if (transaction.isExplicit()) {
|
||||
if (TXN_LOGGER.isInfoEnabled()) {
|
||||
TXN_LOGGER.info(transaction.getLogPrefix()+"Commit");
|
||||
}
|
||||
} else if (TXN_LOGGER.isDebugEnabled()) {
|
||||
TXN_LOGGER.debug(transaction.getLogPrefix()+"Commit");
|
||||
}
|
||||
|
||||
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction.getEvent());
|
||||
|
||||
postCommit.notifyLocalCacheIndex();
|
||||
postCommit.notifyCluster();
|
||||
|
||||
// cluster and text indexing
|
||||
backgroundExecutor.execute(postCommit.notifyPersistListeners());
|
||||
|
||||
for (TransactionEventListener listener : transactionEventListeners) {
|
||||
listener.postTransactionCommit(transaction);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Transaction that comes from another framework or local code.
|
||||
* <p>
|
||||
* For cases where raw SQL/JDBC or other frameworks are used this can
|
||||
* invalidate the appropriate parts of the cache.
|
||||
* </p>
|
||||
*/
|
||||
public void externalModification(TransactionEventTable tableEvents) {
|
||||
|
||||
TransactionEvent event = new TransactionEvent();
|
||||
event.add(tableEvents);
|
||||
|
||||
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event);
|
||||
|
||||
// invalidate parts of local cache and index
|
||||
postCommit.notifyLocalCacheIndex();
|
||||
|
||||
backgroundExecutor.execute(postCommit.notifyPersistListeners());
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify local BeanPersistListeners etc of events from another server in the cluster.
|
||||
*/
|
||||
public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cluster Received: " + remoteEvent.toString());
|
||||
}
|
||||
|
||||
List<TableIUD> tableIUDList = remoteEvent.getTableIUDList();
|
||||
if (tableIUDList != null) {
|
||||
for (int i = 0; i < tableIUDList.size(); i++) {
|
||||
TableIUD tableIUD = tableIUDList.get(i);
|
||||
beanDescriptorManager.cacheNotify(tableIUD);
|
||||
}
|
||||
}
|
||||
|
||||
List<BeanPersistIds> beanPersistList = remoteEvent.getBeanPersistList();
|
||||
if (beanPersistList != null) {
|
||||
for (int i = 0; i < beanPersistList.size(); i++) {
|
||||
BeanPersistIds beanPersist = beanPersistList.get(i);
|
||||
beanPersist.notifyCacheAndListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user