Thinkphp对Oracle的支持简直弱爆,只做到了基本的操作,就连事务都不支持。今天来手动改一改DbOracle.class.php,让它稍微好用一些吧。
首先是insert。原来的insert应该没有什么问题,但实际项目中更多的是需要在插入的时候遇到已存在的记录则进行更新。于是,利用Oracle中的MERGE INTO来实现这一点。
- public function insert($data, $options = array(), $replace = false)
- {
- if (!$replace) {
- return parent::insert($data, $options, $replace);
- }
- $values = $fields = array();
- $this->model = $options['model'];
- $sql_merge = 'MERGE INTO ' . $this->parseTable($options['table']) .
- ' using (select 1 from dual) ' .
- ' ON (' . $this->parseValue($data[$options['marge_key']]) . ' is not null and ' . $this->parseValue($data[$options['marge_key']]) . ' = ' . $options['marge_key'] . ')';
- //insert
- foreach ($data as $key => $val) {
- //主键值为空时,不插入主键
- if ($this->parseKey($key) == $this->parseKey($options['marge_key'])
- && $val == null
- ) {
- } elseif (is_array($val) && 'exp' == $val[0]) {
- $fields[] = $this->parseKey($key);
- $values[] = $val[1];
- } elseif (is_scalar($val) || is_null(($val))) { // 过滤非标量数据
- $fields[] = $this->parseKey($key);
- if (C('DB_BIND_PARAM') && 0 !== strpos($val, ':')) {
- $name = md5($key);
- $values[] = ':' . $name;
- $this->bindParam($name, $val);
- } else {
- $values[] = $this->parseValue($val);
- }
- }
- }
- $sql_insert = 'INSERT (' . implode(',', $fields) . ') VALUES (' . implode(',', $values) . ')';
- //update
- if (isset($data[$this->parseKey($options['marge_key'])])
- || $data[$this->parseKey($options['marge_key'])] == null
- ) {
- unset($data[$this->parseKey($options['marge_key'])]);
- }
- $sql_update = 'UPDATE '
- . $this->parseSet($data)
- . $this->parseWhere(!empty($options['where']) ? $options['where'] : '')
- . $this->parseOrder(!empty($options['order']) ? $options['order'] : '')
- . $this->parseLimit(!empty($options['limit']) ? $options['limit'] : '');
- $sql = $sql_merge . ' WHEN MATCHED THEN ' . $sql_update . ' WHEN NOT MATCHED THEN ' . $sql_insert;
- return $this->execute($sql, $this->parseBind(!empty($options['bind']) ? $options['bind'] : array()));
- }
不支持事务是Thinkphp连接Oracle时的另一个问题,框架作者似乎已经做过适配,但是应该是没有测试,留下一堆bug。DbOracle.class.php中已经有了startTrans,commit,rollback等,稍作修改即可。
Thinkphp对数据库的所有操作最终都是汇集到query或execute上来执行,但这两个函数里放了一句$this->mode = OCI_COMMIT_ON_SUCCESS;活生生的把事务扼杀了,所以,这里先把两个函数里的这句注释掉。
然后,惊人得发现execute()中调用oci_execute时根本没有传入mode!前面辛辛苦苦改mode又是何苦,果断加上oci_execute($stmt, $this->mode)。
接下来才是让事务生效的重头戏。在事务的几个开关函数中加入对mode的修改,在startTrans()中,将mode设为OCI_DEFAULT,commit和rollback中将将mode设为改回OCI_COMMIT_ON_SUCCESS。这三个函数大概就是这样子的:
- /**
- * 启动事务
- * @access public
- * @return void
- */
- public function startTrans() {
- $this->initConnect(true);
- if ( !$this->_linkID ) return false;
- //数据rollback 支持
- if ($this->transTimes == 0) {
- $this->mode = OCI_DEFAULT;
- }
- $this->transTimes++;
- return ;
- }
- /**
- * 用于非自动提交状态下面的查询提交
- * @access public
- * @return boolen
- */
- public function commit(){
- if ($this->transTimes > 0) {
- $result = oci_commit($this->_linkID);
- if(!$result){
- $this->error();
- return false;
- }
- $this->mode = OCI_COMMIT_ON_SUCCESS;//陈宣亦 2014.11.09 14:07
- $this->transTimes = 0;
- }
- return true;
- }
- /**
- * 事务回滚
- * @access public
- * @return boolen
- */
- public function rollback(){
- if ($this->transTimes > 0) {
- $result = oci_rollback($this->_linkID);
- if(!$result){
- $this->error();
- return false;
- }
- $this->mode = OCI_COMMIT_ON_SUCCESS;//陈宣亦 2014.11.09 14:07
- $this->transTimes = 0;
- }
- return true;
- }