有些需求必须把表垂直拆开,一个表变成几个表;在做 model[AR] 的时候,又想做成像一个表一样的接口的 model,这样底层接口看起来就比较简单;最关键要解决的难题是多表操作的事务的实现,现在的解决方案是利用 beforeSave() 和 afterSave() ,在前面开始事务,后面提交事务,如果中间出现异常,则再自定义一个 ErrorHandler,在出错的情况下,对所有事务进行回滚(我还不同模块不同数据库的拆分的,所以有多个dbconnection)。
垂直拆表(或者多表操作)是比较常见的, qiang是否官方提供一些标准的方案呢?
以下是我的一些代码
<?php
/**
* 数据库连接对象
*
* 支持多层事务
*
* @author terry39
*/
class DbConnection extends CDbConnection
{
protected $_attributes = array();
protected $_pdo;
protected function createPdoInstance()
{
$pdoClass='PDOExt';
if(($pos=strpos($this->connectionString,':'))!==false)
{
$driver=strtolower(substr($this->connectionString,0,$pos));
if($driver==='mssql' || $driver==='dblib')
$pdoClass='CMssqlPdoAdapter';
}
return $this->_pdo = new $pdoClass($this->connectionString,$this->username,
$this->password,$this->_attributes);
}
public function setAttribute($name,$value)
{
if($this->_pdo instanceof PDO)
$this->_pdo->setAttribute($name,$value);
else
$this->_attributes[$name]=$value;
}
/**
* 回滚所有未提交的事务
*/
public function rollBackAll()
{
if($this->_pdo instanceof PDOExt){
$this->_pdo->rollBackAll();
}
}
}
/**
* 扩展的PDO
*
* 支持多层事务
*
* @author terry39
*/
class PDOExt extends PDO
{
// Database drivers that support SAVEPOINTs.
protected static $savepointTransactions = array("pgsql", "mysql");
// The current transaction level.
protected $transLevel = 0;
protected function nestable() {
return in_array($this->getAttribute(PDO::ATTR_DRIVER_NAME),
self::$savepointTransactions);
}
public function beginTransaction() {
if(!$this->nestable() || $this->transLevel == 0) {
parent::beginTransaction();
} else {
$this->exec("SAVEPOINT LEVEL{$this->transLevel}");
}
$this->transLevel++;
}
public function commit() {
$this->transLevel--;
if(!$this->nestable() || $this->transLevel == 0) {
parent::commit();
} else {
$this->exec("RELEASE SAVEPOINT LEVEL{$this->transLevel}");
}
}
public function rollBack() {
$this->transLevel--;
if(!$this->nestable() || $this->transLevel == 0) {
parent::rollBack();
} else {
$this->exec("ROLLBACK TO SAVEPOINT LEVEL{$this->transLevel}");
}
}
/**
* 回滚所有未提交的事务
*/
public function rollBackAll()
{
while($this->transLevel >= 0){
$this->rollBack();
}
}
}
?>
<?php
/**
* 数据库连接管理器
*
* 可以从这里获得指定 connId 的 CDbConnection 对象
*
* 在 main.php 中 配置
*
* 'components' => array(
* 'dbConnectionsManager' => array(
* 'class' => 'DbConnectionsManager',
* 'connections' => array(
* 'connId' => array(
* 'connectionString' => 'mysql:host=localhost;dbname=db_name',
* 'username' => 'username',
* 'password' => 'password',
* 'charset' => 'utf8',
* ),
* 'connId2' => array( ... ),
* '...',
* ),
* ),
* ),
*
* @FIXME 如 authManager 等直接通过 Yii::app()->getComponent(component id) 获得连接对象的情况,会因为对象未实例化而导致异常。
*
*
* @author terry39 <gamezoom@gmail.com>
*/
class DbConnectionsManager extends CApplicationComponent
{
private $_connectionConfigs = array();
private $_dbConnections = array();
private $_connectionClass = 'CDbConnection';
// 是否允许 Application 默认的 CDBConnection 对象
private $_useAppDb = false;
/**
* 只写属性 useAppDb
*/
public function setUseAppDb($value)
{
$this->_useAppDb = $value;
}
/**
* 只读属性 connectionClass
* 默认的连接对象类
*/
public function setConnectionClass($cc)
{
$this->_connectionClass = $cc;
}
/**
* set property of connections
*/
public function setConnections($connectionConfigs)
{
$this->_connectionConfigs = $connectionConfigs;
}
/**
* is a connection id
*/
public function isConnectionId($connId)
{
return isset($this->_connectionConfigs[$connId]);
}
/**
* 回滚所有未提交的事务
*/
public function rollBackAll()
{
foreach($this->_dbConnections as $conn)
{
if($conn instanceof DbConnection){
$conn->rollBackAll();
}
}
}
/**
* 根据 connId 获得 CDBConnection 对象
*/
public function getConnection($connId)
{
if(!isset($this->_dbConnections[$connId])){
if(!isset($this->_connectionConfigs[$connId])){
return $this->_useAppDb ? Yii::app()->db : null;
}
$config = $this->_connectionConfigs[$connId];
$config['class'] = isset($config['class']) ? $config['class'] : $this->_connectionClass;
$this->_dbConnections[$connId] = $db = Yii::createComponent($config);
Yii::app()->setComponent($connId, $db);
}
return $this->_dbConnections[$connId];
}
}
<?php
class ErrorHandler extends CErrorHandler
{
/**
* 响应错误事件
*
* 回滚所有未数据库连接对象的 所有未提交的事务
* 然后再由父分类的方法来响应事件
*/
public function handle($event)
{
try{
Yii::app()->dbConnectionsManager->rollBackAll();
$db = Yii::app()->getComponent('db', false);
if($db && $db instanceof PDOExt){
$db->rollBackAll();
}
}catch(Exception $e){}
parent::handle($event);
}
}
?>