Joomla 使用预编译语句
在 Joomla 4 中, Joomla 数据库查询转变为使用预编译语句,本文旨在阐明我们这样做的原因和方式。
为什么要使用预编译语句(Prepared Statements)?
简单的来说,预编译语句相比之前的字符串查询语句将会有两大优势:
- 更加的高效
- 更加的安全
预编译语句(Prepared Statements)是什么?
通常我们的一条sql在db接收到最终执行完毕返回可以分为下面三个过程:
- 词法和语义解析
- 优化sql语句,制定执行计划
- 执行并返回结果
我们把这种普通语句称作Immediate Statements。
但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化,一般称这类语句叫Prepared Statements或者Parameterized Statements 。
预编译语句的优势归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。
如何在Joomla中使用预编译语句(Prepared Statements)?
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(加引号)的方法,因为数据库驱动将自动的帮我们加上。
Joomla中使用预编译语句的一些说明
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(加引号)的方法,因为数据库驱动将自动的帮我们加上。
另外,以下函数接受数组以减少函数调用开销
- bind()
- bindArray()
- whereIn()
- whereNotIn()
另外在编程中使用预编译的一个好处是可以方便的使用循环,下面是一段实例代码:
$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 设置为相同的值和默认的参数类型。