解决验证码最基础的几种方法其实不叫解决,只能算是避过验证码,真正的验证码识别,需要用的很多技术,图像识别源码分析,网络数据流等等。验证码只是为了防止防止用户误操作,或者防止某些刷子,减少服务器的压力而设置的,如果真的是为了
自动化测试,我们完全可以屏蔽改功能。
对于
web应用来说,大部分的系统在用户登录时都要求用户输入验证码,验证码的类型的很多,有字母数字的,有汉字的,甚至还要用户输入一条算术题的答案的,对于系统来说使用验证码可以有效果的防止采用机器猜测方法对口令的刺探,在一定程度上增加了安全性。但对于测试人员来说,不管是进行
性能测试还是自动化测试都是一个棘手的问题。
下面来谈一下处理验证码的几种方法。
去掉验证码
这是最简单的方法,对于开发人员来说,只是把验证码的相关代码注释掉即可,如果是在测试环境,这样做可省去了测试人员不少麻烦,如果自动化脚本是要在正式环境跑,这样就给系统带来了一定的风险。
设置万能码
去掉验证码的主要是安全问题,为了应对在线系统的安全性威胁,可以在修改程序时不取消验证码,而是程序中留一个“后门”---设置一个“万能验证码”,只要用户输入这个“万能验证码”,程序就认为验证通过,否则按照原先的验证方式进行验证。
#coding=utf-8
importrandom
#生成0到10之间的随机数#d = random.uniform(0,10)#print d#生成一个1000到9999之间的随机整数
d = random.randint(1000,9999)
printu"生成的随机数:%d"%d i = input(u"请输入随机数:")
printiifi == d:printu"登录成功!!"
elifi == 1111:printu"登录成功!!"
else:printu"请重新输入验证码!"
运行结果:
>>> ================================ RESTART ================================ >>> 生成的随机数:3764 请输入随机数:1111 1111 登录成功!! >>> ================================ RESTART ================================ >>> 生成的随机数:3763 请输入随机数:3763 3763 登录成功!! >>> ================================ RESTART ================================ >>> 生成的随机数:1928 请输入随机数:1354646 1354646 请重新输入验证码! |
random用于生成随机数
randint()方法用于生成随机整数,传递的两个参数分别是随机数的范围,randint(1000,9999)第二个参数要大于第一个参数。
我们要求用户输入随机数,并且对用户输入做判断,如果等于生成的随机数那么,登录成功,如果等于1111也算登录成功,否则失败。那么等于1111的判断就是一个万能码。
验证码识别技术
例如可以通过Python-tesseract 来识别图片验证码,Python-tesseract是光学字符识别Tesseract OCR引擎的
Python封装类。能够读取任何常规的图片文件(JPG, GIF ,PNG , TIFF等)。不过,目前市面上的验证码形式繁多,目前任何一种验证码识别技术,识别率都不是100% 。
记录cookie
适用于UI自动化测试,且目前在大部应用的用户名密码不记录在cookie 或 进行加密处理。
通过向浏览器中添加cookie 可以绕过登录的验证码,这是比较有意思的一种解决方案。我们可以在用户登录之前,通过add_cookie()方法将用户名密码写入浏览器cookie ,再次访问系统登录链接将自动登录。例如下面的方式:
....#访问xxxx网站driver.get("http://www.xxxx.cn/") #将用户名密码写入浏览器cookie driver.add_cookie({'name':'Login_UserNumber','value':'username'}) driver.add_cookie({'name':'Login_Passwd','value':'password'}) #再次访问xxxx网站,将会自动登录 driver.get("http://www.xxxx.cn/")time.sleep(3)....driver.quit() |
使用cookie进行登录最大的难点是如何获得用户名密码的name ,如果找到不到name 的名字,就没办法向value 中输用户名、密码信息。
可以通过get_cookies()方法来获取登录的所有的cookie信息,从而进行找到用户名、密码的name 对象的名字。
很多时候,我们
测试时,如果进行大数据量的并发测试时,单个电脑的CPU和内存可能无法承受,这个时候,我们需要进行一个分布式的测试,比如10000个并发,使用三台电脑来进行并发,
Jmeter提供了这种功能,你可以很轻松的实现Jmeter的这种分布式测试
1 首先确何所有的电脑上都安装Jmeter
2 在所有电脑上开起Jmeter,开启命令是jmeter-server.bat,而不是以前的jmeter.bat
注意:你所要运行的不要开启,那些用来负载的,才开启服务器模式,这个模式没有界面,只有控制台的
3 在你所想要操作的电脑上,以jmeter.bat命令来启动图形化的jmeter界面
编辑jmeter.properties
remote_hosts=127.0.0.1,192.168.0.102 #remote_hosts=localhost:1099,localhost:2010
通过远程启动,便可驱动远程开启的jmeter同时进行并发测试,这样便同时有几个Jmeter进行并发测试了,这样可以实现了分布式的开发.
1、childNodes引入空白节点问题:使用childElementCount或children
2、innerText: FF中不支持该属性,使用textContent代替
3、变量名与某HTML对象id相同时,引用该变量只会取得id名与其相同的html对象(ie8-);声明变量时前面一律加上var,尽量避免id名与变量名相同
4、为ele.style.property赋值时一律带上单位:e.style.height= 34 + ‘px’
5、禁止选择网页内容:
6、访问form中的元素:ff只支持document.formName.elements['elementName']的方式,ie下可以使用document.formName.item('name');统一使用elements的方式;凡是遇到集合类对象(NodeCollection、NodeList)一律使用collection['name']的方式
7、自定义html元素特性问题:IE下可以使用e.selfAttr = variable/e.selfAttr方式来设值和取值,FF中只能使用e.setAttribute(attr, value)/e.getAttribute('attr')方式
8、input元素的type特性问题:IE下该属性是只读的,FF中可以动态设置;一律不能修改,若需要修改则删除原来元素,重新创建新元素
9、window.location.href问题:就浏览器可以通过这种方式来获取当前页面url;应当统一使用window.location来方位页面url,如:location.hostname,location.port,location.pathname
10、在浏览器中打开新窗口问题:
View Code
11、body载入问题:FF中的body对象在body标签为载入完全时即可访问,IE下必须完全读入后才执行
12、function、new function(){}、new Function('.....')三者的区别
13、FF中不支持e.parentElement方式方位父元素,只能使用e.parentNode方式
14、Table操作问题,IE中无法使用innerHTML方式对table和tr进行操作;一般方法是借助js类库,将innerHLML转化为dom节点,并插入到tbody下
15、IE下不支持使用e.setAttribute方式来整体设值style属性问题:同时使用e.setAttribute('style', '.......')和e.style.cssText = ‘。。。。。。。’方式来设置
16、document.createElement('<div class="name"></div>')方式创建html元素在FF中不支持
17、iframe问题:
<iframe src="xxx.html" id="frameId" name="frameName" />
IE 中可以通过window.top.frmaeId或window.top.frameName方式来访问farme;FF中只支持第二种方式;IE在iframe资源未加载完成时无法访问iframe.contentWindow对象
18、url encoding 问题:encodeURIComponent适用于对url后的参数编码、encodeURI:主要用于location对象跳转时对整个url编码
19、节点插入问题:IE:insertAdjacentElement(position,src);FF:insertBefore(src, ref)
20、IE9以下不能访问html元素的构造器,如判断元素是否为HTMLElement方法只能使用:e.nodeType === 1不能使用 e instanceof HTMLElement方式
客户也经常提这样的概念或者尝试实践。有些客户可能只做易用性测试,有些客户则关注探索性测试。还很少看到两者都做得。这里简单诠释下两者的相同和不同,如果有不同意的地方,敬请指正。
相同点
1. 易用性测试和探索性测试都是面向业务的测试。所谓面向业务的测试是区别于面向技术的测试,它更多关注用户感受,逻辑是否合理,流程是否正确,功能是否有遗漏等。
2. 两者属于手工测试范畴。虽然有时候用户也可以用工具辅助做探索性,但是两者都属于手工测试。依赖于测试人员的专业能力和分析能力
3. 任何人都可以参与的测试方式
不同点
易用性测试用一句话概况就是:软件使用是否方便。具体来讲它分:可理解性,可操作性,可
学习性,吸引性和依从性5个方面进行分析。易用性测试本身不是为了寻找系统还存在哪些
bug,而且在假设没有bug的基础上,对系统从人体工程学上进行测试。如果期间发现bug,那纯当系统Bug处理。易用性一般只以建议的方式对发现的问题进行登记。易用性测试建议在项目初期界面设计阶段引入,因为对于后期来说,系统的更改成本和风险过高。
对于易用性测试,本身不能绝对性,任何界面设计专家也不能完全代表用户。因此需要更多的分析用户群体的行为习惯,例如:老年人会习惯界面文字比较大,图案比较传统的方式。而年轻人会选择页面精细,功能丰富的系统。所以在进行易用性测试的过程中,从用户的角度进行测试是根本。在有些产品用户群体比较广泛的情况下,往往需要做Alpha,Beta测试来进行统计。以获取最合适的人群。
探索性测试比较流行的定义是:边设计用例边测试。
如果把脚本测试作为一张网的话,那么探索性测试就是在寻找能透过网洞的bug。
探索性测试从测试手法上多会采用颠倒次序,改变状态,跟随产生的数据等来发现系统可能存在的潜在问题。因此探索性测试更倾向于是功能性的测试。探索性测试一般发生在系统集成测试完成,可以在UAT之前或者同时。
探索性测试也可以是没有界面的测试,这点跟易用性测试完全不同。对于没有界面的测试,需要开发人员或者测试人员有很强的数据追踪能力,了解数据在不同阶段的变化过程和变化预期。
当然此外还有更多不同之处,这里抛砖引玉,希望读者继续发掘!
哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在
Java的Object类中有一个方法:
public native int hashCode();
根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现。
为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法。
一.hashCode方法的作用
对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)
也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是java.util.HashMap的中put方法的具体实现:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } |
put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
有些朋友误以为默认情况下,hashCode返回的就是对象的存储地址,事实上这种看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样。
因此有人会说,可以直接根据hashcode值判断两个对象是否相等吗?肯定是不可以的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。
也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
前言
最近一直在研究
Android内核驱动开发的相关事宜,使用VMware虚拟机虽然可以更方便的开发,但是对于开发环境硬件的要求还是比较高的,若用于开发,效率太低了,所以考虑使用单独PC去装载
Linux环境进行开发,这里这里挑选的Ubuntu,因为
Google测试Android源代码时使用的就是Ubuntu。因此如果从事Android底层驱动开发,强烈建议使用Ubuntu。本篇博客就Ubuntu系统使用U盘安装到硬盘,进行详细的讲解,这里使用的Ubuntu镜像版本是Ubuntu12.04。
本篇博客的主要内容:
制作启动盘
安装Ubuntu到硬盘
准备工作
安装Ubuntu系统到硬盘中,有一些准备工作需要做:
2GB以上空间的U盘
Universal-USB-Installer(自启动Linux U盘创建工具)
Ubuntu系统镜像
2GB以上空间的U盘
如果需要把Ubuntu安装在硬盘中,一般使用的方式是使用系统光盘或者U盘安装,因为现在U盘的获取比较便捷,基本上谁手边都会有一个可用的U盘。这里需要一个不小于2GB的空白U盘,建议在使用前对U盘中的重要资料进行备份。
Universal-USB-Installer
Universal USB Installer(通用USB安装程序)是一个自启动Linux U盘创建工具,可从大量精选的Linux发行版中挑选一个安装到您的U盘上。通用USB安装程序使用方便,只需选择自启动Linux发行版的ISO文件,和连接的U盘,单击“安装”即可。其他功能包括,持续保存(如果可用的话),以FAT32格式格式化U盘(推荐)确保一个干净的安装。安装完成后,就拥有了一个安装了Linux版本的自启动U盘。
Universal USB Installer网上下载的版本很多,本篇博客中介绍的版本号是1.9.5.2,在我的CSDN资料中提供了下载,方便直接下载。
Universal USB Installer:点击下载。
Ubuntu系统镜像
Ubuntu有三个版本,分别是桌面版(Desktop Edition),服务器版(Server Edition),上网本版(Netbook Remix),这里使用桌面版即可。Ubuntu每6个月发布一个新版本,而每个版本都有代号和版本号,其中有LTS是长期支持版,所以这里下载的也是LTS的。
到现在为止,Ubuntu最新的LTS版本是Ubuntu 14.04 LTS,可以直接从官方网站上下载:点击下载。
但是最新版的肯定有诸多的问题,那么可以考虑下载历史版本,我这里安装的版本是Ubuntu 12.04 LTS。可以考虑从各种开源镜像站下载。
在开源镜像站中选择相应的版本,找到desktop版本的下载ISO镜像,可以发现有非常多的下载链接。其中i386表示32位
操作系统,amd64表示64位操作系统。
所以如果想安装32位Ubuntu操作系统,可以下载ubuntu-12.04.4-desktop-i386.iso,如果想安装64位Ubuntu操作系统,可以下载ubuntu-12.04.4-desktop-amd64.iso,点击下载即可,大约七百多MB。
制作Ubuntu启动盘
运行Universal USB Installer,点击<I Agree>按钮。然后按照提示选中需要安装的Linux版本、镜像文件、制作镜像的U盘。如下图:
最后点击<Create>按钮,接下来软件会自动帮我们把镜像文件写入到U盘中,最后提示完成即可。
安装Ubuntu到硬盘
制作好启动盘之后,就可以正式开始安装Ubuntu系统了。要想让你的电脑从U盘启动,有两种方法,一种是在BIOS里修改启动顺序;另一种是开机时按某功能键(具体决定于机型,通常是F2),然后选择从哪启动,这里就不详细讲解了。
当进入U盘启动之后,可以选择预览或者直接安装。这里选择直接安装,如如下视图,对于英语不太好的可以选择中文,点击<Install Ubuntu>安装。之后的步骤可按照向导一步一步完成即可,安装结束后会提示重新启动电脑。
最近有个项目,需要用php操作mongoDb数据,所以了解下mongoDb为此整理了下,常见的操作......
1,连接MongoDB数据库
$conn = new Mongo(); 其他链接方式 //$conn=new Mongo(); #连接本地主机,默认端口. //$conn=new Mongo(“172.21.15.69″); #连接远程主机 //$conn=new Mongo(“xiaocai.loc:10086″); #连接指定端口远程主机 //$conn=new Mongo(“xiaocai.loc”,array(“replicaSet”=>true)); #负载均衡 //$conn=new Mongo(“xiaocai.loc”,array(“persist”=>”t”)); #持久连接 //$conn=new Mongo(“mongodb://sa:123@localhost”); #带用户名密码 //$conn=new Mongo(“mongodb://localhost:27017,localhost:27018″); #连接多个服务器 //$conn=new Mongo(“mongodb:///tmp/mongo-27017.sock”); #域套接字 //$conn=new Mongo(“mongodb://admin_miss:miss@localhost:27017/test”,array(‘persist’=>’p',”replicaSet”=>true)); #完整 |
$db=$conn->mydb; #选择mydb数据库
$collection=$db->myTable; #选择集合(选择’表’)
//$collection=$db->selectCollection(myTable); #第二种写法
//$collection=$conn->mydb->myTable; #更简洁的写法
3,插入数据记录
$array=array(‘column_name’=>’col’.rand(100,999),’column_exp’=>’xiaocai’); $result=$collection->insert($array); #简单插入 echo “新记录ID:”.$array['_id']; #MongoDB会返回一个记录标识 var_dump($result); #返回:bool(true) //**向集合中安全插入数据,返回插入状态(数组). **/ $array=array(‘column_name’=>’col’.rand(100,999),’column_exp’=>’xiaocai2′); $result=$collection->insert($array,true); #用于等待MongoDB完成操作,以便确定是否成功.(当有大量记录插入时使用该参数会比较有用) echo “新记录ID:”.$array['_id']; #MongoDB会返回一个记录标识 var_dump($result); #返回:array(3) { ["err"]=> NULL ["n"]=> int(0) ["ok"]=> float(1) } |
4,更新数据记录
//** 修改更新 传统更新 **/ $where=array(‘column_name’=>’col123′); $newdata=array(‘column_exp’=>’GGGGGGG’,'column_fid’=>444); $result=$collection->update($where,array(‘$set’=>$newdata)); #$set:让某节点等于给定值,类似的还有$pull $pullAll $pop $inc,在后面慢慢说明用法 /* * 结果: * 原数据 * {“_id”:ObjectId(“4d635ba2d549a02801000003″),”column_name”:”col123″,”column_exp”:”xiaocai”} * 被替换成了 * {“_id”:ObjectId(“4d635ba2d549a02801000003″),”column_name”:”col123″,”column_exp”:”GGGGGGG”,”column_fid”:444} */ //** 替换更新 ,覆盖原记录**/ $where=array(‘column_name’=>’col709′); $newdata=array(‘column_exp’=>’HHHHHHHHH’,'column_fid’=>123); $result=$collection->update($where,$newdata); /* * 结果: * 原数据 * {“_id”:ObjectId(“4d635ba2d549a02801000003″),”column_name”:”col709″,”column_exp”:”xiaocai”} * 被替换成了 * {“_id”:ObjectId(“4d635ba2d549a02801000003″),”column_exp”:”HHHHHHHHH”,”column_fid”:123} */ //** 批量更新 **/ $where=array(‘column_name’=>’col’); $newdata=array(‘column_exp’=>’multiple’,’91u’=>684435); $result=$collection->update($where,array(‘$set’=>$newdata),array(‘multiple’=>true)); /** * 所有’column_name’='col’都被修改 */ //** 自动累加 **/ $where=array(’91u’=>684435); $newdata=array(‘column_exp’=>’edit’); $result=$collection->update($where,array(‘$set’=>$newdata,’$inc’=>array(’91u’=>-5))); /** * 更新91u=684435的数据,并且91u自减5 */ |
PHP操作MongoDB 数据库总结记录
5,删除记录操作
//** 删除节点 **/ $where=array(‘column_name’=>’col685′); $result=$collection->update($where,array(‘$unset’=>’column_exp’)); /** * 删除节点column_exp */ /* * * * 完整格式:update(array $criteria, array $newobj [, array $options = array() ] ) * 注意:1.注意区分替换更新与修改更新 * 2.注意区分数据类型如 array(’91u’=>’684435′)与array(’91u’=>684435) * * */ //** 清空表 **/ $collection->remove(); #清空集合 //** 删除指定MongoId **/ $id = new MongoId(“4d638ea1d549a02801000011″); $collection->remove(array(‘_id’=>(object)$id)); |
6,查询数据记录
//** 统计表记录数 **/ echo ‘count:’.$collection->count().”<br>”; #全部 echo ‘count:’.$collection->count(array(‘type’=>’user’)).”<br>”; #统计‘type’ 为 user 的记录 echo ‘count:’.$collection->count(array(‘age’=>array(‘$gt’=>50,’$lte’=>74))).”<br>”; #统计大于50小于等于74 echo ‘count:’.$collection->find()->limit(5)->skip(0)->count(true).”<br>”; #获得实际返回的结果数 /** * 注:$gt为大于、$gte为大于等于、$lt为小于、$lte为小于等于、$ne为不等于、$exists不存在 */ //** 获取表中所有记录 **/ $cursor = $collection->find()->snapshot(); foreach ($cursor as $id => $value) { echo “$id: “; var_dump($value); echo “<br>”; } /** * 注意: * 在我们做了find()操作,获得$cursor游标之后,这个游标还是动态的. * 换句话说,在我find()之后,到我的游标循环完成这段时间,如果再有符合条件的记录被插入到collection,那么这些记录也会被$cursor 获得. * 如果你想在获得$cursor之后的结果集不变化,需要这样做: * $cursor = $collection->find(); */ //** 查询一条数据 **/ $cursor = $collection->findOne(); /** * 注意:findOne()获得结果集后不能使用snapshot(),fields()等函数; */ //** 设置显示字段 age,type 列不显示 **/ $cursor = $collection->find()->fields(array(“age”=>false,”type”=>false)); $cursor = $collection->find()->fields(array(“user”=>true)); //只显示user 列 /** * 我这样写会出错:$cursor->fields(array(“age”=>true,”type”=>false)); */ //** 设置条件 (存在type,age节点) and age!=0 and age<50 **/ $where=array(‘type’=>array(‘$exists’=>true),’age’=>array(‘$ne’=>0,’$lt’=>50,’$exists’=>true)); $cursor = $collection->find($where); //** 分页获取结果集 **/ $cursor = $collection->find()->limit(5)->skip(0); //** 排序 **/ $cursor = $collection->find()->sort(array(‘age’=>-1,’type’=>1)); ##1表示降序 -1表示升序,参数的先后影响排序顺序 //** 索引 **/ $collection->ensureIndex(array(‘age’ => 1,’type’=>-1)); #1表示降序 -1表示升序 $collection->ensureIndex(array(‘age’ => 1,’type’=>-1),array(‘background’=>true)); #索引的创建放在后台运行(默认是同步运行) $collection->ensureIndex(array(‘age’ => 1,’type’=>-1),array(‘unique’=>true)); #该索引是唯一的 /** * ensureIndex (array(),array(‘name’=>’索引名称’,'background’=true,’unique’=true)) * 详见:http://www.php.net/manual/en/mongocollection.ensureindex.php */ //** 取得查询结果 **/ $cursor = $collection->find(); $array=array(); foreach ($cursor as $id => $value) { $array[]=$value; } |
7.关闭链接
$conn->close(); #关闭连接
8,常见函数使用
a.$inc 如果记录的该节点存在,让该节点的数值加N;如果该节点不存在,让该节点值等于N
设结构记录结构为 array(’a’=>1,’b’=>’t’),想让a加5,那么:
$coll->update(array(’b’=>’t’),array(’$inc’=>array(’a’=>5)))
b.$set 让某节点等于给定值
设结构记录结构为 array(’a’=>1,’b’=>’t’),b为加f,那么:
$coll->update(array(’a’=>1),array(’$set’=>array(’b’=>’f’)))
c.$unset 删除某节点
设记录结构为 array(’a’=>1,’b’=>’t’),想删除b节点,那么:
$coll->update(array(’a’=>1),array(’$unset’=>’b’))
d.$push 如果对应节点是个数组,就附加一个新的值上去;不存在,就创建这个数组,并附加一个值在这个数组上;如果该节点不是数组,返回错误。
设记录结构为array(’a’=>array(0=>’haha’),’b’=& gt;1),想附加新数据到节点a,那么:
$coll->update(array(’b’=>1),array(’$push’=>array(’a’=>’wow’)))
这样,该记录就会成为:array(’a’=>array(0=>’haha’,1=>’wow’),’b’=>1)
e.$pushAll 与$push类似,只是会一次附加多个数值到某节点
f.$addToSet 如果该阶段的数组中没有某值,就添加之
设记录结构为array(’a’=>array(0=& gt;’haha’),’b’=>1),如果想附加新的数据到该节点a,那么:
$coll->update(array(’b’=>1),array(’$addToSet’=>array(’a’=>’wow’)))
如果在a节点中已经有了wow,那么就不会再添加新的,如果没有,就会为该节点添加新的item——wow。
g.$pop 设该记录为array(’a’=>array(0=>’haha’,1=>’wow’),’b’=>1)
删除某数组节点的最后一个元素:
$coll->update(array(’b’=>1),array(’$pop=>array(’a’=>1)))
删除某数组阶段的第一个元素
$coll->update(array(’b’=>1),array(’$pop=>array(’a’=>-1)))
h.$pull 如果该节点是个数组,那么删除其值为value的子项,如果不是数组,会返回一个错误。
设该记录为 array(’a’=>array(0=>’haha’,1=>’wow’),’b’=>1),想要删除a中value为 haha的子项:
$coll->update(array(’b’=>1),array(’$pull=>array(’a’=>’haha’)))
结果为: array(’a’=>array(0=>’wow’),’b’=>1)
i.$pullAll 与$pull类似,只是可以删除一组符合条件的记录。
1、什么是存储过程。存储过程是
数据库服务器端的一段程序,它有两种类型。一种类似于SELECT查询,用于检索数据,检索到的数据能够以数据集的形式返回给客户。另一种类似于INSERT或DELETE查询,它不返回数据,只是执行一个动作。有的服务器允许同一个存储过程既可以返回数据又可以执行动作。
2、什么时候需要用存储过程
如果服务器定义了存储过程,应当根据需要决定是否要用存储过程。存储过程通常是一些经常要执行的任务,这些任务往往是针对大量的记录而进行的。在服务器上执行存储过程,可以改善应用程序的性能。这是因为:
.服务器往往具有强大的计算能力和速度。
.避免把大量的数据下载到客户端,减少网络上的传输量。
例如,假设一个应用程序需要计算一个数据,这个数据需要涉及到许多记录。如果不使用存储过程的话,把这些数据下载到客户端,导致网络上的流量剧增。
不仅如此,客户端可能是一台老掉牙的计算机,它的运算速度很慢。而改用存储过程后,服务器会很快地把数据计算出来,并且只需传递一个数据给客户端,其效率之高是非常明显的。
3、存储过程的参数
要执行服务器上的存储过程,往往要传递一些参数。这些参数分为四种类型:
第一种称为输入参数,由客户程序向存储过程传递值。
第二种称为输出参数,由存储过程向客户程序返回结果。
第三种称为输入/输出参数,既可以由客户程序向存储过程传递值,也可以由存储过程向客户程序返回结果。
第四种称为状态参数,由存储过程向客户程序返回错误信息。
要说明的是,并不是所有的服务器都支持上述四种类型的参数,例如,InterBase就不支持状态参数。
4、oracle 存储过程的基本语法
1.基本结构
CREATE OR REPLACEPROCEDURE 存储过程名字
(
参数1 IN NUMBER,
参数2 IN NUMBER
) IS
变量1 INTEGER :=0;
变量2 DATE;
BEGIN
END 存储过程名字
2.SELECT INTO STATEMENT
将select查询的结果存入到变量中,可以同时将多个列存储多个变量中,必须有一条
记录,否则抛出异常(如果没有记录抛出NO_DATA_FOUND)
例子:
BEGIN
SELECT col1,col2 into 变量1,变量2 FROM typestruct where xxx;
EXCEPTION
WHEN NO_DATA_FOUND THEN
xxxx;
END;
一:无返回值的存储过程
存储过程为:
CREATE OR REPLACE PROCEDURE TESTA(PARA1 IN VARCHAR2,PARA2 IN VARCHAR2) AS
BEGIN
INSERT INTO HYQ.B_ID (I_ID,I_NAME) S (PARA1, PARA2);
END TESTA;
然后呢,在java里调用时就用下面的代码:
package com.hyq.src; import java.sql.*; import java.sql.ResultSet; public class TestProcedureOne { public TestProcedureOne() { } public static void main(String[] args ){ String driver = "oracle.jdbc.driver.OracleDriver"; String strUrl = "jdbc:oracle:thin:@127.0.0.1:1521: hyq "; Statement stmt = null; ResultSet rs = null; Connection conn = null; CallableStatement cstmt = null; try { Class.forName(driver); conn = DriverManager.getConnection(strUrl, " hyq ", " hyq "); CallableStatement proc = null; proc = conn.prepareCall("{ call HYQ.TESTA(?,?) }"); proc.setString(1, "100"); proc.setString(2, "TestOne"); proc.execute(); } catch (SQLException ex2) { ex2.printStackTrace(); } catch (Exception ex2) { ex2.printStackTrace(); } finally{ try { if(rs != null){ rs.close(); if(stmt!=null){ stmt.close(); } if(conn!=null){ conn.close(); } } } catch (SQLException ex1) { } } } } |
当然了,这就先要求要建张表TESTTB,里面两个字段(I_ID,I_NAME)。
二:有返回值的存储过程(非列表)
存储过程为:
CREATE OR REPLACE PROCEDURE TESTB(PARA1 IN VARCHAR2,PARA2 OUT VARCHAR2) AS
BEGIN
SELECT INTO PARA2 FROM TESTTB WHERE I_ID= PARA1;
END TESTB;
在java里调用时就用下面的代码:
package com.hyq.src; public class TestProcedureTWO { public TestProcedureTWO() { } public static void main(String[] args ){ String driver = "oracle.jdbc.driver.OracleDriver"; String strUrl = "jdbc:oracle:thin:@127.0.0.1:1521:hyq"; Statement stmt = null; ResultSet rs = null; Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(strUrl, " hyq ", " hyq "); CallableStatement proc = null; proc = conn.prepareCall("{ call HYQ.TESTB(?,?) }"); proc.setString(1, "100"); proc.registerOutParameter(2, Types.VARCHAR); proc.execute(); String testPrint = proc.getString(2); System.out.println("=testPrint=is="+testPrint); } catch (SQLException ex2) { ex2.printStackTrace(); } catch (Exception ex2) { ex2.printStackTrace(); } finally{ try { if(rs != null){ rs.close(); if(stmt!=null){ stmt.close(); } if(conn!=null){ conn.close(); } } } catch (SQLException ex1) { } } } } } |
注意,这里的proc.getString(2)中的数值2并非任意的,而是和存储过程中的out列对应的,如果out是在第一个位置,那就是proc.getString(1),如果是第三个位置,就是proc.getString(3),当然也可以同时有多个返回值,那就是再多加几个out参数了。
三:返回列表
由于oracle存储过程没有返回值,它的所有返回值都是通过out参数来替代的,列表同样也不例外,但由于是集合,所以不能用一般的参数,必须要用pagkage了.所以要分两部分,
1, 建一个程序包。如下:
CREATE OR REPLACE PACKAGE TESTPACKAGE AS
TYPE Test_CURSOR IS REF CURSOR;
end TESTPACKAGE;
2,建立存储过程,存储过程为:
CREATE OR REPLACE PROCEDURE TESTC(p_CURSOR out TESTPACKAGE.Test_CURSOR) IS
BEGIN
OPEN p_CURSOR FOR SELECT * FROM HYQ.TESTTB;
END TESTC;
可以看到,它是把游标(可以理解为一个指针),作为一个out 参数来返回值的。
在java里调用时就用下面的代码:
package com.hyq.src; import java.sql.*; import java.io.OutputStream; import java.io.Writer; import java.sql.PreparedStatement; import java.sql.ResultSet; import oracle.jdbc.driver.*; public class TestProcedureTHREE { public TestProcedureTHREE() { } public static void main(String[] args ){ String driver = "oracle.jdbc.driver.OracleDriver"; String strUrl = "jdbc:oracle:thin:@127.0.0.1:1521:hyq"; Statement stmt = null; ResultSet rs = null; Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(strUrl, "hyq", "hyq"); CallableStatement proc = null; proc = conn.prepareCall("{ call hyq.testc(?) }"); proc.registerOutParameter(1,oracle.jdbc.OracleTypes.CURSOR); proc.execute(); rs = (ResultSet)proc.getObject(1); while(rs.next()) { System.out.println("<tr><td>" + rs.getString(1) + "</td><td>"+rs.getString(2)+"</td></tr>"); } } catch (SQLException ex2) { ex2.printStackTrace(); } catch (Exception ex2) { ex2.printStackTrace(); } finally{ try { if(rs != null){ rs.close(); if(stmt!=null){ stmt.close(); } if(conn!=null){ conn.close(); } } } catch (SQLException ex1) { } } } } |
在这里要注意,在执行前一定要先把oracle的驱动包放到class路径里,否则会报错。
Hibernate中对动态查询参数绑定提供了丰富的支持,那么什么是查询参数动态绑定呢?其实如果我们熟悉传统JDBC编程的话,我们就不难理解查询参数动态绑定,如下代码传统JDBC的参数绑定:
PrepareStatement pre=connection.prepare(“select * from User where user.name=?”);
pre.setString(1,”zhaoxin”);
ResultSet rs=pre.executeQuery();
在Hibernate中也提供了类似这种的查询参数绑定功能,而且在Hibernate中对这个功能还提供了比传统JDBC操作丰富的多的特性,在Hibernate中共存在4种参数绑定的方式,下面我们将分别介绍:
A、 按参数名称绑定:
在HQL语句中定义命名参数要用”:”开头,形式如下:
Query query=session.createQuery(“from User user where user.name=:customername and user:customerage=:age ”);
query.setString(“customername”,name);
query.setInteger(“customerage”,age);
上面代码中用:customername和:customerage分别定义了命名参数customername和customerage,然后用Query接口的setXXX()方法设定名参数值,setXXX()方法包含两个参数,分别是命名参数名称和命名参数实际值。
B、 按参数位置邦定:
在HQL查询语句中用”?”来定义参数位置,形式如下:
Query query=session.createQuery(“from User user where user.name=? and user.age =? ”);
query.setString(0,name);
query.setInteger(1,age);
同样使用setXXX()方法设定绑定参数,只不过这时setXXX()方法的第一个参数代表邦定参数在HQL语句中出现的位置编号(由0开始编号),第二个参数仍然代表参数实际值。
注:在实际开发中,提倡使用按名称邦定命名参数,因为这不但可以提供非常好的程序可读性,而且也提高了程序的易维护性,因为当查询参数的位置发生改变时,按名称邦定名参数的方式中是不需要调整程序代码的。
C、 setParameter()方法:
在Hibernate的HQL查询中可以通过setParameter()方法邦定任意类型的参数,如下代码:
String hql=”from User user where user.name=:customername ”;
Query query=session.createQuery(hql);
query.setParameter(“customername”,name,Hibernate.STRING);
如上面代码所示,setParameter()方法包含三个参数,分别是命名参数名称,命名参数实际值,以及命名参数映射类型。对于某些参数类型setParameter()方法可以更具参数值的
Java类型,猜测出对应的映射类型,因此这时不需要显示写出映射类型,像上面的例子,可以直接这样写:
query.setParameter(“customername”,name);但是对于一些类型就必须写明映射类型,比如java.util.Date类型,因为它会对应Hibernate的多种映射类型,比如Hibernate.DATA或者Hibernate.TIMESTAMP。
D、 setProperties()方法:
在Hibernate中可以使用setProperties()方法,将命名参数与一个对象的属性值绑定在一起,如下程序代码:
Customer customer=new Customer();
customer.setName(“pansl”);
customer.setAge(80);
Query query=session.createQuery(“from Customer c where c.name=:name and c.age=:age ”);
query.setProperties(customer);
setProperties()方法会自动将customer对象实例的属性值匹配到命名参数上,但是要求命名参数名称必须要与实体对象相应的属性同名。
这里还有一个特殊的setEntity()方法,它会把命名参数与一个持久化对象相关联,如下面代码所示:
Customer customer=(Customer)session.load(Customer.class,”1”);
Query query=session.createQuery(“from Order order where order.customer=:customer ”);
query. setEntity(“customer”,customer);
List list=query.list();
上面的代码会生成类似如下的SQL语句:
Select * from order where customer_ID=’1’;
以下实现复制步骤(以快照复制为例)
1.建立一个 WINDOWS 用户,设置为管理员权限,并设置密码,作为发布快照文件的有效访问用户。
2.在SQL SERVER下实现发布服务器和订阅服务器的通信正常(即可以互访)。打开1433端口,在防火墙中设特例
3.在发布服务器上建立一个共享目录,作为发布快照文件的存放目录。例如:在D盘根目录下建文件夹名为SqlCopy
4.设置SQL 代理(发布服务器和订阅服务器均设置)本篇
文章发表于www.xker.com(小新技术网)
打开服务(控制面板---管理工具---服务)
---右击SQLSERVER AGENT---属性---登录---选择“此帐户“
---输入或选择第一步中创建的WINDOWS 用户
---“密码“中输入该用户密码
5.设置SQL SERVER 身份验证,解决连接时的权限问题(发布、订阅服务器均设置)
步骤为:对象资源管理器----右击SQL实例-----属性----安全性----服务器身份验证------选“SQL Server和WINDOWS“,然后点确定
6.开启SQL Server 2005的网络协议TCP/IP和管道命名协议并重启网络服务。
7.在SQL Server中创建步骤1中对应的系统用户登陆名,作为发布
数据库的拥有者(设置为dbo_owner和public)。
8.以系统超级用户sa登陆SQL Server建立数据库和表。
9.发布服务器和订阅服务器互相注册
步骤如下:视图----单击以注册服务器----右键数据库引擎----新建服务器注册-----填写要注册的远程服务器名称------身份验证选“SQL Server验证“-----用户名(sa) 密码------创建组(也可不建)-----完成。
10.对于只能用IP,不能用计算机名的,为其注册服务器别名
二、开始:
发布服务器配置(在发布服务器上配置发布和订阅)
1. 选择 复制 节点
2. 右键本地发布 ----下一步---------系统弹出对话框看提示----直到“指定快照文件夹“
----在“快照文件夹“中输入准备工作中创建的目录(指向步骤3所建的共享文件夹)------选择发布数据库-------选择发布类型-------选择订阅服务器类型-------选择要发布的对象------设置快照代理-------填写发布名称。本篇文章发表于www.xker.com(小新技术网)
3. 右键本地订阅--------选择发布服务器-------选择订阅方式(如果是在服务器方订阅的话选择推送订阅反之
选择请求订阅)-------填加订阅服务器--------选择代理计划(一般选择连续运行)---------其余选择默认项。
至此, SQL SERVER 2005 同步复制就完成了。使用复制技术,用户可以将一份客户端的数据发布到多台服务器上,从而使不同的服务器用户都可以在权限的许可的范围内共享这份数据。复制技术可以确保分布在不同地点的数据自动同步更新,从而保证数据的一致性,就无需编程实现客户端和服务器端数据同步了!大大提高了工作效率!SQL Server 2000订阅与发布的具体操作
同步过程
一、准备工作,如果完成则可跳过。
1、内网DB服务器作为发布服务器,外网DB服务器作为订阅服务器。
发布服务器和订阅服务器上分别创建Windows用户jl,密码jl,隶属于administrators,注意要保持一致。
2、发布服务器上创建一个共享目录,作为发布快照文件的存放目录。例如:在D盘根目录下建文件夹名为SqlCopy,设置用户jl,权限为完全控制。
3、确定发布服务器和订阅服务器的数据库autoweb保持一致。
4、在发布服务器和订阅服务器的SQL Server中创建用户登陆名jl,作为发布数据库autoweb的拥有者(设置为dbo_owner和public)。用户名和密码都一致。
5、打开服务(控制面板---管理工具---服务)
---右击SQLSERVER AGENT---属性---登录---选择“此帐户”
---输入或选择第一步中创建的WINDOWS 用户jl,
---“密码“中输入该用户密码jl
6、开启SQL Server 2000的网络协议TCP/IP和管道命名协议并重启网络服务。
7、设置SQL SERVER 身份验证,解决连接时的权限问题(发布、订阅服务器均设置)
步骤为:对象资源管理器----右击SQL实例-----属性----安全性----服务器身份验证------选“SQL Server和WINDOWS“,然后点确定。
8、发布服务器和订阅服务器互相注册
步骤如下:视图----单击以注册服务器----右键数据库引擎----新建服务器注册-----填写要注册的远程服务器名称------身份验证选“SQL Server验证“-----用户名(sa) 密码------创建组(也可不建)-----完成。对于只能用IP,不能用计算机名的,为其注册服务器别名
二、发布和订阅
如下工作都在发布服务器上配置,包括发布和订阅。
快照发布和订阅
1、 选择 复制 节点,右键本地发布 ----下一步---------系统弹出对话框看提示----直到“指定快照文件夹”----在“快照文件夹“中输入准备工作中创建的目录(指向步骤3所建的共享文件夹)------选择发布数据库-------选择发布类型
下一步―――选择要发布的数据库autoweb中的表,将b(B)开头的表去掉,V开头的表去掉,c_开头的表去掉,t_开头的表去掉,剩下的表作为快照发布到订阅服务器上(单向传输)
根据情况决定执行发布的间隔时间,如图每天每20分钟执行一次。
下一步快照代理安全性,设置如图,连接到发布服务器用户jl,密码jl.
-------填写发布名称。
2、 选择 复制 节点,右键本地订阅,选择发布服务器-------选择订阅方式(选择推送订阅))-------填加订阅服务器--------选择代理计划(一般选择连续运行)---------其余选择默认项。
至此完成快照发布和订阅。
合并发布和订阅
1、选择如下三个表作为合并发布的对象,用于双向通讯
根据情况决定执行发布的间隔时间,如图每天每20分钟执行一次。
2、 选择 复制 节点,右键本地订阅,选择发布服务器-------选择订阅方式(选择推送订阅))-------填加订阅服务器--------选择代理计划(一般选择连续运行)---------其余选择默认项。
至此完成合并发布和订阅
-------------------------------------------------------------------
主要是要注意权限的问题,一般做发布/订阅,建议你做如下准备工作:
1.发布服务器,订阅服务器都创建一个同名的windows用户,并设置相同的密码,做为发布快照文件夹的有效访问用户
我的电脑
--控制面板
--管理工具
--计算机管理
--用户和组
--右键用户
--新建用户
--建立一个隶属于administrator组的登陆windows的用户
2.在发布服务器上,新建一个共享目录,做为发布的快照文件的存放目录,操作:
我的电脑--D: 新建一个目录,名为: PUB
--右键这个新建的目录
--属性--共享
--选择"共享该文件夹"
--通过"权限"按纽来设置具体的用户权限,保证第一步中创建的用户具有对该文件夹的所有权限
--确定
3.设置SQL代理(SQLSERVERAGENT)服务的启动用户(发布/订阅服务器均做此设置)
开始--程序--管理工具--服务
--右键SQLSERVERAGENT
--属性--登陆--选择"此账户"
--输入或者选择第一步中创建的windows登录用户名
--"密码"中输入该用户的密码
4.设置SQL Server身份验证模式,解决连接时的权限问题(发布/订阅服务器均做此设置)
企业管理器
--右键SQL实例--属性
--安全性--身份验证
--选择"SQL Server 和 Windows"
--确定
5.在发布服务器和订阅服务器上互相注册
企业管理器
--右键SQL Server组
--新建SQL Server注册...
--下一步--可用的服务器中,输入你要注册的远程服务器名--添加
--下一步--连接使用,选择第二个"SQL Server身份验证"
--下一步--输入用户名和密码
--下一步--选择SQL Server组,也可以创建一个新组
--下一步--完成
6.对于只能用IP,不能用计算机名的,为其注册服务器别名
(在连接端配置,比如,在订阅服务器上配置的话,服务器名称中输入的是发布服务器的IP)
开始--程序--Microsoft SQL Server--客户端网络实用工具
--别名--添加
--网络库选择"tcp/ip"--服务器别名输入SQL服务器名
--连接参数--服务器名称中输入SQL服务器ip地址
--如果你修改了SQL的端口,取消选择"动态决定端口",并输入对应的端口号