当前位置: 首页 > 后端技术 > PHP

编写一个“特殊”查询构建器——(7.DML语句、事务)

时间:2023-03-29 22:32:56 PHP

查询语句(DQL)构造函数开发完成,我们会在查询构建器中加入一些对DML(DataManipulationLanguage)语句的支持,比如简单的插入、更新、删除操作。insert让我们先回顾一下PDO的原生插入操作是如何工作的://Precompile$pdoSt=$pdo->prepare("INSERTINTOtest_table('username','age')VALUES(:username,:age);");//绑定参数$pdoSt->bindValue(':username','Jack',PDO::PARAM_STR)$pdoSt->bindValue(':age',18,PDO::PARAM_INT)//执行$pdoSt->execute();//获取执行数据$count=$pdoSt->rowCount();//返回受影响的行数$lastId=$pdo->lastInsertId();//获取最后插入行的id数据insert和查询语句的执行过程没有太大区别,只是语法不同而已。回顾第二篇文章,我们创建了一个新的_buildQuery()方法来构建最终的SQL。对于insert,我们在基类中新建了一个_buildInsert()方法:protectedfunction_buildInsert(){$this->_prepare_sql='INSERTINTO'。$this->_table.$this->_insert_str;}为基类添加_insert_str属性:protected$_insert_str='';修改_reset()函数,增加_insert_str属性的初始化过程:protectedfunction_reset(){$this->_table='';$this->_prepare_sql='';$this->_cols_str='*';$this->_where_str='';$this->_orderby_str='';$this->_groupby_str='';$this->_having_str='';$this->_join_str='';$this->_limit_str='';$this->_insert_str='';//重置插入语句$this->_bind_params=[];}在基类中添加insert()方法:publicfunctioninsert(array$data){//构建字符串$field_str='';$value_str='';foreach($dataas$key=>$value){$field_str.=''.self::_wrapRow($key).',';$plh=self::_getPlh();//产生占位符$this->_bind_params[$plh]=$value;//保存绑定数据$value_str.=''.$plh.',';}//清除右边多余的逗号$field_str=rtrim($field_str,',');$value_str=rtrim($value_str,',');$this->_insert_str='('.$field_str.')VALUES('.$value_str.')';//构造插入语句$this->_buildInsert();//执行$this->_execute();//获取受影响的行数return$this->_pdoSt->rowCount();}对于上面的代码,我们声明insert()方法的参数是一个key-value数组,用来传入要插入的字段,默认值映射返回受影响的行数(更一般)。测试并尝试插入一条数据:$insert_data=['username'=>'jack','age'=>18,];$results=$driver->table('test_table')->insert($insert_data);获取最后插入行的ID当一张表有自增id并且是主键时,这个id可以看成是区分数据的唯一标识。插入一条数据后获取这条新增数据的id也是一个常见的业务需求。PDO提供了一种简单的方法来获取最后插入的行的IDPDO::lastInsertId()供我们使用。将insertGetLastId()方法添加到基类:publicfunctioninsertGetLastId(array$data){$this->insert($data);返回$this->_pdo->lastInsertId();}测试:$insert_data=['username'=>'jack','age'=>18,];$lastId=$driver->table('test_table')->insertGetLastId($insert_data);个体差异但是,上面的insertGetLastId()方法在PostgreSql中不起作用。在PostgreSql中,使用PDO::lastInsertId()获取结果需要传入正确的自增序列名(PostgreSQL建表时,如果使用序列类型,默认的自增序列名是:table名称++字段名++序列)。[1]但是这个方法不好用,因为在访问insertGetLastId()方法的时候需要手动传入序列名,所以insertGetLastId()方法对底层的依赖性很强,比如当底层驱动从postgresql改成mysql,需要上层应用改。而我们希望无论是mysql还是postgresql,在上层应用调用insertGetLastId()方法时没有区别,即底层对上层是透明的。为了解决这个问题,需要使用postgresql的返回语法。postgresql中的insert、update、delete操作都有一个可选的return子句,可以指定最后执行的字段返回,返回的数据可以和select一样。[2]对于我们需要返回最后插入的行的id,我们只需要返回id即可。当然,基类的insertGetLastId()方法对于postgresql是无效的。我们重写Pgsql驱动类中的insertGetLastId()方法:publicfunctioninsertGetLastId(array$data){//构建语句字符串并绑定数据$field_str='';$value_str='';foreach($dataas$key=>$value){$field_str.=''.self::_wrapRow($key).',';$plh=self::_getPlh();$this->_bind_params[$plh]=$value;$value_str.=''.$plh.',';}$field_str=rtrim($field_str,',');$value_str=rtrim($value_str,',');//使用返回子句返回id$this->_insert_str='('.$field_str.')VALUES('.$value_str.')RETURNINGid';//执行$this->_buildInsert();$this->_execute();//使用returning子句后,可以像使用SELECT一样得到一个返回指定字段的结果集。$result=$this->_pdoSt->fetch(PDO::FETCH_ASSOC);//returnidreturn$result['id'];}OK,我们再测试一下,好用吗?insert完成后update就很简单了。不同的是为了防止全局更新的错误,在构造更新的时候强制使用了where子句。同样,添加_update_str属性,修改_reset()函数:protected$_update_str='';...protectedfunction_reset(){$this->_table='';$this->_prepare_sql='';$this->_cols_str='*';$this->_where_str='';$this->_orderby_str='';$this->_groupby_str='';$this->_having_str='';$this->_join_str='';$this->_limit_str='';$this->_insert_str='';$this->_update_str='';$this->_bind_params=[];}构造update语句的方法:protectedfunction_buildUpdate(){$this->_prepare_sql='UPDATE'.$this->_table.$this->_update_str.$this->_where_str;}在基类中添加update()方法:publicfunctionupdate(array$data){//检测是否设置了where子句if(empty($this->_where_str)){thrownew\InvalidArgumentException("需要where条件");}//构建语句,参数绑定$this->_update_str='SET';foreach($dataas$key=>$value){$plh=self::_getPlh();$this->_bind_params[$plh]=$值;$this->_update_str.=''.self::_wrapRow($key).'='.$plh.',';}$this->_update_str=rtrim($this->_update_str,',');$this->_buildUpdate();$this->_execute();//返回受影响的行数return$this->_pdoSt->rowCount();}更新数据示例:$update_data=['username'=>'lucy','age'=>22,];$results=$driver->table('test_table')->where('username','jack')->update($update_data);删除比插入和更新简单。只需要where子句和update一样,并且要防止误操作删除所有数据。构造delete语句的方法:protectedfunction_buildDelete(){$this->_prepare_sql='DELETEFROM'.$this->_table.$this->_where_str;}在基类中添加delete()方法:publicfunctiondelete(){//检查where子句是否设置if(empty($this->_where_str)){thrownew\InvalidArgumentException("Needwherecondition");}$this->_buildDelete();$this->_execute();return$this->_pdoSt->rowCount();}删除数据示例:$results=$driver->table('test_table')->where('age',18)->delete();如果DML操作完成,那么事务是必不可少的。对于事务,我们可以直接使用PDO提供的PDO::beginTransaction()、PDO::commit()、PDO::rollBack()、PDO::inTransaction()方法来实现。将beginTrans()方法添加到基类://开始事务publicfunctionbeginTrans(){try{return$this->_pdo->beginTransaction();}catch(PDOException$e){//重新连接if($this->_isTimeout($e)){$this->_closeConnection();$this->_connect();尝试{return$this->_pdo->beginTransaction();}catch(PDOException$e){抛出$e;}}else{抛出$e;注意:因为PDO::beginTransaction()也是和PDO::prepare()一样连接数据库的方法,所以需要断线重连。commitTrans()方法://提交事务publicfunctioncommitTrans(){return$this->_pdo->commit();}rollBackTrans()方法://回滚事务publicfunctionrollBackTrans(){if($this->_pdo->inTransaction()){//如果事务已经开始,运行回滚操作return$this->_pdo->rollBack();}}事务使用示例://注册事务$driver->beginTrans();$results=$driver->table('test_table')->where('age',18)->delete();$driver->提交交易();//确认删除//回滚事务$driver->beginTrans();$results=$driver->table('test_table')->where('age',18)->delete();$driver->rollBackTrans();//undodelete参考【1】PHP手册-PDO::lastInsertId【2】PostgreSQL文档-从修改行返回数据