在 Joomla 4 中, Joomla 数据库查询转变为使用预编译语句,本文旨在阐明我们这样做的原因和方式。
简单的来说,预编译语句相比之前的字符串查询语句将会有两大优势:
通常我们的一条sql在db接收到最终执行完毕返回可以分为下面三个过程:
我们把这种普通语句称作Immediate Statements。
但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化,一般称这类语句叫Prepared Statements或者Parameterized Statements 。
预编译语句的优势归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。
joomla在底层的JDatabaseDriver类中已经帮我们实现好了预编译语句这项功能,开发者只需要在写查询语句的时候调用即可。以下面的查询语句为例:
正常的SQL语句写法:
$query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id', 'password'))) ->from($this->db->quoteName('#__users')) ->where($this->db->quoteName('username') . '=' . $this->db->quote($credentials['username']));
转换为预编译语句的SQL语句写法:
$query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id', 'password'))) ->from($this->db->quoteName('#__users')) ->where($this->db->quoteName('username') . ' = :username') ->bind(':username', $credentials['username']);
通过在where中使用占位符,在bind中进行参数绑定就实现了预编译的功能。另外,请注意最后的bind方法,在这里我们不再使用$db的quote(加引号)的方法,因为数据库驱动将自动的帮我们加上。
JDatabaseDriver 会对一些函数自动使用预编译语句,如 whereIn()和whereNotIn()方法将自动的使用。
典型的代码如下:
$query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id, password'))) ->from($this->db->quoteName('#__users')) ->whereIn($this->db->quoteName('id'), [ 1, 2, 3 ]);
这个查询将会转换为预编译语句的SQL如下:
SELECT `id`, `password` FROM `#__users` WHERE `id` IN ( :preparedArray1, :preparedArray2, :preparedArray3 );
其中的占位符 :preparedArray1-3 将在执行时填充 1,2,3。
通过在where中使用占位符,在bind中进行参数绑定就实现了预编译的功能。另外,请注意最后的bind方法,在这里我们不再使用$db的quote(加引号)的方法,因为数据库驱动将自动的帮我们加上。
另外,以下函数接受数组以减少函数调用开销
另外在编程中使用预编译的一个好处是可以方便的使用循环,下面是一段实例代码:
$listOfUsernames = [ 'admin', 'user1' ]; $query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id', 'password'))) ->from($this->db->quoteName('#__users')) ->where($this->db->quoteName('username') . ' = :username') ->bind(':username', $username); foreach($listOfUsernames as $name) { $username = $name; $this->db->setQuery($query); $user = $this->db->loadObject(); print_r($user); }
在上面的循环中,给$username赋值,然后重新调用setQuery方法来获得数据,非常的方便。
同样,也可以使用数据组来完成参数的绑定,代码如下:
$query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id', 'password'))) ->from($this->db->quoteName('#__users')) ->where($this->db->quoteName('username') . ' = :username') ->where($this->db->quoteName('id') . ' = :id') ->bind([':username', ':id'], [$credentials['username'], 42], [Joomla\Database\ParameterType::STRING, Joomla\Database\ParameterType::INTEGER]);
在上面的代码中添加 username 和 id 作为绑定参数,并为每个变量设置正确的 ParameterType。也可以对所有绑定值和 ParameterType 使用一个变量。 代码如下:
$query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id', 'password'))) ->from($this->db->quoteName('#__users')) ->where($this->db->quoteName('username') . ' = :username') ->where($this->db->quoteName('password') . ' = :password') ->bind([':username', ':password], $credentials['username']);
在上面参数 :username 和 :password 设置为相同的值和默认的参数类型。