package com.lizard.fastdb.connection.bonecp;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.enhydra.jdbc.standard.StandardXADataSource;
import com.lizard.fastdb.DBException;
import com.lizard.fastdb.connection.ConfigureReflect;
import com.lizard.fastdb.connection.ConnectionProvider;
import com.lizard.fastdb.datasource.DataSourceException;
import com.lizard.fastdb.datasource.DataSourceUtil;
import com.lizard.fastdb.dialect.Dialect;
import com.jolbox.bonecp.BoneCP;
import com.jolbox.bonecp.BoneCPConfig;
/**
* 数据库连接策略 BoneCP 实现
*
* @author SHEN.GANG
*/
public class BoneCPConnectionProvider implements ConnectionProvider
{
private static final long serialVersionUID = 8860369586785396697L;
private static final Log LOG = LogFactory.getLog(BoneCPConnectionProvider.class);
/**
* 数据源配置
*/
private Properties ds;
/**
* 连接池对象
*/
private BoneCP pool;
public void configure(Properties prop)
{
// 如果已经存在,则直接返回
if (pool != null)
{
return;
}
try
{
// 加载数据库连接驱动类
Class.forName(prop.getProperty("driver-class"));
// 初始化配置
BoneCPConfig config = initConfig(prop);
// 创建连接池
pool = new BoneCP(config);
LOG.info("BoneCP pool [" + prop.getProperty("name") + "] registered.");
}
catch (ClassNotFoundException e)
{
LOG.error("Fail to load jdbc driver: " + prop.getProperty("driver-class"), e);
}
catch (SQLException e)
{
LOG.error("Fail to register BoneCP pool [" + prop.getProperty("name") + "]", e);
}
}
/**
* 初始化配置
*
* @param prop
* @throws Exception
*/
private BoneCPConfig initConfig(Properties prop)
{
/*
* 以下 连接属性 BoneCP 暂不支持,但是不影响使用
* -- init-connection-size
* -- test-connection-checkin
* -- test-connection-checkout
*/
BoneCPConfig config = new BoneCPConfig();
ds = prop;
// 数据源名称
ds.setProperty("poolName", prop.getProperty("name"));
// 数据库连接URL
ds.setProperty("jdbcUrl", prop.getProperty("driver-url"));
// 连接用户名
ds.setProperty("username", prop.getProperty("user"));
// 连接密码(与 BoneCP 名称一致)
ds.setProperty("password", prop.getProperty("password"));
// 分区数
int _partition = config.getPartitionCount();
String prop_partition = prop.getProperty("partitionCount");
if( null != prop_partition && prop_partition.trim().matches("^\\d+$"))
{
_partition = Math.max(_partition, Integer.parseInt(prop_partition.trim()));
}
// 最大连接数
int maxConns = Integer.parseInt(prop.getProperty("max-connection-size"));
if( _partition > maxConns )
{
throw new DataSourceException("Your BoneCP[partitionCount] attribute value["+_partition+"] is more than max-connection-size["+ maxConns +"]");
}
// 每个分区的最大连接数 = 总连接数 / 分区总数
ds.setProperty("maxConnectionsPerPartition", String.valueOf( maxConns/_partition ));
// 最小连接数
int minConns = Integer.parseInt(prop.getProperty("min-connection-size"));
// 每个分区的最小连接数
ds.setProperty("minConnectionsPerPartition", String.valueOf( Math.min(minConns, maxConns/_partition)));
// 始终维持的可用连接数(BoneCP 为百分比格式)
int availableSize = Integer.parseInt(prop.getProperty("available-connection-size"));
if (availableSize > 0 && maxConns > availableSize)
{
ds.setProperty("poolAvailabilityThreshold", String.valueOf(availableSize * 100 / maxConns));
}
// 连接的最大空闲时间
ds.setProperty("idleMaxAgeInSeconds", ds.getProperty("max-connection-idletime"));
// 数据库连接的最大生命周期(包含了活动和空闲)
ds.setProperty("maxConnectionAgeInSeconds", ds.getProperty("max-connection-lifetime"));
// 当没有连接可用时,同时创建的连接数
ds.setProperty("acquireIncrement", ds.getProperty("acquire-increment-size"));
// 连接超时时间
ds.setProperty("connectionTimeoutInMs", String.valueOf(TimeUnit.SECONDS.toMillis(Long.parseLong(ds.getProperty("connection-timeout")))));
// 重试次数
ds.setProperty("acquireRetryAttempts", ds.getProperty("acquire-retry-attempts"));
// 每次重试间隔时间
ds.setProperty("acquireRetryDelayInMs", ds.getProperty("acquire-retry-delay"));
// 测试连接是否有效的sql语句(该语句应该被很快的执行,否则会造成性能损失)
// 如果不定义, BoneCP默认采用 connection.getMetaData().getTables() 进行测试
// 如果定义为空(不是null),BoneCP会检测出错(因为 stmt.execute("") 报异常 ),而关闭这个连接,造成误关闭;
// 因为BoneCP使用 if(connectionTestStatement == null){} 来判断是否执行用户定义的test sql,代码片段是:
// String testStatement = this.config.getConnectionTestStatement();
// ResultSet rs = null;
// if (testStatement == null){
// rs = connection.getMetaData().getTables(null, null, "BONECPKEEPALIVE", METADATATABLE);
// } else {
// stmt = connection.createStatement();
// stmt.execute(testStatement);
// }
// 而 datasource-default.properties 中 test-sql 默认值是空(非null),则就会造成上面的检查异常失败
// 为了避免这样的错误,下面将针对BoneCP使用 MySQL: SELECT 1, Oracle: SELECT 1 FROM dual 作为默认值
// 注:只有当配置了 max-connection-idletime 或 test-connection-test-period 时(值>0),下面的connectionTestStatement才会被执行
String testSql = ds.getProperty("test-sql");
ds.setProperty("connectionTestStatement", (null == testSql || testSql.trim().length() == 0 ) ? Dialect.getDialect(prop.getProperty("driver-class")).getTestSQL() : testSql);
// 空闲连接的检查周期(包括 检查空闲连接是否超期 和 连接是否可用-使用connectionTestStatement测试Connection的有效性 )
ds.setProperty("idleConnectionTestPeriodInSeconds", ds.getProperty("idle-connection-test-period"));
// 初始化BoneCP属性
try
{
ConfigureReflect.setProperties(config, ds);
}
catch (Exception e)
{
LOG.error("Fail to initialize BoneCPConfig", e);
throw new DBException(e);
}
return config;
}
public Connection getConnection() throws SQLException
{
return pool.getConnection();
}
public Connection getXAConnection(TransactionManager tm) throws SQLException
{
StandardXADataSource sxd = DataSourceUtil.convertPropertiesToXADataSource(ds);
sxd.setTransactionManager(tm);
Connection conn = sxd.getXAConnection().getConnection();
conn.setAutoCommit(false);
return conn;
}
public void closeConnection(Connection conn) throws SQLException
{
if (conn != null && !conn.isClosed())
{
conn.close();
}
}
public void shutdown()
{
if (pool != null)
{
pool.shutdown();
}
}
// public synchronized void destory()
// {
// if (pool != null)
// {
// pool.shutdown();
// pool = null;
// }
// }
// public boolean isDataSourceRegistered(String name)
// {
// // (BoneCP 暂不支持)
// return false;
// }
}