数据库手册
  1. Soter数据库功能说明

    Soter数据库驱动主要特点:
    1.支持多主多从,可以设置多个主数据库和多个从数据库,主数据库之间看用户自己情况保持数据一致即可。主之间数据保持一致可以配置主主之间相互主从,或者使用MariaDB Galera Cluster主集群。(原理:所有的读都在随机选取的一个从上面进行,所有的写都在随机选取的一个主上进行)
    2.多主多从模式下也支持事务(原理:事务模式下,所有的读写操作都在一个固定的随机的主上进行)
    3.非事务模式下,支持用户锁定数据库连接,锁定之后所有的读写都在一个固定的随机的主数据库连接上进行,直到用户释放锁定,这解决了有些业务逻辑是插入数据之后立即查询,避免了主从复制延迟的带来的问题。
    4.支持慢查询记录,支持用户自定义处理类记录慢查询,是把查询语句写到数据库还是发到远程用户完全可以自定义。
    5.支持查询索引分析,自动记录不满足设置的索引的查询。此功能只对MySQL有效。
    原理是通过explain查询语句,检查结果中的type值,值从好到坏依次是:
    system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL, ALL意味着全表扫描了,Soter默认级别是index,意思就是如果explain查询type是index之后的时候会记录这个查询。
    支持用户自定义处理类处理不满足索引条件的查询。
    6.支持批量更新,批量插入,批量replace。大幅度提升程序性能。
    7.支持便捷的ActiveRecord链式数据库查询。
    8.支持数据库查询缓存,大幅度减少缓存数据库查询结果数据的编码,Soter里面只需要查询的时候加上一个->cache($cacheTime,$cacheKey=''),
    那么这个查询就具有了缓存的功能,用户无需关心缓存的获取和存储。方法中用户可以省略缓存kye参数,Soter会自动使用md5(sql语句)作为缓存key。
    另外支持自定义缓存处理类用来处理数据库缓存数据。
    9.所有查询都是通过PDO预处理语句实现,完全杜绝SQL注入。
  2. 数据库配置信息说明

    连接数据库的配置信息文件默认是在:application/config/default/database.php 打开这个文件我们可以看到一个数据库信息配置的数组,每部分的说明,会在下面继续详细说明。
    完整配置如下:
    						<?php
    						return array(
    						    //默认组
    						    'default_group' => 'mysql',
    						    //组名=>配置
    						    //mysql配置示例
    						    'mysql' => array(
    								'driverType' => 'mysql',
    								'debug' => true,
    								'pconnect' => false,
    								'charset' => 'utf8',
    								'collate' => 'utf8_general_ci',
    								'database' => '',
    								'tablePrefix' => '',
    								'tablePrefixSqlIdentifier' => '_tablePrefix_',
    								//是否开启慢查询记录
    								'slowQueryDebug' => false,
    								'slowQueryTime' => 3000, //慢查询最小时间,单位毫秒,1秒=1000毫秒
    								'slowQueryHandle' => new Soter_Database_SlowQuery_Handle_Default(),
    								/**
    								 * 是否开启没有满足设置的索引类型的查询记录
    								 */
    								'indexDebug' => false,
    								/**
    								 * 索引使用的最小情况,只有小于最小情况的时候才会记录sql到日志
    								 * minIndexType值从好到坏依次是:
    								 * system > const > eq_ref > ref > fulltext > ref_or_null 
    								 * > index_merge > unique_subquery > index_subquery > range 
    								 * > index > ALL 一般来说,得保证查询至少达到range级别,最好能达到ref
    								 * 避免ALL即全表扫描
    								 */
    								'minIndexType' => 'index',
    								'indexHandle' => new Soter_Database_Index_Handle_Default(),
    								'masters' => array(
    									'master01' => array(
    									    'hostname' => '127.0.0.1',
    									    'port' => 3306,
    									    'username' => 'root',
    									    'password' => '',
    									)
    								),
    								'slaves' => array(
    								//		    'slave01' => array(
    								//			'hostname' => '127.0.0.1',
    								//			'port' => 3306,
    								//			'username' => 'root',
    								//			'password' => '',
    								//		    )
    								)
    						    ),
    						    //sqlite3配置示例
    						    'sqlite3' => array(
    									'driverType' => 'sqlite',
    									'debug' => true,
    									'pconnect' => false,
    									'tablePrefix' => '',
    									'tablePrefixSqlIdentifier' => '_tablePrefix_',
    									'database'=>'test.sqlite3',
    									//是否开启慢查询记录
    									'slowQueryDebug' => true,
    									'slowQueryTime' => 3000, //单位毫秒,1秒=1000毫秒
    									'slowQueryHandle' => new Soter_Database_SlowQuery_Handle_Default()
    								    )
    							)
    						;
    		    
  3. 数据库配置数组结构

    在database.php配置文件中我们看到的一个大数组,这个数组就是数据库信息,由几大模块组成。
    下面进行详细说明:

    1.默认数据库组说明

    首先是最重要的一个,默认使用的数据库组,对应的配置如下,已经省略无关的配置。
    						 //默认组
    						'default_group' => 'mysql',
    						//组名=>配置
    						//mysql配置示例
    						'mysql' => array(
    							...
    						),
    						//sqlite3配置示例
    						'sqlite' => array(
    							...
    						),
    		    
    可以看到有两个数据库组配置,一个是mysql,一个是sqlite,它们都作为配置数组的一个键名称。
    default_group就是告诉Stoer在我们使用Sr::db()不传递参数的时候,默认使用哪一组配置。

    2.Mysql配置组说明

    mysql配置组里面我们可以看到下面的一些公用配置。
    						'driverType' => 'mysql',
    						'debug' => true,
    						'pconnect' => false,
    						'charset' => 'utf8',
    						'collate' => 'utf8_general_ci',
    						'database' => '',
    						'tablePrefix' => '',
    						'tablePrefixSqlIdentifier' => '_tablePrefix_',
    						'masters' => array(
    							'master01' => array(
    							'hostname' => '127.0.0.1',
    							'port' => 3306,
    							'username' => 'root',
    							'password' => '',
    							)
    						),
    						'slaves' => array(
    						//          'slave01' => array(
    						//          'hostname' => '127.0.0.1',
    						//          'port' => 3306,
    						//          'username' => 'root',
    						//          'password' => '',
    						//          )
    						)
    		    
    下面对上面每一个配置进行说明:
    1.driverType
    作用:告诉Soter这是一个什么类型的数据库配置,以便Soter正确的初始化数据库连接。
    值:mysql 固定不变。
    2.debug
    作用:告诉Soter发生数据库错误的时候,是否报告错误信息。
    值:ture开启数据库错误报告,false关闭数据库错误报告。
    提示:如果我们在代码中临时需要“有意的”进行一些错误查询,通过判断错误信息,达到一些目的时候。
    我们可以通过Sr::db()->setDebug(false)关闭数据库错误报告,
    然后进行“有意的”错误查询,可以通过Sr::db()->error()获取错误信息字符串。
    最后再Sr::db()->setDebug(true)开启即可。
    另外:debug为true的时候,只是报告错误给Soter,
    Soter是否显示这个错误是由入口文件里面设置的setShowError决定的,
    setShowError是true的时候才会显示错误信息。
    使用场景之一:
    需求:比如我们设计了一个SQL学习的网站,用户可以执行sql,返回数据库错误信息。
    我们的程序应该大概是这个样子:
    						...
    						$sql=Sr::post('sql');
    						Sr::db()->setDebug(false);
    						if(!Sr::db()->execute($sql)){
    							echo Sr::db()->error();
    						}
    						Sr::db()->setDebug(true);
    						...
    		    

    上面的代码接受了浏览器post过来的$sql语句,
    然后关闭数据库错误报告,
    执行sql如果出错就显示具体的错误信息。
    最后开启数据库错误报告,继续下面的逻辑。
    3.pconnect
    作用:告诉Soter连接数据库的时候,是否采用持久连接。
    值:ture采用持久连接,false不采用持久连接。
    4.charset
    作用:连接数据库使用的编码。
    值:数据库支持的编码,常见的有utf8 , gbk。
    5.collate
    作用:连接数据库使用的collate编码。
    值:数据库支持的collate编码,常见的有utf8_general_ci,utf8mb4_general_ci。
    6.database
    作用:告诉Soter连接到哪个数据库。
    值:数据库名称。
    7.tablePrefix
    作用:告诉Soter数据库查询的时候,在表名称前面加上的前缀。
    值:数据库表前缀,没有前缀留空即可。
    8.tablePrefixSqlIdentifier
    作用:当我们使用ActiveRecord模式链式查询数据库的时候,
    Soter能够自动在表名称前面加上我们设置的表前缀。
    但是当我们直接执行自定义的sql语句的时候,Soter是无法知道在sql语句字符串里面哪里需要加上表前缀。
    为了避免我们自己手动加前缀,适应数据库表前缀变化,那么我们就可以在sql语句里面需要加上表前缀的地方
    插入一个“占位字符串”这个占位字符串就是tablePrefixSqlIdentifier的值,这样一来,当我们执行自定义sql语句的时候,
    Soter会把sql语句里面出现“占位字符串”替换成表前缀。
    值:默认是_tablePrefix_,可以设置为用户想用的。
    使用实例:
    需求:数据库配置里面配置了表前缀“test_”,tablePrefixSqlIdentifier配置的是_tablePrefix_
    现在自定义一个sql语句查询,不用ActiveRecord模式链式查询。又不想手动加表前缀在sql语句里面。
    代码如下:
    						$sql="select * from _tablePrefix_user";
    						Sr::db()->execute($sql);
    		    
    上面代码中,Soter执行$sql之前,会把$sql里面的_tablePrefix_user替换成test_user,
    这样我们就不用手动加上表前缀,保证了代码的通用性可移植性。
    9.masters
    masters是让我们设置我们有哪些主数据库要用,可以有一个或者多个。
    设置的时候为每个数据库主机指定一个数组key就行,这样能够让Soter区分出不同的主机。
    上面的master01就是一个key,我们也可以把它叫做master001。
    有多个主数据库的时候,类比master01加在master01下面就行了。
    有两个主数据库的时候,配置实例:
    							...
    						'masters' => array(
    								'master01' => array(
    								    'hostname' => '192.168.233.2',
    								    'port' => 3306,
    								    'username' => 'root',
    								    'password' => 'test',
    								),
    								'master02' => array(
    								    'hostname' => '192.168.233.3',
    								    'port' => 3306,
    								    'username' => 'root',
    								    'password' => 'test',
    								)
    						    ),
    							...
    		    
    当我们只有一个数据库的时候,设置一个master就可以了。slaves保留空就行了。
    10.slaves
    slaves是让我们设置我们有哪些从数据库要用,可以有一个或者多个。
    设置的时候为每个数据库主机指定一个数组key就行,这样能够让Soter区分出不同的主机。
    上面的slave01就是一个key,我们也可以把它叫做slave001。
    有多个从数据库的时候,类比slave01加在slave01下面就行了。
    有两个从数据库的时候,配置实例:
    							...
    						'slaves' => array(
    								'slave01' => array(
    								    'hostname' => '192.168.233.2',
    								    'port' => 3306,
    								    'username' => 'root',
    								    'password' => 'test',
    								),
    								'slave02' => array(
    								    'hostname' => '192.168.233.3',
    								    'port' => 3306,
    								    'username' => 'root',
    								    'password' => 'test',
    								)
    						    ),
    							...
    		    

    2.SQLite配置组说明

    SQLite配置和Mysql基本相同,可以参考mysql配置部分的说明,这里不再重复说明。
    不同的地方是:
    1.数据库里使用databse指定数据库文件位置。
    2.不支持记录不满足条件索引的查询。
    2.不支持主从。
    3.组名是sqlite
    4.不支持pconnect,charset,collate设置
  4. 连接数据库

    当我们配置好了数据库文件database.php后,需要在入口文件里面启用它。 在入口文件里面我们可以看到:
    						/* 设置数据库连接信息,参数可以是配置文件名称;也可以是数据库配置信息数组,即配置文件返回的那个数组。 */
    						//->setDatabseConfig('database')
    		    
    我们要取消//->setDatabseConfig前面的注释,后面的参数database就是告诉Soter要使用的数据库配置文件名称,
    默认是database,也就是使用配置文件database.php。
    另外:
    我们还可以传入一个数据库配置数组,也就是database.php里返回的配置数组。
    比如下面是用数据库配置数组作为参数:
    							->setDatabseConfig(array(
    									//默认组
    									'default_group' => 'mysql',
    									//组名=>配置
    									//mysql配置示例
    									'mysql' => array(
    										    'driverType' => 'mysql',
    										    'debug' => true,
    										    'pconnect' => false,
    										    'charset' => 'utf8',
    										    'collate' => 'utf8_general_ci',
    										    'database' => '',
    										    'tablePrefix' => '',
    										    'tablePrefixSqlIdentifier' => '_tablePrefix_',
    										    //是否开启慢查询记录
    										    'slowQueryDebug' => false,
    										    'slowQueryTime' => 3000, //慢查询最小时间,单位毫秒,1秒=1000毫秒
    										    'slowQueryHandle' => new Soter_Database_SlowQuery_Handle_Default(),
    										    /**
    										     * 是否开启没有满足设置的索引类型的查询记录
    										     */
    										    'indexDebug' => false,
    										    /**
    										     * 索引使用的最小情况,只有小于最小情况的时候才会记录sql到日志
    										     * minIndexType值从好到坏依次是:
    										     * system > const > eq_ref > ref > fulltext > ref_or_null 
    										     * > index_merge > unique_subquery > index_subquery > range 
    										     * > index > ALL 一般来说,得保证查询至少达到range级别,最好能达到ref
    										     * 避免ALL即全表扫描
    										     */
    										    'minIndexType' => 'index',
    										    'indexHandle' => new Soter_Database_Index_Handle_Default(),
    										    'masters' => array(
    											    'master01' => array(
    												'hostname' => '127.0.0.1',
    												'port' => 3306,
    												'username' => 'root',
    												'password' => '',
    											    )
    										    ),
    										    'slaves' => array(
    										    //		    'slave01' => array(
    										    //			'hostname' => '127.0.0.1',
    										    //			'port' => 3306,
    										    //			'username' => 'root',
    										    //			'password' => '',
    										    //		    )
    										    )
    									),
    									//sqlite3配置示例
    									'sqlite3' => array(
    											    'driverType' => 'sqlite',
    											    'debug' => true,
    											    'pconnect' => false,
    											    'tablePrefix' => '',
    											    'tablePrefixSqlIdentifier' => '_tablePrefix_',
    											    'database'=>'test.sqlite3',
    											    //是否开启慢查询记录
    											    'slowQueryDebug' => true,
    											    'slowQueryTime' => 3000, //单位毫秒,1秒=1000毫秒
    											    'slowQueryHandle' => new Soter_Database_SlowQuery_Handle_Default()
    											)
    									    )
    						)
    		    
    入口文件里面使用数据库配置文件名称和直接使用数据库配置数组的区别是:
    1.使用数据库配置文件名称,我们可以在不同环境下面自动切换数据库文件,
    只需要在配置目录下每个环境下面的目录里面放上一个database.php即可。
    关于配置目录的详细信息可以参考配置文件手册部分。
    2.直接使用数据库配置数组我们就不能自动的根据环境切换数据库连接了。
    程序里面使用的数据库信息就是入口里面设置的固定的数据库配置数组。
    有三种方式连接数据库:

    1.默认方式

    默认情况下使用Sr::db()操作数据库,Sr::db()连接的是默认数据库组,也就是上面'default_group' => 'mysql',设置的,
    上面设置的默认组是mysql,那么Sr::db()操作数据库用的就是mysql配置组。

    2.手动指定组方式

    有时候我们有多个不同的数据库,需要分别连接它们进行数据操作。
    我们只要把它们的配置组信息配置在database.php配置文件里面即可,
    赋予一个组名比如:mysql_site,mysql_forum,然后我们就可以用下面的代码使用它们。
    						$site=Sr::db('mysql_site');
    						$forum=Sr::db('mysql_forum');
    						//下面就可以用$site和$forum进行各种操作数据库了,比如查询数据:$site->from('user')->execute()->rows();
    		    
    配置例子(省略了细节配置):
    						array(
    							//默认组
    							'default_group' => 'mysql',
    							//组名=>配置
    							//mysql配置示例
    							'mysql' => array(
    								'driverType' => 'mysql',
    								 ....
    							    ),
    							'mysql_site' => array(
    								'driverType' => 'mysql',
    								 ....
    							    ),
    							'mysql_forum' => array(
    								'driverType' => 'mysql',
    								 ....
    							    )
    						)
    		    

    3.传递配置组信息方式

    我们还可以直接传递Sr::db()一个数据库配置组信息数组,
    比如:
    						$dbConfig=array(
    							    'driverType' => 'mysql',
    							    'debug' => true,
    							    'pconnect' => false,
    							    'charset' => 'utf8',
    							    'collate' => 'utf8_general_ci',
    							    'database' => '',
    							    'tablePrefix' => '',
    							    'tablePrefixSqlIdentifier' => '_tablePrefix_',
    							    //是否开启慢查询记录
    							    'slowQueryDebug' => false,
    							    'slowQueryTime' => 3000, //慢查询最小时间,单位毫秒,1秒=1000毫秒
    							    'slowQueryHandle' => new Soter_Database_SlowQuery_Handle_Default(),
    							    /**
    							     * 是否开启没有满足设置的索引类型的查询记录
    							     */
    							    'indexDebug' => false,
    							    /**
    							     * 索引使用的最小情况,只有小于最小情况的时候才会记录sql到日志
    							     * minIndexType值从好到坏依次是:
    							     * system > const > eq_ref > ref > fulltext > ref_or_null 
    							     * > index_merge > unique_subquery > index_subquery > range 
    							     * > index > ALL 一般来说,得保证查询至少达到range级别,最好能达到ref
    							     * 避免ALL即全表扫描
    							     */
    							    'minIndexType' => 'index',
    							    'indexHandle' => new Soter_Database_Index_Handle_Default(),
    							    'masters' => array(
    								    'master01' => array(
    									'hostname' => '127.0.0.1',
    									'port' => 3306,
    									'username' => 'root',
    									'password' => '',
    								    )
    							    ),
    							    'slaves' => array(
    							    //		    'slave01' => array(
    							    //			'hostname' => '127.0.0.1',
    							    //			'port' => 3306,
    							    //			'username' => 'root',
    							    //			'password' => '',
    							    //		    )
    							    )
    						);
    						$db=Sr::db($dbConfig);
    						//...do something
    		    
    上面我们直接传递了一个数据库组配置数组$dbConfig给Sr::db(),
    然后用返回的$db数据库操作对象操作$dbConfig连接的数据库。
    Sr::db()第二个参数说明
    Sr::db($group = '', $isNewInstance = false)是db方法的完整参数,
    可以看到有两个参数:
    $group 数据库配置组名称,也就是上面提到的mysql,sqlite等
    $isNewInstance 是否使用新的数据库操作对象重新连接数据库
    $isNewInstance参数详细说明如下
    默认情况下,每个数据库配置组,为了节省资源Soter只会生成对应的唯一一个单例数据库操作对象。
    通过第二个参数为true的时候,可以让Soter重新生成一个和配置组对应的单例对象。
    第二个参数的意义:
    比如下面的操作:
    a.Sr::db()->insert('a',$data)->execute();
    b.//做了一个耗时的操作,时间比较长,导致上面a的数据库操作单例对象连接到数据库的链接超时。
    c.Sr::db()->update('b',$data)->execute()//这里就会报错:mysql server has gone away
    c之所以报错,因为a和c使用的都是一个对象,而且是执行a的时候连接一次数据库,以后不再连接。
    经过b耗时操作,导致a中建立的数据库连接超时,失去到数据库服务器的连接。然后下面的c执行的时候就会报错。
    为了避免这种情况,我们可以通过第二个参数,让Soter不在使用旧的单例,每次都使用新的数据库操作对象,
    这样c执行的时候就会使用新的对象去重新连接数据库就不会出现超时的问题。

    上面的问题可以进行下面的修改,可以避免超时:
    a.Sr::db()->insert('a',$data)->execute();
    b.//做了一个耗时的操作,时间比较长,导致上面a的数据库操作单例对象连接到数据库的链接超时。
    c.Sr::db('',true)->update('b',$data)->execute()//这里不会报错:mysql has gone away,因为这里通过第二个参数true,
    使用了新的数据库操作对象,会重新去连接数据库。
  5. 配置中自定义处理类的使用说明

    上面介绍中说了,Soter支持:
    1.自定义慢查询记录处理类。
    2.自定义不满足设置的索引条件的查询处理类。
    3.自定义数据库缓存数据的缓存处理类。
    下面对这三个类的自定义过程进行说明:

    1.自定义慢查询记录处理类

    配置中我们看到下面三行和慢查询有关的配置。
    						//是否开启慢查询记录
    						'slowQueryDebug' => false,
    						'slowQueryTime' => 3000, //慢查询最小时间,单位毫秒,1秒=1000毫秒
    						'slowQueryHandle' => new Soter_Database_SlowQuery_Handle_Default(),
    		    
    你会注意到,slowQueryHandle默认使用了Soter_Database_SlowQuery_Handle_Default类处理慢查询,
    它会把慢查询都写入到文件application/storage/slow-query-debug/slow-query-debug.php以便我们分析,
    这个类实现了Soter_Database_SlowQuery_Handle这个接口,然后就能在这里处理慢查询了,
    接下来就是知道怎么实现Soter_Database_SlowQuery_Handle接口,我们就可以写自己的慢查询处理类处理慢查询了。
    首先我们看一下接口的定义代码:
    						interface Soter_Database_SlowQuery_Handle {
    
    							public function handle($sql, $explainString, $time);
    						}
    		    
    可以看到接口就定义了一个handle()方法,有三个参数:
    第一个是慢查询的sql语句字符串。
    第二个是explain查询得到的字符串信息(只有是mysql的时候有效,是sqlite3的时候为空)。
    第三个是这次查询使用的时间。
    接下来就是写一个自己的类实现这个接口,把类文件放到合适的位置即可。
    现在我们实现一个简单的显示出慢查询的自定义类。
    步骤如下:
    1.新建一个文件application/classes/SlowQuery/Handle.php
    2.输入下面代码,其实就是定义了一个类,实现接口并简单的显示出慢查询。
    						class SlowQuery_Handle implements Soter_Database_SlowQuery_Handle {
    
    							public function handle($sql, $explainString, $time) {
    								$content = "\nSQL : " . $sql
    									. "\nExplain : " . $explainString
    									. "\nUsingTime : " . $time . " ms"
    									. "\nTime : " . date('Y-m-d H:i:s') . "\n";
    								echo $content;
    							}
    
    						}
    		    
    3.到此为止我们的自定义处理类搞定了,我们为了测试你可以:
    slowQueryDebug设置为true
    把慢查询时间slowQueryTime改成1
    slowQueryHandle设置为new SlowQuery_Handle()
    然后在控制器里面执行一个查询,刷新几次我们会发现显示出自定义类输出的内容。

    2.自定义不满足设置的索引条件的查询处理类

    配置中我们看到下面三行和不满足索引条件的查询处理类有关的配置。
    						'indexDebug' => false,
    						'minIndexType' => 'index',
    						'indexHandle' => new Soter_Database_Index_Handle_Default(),
    		    
    你会注意到,indexHandle默认使用了Soter_Database_Index_Handle_Default类处理不满足索引条件的查询,
    它会把不满足设置的索引条件的查询都写入到文件application/storage/index-debug/index-debug.php以便我们分析,
    这个类实现了Soter_Database_Index_Handle这个接口,然后就能在这里处理慢查询了,
    接下来就是知道怎么实现Soter_Database_Index_Handle接口,我们就可以写自己的不满足设置的索引条件的查询处理类处理查询了。
    首先我们看一下接口的定义代码:
    						interface Soter_Database_Index_Handle {
    
    							public function handle($sql, $explainString, $time);
    						}
    		    
    可以看到接口就定义了一个handle()方法,有三个参数:
    第一个是慢查询的sql语句字符串。
    第二个是explain查询得到的字符串信息(只有是mysql的时候有效,是sqlite3的时候为空)。
    第三个是这次查询使用的时间。
    接下来就是写一个自己的类实现这个接口,把类文件放到合适的位置即可。
    现在我们实现一个简单的显示出不满足设置的索引条件的查询的自定义类。
    步骤如下:
    1.新建一个文件application/classes/BadIndexQuery/Handle.php
    2.输入下面代码,其实就是定义了一个类,实现接口并简单的显示出慢查询。
    						class BadIndexQuery_Handle implements Soter_Database_Index_Handle {
    
    							public function handle($sql, $explainString, $time) {
    								$content = "\nSQL : " . $sql
    									. "\nExplain : " . $explainString
    									. "\nUsingTime : " . $time . " ms"
    									. "\nTime : " . date('Y-m-d H:i:s') . "\n";
    								echo $content;
    							}
    
    						}
    		    
    3.到此为止我们的自定义处理类搞定了,我们为了测试,可以自己手动建立一个表,然后不建立索引。
    indexDebug设置为true
    indexHandle设置为new BadIndexQuery_Handle()
    然后在控制器里面执行搜索所有数据的查询,访问一下我们会发现显示出自定义类输出的内容。

    3.自定义数据库缓存数据的缓存处理类

    数据库缓存操作使用的是入口文件里面配置的程序使用的缓存处理类,配置位于入口文件index.php里面,
    在入口文件里面我们看到下面这个一行:
    						//设置缓存类型
    						->setCacheHandle(new Soter_Cache_File(SOTER_APP_PATH . 'storage/cache/'))
    		    
    这个就是配置程序中使用的缓存处理类,可以看到默认使用的是Soter提供的文件缓存处理类Soter_Cache_File,
    它会把缓存数据写到application/storage/cache/目录下面。
    关于如何自定义缓存处理类,可以参考手册缓存部分
  6. 查询数据

    查询数据,我可以通过Sr::db()获取数据库操作对象,然后进行查询。
    为了简单明了的演示查询功能,请看下面的查询示例:
    假设表前缀是空。

    1.execute

    Soter的数据库查询无论是增删改查,最后都是通过调用execute()方法执行的。
    execute()根据查询的类型不同返回的数据类型也不同,具体如下:
    1.数据库中受到影响的行数
    当执行的是DELETE,REPLACE,UPDATE,INSERT的时候,execute()返回的是数据库中受到影响的行数;
    INSERT,REPLACE操作的时候可以通过Sr::db()->lastId()获取最后插入的ID。
    2.Soter_Database_ResultSet结果集对象
    当执行的是SELECT的时候,execute()返回的是Soter_Database_Resultset结果集对象。
    3.true或false
    当执行的是上面1和2之外的查询的时候,execute()返回的是执行成功true或者失败false。

    2.select

    						echo Sr::db()->select('*')->from('user');
    						//将输出:SELECT * FROM `user`;
    						$rs=Sr::db()->execute()->rows();
    						//$rs是数据库结果集数组
    		    
    提示:上面第一句,并没有真正的连接数据库进行查询,只是编译sql语句,
    只有最后调用了execute()方法时才会真正的查询数据库。

    select还有第二个参数,第二个参数意思是:是否用反引号保护字段。true:保护。false:不保护。默认:true。
    比如:
    1、Sr::db()->select(‘username’,true) 生成 : select `username`
    2、Sr::db()->select(‘username’,false) 生成 : select username

    2.from

    						echo Sr::db()->select('c.id as cat_id , b.id as group_id, dd.id as user_id')
    								->from('a', 'dd');
    						//将输出: SELECT `c`.`id` AS `cat_id`,`b`.`id` AS `group_id`,`dd`.`id` AS `user_id`
    						//	   FROM  `a` AS `dd`
    		    

    3.join

    第一个参数是数组的时候:array('原始表名称'=>'as别名')。
    						echo Sr::db()->select('x.id as cat_id , b.id as group_id, dd.id as user_id')
    								->from('a', 'dd')
    								->join('b', 'dd.gid=b.id', 'left')
    								->join(array('c'=>'x'), 'b.cid=x.id', 'left')
    								->where(array('dd.id <=' => 2));
    						//将输出: SELECT `x`.`id` AS `cat_id`,`b`.`id` AS `group_id`,`dd`.`id` AS `user_id`
    						//	   FROM  `a` AS `dd`   LEFT JOIN `b` ON  `dd`.`gid`  =  `b`.`id`   LEFT JOIN `c` as `x` ON  `b`.`cid`  =  `x`.`id`  
    						//	   WHERE `dd`.`id` <= ?
    		    

    4.where

    我们可以通过一个键值对数组方便的作为查询条件
    						Sr::db()->from('user');
    						$where=array();
    						$where['id']=1;
    						$where['uid']=array(1,2,3);
    						Sr::db()->where($where);
    						$where=array();
    						$where['name']=NULL;
    						$where['man']=true;
    						Sr::db()->where($where);
    						$where=array();
    						$where['uid not']=array(4,5,6);
    						$where['id >']=0;
    						$where['id <>']=-1;
    						$where['name like']='%test%';
    						echo Sr::db()->where($where);
    						//多次调用where都是可以的
    						//将输出:SELECT * FROM `user` WHERE `id` = ? AND `uid` IN (?,?,?) AND `name` IS NULL AND `man` = ? AND `uid` NOT IN (?,?,?) AND `id` > ? AND `id` <> ? AND `name` like ?
    						//如果要执行这条语句,我们调用execute方法
    						 Sr::db()->execute();
    		    

    5.高级where

    						echo Sr::db()->select('cname')->from('user')
    							->where(array('id <=' => 2, 'id <>' => 3), '(', ')')
    							->where(array('id >=' => 0));
    						//将输出:SELECT `cname` FROM `user` WHERE ( `id` <= ? AND `id` <> ? ) AND `id` >= ?
    		    
    WHERE条件之间默认是AND连接,我们可以通过第二个参数改变为OR,例子如下:
    						echo Sr::db()->select('cname')->from('user')
    							->where(array('id <=' => 2, 'id <>' => 3), '(', ')')
    							->where(array('id >=' => 0),'OR');
    						//将输出:SELECT `cname` FROM `user` WHERE ( `id` <= ? AND `id` <> ? ) OR `id` >= ?
    		    

    6.group by

    						echo Sr::db()->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->groupBy('cname');
    						//将输出:SELECT count(`id`) as total,`id` FROM `c` GROUP BY `cname` 
    		    

    7.having

    						echo Sr::db()->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->groupBy('cname')
    								->having('total >= 1');
    						//将输出:SELECT count(`id`) as total,`id` FROM `c` GROUP BY `cname` HAVING total >= 1 
    		    

    8.order by

    						echo Sr::db()->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->groupBy('cname')
    								->having('total >= 1')
    								->orderBy('total', 'desc');
    						//将输出:SELECT count(`id`) as total,`id` FROM `c` GROUP BY `cname` HAVING total >= 1 ORDER BY `total` DESC
    		    

    9.limit

    						echo Sr::db()->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->limit(0,1);
    						//将输出:SELECT count(`id`) as total,`id` FROM `c`  LIMIT 0 , 1
    		    

    10.wrap

    上面我们看见好几处都使用了方法Sr::db()->wrap(),
    当我们在查询的时候使用了函数的字段不会自动处理需要自己加上保护符号和表前缀,
    需要使用方法Sr::db()->wrap()进行自动加上表前缀和字段保护符号。
    使用示例:
    						echo Sr::db()->select('count('.Sr::db()->wrap('user.id').') as total,id')->from('c')
    								->limit(0,1);
    						//将输出:SELECT count(`user`.`id`) as total,`id` FROM `c`  LIMIT 0 , 1
    		    
    						echo Sr::db()->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->limit(0,1);
    						//将输出:SELECT count(`id`) as total,`id` FROM `c`  LIMIT 0 , 1
    		    
  7. 查询结果集的使用

    上面说到,当我们执行select查询的时候,execute方法返回的是一个Soter_Database_Reaultset对象。
    这个对象提供了很多有用的方法处理结果集。
    下面对它的所有方法一一介绍:
    1.rows($isAssoc = true)
    作用:得到查询结果数据数组
    参数:$isAssoc 是否返回关联数组,true返回关联数组,false返回数字索引数组
    2.row($index = null, $isAssoc = true)
    作用:得到查询结果数据数组中的第$index个记录的数据,默认取第一条
    参数:$index 获取查询结果中的第几条,0开始的数字,0是第一条
    $isAssoc 是否返回关联数组
    3.values($columnName)
    作用:得到查询结果数据数组中的某一字段的一维数组
    参数:$columnName 字段名称
    4.value($columnName, $default = null, $index = null)
    作用:得到查询结果数据数组中的第$index条记录的$columnName字段的值
    参数:$columnName 字段名称
    5.key($columnName)
    作用:设置使用哪个字段的值作为返回的结果集数组的键
    参数:$columnName 字段名称
    比如我们查询用户信息,设置返回的二维数据,需要一维数组键值不是0开始的数字,而是用户id,
    我们可以这样查询
    						$rows=Sr::db()->select('*')->from('user')->execute()->key('user_id')->rows();
    						//那么我们得到类似的下面的结果
    						$rows形如:array(
    								'223'=>array('user_id'=>223,'uname'=>'test'),
    								'224'=>array('user_id'=>224,'uname'=>'test'),
    								...
    							)
    		    

    6.total()
    作用:返回查询结果中有多少条数据
    7.objects($beanClassName)
    作用:得到查询结果数据数组,数组元素是传入的bean对象。
    参数:$beanClassName 数据库表的Soter_Bean类名称,Soter_Bean规范可以参考“Bean实体
    8.object($beanClassName, $index = null)
    作用:得到查询结果数据数组中的第$index个记录的数据的bean对象,默认取第一条
    参数:$beanClassName 数据库表的Soter_Bean类名称,Soter_Bean规范可以参考“Bean实体
    参数:$index 获取查询结果中的第几条,0开始的数字,0是第一条
  8. 查询缓存

    通过cache方法我们可以十分方便的把我们的查询结果缓存一定的时间。
    需要对一个查询开启缓存效果我们只需要在查询前面调用cache方法即可。
    示例如下:
    						//缓存查询结果60秒
    						$rows= Sr::db()->cache(60)->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->limit(0,1)
    								->execute()
    								->rows();
    						//缓存查询结果60秒,并指定缓存key是top_user
    						$rows= Sr::db()->cache(60,'top_user')->select('count('.Sr::db()->wrap('id').') as total,id')->from('c')
    								->limit(0,1)
    								->execute()
    								->rows();
    		    
  9. 插入数据

    我们可以通过insert方法方便的将键值对数组插入数据库,键是字段名,值是字段值
    						echo Sr::db()->insert('a', array('name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000)));
    						//将输出:INSERT INTO `a` (`name`,`gid`) VALUES (?,?)
    		    
    然后我们执行Sr::db()->execute()返回的是数据库中受到影响的行数。
    可以通过Sr::db()->lastId()获取最后插入的ID。
  10. 批量插入数据

    我们可以通过insertBatch方法方便的将二维数组插入数据库
    						$data[] = array('name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						$data[] = array('name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						$data[] = array('name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						echo Sr::db()->insertBatch('a', $data);
    						//将输出:INSERT INTO `a` (`name`,`gid`) VALUES (?,?) , (?,?) , (?,?)
    		    
    提示:
    1.执行完批量插入之后,我们通过Sr::db()->lasstId()获取的是这次批量插入数据的第一条数据的ID,
    后面的ID依次加一,自己可以计算出所有的ID。
    2.关于sqlite批量插入功能,因为sqlite>= 3.7.11(PHP5.5)开始才支持一次insert多个values,
    所以sqlite批量插入功能,要求PHP>=5.5的版本,PHP>=5.5的版本的PDO SQLite 版本才>=3.7.11。
  11. 更新数据

    通过update方法我们可以方便的更新一条数据,第一个参数是表名称,第二个是数据,第三个是where条件。
    						echo Sr::db()->update('a', array('name' => '2222'), array('id' => 1));
    						//将输出:UPDATE `a` SET `name` = ? WHERE `id` = ?
    		    
    如果我们想设置字段值自增,可以使用set方法,示例如下:
    						echo Sr::db()->where(array('id' => 1))->set('gid', 'gid + 1', false)->update('a',array('name'=>'xxx'));
    						//将输出:UPDATE `a` SET `gid` = gid + 1 , `name` = ? WHERE `id` = ?
    		    
    set方法有三个参数:
    第一个:字段名称, 比如 gid
    第二个:字段值表达,比如 gid+1
    第三个:是否把第二个参数值全部作为值,默认true
  12. 批量更新数据

    通过updateBatch我们可以一次性更新多条数据,更新数据的格式如下$updata,$updata数组元素里面必须包含主键id,
    这样Soter才知道哪一个数据应该被修改。
    						$updata[] = array('id' => 1, 'name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						$updata[] = array('id' => 2, 'name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						$updata[] = array('id' =>3, 'name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						echo Sr::db()->updateBatch('a', $updata, 'id');
    						//将输出:UPDATE `a` SET `name` = CASE WHEN `id` = 1 THEN ? WHEN `id` = 2 THEN ? WHEN `id` = 3 THEN ? ELSE `name` END, `gid` = CASE WHEN `id` = 1 THEN ? WHEN `id` = 2 THEN ? WHEN `id` = 3 THEN ? ELSE `gid` END WHERE `id` IN (?,?,?)
    		    
    如果我们想在把某个字段的值自增,怎么办呢。只要把字段的值按着数组的形式即可,比如array('num +'=>3),就会生成 `num` + 3
    注意num后面要有空格,空格后面是数据库支持的计算操作符。
    下面是完整的例子:
    						$updata[] = array('id' => 1, 'gid' =>array('gid +'=>1));
    						$updata[] = array('id' => 2,'gid' => array('gid +'=>2));
    						$updata[] = array('id' =>3,  'gid' => array('gid +'=>3));
    						echo Sr::db()->updateBatch('a', $updata, 'id');
    						//将输出:UPDATE `a` SET  `gid` = CASE WHEN `id` = 1 THEN `gid` + ? WHEN `id` = 2 THEN `gid` + ? WHEN `id` = 3 THEN `gid` + ? ELSE `gid` END WHERE `id` IN (?,?,?)
    		    
  13. 替换数据

    我们可以通过replace方法方便的替换数据,
    数据是否替换,是根据数据里面的主键字段或者唯一索引字段进行判断的,不存在就插入,
    存在就先删除后插入保持和原来删除的主键或唯一索引一样。
    						echo Sr::db()->replace('a', array('name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000)));
    						//将输出:REPLACE INTO `a` (`name`,`gid`) VALUES (?,?)
    		    
  14. 批量替换数据

    通过replaceBatch我们可以一次性替换多条数据,更新数据的格式如下$updata,
    数据是否替换,是根据数据里面的主键字段或者唯一索引字段进行判断的,不存在就插入,
    存在就先删除后插入保持和原来删除的主键或唯一索引一样。
    						$updata[] = array('id' => 1, 'name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						$updata[] = array('id' => 2, 'name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						$updata[] = array('id' =>3, 'name' => 'name' . rand(1000, 10000), 'gid' => rand(1000, 10000));
    						echo Sr::db()->replaceBatch('a', $updata);
    						//将输出:REPLACE INTO `a` (`id`,`name`,`gid`) VALUES (?,?,?) , (?,?,?) , (?,?,?)
    		    
  15. 删除数据

    通过delete方法我们可以方便的删除数据,第二个参数我们可以传入where条件。
    						$where=array('id >'=>100);
    						echo Sr::db()->delete('a',$where);
    						//将输出:DELETE FROM `a` WHERE `id` > ?
    		    
  16. 显式获取sql语句和值数组

    上的例子中我们都是直接这样直接 echo Sr::db()->delete('a',$where),echo一个数据库操作对象得到sql预处理语句。
    我们还可以通过getSql方法显示获取sql预处理语句,使用getSqlValues方法获取我们在的预处理语句中使用的值。
    比如下面:
    						$where=array('id >'=>100);
    						Sr::db()->delete('a',$where);
    						echo Sr::db()->getSql();
    						//将输出:DELETE FROM `a` WHERE `id` > ?
    						Sr::dump(Sr::db()->getSqlValues());
    						//将输出:array(1) {
    						//		[0]=>
    						//		int(100)
    						//	      }
    		    
    提示:
    当调用getSql()方法之后,之后如果再进行sql的变化将不会起作用。
    execute使用的的还是调用getSql()时的sql。
    也就是说调用getSql()方法之后不应该再进行sql查询改变。
  17. 直接执行SQL语句

    通过Sr::db()->execute($sql,$values=array())方法我们可以直接执行一个sql语句,
    第一个参数是sql语句,第二个参数是第一个参数中的问号?占位符对应的值。
    写入型的sql返回bool或者影响的行数(insert,delete,replace,update),查询型的返回结果集。
    示例:
    						Sr::db()->execute('select * from test');
    						Sr::db()->execute('select * from test where id>? and uid<?',array(21,33));
    						//执行的是:select * from test where id>21 and uid<33
    		    
    提示:
    直接执行sql语句的时候,如果我们在数据库配置里面设置了表前缀,为了应对前缀的不断变化,
    我们可以看到数据库配置里面有一个配置项“tablePrefixSqlIdentifier”,它的默认值是“_tablePrefix_”。
    1.那么当我们设置的表前缀是“soter_”的时候,执行下面的sql:
    						Sr::db()->execute('select * from _tablePrefix_test');
    						//实际执行的是:select * from soter_test
    		    
    2.那么当我们设置的表前缀是“空”的时候,执行下面的sql:
    						Sr::db()->execute('select * from _tablePrefix_test');
    						//实际执行的是:select * from test
    		    
    通过1和2我们可以发现,配置项“tablePrefixSqlIdentifier”的值就是表前缀的占位符,
    这样我们每次修改表前缀都不用修改sql语句,增加代码了灵活性。
  18. 事务

    通过begin、commit、rollback三个方法的配合 我们可以方便的处理事务,下面是一个完整的事务例子。
    						try {
    							//开始事务
    							Sr::db()->begin();
    							$datac = array();
    							$datac[] = array('cname' => 'cname' . rand(1000, 10000));
    							$datac[] = array('cname' => 'cname' . rand(1000, 10000));
    							$datac[] = array('cname' => 'cname' . rand(1000, 10000));
    							$this->db->insertBatch('c', $datac)->execute();
    							//....其它操作
    							Sr::db()->select('none')->from('a')->execute();
    							//提交事务							
    							Sr::db()->commit();
    						} catch (Exception $exc) {
    							//发生异常回滚事务
    							Sr::db()->rollback();
    						}
    		    
  19. 关闭数据库连接

    关闭数据库连接可以通过Sr::db()返回的Soter_Database_ActiveRecord对象的close方法,
    可以通过Sr::db()->close()手动关闭数据库连接,close方法会清除当前所有已经连接的数据库。
    如果要重新连接数据库怎么办?
    无需手动进行任何多余操作,直接Sr::db()继续使用就行!Soter会自己检查是否连接,没有连接然后会自动连接。
    比如下面代码:
    						//查询
    						Sr::db()->select('none')->from('a')->execute();
    						//关闭连接						
    						Sr::db()->close();
    						//继续查询,不用担心上面关闭了连接,下面查询的时候Soter会自己检查是否连接
    						Sr::db()->select('none')->from('a')->execute();
    		    
    提示: 我们一般是不需要手动调用close方法的,当php页面执行完毕数据库对象就会被销毁,数据库连接就会断开。