/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jef.database;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.Collection;
import javax.persistence.PersistenceException;
import jef.database.cache.Cache;
import jef.database.dialect.DatabaseDialect;
import jef.database.innerpool.IConnection;
import jef.database.innerpool.IUserManagedPool;
import jef.database.innerpool.PartitionSupport;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.support.DbOperatorListener;
import jef.database.support.SavepointNotSupportedException;
import jef.tools.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 事务状态下的数据库连接封装。
*
* @author jiyi
*
*/
public abstract class Transaction extends Session implements TransactionalSession {
protected static Logger log = LoggerFactory.getLogger(Transaction.class);
/**
* 内部事务标记
* <p>
* ORM中部分操作会在非事务场景下进行,而JEF本身的一些内部操作则必须保证在事务环境下执行。(如级联操作、批操作)
* 为此JEF会在内部创建一个事务对象。这类内部的食物对象当操作完成后即提交并关闭。对用户透明。
*
* @author jiyi
*
*/
enum TransactionFlag {
/**
* 因为操作悲观锁模式,为了阻止连接被释放(例如线程重入),因此使用一个事务来独占连接。
*/
ResultHolder,
/**
* 级联写操作,为了保证一致性的隐含事务
*/
Cascade,
/**
* 被外部管理的事务.比如共享hibernate事务或者iBatis事务的场合。
*/
Managed
}
/**
* Use the default timeout of the underlying transaction system, or none if
* timeouts are not supported.
*/
final static int TIMEOUT_DEFAULT = -1;
/**
* 使用数据库的默认事务隔离级别。
*/
final static int ISOLATION_DEFAULT = -1;
protected String parentName;// 当关闭后parent即为null,此时需要使用该变量显示日志
protected DbClient parent;
protected volatile IConnection conn;
protected Cache cache;
@Override
public String toString() {
return getTransactionId(null);
}
void releaseConnection(IConnection conn) {
}
@Override
public <T> NativeQuery<T> createNamedQuery(String name, Class<T> resultWrapper) {
if (parent.namedQueries == null)
parent.initNQ();
NQEntry nc = parent.namedQueries.get(name);
if (nc == null)
return null;
return selectTarget(MetaHolder.getMappingSite(nc.getTag())).createNativeQuery(nc, resultWrapper);
}
@Override
public <T> NativeQuery<T> createNamedQuery(String name, ITableMetadata resultMeta) {
if (parent.namedQueries == null)
parent.initNQ();
NQEntry nc = parent.namedQueries.get(name);
if (nc == null)
return null;
return selectTarget(MetaHolder.getMappingSite(nc.getTag())).createNativeQuery(nc, resultMeta);
}
@Override
protected Cache getCache() {
return cache;
}
@Override
protected DbOperatorListener getListener() {
return parent.getListener();
}
/**
* 得到当前事务所关联的DbClient
*
* @return
*/
public DbClient getParent() {
return parent;
}
/**
* 设置恢复点
*
* @param savepointName
* @throws SQLException
* @throws SavepointNotSupportedException
*/
public Savepoint setSavepoint(String savepointName) throws SQLException, SavepointNotSupportedException {
// Oracle SavePoint必须用字母开头,不能用数字开头
// if (!parent.asOperateTarget(null).getMetaData().supportsSavepoints())
// throw new SavepointNotSupportedException("Savepoints are not supported by your JDBC driver.");
// XA模式下不支持savepoint;
Savepoint sp = getConnection().setSavepoint(savepointName);// 如果不支持SP,返回null
if (sp == null) {
throw new SavepointNotSupportedException("Savepoints are not supported.");
}
return sp;
}
public Savepoint setSavepoint() throws SQLException, SavepointNotSupportedException {
if (!parent.selectTarget(null).getMetaData().supportsSavepoints())
throw new SavepointNotSupportedException("Savepoints are not supported by your JDBC driver.");
return getConnection().setSavepoint();// 如果不支持SP,返回null
}
public void rollbackToSavepoint(Savepoint savepoint) throws SQLException, SavepointNotSupportedException {
getConnection().releaseSavepoint(savepoint);
}
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
getConnection().releaseSavepoint(savepoint);
}
@Override
public OperateTarget selectTarget(String dbKey) {
if (StringUtils.isEmpty(dbKey))
return new OperateTarget(this, null);
return new OperateTarget(this, dbKey);
}
@Override
protected Collection<String> getAllDatasourceNames() {
ensureOpen();
return parent.getAllDatasourceNames();
}
@Override
protected String getDbName(String dbKey) {
return parent == null ? this.parentName : parent.getDbName(dbKey);
}
@Override
IUserManagedPool getPool() {
ensureOpen();
return parent.getPool();
}
@Override
public DbClient getNoTransactionSession() {
ensureOpen();
return parent;
}
@Override
public DatabaseDialect getProfile(String key) {
ensureOpen();
return parent.getProfile(key);
}
private void ensureOpen() {
if (parent == null) {
throw new IllegalStateException("Current transaction is closed!|" + getTransactionId(null));
}
}
@Override
public PartitionSupport getPartitionSupport() {
return parent.getPartitionSupport();
}
@Override
protected String getTransactionId(String dbkey) {
StringBuilder sb = new StringBuilder();
TransactionFlag innerFlag = getTransactionFlag();
if (innerFlag == null) {
sb.append("[Tx");
} else {
sb.append('[').append(innerFlag.name());
}
if(isReadonly()){
sb.append("(R)");
}
sb.append(StringUtils.toFixLengthString(this.hashCode(), 8)).append('@')
.append(parent != null ? parent.getDbName(dbkey) : parentName)
.append('@').append(Thread.currentThread().getId()).append(']');
return sb.toString();
}
/**
* 提交当前事务
* <p>
* 仅提交事务,不会关闭事务和释放连接。您需要调用{@link #close()}方法才能真正释放事务所用的连接。<br>
* 你也可以使用{@code commit(true) }方法提交事务并释放连接。
* @throws PersistenceException 如果回滚中出现数据库错误抛出。
*/
public void commit() {
commit(false);
}
/**
* 回滚当前事务
* <p>
* 仅回滚事务,不会关闭事务和释放连接。您需要调用{@link #close()}方法才能真正释放事务所用的连接。<br>
* 你也可以使用{@code rollback(true) }方法提交事务并释放连接。
* @throws PersistenceException 如果回滚中出现数据库错误抛出。
*/
public void rollback() {
rollback(false);
}
@Override
boolean isRoutingDataSource() {
return this.parent.isRoutingDataSource();
}
}