2006年11月29日
#
一、POI概述
Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
结构:
HSSF - 提供读写Microsoft Excel格式档案的功能。
XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
HWPF - 提供读写Microsoft Word格式档案的功能。
HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF - 提供读写Microsoft Visio格式档案的功能。
使用必须引入依赖
org.apache.poi
poi
3.17
注:3.17版本是支持jdk6的最后版本
二、HSSF概况
HSSF 是Horrible SpreadSheet Format的缩写,通过HSSF,你可以用纯Java代码来读取、写入、修改Excel文件。HSSF 为读取操作提供了两类API:usermodel和eventusermodel,即“用户模型”和“事件-用户模型”。
三、 POI EXCEL文档结构类
HSSFWorkbook excel文档对象
HSSFSheet excel的sheet
HSSFRow excel的行
HSSFCell excel的单元格
HSSFFont excel字体
HSSFName 名称
HSSFDataFormat 日期格式
HSSFHeader sheet头
HSSFFooter sheet尾
HSSFCellStyle cell样式
HSSFDateUtil 日期
HSSFPrintSetup 打印
HSSFErrorConstants 错误信息表
四、EXCEL的读写操作
1、读取“区域数据.xls”并储存于list集合中,“区域数据.xls”如下图
public List
importXLS(){
ArrayList
list = new ArrayList<>();
try {
//1、获取文件输入流
InputStream inputStream = new FileInputStream("/Users/Shared/区域数据.xls");
//2、获取Excel工作簿对象
HSSFWorkbook workbook = new HSSFWorkbook(inputStream);
//3、得到Excel工作表对象
HSSFSheet sheetAt = workbook.getSheetAt(0);
//4、循环读取表格数据
for (Row row : sheetAt) {
//首行(即表头)不读取
if (row.getRowNum() == 0) {
continue;
}
//读取当前行中单元格数据,索引从0开始
String areaNum = row.getCell(0).getStringCellValue();
String province = row.getCell(1).getStringCellValue();
String city = row.getCell(2).getStringCellValue();
String district = row.getCell(3).getStringCellValue();
String postcode = row.getCell(4).getStringCellValue();
Area area = new Area();
area.setCity(city);
area.setDistrict(district);
area.setProvince(province);
area.setPostCode(postcode);
list.add(area);
}
//5、关闭流
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
2、导出数据到“区域数据.xls”文件中,页面数据如下图:
public void exportExcel() throws IOException {
Page
page = areaService.pageQuery(null);
List
list = page.getContent();
//1.在内存中创建一个excel文件
HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
//2.创建工作簿
HSSFSheet sheet = hssfWorkbook.createSheet();
//3.创建标题行
HSSFRow titlerRow = sheet.createRow(0);
titlerRow.createCell(0).setCellValue("省");
titlerRow.createCell(1).setCellValue("市");
titlerRow.createCell(2).setCellValue("区");
titlerRow.createCell(3).setCellValue("邮编");
titlerRow.createCell(4).setCellValue("简码");
titlerRow.createCell(5).setCellValue("城市编码");
//4.遍历数据,创建数据行
for (Area area : list) {
//获取最后一行的行号
int lastRowNum = sheet.getLastRowNum();
HSSFRow dataRow = sheet.createRow(lastRowNum + 1);
dataRow.createCell(0).setCellValue(area.getProvince());
dataRow.createCell(1).setCellValue(area.getCity());
dataRow.createCell(2).setCellValue(area.getDistrict());
dataRow.createCell(3).setCellValue(area.getPostcode());
dataRow.createCell(4).setCellValue(area.getShortcode());
dataRow.createCell(5).setCellValue(area.getCitycode());
}
//5.创建文件名
String fileName = "区域数据统计.xls";
//6.获取输出流对象
HttpServletResponse response = ServletActionContext.getResponse();
ServletOutputStream outputStream = response.getOutputStream();
//7.获取mimeType
ServletContext servletContext = ServletActionContext.getServletContext();
String mimeType = servletContext.getMimeType(fileName);
//8.获取浏览器信息,对文件名进行重新编码
HttpServletRequest request = ServletActionContext.getRequest();
fileName = FileUtils.filenameEncoding(fileName, request);
//9.设置信息头
response.setContentType(mimeType);
response.setHeader("Content-Disposition","attachment;filename="+fileName);
//10.写出文件,关闭流
hssfWorkbook.write(outputStream);
hssfWorkbook.close();
}
工具类
public class FileUtils {
public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {
String agent = request.getHeader("User-Agent"); //获取浏览器
if (agent.contains("Firefox")) {
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"
+ base64Encoder.encode(filename.getBytes("utf-8"))
+ "?=";
} else if(agent.contains("MSIE")) {
filename = URLEncoder.encode(filename, "utf-8");
} else if(agent.contains ("Safari")) {
filename = new String (filename.getBytes ("utf-8"),"ISO8859-1");
} else {
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}
写出xls文件:
五、 EXCEL常用操作方法
1、 得到Excel常用对象
POIFSFileSystem fs=newPOIFSFileSystem(new FileInputStream("d:/test.xls"));
//得到Excel工作簿对象
HSSFWorkbook wb = new HSSFWorkbook(fs);
//得到Excel工作表对象
HSSFSheet sheet = wb.getSheetAt(0);
//得到Excel工作表的行
HSSFRow row = sheet.getRow(i);
//得到Excel工作表指定行的单元格
HSSFCell cell = row.getCell((short) j);
cellStyle = cell.getCellStyle();//得到单元格样式
2、建立Excel常用对象
HSSFWorkbook wb = new HSSFWorkbook();//创建Excel工作簿对象
HSSFSheet sheet = wb.createSheet("new sheet");//创建Excel工作表对象
HSSFRow row = sheet.createRow((short)0); //创建Excel工作表的行
cellStyle = wb.createCellStyle();//创建单元格样式
row.createCell((short)0).setCellStyle(cellStyle); //创建Excel工作表指定行的单元格
row.createCell((short)0).setCellValue(1); //设置Excel工作表的值
3、设置sheet名称和单元格内容
wb.setSheetName(1, "第一张工作表",HSSFCell.ENCODING_UTF_16);
cell.setEncoding((short) 1);
cell.setCellValue("单元格内容");
4、取得sheet的数目
wb.getNumberOfSheets()
5、 根据index取得sheet对象
HSSFSheet sheet = wb.getSheetAt(0);
6、取得有效的行数
int rowcount = sheet.getLastRowNum();
7、取得一行的有效单元格个数
row.getLastCellNum();
8、单元格值类型读写
cell.setCellType(HSSFCell.CELL_TYPE_STRING); //设置单元格为STRING类型
cell.getNumericCellValue();//读取为数值类型的单元格内容
9、设置列宽、行高
sheet.setColumnWidth((short)column,(short)width);
row.setHeight((short)height);
10、添加区域,合并单元格
Region region = new Region((short)rowFrom,(short)columnFrom,(short)rowTo
,(short)columnTo);//合并从第rowFrom行columnFrom列
sheet.addMergedRegion(region);// 到rowTo行columnTo的区域
//得到所有区域
sheet.getNumMergedRegions()
11、保存Excel文件
FileOutputStream fileOut = new FileOutputStream(path);
wb.write(fileOut);
12、根据单元格不同属性返回字符串数值
public String getCellStringValue(HSSFCell cell) {
String cellValue = "";
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_STRING://字符串类型
cellValue = cell.getStringCellValue();
if(cellValue.trim().equals("")||cellValue.trim().length()<=0)
cellValue=" ";
break;
case HSSFCell.CELL_TYPE_NUMERIC: //数值类型
cellValue = String.valueOf(cell.getNumericCellValue());
break;
case HSSFCell.CELL_TYPE_FORMULA: //公式
cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
cellValue = String.valueOf(cell.getNumericCellValue());
break;
case HSSFCell.CELL_TYPE_BLANK:
cellValue=" ";
break;
case HSSFCell.CELL_TYPE_BOOLEAN:
break;
case HSSFCell.CELL_TYPE_ERROR:
break;
default:
break;
}
return cellValue;
}
13、常用单元格边框格式
HSSFCellStyle style = wb.createCellStyle();
style.setBorderBottom(HSSFCellStyle.BORDER_DOTTED);//下边框
style.setBorderLeft(HSSFCellStyle.BORDER_DOTTED);//左边框
style.setBorderRight(HSSFCellStyle.BORDER_THIN);//右边框
style.setBorderTop(HSSFCellStyle.BORDER_THIN);//上边框
14、设置字体和内容位置
HSSFFont f = wb.createFont();
f.setFontHeightInPoints((short) 11);//字号
f.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);//加粗
style.setFont(f);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);//左右居中
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//上下居中
style.setRotation(short rotation);//单元格内容的旋转的角度
HSSFDataFormat df = wb.createDataFormat();
style1.setDataFormat(df.getFormat("0.00%"));//设置单元格数据格式
cell.setCellFormula(string);//给单元格设公式
style.setRotation(short rotation);//单元格内容的旋转的角度
15、插入图片
//先把读进来的图片放到一个ByteArrayOutputStream中,以便产生ByteArray
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
BufferedImage bufferImg = ImageIO.read(new File("ok.jpg"));
ImageIO.write(bufferImg,"jpg",byteArrayOut);
//读进一个excel模版
FileInputStream fos = new FileInputStream(filePathName+"/stencil.xlt");
fs = new POIFSFileSystem(fos);
//创建一个工作薄
HSSFWorkbook wb = new HSSFWorkbook(fs);
HSSFSheet sheet = wb.getSheetAt(0);
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
HSSFClientAnchor anchor = new HSSFClientAnchor(0,0,1023,255,(short) 0,0,(short)10,10);
patriarch.createPicture(anchor , wb.addPicture(byteArrayOut.toByteArray(),HSSFWorkbook.PICTURE_TYPE_JPEG));
16、调整工作表位置
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("format sheet");
HSSFPrintSetup ps = sheet.getPrintSetup();
sheet.setAutobreaks(true);
ps.setFitHeight((short)1);
ps.setFitWidth((short)1);
1、在学习从文件读取数据中,写了个示例代码,读取不在同一个目录的file.txt,运行后报这个Python OSError: [Errno 22] Invalid argument:错误:
(1)、首先,在F盘的python_stu中新增了一个file.txt,同时在F盘的python_stu文件目录底下新增一个file文件夹,里面有个file_reader.py来读取python_stu文件目录底下的file.txt,代码分别如下:
file.txt:
测试
测试2
测试3
file_reader.py:
with open('F:\python_stu\file.txt') as file_obj:
contents = file_obj.read();
print(contents.rstrip());
(2)、运行后报错:
(3)、出现这种错误的原因是由于读取不到这个文件,看Traceback报的错误,最后一行,很明显读取不到file.txt,前面的F:\\python_stu没错,后面的名称怎么变了,还是x0cile.txt。
(4)、解决办法,可修改上述第一行代码为:
with open('F:\python_stu/file.txt') as file_obj:
或者:
with open('F:/python_stu/file.txt') as file_obj:
或者:
with open('F://python_stu//file.txt') as file_obj:
又或者:
with open('F:\\python_stu\\file.txt') as file_obj:
还有一些我就不附上了,上面第一种方式不统一,最好不要用,用统一的方式,而且有时候还有注意一些转义字符,比如 \t,\n也会导致报错。
前面学习了使用命令hdfs haadmin -failover手动进行故障转移,在该模式下,即使现役NameNode已经失效,系统也不会自动从现役NameNode转移到待机NameNode,下面学习如何配置部署HA自动进行故障转移。自动故障转移为HDFS部署增加了两个新组件:ZooKeeper和ZKFailoverController(ZKFC)进程。ZooKeeper是维护少量协调数据,通知客户端这些数据的改变和监视客户端故障的高可用服务。HA的自动故障转移依赖于ZooKeeper的以下功能:
- 故障检测:集群中的每个NameNode在ZooKeeper中维护了一个持久会话,如果机器崩溃,ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。
- 现役NameNode选择:ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃,另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。
ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责:
- 健康监测:ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非健康的。
- ZooKeeper会话管理:当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。如果本地NameNode处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
- 基于ZooKeeper的选择:如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,它将为自己获取该锁。如果成功,则它已经赢得了选择,并负责运行故障转移进程以使它的本地NameNode为active。故障转移进城与前面描述的手动故障转移相似,首先如果必要保护之前的现役NameNode,然后本地NameNode转换为active状态。
在典型部署中,ZooKeeper守护进程运行在三个或者五个节点上,但由于ZooKeeper本身需要较少的资源,所以将ZooKeeper部署在与现役NameNode和待机NameNode相同的主机上,还可以将ZooKeeper部署到与YARN的ResourceManager相同的节点上。建议配置ZooKeeper将数据存储在与HDFS元数据不同的硬盘上以得到最好的性能和隔离性。在配置自动故障转移之前需要先停掉集群,目前在集群运行时还不可能将手动故障转移的安装转换为自动故障转移的安装。接下来看看如何配置HA的自动故障转移。首先在hdfs-site.xml中添加下面的参数,该参数的值默认为false:
- <property>
- <name>dfs.ha.automatic-failover.enabled</name>
- <value>true</value>
- </property>
在core-site.xml文件中添加下面的参数,该参数的值为ZooKeeper服务器的地址,ZKFC将使用该地址。
- <property>
- <name>ha.zookeeper.quorum</name> <value>zk1.example.com:2181,zk2.example.com:2181,zk3.example.com:2181</value>
- </property>
在HA或者HDFS联盟中,上面的两个参数还需要以NameServiceID为后缀,比如dfs.ha.automatic-failover.enabled.mycluster。除了上面的两个参数外,还有其它几个参数用于自动故障转移,比如ha.zookeeper.session-timeout.ms,但对于大多数安装来说都不是必须的。
在添加了上述的配置参数后,下一步就是在ZooKeeper中初始化要求的状态,可以在任一NameNode中运行下面的命令实现该目的,该命令将在ZooKeeper中创建znode:
在启用自动故障转移的集群中,start-dfs.sh脚本将在任何运行NameNode的主机上自动启动ZKFC守护进程,一旦ZKFC启动完毕,它们将自动选择一个NameNode为现役NameNode。如果手动管理集群中的服务,需要在每台运行NameNode的主机上手动启动ZKFC,命令为:
- hadoop-daemon.sh start zkfc
- hdfs zkfc
如果正在运行一个安全的集群,可能想确保存储在ZooKeeper中的信息也是安全的,这将阻止恶意的客户端修改ZooKeeper中的元数据或者潜在地触发一个错误的故障转移。为了保护ZooKeeper中的信息,首先在core-site.xml中添加下面的参数:
- <property>
- <name>ha.zookeeper.auth</name>
- <value>@/path/to/zk-auth.txt</value>
- </property>
- <property>
- <name>ha.zookeeper.acl</name>
- <value>@/path/to/zk-acl.txt</value>
- </property>
参数值中的@字符表示参数值保存在@后的硬盘文件中。第一个配置文件指定了ZooKeeper的认证列表,其格式与ZK CLI使用的相同,例如:digest:hdfs-zkfcs:mypassword,其中hdfs-zkfcs为ZooKeeper的用户名,mypassword为密码。其次使用下面的命令为该认证生成一个ZooKeeper访问控制列表:
- $ java -cp $ZK_HOME/lib/*:$ZK_HOME/zookeeper-3.4.2.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider hdfs-zkfcs:mypassword
- output: hdfs-zkfcs:mypassword->hdfs-zkfcs:P/OQvnYyU/nF/mGYvB/xurX8dYs=
拷贝->之后的字符串并添加digest:前缀,然后粘贴到zk-acls.txt中,例如:digest:hdfs-zkfcs:vlUvLnd8MlacsE80rDuu6ONESbM=:rwcda。要想使ACLs生效,需要再次运行zkfc –formatZK。最后可能像下面这样在ZK CLI中验证ACLs:
- [zk: localhost:2181(CONNECTED) 1] getAcl /hadoop-ha
- 'digest,'hdfs-zkfcs:vlUvLnd8MlacsE80rDuu6ONESbM=
- : cdrwa
在安装完成自动故障转移后,或许需要测试一下。首先定位现役NameNode,可以通过访问NameNode的web页面来确定哪个NameNode是active状态的。一旦确定了处于active状态的NameNode,就需要在该节点上制造点故障,比如使用命令kill -9 <pid of NN>模拟JVM崩溃,或重启主机或拔掉网线来模拟不同的中断。一旦触发了自动故障转移,另一个NameNode应该自动在几秒钟内变为active状态。检测到故障并触发故障转移由参数ha.zookeeper.session-timeout.ms控制,该参数为与core-site.xml中,默认为5秒。如果测试不成功,可能是配置问题,检查ZKFC和NameNode进程的日志以进一步诊断问题,通常错误都是很明显的。
理想情况下,我们应用对Yarn资源的请求应该立刻得到满足,但现实情况资源往往是有限的,特别是在一个很繁忙的集群,一个应用资源的请求经常需要等待一段时间才能的到相应的资源。在Yarn中,负责给应用分配资源的就是Scheduler。其实调度本身就是一个难题,很难找到一个完美的策略可以解决所有的应用场景。为此,Yarn提供了多种调度器和可配置的策略供我们选择。
一、调度器的选择
在Yarn中有三种调度器可以选择:FIFO Scheduler
,Capacity Scheduler
,FairS cheduler
。
FIFO Scheduler
把应用按提交的顺序排成一个队列,这是一个先进先出队列,在进行资源分配的时候,先给队列中最头上的应用进行分配资源,待最头上的应用需求满足后再给下一个分配,以此类推。
FIFO Scheduler
是最简单也是最容易理解的调度器,也不需要任何配置,但它并不适用于共享集群。大的应用可能会占用所有集群资源,这就导致其它应用被阻塞。在共享集群中,更适合采用Capacity Scheduler
或Fair Scheduler
,这两个调度器都允许大任务和小任务在提交的同时获得一定的系统资源。
下面“Yarn调度器对比图”展示了这几个调度器的区别,从图中可以看出,在FIFO 调度器中,小任务会被大任务阻塞。
而对于Capacity调度器,有一个专门的队列用来运行小任务,但是为小任务专门设置一个队列会预先占用一定的集群资源,这就导致大任务的执行时间会落后于使用FIFO调度器时的时间。
在Fair调度器中,我们不需要预先占用一定的系统资源,Fair调度器会为所有运行的job动态的调整系统资源。如下图所示,当第一个大job提交时,只有这一个job在运行,此时它获得了所有集群资源;当第二个小任务提交后,Fair调度器会分配一半资源给这个小任务,让这两个任务公平的共享集群资源。
需要注意的是,在下图Fair调度器中,从第二个任务提交到获得资源会有一定的延迟,因为它需要等待第一个任务释放占用的Container。小任务执行完成之后也会释放自己占用的资源,大任务又获得了全部的系统资源。最终的效果就是Fair调度器即得到了高的资源利用率又能保证小任务及时完成。
Yarn调度器对比图:
二、Capacity Scheduler(容器调度器)的配置
2.1 容器调度介绍
Capacity 调度器允许多个组织共享整个集群,每个组织可以获得集群的一部分计算能力。通过为每个组织分配专门的队列,然后再为每个队列分配一定的集群资源,这样整个集群就可以通过设置多个队列的方式给多个组织提供服务了。除此之外,队列内部又可以垂直划分,这样一个组织内部的多个成员就可以共享这个队列资源了,在一个队列内部,资源的调度是采用的是先进先出(FIFO)策略。
通过上面那幅图,我们已经知道一个job可能使用不了整个队列的资源。然而如果这个队列中运行多个job,如果这个队列的资源够用,那么就分配给这些job,如果这个队列的资源不够用了呢?其实Capacity调度器仍可能分配额外的资源给这个队列,这就是“弹性队列”(queue elasticity)的概念。
在正常的操作中,Capacity调度器不会强制释放Container,当一个队列资源不够用时,这个队列只能获得其它队列释放后的Container资源。当然,我们可以为队列设置一个最大资源使用量,以免这个队列过多的占用空闲资源,导致其它队列无法使用这些空闲资源,这就是”弹性队列”需要权衡的地方。
2.2 容器调度的配置
假设我们有如下层次的队列:
root ├── prod └── dev ├── eng └── science
下面是一个简单的Capacity调度器的配置文件,文件名为capacity-scheduler.xml
。在这个配置中,在root队列下面定义了两个子队列prod
和dev
,分别占40%和60%的容量。需要注意,一个队列的配置是通过属性yarn.sheduler.capacity.<queue-path>.<sub-property>
指定的,<queue-path>
代表的是队列的继承树,如root.prod
队列,<sub-property>
一般指capacity
和maximum-capacity
。
我们可以看到,dev
队列又被分成了eng
和science
两个相同容量的子队列。dev
的maximum-capacity
属性被设置成了75%,所以即使prod
队列完全空闲dev
也不会占用全部集群资源,也就是说,prod
队列仍有25%的可用资源用来应急。我们注意到,eng
和science
两个队列没有设置maximum-capacity
属性,也就是说eng
或science
队列中的job可能会用到整个dev
队列的所有资源(最多为集群的75%)。而类似的,prod
由于没有设置maximum-capacity属性,它有可能会占用集群全部资源。
Capacity容器除了可以配置队列及其容量外,我们还可以配置一个用户或应用可以分配的最大资源数量、可以同时运行多少应用、队列的ACL认证等。
2.3 队列的设置
关于队列的设置,这取决于我们具体的应用。比如,在MapReduce中,我们可以通过mapreduce.job.queuename
属性指定要用的队列。如果队列不存在,我们在提交任务时就会收到错误。如果我们没有定义任何队列,所有的应用将会放在一个default
队列中。
注意:对于Capacity调度器,我们的队列名必须是队列树中的最后一部分,如果我们使用队列树则不会被识别。比如,在上面配置中,我们使用prod
和eng
作为队列名是可以的,但是如果我们用root.dev.eng
或者dev.eng
是无效的。
三、Fair Scheduler(公平调度器)的配置
3.1 公平调度
Fair调度器的设计目标是为所有的应用分配公平的资源(对公平的定义可以通过参数来设置)。在上面的“Yarn调度器对比图”展示了一个队列中两个应用的公平调度;当然,公平调度在也可以在多个队列间工作。举个例子,假设有两个用户A和B,他们分别拥有一个队列。当A启动一个job而B没有任务时,A会获得全部集群资源;当B启动一个job后,A的job会继续运行,不过一会儿之后两个任务会各自获得一半的集群资源。如果此时B再启动第二个job并且其它job还在运行,则它将会和B的第一个job共享B这个队列的资源,也就是B的两个job会用于四分之一的集群资源,而A的job仍然用于集群一半的资源,结果就是资源最终在两个用户之间平等的共享。过程如下图所示:
3.2 启用Fair Scheduler
调度器的使用是通过yarn-site.xml
配置文件中的yarn.resourcemanager.scheduler.class
参数进行配置的,默认采用Capacity Scheduler调度器。如果我们要使用Fair调度器,需要在这个参数上配置FairScheduler类的全限定名: org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler
。
3.3 队列的配置
Fair调度器的配置文件位于类路径下的fair-scheduler.xml
文件中,这个路径可以通过yarn.scheduler.fair.allocation.file
属性进行修改。若没有这个配置文件,Fair调度器采用的分配策略,这个策略和3.1节介绍的类似:调度器会在用户提交第一个应用时为其自动创建一个队列,队列的名字就是用户名,所有的应用都会被分配到相应的用户队列中。
我们可以在配置文件中配置每一个队列,并且可以像Capacity 调度器一样分层次配置队列。比如,参考capacity-scheduler.xml
来配置fair-scheduler:
队列的层次是通过嵌套<queue>
元素实现的。所有的队列都是root
队列的孩子,即使我们没有配到<root>
元素里。在这个配置中,我们把dev
队列有分成了eng
和science
两个队列。
Fair调度器中的队列有一个权重属性(这个权重就是对公平的定义),并把这个属性作为公平调度的依据。在这个例子中,当调度器分配集群40:60
资源给prod
和dev
时便视作公平,eng
和science
队列没有定义权重,则会被平均分配。这里的权重并不是百分比,我们把上面的40和60分别替换成2和3,效果也是一样的。注意,对于在没有配置文件时按用户自动创建的队列,它们仍有权重并且权重值为1。
每个队列内部仍可以有不同的调度策略。队列的默认调度策略可以通过顶级元素<defaultQueueSchedulingPolicy>
进行配置,如果没有配置,默认采用公平调度。
尽管是Fair调度器,其仍支持在队列级别进行FIFO调度。每个队列的调度策略可以被其内部的<schedulingPolicy>
元素覆盖,在上面这个例子中,prod
队列就被指定采用FIFO进行调度,所以,对于提交到prod
队列的任务就可以按照FIFO规则顺序的执行了。需要注意,prod
和dev
之间的调度仍然是公平调度,同样eng
和science
也是公平调度。
尽管上面的配置中没有展示,每个队列仍可配置最大、最小资源占用数和最大可运行的应用的数量。
3.4 队列的设置
Fair调度器采用了一套基于规则的系统来确定应用应该放到哪个队列。在上面的例子中,<queuePlacementPolicy>
元素定义了一个规则列表,其中的每个规则会被逐个尝试直到匹配成功。例如,上例第一个规则specified
,则会把应用放到它指定的队列中,若这个应用没有指定队列名或队列名不存在,则说明不匹配这个规则,然后尝试下一个规则。primaryGroup
规则会尝试把应用放在以用户所在的Unix组名命名的队列中,如果没有这个队列,不创建队列转而尝试下一个规则。当前面所有规则不满足时,则触发default
规则,把应用放在dev.eng
队列中。
当然,我们可以不配置queuePlacementPolicy
规则,调度器则默认采用如下规则:
<queuePlacementPolicy> <rule name="specified" /> <rule name="user" /> </queuePlacementPolicy>
上面规则可以归结成一句话,除非队列被准确的定义,否则会以用户名为队列名创建队列。
还有一个简单的配置策略可以使得所有的应用放入同一个队列(default),这样就可以让所有应用之间平等共享集群而不是在用户之间。这个配置的定义如下:
<queuePlacementPolicy> <rule name="default" /> </queuePlacementPolicy>
实现上面功能我们还可以不使用配置文件,直接设置yarn.scheduler.fair.user-as-default-queue=false
,这样应用便会被放入default 队列,而不是各个用户名队列。另外,我们还可以设置yarn.scheduler.fair.allow-undeclared-pools=false
,这样用户就无法创建队列了。
3.5 抢占(Preemption)
当一个job提交到一个繁忙集群中的空队列时,job并不会马上执行,而是阻塞直到正在运行的job释放系统资源。为了使提交job的执行时间更具预测性(可以设置等待的超时时间),Fair调度器支持抢占。
抢占就是允许调度器杀掉占用超过其应占份额资源队列的containers,这些containers资源便可被分配到应该享有这些份额资源的队列中。需要注意抢占会降低集群的执行效率,因为被终止的containers需要被重新执行。
可以通过设置一个全局的参数yarn.scheduler.fair.preemption=true
来启用抢占功能。此外,还有两个参数用来控制抢占的过期时间(这两个参数默认没有配置,需要至少配置一个来允许抢占Container):
- minimum share preemption timeout - fair share preemption timeout
如果队列在minimum share preemption timeout
指定的时间内未获得最小的资源保障,调度器就会抢占containers。我们可以通过配置文件中的顶级元素<defaultMinSharePreemptionTimeout>
为所有队列配置这个超时时间;我们还可以在<queue>
元素内配置<minSharePreemptionTimeout>
元素来为某个队列指定超时时间。
与之类似,如果队列在fair share preemption timeout
指定时间内未获得平等的资源的一半(这个比例可以配置),调度器则会进行抢占containers。这个超时时间可以通过顶级元素<defaultFairSharePreemptionTimeout>
和元素级元素<fairSharePreemptionTimeout>
分别配置所有队列和某个队列的超时时间。上面提到的比例可以通过<defaultFairSharePreemptionThreshold>
(配置所有队列)和<fairSharePreemptionThreshold>
(配置某个队列)进行配置,默认是0.5。
在做Shuffle阶段的优化过程中,遇到了数据倾斜的问题,造成了对一些情况下优化效果不明显。主要是因为在Job完成后的所得到的Counters是整个Job的总和,优化是基于这些Counters得出的平均值,而由于数据倾斜的原因造成map处理数据量的差异过大,使得这些平均值能代表的价值降低。Hive的执行是分阶段的,map处理数据量的差异取决于上一个stage的reduce输出,所以如何将数据均匀的分配到各个reduce中,就是解决数据倾斜的根本所在。规避错误来更好的运行比解决错误更高效。在查看了一些资料后,总结如下。
1数据倾斜的原因
1.1操作:
关键词 | 情形 | 后果 |
Join | 其中一个表较小, 但是key集中 | 分发到某一个或几个Reduce上的数据远高于平均值 |
大表与大表,但是分桶的判断字段0值或空值过多 | 这些空值都由一个reduce处理,灰常慢 |
group by | group by 维度过小, 某值的数量过多 | 处理某值的reduce灰常耗时 |
Count Distinct | 某特殊值过多 | 处理此特殊值的reduce耗时 |
1.2原因:
1)、key分布不均匀
2)、业务数据本身的特性
3)、建表时考虑不周
4)、某些SQL语句本身就有数据倾斜
1.3表现:
任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。
单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。 最长时长远大于平均时长。
2数据倾斜的解决方案
2.1参数调节:
hive.map.aggr=true
Map 端部分聚合,相当于Combiner
hive.groupby.skewindata=true
有数据倾斜的时候进行负载均衡,当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。
2.2 SQL语句调节:
如何Join:
关于驱动表的选取,选用join key分布最均匀的表作为驱动表
做好列裁剪和filter操作,以达到两表做join的时候,数据量相对变小的效果。
大小表Join:
使用map join让小的维度表(1000条以下的记录条数) 先进内存。在map端完成reduce.
大表Join大表:
把空值的key变成一个字符串加上随机数,把倾斜的数据分到不同的reduce上,由于null值关联不上,处理后并不影响最终结果。
count distinct大量相同特殊值
count distinct时,将值为空的情况单独处理,如果是计算count distinct,可以不用处理,直接过滤,在最后结果中加1。如果还有其他计算,需要进行group by,可以先将值为空的记录单独处理,再和其他计算结果进行union。
group by维度过小:
采用sum() group by的方式来替换count(distinct)完成计算。
特殊情况特殊处理:
在业务逻辑优化效果的不大情况下,有些时候是可以将倾斜的数据单独拿出来处理。最后union回去。
3典型的业务场景
3.1空值产生的数据倾斜
场景:如日志中,常会有信息丢失的问题,比如日志中的 user_id,如果取其中的 user_id 和 用户表中的user_id 关联,会碰到数据倾斜的问题。
解决方法1: user_id为空的不参与关联(红色字体为修改后)
select * from log a join users b on a.user_id is not null and a.user_id = b.user_id union all select * from log a where a.user_id is null;
解决方法2 :赋与空值分新的key值
select * from log a left outer join users b on case when a.user_id is null then concat(‘hive’,rand() ) else a.user_id end = b.user_id;
结论:方法2比方法1效率更好,不但io少了,而且作业数也少了。解决方法1中 log读取两次,jobs是2。解决方法2 job数是1 。这个优化适合无效 id (比如 -99 , ’’, null 等) 产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上 ,解决数据倾斜问题。
3.2不同数据类型关联产生数据倾斜
场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reducer中。
解决方法:把数字类型转换成字符串类型
select * from users a left outer join logs b on a.usr_id = cast(b.user_id as string)
3.3小表不小不大,怎么用 map join 解决倾斜问题
使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理。 以下例子:
select * from log a left outer join users b on a.user_id = b.user_id;
users 表有 600w+ 的记录,把 users 分发到所有的 map 上也是个不小的开销,而且 map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。
解决方法:
select /*+mapjoin(x)*/* from log a
left outer join (
select /*+mapjoin(c)*/d.* from (
select distinct user_id from log ) c join users d
on c.user_id = d.user_id ) x
on a.user_id = b.user_id;
假如,log里user_id有上百万个,这就又回到原来map join问题。所幸,每日的会员uv不会太多,有交易的会员不会太多,有点击的会员不会太多,有佣金的会员不会太多等等。所以这个方法能解决很多场景下的数据倾斜问题。
4总结
使map的输出数据更均匀的分布到reduce中去,是我们的最终目标。由于Hash算法的局限性,按key Hash会或多或少的造成数据倾斜。大量经验表明数据倾斜的原因是人为的建表疏忽或业务逻辑可以规避的。在此给出较为通用的步骤:
1、采样log表,哪些user_id比较倾斜,得到一个结果表tmp1。由于对计算框架来说,所有的数据过来,他都是不知道数据分布情况的,所以采样是并不可少的。
2、数据的分布符合社会学统计规则,贫富不均。倾斜的key不会太多,就像一个社会的富人不多,奇特的人不多一样。所以tmp1记录数会很少。把tmp1和users做map join生成tmp2,把tmp2读到distribute file cache。这是一个map过程。
3、map读入users和log,假如记录来自log,则检查user_id是否在tmp2里,如果是,输出到本地文件a,否则生成<user_id,value>的key,value对,假如记录来自member,生成<user_id,value>的key,value对,进入reduce阶段。
4、最终把a文件,把Stage3 reduce阶段输出的文件合并起写到hdfs。
如果确认业务需要这样倾斜的逻辑,考虑以下的优化方案:
1、对于join,在判断小表不大于1G的情况下,使用map join
2、对于group by或distinct,设定 hive.groupby.skewindata=true
3、尽量使用上述的SQL语句调节进行优化
Hive的一般学习者和培训者在谈性能优化的时候一般都会从语法和参数这些雕虫小技的角度谈优化,而不会革命性的优化Hive的性能,产生这种现象的原因有:1,历史原因和思维定势:大家学习SQL的时候一般都是就单机DB,这个时候你的性能优化技巧确实主要是SQL语法和参数调优;2,Hive的核心的性能问题往往是产生在超过规模数据集,例如说100亿条级别的数据集,以及每天处理上千上万个Hive作业的情况下产生的;上面的第二点是我们现在Hive性能调优部分要彻底解决的内容;要从根本上解决和显著的解决实际企业中Hive真正的性能优化问题,必须考虑到底什么是Hive性能的限制,我们按照优先级来说:第一重要的是:战略性架构 解决海量数据下大量Job过于频繁的IO问题,而这个问题实质上涉及了架构方面的分表 数据复用 以及分区表等调优的方式; 补充:1,海量的数据中有些数据是高频使用的数据,而有些是很少使用的,如果能够分离成为不同的表,会极大的提升效率;很多的作业可能会有共同点,抽离出来先进行计算并保留计算结果,后面的作业都可以复用;同时,底层的基础功能也可以先计算,在上层应用的时候直接拿数据结果,而不是每次都重复计算; 2,合理从用静态分区表和动态分区表,可以避免数据全局扫描及计算资源更合理的利用; 3,数据倾斜的一站式解决方案;第二重要的是:引擎和物理层面,很多内容都是普通Hive使用这不知道的! 从Hive语法和Job内部的角度去进行优化,这要求MapReduce以及Hive如何被翻译成为MapReduce要非常精通;第三重要的是:一些关键的参数;归根到底,Hive的性能优化主要考虑的是如何最大化和最有效的使用CPU Memory IO;
Hive背后的Mapper调优:
1,Mapper数过大,会产生大量小文件,由于Mapper是基于虚拟机的,过多的Mapper创建和初始化及关闭虚拟机都会消耗大量的硬件资源;
Mapper数太小,并发度过小,Job执行时间过长,无法充分利用分布式硬件资源;
2,Mapper数据由什么决定呢?
输入文件数目;
输入文件的大小;
配置参数;
默认情况下:例如一个文件800M,BLock大小是128M,那么Mapper数目就是7个,6个Mapper处理的数据是 128M, 1个Mapper处理的数据是32M;再例如,一个目录下有三个文件分别大小问5M 10M 150M
此时会产生4个Mapper,处理的数据分别是5M 10M 128M 22M;
减少Mapper的个数,就要合并小文件,这种小文件有可能是直接来自于数据源的小文件,也可能是Reducer产生的小文件;
set hive.input.format=org.apache.Hadoop.hive.ql.io.CombineHiveInputFormat;
set hive.merge.mapFiles=true;
set hive.merge.mapredFiles=true;
set hive.merge.size.per.task=256000000
set mapred.max.split.size=256000000
set mapred.min.split.size.per.node=128000000
增加Mapper的个数,一般是通过控制Hive SQL中上一个Job的Reducer个数来控制的,例如在Join操作的时候会把多个表分解为多个Job;
set mapred.map.tasks=2;
set hive.merge.mapFiles=true;
set hive.merge.mapredFiles=true;
set hive.merge.size.per.task=256000000
例如我们有5个300M的文件;按照上面的配置会产生10个Mapper,5个Mapper处理的都是256M的数据,另外5个Mapper处理的都是44M的数据,问题是:大的Mapper会数据倾斜
如何解决,设置set mapred.map.tasks=6,此时根据MapRed的运行机制,会划分6个Mapper,每个Mapper的处理数据的大小是250M, min(1500M/6, 256M) =250M
Hive背后的Reducer调优:
1,Reducer数目过大的话,会产生很多小文件,每个Reducer都会产生一个文件,如果这些小文件是下一个JOB的输入,则会需要对小文件进行合并;同样启动 初始化和销毁Reducer的虚拟机也需要消耗大量的硬件;
Reducer数据过小的话,Reduce的时间会比较长,也可能会出现数据倾斜;
2,如何控制Reducer的个数呢?
set hive.exec.reducers.byte.per.reducer=1G
set hive.exec.reducers.max=999
Reducer个数=min(999, Reducer的数据输入总量/1G);
set mapred.reduce.tasks = 10, 默认是1; 如果说当前的Reducer的结果很大,且被接下来多个Job使用其结果,我们该如何设置参数呢?一般都需要调大该参数;
什么情况下只有一个Reducer?如果不进行Group by但却需要汇总,或者说Order by,当然如果最后Reducer的数据小于默认的1G的话,也会只有一个Reducer;
1,Hive在分布式运行的时候最害怕的是数据倾斜,这是由于分布式系统的特性决定的,因为分布式系统之所以很快是由于作业平均分配给了不同的节点,不同节点同心协力,从而达到更快处理完作业的目的;
顺便说明一下,处理数据倾斜的能力是hadoop和Spark工程师最核心的竞争力之一;
2,Hive中数据倾斜的原因:
数据在分布式节点上分布不平衡;
join时某些key可能特别大;
groupBy的时候某个Key可能特别多;
count(distinct)有可能出现数据倾斜,因为其内部首先会进行groupBy操作;
3,join,我们希望join时候key是分散,如果一个key的数据量特别大,有可能会出现数据倾斜和OOM,一个核心点是:小表join大表,在reduce阶段左侧的小表会加载进内存,减少OOM的风险;
4,大表join大表的情况:数据倾斜,例如null值,解决办法一般是要打散null值,例如说使用随机数等,如果数据倾斜比较严重,采用这种方式可以提升至少一倍的速度;
5,mapJoin:小表join(超)大表的时候,可以采用mapJoin的方式把小表全部加载到Mapper端的内存中/*+MAPJOIN(table_name)*/;
6,小表join(超)大表的时候,是否会自动进行mapJoin,想进行mapJoin,需要设置:set hive.auto.convert.join=true,Hive在进行join的时候会判断左表的大小来决定是否进行mapJoin:
set hive.mapjoin.smalltable.filesize=128000000;
set hive.mapjoin.cache.numrows=100000;
上述参数可以根据实际的硬件机器的内存进行调整,对性能有至关重要的影响,因为没有了Shuffle;
对于mapJoin我们能够使用Mapper端JVM中多大的内存呢?
set hive.mapjoin.followby.gby.localtask.max.momery.usage = 0.8
set hive.mapjoin.localtask.max.memory.uage=0.9
7,groupBy,我们可以设置在Mapper端进行部分聚合,最后在Reducer端进行全局聚合
set hive.map.aggr=true;
set hive.groupby.mapaggr.checkinterval=100000
set hive.groupby.skewindata = true 内部会产生两个Job,第一个Job会通过自己的算法打散倾斜的Key并进行聚合操作且保留结果,第二个Job会完成全部的groupBy操作,会产生Mapper-Reducer-Reducer的结构
8, count(distinct),如果某个字段特别多,容易产生数据倾斜,解决思路:
在查询语句中例如对null进行过滤,在结果中加1
9, 笛卡尔积:join时候没有on条件,或者on条件无效,这个时候会使用Reducer进行笛卡尔积的操作;
1. 两者分别是什么?
Apache Hive是一个构建在Hadoop基础设施之上的数据仓库。通过Hive可以使用HQL语言查询存放在HDFS上的数据。HQL是一种类SQL语言,这种语言最终被转化为Map/Reduce. 虽然Hive提供了SQL查询功能,但是Hive不能够进行交互查询--因为它只能够在Haoop上批量的执行Hadoop。
Apache HBase是一种Key/Value系统,它运行在HDFS之上。和Hive不一样,Hbase的能够在它的数据库上实时运行,而不是运行MapReduce任务。Hive被分区为表格,表格又被进一步分割为列簇。列簇必须使用schema定义,列簇将某一类型列集合起来(列不要求schema定义)。例如,“message”列簇可能包含:“to”, ”from” “date”, “subject”, 和”body”. 每一个 key/value对在Hbase中被定义为一个cell,每一个key由row-key,列簇、列和时间戳。在Hbase中,行是key/value映射的集合,这个映射通过row-key来唯一标识。Hbase利用Hadoop的基础设施,可以利用通用的设备进行水平的扩展。
2. 两者的特点
Hive帮助熟悉SQL的人运行MapReduce任务。因为它是JDBC兼容的,同时,它也能够和现存的SQL工具整合在一起。运行Hive查询会花费很长时间,因为它会默认遍历表中所有的数据。虽然有这样的缺点,一次遍历的数据量可以通过Hive的分区机制来控制。分区允许在数据集上运行过滤查询,这些数据集存储在不同的文件夹内,查询的时候只遍历指定文件夹(分区)中的数据。这种机制可以用来,例如,只处理在某一个时间范围内的文件,只要这些文件名中包括了时间格式。
HBase通过存储key/value来工作。它支持四种主要的操作:增加或者更新行,查看一个范围内的cell,获取指定的行,删除指定的行、列或者是列的版本。版本信息用来获取历史数据(每一行的历史数据可以被删除,然后通过Hbase compactions就可以释放出空间)。虽然HBase包括表格,但是schema仅仅被表格和列簇所要求,列不需要schema。Hbase的表格包括增加/计数功能。
3. 限制
Hive目前不支持更新操作。另外,由于hive在hadoop上运行批量操作,它需要花费很长的时间,通常是几分钟到几个小时才可以获取到查询的结果。Hive必须提供预先定义好的schema将文件和目录映射到列,并且Hive与ACID不兼容。
HBase查询是通过特定的语言来编写的,这种语言需要重新学习。类SQL的功能可以通过Apache Phonenix实现,但这是以必须提供schema为代价的。另外,Hbase也并不是兼容所有的ACID特性,虽然它支持某些特性。最后但不是最重要的--为了运行Hbase,Zookeeper是必须的,zookeeper是一个用来进行分布式协调的服务,这些服务包括配置服务,维护元信息和命名空间服务。
4. 应用场景
Hive适合用来对一段时间内的数据进行分析查询,例如,用来计算趋势或者网站的日志。Hive不应该用来进行实时的查询。因为它需要很长时间才可以返回结果。
Hbase非常适合用来进行大数据的实时查询。Facebook用Hbase进行消息和实时的分析。它也可以用来统计Facebook的连接数。
5. 总结
Hive和Hbase是两种基于Hadoop的不同技术--Hive是一种类SQL的引擎,并且运行MapReduce任务,Hbase是一种在Hadoop之上的NoSQL 的Key/vale数据库。当然,这两种工具是可以同时使用的。就像用Google来搜索,用FaceBook进行社交一样,Hive可以用来进行统计查询,HBase可以用来进行实时查询,数据也可以从Hive写到Hbase,设置再从Hbase写回Hive。
摘要: 目录 1,环境准备 2,安装Hive和配置环境变量 3,安装MySQL 4,在mysql上创建hive元数据库,并对hive进行授权 5,安装jar包到hive 6,配置hive-site.xml 7,元数据存储初始化 8,启动验证hive 9,报错及解决方法
1,环境准备:
准备好Hadoop集群,参照...
阅读全文
TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。
UDP(User Datagram Protocol)
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。
即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。
TCP(Transmission Control Protocol)
TCP充分实现爱呢了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。此处不一一叙述。
TCP与UDP如何加以区分使用?
TCP用于在传输层有必要实现可靠性传输的情况。由于它是面向有连接并具备顺序控制、重发控制等机制的。所以它可以为应用提供可靠传输。
另一方面,UDP主要用于那些对高速传输和实时性有较高要求的通信或广播通信。举一个IP电话进行通话的例子。如果使用TCP,数据在传送途中如果丢失会被重发,但是这样无法流畅地传输通话人的声音,会导致无法进行正常交流。而采用UDP,它不会进行重发处理。从而也就不会有声音大幅度延迟到达的问题。即使有部分数据丢失,也只是影响某一小部分的通话。此外,在多播与广播通信中也使用UDP而不是UDP。RIP、DHCP等基于广播的协议也要依赖于UDP。
TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
UDP如何实现可靠传输
由于在传输层UDP已经是不可靠的连接,那就要在应用层自己实现一些保障可靠传输的机制
简单来讲,要使用UDP来构建可靠的面向连接的数据传输,就要实现类似于TCP协议的
超时重传(定时器)
有序接受 (添加包序号)
应答确认 (Seq/Ack应答机制)
滑动窗口流量控制等机制 (滑动窗口协议)
等于说要在传输层的上一层(或者直接在应用层)实现TCP协议的可靠数据传输机制,比如使用UDP数据包+序列号,UDP数据包+时间戳等方法。
目前已经有一些实现UDP可靠传输的机制,比如
UDT(UDP-based Data Transfer Protocol)
基于UDP的数据传输协议(UDP-based Data Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。
一、
1、什么是Hbase。
是一个高可靠性、高性能、列存储、可伸缩、实时读写的分布式数据库系统。
适合于存储非结构化数据,基于列的而不是基于行的模式
如图:Hadoop生态中hbase与其他部分的关系。
2、关系数据库已经流行很多年,并且hadoop已经有了HDFS和MapReduce,为什么需要HBase?
Hadoop可以很好地解决大规模数据的离线批量处理问题,但是,受限于HadoopMapReduce编程框架的高延迟数据处理机制,使得Hadoop无法满足大规模数据实时处理应用的需求
HDFS面向批量访问模式,不是随机访问模式
传统的通用关系型数据库无法应对在数据规模剧增时导致的系统扩展性和性能问题(分库分表也不能很好解决)
传统关系数据库在数据结构变化时一般需要停机维护;空列浪费存储空间
因此,业界出现了一类面向半结构化数据存储和处理的高可扩展、低写入/查询延迟的系统,例如,键值数据库、文档数据库和列族数据库(如BigTable和HBase等)
HBase已经成功应用于互联网服务领域和传统行业的众多在线式数据分析处理系统中
3、HBase与传统的关系数据库的区别
(1)数据类型:关系数据库采用关系模型,具有丰富的数据类型和存储方式,HBase则采用了更加简单的数据模型,它把数据存储为未经解释的字符串
(2)数据操作:关系数据库中包含了丰富的操作,其中会涉及复杂的多表连接。HBase操作则不存在复杂的表与表之间的关系,只有简单的插入、查询、删除、清空等,因为HBase在设计上就避免了复杂的表和表之间的关系
(3)存储模式:关系数据库是基于行模式存储的。HBase是基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的
(4)数据索引:关系数据库通常可以针对不同列构建复杂的多个索引,以提高数据访问性能。HBase只有一个索引——行键,通过巧妙的设计,HBase中的所有访问方法,或者通过行键访问,或者通过行键扫描,从而使得整个系统不会慢下来
(5)数据维护:在关系数据库中,更新操作会用最新的当前值去替换记录中原来的旧值,旧值被覆盖后就不会存在。而在HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留
(6)可伸缩性:关系数据库很难实现横向扩展,纵向扩展的空间也比较有限。相反,HBase和BigTable这些分布式数据库就是为了实现灵活的水平扩展而开发的,能够轻易地通过在集群中增加或者减少硬件数量来实现性能的伸缩
二、Hbase数据模型
1、模型概述
HBase是一个稀疏、多维度、排序的映射表,这张表的索引是行键、列族、列限定符和时间戳
每个值是一个未经解释的字符串,没有数据类型
用户在表中存储数据,每一行都有一个可排序的行键和任意多的列
表在水平方向由一个或者多个列族组成,一个列族中可以包含任意多个列,同一个列族里面的数据存储在一起
列族支持动态扩展,可以很轻松地添加一个列族或列,无需预先定义列的数量以及类型,所有列均以字符串形式存储,用户需要自行进行数据类型转换
HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个新的版本,旧有的版本仍然保留(这是和HDFS只允许追加不允许修改的特性相关的)
2、数据坐标
HBase中需要根据行键、列族、列限定符和时间戳来确定一个单元格,因此,可以视为一个“四维坐标”,即[行键,列族, 列限定符,时间戳]
键 |
值 |
[“201505003”,“Info”,“email”, 1174184619081] |
“xie@qq.com” |
[“201505003”,“Info”,“email”, 1174184620720] |
“you@163.com”
|
3、概念视图
4、物理视图
三、HBase实现原理
1、HBase的实现包括三个主要的功能组件:
(1)库函数:链接到每个客户端
(2)一个Master主服务器
(3)许多个Region服务器
主服务器Master负责管理和维护HBase表的分区信息,维护Region服务器列表,分配Region,负载均衡
Region服务器负责存储和维护分配给自己的Region,处理来自客户端的读写请求
客户端并不是直接从Master主服务器上读取数据,而是在获得Region的存储位置信息后,直接从Region服务器上读取数据
客户端并不依赖Master,而是通过Zookeeper来获得Region位置信息,大多数客户端甚至从来不和Master通信,这种设计方式使得Master负载很小
2、Region
开始只有一个Region,后来不断分裂
Region拆分操作非常快,接近瞬间,因为拆分之后的Region读取的仍然是原存储文件,直到“合并”过程把存储文件异步地写到独立的文件之后,才会读取新文件
同一个Region不会被分拆到多个Region服务器
每个Region服务器存储10-1000个Region
元数据表,又名.META.表,存储了Region和Region服务器的映射关系
当HBase表很大时, .META.表也会被分裂成多个Region
根数据表,又名-ROOT-表,记录所有元数据的具体位置
-ROOT-表只有唯一一个Region,名字是在程序中被写死的
Zookeeper文件记录了-ROOT-表的位置
客户端访问数据时的“三级寻址”
为了加速寻址,客户端会缓存位置信息,同时,需要解决缓存失效问题
寻址过程客户端只需要询问Zookeeper服务器,不需要连接Master服务器
3、HBase的三层结构中各层次的名称和作用
层次 |
名称 |
作用 |
第一层 |
Zookeper文件 |
记录了-ROOT-表的位置信息 |
第二层 |
-ROOT-表 |
记录了.META.表的Region位置信息
-ROOT-表只能有一个Region。通过-ROOT-表,就可以访问.META.表中的数据 |
第三层 |
.META.表 |
记录了用户数据表的Region位置信息,.META.表可以有多个Region,保存了HBase中所有用户数据表的Region位置信息 |
四、HBase运行机制
1、HBase系统架构
(1、客户端包含访问HBase的接口,同时在缓存中维护着已经访问过的Region位置信息,用来加快后续数据访问过程
(2、Zookeeper可以帮助选举出一个Master作为集群的总管,并保证在任何时刻总有唯一一个Master在运行,这就避免了Master的“单点失效”问题
(Zookeeper是一个很好的集群管理工具,被大量用于分布式计算,提供配置维护、域名服务、分布式同步、组服务等。)
(3. Master
主服务器Master主要负责表和Region的管理工作:
管理用户对表的增加、删除、修改、查询等操作
实现不同Region服务器之间的负载均衡
在Region分裂或合并后,负责重新调整Region的分布
对发生故障失效的Region服务器上的Region进行迁移
(4. Region服务器
Region服务器是HBase中最核心的模块,负责维护分配给自己的Region,并响应用户的读写请求
2、Region
(1、用户读写数据过程
用户写入数据时,被分配到相应Region服务器去执行
用户数据首先被写入到MemStore和Hlog中
只有当操作写入Hlog之后,commit()调用才会将其返回给客户端
当用户读取数据时,Region服务器会首先访问MemStore缓存,如果找不到,再去磁盘上面的StoreFile中寻找
(2、缓存的刷新
系统会周期性地把MemStore缓存里的内容刷写到磁盘的StoreFile文件中,清空缓存,并在Hlog里面写入一个标记、
每次刷写都生成一个新的StoreFile文件,因此,每个Store包含多个StoreFile文件
每个Region服务器都有一个自己的HLog文件,每次启动都检查该文件,确认最近一次执行缓存刷新操作之后是否发生新的写入操作;如果发现更新,则先写入MemStore,再刷写到StoreFile,最后删除旧的Hlog文件,开始为用户提供服务
(3、StroreFile的合并
每次刷写都生成一个新的StoreFile,数量太多,影响查找速度
调用Store.compact()把多个合并成一个
合并操作比较耗费资源,只有数量达到一个阈值才启动合并
3、Store工作原理
Store是Region服务器的核心
多个StoreFile合并成一个
触发分裂操作,1个父Region被分裂成两个子Region
单个StoreFile过大时,又 4、HLog工作原理
分布式环境必须要考虑系统出错。HBase采用HLog保证系统恢复
HBase系统为每个Region服务器配置了一个HLog文件,它是一种预写式日志(WriteAhead Log)
用户更新数据必须首先写入日志后,才能写入MemStore缓存,并且,直到MemStore缓存内容对应的日志已经写入磁盘,该缓存内容才能被刷写到磁盘
Zookeeper会实时监测每个Region服务器的状态,当某个Region服务器发生故障时,Zookeeper会通知Master
Master首先会处理该故障Region服务器上面遗留的HLog文件,这个遗留的HLog文件中包含了来自多个Region对象的日志记录
系统会根据每条日志记录所属的Region对象对HLog数据进行拆分,分别放到相应Region对象的目录下,然后,再将失效的Region重新分配到可用的Region服务器中,并把与该Region对象相关的HLog日志记录也发送给相应的Region服务器
Region服务器领取到分配给自己的Region对象以及与之相关的HLog日志记录以后,会重新做一遍日志记录中的各种操作,把日志记录中的数据写入到MemStore缓存中,然后,刷新到磁盘的StoreFile文件中,完成数据恢复
共用日志优点:提高对表的写操作性能;缺点:恢复时需要分拆日志
五、HBase性能
1、
行键(RowKey)
行键是按照字典序存储,因此,设计行键时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为行键的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE- timestamp作为行键,这样能保证新写入的数据在读取时可以被快速命中。
InMemory:创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到Region服务器的缓存中,保证在读取的时候被cache命中。
Max Version:创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。
Time To Live创建表的时候,可以通过HColumnDescriptor.setTimeToLive(inttimeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2* 24 * 60 * 60)。
2、HBaseMaster默认基于Web的UI服务端口为60010,HBase region服务器默认基于Web的UI服务端口为60030.如果master运行在名为master.foo.com的主机中,mater的主页地址就是http://master.foo.com:60010,用户可以通过Web浏览器输入这个地址查看该页面
可以查看HBase集群的当前状态
3、
NoSQL区别于关系型数据库的一点就是NoSQL不使用SQL作为查询语言,至于为何在NoSQL数据存储HBase上提供SQL接口
易使用,减少编码
4、HBase只有一个针对行健的索引
访问HBase表中的行,只有三种方式:
通过单个行健访问
通过一个行健的区间来访问
全表扫描
总结:
1、HBase数据库是BigTable的开源实现,和BigTable一样,支持大规模海量数据,分布式并发数据处理效率极高,易于扩展且支持动态伸缩,适用于廉价设备
2、HBase可以支持NativeJava API、HBaseShell、ThriftGateway、Hive等多种访问接口,可以根据具体应用场合选择相应访问方式
3、HBase实际上就是一个稀疏、多维、持久化存储的映射表,它采用行键、列键和时间戳进行索引,每个值都是未经解释的字符串。
4、HBase采用分区存储,一个大的表会被分拆许多个Region,这些Region会被分发到不同的服务器上实现分布式存储
5、HBase的系统架构包括客户端、Zookeeper服务器、Master主服务器、Region服务器。客户端包含访问HBase的接口;Zookeeper服务器负责提供稳定可靠的协同服务;Master主服务器主要负责表和Region的管理工作;Region服务器负责维护分配给自己的Region,并响应用户的读写请求
python 获得本机MAC地址:
import uuid
def get_mac_address():
mac=uuid.UUID(int=uuid.getnode()).hex[-12:]
return ":".join([mac[e:e+2] for e in range(0,11,2)])
python获取IP的方法:使用socket
import socketmyname=socket.getfqdn(socket.gethostname( ))
myaddr=socket.gethostbyname(myname)
print(myname)
print(myaddr)
spring中使用Quartz时 时间配置例子:
<!-- 定义调用对象和调用对象的方法 end -->
<!-- 定义调用时间 begin -->
<bean id="realweatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="realweatherTask" />
</property>
<property name="cronExpression">
<value>0 10/30 * * * ?</value><!-- 表示每小时的10,40时执行任务 -->
</property>
</bean>
<!-- 定义调用时间 end -->
<!-- 定义调用对象和调用对象的方法 end -->
<!-- 定义调用时间 begin -->
<bean id="weatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="weatherTask" />
</property>
<property name="cronExpression">
<!-- <value>0 30 8,13 * * ?</value> --><!-- 表示每天的8:30和13:30时执行任务 -->
<value>0 0,30 0-23 * * ?</value><!---表示每天从0-23时中每时的整点或半点执行任务->
</property>
</bean>
<!-- 定义调用时间 end -->1、 CronTrigger时间格式配置说明
CronTrigger配置格式:
格式: [秒] [分] [小时] [日] [月] [周] [年]
序号 | 说明 | 是否必填 | 允许填写的值 | 允许的通配符 |
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 | , - * / |
3 | 小时 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符说明:
* :表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。
? :表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?
- :表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。
, :表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发
/ :用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。
L :表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。
例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"
W :表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").
'L'和 'W'可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发
# :序号(表示每月的第几周星期几),例如在周字段上设置"6#3"表示在每月的第三个周星期六.注意如果指定"6#5",正好第五周没有星期六,则不会触发该配置(用在母亲节和父亲节再合适不过了)
周字段的设置,若使用英文字母是不区分大小写的 MON 与mon相同.
常用示例:
格式: [秒] [分] [小时] [日] [月] [周] [年]
0 0 12 * * ? 每天12点触发
0 15 10 ? * * 每天10点15分触发
0 15 10 * * ? 每天10点15分触发
0 15 10 * * ? * 每天10点15分触发
0 15 10 * * ? 2005 2005年每天10点15分触发
0 * 14 * * ? 每天下午的 2点到2点59分每分触发
0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
0 0/5 14,18 * * ? 每天下午的 18点到18点59分(整点开始,每隔5分触发)
0 0-5 14 * * ? 每天下午的 2点到2点05分每分触发
0 10,44 14 ? 3 WED 3月分每周三下午的 2点10分和2点44分触发
0 15 10 ? * MON-FRI 从周一到周五每天上午的10点15分触发
0 15 10 15 * ? 每月15号上午10点15分触发
0 15 10 L * ? 每月最后一天的10点15分触发
0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
0 15 10 ? * 6L 2002-2005 从2002年到2005年每月最后一周的星期五的10点15分触发
0 15 10 ? * 6#3 每月的第三周的星期五开始触发
0 0 12 1/5 * ? 每月的第一个中午开始每隔5天触发一次
0 11 11 11 11 ? 每年的11月11号 11点11分触发(光棍节)
spring中使用Quartz时 时间配置例子:
<!-- 定义调用对象和调用对象的方法 end -->
<!-- 定义调用时间 begin -->
<bean id="realweatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="realweatherTask" />
</property>
<property name="cronExpression">
<value>0 10/30 * * * ?</value><!-- 表示每小时的10,40时执行任务 -->
</property>
</bean>
<!-- 定义调用时间 end -->
<!-- 定义调用对象和调用对象的方法 end -->
<!-- 定义调用时间 begin -->
<bean id="weatherTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail">
<ref bean="weatherTask" />
</property>
<property name="cronExpression">
<!-- <value>0 30 8,13 * * ?</value> --><!-- 表示每天的8:30和13:30时执行任务 -->
<value>0 0,30 0-23 * * ?</value><!---表示每天从0-23时中每时的整点或半点执行任务->
</property>
</bean>
<!-- 定义调用时间 end -->
1.有时表格内容太多,只显示部分,其余部分已省略号表示,用css处理如下:
.template td{
word-break:keep-all;/* 不换行 */
white-space:nowrap;/* 不换行 */
overflow:hidden;/* 内容超出宽度时隐藏超出部分的内容 */
text-overflow:ellipsis;/* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
}
template 是该表单所在的table class属性。
代码如下:
引用
function fmoney(s, n)
{
n = n > 0 && n <= 20 ? n : 2;
s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
var l = s.split(".")[0].split("").reverse(),
r = s.split(".")[1];
t = "";
for(i = 0; i < l.length; i ++ )
{
t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : "");
}
return t.split("").reverse().join("") + "." + r;
}
调用:fmoney("12345.675910", 3),返回12,345.676
还原函数:
引用
function rmoney(s)
{
return parseFloat(s.replace(/[^\d\.-]/g, ""));
}
示例(可保存一下代码为html文件,运行查看效果):
引用
<SCRIPT>
function fmoney(s, n)
{
n = n > 0 && n <= 20 ? n : 2;
s = parseFloat((s + "").replace(/[^\d\.-]/g, "")).toFixed(n) + "";
var l = s.split(".")[0].split("").reverse(),
r = s.split(".")[1];
t = "";
for(i = 0; i < l.length; i ++ )
{
t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : "");
}
return t.split("").reverse().join("") + "." + r;
}
function rmoney(s)
{
return parseFloat(s.replace(/[^\d\.-]/g, ""));
}
function g(id)
{
return document.getElementById(id);
}
window.onload = function()
{
var num,
txt = g("txt"),
txt2 = g("txt2"),
btn = g("btn"),
btn2 = g("btn2"),
span = g("span");
btn.onclick = function()
{
num = parseInt(g("num").value);
txt.value = fmoney(txt.value, num);
txt2.value = fmoney(txt2.value, num);
}
;
btn2.onclick = function()
{
num = parseInt(g("num").value);
span.innerHTML = "=" + fmoney(rmoney(txt.value) + rmoney(txt2.value), num);
}
;
}
;
</SCRIPT>
小数点位数:
<select id="num">
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<input type="text" id="txt" value="12345.675910"> +
<input type="text" id="txt2" value="1223"> <span id="span"></span>
<br>
<input type="button" id="btn" value="
格式化">
<input type="button" id="btn2" value="相加">
一、简介
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
二、构造器描述
BigDecimal(int) 创建一个具有参数所指定整数值的对象。
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。
BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。
三、方法描述
add(BigDecimal) BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。
multiply(BigDecimal) BigDecimal对象中的值相乘,然后返回这个对象。
divide(BigDecimal) BigDecimal对象中的值相除,然后返回这个对象。
toString() 将BigDecimal对象的数值转换成字符串。
doubleValue() 将BigDecimal对象中的值以双精度数返回。
floatValue() 将BigDecimal对象中的值以单精度数返回。
longValue() 将BigDecimal对象中的值以长整数返回。
intValue() 将BigDecimal对象中的值以整数返回。
四、格式化及例子
由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。
以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。
public static void main(String[] args) {
NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位
BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率
BigDecimal interest = loanAmount.multiply(interestRate); //相乘
System.out.println("贷款金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest)); }
运行结果如下:
贷款金额: ¥15,000.48
利率: 0.8%
利息: ¥120.00
五、BigDecimal比较
BigDecimal是通过使用compareTo(BigDecimal)来比较的,具体比较情况如下:
public static void main(String[] args) {
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("2");
BigDecimal c = new BigDecimal("1");
int result1 = a.compareTo(b);
int result2 = a.compareTo(c);
int result3 = b.compareTo(a);
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
}
打印结果是:-1、0、1,即左边比右边数大,返回1,相等返回0,比右边小返回-1。
注意不能使用equals方法来比较大小。
使用BigDecimal的坏处是性能比double和float差,在处理庞大,复杂的运算时尤为明显,因根据实际需求决定使用哪种类型。
如果你只是寻求多行转换成一列,比如把同一个id的某个字段col变成一行数据库,把多个col用逗号链接起来。下面几个SQL可以立竿见影。
《1》最简短的方式,使用WMSYS.WM_CONCAT:
SELECT id, REPLACE(wmsys.wm_concat(col), ',', '/') str
FROM Table1
GROUP BY id;
《2》使用sys_connect_by_path:
SELECT t.id id, MAX(substr(sys_connect_by_path(t.col, ','), 2)) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM Table1) t
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id
GROUP BY t.id;
或者
SELECT t.id id, substr(sys_connect_by_path(t.col, ','), 2) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM Table1) t
WHERE connect_by_isleaf = 1
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id;
《3》使用MODEL:
SELECT id, substr(str, 2) str FROM Table1
MODEL
RETURN UPDATED ROWS
PARTITION BY(ID)
DIMENSION BY(row_number() over(PARTITION BY ID ORDER BY col) AS rn)
MEASURES (CAST(col AS VARCHAR2(20)) AS str)
RULES UPSERT
ITERATE(3) UNTIL( presentv(str[iteration_number+2],1,0)=0)
(str[0] = str[0] || ',' || str[iteration_number+1])
ORDER BY 1;
下面是原文:
1.
概述
最近论坛很多人提的问题都与行列转换有关系,所以我对行列转换的相关知识做了一个总结,希望对大家有所帮助,同时有何错疏,恳请大家指出,我也是在写作过程中学习,算是一起和大家学习吧!
行列转换包括以下六种情况:
1)
列转行
2)
行转列
3)
多列转换成字符串
4)
多行转换成字符串
5)
字符串转换成多列
6)
字符串转换成多行
下面分别进行举例介绍。
首先声明一点,有些例子需要如下10g及以后才有的知识:
A.
掌握model子句
B.
正则表达式
C.
加强的层次查询
讨论的适用范围只包括8i,9i,10g及以后版本。
2.
列转行
CREATE TABLE t_col_row(
ID INT,
c1 VARCHAR2(10),
c2 VARCHAR2(10),
c3 VARCHAR2(10));
INSERT INTO t_col_row VALUES (1, 'v11', 'v21', 'v31');
INSERT INTO t_col_row VALUES (2, 'v12', 'v22', NULL);
INSERT INTO t_col_row VALUES (3, 'v13', NULL, 'v33');
INSERT INTO t_col_row VALUES (4, NULL, 'v24', 'v34');
INSERT INTO t_col_row VALUES (5, 'v15', NULL, NULL);
INSERT INTO t_col_row VALUES (6, NULL, NULL, 'v35');
INSERT INTO t_col_row VALUES (7, NULL, NULL, NULL);
COMMIT;
SELECT * FROM t_col_row;
2.1
UNION ALL
适用范围:8i,9i,10g及以后版本
SELECT id, 'c1' cn, c1 cv
FROM t_col_row
UNION ALL
SELECT id, 'c2' cn, c2 cv
FROM t_col_row
UNION ALL
SELECT id, 'c3' cn, c3 cv FROM t_col_row;
若空行不需要转换,只需加一个where条件,
WHERE COLUMN IS NOT NULL 即可。
2.2
MODEL
适用范围:10g及以后
SELECT id, cn, cv FROM t_col_row
MODEL
RETURN UPDATED ROWS
PARTITION BY (ID)
DIMENSION BY (0 AS n)
MEASURES ('xx' AS cn,'yyy' AS cv,c1,c2,c3)
RULES UPSERT ALL
(
cn[1] = 'c1',
cn[2] = 'c2',
cn[3] = 'c3',
cv[1] = c1[0],
cv[2] = c2[0],
cv[3] = c3[0]
)
ORDER BY ID,cn;
2.3
COLLECTION
适用范围:8i,9i,10g及以后版本
要创建一个对象和一个集合:
CREATE TYPE cv_pair AS OBJECT(cn VARCHAR2(10),cv VARCHAR2(10));
CREATE TYPE cv_varr AS VARRAY(8) OF cv_pair;
SELECT id, t.cn AS cn, t.cv AS cv
FROM t_col_row,
TABLE(cv_varr(cv_pair('c1', t_col_row.c1),
cv_pair('c2', t_col_row.c2),
cv_pair('c3', t_col_row.c3))) t
ORDER BY 1, 2;
3.
行转列
CREATE TABLE t_row_col AS
SELECT id, 'c1' cn, c1 cv
FROM t_col_row
UNION ALL
SELECT id, 'c2' cn, c2 cv
FROM t_col_row
UNION ALL
SELECT id, 'c3' cn, c3 cv FROM t_col_row;
SELECT * FROM t_row_col ORDER BY 1,2;
3.1
AGGREGATE FUNCTION
适用范围:8i,9i,10g及以后版本
SELECT id,
MAX(decode(cn, 'c1', cv, NULL)) AS c1,
MAX(decode(cn, 'c2', cv, NULL)) AS c2,
MAX(decode(cn, 'c3', cv, NULL)) AS c3
FROM t_row_col
GROUP BY id
ORDER BY 1;
MAX聚集函数也可以用sum、min、avg等其他聚集函数替代。
被指定的转置列只能有一列,但固定的列可以有多列,请看下面的例子:
SELECT mgr, deptno, empno, ename FROM emp ORDER BY 1, 2;
SELECT mgr,
deptno,
MAX(decode(empno, '7788', ename, NULL)) "7788",
MAX(decode(empno, '7902', ename, NULL)) "7902",
MAX(decode(empno, '7844', ename, NULL)) "7844",
MAX(decode(empno, '7521', ename, NULL)) "7521",
MAX(decode(empno, '7900', ename, NULL)) "7900",
MAX(decode(empno, '7499', ename, NULL)) "7499",
MAX(decode(empno, '7654', ename, NULL)) "7654"
FROM emp
WHERE mgr IN (7566, 7698)
AND deptno IN (20, 30)
GROUP BY mgr, deptno
ORDER BY 1, 2;
这里转置列为empno,固定列为mgr,deptno。
还有一种行转列的方式,就是相同组中的行值变为单个列值,但转置的行值不变为列名:
ID CN_1 CV_1 CN_2 CV_2 CN_3 CV_3
1 c1 v11 c2 v21 c3 v31
2 c1 v12 c2 v22 c3
3 c1 v13 c2 c3 v33
4 c1 c2 v24 c3 v34
5 c1 v15 c2 c3
6 c1 c2 c3 v35
7 c1 c2 c3
这种情况可以用分析函数实现:
SELECT id,
MAX(decode(rn, 1, cn, NULL)) cn_1,
MAX(decode(rn, 1, cv, NULL)) cv_1,
MAX(decode(rn, 2, cn, NULL)) cn_2,
MAX(decode(rn, 2, cv, NULL)) cv_2,
MAX(decode(rn, 3, cn, NULL)) cn_3,
MAX(decode(rn, 3, cv, NULL)) cv_3
FROM (SELECT id,
cn,
cv,
row_number() over(PARTITION BY id ORDER BY cn, cv) rn
FROM t_row_col)
GROUP BY ID;
3.2
PL/SQL
适用范围:8i,9i,10g及以后版本
这种对于行值不固定的情况可以使用。
下面是我写的一个包,包中
p_rows_column_real用于前述的第一种不限定列的转换;
p_rows_column用于前述的第二种不限定列的转换。
CREATE OR REPLACE PACKAGE pkg_dynamic_rows_column AS
TYPE refc IS REF CURSOR;
PROCEDURE p_print_sql(p_txt VARCHAR2);
FUNCTION f_split_str(p_str VARCHAR2, p_division VARCHAR2, p_seq INT)
RETURN VARCHAR2;
PROCEDURE p_rows_column(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_cols IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc);
PROCEDURE p_rows_column_real(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_col IN VARCHAR2,
p_pivot_val IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc);
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_dynamic_rows_column AS
PROCEDURE p_print_sql(p_txt VARCHAR2) IS
v_len INT;
BEGIN
v_len := length(p_txt);
FOR i IN 1 .. v_len / 250 + 1 LOOP
dbms_output.put_line(substrb(p_txt, (i - 1) * 250 + 1, 250));
END LOOP;
END;
FUNCTION f_split_str(p_str VARCHAR2, p_division VARCHAR2, p_seq INT)
RETURN VARCHAR2 IS
v_first INT;
v_last INT;
BEGIN
IF p_seq < 1 THEN
RETURN NULL;
END IF;
IF p_seq = 1 THEN
IF instr(p_str, p_division, 1, p_seq) = 0 THEN
RETURN p_str;
ELSE
RETURN substr(p_str, 1, instr(p_str, p_division, 1) - 1);
END IF;
ELSE
v_first := instr(p_str, p_division, 1, p_seq - 1);
v_last := instr(p_str, p_division, 1, p_seq);
IF (v_last = 0) THEN
IF (v_first > 0) THEN
RETURN substr(p_str, v_first + 1);
ELSE
RETURN NULL;
END IF;
ELSE
RETURN substr(p_str, v_first + 1, v_last - v_first - 1);
END IF;
END IF;
END f_split_str;
PROCEDURE p_rows_column(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_cols IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc) IS
v_sql VARCHAR2(4000);
TYPE v_keep_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_keep v_keep_ind_by;
TYPE v_pivot_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_pivot v_pivot_ind_by;
v_keep_cnt INT;
v_pivot_cnt INT;
v_max_cols INT;
v_partition VARCHAR2(4000);
v_partition1 VARCHAR2(4000);
v_partition2 VARCHAR2(4000);
BEGIN
v_keep_cnt := length(p_keep_cols) - length(REPLACE(p_keep_cols, ',')) + 1;
v_pivot_cnt := length(p_pivot_cols) -
length(REPLACE(p_pivot_cols, ',')) + 1;
FOR i IN 1 .. v_keep_cnt LOOP
v_keep(i) := f_split_str(p_keep_cols, ',', i);
END LOOP;
FOR j IN 1 .. v_pivot_cnt LOOP
v_pivot(j) := f_split_str(p_pivot_cols, ',', j);
END LOOP;
v_sql := 'select max(count(*)) from ' || p_table || ' group by ';
FOR i IN 1 .. v_keep.LAST LOOP
v_sql := v_sql || v_keep(i) || ',';
END LOOP;
v_sql := rtrim(v_sql, ',');
EXECUTE IMMEDIATE v_sql
INTO v_max_cols;
v_partition := 'select ';
FOR x IN 1 .. v_keep.COUNT LOOP
v_partition1 := v_partition1 || v_keep(x) || ',';
END LOOP;
FOR y IN 1 .. v_pivot.COUNT LOOP
v_partition2 := v_partition2 || v_pivot(y) || ',';
END LOOP;
v_partition1 := rtrim(v_partition1, ',');
v_partition2 := rtrim(v_partition2, ',');
v_partition := v_partition || v_partition1 || ',' || v_partition2 ||
', row_number() over (partition by ' || v_partition1 ||
' order by ' || v_partition2 || ') rn from ' || p_table;
v_partition := rtrim(v_partition, ',');
v_sql := 'select ';
FOR i IN 1 .. v_keep.COUNT LOOP
v_sql := v_sql || v_keep(i) || ',';
END LOOP;
FOR i IN 1 .. v_max_cols LOOP
FOR j IN 1 .. v_pivot.COUNT LOOP
v_sql := v_sql || ' max(decode(rn,' || i || ',' || v_pivot(j) ||
',null))' || v_pivot(j) || '_' || i || ',';
END LOOP;
END LOOP;
IF p_where IS NOT NULL THEN
v_sql := rtrim(v_sql, ',') || ' from (' || v_partition || ' ' ||
p_where || ') group by ';
ELSE
v_sql := rtrim(v_sql, ',') || ' from (' || v_partition ||
') group by ';
END IF;
FOR i IN 1 .. v_keep.COUNT LOOP
v_sql := v_sql || v_keep(i) || ',';
END LOOP;
v_sql := rtrim(v_sql, ',');
p_print_sql(v_sql);
OPEN p_refc FOR v_sql;
EXCEPTION
WHEN OTHERS THEN
OPEN p_refc FOR
SELECT 'x' FROM dual WHERE 0 = 1;
END;
PROCEDURE p_rows_column_real(p_table IN VARCHAR2,
p_keep_cols IN VARCHAR2,
p_pivot_col IN VARCHAR2,
p_pivot_val IN VARCHAR2,
p_where IN VARCHAR2 DEFAULT NULL,
p_refc IN OUT refc) IS
v_sql VARCHAR2(4000);
TYPE v_keep_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_keep v_keep_ind_by;
TYPE v_pivot_ind_by IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
v_pivot v_pivot_ind_by;
v_keep_cnt INT;
v_group_by VARCHAR2(2000);
BEGIN
v_keep_cnt := length(p_keep_cols) - length(REPLACE(p_keep_cols, ',')) + 1;
FOR i IN 1 .. v_keep_cnt LOOP
v_keep(i) := f_split_str(p_keep_cols, ',', i);
END LOOP;
v_sql := 'select ' || 'cast(' || p_pivot_col ||
' as varchar2(200)) as ' || p_pivot_col || ' from ' || p_table ||
' group by ' || p_pivot_col;
EXECUTE IMMEDIATE v_sql BULK COLLECT
INTO v_pivot;
FOR i IN 1 .. v_keep.COUNT LOOP
v_group_by := v_group_by || v_keep(i) || ',';
END LOOP;
v_group_by := rtrim(v_group_by, ',');
v_sql := 'select ' || v_group_by || ',';
FOR x IN 1 .. v_pivot.COUNT LOOP
v_sql := v_sql || ' max(decode(' || p_pivot_col || ',' || chr(39) ||
v_pivot(x) || chr(39) || ',' || p_pivot_val ||
',null)) as "' || v_pivot(x) || '",';
END LOOP;
v_sql := rtrim(v_sql, ',');
IF p_where IS NOT NULL THEN
v_sql := v_sql || ' from ' || p_table || p_where || ' group by ' ||
v_group_by;
ELSE
v_sql := v_sql || ' from ' || p_table || ' group by ' || v_group_by;
END IF;
p_print_sql(v_sql);
OPEN p_refc FOR v_sql;
EXCEPTION
WHEN OTHERS THEN
OPEN p_refc FOR
SELECT 'x' FROM dual WHERE 0 = 1;
END;
END;
/
4.
多列转换成字符串
CREATE TABLE t_col_str AS
SELECT * FROM t_col_row;
这个比较简单,用||或concat函数可以实现:
SELECT concat('a','b') FROM dual;
4.1
|| OR CONCAT
适用范围:8i,9i,10g及以后版本
SELECT * FROM t_col_str;
SELECT ID,c1||','||c2||','||c3 AS c123
FROM t_col_str;
5.
多行转换成字符串
CREATE TABLE t_row_str(
ID INT,
col VARCHAR2(10));
INSERT INTO t_row_str VALUES(1,'a');
INSERT INTO t_row_str VALUES(1,'b');
INSERT INTO t_row_str VALUES(1,'c');
INSERT INTO t_row_str VALUES(2,'a');
INSERT INTO t_row_str VALUES(2,'d');
INSERT INTO t_row_str VALUES(2,'e');
INSERT INTO t_row_str VALUES(3,'c');
COMMIT;
SELECT * FROM t_row_str;
5.1
MAX + DECODE
适用范围:8i,9i,10g及以后版本
SELECT id,
MAX(decode(rn, 1, col, NULL)) ||
MAX(decode(rn, 2, ',' || col, NULL)) ||
MAX(decode(rn, 3, ',' || col, NULL)) str
FROM (SELECT id,
col,
row_number() over(PARTITION BY id ORDER BY col) AS rn
FROM t_row_str) t
GROUP BY id
ORDER BY 1;
5.2
ROW_NUMBER + LEAD
适用范围:8i,9i,10g及以后版本
SELECT id, str
FROM (SELECT id,
row_number() over(PARTITION BY id ORDER BY col) AS rn,
col || lead(',' || col, 1) over(PARTITION BY id ORDER BY col) ||
lead(',' || col, 2) over(PARTITION BY id ORDER BY col) ||
lead(',' || col, 3) over(PARTITION BY id ORDER BY col) AS str
FROM t_row_str)
WHERE rn = 1
ORDER BY 1;
5.3
MODEL
适用范围:10g及以后版本
SELECT id, substr(str, 2) str FROM t_row_str
MODEL
RETURN UPDATED ROWS
PARTITION BY(ID)
DIMENSION BY(row_number() over(PARTITION BY ID ORDER BY col) AS rn)
MEASURES (CAST(col AS VARCHAR2(20)) AS str)
RULES UPSERT
ITERATE(3) UNTIL( presentv(str[iteration_number+2],1,0)=0)
(str[0] = str[0] || ',' || str[iteration_number+1])
ORDER BY 1;
5.4
SYS_CONNECT_BY_PATH
适用范围:8i,9i,10g及以后版本
SELECT t.id id, MAX(substr(sys_connect_by_path(t.col, ','), 2)) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM t_row_str) t
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id
GROUP BY t.id;
适用范围:10g及以后版本
SELECT t.id id, substr(sys_connect_by_path(t.col, ','), 2) str
FROM (SELECT id, col, row_number() over(PARTITION BY id ORDER BY col) rn
FROM t_row_str) t
WHERE connect_by_isleaf = 1
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND id = PRIOR id;
5.5
WMSYS.WM_CONCAT
适用范围:10g及以后版本
这个函数预定义按','分隔字符串,若要用其他符号分隔可以用,replace将','替换。
SELECT id, REPLACE(wmsys.wm_concat(col), ',', '/') str
FROM t_row_str
GROUP BY id;
6.
字符串转换成多列
其实际上就是一个字符串拆分的问题。
CREATE TABLE t_str_col AS
SELECT ID,c1||','||c2||','||c3 AS c123
FROM t_col_str;
SELECT * FROM t_str_col;
6.1
SUBSTR + INSTR
适用范围:8i,9i,10g及以后版本
SELECT id,
c123,
substr(c123, 1, instr(c123 || ',', ',', 1, 1) - 1) c1,
substr(c123,
instr(c123 || ',', ',', 1, 1) + 1,
instr(c123 || ',', ',', 1, 2) - instr(c123 || ',', ',', 1, 1) - 1) c2,
substr(c123,
instr(c123 || ',', ',', 1, 2) + 1,
instr(c123 || ',', ',', 1, 3) - instr(c123 || ',', ',', 1, 2) - 1) c3
FROM t_str_col
ORDER BY 1;
6.2
REGEXP_SUBSTR
适用范围:10g及以后版本
SELECT id,
c123,
rtrim(regexp_substr(c123 || ',', '.*?' || ',', 1, 1), ',') AS c1,
rtrim(regexp_substr(c123 || ',', '.*?' || ',', 1, 2), ',') AS c2,
rtrim(regexp_substr(c123 || ',', '.*?' || ',', 1, 3), ',') AS c3
FROM t_str_col
ORDER BY 1;
7.
字符串转换成多行
CREATE TABLE t_str_row AS
SELECT id,
MAX(decode(rn, 1, col, NULL)) ||
MAX(decode(rn, 2, ',' || col, NULL)) ||
MAX(decode(rn, 3, ',' || col, NULL)) str
FROM (SELECT id,
col,
row_number() over(PARTITION BY id ORDER BY col) AS rn
FROM t_row_str) t
GROUP BY id
ORDER BY 1;
SELECT * FROM t_str_row;
7.1
UNION ALL
适用范围:8i,9i,10g及以后版本
SELECT id, 1 AS p, substr(str, 1, instr(str || ',', ',', 1, 1) - 1) AS cv
FROM t_str_row
UNION ALL
SELECT id,
2 AS p,
substr(str,
instr(str || ',', ',', 1, 1) + 1,
instr(str || ',', ',', 1, 2) - instr(str || ',', ',', 1, 1) - 1) AS cv
FROM t_str_row
UNION ALL
SELECT id,
3 AS p,
substr(str,
instr(str || ',', ',', 1, 1) + 1,
instr(str || ',', ',', 1, 2) - instr(str || ',', ',', 1, 1) - 1) AS cv
FROM t_str_row
ORDER BY 1, 2;
适用范围:10g及以后版本
SELECT id, 1 AS p, rtrim(regexp_substr(str||',', '.*?' || ',', 1, 1), ',') AS cv
FROM t_str_row
UNION ALL
SELECT id, 2 AS p, rtrim(regexp_substr(str||',', '.*?' || ',', 1, 2), ',') AS cv
FROM t_str_row
UNION ALL
SELECT id, 3 AS p, rtrim(regexp_substr(str||',', '.*?' || ',',1,3), ',') AS cv
FROM t_str_row
ORDER BY 1, 2;
7.2
VARRAY
适用范围:8i,9i,10g及以后版本
要创建一个可变数组:
CREATE OR REPLACE TYPE ins_seq_type IS VARRAY(8) OF NUMBER;
SELECT * FROM TABLE(ins_seq_type(1, 2, 3, 4, 5));
SELECT t.id,
c.column_value AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.column_value) + 1,
instr(t.ca, ',', 1, c.column_value + 1) -
(instr(t.ca, ',', 1, c.column_value) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t
INNER JOIN TABLE(ins_seq_type(1, 2, 3)) c ON c.column_value <=
t.cnt
ORDER BY 1, 2;
7.3
SEQUENCE SERIES
这类方法主要是要产生一个连续的整数列,产生连续整数列的方法有很多,主要有:
CONNECT BY,ROWNUM+all_objects,CUBE等。
适用范围:8i,9i,10g及以后版本
SELECT t.id,
c.lv AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.lv) + 1,
instr(t.ca, ',', 1, c.lv + 1) -
(instr(t.ca, ',', 1, c.lv) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t,
(SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 5) c
WHERE c.lv <= t.cnt
ORDER BY 1, 2;
SELECT t.id,
c.rn AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.rn) + 1,
instr(t.ca, ',', 1, c.rn + 1) -
(instr(t.ca, ',', 1, c.rn) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t,
(SELECT rownum rn FROM all_objects WHERE rownum <= 5) c
WHERE c.rn <= t.cnt
ORDER BY 1, 2;
SELECT t.id,
c.cb AS p,
substr(t.ca,
instr(t.ca, ',', 1, c.cb) + 1,
instr(t.ca, ',', 1, c.cb + 1) -
(instr(t.ca, ',', 1, c.cb) + 1)) AS cv
FROM (SELECT id,
',' || str || ',' AS ca,
length(str || ',') - nvl(length(REPLACE(str, ',')), 0) AS cnt
FROM t_str_row) t,
(SELECT rownum cb FROM (SELECT 1 FROM dual GROUP BY CUBE(1, 2))) c
WHERE c.cb <= t.cnt
ORDER BY 1, 2;
适用范围:10g及以后版本
SELECT t.id,
c.lv AS p,
rtrim(regexp_substr(t.str || ',', '.*?' || ',', 1, c.lv), ',') AS cv
FROM (SELECT id,
str,
length(regexp_replace(str || ',', '[^' || ',' || ']', NULL)) AS cnt
FROM t_str_row) t
INNER JOIN (SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 5) c ON c.lv <= t.cnt
ORDER BY 1, 2;
7.4
HIERARCHICAL + DBMS_RANDOM
适用范围:10g及以后版本
SELECT id,
LEVEL AS p,
rtrim(regexp_substr(str || ',', '.*?' || ',', 1, LEVEL), ',') AS cv
FROM t_str_row
CONNECT BY id = PRIOR id
AND PRIOR dbms_random.VALUE IS NOT NULL
AND LEVEL <=
length(regexp_replace(str || ',', '[^' || ',' || ']', NULL))
ORDER BY 1, 2;
7.5
HIERARCHICAL + CONNECT_BY_ROOT
适用范围:10g及以后版本
SELECT id,
LEVEL AS p,
rtrim(regexp_substr(str || ',', '.*?' || ',', 1, LEVEL), ',') AS cv
FROM t_str_row
CONNECT BY id = connect_by_root id
AND LEVEL <=
length(regexp_replace(str || ',', '[^' || ',' || ']', NULL))
ORDER BY 1, 2;
7.6
MODEL
适用范围:10g及以后版本
SELECT id, p, cv FROM t_str_row
MODEL
RETURN UPDATED ROWS
PARTITION BY(ID)
DIMENSION BY( 0 AS p)
MEASURES( str||',' AS cv)
RULES UPSERT
(cv
[ FOR p
FROM 1 TO length(regexp_replace(cv[0],'[^'||','||']',null))
例子:
SELECT t.dutyname , substr(sys_connect_by_path(t.username, ','), 2) str
FROM (SELECT dutyname, username, row_number() over(PARTITION BY dutyname ORDER BY username) rn
FROM test) t
WHERE connect_by_isleaf = 1
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1
AND dutyname = PRIOR dutyname;
Https是什么?
Https是基于安全目的的Http通道,其安全基础由SSL层来保证。最初由netscape公司研发,主要提供了通讯双方的身份认证和加密通信方法。现在广泛应用于互联网上安全敏感通讯。
Https与Http主要区别
协议基础不同:Https在Http下加入了SSL层,
通讯方式不同:Https在数据通信之前需要客户端、服务器进行握手(身份认证),建立连接后,传输数据经过加密,通信端口443。
Http传输数据不加密,明文,通信端口80。
SSL协议基础
SSL协议位于TCP/IP协议与各种应用层协议之间,本身又分为两层:
SSL记录协议(SSL Record Protocol):建立在可靠传输层协议(TCP)之上,为上层协议提供数据封装、压缩、加密等基本功能。
SSL握手协议(SSL Handshake Procotol):在SSL记录协议之上,用于实际数据传输前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
SSL协议通信过程
(1) 浏览器发送一个连接请求给服务器;服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端;
(2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。
(3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。
(4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接;
(5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器;
(6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器;
(7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。
(8) 接下来的数据传输都使用该对称密钥key进行加密。
上面所述的是双向认证 SSL 协议的具体通讯过程,服务器和用户双方必须都有证书。由此可见,SSL协议是通过非对称密钥机制保证双方身份认证,并完成建立连接,在实际数据通信时通过对称密钥机制保障数据安全性
1. CouchDB
•所用语言: Erlang
•特点:DB一致性,易于使用
•使用许可: Apache
•协议: HTTP/REST
•双向数据复制,
•持续进行或临时处理,
•处理时带冲突检查,
•因此,采用的是master-master复制(见编注2)
•MVCC – 写操作不阻塞读操作
•可保存文件之前的版本
•Crash-only(可靠的)设计
•需要不时地进行数据压缩
•视图:嵌入式 映射/减少
•格式化视图:列表显示
•支持进行服务器端文档验证
•支持认证
•根据变化实时更新
•支持附件处理
•因此, CouchApps(独立的 js应用程序)
•需要 jQuery程序库
最佳应用场景:适用于数据变化较少,执行预定义查询,进行数据统计的应用程序。适用于需要提供数据版本支持的应用程序。 例如: CRM、CMS系统。
master-master复制对于多站点部署是非常有用的。 (编注2:master-master复制:是一种数据库同步方法,允许数据在一组计算机之间共享数据,
并且可以通过小组中任意成员在组内进行数据更新。)
2. Redis
•所用语言:C/C++
•特点:运行异常快
•使用许可: BSD
•协议:类 Telnet
•有硬盘存储支持的内存数据库,
•但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!)
•Master-slave复制(见编注3)
•虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。
•INCR & co (适合计算极限值或统计数据)
•支持 sets(同时也支持 union/diff/inter)
•支持列表(同时也支持队列;阻塞式 pop操作)
•支持哈希表(带有多个域的对象)
•支持排序 sets(高得分表,适用于范围查询)
•Redis支持事务 •支持将数据设置成过期数据(类似快速缓冲区设计)
•Pub/Sub允许用户实现消息机制
最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。 例如:股票价格、数据分析、实时数据搜集、实时通讯。
(编注3:Master-slave复制:如果同一时刻只有一台服务器处理所有的复制请求,这被称为 Master-slave复制,通常应用在需要提供高可用性的服务器集群。)
3. MongoDB
•所用语言:C++
•特点:保留了SQL一些友好的特性(查询,索引)。
•使用许可: AGPL(发起者: Apache)
•协议: Custom, binary( BSON)
•Master/slave复制(支持自动错误恢复,使用 sets 复制)
•内建分片机制
•支持 javascript表达式查询
•可在服务器端执行任意的 javascript函数
•update-in-place支持比CouchDB更好
•在数据存储时采用内存到文件映射
•对性能的关注超过对功能的要求
•建议最好打开日志功能(参数 –journal)
•在32位操作系统上,数据库大小限制在约2.5Gb
•空数据库大约占 192Mb
•采用 GridFS存储大数据或元数据(不是真正的文件系统)
最佳应用场景:适用于需要动态查询支持;需要使用索引而不是 map/reduce功能;需要对大数据库有性能要求;需要使用 CouchDB但因为数据改变太频繁而占满内存的应用程序。 例如:你本打算采用 MySQL或 PostgreSQL,但因为它们本身自带的预定义栏让你望而却步。
4. Riak
•所用语言:Erlang和C,以及一些Javascript
•特点:具备容错能力
•使用许可: Apache
•协议: HTTP/REST或者 custom binary
•可调节的分发及复制(N, R, W)
•用 JavaScript or Erlang在操作前或操作后进行验证和安全支持。
•使用JavaScript或Erlang进行 Map/reduce
•连接及连接遍历:可作为图形数据库使用
•索引:输入元数据进行搜索(1.0版本即将支持)
•大数据对象支持( Luwak)
•提供“开源”和“企业”两个版本
•全文本搜索,索引,通过 Riak搜索服务器查询( beta版)
•支持Masterless多站点复制及商业许可的 SNMP监控
最佳应用场景:适用于想使用类似 Cassandra(类似Dynamo)数据库但无法处理 bloat及复杂性的情况。适用于你打算做多站点复制,但又需要对单个站点的扩展性,可用性及出错处理有要求的情况。 例如:销售数据搜集,工厂控制系统;对宕机时间有严格要求;可以作为易于更新的 web服务器使用。
5. Membase
•所用语言: Erlang和C
•特点:兼容 Memcache,但同时兼具持久化和支持集群
•使用许可: Apache 2.0
•协议:分布式缓存及扩展
•非常快速(200k+/秒),通过键值索引数据
•可持久化存储到硬盘
•所有节点都是唯一的( master-master复制)
•在内存中同样支持类似分布式缓存的缓存单元
•写数据时通过去除重复数据来减少 IO
•提供非常好的集群管理 web界面
•更新软件时软无需停止数据库服务
•支持连接池和多路复用的连接代理
最佳应用场景:适用于需要低延迟数据访问,高并发支持以及高可用性的应用程序 例如:低延迟数据访问比如以广告为目标的应用,高并发的 web 应用比如网络游戏(例如 Zynga)
6. Neo4j
•所用语言: Java
•特点:基于关系的图形数据库
•使用许可: GPL,其中一些特性使用 AGPL/商业许可
•协议: HTTP/REST(或嵌入在 Java中)
•可独立使用或嵌入到 Java应用程序
•图形的节点和边都可以带有元数据
•很好的自带web管理功能
•使用多种算法支持路径搜索
•使用键值和关系进行索引
•为读操作进行优化
•支持事务(用 Java api)
•使用 Gremlin图形遍历语言
•支持 Groovy脚本
•支持在线备份,高级监控及高可靠性支持使用 AGPL/商业许可 最佳应用场景:适用于图形一类数据。这是 Neo4j与其他nosql数据库的最显著区别 例如:社会关系,公共交通网络,地图及网络拓谱
7. Cassandra
•所用语言: Java
•特点:对大型表格和 Dynamo支持得最好
•使用许可: Apache
•协议: Custom, binary (节约型)
•可调节的分发及复制(N, R, W)
•支持以某个范围的键值通过列查询
•类似大表格的功能:列,某个特性的列集合
•写操作比读操作更快
•基于 Apache分布式平台尽可能地 Map/reduce
•我承认对 Cassandra有偏见,一部分是因为它本身的臃肿和复杂性,也因为 Java的问题(配置,出现异常,等等)
最佳应用场景:当使用写操作多过读操作(记录日志)如果每个系统组建都必须用 Java编写(没有人因为选用 Apache的软件被解雇) 例如:银行业,金融业(虽然对于金融交易不是必须的,但这些产业对数据库的要求会比它们更大)写比读更快,所以一个自然的特性就是实时数据分析
8. HBase (配合 ghshephard使用)
•所用语言: Java
•特点:支持数十亿行X上百万列
•使用许可: Apache
•协议:HTTP/REST (支持 Thrift,见编注4)
•在 BigTable之后建模
•采用分布式架构 Map/reduce
•对实时查询进行优化
•高性能 Thrift网关
•通过在server端扫描及过滤实现对查询操作预判
•支持 XML, Protobuf, 和binary的HTTP
•Cascading, hive, and pig source and sink modules
•基于 Jruby( JIRB)的shell
•对配置改变和较小的升级都会重新回滚
•不会出现单点故障
•堪比MySQL的随机访问性能 最佳应用场景:适用于偏好BigTable:)并且需要对大数据进行随机、实时访问的场合。 例如: Facebook消息数据库(更多通用的用例即将出现)
SGA中的第三个组成部分是共享池。共享池是对sql ,pl/sql 程序进行语法分析、编译、执行的内存区域。共享池包括库缓冲区(library cache)、数据字典缓冲区(Data Directory Cache)用户全局区(User Global Area)。其中库缓冲区含有Sql 语句的分析码、执行计划;数据字典缓冲区含有从数据字典中得到的表、列定义、权限。用户全局区包含用户的MTS 会话信息。
共享池主要用于对SQL 、pl/sql 程序语句进行语法分析、编译、执行、所以,如果应用中药运行大量存储过程或包,则要增加共享池的尺寸。共享池的大小由参数SHARE_POOL_SIZE确定。要了解共享池大小,可以用以下方法:
方法一:
- ………
- Shared_Pool_size = 52428800
- …..
方法二:
- SQL> select name,value from v$parameter where name like ‘%size’;
方法三:
- SQL> show parameter share_pool_size
共享池应计算存储过程、包等的成功率。
可以查询数据字典 v$rowcache 了解数据字典的成功与失败次数。
- SQL> select sum(gets) “dictionary gets”,
- Sum(getmisses) “dictionary cache getmisses”
- From v$rowcache ;
其中gets 表示读取某一类数据字典的成功次数,getsmisses 表示读取某一类数据字典的失败次数。此外还可以通过查询结果计算共享池中读取数据字典的成功率
- SQL> select parameter, get, getmisses, getmisses/(getmisses+gets)*100 “miss ratio”,
- (1- (sum(getmisses)/(sum(getmisses)+sum(gets)) ) ) *100 “hit ratio”
- From v$rowcache
- Where gets+getmisses<>0
- Group by parameter,gets,getmisses;
查询数据字典 v$librarycache 可以计算共享池中库缓存的失败率,结果应该小于1%。
- SQL>select sum(pins) “ total pins”, sum(reloads) “ total reloads”, sum(reloads)/sum(pins)*100 libarycache from v$librarycache;
其中 total pins 表示驻留内存的次数, total reloads 表示重新加载到内存的次数,librarycache 表示失败率。
上面分析了系统全局区的三个组成部分-----数据缓冲区、日志缓冲区及共享池,如果要得到SGA的总大小,可以在SQL*Plus中使用show sga 命令。
SQL>show sga或查询数据字典
SQL> select * from v$sga;
如果要查询某个参数的大小,可以查询数据字典v_$sagstat,通过计算可以知道sga的使用空间与空闲空间的比。
- SQL>col OBJECT_NAME format a20
- SQL> col 空闲空间百分比(%) format 90.99
- SQL> select name,
- Sgasize/1024/1024 “allocated(M)” ,
- Bytes/1024 “空闲空间(k)” ,
- Round(bytes/sagsize*100,2) “空闲空间百分比(%)”
- From ( select sum(bytes) sgasize from sys.v_$sgastat) s ,sys.v_$sgastat f
- Where f.name=’free memory’ ;
关于Oracle 10g内存结构之共享池的相关知识及使用方法就介绍到这里了,希望本次的介绍能够对您有所收获!
我们知道,内存结构是Oracle体系结构中最重要的部分之一。按照系统对内存使用方法的不同,可以分为系统全局区(SGA)、程序全局区(PGA)、排序区(Sort Area)、大池(Large Pool)、及java池(java Pool),本文我们先介绍一下Oracle 10g内存结构之系统全局区的内容,接下来我们就开始介绍这部分内容。
系统全局区(System Global Area)
它是一组为系统分配的内存共享结构,可以包含一个数据库实例的数据和控制信息。如果多个用户连接到一个实例,在实例的系统全局区中,数据可以被多个用户共享,所以又称共享全局区。系统全局区按其作用不同,可以分为数据缓冲区、日志缓冲区及共享池。
数据缓冲区:
数据缓冲区用于从磁盘读入的数据,供所有用户共享。
修改的数据、插入的数据存储在数据缓冲区中,修改完成或DBWR进程的其他条件引发时,数据被写入数据文件
数据缓冲区工作原理:
LRU (Least recently used):最近最少使用原则的缩写,是一种数据缓冲区的一种管理机制,,只保留最近数据,不保留旧数据。
Dirty:表示脏数据,脏数据是修改后还没有写到数据文件的数据。
Oracle10g 的数据库内存的设置参数不再由DB_BLOCK_BUFFERS确定,而是由oracle的新参数DB_CACHE_SIZE 和DB_nK_CACHE_SIZE确定,不同的数据段可以使用不同的数据块。大表可以存储在大的数据块表空间中,小表可以存储在小的数据块表空间中,以优化i/o性能。对于系统表空间、临时表空间、及其它默认设置的表空间,可以使用标准的数据块DB_BLOCK_SIZE确定。
标准数据块DB_BLOCK_SIZE用于系统表空间及默认表空间,其他表空间可以使用非标准数据块BLOCKSIZE(创建表空间时使用),其值分别为 2k 4k 8k 16k 32k ,非标准数据块的数据缓冲区使用参数DB_Nk_CACHE_SIZE确定。
需要注意的是BLOCKSIZE不得用于标准块。如果设置了DB_BLOCK_SIZE=2048,则不得设置DB_2K_CACHE_SIZE,标准块必须使用参数DB_CACHE_SIZE 来设置。同时可以在线修改数据缓冲区参数:SQL> alter system set db_2k_cache_size = 10M ;如果要查询数据缓冲区大小,可以如下:SQL> show parameter db。
在创建不同数据块表空间时,要使用参数BLOCKSIZE指出数据块的大小,同时在参数文件中要使用DB_Nk_CACHE_SIZE 进行配置,与BLOCKSIZE的个数相对应,否则会出现错误。
设置动态内存时,可以将多个参数全部写入参数文件,格式如下:
- # cache and i/o
- DB_BLOCK_SIZE=4096
- DB_CACHE_SIZE=20971520
- DB_2K_CACHE_SIZE=8M
- DB_8K_CACHE_SIZE=4M
- ……..
其中,参数 DB_CACHE_SIZE 只适用于系统表空间、临时表空间、及默认表空间,DB_2K_CACHE_SIZE 适合 BLOCKSIZE 为2K的表空间。8K 也是一样的道理。
数据缓冲区对数据库德存取速度又直接影响。一般的缓冲区命中率应该在90% 以上。例如,使用数据字典 v$sysstat 计算数据缓冲区命中率:
- SQL> select a.value+b.value “logical_reads” , c.value “phys_reads”,
- Round(100* ( ( a.value+b.value)- c.value) /
- ( a.value+b.value ) ) “buffer hit radio “
- From v$sysstat a, v$sysstat b,v$sysstat c
- Where a.statistic#=38 and b.statistic#=39 and c.statistic#=40;
下面是计算数据缓冲命中率的另一种方法:
- SQL> select name, value
- From v$sysstat
- Where name in ( ‘session logical reads’,’physical reads’,physical reads direct’, ‘physical reads direct (lob)’);
其中:Session logical reads 为读的总量。Physical reads为从数据文件读。Physical reads direct 为从缓冲区读(不含lobs)。Physical reads direct (lobs) 为从缓冲区读(含lobs)。Hit Ratio = 1- ( ( physical reads- physical reads direct – physical reads direct(lob) ) /session logical reads) = 95%。
日志缓冲区
日志缓冲区用来存储数据库的修改信息。日志信息首先在日志缓冲区中产生,当日志缓冲区的日志达到一定数量时,由日志写入进程LGWR将日志数据写入日志文件组,再经过切换,由归档进程ARCH将日志数据写入归档介质。
日志缓冲区大小由参数LOG_BUFFER确定,要查询日志缓冲区大小可以用以下方法:
方法一:参数文件中:
- ……
- Processes = 150
- Parallel_max_servers = 5
- Log_buffer = 32768
- ……..
方法二:
- SQL> select name,value from v$parameter where name like ‘%buffer’;
方法三:
- SQL> show parameter log_buffer
对于日志缓冲区而言可以计算失败率,使用数据字典v$latch 计算日志缓冲区的失败率
- SQL>select name,gets,misses,immediate_gets,immediate_misses,
- Decode(gets,0,0,misses/gets*100) ratiol,
- Decode (immediate_gets+immediate_misses,0,0,
- immediate_misses/(immediate_gets+immediate_misses)*100) ratio2
- from v$latch
- where name in (‘redo allocation’, ‘redo copy’);
其中
Gets 表示成功等待日志缓冲区的次数。
Immediate gets 表示成功立即得到日志缓冲区的次数。
Immediate misses 表示未成功立即得到日志缓冲区的次数。
等待表示日志在进入日志缓冲区时,因为日志缓冲区过小而没有空闲空间,所以日志缓冲区的失败可以表示日志缓冲区是否足够大,不够大时,用户的日志写将产生等待过程。日志缓冲区的失败率应该小于1%。
此外,可以查询用户进程等待日志缓冲区时的次数,通过数据字典v$sysstat 得到:
- SQL> select name,value from v$sysstat
- Where name = ‘ redo buffer allocation retries’ ;
关于Oracle 10g内存结构之系统全局区的相关知识就介绍到这里了,希望本次的介绍能够对您有所收获!
本文是关于Oracle数据库调试与优化方面的文章,主要介绍Oracle数据库中命中率相关的问题,包括不同的算法之间性能的比对。关于Oracle中各个命中率的计算以及相关的调优 1) Library Cache的命中率: 计算公式:Library Cache Hit Ratio = sum(pinhits) / sum(pi
本文是关于Oracle数据库调试与优化方面的文章,主要介绍Oracle数据库中命中率相关的问题,包括不同的算法之间性能的比对。关于Oracle中各个命中率的计算以及相关的调优
1)Library Cache的命中率:计算公式:Library Cache Hit Ratio = sum(pinhits) / sum(pins)
1 |
SELECT SUM (pinhits)/ sum (pins) FROM V$LIBRARYCACHE;
|
通常在98%以上,否则,需要要考虑加大共享池,绑定变量,修改cursor_sharing等参数。
2)计算共享池内存使用率:
1
2 |
SELECT (1 - ROUND(BYTES / (&TSP_IN_M * 1024 * 1024), 2)) * 100 || '%'
FROM V$SGASTAT WHERE NAME = 'free memory' AND POOL = 'shared pool' ;
|
其中: &TSP_IN_M是你的总的共享池的SIZE(M)
共享池内存使用率,应该稳定在75%-90%间,太小浪费内存,太大则内存不足。
查询空闲的共享池内存:
1
2 |
SELECT * FROM V$SGASTAT WHERE
NAME = 'free memory' AND POOL = 'shared pool' ;
|
3)db buffer cache命中率:计算公式:Hit ratio = 1 - [physical reads/(block gets + consistent gets)]
1
2
3 |
SELECT NAME , PHYSICAL_READS, DB_BLOCK_GETS, CONSISTENT_GETS,
1 - (PHYSICAL_READS / (DB_BLOCK_GETS + CONSISTENT_GETS))
"Hit Ratio" FROM V$BUFFER_POOL_STATISTICS WHERE NAME = 'DEFAULT' ;
|
通常应在90%以上,否则,需要调整,加大DB_CACHE_SIZE
外一种计算命中率的方法(摘自ORACLE官方文档<<数据库性能优化>>):
命中率的计算公式为:
Hit Ratio = 1 - ((physical reads - physical reads direct - physical reads direct (lob)) / (db block gets + consistent gets - physical reads direct - physical reads direct (lob))
分别代入上一查询中的结果值,就得出了Buffer cache的命中率
1
2
3
4 |
SELECT NAME , VALUE FROM V$SYSSTAT WHERE NAME IN (
'session logical reads' , 'physical reads' ,
'physical reads direct' , 'physical reads direct (lob)' ,
'db block gets' , 'consistent gets' );
|
4)数据缓冲区命中率:
1
2
3
4
5 |
SQL> select value from v$sysstat where name = 'physical reads' ;
SQL> select value from v$sysstat where name = 'physical reads direct' ;
SQL> select value from v$sysstat where name = 'physical reads direct (lob)' ;
SQL> select value from v$sysstat where name = 'consistent gets' ;
SQL> select value from v$sysstat where name = 'db block gets' ;
|
这里命中率的计算应该是令 x = physical reads direct + physical reads direct (lob),命中率 =100 - ( physical reads - x) / (consistent gets + db block gets - x)*100,通常如果发现命中率低于90%,则应该调整应用可可以考虑是否增大数据缓冲区
5)共享池的命中率:
1
2 |
select sum (pinhits-reloads)/ sum (pins)*100 "hit radio"
from v$librarycache;
|
假如共享池的命中率低于95%,就要考虑调整应用(通常是没使用bind var )或者增加内存
6)计算在内存中排序的比率:
1 |
SELECT * FROM v$sysstat t WHERE NAME = 'sorts (memory)' ;
|
查询内存排序数
1 |
SELECT * FROM v$sysstat t WHERE NAME = 'sorts (disk)' ;
|
查询磁盘排序数caculate sort in memory ratio
1 |
SELECT round(&sort_in_memory/(&sort_in_memory+&sort_in_disk),4)*100|| '%' FROM dual;
|
此比率越大越好,太小整要考虑调整,加大PGA
7)PGA的命中率:
计算公式:BP x 100 / (BP + EBP)
BP: bytes processed
EBP: extra bytes read/written
1 |
SELECT * FROM V$PGASTAT WHERE NAME = 'cache hit percentage' ;
|
或者从OEM的图形界面中查看
我们可以查看一个视图以获取Oracle的建议值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
SELECT round(PGA_TARGET_FOR_ESTIMATE/1024/1024) target_mb,
ESTD_PGA_CACHE_HIT_PERCENTAGE cache_hit_perc, ESTD_OVERALLOC_COUNT
FROM V$PGA_TARGET_ADVICE;
The output of this query might look like the following:
TARGET_MB CACHE_HIT_PERC ESTD_OVERALLOC_COUNT
63 23 367
125 24 30
250 30 3
375 39 0
500 58 0
600 59 0
700 59 0
800 60 0
900 60 0
|
在此例中:PGA至少要分配375M
我个人认为PGA命中率不应该低于50%
以下的SQL统计sql语句执行在三种模式的次数: optimal memory size, one-pass memory size, multi-pass memory size:
1
2
3 |
SELECT name profile, cnt, decode(total, 0, 0, round(cnt*100/total,4))
percentage FROM ( SELECT name , value cnt, ( sum (value) over ())
total FROM V$SYSSTAT WHERE name like 'workarea exec%' );
|
8)共享区字典缓存区命中率
计算公式:SUM(gets - getmisses - usage -fixed) / SUM(gets)
命中率应大于0.85
1 |
select sum (gets-getmisses-usage-fixed)/ sum (gets) from v$rowcache;
|
9)数据高速缓存区命中率
计算公式:1-(physical reads / (db block gets + consistent gets))
命中率应大于0.90最好
1 |
select name ,value from v$sysstat where name in ( 'physical reads' , 'db block gets' , 'consistent gets' );
|
10)共享区库缓存区命中率
计算公式:SUM(pins - reloads) / SUM(pins)
命中率应大于0.99
1 |
select sum (pins-reloads)/ sum (pins) from v$librarycache;
|
11)检测回滚段的争用
SUM(waits)值应小于SUM(gets)值的1%
1 |
select sum (gets), sum (waits), sum (waits)/ sum (gets) from v$rollstat;
|
12)检测回滚段收缩次数
1
2 |
select name ,shrinks from v$rollstat, v$rollname
where v$rollstat.usn = v$rollname.usn;
|
几个常用的检查语句
1. 查找排序最多的SQL:
1
2 |
SELECT HASH_VALUE, SQL_TEXT, SORTS, EXECUTIONS
FROM V$SQLAREA ORDER BY SORTS DESC ;
|
2.查找磁盘读写最多的SQL:
1
2
3
4 |
SELECT * FROM ( SELECT sql_text,disk_reads "total disk" ,
executions "total exec" ,disk_reads/executions "disk/exec"
FROM v$sql WHERE executions>0 and is_obsolete= 'N' ORDER BY
4 desc ) WHERE ROWNUM<11 ;
|
3.查找工作量最大的SQL(实际上也是按磁盘读写来排序的):
1
2
3
4
5
6
7
8 |
select substr(to_char(s.pct, '99.00' ), 2) || '%' load ,
s.executions executes,p.sql_text from ( select address,disk_reads,executions,pct,rank() over
( order by disk_reads desc ) ranking from ( select
address,disk_reads,executions,100 * ratio_to_report
(disk_reads) over () pct from sys.v_$sql where
command_type != 47) where disk_reads > 50 * executions) s,
ys.v_$sqltext p where s.ranking <= 5 and p.address = s.address
order by 1, s.address, p.piece;
|
4. 用下列SQL工具找出低效SQL:
1
2
3
4
5 |
select executions,disk_reads,buffer_gets,round((buffer_gets-
disk_reads)/buffer_gets,2) Hit_radio,round(disk_reads/executions,2)
reads_per_run,sql_text From v$sqlarea Where executions>0 and
buffer_gets >0 and (buffer_gets-disk_reads)/buffer_gets<0.8
Order by 4 desc ;
|
5、根据sid查看对应连接正在运行的sql
1
2
3
4
5
6
7 |
select /*+ push_subq */command_type,sql_text,sharable_mem,persistent_mem,
runtime_mem,sorts,version_count,loaded_versions,open_versions,
users_opening,executions,users_executing,loads,first_load_time,
invalidations,parse_calls,disk_reads,buffer_gets,rows_processed,
sysdate start_time,sysdate finish_time,’>’||address
sql_address,’N’status From v$sqlarea Where address=
( select sql_address from v$session where sid=&sid);
|
***************Oracle 缓冲区命中率低的分析及解决办法******************
首先确定下面的查询结果:
1,缓冲区命中率的查询(是否低于90%):
1
2
3 |
select round((1 - sum (decode( name , 'physical reads' ,value,0)) /
( sum (decode( name , 'db block gets' ,value,0)) + sum (decode( name , '
consistent gets' ,value,0))) ),4) *100 || '%' chitrati from v$sysstat;
|
2,使用率的查询(有无free状态的数据快.):
1 |
select count (*), status from v$bh group by status ;
|
3,相关等待事件的查询(是否有相关等待事件)
1
2 |
select event,total_waits from v$system_event where event
in ( 'free buffer waits' );
|
4,当前大小(是否已经很大)
1
2 |
select value/1024/1024 cache_size from v$parameter
where name = 'db_cache_size'
|
5,top等待事件分析(Db file scatered read的比率是否大)
1
2
3
4
5
6
7 |
select event ,total_waits,suml from ( select
event,total_waits,round(total_waits/sumt*100,2)|| '%' suml
from ( select event,total_waits from v$system_event ),
( select sum (total_waits) sumt from v$system_event)
order by total_waits desc ) where rownum<6 and event
not like 'rdbms%' and event not like 'pmon%' and event
not like 'SQL*Net%' and event not like 'smon%' ;
|
6,db_cache_advice建议值(9i后的新特性,可以根据他更好的调整cache_size)
1
2 |
select block_size,size_for_estimate,size_factor,
estd_physical_reads from v$db_cache_advice;
|
说明分析:
缓冲区命中率(低于90的命中率就算比较低的).
没有free不一定说明需要增加,还要结合当前cache_size的大小(我们是否还可以再增大,是否有需要增加硬件,增加开销),
空闲缓冲区等待说明进程找不到空闲缓冲区,并通过写出灰缓冲区,来加速数据库写入器生成空闲缓冲区,当DBWn将块写入磁盘后,灰数据缓冲区将被释放,以便重新使用.产生这种原因主要是:
1,DBWn可能跟不上写入灰缓冲区:i/0系统较慢,尽量将文件均匀的分布于所有设备,
2,缓冲区过小或过大。
3,可以增加db_writer_processes数量。
4,可能有很大的一个事物,或者连续的大事物
我们需要长期观察这个事件是否长期存在并数值一直在增大,如果一直在增大,则说明需要增大db_cache大小.或优化sql.
数据分散读等待,通常表现存在着与全表扫描相关的等待,逻辑读时,在内存中进行的全表扫描一般是零散地,而并非连续的被分散到缓冲区的各个部分,可能有索引丢失,或被仰制索引的存在。该等待时间在数据库会话等待多块io读取结束的时候产生,并把指定的块数离散的分布在数据缓冲区。这意味这全表扫描过多,或者io不足或争用,
存在这个事件,多数都是问题的,这说明大量的全部扫描而未采用索引.
db_cache_advice对我们调整db_cache_size大小有一定的帮助,但这只是一个参考,不一定很精确。
通过上面6种情况的综合分析,判断是否需要增加大cache_size. 或者把常用的(小)表放到keep区。
但多数的时候做这些不会解决质的问题,
而真正的问题主要是对sql语句的优化(如:是否存在大量的全表扫描等)
索引是在不需要改变程序的情况下,对数据库性能,sql语句提高的最实用的方法.
我在生产中遇到过类似的问题,200M的cache_size,命中率很低21%,但通过对sql语句的优化(添加索引,避免全表扫描),命中率增加到96%,程序运行时间由原来的2小时减少到不到10分钟.
这就提到了怎么定位高消耗的sql问题.全表扫描的问题,在这里不做细致的解说,这里只说明方法,我会在相关的章节专门介绍怎么使用这些工具
1,sql_trace跟踪session.用tkprof 分别输出磁盘读,逻辑读,运行时间长的sql进行优化.这些高消耗的sql一般都伴随着全表扫描.
2,statspack分析.在系统繁忙时期进行时间点的统计分析,产看TOP事件是否有Db file scatered read.并查看TOP sql语句是否存在问题等.
还要说一句:当然在硬件允许的情况下,尽量增大db_cache_size 减少磁盘读,但并不是越大越好,一定要根据自己的库数据量的程度来调节,因为大的db_cache_size同样会增大数据库管理的开销,当然可能开销并不会明显的影响数据库的性能,硬件价格也越来越低,这就需要我们具体问题具体分析了,在我看来物尽其用就最好了,尽量不要浪费,找到问题的本质。调优是一件很艺术的事。
***********************Oracle数据库缓冲区命中率*****************
1、查看Oracle数据库缓冲区命中率
1
2
3
4 |
select a.value + b.value "logical_reads" , c.value "phys_reads" ,
round(100 * ((a.value+b.value)-c.value) / (a.value+b.value))
"BUFFER HIT RATIO" from v$sysstat a, v$sysstat b, v$sysstat c
where a.statistic# = 40 and b.statistic# = 41 and c.statistic# = 42;
|
2、Tags: oracle
数据库缓冲区命中率:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
select value from v$sysstat where name = 'physical reads' ;
value 3714179
select value from v$sysstat where name = 'physical reads direct' ;
value 0
select value from v$sysstat where name = 'physical reads direct(lob)' ;
value 0
select value from v$sysstat where name = 'consistent gets' ;
value 856309623
select value from v$sysstat where name = 'db block gets' ;
value 19847790
|
这里命中率的计算应该是
令x=physical reads direct + physical reads direct(lob)
命中率=100-(physical reads -x)/(consistent gets +db block gets -x)*100
通常如果发现命中率低于90%,则应该调整应用可以考虑是否增大数据加
共享池的命中率
1 |
select sum (pinhits)/ sum (pins)*100 "hit radio" from v$librarycache;
|
如果共享池的命中率低于95%就要考虑调整应用(通常是没应用bind var)或者增加内存。
关于排序部分
1 |
select name ,value from v$sysstat where name like '%sort%' ;
|
如果我们发现sorts(disk)/(sorts(memory)+sorts(disk))的比例过高,则通常意味着sort_area_size部分内存教较小,可考虑调整相应的参数。
关于log_buffer
1
2 |
select name ,value from v$sysstat where name
in ( 'redo entries' , 'redo buffer allocation retries' );
|
假如redo buffer allocation retries/redo entries的比例超过1%我们就可以考虑增加log_buffer.
1.统计信息简介 统计信息主要是描述数据库中表,索引的大小,规模,数据分布状况等的一类信息。比如,表的行数,块数,平均每行的大小,索引的leaf blocks,索引字段的行数,不同值的大小等,都属于统计信息。CBO正是根据这些统计信息数据,计算出不同访问路
1.统计信息简介
统计信息主要是描述数据库中表,索引的大小,规模,数据分布状况等的一类信息。比如,表的行数,块数,平均每行的大小,索引的leaf blocks,索引字段的行数,不同值的大小等,都属于统计信息。CBO正是根据这些统计信息数据,计算出不同访问路径下,不同join 方式下,各种计划的成本,最后选择出成本最小的计划。
在CBO(基于代价的优化器模式)条件下,SQL语句的执行计划由统计信息来决定,若没有统计信息则会采取动态采样的方式决定执行计划!可以说统计信息关乎sql的执行计划是否正确,属于sql执行的指导思想,oracle的初始化参数statistics_level控制收集统计信息的级别,有三个参数值:
BASIC :收集基本的统计信息
TYPICAL:收集大部分统计信息(数据库的默认设置)
ALL:收集全部统计信息
Oracle 10g之后,Query Optimizer就已经将CBO作为默认优化器,并且Oracle官方不再支持RBO服务。但是,通过优化器参数optimizer_mode,我们可以控制Oracle优化器生成不同模式下的执行计划。
关于优化器的请参考:《SQL性能优化之optimizer_mode参数原理渗透解析》
2.如何收集统计信息
2.1 统计信息的内容:
1)Table statistics
Number of rows --行数量
Number of blocks --block数量
Average row length --平均行的长度.
2)Column statistics
Number of distinct values (NDV) in column --列中distinct的值
Number of nulls in column --列中null的值
Data distribution (histogram) --数据分布
3)Index statistics
Number of leaf blocks --子节点的块数量
Levels --子节点数量
Clustering factor --集群因子
4)System statistics
I/O performance and utilization --IO性能和利用率
CPU performance and utilization --CPU的性能和利用率
2.2 收集统计信息
Oracle Statistic 的收集,可以使用analyze 命令,也可以使用DBMS_STATS 包来收集,Oracle 建议使用DBMS_STATS包来收集统计信息,因为DBMS_STATS包收集的更广,并且更准确。analyze 在以后的版本中可能会被移除。
DBMS_STATS常用的几个过程如下:
1
2
3
4
5
6
7
8
9
10
11
12 |
dbms_stats.gather_table_stats 收集表、列和索引的统计信息;
dbms_stats.gather_schema_stats 收集SCHEMA下所有对象的统计信息;
dbms_stats.gather_index_stats 收集索引的统计信息;
dbms_stats.gather_system_stats 收集系统统计信息
dbms_stats.GATHER_DICTIONARY_STATS:所有字典对象的统计;
DBMS_STATS.GATHER_DICTIONARY_STATS 其收集所有系统模式的统计
dbms_stats.delete_table_stats 删除表的统计信息
dbms_stats.delete_index_stats 删除索引的统计信息
dbms_stats.export_table_stats 输出表的统计信息
dbms_stats.create_state_table
dbms_stats.set_table_stats 设置表的统计
dbms_stats.auto_sample_size
|
analyze 命令的语法如下:
1
2
3 |
SQL>analyze table tablename compute statistics ;
SQL>analyze table tablename compute statistics for all indexes;
SQL>analyze table tablename delete statistics
|
2.3 统计信息的分类
Oracle 的Statistic 信息的收集分两种:自动收集和手工收集。
Oracle 的Automatic Statistics Gathering 是通过Scheduler 来实现收集和维护的。Job 名称是GATHER_STATS_JOB, 该Job收集数据库所有对象的2种统计信息:
(1)Missing statistics(统计信息缺失)
(2)Stale statistics(统计信息陈旧)
该Job 是在数据库创建的时候自动创建,并由Scheduler来管理。Scheduler 在maintenance windows open时运行gather job。 默认情况下,job 会在每天晚上10到早上6点和周末全天开启。该过程首先检测统计信息缺失和陈旧的对象。然后确定优先级,再开始进行统计信息。
Scheduler Job的stop_on_window_close 属性控制GATHER_STATS_JOB 是否继续。该属性默认值为True. 如果该值设置为False,那么GATHER_STATS_JOB 会中断, 而没有收集完的对象将在下次启动时继续收集。
Gather_stats_job 调用dbms_stats.gather_database_stats_job_proc过程来收集statistics 的信息。 该过程收集对象statistics的条件如下:
(1)对象的统计信息之前没有收集过。
(2)当对象有超过10%的rows 被修改,此时对象的统计信息也称为stale statistics。
但是对于高度变化的表在白天的活动期间被TRUNCATE/DROP并重建或者块加载超过本身总大小10%的对象;我们可以将这些表上的统计设置为NULL
可以通过以下SQL来查看:
1
2
3 |
select job_name, program_name, enabled, stop_on_window_close
from dba_scheduler_jobs
where job_name = 'gather_stats_job' ;
|
为了决定是否对对象进行监控,Oracle 提供了一个参数STATISTICS_LEVEL。通过设置初始化参数STATISTIC_LEVEL 为TYPICAL 或ALL,就可以自动收集统计信息(默认值为TYPICAL,因此可以随即启用自动收集统计信息的功能)。STATISTIC_LEVEL 参数的值可以激活GATHER_STATS_JOB。
在10g中表监控默认是激活的,如果STATISTICS_LEVEL设置为basic,不仅不能监控表,而且将禁掉如下一些10g的新功能:
(1)ASH(Active Session History)
(2)ASSM(Automatic Shared Memory Management)
(3)AWR(Automatic Workload Repository)
(4)ADDM(Automatic Database Diagnostic Monitor)
1
2
3
4
5 |
sys@ORCL> show parameter statistics_level;
NAME TYPE VALUE
statistics_level string TYPICAL
|
当启动对象的监控后,从上次统计信息收集之后的的信息,如inserts,updates,deletes 等,这些改变的信息会记录到user_tab_modifications 视图。
当对象的数据发生改变之后, 经过几分钟的延时,这些信息写入到user_tab_modifications视图,然后dbms_stats.flush_database_monitoring_info过程就会发现这些信息,并讲这些信息保存在内存中。
当监控的对象被修改的部分超过10%时,gather_database_stats 或者gather_schema_stats 过程就会去收集这些stale statistics
3.统计信息的存储位置以及常用数据字典
3.1 统计信息常用数据字典
统计信息收集如下数据:
(1)表自身的分析: 包括表中的行数,数据块数,行长等信息。
(2)列的分析:包括列值的重复数,列上的空值,数据在列上的分布情况。
(3)索引的分析: 包括索引叶块的数量,索引的深度,索引的聚合因子等。
这些统计信息存放在以下的数据字典里:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
DBA_TABLES
DBA_OBJECT_TABLES
DBA_TAB_STATISTICS
DBA_TAB_COL_STATISTICS
DBA_TAB_HISTOGRAMS
DBA_INDEXES
DBA_IND_STATISTICS
DBA_CLUSTERS
DBA_TAB_PARTITIONS
DBA_TAB_SUBPARTITIONS
DBA_IND_PARTITIONS
DBA_IND_SUBPARTITIONS
DBA_PART_COL_STATISTICS
DBA_PART_HISTOGRAMS
DBA_SUBPART_COL_STATISTICS
DBA_SUBPART_HISTOGRAMS
|
3.2 表的统计信息
包含表行数,使用的块数,空的块数,块的使用率,行迁移和链接的数量,pctfree,pctused的数据,行的平均大小:
1
2
3
4
5
6
7 |
SELECT NUM_ROWS,
BLOCKS,
EMPTY_BLOCKS,
AVG_SPACE,
CHAIN_CNT,
AVG_ROW_LEN
FROM USER_TABLES
|
3.3索引列的统计信息
包含索引的深度(B-Tree的级别),索引叶级的块数量,集群因子(clustering_factor), 唯一值的个数。
1
2
3
4
5
6
7 |
SELECT BLEVEL,
LEAF_BLOCKS,
DISTINCT_KEYS,
AVG_LEAF_BLOCKS_PER_KEY,
AVG_DATA_BLOCKS_PER_KEY,
CLUSTERING_FACTOR
FROM USER_INDEXES
|
3.4 列的统计信息
包含唯一的值个数,列最大小值,密度(选择率),数据分布(直方图信息),NUll值个数
1
2
3
4
5
6
7
8 |
SELECT NUM_DISTINCT,
LOW_VALUE,
HIGH_VALUE,
DENSITY,
NUM_NULLS,
NUM_BUCKETS,
HISTOGRAM
FROM USER_TAB_COLUMNS
|
对于统计信息的搜集,谈谈个人的几点理解:
1.统计信息默认是存放在数据字典表中的,也只有数据字典中的统计信息,才会影响到CBO。
2.DBMS_STATS 提供的CREATE_STAT_TABLE 过程,只是生成一个用户自定义的特定格式的表,用来存放统计信息罢了,这个表中的统计信息是不会影响到统计信息的。
3.GATHER 系列过程中,如果指定stattab,statid,statown 参数(也可以不指定),则是搜集的统计信息除了更新到数据字典外,还在statown 用户下的stattab 表中存放一份,标示为 statid;
4.EXPORT和IMPORT 系列的过程中,stattab,statid,statown 参数不能为空,分别表示把数据字典中的当前统计信息导出到用户自定义的表中,以及把用户表中的统计信息导入到数据字典中,很明显可以看出,这里的导入操作和上面GATHER 操作会改变统计信息,可能会引起执行执行计划的改变,因此要慎重操作。
5.每次统计信息搜集前,将旧的统计信息备份起来是很有必要的;特别是保留一份或多份系统在稳定时期的统计信息也是很有必要的。
6.多长时间搜集一次统计信息,对于统计信息如何备份和保留,搜集统计信息时如何选择合适的采样,并行,直方图设置等都比较重要,需要设计一个较好的统计信息搜集策略。
在OCP 10g考试中会有个跟统计信息相关的考试题:
1. In your Oracle 10g database , you have scheduled a job to update the optimizer statistics at 05:00 pm
every Friday. The job has successfully completed. Which three pieces of information would you check to
confirm that the statistics have been collected? (Choose three.)
A. average row size
B. last analyzed date
C. size of table in bytes
D. size of table in database blocks
E. number of free blocks in the free list
F. number of extents present in the table
Answer: ABD
摘要: 以下测试环境为Oracle 10g 10.2.0.4版本,测试对Oracle的统计信息的收集与管理。首先依据dba_objects创建一张测试表:
SQL> create table eygle as select * from dba_objects; Table created对该表进行统计信息收集,这里未指定method_opt,则Oracle将采用 FOR ALL COL...
阅读全文
摘要: 获取昨天:
select trunc(SYSDATE-1) from dual;
检查一下:
select to_char (trunc(SYSDATE-1),'yyyy-mm-dd HH24:MI:SS') from dual;
获取上个月第一天00:00:00:
select add_months(trunc(sysdate,'MON'),-1) from d...
阅读全文
Oracle数据库启动分为三个步骤:
nomount,找到初始化文件pfile或者spfile,创建SGA并启动后台进程但不允许访问数据库。
mount,根据初始化文件找到控制文件(Control File),为某些DBA活动装载数据库但不允许用户访问数据库。
open,根据控制文件找到数据文件(Data File),重做日志文件(Redo File),使用户可以访问数据库。
关闭数据库的4个不同命令
shutdown normal(等于shutdown)
正常是关闭的缺省方式正常的数据库关闭在下列情况下进行
• 不允许新的连接
• 等待会话结束
• 等待事务结束
• 做一个检查点并关闭数据文件
• 下一次启动时将不要求实例恢复
shutdown transactional
事务处理关闭防止客户丢失工作事务处理数据库关闭在下列情况下进行
• 不允许新的连接
• 不等待会话结束
• 等待事务结束
• 做一个检查点并关闭数据文件
• 下一次启动将不要求实例恢复
shutdown immediate
立即关闭数据库在下列情况下进行
• 不允许新的连接
• 不等待会话结束
• 不等待事务结束
• 未结束的事务自动回滚(rollback)
• 做一个检查点并关闭数据文件
• 下一次启动将不要求例程恢复
shutdown abort
如果正常和立即关闭选项不起作用可以中止当前数据库例程中止例程可以在下列情况下进行
• 不允许新的连接
• 不等待会话结束
• 不等待事务结束
• 不做检查点且没有关闭数据文件
• 下一次启动将要求实例恢复
启动数据库相关命令
startup(默认启动到open状态)
startup nomount
startup mount
alter database mount;
alter database open;
初始化文件
pfile,文本文件,可以手工编辑该文件
spfile,二进制文件,不能手工修改里面的参数,只能使用数据库命令进行修改,数据库启动默认使用spfile
查看初始化文件路径
show parameter spfile;
根据spfile创建pfile文件
create pfile from spfile;
create pfile='/u01/app/pfile.ora' fromspfile;
create spfile from pfile='/u01/app/pfile.ora';
查看控制文件的路径
select name from v$controlfile;
查看控制文件的内容,可以根据ctl.trc重建控制文件
alter database backup controlfile to trace as '/u01/app/ctl.trc';
查看数据文件的位置
select name from v$datafile;
查看重做日志文件的位置
select member from v$logfile;
数据库的密码文件
Windows,$ORACLE_HOME/database/PWDorcl.ora
Linux/Unix,$ORACLE_HOME/dbs/orapworcl
密码文件的作用是允许数据库的sysdba、sysoper用户通过口令来远登陆
查看数据库是否允许用户远程登陆数据库
show parameter remote_login_passwordfile;
如果remote_login_passwordfile为EXCLUSIVE则表示允许远程连接数据库
如果remote_login_passwordfile为NONE则表示不允许远程连接数据库
禁止用户远程登陆数据库(需要重启数据库生效)
alter system set remote_login_passwordfile=none scope=spfile;
表空间
select * from dba_tablespaces;
数据文件
select * from dba_data_files;
重做日志文件
select * from v$logfile;
重做日志文件的状态
STALE表示数据已经提交到数据库中,空白状态表示正在使用该文件
1. 如何查看及解决最耗CPU的SQL语句1.1. 用top监控服务器负载
[root@node1 ~]# top
top - 22:51:02 up 56 min, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 96 total, 1 running, 95 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 1035096k total, 351488k used, 683608k free, 24140k buffers
Swap: 2096472k total, 0k used, 2096472k free, 270360k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 15 0 2084 660 560 S 0.0 0.1 0:00.22 init
2 root RT -5 0 0 0 S 0.0 0.0 0:00.00 migration/0
|
如果发现user中的CPU过高,比如oracle中最高的进程pid为1138782,占CPU27%,则执行下一步。 1.2. 查询数据库会话的sid、serial#
进入数据库,根据oracle进程的pid查出对应数据库会话的sid、serial#:
select s.sid,s.serial#
from v$session s,v$process p
where s.paddr=p.addr and p.spid='1138782';
|
查询出来的结果sid、serial#分别为482、56767 1.3. 查询SQL语句
根据数据库会话的sid查出具体的SQL语句:
名称 是否为空? 类型
----------------------------------------- -------- --------------
ADDRESS RAW(4)
HASH_VALUE NUMBER
COMMAND_TYPE NUMBER
PIECE NUMBER
SQL_TEXT VARCHAR2(64)
select sql_text
from v$sqltext a
where a.hash_value=(select sql_hash_value from v$session b
where b.sid='&sid')
order by piece;
|
输入 sid 的值: 1.4. 处理SQL语句
如果SQL语句影响了数据库的运行,可以kill掉SQL语句的会话:
①在数据库中杀死SQL语句的会话:
alter system kill session '482,56767';
|
如果不能在数据库中杀死SQL语句,可在LINUX系统中强制杀死Oracle进程
②在linux系统中强制杀死oracle进程
1.5. SQL语句优化
最后可以根据步骤(3)查询出来的SQL语句进行优化,以避免再次出现上述消耗CPU的情况。 |
摘要: 一. 分区表理论知识
Oracle提供了分区技术以支持VLDB(Very Large DataBase)。分区表通过对分区列的判断,把分区列不同的记录,放到不同的分区中。分区完全对应用透明。
Oracle的分区表可以包括多个分区,每个分区都是一个独立的段(SEGMENT),可以存放到不同的表空间中。查询时可以通过查询表来访问各个分区中的数据,也可以通过在查询时直接指定分区的方法来进行查询。 ...
阅读全文
1.分区表的维护注意事项
- 若分区表跨不同表空间,做导出、导入时目标数据库必须预建这些表空间。分表区各区所在表空间在做导入时目标数据库一定要预建这些表空间!这些表空间不一定是用户的默认表空间,只要存在即可。如果有一个不存在,就会报错!
- 默认时,对分区表的许多表维护操作会使全局索引不可用,标记成UNUSABLE。 那么就必须重建整个全局索引或其全部分区。如果已被分区,Oracle 允许在用于维护操作的ALTER TABLE 语句中指定UPDATE GLOBAL INDEXES 来重载这个默认特性,指定这个子句也就告诉Oracle 当它执行维护操作的DDL 语句时更新全局索引,这提供了如下好处:
- 在操作基础表的同时更新全局索引这就不需要后来单独地重建全局索引;
- 因为没有被标记成UNUSABLE, 所以全局索引的可用性更高了,甚至正在执行分区的DDL 语句时仍然可用索引来访问表中的其他分区,避免了查询所有失效的全局索引的名字以便重建它们;
另外在指定UPDATE GLOBAL INDEXES 之前还要考虑如下性能因素:
- 因为要更新事先被标记成UNUSABLE 的索引,所以分区的DDL 语句要执行更长时间,当然这要与先不更新索引而执行DDL 然后再重建索引所花的时间做个比较,一个适用的规则是如果分区的大小小于表的大小的5% ,则更新索引更快一点;
- DROP TRUNCATE 和EXCHANGE 操作也不那么快了,同样这必须与先执行DDL 然后再重建所有全局索引所花的时间做个比较;
- 要登记对索引的更新并产生重做记录和撤消记录,重建整个索引时可选择NOLOGGING;
- 重建整个索引产生一个更有效的索引,因为这更利于使用空间,再者重建索引时允许修改存储选项;
- 分区索引结构表不支持UPDATE GLOBAL INDEXES 子句。
2.普通表变为分区表
将已存在数据的普通表转变为分区表,没有办法通过修改属性的方式直接转化为分区表,必须通过重建的方式进行转变,一般可以有三种方法,视不同场景使用:
2.1方法一:利用原表重建分区表。
CREATE TABLE T (ID NUMBER PRIMARY KEY, TIME DATE);
INSERT INTO t
SELECT Rownum, SYSDATE - Rownum FROM Dba_Objects WHERE Rownum <= 5000;
COMMIT;
CREATE TABLE T_NEW (ID, TIME) PARTITION BY RANGE (TIME)
(PARTITION P1 VALUES LESS THAN (TO_DATE('2000-1-1', 'YYYY-MM-DD')),
PARTITION P2 VALUES LESS THAN (TO_DATE('2002-1-1', 'YYYY-MM-DD')),
PARTITION P3 VALUES LESS THAN (TO_DATE('2005-1-1', 'YYYY-MM-DD')),
PARTITION P4 VALUES LESS THAN (MAXVALUE))
AS SELECT ID, TIME FROM T;
RENAME T TO T_OLD;
RENAME T_NEW TO T;
优点:方法简单易用,由于采用DDL语句,不会产生UNDO,且只产生少量REDO,效率相对较高,而且建表完成后数据已经在分布到各个分区中了。
不足:对于数据的一致性方面还需要额外的考虑。由于几乎没有办法通过手工锁定T表的方式保证一致性,在执行CREATE TABLE语句和RENAME T_NEW TO T语句直接的修改可能会丢失,如果要保证一致性,需要在执行完语句后对数据进行检查,而这个代价是比较大的。另外在执行两个RENAME语句之间执行的对T的访问会失败。
适用性:适用于修改不频繁的表,在闲时进行操作,表的数据量不宜太大。
2.2方法二:使用交换分区的方法
Drop table t;
CREATE TABLE T (ID NUMBER PRIMARY KEY, TIME DATE);
INSERT INTO t
SELECT Rownum, SYSDATE - Rownum FROM Dba_Objects WHERE Rownum <= 5000;
COMMIT;
CREATE TABLE T_NEW (ID NUMBER PRIMARY KEY, TIME DATE) PARTITION BY RANGE (TIME)
(PARTITION P1 VALUES LESS THAN (TO_DATE('2005-9-1', 'YYYY-MM-DD')),
PARTITION P2 VALUES LESS THAN (MAXVALUE));
ALTER TABLE T_NEW EXCHANGE PARTITION P1 WITH TABLE T;
RENAME T TO T_OLD;
RENAME T_NEW TO T;
优点:只是对数据字典中分区和表的定义进行了修改,没有数据的修改或复制,效率最高。如果对数据在分区中的分布没有进一步要求的话,实现比较简单。在执行完RENAME操作后,可以检查T_OLD中是否存在数据,如果存在的话,直接将这些数据插入到T中,可以保证对T插入的操作不会丢失。
不足:仍然存在一致性问题,交换分区之后RENAME T_NEW TO T之前,查询、更新和删除会出现错误或访问不到数据。如果要求数据分布到多个分区中,则需要进行分区的SPLIT操作,会增加操作的复杂度,效率也会降低。
适用性:适用于包含大数据量的表转到分区表中的一个分区的操作。应尽量在闲时进行操作。
2.3方法三:Oracle9i以上版本,利用在线重定义功能
Drop table t;
CREATE TABLE T (ID NUMBER PRIMARY KEY, TIME DATE);
INSERT INTO T
SELECT ROWNUM, SYSDATE - ROWNUM FROM DBA_OBJECTS WHERE ROWNUM <= 5000;
COMMIT;
EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE(USER, 'T');
PL/SQL 过程已成功完成。
CREATE TABLE T_NEW (ID NUMBER PRIMARY KEY, TIME DATE) PARTITION BY RANGE (TIME)
(PARTITION P1 VALUES LESS THAN (TO_DATE('2004-7-1', 'YYYY-MM-DD')),
PARTITION P2 VALUES LESS THAN (TO_DATE('2005-1-1', 'YYYY-MM-DD')),
PARTITION P3 VALUES LESS THAN (TO_DATE('2005-7-1', 'YYYY-MM-DD')),
PARTITION P4 VALUES LESS THAN (MAXVALUE));
表已创建。
EXEC DBMS_REDEFINITION.START_REDEF_TABLE(USER, 'T', 'T_NEW');
PL/SQL 过程已成功完成。
EXEC DBMS_REDEFINITION.FINISH_REDEF_TABLE(USER, 'T', 'T_NEW');
PL/SQL 过程已成功完成。
优点:保证数据的一致性,在大部分时间内,表T都可以正常进行DML操作。只在切换的瞬间锁表,具有很高的可用性。这种方法具有很强的灵活性,对各种不同的需要都能满足。而且,可以在切换前进行相应的授权并建立各种约束,可以做到切换完成后不再需要任何额外的管理操作。
不足:实现上比上面两种略显复杂。
适用性:适用于各种情况。
这里只给出了在线重定义表的一个最简单的例子,详细的描述和例子可以参考下面两篇文章。
Oracle的在线重定义表功能:http://blog.itpub.net/post/468/12855
Oracle的在线重定义表功能(二):http://blog.itpub.net/post/468/12962
2.4把一个已存在数据的大表改成分区表:
第一种(表不是太大):
--1.把原表改名:
rename xsb1 to xsb2;
--2.创建分区表:
CREATE TABLE xsb1
PARTITION BY LIST (c_test)
(PARTITION xsb1_p1 VALUES (1),
PARTITION xsb1_p2 VALUES (2),
PARTITION xsb1_p0 VALUES (default))
nologging AS SELECT * FROM xsb2;
--3.将原表上的触发器、主键、索引等应用到分区表上;
--4.删除原表:
drop table xsb2;
第二种(表很大):
--1. 创建分区表:
CREATE TABLE x PARTITION BY LIST (c_test) [range ()]
(PARTITION p0 VALUES [less than ](1) tablespace tbs1,
PARTITION p2 VALUES (2) tablespace tbs1,
PARTITION xsb1_p0 VALUES ([maxvalue]default))
AS SELECT * FROM xsb2 [where 1=2];
--2. 交换分区
alter table x exchange partition p0 with table bsvcbusrundatald ;
--3. 原表改名
alter table bsvcbusrundatald rename to x0;
--4. 新表改名
alter table x rename to bsvcbusrundatald ;
--5. 删除原表
drop table x0;
--6. 创建新表触发器和索引
create index ind_busrundata_lp on bsvcbusrundatald(。。。) local tablespace tbs_brd_ind ;
或者:
1. 规划原大表中数据分区的界限,原则上将原表中近期少量数据复制至另一表;
2. 暂停原大表中的相关触发器;
3. 删除原大表中近期数据;
4. 改名原大表名称;
5. 创建分区表;
6. 交换分区;
7. 重建相关索引及触发器(先删除之再重建).
参考脚本:
select count(*) from t1 where recdate>sysdate-2;
create table x2 nologging as select * from t1 where recdate>trunc(sysdate-2);
alter triger trg_t1 disable;
delete t1 where recdate>sysdate-2;
commit;
rename t1 to x1;
create table t1 [nologging] partition by range(recdate)
(partition pbefore values less than (trunc(sysdate-2)),
partition pmax values less than (maxvalue))
as select * from x1 where 1=2;
alter table t1 exchange partition pbefore with table x1;
alter table t1 exchange partition pmax with table x2;
drop table x2;
--重建触发器
drop table x1;
3.分区的方法:
- 范围分区Range
- 散列分区Hash
- 列表分区List
- 组合范围-散列分区Range-Hash
- 组合范围-列表分区Range-List
可对索引和表分区。全局索引只能按范围分区,但可以将其定义在任何类型的分区或非分区表上。通常全局索引比局部索引需要更多的维护。
一般组建局部索引,以便反映其基础表的结构。它与基础表是等同分区的,即它与基础表在同样的列上分区,创建同样数量的分区或子分区,设置与基础表相对应的同样的分区边界。对局部索引而言,当维护活动影响分区时,会自动维护索引分区。这保证了索引与基础表之间的等同分区。
关于范围分区Range:
要想将行映射到基于列值范围的分区,就使用范围分区方法。当数据可以被划分成逻辑范围时如年度中的月份,这种类型的分区就有用了。当数据在整个范围中能被均等地划分时性能最好。如果靠范围的分区会由于不均等的划分而导致分区在大小上明显不同时,就需要考虑其他的分区方法。
关于散列分区Hash:
如果数据不那么容易进行范围分区,但为了性能和管理的原因又想分区时,就使用散列分区方法。散列分区提供了一种在指定数量的分区中均等地划分数据的方法。基于分区键的散列值将行映射到分区中。创建和使用散列分区会给你提供了一种很灵活的放置数据的方法,因为你可以通过在I/O 驱动器之间播撒(摘掉)这些均等定量的分区,来影响可用性和性能。
关于列表分区List:
当你需要明确地控制如何将行映射到分区时,就使用列表分区方法。可以在每个分区的描述中为该分区列指定一列离散值,这不同于范围分区,在那里一个范围与一个分区相关,这也不同于散列分区,在那里用户不能控制如何将行映射到分区。列表分区方法是特意为遵从离散值的模块化数据划分而设计的。范围分区或散列分区不那么容易做到这一点。进一步说列表分区可以非常自然地将无序的和不相关的数据集进行分组和组织到一起。
与范围分区和散列分区所不同,列表分区不支持多列分区。如果要将表按列分区,那么分区键就只能由表的一个单独的列组成,然而可以用范围分区或散列分区方法进行分区的所有的列,都可以用列表分区方法进行分区。
关于组合范围-散列分区:
范围和散列技术的组合,首先对表进行范围分区,然后用散列技术对每个范围分区再次分区。给定的范围分区的所有子分区加在一起表示数据的逻辑子集。
关于组合范围-列表分区:
范围和列表技术的组合,首先对表进行范围分区,然后用列表技术对每个范围分区再次分区。与组合范围-散列分区不同的是,每个子分区的所有内容表示数据的逻辑子集,由适当的范围和列表分区设置来描述。
注意:创建或更改分区表时可以指定行移动子句,即ENABLE ROW MOVEMENT 或者DISABLE ROW MOVEMENT ,当其键被更改时,该子句启用或停用将行迁移到一个新的分区。默认值为DISABLE ROW MOVEMENT。
如果表中预期的数据量较大,通常都需要考虑使用分区表,确定使用分区表后,还要确定什么类型的分区(range partition、hash partition、list partition等)、分区区间大小等。分区的创建最好与程序有某种默契,比如创建一个分区表,按自然月份定义分区的,但程序却在查询时默认的开始时间与结束时间是:当前日期-30至当前日期,比如当天是9.18号,那查询条件被产生为8.18-9.18,结果分区后并不没有大幅提高性能。为此将程序的查询日期做了调整,按自然月查询,系统的负载小了很多。
4.补充资料
从Oracle8.0开始支持表分区(MSSQL2005开始支持表分区)。
Oracle9i 分区能够提高许多应用程序的可管理性、性能与可用性。分区可以将表、索引及索引编排表进一步划分,从而可以更精细地对这些数据库对象进行管理和访问。Oracle 提供了种类繁多的分区方案以满足所有的业务需要。另外,由于在 SQL 语句中是完全透明的,所以分区可以用于几乎所有的应用程序。
分区表允许将数据分成被称为分区甚至子分区的更小的更好管理的块。索引也可以这么分区。每个分区可以被单独管理,可以不依赖于其他分区而单独发挥作用,因此提供了一个更有利于可用性和性能的结构。
分区可以提高可管理性、性能与可用性,从而给各种各样的应用程序带来极大的好处。通常,分区可以使某些查询以及维护操作的性能大大提高。此外,分区还能够在很大程度上简化日常管理任务。分区还使数据库设计人员和管理员能够解决尖端应用程序带来的最难的问题。分区是建立上亿万字节数据系统或需要极高可用性系统的关键工具。
在多CPU配置环境下,如果打算使用并行执行,则分区提供了另一种并行的方法。通过给表或索引的不同分区分配不同的并行执行服务器,就可以并行执行对分区表和分区索引的操作。
表或索引的分区和子分区都共享相同的逻辑属性。例如表的所有分区或子分区共享相同的列和约束定义,一个索引的分区或子分区共享相同的索引选项。然而它们可以具有不同的物理属性如表空间。
尽管不需要将表或索引的每个分区或子分区放在不同的表空间,但这样做更好。将分区存储到不同的表空间能够:
- 减少数据在多个分区中冲突的可能性
- 可以单独备份和恢复每个分区
- 控制分区与磁盘驱动器之间的映射对平衡I/O 负载是重要的
- 改善可管理性可用性和性能
分区操作对现存的应用和运行在分区表上的标准DML 语句来说是透明的。但是可以通过在DML 中使用分区扩展表或索引的名字来对应用编程,使其利用分区的优点。
可以使用SQL*Loader、Import 和Export 工具来装载或卸载分区表中的数据。这些工具都是支持分区和子分区的。
4.1分区技术能够提高数据库的可管理性:
使用分区技术,维护操作可集中于表的特定部分。例如,数据库管理员可以只对表的一部分做备份,而不必对整个表做备份。对整个数据库对象的维护操作,可以在每个分区的基础上进行,从而将维护工作分解成更容易管理的小块。
分区技术提高可管理性的一个典型用法是支持数据仓库中的‘滚动视窗’加载进程。假设数据库管理员每周向表中加载新数据。该表可以是范围分区,以便每个分区包含一周的数据。加载进程只是简单地添加新的分区。添加一个新分区的操作比修改整个表效率高很多,因为数据库管理员不需要修改任何其他分区。从分区后的表中去除数据也是一样。你只要用一个很简便快捷的数据字典操作删掉一个分区,而不必发出使用大量资源和调动所有要删除的数据的 ‘DELETE’ 命令。
4.2分区技术能够提高数据库的性能:
由于减少了所检查或操作的数据数量,同时允许并行执行,Oracle9i 的分区功能提供了性能上的优势。这些性能包括:
- 分区修整:分区修整是用分区技术提高性能的最简单最有价值的手段。分区修整常常能够将查询性能提高几个数量级。例如,假定应用程序中有包含定单历史记录的定单表,该表用周进行了分区。查询一周的定单只需访问该定单表的一个分区。如果该定单表包含两年的历史记录,这个查询只需要访问一个而不是一百零四个分区。该查询的执行速度因为分区修整而有可能快一百倍。分区修整能与所有其他 Oracle 性能特性协作。Oracle 公司将把分区修整技术与索引技术、连结技术和并行访问方法一起联合使用。
- 分区智能联接:分区功能可以通过称为分区智能联接的技术提高多表联接的性能。当两个表要联接在一起,而且每个表都用联接关键字来分区时,就可以使用分区智能联接。分区智能联接将大型联接分解成较小的发生在各个分区间的联接,从而用较少的时间完成全部联接。这就给串行和并行的执行都能带来显著的性能改善。
- 更新和删除的并行执行:分区功能能够无限地并行执行 UPDATE、DELETE 与 MERGE 语句。当访问分区或未分区的数据库对象时Oracle 将并行处理 SELECT 与 INSERT 语句。当不使用位图索引时,也可以对分区或未分区的数据库对象并行处理 UPDATE、DELETE 和 MERGE 语句。为了对有位图索引的对象并行处理那些操作,目标表必须先分区。这些 SQL 语句的并行执行可以大大提高性能,特别是提高 UPDATE 与 DELETE 或 MERGE 操作涉及大量数据时的性能。
4.3分区技术提高可用性:
分区的数据库对象具有分区独立性。该分区独立性特点可能是高可用性战略的一个重要部分,例如,如果分区表的分区不能用,但该表的所有其他分区仍然保持在线并可用。那么这个应用程序可以继续针对该分区表执行查询和事务处理,只要不是访问那个不可用的分区,数据库操作仍然能够成功运行。 数据库管理员可以指定各分区存放在不同的表空间里,从而让管理员独立于其它表分区针对每个分区进行备份与恢复操作。 还有,分区功能可以减少计划停机时间。性能由于分区功能得到了改善,使数据库管理员在相对较小的批处理窗口完成大型数据库对象的维护工作。
将普通表转换成分区表有4种方法:
1. Export/import method
2. Insert with a subquery method
3. Partition exchange method
4. DBMS_REDEFINITION
select * from t_user_info_test;
drop table t_phone_test purge;
create table t_phone_test(phone,part) nologging partition by list(part)
(
partition p0 values('0'),
partition p1 values('1'),
partition p2 values('2'),
partition p3 values('3'),
partition p4 values('4'),
partition p5 values('5'),
partition p6 values('6'),
partition p7 values('7'),
partition p8 values('8'),
partition p9 values('9')
)
as
select user_mobile phone,substr(user_mobile,-1,1) part
from t_user_info_test;
select * from t_phone_test partition(p0);
select * from t_phone_test where part='0';
这种方法只是对数据字典中分区和表的定义进行了修改,没有数据的修改或复制,效率最高。适用于包含大数据量的表转到分区表中的一个分区的操作。尽量在闲时进行操作。
交换分区的操作步骤如下:
1. 创建分区表,假设有2个分区,P1,P2.
2. 创建表A存放P1规则的数据。
3. 创建表B 存放P2规则的数据。
4. 用表A 和P1 分区交换。 把表A的数据放到到P1分区
5. 用表B 和p2 分区交换。 把表B的数据存放到P2分区。
create table t_phone_test_0 nologging
as
select user_mobile phone,substr(user_mobile,-1,1) part
from t_user_info_test where substr(user_mobile,-1,1)='0';
select count(*) from t_phone_test where part='0';
select count(*) from t_user_info_test where substr(user_mobile,-1,1)='0';
alter table t_phone_test exchange partition p0 with table t_phone_test_0;
delete from t_phone_test_0;
select count(*) from t_phone_test where part='0';
select count(*) from t_phone_test_0;
insert into t_phone_test(phone,part) values('15267046070','0');
insert into t_phone_test_0(phone,part) values('15267046070','1');
alter table t_phone_test exchange partition p0 with table t_phone_test_0;
delete from t_phone_test_0 where part='1';
alter table t_phone_test merge partitions p0,p1 into partition p0;
select count(*) from t_phone_test where part='0';
select count(*) from t_phone_test where part='1';
select count(*) from t_phone_test partition(p0);
select count(*) from t_phone_test partition(p1);
alter table t_phone_test add partition p10 values(default);
insert into t_phone_test(phone,part) values('15267046010','10');
insert into t_phone_test(phone,part) values('15267046020','20');
select * from
alter table t_phone_test drop partition p10;
alter table t_phone_test add partition p10 values( '10');
alter table t_phone_test exchange partition p10 with table t_phone_test_10;
alter table T_PHONE_TEST_10 modify PART VARCHAR2(2);
alter table t_phone_test merge partitions p0,p10 into partition p0;
partition P0 values ('10', '0')
tablespace APP_DATAN
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 1M
next 1M
minextents 1
maxextents unlimited
pctincrease 0
),
alter table t_phone_test exchange partition p0 with table t_phone_test_10;
alter table t_phone_test drop partition p0;
alter table t_phone_test add partition p0 values( '0');
alter table t_phone_test exchange partition p0 with table t_phone_test_10;
drop table t_phone_test_10 purge;
create table t_phone_test_10 nologging
as
select user_mobile phone,substr(user_mobile,-2,2) part
from t_user_info_test where substr(user_mobile,-2,2)='10';
drop table t_phone_test_0 purge;
create table t_phone_test_0 nologging
as
select phone,substr(phone,-1,1) part
from t_phone_test_10;
alter table t_phone_test exchange partition p0 with table t_phone_test_0;
select * from t_phone_test_10;
select count(*) from t_phone_test partition(p0);
select count(*) from t_phone_test partition(p10);
select count(*) from t_phone_test_10;
select count(*) from t_phone_test_0;
select substr('123456',-1,1),substr('123456',-2,2),substr('123456',-3,2) from dual;
1.创建分区表
drop table t_phone_test purge;
create table t_phone_test(phone,part) nologging partition by list(part)
(
partition p0 values('0'),
partition p1 values('1'),
partition p2 values('2'),
partition p3 values('3'),
partition p4 values('4'),
partition p5 values('5'),
partition p6 values('6'),
partition p7 values('7'),
partition p8 values('8'),
partition p9 values('9')
)
as
select user_mobile phone,substr(user_mobile,-1,1) part
from t_user_info_test;
select count(*) from t_phone_test partition(p0);
select count(*) from t_phone_test partition(p10);
select count(*) from t_phone_test_10;
select count(*) from t_phone_test_0;
2.创建基表
drop table t_phone_test_10 purge;
create table t_phone_test_10 nologging
as
select phone,substr(phone,-2,2) part
from t_phone_test where substr(phone,-2,2)='10';
select count(*) from t_phone_test_10;
alter table T_PHONE_TEST_10 modify PART VARCHAR2(2);
3.添加分区
alter table t_phone_test add partition p10 values( '10');
select count(*) from t_phone_test partition(p10);
4.交换分区
alter table t_phone_test exchange partition p10 with table t_phone_test_10;
select count(*) from t_phone_test partition(p10);
5.合并分区
alter table t_phone_test merge partitions p0,p10 into partition p0;
select count(*) from t_phone_test partition(p0);
partition P0 values ('10', '0')
tablespace APP_DATAN
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 1M
next 1M
minextents 1
maxextents unlimited
pctincrease 0
),
6.交换分区
alter table t_phone_test exchange partition p0 with table t_phone_test_10;
select count(*) from t_phone_test partition(p0);
select count(*) from t_phone_test_10;
6.删除分区 和添加分区
alter table t_phone_test drop partition p0;
alter table t_phone_test add partition p0 values('0');
7.筛选数据
drop table t_phone_test_0 purge;
create table t_phone_test_0 nologging
as
select phone,substr(phone,-1,1) part
from t_phone_test_10 where substr(phone,-1,1)='0';
select count(*) from t_phone_test_0;
8.交换分区
alter table t_phone_test exchange partition p0 with table t_phone_test_0;
select count(*) from t_phone_test partition(p0);
select count(*) from t_phone_test_0;
Oracle数据库中,有两种类型的分区索引,全局索引和本地索引,其中本地索引又可以分为本地前缀索引和本地非前缀索引。下面就分别看看每种类型的索引各自的特点。
全局索引以整个表的数据为对象建立索引,索引分区中的索引条目既可能是基于相同的键值但是来自不同的分区,也可能是多个不同键值的组合。
全局索引既允许索引分区的键值和表分区键值相同,也可以不相同。全局索引和表之间没有直接的联系,这一点和本地索引不同。
SQL> create table orders (
order_no number,
part_no varchar2(40),
ord_date date
)
partition by range (ord_date)
(partition Q1 values less than (TO_DATE('01-APR-1999','DD-MON-YYYY')),
partition Q2 values less than (TO_DATE('01-JUL-1999','DD-MON-YYYY')),
partition Q3 values less than (TO_DATE('01-OCT-1999','DD-MON-YYYY')),
partition Q4 values less than (TO_DATE('01-JAN-2000','DD-MON-YYYY'))
)
;
Table created.
SQL> create index orders_global_1_idx
on orders(ord_date)
global partition by range (ord_date)
(partition GLOBAL1 values less than (TO_DATE('01-APR-1999','DD-MON-YYYY')),
partition GLOBAL2 values less than (TO_DATE('01-JUL-1999','DD-MON-YYYY')),
partition GLOBAL3 values less than (TO_DATE('01-OCT-1999','DD-MON-YYYY')),
partition GLOBAL4 values less than (MAXVALUE)
)
;
Index created.
SQL> create index orders_global_2_idx
on orders(part_no)
global partition by range (part_no)
(partition IND1 values less than (555555),
partition IND2 values less than (MAXVALUE)
)
;
Index created.
从上面的语句可以看出,全局索引和表没有直接的关联,必须显式的指定maxvalue值。假如表中新加了分区,不会在全局索引中自动增加新的分区,必须手工添加相应的分区。
SQL> alter table orders add partition Q5 values less than (TO_DATE('01-APR-2000','DD-MON-YYYY'));
Table altered.
SQL> select TABLE_NAME, PARTITION_NAME from dba_tab_partitions where table_name='ORDERS';
TABLE_NAME PARTITION_NAME
------------------------------ ------------------------------
ORDERS Q1
ORDERS Q2
ORDERS Q3
ORDERS Q4
ORDERS Q5
SQL> select INDEX_NAME, PARTITION_NAME from dba_ind_partitions where index_name=upper('orders_global_1_idx');
INDEX_NAME PARTITION_NAME
------------------------------ ------------------------------
ORDERS_GLOBAL_1_IDX GLOBAL1
ORDERS_GLOBAL_1_IDX GLOBAL2
ORDERS_GLOBAL_1_IDX GLOBAL3
ORDERS_GLOBAL_1_IDX GLOBAL4
使用全局索引,索引键值必须和分区键值相同,这就是所谓的前缀索引。Oracle不支持非前缀的全局分区索引,如果需要建立非前缀分区索引,索引必须建成本地索引。
SQL> create index orders_global_2_idx
2 on orders(part_no)
3 global partition by range (order_no)
4 (partition IND1 values less than (555555),
5 partition IND2 values less than (MAXVALUE)
6 )
7 ;
global partition by range (order_no)
*
ERROR at line 3:
ORA-14038: GLOBAL partitioned index must be prefixed
接下来再来看看本地分区。
本地索引的分区和其对应的表分区数量相等,因此每个表分区都对应着相应的索引分区。使用本地索引,不需要指定分区范围因为索引对于表而言是本地的,当本地索引创建时,Oracle会自动为表中的每个分区创建独立的索引分区。
创建本地索引不必显式的指定maxvalue值,因为为表新添加表分区时,会自动添加相应的索引分区。
create index orders_local_1_idx
on orders(ord_date)
local
(partition LOCAL1,
partition LOCAL2,
partition LOCAL3,
partition LOCAL4
)
;
Index created.
SQL> select INDEX_NAME, PARTITION_NAME from dba_ind_partitions where index_name=upper('orders_local_1_idx');
INDEX_NAME PARTITION_NAME
------------------------------ ------------------------------
ORDERS_LOCAL_1_IDX LOCAL1
ORDERS_LOCAL_1_IDX LOCAL2
ORDERS_LOCAL_1_IDX LOCAL3
ORDERS_LOCAL_1_IDX LOCAL4
SQL> alter table orders add partition Q5 values less than (TO_DATE('01-APR-2000','DD-MON-YYYY'));
Table altered.
SQL> select INDEX_NAME, PARTITION_NAME from dba_ind_partitions where index_name=upper('orders_local_1_idx');
INDEX_NAME PARTITION_NAME
------------------------------ ------------------------------
ORDERS_LOCAL_1_IDX LOCAL1
ORDERS_LOCAL_1_IDX LOCAL2
ORDERS_LOCAL_1_IDX LOCAL3
ORDERS_LOCAL_1_IDX LOCAL4
ORDERS_LOCAL_1_IDX Q5
这里系统已经自动以和表分区相同的名字自动创建了一个索引分区。同理,删除表分区时相对应的索引分区也自动被删除。
本地索引和全局索引还有一个显著的差别,就是上面提到的,本地索引可以创建成本地非前缀型,而全局索引只能是前缀型。
SQL> create index orders_local_2_idx
on orders(part_no)
local
(partition LOCAL1,
partition LOCAL2,
partition LOCAL3,
partition LOCAL4)
;
Index created.
SQL> select INDEX_NAME, PARTITION_NAME, HIGH_VALUE from dba_ind_partitions
where index_name=upper('orders_local_2_idx');
INDEX_NAME PARTITION_NAME HIGH_VALUE
------------------------------ ------------------------------ ---------------------------------------------------------
ORDERS_LOCAL_2_IDX LOCAL1 TO_DATE(' 1999-04-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIA'
ORDERS_LOCAL_2_IDX LOCAL2 TO_DATE(' 1999-07-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIA'
ORDERS_LOCAL_2_IDX LOCAL3 TO_DATE(' 1999-10-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIA'
ORDERS_LOCAL_2_IDX LOCAL4 TO_DATE(' 2000-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIA'
从上面的输出可以看出,虽然索引的键值是part_no,但索引分区的键值仍然和表的分区键值相同,即ord_date,也即是所谓的非前缀型索引。
最后,再引用一个例子说明前缀索引和非前缀索引的应用。
假设有一个使用DATE列分区的大表。我们经常使用一个VARCHAR2列(VCOL)进行查询,但这个列并不是表的分区键值。
有两种可能的方法来访问VCOL列的数据,一是建立基于VCOL列的本地非前缀索引,
| |
------- -------
| | (10 more | |
Values: A.. Z.. partitions here) A.. Z..
另一种是建立基于VCOL列的全局索引,
| |
------- -------
| | (10 more | |
Values: A.. D.. partitions here) T.. Z..
可以看出,如果能够保证VCOL列值的唯一性,全局索引将会是最好的选择。如果VCOL列值不唯一,就需要在本地非前缀索引的并行查询和全局索引顺序查询以及高昂的维护代价之间做出选择。
造成ORA-12560: TNS: 协议适配器错误的问题的原因可能有如下三个:
1.监听服务没有起起来。windows平台个一如下操作:开始---程序---管理工具---服务,打开服务面板,
启动
oraclehome92TNSlistener服务。
2.database instance没有起起来。windows平台如下操作:开始---程序---管理工具---服务,打开服务
面板,启动oracleserviceXXXX,XXXX就是你的database SID.
3.注册表问题。regedit,然后进入HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\HOME0将该环境变量ORACLE_SI
D设置为XXXX,XXXX就是你的database SID.或者右几我的电脑,属性--高级--环境变量---系统变量--新建
,变量名=oracle_sid,变量值=XXXX,XXXX就是你的database SID.或者进入sqlplus前,在command line下
输set oracle_sid=XXXX,XXXX就是你的database SID.
经过以上步骤,就可以解决问题。
其实Oracle的优化器有两种优化方式,
基于规则的优化方式(Rule-Based Optimization,简称为RBO)
基于代价的优化方式(Cost-Based Optimization,简称为CBO)
所以hint也不例外,除了/*+rule*/其他的都是CBO优化方式
RBO方式:
优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。
CBO方式:
它是看语句的代价(Cost),这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是做analyze后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些应及时更新这些信息。
优化模式包括Rule、Choose、First rows、All rows四种方式:
Rule:基于规则的方式。
Choolse:默认的情况下Oracle用的便是这种方式。指的是当一个表或或索引有统计信息,则走CBO的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走RBO的方式。
First Rows:它与Choose方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。
All Rows:也就是我们所说的Cost的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走RBO的方式
Oracle在那配置默认的优化规则
A、Instance级别我们可以通过在initSID.ora文件中设定OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS如果没设定OPTIMIZER_MODE参数则默认用的是Choose方式。
B、Sessions级别通过ALTER SESSION SET OPTIMIZER_MODE=RULE/CHOOSE/FIRST_ROWS/ALL_ROWS来设定。
C、语句级别用Hint(/*+ ... */)来设定
为什么表的某个字段明明有索引,但执行计划却不走索引?
1、优化模式是all_rows的方式
2、表作过analyze,有统计信息
3、表很小,Oracle的优化器认为不值得走索引。
提示
不区分大小写, 多个提示用空格分开
如:select /*+ hint1(tab1) hint2(TAB1 idx1) */ col1, col2 from tab1 where col1='xxx';
如果表使用了别名, 那么提示里也必须使用别名
如:select /*+ hint1(t1) */ col1, col2 from tab1 t1 where col1='xxx';
如果使用同一个表的多个用,号分开
如: select /*+ index(t1.A,t1.B) */ col1, col2
from tab1 t1
where col1='xxx';
oracle 10g hints知识,
10g数据库可以使用更多新的optimizer hints来控制优化行为。现在让我们快速解析一下这些强大的新hints:
1、spread_min_analysis
使用这一hint,你可以忽略一些关于如详细的关系依赖图分析等电子表格的编译时间优化规则。其他的一些优化,如创建过滤以有选择性的定位电子表格访问结构并限制修订规则等,得到了继续使用。
由于在规则数非常大的情况下,电子表格分析会很长。这一提示可以帮助我们减少由此产生的数以百小时计的编译时间。
例:
SELECT /*+ SPREAD_MIN_ANALYSIS */ ...
2、spread_no_analysis
通过这一hint,可以使无电子表格分析成为可能。同样,使用这一hint可以忽略修订规则和过滤产生。如果存在一电子表格分析,编译时间可以被减少到最低程度。
例:
SELECT /*+ SPREAD_NO_ANALYSIS */ ...
3、use_nl_with_index
这项hint使CBO通过嵌套循环把特定的表格加入到另一原始行。只有在以下情况中,它才使用特定表格作为内部表格:如果没有指定标签,CBO必须可以使用一些标签,且这些标签至少有一个作为索引键值加入判断;反之,CBO必须能够使用至少有一个作为索引键值加入判断的标签。
例:
SELECT /*+ USE_NL_WITH_INDEX (polrecpolrind) */ ...
4、CARDINALITY
此hint定义了对由查询或查询部分返回的基数的评价。注意如果没有定义表格,基数是由整个查询所返回的总行数。
例:
SELECT /*+ CARDINALITY ( [tablespec] card ) */
5、SELECTIVITY
此hint定义了对查询或查询部分选择性的评价。如果只定义了一个表格,选择性是在所定义表格里满足所有单一表格判断的行部分。如果定义了一系列表格,选择性是指在合并以任何顺序满足所有可用判断的全部表格后,所得结果中的行部分。
例:
SELECT /*+ SELECTIVITY ( [tablespec] sel ) */
然而,注意如果hints CARDINALITY 和 SELECTIVITY都定义在同样的一批表格,二者都会被忽略。
6、no_use_nl
Hint no_use_nl使CBO执行循环嵌套,通过把指定表格作为内部表格,把每个指定表格连接到另一原始行。通过这一hint,只有hash join和sort-merge joins会为指定表格所考虑。
例:
SELECT /*+ NO_USE_NL ( employees ) */ ...
7、no_use_merge
此hint使CBO通过把指定表格作为内部表格的方式,拒绝sort-merge把每个指定表格加入到另一原始行。
例:
SELECT /*+ NO_USE_MERGE ( employees dept ) */ ...
8、no_use_hash
此hint使CBO通过把指定表格作为内部表格的方式,拒绝hash joins把每个指定表格加入到另一原始行。
例:
SELECT /*+ NO_USE_HASH ( employees dept ) */ ...
9、no_index_ffs
此hint使CBO拒绝对指定表格的指定标签进行fast full-index scan。
Syntax: /*+ NO_INDEX_FFS ( tablespecindexspec ) */
在SQL优化过程中常见HINT的用法(前10个比较常用, 前3个最常用):
1. /*+ INDEX */ 和 /*+ INDEX(TABLE INDEX1, index2) */ 和 /*+ INDEX(tab1.col1 tab2.col2) */ 和 /*+ NO_INDEX */ 和 /*+ NO_INDEX(TABLE INDEX1, index2) */
表明对表选择索引的扫描方法. 第一种不指定索引名是让oracle对表中可用索引比较并选择某个最佳索引; 第二种是指定索引名且可指定多个索引; 第三种是10g开始有的, 指定列名, 且表名可不用别名; 第四种即全表扫描; 第五种表示禁用某个索引, 特别适合于准备删除某个索引前的评估操作. 如果同时使用了INDEX和NO_INDEX则两个提示都会被忽略掉.
例如:SELECT /*+ INDEX(BSEMPMS SEX_INDEX) USE SEX_INDEX BECAUSE THERE ARE FEWMALE BSEMPMS */ FROM BSEMPMS WHERE SEX='M';
2. /*+ ORDERED */
FROM子句中默认最后一个表是驱动表,ORDERED将from子句中第一个表作为驱动表. 特别适合于多表连接非常慢时尝试.
例如:SELECT /*+ ORDERED */ A.COL1,B.COL2,C.COL3 FROM TABLE1 A,TABLE2 B,TABLE3 C WHERE A.COL1=B.COL1 AND B.COL1=C.COL1;
3. /*+ PARALLEL(table1,DEGREE) */ 和 /*+ NO_PARALLEL(table1) */
该提示会将需要执行全表扫描的查询分成多个部分(并行度)执行, 然后在不同的操作系统进程中处理每个部分. 该提示还可用于DML语句. 如果SQL里还有排序操作, 进程数会翻倍,此外还有一个一个负责组合这些部分的进程,如下面的例子会产生9个进程. 如果在提示中没有指定DEGREE, 那么就会使用创建表时的默认值. 该提示在默认情况下会使用APPEND提示. NO_PARALLEL是禁止并行操作,否则语句会使用由于定义了并行对象而产生的并行处理.
例如:select /*+ PARALLEL(tab_test,4) */ col1, col2 from tab_test order by col2;
4. /*+ FIRST_ROWS */ 和 /*+ FIRST_ROWS(n) */
表示用最快速度获得第1/n行, 获得最佳响应时间, 使资源消耗最小化.
在update和delete语句里会被忽略, 使用分组语句如group by/distinct/intersect/minus/union时也会被忽略.
例如:SELECT /*+ FIRST_ROWS */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';
5. /*+ RULE */
表明对语句块选择基于规则的优化方法.
例如:SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';
6. /*+ FULL(TABLE) */
表明对表选择全局扫描的方法.
例如:SELECT /*+ FULL(A) */ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE EMP_NO='SCOTT';
7. /*+ LEADING(TABLE) */
类似于ORDERED提示, 将指定的表作为连接次序中的驱动表.
8. /*+ USE_NL(TABLE1,TABLE2) */
将指定表与嵌套的连接的行源进行连接,以最快速度返回第一行再连接,与USE_MERGE刚好相反.
例如:SELECT /*+ ORDERED USE_NL(BSEMPMS) */ BSDPTMS.DPT_NO,BSEMPMS.EMP_NO,BSEMPMS.EMP_NAM FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;
9. /*+ APPEND */ 和 /*+ NOAPPEND */
直接插入到表的最后,该提示不会检查当前是否有插入操作所需的块空间而是直接添加到新块中, 所以可以提高速度. 当然也会浪费些空间, 因为它不会使用那些做了delete操作的块空间. NOAPPEND提示则相反,所以会取消PARALLEL提示的默认APPEND提示.
例如:insert /*+ append */ into test1 select * from test4;
insert /*+ parallel(test1) noappend */ into test1 select * from test4;
10. /*+ USE_HASH(TABLE1,table2) */
将指定的表与其它行源通过哈希连接方式连接起来.为较大的结果集提供最佳响应时间. 类似于在连接表的结果中遍历每个表上每个结果的嵌套循环, 指定的hash表将被放入内存, 所以需要有足够的内存(hash_area_size或pga_aggregate_target)才能保证语句正确执行, 否则将在磁盘里进行.
例如:SELECT /*+ USE_HASH(BSEMPMS,BSDPTMS) */ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;
---------------------------------------------------------------------
11. /*+ USE_MERGE(TABLE) */
将指定的表与其它行源通过合并排序连接方式连接起来.特别适合于那种在多个表大量行上进行集合操作的查询, 它会将指定表检索到的的所有行排序后再被合并, 与USE_NL刚好相反.
例如:SELECT /*+ USE_MERGE(BSEMPMS,BSDPTMS) */ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;
12. /*+ ALL_ROWS */
表明对语句块选择基于开销的优化方法,并获得最佳吞吐量,使资源消耗最小化. 可能会限制某些索引的使用.
例如:SELECT /*+ ALL+_ROWS */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';
13. /*+ CLUSTER(TABLE) */
提示明确表明对指定表选择簇扫描的访问方法. 如果经常访问连接表但很少修改它, 那就使用集群提示.
例如:SELECT /*+ CLUSTER */ BSEMPMS.EMP_NO,DPT_NO FROM BSEMPMS,BSDPTMS WHERE DPT_NO='TEC304' AND BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;
14. /*+ INDEX_ASC(TABLE INDEX1, INDEX2) */
表明对表选择索引升序的扫描方法. 从8i开始, 这个提示和INDEX提示功能一样, 因为默认oracle就是按照升序扫描索引的, 除非未来oracle还推出降序扫描索引.
例如:SELECT /*+ INDEX_ASC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='SCOTT';
15. /*+ INDEX_COMBINE(TABLE INDEX1, INDEX2) */
指定多个位图索引, 对于B树索引则使用INDEX这个提示,如果INDEX_COMBINE中没有提供作为参数的索引,将选择出位图索引的布尔组合方式.
例如:SELECT /*+ INDEX_COMBINE(BSEMPMS SAL_BMI HIREDATE_BMI) */ * FROM BSEMPMS WHERE SAL<5000000 AND HIREDATE<SYSDATE;
16. /*+ INDEX_JOIN(TABLE INDEX1, INDEX2) */
合并索引, 所有数据都已经包含在这两个索引里, 不会再去访问表, 比使用索引并通过rowid去扫描表要快5倍.
例如:SELECT /*+ INDEX_JOIN(BSEMPMS SAL_HMI HIREDATE_BMI) */ SAL,HIREDATE FROM BSEMPMS WHERE SAL<60000;
17. /*+ INDEX_DESC(TABLE INDEX1, INDEX2) */
表明对表选择索引降序的扫描方法.
例如:SELECT /*+ INDEX_DESC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='SCOTT';
18. /*+ INDEX_FFS(TABLE INDEX_NAME) */
对指定的表执行快速全索引扫描,而不是全表扫描的办法.要求要检索的列都在索引里, 如果表有很多列时特别适用该提示.
例如:SELECT /*+ INDEX_FFS(BSEMPMS IN_EMPNAM) */ * FROM BSEMPMS WHERE DPT_NO='TEC305';
19. /*+ NO_EXPAND */
对于WHERE后面的OR 或者IN-LIST的查询语句,NO_EXPAND将阻止其基于优化器对其进行扩展, 缩短解析时间.
例如:SELECT /*+ NO_EXPAND */ * FROM BSEMPMS WHERE DPT_NO='TDC506' AND SEX='M';
20. /*+ DRIVING_SITE(TABLE) */
强制与ORACLE所选择的位置不同的表进行查询执行.特别适用于通过dblink连接的远程表.
例如:SELECT /*+ DRIVING_SITE(DEPT) */ * FROM BSEMPMS,DEPT@BSDPTMS DEPT WHERE BSEMPMS.DPT_NO=DEPT.DPT_NO;
21. /*+ CACHE(TABLE) */ 和 /*+ NOCACHE(TABLE) */
当进行全表扫描时,CACHE提示能够将表全部缓存到内存中,这样访问同一个表的用户可直接在内存中查找数据. 比较适合数据量小但常被访问的表, 也可以建表时指定cache选项这样在第一次访问时就可以对其缓存. NOCACHE则表示对已经指定了CACHE选项的表不进行缓存.
例如:SELECT /*+ FULL(BSEMPMS) CAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS;
22. /*+ PUSH_SUBQ */
当SQL里用到了子查询且返回相对少的行时, 该提示可以尽可能早对子查询进行评估从而改善性能, 不适用于合并连接或带远程表的连接.
例如:select /*+ PUSH_SUBQ */ emp.empno, emp.ename, itemno from emp, orders where emp.empno = orders.empno and emp.deptno = (select deptno from dept where loc='XXX');
远程连接其他数据库,注意判断数据库是否启动,或者是否有需要的表,否则会出错
23. /*+ INDEX_SS(TABLE INDEX1,INDEX2) */
指示对特定表的索引使用跳跃扫描, 即当组合索引的第一列不在where子句中时, 让其使用该索引
参考资料
Oracle SQL hints
/*+ hint */
/*+ hint(argument) */
/*+ hint(argument-1 argument-2) */
All hints except /*+ rule */ cause the CBO to be used. Therefore, it is good practise to analyze the underlying tables if hints are used (or the query is fully hinted. There should be no schema names in hints. Hints must use aliases if alias names are used for table names. So the following is wrong:
select /*+ index(scott.emp ix_emp) */ from scott.emp emp_alias
better:
select /*+ index(emp_alias ix_emp) */ ... from scott.emp emp_alias
Why using hints
It is a perfect valid question to ask why hints should be used. Oracle comes with an optimizer that promises to optimize a query's execution plan. When this optimizer is really doing a good job, no hints should be required at all. Sometimes, however, the characteristics of the data in the database are changing rapidly, so that the optimizer (or more accuratly, its statistics) are out of date. In this case, a hint could help. It must also be noted, that Oracle allows to lock the statistics when they look ideal which should make the hints meaningless again.
Hint categories
Hints can be categorized as follows:
Hints for Optimization Approaches and Goals,
Hints for Access Paths, Hints for Query Transformations,
Hints for Join Orders,
Hints for Join Operations,
Hints for Parallel Execution,
Additional Hints
Documented Hints
Hints for Optimization Approaches and Goals
ALL_ROWS
One of the hints that 'invokes' the Cost based optimizer
ALL_ROWS is usually used for batch processing or data warehousing systems.
FIRST_ROWS
One of the hints that 'invokes' the Cost based optimizer
FIRST_ROWS is usually used for OLTP systems.
CHOOSE
One of the hints that 'invokes' the Cost based optimizer
This hint lets the server choose (between ALL_ROWS and FIRST_ROWS, based on statistics gathered.
RULE
The RULE hint should be considered deprecated as it is dropped from Oracle9i2.
See also the following initialization parameters: optimizer_mode, optimizer_max_permutations, optimizer_index_cost_adj, optimizer_index_caching and
Hints for Access Paths
CLUSTER
Performs a nested loop by the cluster index of one of the tables.
FULL
Performs full table scan.
HASH
Hashes one table (full scan) and creates a hash index for that table. Then hashes other table and uses hash index to find corresponding records. Therefore not suitable for < or > join conditions.
ROWID
Retrieves the row by rowid
INDEX
Specifying that index index_name should be used on table tab_name: /*+ index (tab_name index_name) */
Specifying that the index should be used the the CBO thinks is most suitable. (Not always a good choice).
Starting with Oracle 10g, the index hint can be described: /*+ index(my_tab my_tab(col_1, col_2)) */. Using the index on my_tab that starts with the columns col_1 and col_2.
INDEX_ASC
INDEX_COMBINE
INDEX_DESC
INDEX_FFS
INDEX_JOIN
NO_INDEX
AND_EQUAL
The AND_EQUAL hint explicitly chooses an execution plan that uses an access path that merges the scans on several single-column indexes
Hints for Query Transformations
FACT
The FACT hint is used in the context of the star transformation to indicate to the transformation that the hinted table should be considered as a fact table.
MERGE
NO_EXPAND
NO_EXPAND_GSET_TO_UNION
NO_FACT
NO_MERGE
NOREWRITE
REWRITE
STAR_TRANSFORMATION
USE_CONCAT
Hints for Join Operations
DRIVING_SITE
HASH_AJ
HASH_SJ
LEADING
MERGE_AJ
MERGE_SJ
NL_AJ
NL_SJ
USE_HASH
USE_MERGE
USE_NL
Hints for Parallel Execution
NOPARALLEL
PARALLEL
NOPARALLEL_INDEX
PARALLEL_INDEX
PQ_DISTRIBUTE
Additional Hints
ANTIJOIN
APPEND
If a table or an index is specified with nologging, this hint applied with an insert statement produces a direct path insert which reduces generation of redo.
BITMAP
BUFFER
CACHE
CARDINALITY
CPU_COSTING
DYNAMIC_SAMPLING
INLINE
MATERIALIZE
NO_ACCESS
NO_BUFFER
NO_MONITORING
NO_PUSH_PRED
NO_PUSH_SUBQ
NO_QKN_BUFF
NO_SEMIJOIN
NOAPPEND
NOCACHE
OR_EXPAND
ORDERED
ORDERED_PREDICATES
PUSH_PRED
PUSH_SUBQ
QB_NAME
RESULT_CACHE (Oracle 11g)
SELECTIVITY
SEMIJOIN
SEMIJOIN_DRIVER
STAR
The STAR hint forces a star query plan to be used, if possible. A star plan has the largest table in the query last in the join order and joins it with a nested loops join on a concatenated index. The STAR hint applies when there are at least three tables, the large table's concatenated index has at least three columns, and there are no conflicting access or join method hints. The optimizer also considers different permutations of the small tables.
SWAP_JOIN_INPUTS
USE_ANTI
USE_SEMI
Undocumented hints:
BYPASS_RECURSIVE_CHECK
Workaraound for bug 1816154
BYPASS_UJVC
CACHE_CB
CACHE_TEMP_TABLE
CIV_GB
COLLECTIONS_GET_REFS
CUBE_GB
CURSOR_SHARING_EXACT
DEREF_NO_REWRITE
DML_UPDATE
DOMAIN_INDEX_NO_SORT
DOMAIN_INDEX_SORT
DYNAMIC_SAMPLING
DYNAMIC_SAMPLING_EST_CDN
EXPAND_GSET_TO_UNION
FORCE_SAMPLE_BLOCK
GBY_CONC_ROLLUP
GLOBAL_TABLE_HINTS
HWM_BROKERED
IGNORE_ON_CLAUSE
IGNORE_WHERE_CLAUSE
INDEX_RRS
INDEX_SS
INDEX_SS_ASC
INDEX_SS_DESC
LIKE_EXPAND
LOCAL_INDEXES
MV_MERGE
NESTED_TABLE_GET_REFS
NESTED_TABLE_SET_REFS
NESTED_TABLE_SET_SETID
NO_FILTERING
NO_ORDER_ROLLUPS
NO_PRUNE_GSETS
NO_STATS_GSETS
NO_UNNEST
NOCPU_COSTING
OVERFLOW_NOMOVE
PIV_GB
PIV_SSF
PQ_MAP
PQ_NOMAP
REMOTE_MAPPED
RESTORE_AS_INTERVALS
SAVE_AS_INTERVALS
SCN_ASCENDING
SKIP_EXT_OPTIMIZER
SQLLDR
SYS_DL_CURSOR
SYS_PARALLEL_TXN
SYS_RID_ORDER
TIV_GB
TIV_SSF
UNNEST
USE_TTT_FOR_GSETS
摘要: 如果要分析某条SQL的性能问题,通常我们要先看SQL的执行计划,看看SQL的每一步执行是否存在问题。 如果一条SQL平时执行的好好的,却有一天突然性能很差,如果排除了系统资源和阻塞的原因,那么基本可以断定是执行计划出了问题。
&nb...
阅读全文
摘要: SQL查询语句的性能从一定程度上影响整个数据库的性能。很多情况下,数据库性能的低下差不多都是不良SQL语句所引起。而SQL语句的执行计划则决定了SQL语句将会采用何种方式从数据库提取数据并返回给客户端,本文描述的将是如何通过EXPLAIN PLAN 获取SQL语句执行计划来获取SQL语句的执行计划。一、获取SQL语句执行计划的方式 1. 使用expl...
阅读全文
对于sql执行的小量高低。我们可以通过执行计划的信息基本上可以进行分析查看该SQL语句执行的时间。连接顺序及浪费的数据库资源等信息,从而判断该SQL语句执行的效率如何,下面就简单的介绍一下执行计划的使用
2. Explain使用
Oracle RDBMS执行每一条SQL语句,都必须经过Oracle优化器的评估。所 以,了解优化器是如何选择(搜索)路径以及索引是如何被使用的,对优化SQL语句有很大的帮助。Explain可以用来迅速方便地查出对于给定SQL语句中的查询数据是如何得到的即搜索路径(我们通常称为Access Path)。从而使我们选择最优的查询方式达到最大的优化效果。
2.1. 安装
要使用执行计划首先需要执行相应的脚本。
使用Explain工具需要创建Explain_plan表,这必须先进入相关应用表、视图和索引的所有者的帐户内。Oracle的介质中包含有执行此项工作的SQL源程序,例如:
ORA_RDBMS: XPLAINPL.SQL (VMS)
$ORACLE_HOME/rdbms/admin/utlxplan.sql (UNIX)
该脚本后会生成一个表这个程序会创建一个名为plan_table的表,表结构如下:
我们简单的介绍一下主要的字段含义:
字段名 字段类型 含义
STATEMENT_ID VARCHAR2(30) explain PLAN 语句中所指定的最优STATEMENT_ID 参数值, 如果在EXPLAN PLAN语句中没有使用SET STATEMENT_ID,那么此值会被设为NULL。
REMARKS VARCHAR2(80) 与被解释规划的各步骤相关联的注释最长可达80 字节
OPERATION VARCHAR2(30) 各步骤所执行内部操作的名称在某条语句所产生的第一行中该列的可能取值如下DELETE STATEMENT INSERT STATEMENT SELECT STATEMENT UPDATE STATEMENT
OPTIONS VARCHAR2(30) 对OPERATION 列中所描述操作的变种
OBJECT_NODE VARCHAR2(128) 用于访问对象的数据库链接database link 的名称对于使用并行执行的本地查询该列能够描述操作中输出的次序
OBJECT_OWNER VARCHAR2(30) 对于包含有表或索引的架构schema 给出其所有者的名称
OBJECT_NAME VARCHAR2(30) 表或索引的名称
OBJECT_INSTANCE INTEGER 根据对象出现在原始original 语句中的次序所给出的相应次序编号就原始的语句文本而论其处理顺序为自左至右自外向内景象扩张view
OBJECT_TYPE VARCHAR2(30) 用于提供对象描述性信息的修饰符例如索引的NON-UNIQUE
OPTIMIZER VARCHAR2(255) 当前优化程序的模式
ID INTEGER 分配给执行规划各步骤的编号
PARENT_ID INTEGER 对ID 步骤的输出进行操作的下一个执行步骤的ID
POSITION INTEGER 对于具有相同PARENT_ID 的步骤其相应的处理次序
COST INTEGER 根据优化程序的基于开销的方法所估计出的操作开销值对于使用基于规则方法的语句该列为空该列值没有特定的测量单位它只是一个用于比较执行规划开销大小的权重值
CARDINALITY INTEGER 根据基于开销的方法对操作所访问行数的估计值
BYTES INTEGER 根据基于开销的方法对操作所访问字节的估计
2.2. 使用
2.2.1. 常规使用
常规使用语法:
explain PLAN [ SET STATEMENT_ID [=] < string literal > ]
[ INTO < table_name > ]
FOR < sql_statement >
其中:
STATEMENT_ID是一个唯一的字符串,把当前执行计划与存储在同一PLAN表中的其它执行计划区别开来。
TABLE_NAME是plan表名,它结构如前所示,你可以任意设定这个名称。
SQL_STATEMENT是真正的SQL语句。
如:
SQL> explain plan set statement_id='test1' for
2 SELECT a.soctermbegin,
3 a.soctermend,
4 a.dealserialno,
5 a.levydataid,
6 a.dealtotal,
7 e.categoryitemcode,
8 row_number() over(PARTITION BY a.levydataid ORDER BY 1) AS theRow
9 FROM tb_soc_packdealdata a,
10 tb_Lvy_TaxDataBillMap c,
11 Tb_lvy_BillData d,
12 tb_soc_levydetaildata e
13 WHERE a.levydataid = c.datafrompointer(+)
14 AND c.billdataid = d.billdataid(+)
15 AND a.levydataid = e.levydataid
16 AND a.packdealstatuscode = '10'
17 AND (a.datastatus <> '9' OR a.datastatus is NULL)
18 AND (d.billstatus IS NULL OR
19 (d.billstatus <> '2' AND d.billstatus <> '8'))
20 AND a.Insurcode = '6010952'
21 ;
Explained
执行下面语句就可以查看该语句执行的执行计划:
SQL> SELECT A.OPERATION,OPTIONS,OBJECT_NAME,OBJECT_TYPE,ID,PARENT_ID
2 FROM PLAN_TABLE a
3 WHERE STATEMENT_ID='test1'
4 ORDER BY Id;
OPERATION OPTIONS OBJECT_NAME OBJECT_TYPEID PARENT_ID
---------------- --------------------------------------------- ------------- ----------
SELECT STATEMENT 0
WINDOW SORT 1 0
FILTER 2 1
NESTED LOOPS OUTER 3 2
NESTED LOOPS OUTER 4 3
NESTED LOOPS 5 4
TABLE ACCESS FULL TB_SOC_PACKDEALDATA 6 5
TABLE ACCESS BY INDEX ROWID TB_SOC_LEVYDETAILDATA 7 5
INDEX RANGE SCAN IND_DATAID_LEVSOC NON-UNIQUE 8 7
TABLE ACCESS BY INDEX ROWID TB_LVY_TAXDATABILLMAP 9 4
INDEX RANGE SCAN TBLVYTAXDATABIL_DATAFROMPOINTE NON-UNIQUE 10 9
TABLE ACCESS BY INDEX ROWID TB_LVY_BILLDATA 11 3
INDEX UNIQUE SCAN TBLVYBILLDATA_BILLDATAID UNIQUE
2.2.2. 自动显示使用
在SQLPLUS中自动跟踪显示执行计划及相关信息
SQL>set timing on --显示执行时间
SQL>set autorace on ?C显示执行计划
SQL>set autorace on ?C显示执行计划
SQL>set autotrace traceonly ?C只显示执行计划即不显示查询出来的数据
设置完毕后执行SQL语句就会显示执行计划信息及相应的统计信息(需要设置显示该选项)
SQL> select nvl(sum(t.taxdue), 0)
2 from tb_lvy_sbzs100 t, tb_lvy_declaredoc a, tb_lvy_declaredoc b
3 where a.dossiercode = 'SB02041108'
4 and a.pages = 123
5 and a.remarkid = b.remarkid
6 AND A.REMARKID IS NOT NULL
7 and b.declaredocid = t.declaredocid;
NVL(SUM(T.TAXDUE),0)
--------------------
0
已用时间: 00: 00: 04.07
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=110)
1 0 SORT (AGGREGATE)
2 1 NESTED LOOPS (Cost=6 Card=1 Bytes=110)
3 2 MERGE JOIN (CARTESIAN) (Cost=4 Card=1 Bytes=74)
4 3 TABLE ACCESS (FULL) OF 'TB_LVY_SBZS100' (Cost=2 Card =1 Bytes=31)
5 3 BUFFER (SORT) (Cost=2 Card=1 Bytes=43)
6 5 TABLE ACCESS (FULL) OF 'TB_LVY_DECLAREDOC' (Cost=2 Card=1 Bytes=43)
7 2 TABLE ACCESS (BY INDEX ROWID) OF 'TB_LVY_DECLAREDOC' (Cost=2 Card=1 Bytes=36)
8 7 INDEX (UNIQUE SCAN) OF 'TBLVYDECLAREDOC_DECLAREDOCID' (UNIQUE)
Statistics
----------------------------------------------------------
0 recursive calls --循环递归次数
0 db block gets―请求的数据块在buffer能满足的个数
6675 consistent gets --逻辑IO用于读表并计算行数, 数据请求总数在回滚段Buffer中
45 physical reads ?C从磁盘读到Buffer Cache数据块数量
0 redo size ?C产生的redo日志大小
217 bytes sent via SQL*Net to client
276 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
如果6675 consistent gets --逻辑IO用于读表并计算行数, 数据请求总数在回滚段Buffer中
45 physical reads ?C从磁盘读到Buffer Cache数据块数量的数值比较小则该语句对对数据库的性能比较高。
2.2.3. PL/SQL和TOAD中使用
如果在PL/SQL中使用选择要查询语句显示执行计划,则只需要SQL WINDOWS 窗口里面输入要查询的SQL语句,然后选择按键F5或者在菜单TOOLS?D?D>Explain Plan 菜单按键就可以在执行计划窗口查看该语句的执行计划。
在TOAD语句中在执行当前的SQL窗口中选择下方的Explain PlanTAB页即可以查看要执行语句的执行计划信息。
2.3. 限制
虽然任何SQL语句都可以用explain解释,但对于没有查询的INSERT,UPDATE,DELETE操作来说,这个工具并没有太大的用处。没有子查询的INSERT操作不会创建执行计划,但没有WHERE子句或子查询的UPDATE和DELETE操作会创建执行计划,因为这些操作必须先找出所要的记录。
另外,如果你在SQL语句中使用其它类型如sequence等,explain也能揭示它的用法。
explain真正的唯一的限制是用户不能去解释其它用户的表,视图,索引或其它类型,用户必须是所有被解释事物的所有者,如果不是所有者而只有select权限,explain会返回一个错误。
《Oracle物化视图实战手册》
场合:数据变化小,查询出数据还要2次利用,需要数据双向同步的场合
视图:就是一条sql语句,每次查询时都要重新生成执行计划,重新执行,非常消耗时间,放在内存中一次性的
物化视图:执行sql并保留结果,直接放在数据文件中,不放在内存中方便重用【空间换时间】,不受开关机的影响
1.创建基表并插入数据
create table sino_person_address
(
iid NUMBER(16) not null,
ipersonid NUMBER(16),
spin NUMBER(16),
dgettime DATE,
sorgcode VARCHAR2(20),
smsgfilename VARCHAR2(20),
ilineno NUMBER(8),
saddress VARCHAR2(60),
szip CHAR(6),
scondition CHAR(1),
itrust NUMBER(1),
stoporgcode VARCHAR2(14),
istate NUMBER(1),
constraint PK_SINO_PERSON_ADDRESS primary key (iid)
);
插入数据(插入自动增长序列号字段的方法)
INSERT 语句插入这个字段值为: 序列号的名称.NEXTVAL,seq_sino_person_address.nextval
insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-08 12:12:12','yyyy-mm-dd hh24:mi:ss'),'110','test_report',111,'beijing
xicheng','100100','1',123,1,'1000',0);
insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-09 12:12:12','yyyy-mm-dd hh24:mi:ss'),'120','test_report2',121,'beijing
xicheng','100200','2',123,1,'1002',2);
insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-10 12:12:12','yyyy-mm-dd hh24:mi:ss'),'130','test_report3',131,'beijing
xicheng','100300','3',123,1,'1003',3);
commit
###################################################################################################
2.创建物化视图日志
意义:记录基表DML操作的变化,实时刷新物化视图
注:包含所有字段
删除物化视图日志
drop materialized view log on t
创建物化视图日志
create materialized view log on t with sequence,rowid (x,y,z) including new values;
参数说明:
with sequence:以序号增1的方式进行变化记录
rowid (x,y,z):定位哪些数据发生了变化,日志记录rowid指向的数据块的位置和变化
删除物化视图日志
drop materialized view log on sino_person_address;
基于主键方式的刷新,创建物化视图日志
CREATE MATERIALIZED VIEW LOG ON sino_person_address
WITH PRIMARY KEY
INCLUDING NEW VALUES
【TABLESPACE sinojfs2】; 可选项
3.创建物化视图
创建物化视图
create materialized view mv_t build immediate refresh fast on commit enable query rewrite as select x,y,z,count(*) from t group by x,y,z;
删除物化视图
drop materialized view mv_sino_person_address;
create materialized view mv_sino_person_address
tablespace SINOJFS2
build immediate 创建物化视图时,立即刷新基表
refresh fast with primary key 支持基于主键的快速刷新(增量刷新),基表必须有主键
on commit 支持commit动作自动刷新
enable query rewrite
as select * from sino_person_address;
create materialized view mv_sino_person_address
tablespace SINOJFS2
build immediate
refresh fast with primary key refresh complete全部刷新【全表刷新】可选项
on demand 支持需求时手工刷新
enable query rewrite
as select * from sino_person_address;
########################################################################################
参数说明:
build immediate:创建物化视图时,立即刷新基表
refresh fast with primary key:支持基于主键的快速刷新(增量刷新),基表必须有主键
on commit:基于commit动作的自动刷新 on demand:基于需求时的手工刷新
enable query rewrite:支持查询重新(使用物化视图代替基表,查询必须重写,查询重写是透明的并且不需要对物化视图有任何权限,物化视图可以启用和禁用查询重写)
查询重写:select * from t基表,执行计划走的是mv_t物化视图,禁用后,执行计划走的就是t基表了
tablespace SINOJFS2 创建于SINOJFS2表空间
(1)创建方式:BUILD IMMEDIATE(立即生成数据), BUILD DEFERRED(下一次刷新时生新数据), ON PREBUILD TABLE(不创建新的数据段,用已存在的含有当前物化视图数据的表来代替);
(2)ENABLE | DISABLE QUERY REWRITE指定是否启用当前物化视图用于查询重写,启用该选项时,系统会检查以保证查询的可确定性(不允许有如序列数,USER, DATE等不确定的返回值),DISABLE时物化视图照样可以被刷新;
与物化视图生效相关的设置
(1)初始化参数JOB_QUEUE_PROCESSES设置大于零,物化的自动刷新操作需要JOB QUEUE进程来执行;
(2)初始化参数OPTIMIZER_MODE要设成某种CBO优化模式;
(3)用户会话有QUERY_REWRITE(优化器能将查询重写到本方案物化视图)或GLOBAL_QUERY_REWRITE(优化器能将查询重写到其它方案的物化视图)系统权限;
(4)初始化参数QUERY_REWRITE_ENABLED 指示优化器是否动态重写查询来使用物化视图,这个参数可以在四个级别上进行设置(参数文件,ALTER SYSTEM, ALTER SESSION, HINTS);
(5)初始化参数QUERY_REWRITE_INTEGRITY 指示优化器在不同的数据一致性情况下决定是否使用物化视图来重写查询,ENFORCED(只有在能确保数据一致的前提下才使用物化视图), TRUSTED(数据不一定一致,只要有用维度对象定义的关系存在,就可使用物化视图), STALE_TOLERATED(数据不一致,也没有相关的维度定义时仍可使用物化视图),这个参数可以在三个级别上进行设置(参数文件,ALTER SYSTEM, ALTER SESSION);
4. 物化视图DML操作测试
(1)验证物化视图是否随记录增加而增加
insert into sino_person_address values(seq_sino_person_address.nextval,123,to_date('2013-04-11 13:13:13','yyyy-mm-dd hh24:mi:ss'),'140','test_report4',141,'beijing
xicheng','100400','4',123,1,'1004',4);
select * from sino_person_address order by dgetdate;
select * from mv_sino_person_address order by dgetdate; 随记录增加而木有刷新,必须commit之后才触发物化视图刷新,没有问题
exec dbms_mview.refresh('mv_sino_person_address','c'); 还可以手动全部刷新【全表刷新】(先清除,再重装数据)
exec dbms_mview.refresh('mv_sino_person_address','f'); 也可以快速刷新【增量刷新】借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)
(2)验证物化视图是否随记录删除而减少
delete from sino_person_address where iid=21;
select * from sino_person_address order by dgetdate;
select * from mv_sino_person_address order by dgetdate; 随记录删除而木有刷新,必须commit之后才触发物化视图刷新,没有问题
exec dbms_mview.refresh('mv_sino_person_address','c'); 还可以手动全部刷新【全表刷新】(先清除,再重装数据)
exec dbms_mview.refresh('mv_sino_person_address','f'); 也可以快速刷新【增量刷新】借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)
(3)验证物化视图是否随记录修改而更新
update sino_person_address set sorgcode='200' where sorgcode='120';
select * from sino_person_address order by dgetdate;
select * from mv_sino_person_address order by dgetdate; 随记录修改而木有刷新,必须commit之后才触发物化视图刷新,没有问题
exec dbms_mview.refresh('mv_sino_person_address','c'); 还可以手动全部刷新【全表刷新】(先清除,再重装数据)
exec dbms_mview.refresh('mv_sino_person_address','f'); 也可以快速刷新【增量刷新】(借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)
(4)验证物化视图是否随truncate而清空
truncate table sino_person_address;
select * from sino_person_address order by dgetdate;
select * from mv_sino_person_address order by dgetdate; 随记录truncate而木有清空,必须手动truncate table mv_sino_person_address;才能清空(两者是没有关联的),没有问题
5.物化视图刷新
根据业务需求,每月定时刷新。根据以上条件,选择使用ORACLE自带工具DBMS_MVIEW工具包中REFRESH方法对物化视图进行刷新。该方法有两个参数,第一个参数是需要刷新的物化视图名称,第二个参数是刷新方式。我们可以写存储过程,对每个物化视图调用一次REFRESH方法,也可以使用“,”把物化视图连接以来,一次刷新。
定义存储过程
create or replace procedure pro_mview_refresh
as
begin
dbms_mview.refresh('mv_sino_person_address','f');
end;
/
执行存储过程
execute pro_mview_refresh;
还可以刷新所有物化视图 dbms_mview.refresh_all_mviews;
创建存储过程
drop procedure pro_refresh_all_mviews;
create or replace procedure pro_refresh_all_mviews
as
i number;
begin
dbms_mview.refresh_all_mviews(number_of_failures=>i);
dbms_output.put_line('number_of_failures=>'||i);
end;
/
执行
executepro_refresh_all_mviews;
set serveroutput on;不可放在存储过程中,因为这是sqlplus命令,如果你怕忘记或者嫌麻烦可以把set serveroutput on;
写入/opt/oracle/product/11.2.0/dbhome_1/sqlplus/admin/glogin.sql中,每次使用sqlplus时自动加载这个文件
如果想用PL/SQL Developer工具访问数据库,请在C:\Program Files\PLSQL Developer\Login.sql 文件里添加
-- Autostart Command Window script
set serveroutput on;
这样以后再使用PL/SQL Developer工具访问数据库就可以自动加载这条命令了
###############################################################################################
研发人员专用,手动刷新,想刷就刷
set serveroutput on; 打开屏幕显示功能,就可以看到number_of_failures=>0结果
PL/SQL 匿名块
declare
i number;
begin
dbms_mview.refresh_all_mviews(number_of_failures=>i);
dbms_output.put_line('number_of_failures=>'||i);
end;
/
number_of_failures=>0
Number_of_failures 表示刷新物化视图失败个数
采用默认refresh force 刷新方式:先试图用FAST方式刷新,如果失败再用COMPLETE方式刷新,这是默认的刷新方式
注意:
1、 如果需要同时刷新多个物化视图,必须用逗号把各个物化视图名称连接起来,并对每个视图都要指明刷新方式(f、增量刷新,c、完全刷新,?、强制刷新,从不刷新)。
NEVER REFRESH(不刷新)
REFREST FAST(借助物化视图日志,只检查自上次刷新后改变了的数据来进行刷新)
REFRESH COMPLETE(先清除,再重装数据)
REFRESH FORCE(先试图用FAST方式刷新,如果失败再用COMPLETE方式刷新,这是默认的刷新方式)
确定刷新时机:
ON COMMIT(事务提交时刷新),
ON DEMAND(用DBMS_MVIEW.REFRESH, DBMS_MVIEW.REFRESH_DEPENDENT, DBMS_MVIEW.REFRESH_ALL_MVIEWS来手工刷新),
By Time(用START WITH 和 NEXT 子句创建的job来定时自动刷新);
[dbms_mview.refresh('mv_sino_person_address,mv_person_address_his','ff');]
2、当日志和物化视图创建好后,删除日志,则需要重新创建物化视图,否则无法增量刷新。
drop materialized view log on sino_person_address; 删除日志
SQL> exec dbms_mview.refresh('mv_sino_person_address','c'); 删除物化视图日志,只可以支持物化视图全部刷新
PL/SQL procedure successfully completed
#################################################################################
SQL> exec dbms_mview.refresh('mv_sino_person_address','f'); 无法增量刷新
begin dbms_mview.refresh('mv_sino_person_address','f'); end;
ORA-23413: 表 "SINOJFS"."SINO_PERSON_ADDRESS" 没有实体化视图日志
ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2558
ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2771
ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2740
ORA-06512: 在 line 2
SQL> create materialized view log on sino_person_employment 重新创建物化视图日志
2 with primary key
3 including new values;
Materialized view log created
SQL> exec dbms_mview.refresh('mv_sino_person_employment','f'); 但还是不支持增量刷新,因为日志内容和原表内容不一致了
begin dbms_mview.refresh('mv_sino_person_employment','f'); end;
ORA-12034: "SINOJFS"."SINO_PERSON_EMPLOYMENT" 上的实体化视图日志比上次刷新后的内容新
ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2558
ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2771
ORA-06512: 在 "SYS.DBMS_SNAPSHOT", line 2740
ORA-06512: 在 line 2
因为:丢失了删除日志那一点->重建日志那一点之间的原表DML变化,因此日志内容和原表内容不一致了
解决方案:重建物化视图日志 重新【增量刷新】和【全表刷新】一遍
SQL> drop materialized view log on sino_loan_compact; 删除日志
Materialized view log dropped
SQL> create materialized view log on sino_loan_compact 重建日志
2 with primary key
3 including new values;
Materialized view log created
SQL> exec dbms_mview.refresh('mv_sino_loan_compact','c'); 必须先全表刷新
PL/SQL procedure successfully completed
SQL> exec dbms_mview.refresh('mv_sino_loan_compact','f'); 再增量刷新,否则ORA-12034: "SINOJFS"."SINO_LOAN_COMPACT" 上的实体化视图日志比上次刷新后的内容新
PL/SQL procedure successfully completed
小结:只要能够增量刷新,说明日志没有问题了
简述所有视图的快速刷新和全表刷新命令(测试使用) 10张视图
select owner,table_name,tablespace_name,status from dba_tables where table_name in ('SINO_LOAN_APPLY');
update SINO_LOAN_APPLY set sorgcode ='1000' where iid =858;
exec dbms_mview.refresh('mv_sino_loan_compact','c');
exec dbms_mview.refresh('mv_sino_loan_compact','f');
exec dbms_mview.refresh('mv_sino_loan_apply','c');
exec dbms_mview.refresh('mv_sino_loan_apply','f');
exec dbms_mview.refresh('mv_sino_loan_spec_trade','c');
exec dbms_mview.refresh('mv_sino_loan_spec_trade','f');
exec dbms_mview.refresh('mv_sino_loan','c');
exec dbms_mview.refresh('mv_sino_loan','f');
exec dbms_mview.refresh('mv_sino_loan_guarantee','c');
exec dbms_mview.refresh('mv_sino_loan_guarantee','f');
exec dbms_mview.refresh('mv_sino_loan_investor','c');
exec dbms_mview.refresh('mv_sino_loan_investor','f');
###############################################################################
exec dbms_mview.refresh('mv_sino_person_employment','c');
exec dbms_mview.refresh('mv_sino_person_employment','f');
exec dbms_mview.refresh('mv_sino_person_address','c');
exec dbms_mview.refresh('mv_sino_person_address','f');
exec dbms_mview.refresh('mv_sino_person_certification','c');
exec dbms_mview.refresh('mv_sino_person_certification','f');
exec dbms_mview.refresh('mv_sino_person','c');
exec dbms_mview.refresh('mv_sino_person','f');
3.基表增加字段后对应物化视图不能自动同步结构
业务表增加 上报状态 字段 ipbcstate number(1) 可以为空
文档 建模 脚本 物化视图
sino_person_certification 完成 完成 完成 完成
sino_person 完成 完成 完成 完成
sino_person_address 完成 完成 完成 完成
sino_person_employment 完成 完成 完成 完成
sino_person_address_his 完成 完成 完成
sino_person_employment_his 完成 完成 完成
sino_person_his 完成 完成 完成
sino_loan 完成 完成 完成 完成
sino_loan_compact 完成 完成 完成 完成
sino_loan_spec_trade 完成 完成 完成 完成
sino_loan_guarantee 完成 完成 完成 完成
sino_loan_investor 完成 完成 完成 完成
sino_loan_apply 完成 完成 完成 完成
对比IPBCSTATE 字段基表有,但物化视图没有,需要重建物化视图解决
select * from mv_sino_loan_compact where rownum<2;
select * from sino_loan_compact where rownum<2;
select * from mv_sino_loan where rownum < 2;
select * from sino_loan where rownum < 2;
select * from mv_sino_loan_apply where rownum < 2;
select * from sino_loan_apply where rownum < 2;
select * from mv_sino_loan_guarantee where rownum < 2;
select * from sino_loan_guarantee where rownum < 2;
select * from mv_sino_loan_guarantee where rownum < 2;
select * from sino_loan_guarantee where rownum < 2;
select * from mv_sino_loan_investor where rownum < 2;
select * from sino_loan_investor where rownum < 2;
select * from mv_sino_loan_spec_trade where rownum < 2;
select * from sino_loan_spec_trade where rownum < 2;
################################################################################
select * from mv_sino_person where rownum < 2;
select * from sino_person where rownum < 2;
select * from mv_sino_person_address where rownum < 2;
select * from sino_person_address where rownum < 2;
select * from mv_sino_person_certification where rownum < 2;
select * from sino_person_certification where rownum < 2;
select * from mv_sino_person_employment where rownum < 2;
select * from sino_person_employment where rownum < 2;
##################################################################################
4.因为上面写的物化视图是基于主键进行刷新的,因此原表必须要有主键
6.定时刷新JOB
确定执行时间间隔
1)、 每分钟执行
Interval => TRUNC(sysdate,'mi') + 1 / (24*60)
2)、 每天定时执行
例如:每天下午2点执行一次pro_mview_refresh存储过程
Interval => TRUNC(sysdate) + 1 +14/ (24)
3)、 每周定时执行
例如:每周一凌晨2点执行
Interval => TRUNC(next_day(sysdate,2))+2/24 --星期一,一周的第二天
4)、 每月定时执行
例如:每月1日凌晨2点执行
Interval =>TRUNC(LAST_DAY(SYSDATE))+1+2/24
5)、 每季度定时执行
例如每季度的第一天凌晨2点执行
Interval => TRUNC(ADD_MONTHS(SYSDATE,3),'Q') + 2/24
6)、 每半年定时执行
例如:每年7月1日和1月1日凌晨2点
Interval => ADD_MONTHS(trunc(sysdate,'yyyy'),6)+2/24
7)、 每年定时执行
例如:每年1月1日凌晨2点执行
Interval =>ADD_MONTHS(trunc(sysdate,'yyyy'),12)+2/24
通过jobs的使用就能实现每天或每月的指定时间执行一个函数、过程与命令
set serveroutput on 启动屏幕输出功能
SQL> execute dbms_output.put_line('This is'); 已经可以正常输出
This is
创建作业
variable job_num number; 定义存储job编号的变量
declare job_num number; pro_refresh_all_mviews
begin
dbms_job.submit
(job=>:job_num,
what=>'pro_refresh_all_mviews;',
next_date=>sysdate,
interval=>'sysdate+1/1440'); 每天1440分钟,每一分钟运行pro_mview_refresh过程一次
dbms_output.put_line('Job Number is'||to_char(job_num));
commit;
end;
/
############################################################################################
绑定变量版,必须先定义变量
variable job_num number;
declare job_num number;
begin
dbms_job.submit
(job=>:job_num,
what=>'pro_refresh_all_mviews;',
next_date=>sysdate,
interval=>'trunc(SYSDATE+5/1440,''MI'')'); 每5分钟运行一次job
dbms_output.put_line('Job Number is'||to_char(job_num));
commit;
end;
/
例如:每天上午10点执行一次pro_refresh_all_mviews存储过程
Interval => TRUNC(sysdate) + 1 +10/ (24)
declare job_num number;
begin
dbms_job.submit
(job=>:job_num,
what=>'pro_refresh_all_mviews;',
next_date=>sysdate,
interval=>'trunc(SYSDATE)+1+10/24'); 每天上午10点运行一次job
dbms_output.put_line('Job Number is'||to_char(job_num));
commit;
end;
/
Job Number is
PL/SQL procedure successfully completed
job_num
---------
1
####################################################################################
PL/SQL 匿名块版,可以直接在块中定义变量,比较方面现在采用这种
declare
job_num number;
begin
dbms_job.submit
(job=>job_num,
what=>'pro_refresh_all_mviews;',
next_date=>sysdate,
interval=>'trunc(SYSDATE)+1+10/24');
dbms_output.put_line('Job Number is '||job_num);
commit;
end;
/
Job Number is 4
PL/SQL procedure successfully completed
####################################################################################
dbms_job.submit( job out binary_integer,
what in varchar2,
next_date in date,
interval in varchar2,
no_parse in boolean)
●job:输出变量,这是作业在作业队列中的编号;
●what:执行作业的存储过程及其输入参数;
●next_date:作业初次执行的时间;
●interval:作业执行的时间间隔。指上一次执行结束到下一次开始执行的时间间隔
其中Interval这个值是决定Job何时,被重新执行的关键;当interval设置为null时,该job执行结束后,就被从队列中删除。假如我们需要该job周期性地执行,则要用‘sysdate+m’表示。如何更好地确定执行时间的间隔需要我们掌握一个函数TRUNC。
SQL> show parameter job_queue_process 作业队列进程数,oracle能够并发job数量,0~1000
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
job_queue_processes integer 1000
Oracle提供的数据字典user_jobs监控作业状态
SQL> select job,log_user,what,last_date,last_sec,next_date,next_sec,failures,broken from user_jobs;
Job 作业唯一编号
Log_user 提交作业的用户
What 作业执行的存储过程
Last_date 最后一次成功运行作业的日期
Last_sec 最后一次成功运行作业的时间
Next_date 下一次运行作业日期
Next_sec 下一次运行作业时间
Failures 执行失败次数,当执行job出现错误时,Oracle将其记录在日志里,失败次数每次自动加1,加到16之后Oracle就不在执行它了
Broken 是否是异常作业,当执行失败次数达到16时,Oracle就将该job标志为broken。此后,Oracle不再继续执行它,直到用户调用过程dbms_job.broken,重新设置为not broken,或强制调用dbms_job.run来重新执行它。Y标示作业中断,以后不会运行,N表示作业正常,可以运行
运行作业
begin
dbms_job.run(:job_num); job_num是存储job编号的变量
end;
查询作业状态
SQL> select job,log_user,what,last_date,last_sec,next_date,next_sec,failures,broken from user_jobs;
JOB LOG_USER WHAT LAST_DATE LAST_SEC NEXT_DATE NEXT_SEC FAILURES BROKEN
---------- --------- ---------------------------------------------------- ----------- ---------------- -----------
1 SINOJFS pro_refresh_all_mviews; 2013-4-26 1 11:27:38 2013-4-27 1 10:00:00 0 N
Job 作业唯一编号
Log_user 提交作业的用户
What 作业执行的存储过程
Last_date 最后一次成功运行作业的日期
Last_sec 最后一次成功运行作业的时间
Next_date 下一次运行作业日期
Next_sec 下一次运行作业时间
Failures 执行失败次数,当执行job出现错误时,Oracle将其记录在日志里,失败次数每次自动加1,加到16之后Oracle就不在执行它了
Broken 是否是异常作业,当执行失败次数达到16时,Oracle就将该job标志为broken。此后,Oracle不再继续执行它,直到用户调用过程dbms_job.broken,重新设置为not broken;
或强制调用dbms_job.run来重新执行它。Y标示作业中断,以后不会运行,N表示作业正常,可以运行
删除作业
begin
dbms_job.remove(:job_num);
end;
修改作业
dbms_job.remove(jobno); 删除job号
例 execute dbms_job.remove(1);
######################################################################
dbms_job.what(jobno,what); 修改执行的存储过程
dbms_job.next_date(job,next_date)修改下次执行的时间
例 exec dbms_job.next_date(46,sysdate+2/(24*60)); 46作业号
#####################################################################
dbms_job.interval(job,interval) :修改间隔时间
例 exec dbms_job.interval(46,sysdate+3/(24*60));
######################################################################
dbms_job.broken(job,true) 中断job
例 exec dbms_job.broken(46,true); 46作业号 exec dbms_job.broken(2,true) BROKEN=Y
#######################################################################
dbms_job.broken(job,false,next_date) next_date:下次执行时间,如果不填则马上启动job
例 exec dbms_job.broken(46,false); 启动job exec dbms_job.broken(2,false); BROKEN=N
########################################################################
dbms_job.run(jobno); 运行作业
例子 execute dbms_job.run(1);
一. 物化视图概述
Oracle的物化视图是包括一个查询结果的数据库对像,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表。物化视图存储基于远程表的数据,也可以称为快照。
物化视图可以用于预先计算并保存表连接或聚集等耗时较多的操作的结果,这样,在执行查询时,就可以避免进行这些耗时的操作,而从快速的得到结果。物化视图有很多方面和索引很相似:使用物化视图的目的是为了提高查询性能;物化视图对应用透明,增加和删除物化视图不会影响应用程序中SQL语句的正确性和有效性;物化视图需要占用存储空间;当基表发生变化时,物化视图也应当刷新。
物化视图可以查询表,视图和其它的物化视图。
通常情况下,物化视图被称为主表(在复制期间)或明细表(在数据仓库中)。
对于复制,物化视图允许你在本地维护远程数据的副本,这些副本是只读的。如果你想修改本地副本,必须用高级复制的功能。当你想从一个表或视图中抽取数据时,你可以用从物化视图中抽取。
对于数据仓库,创建的物化视图通常情况下是聚合视图,单一表聚合视图和连接视图。
在复制环境下,创建的物化视图通常情况下主键,rowid,和子查询视图。
物化视图由于是物理真实存在的,故可以创建索引。
1.1 物化视图可以分为以下三种类型
(1) 包含聚集的物化视图;
(2) 只包含连接的物化视图;
(3) 嵌套物化视图。
三种物化视图的快速刷新的限制条件有很大区别,而对于其他方面则区别不大。创建物化视图时可以指定多种选项,下面对几种主要的选择进行简单说明:
(1)创建方式(BuildMethods):包括BUILD IMMEDIATE和BUILD DEFERRED两种。
BUILD IMMEDIATE是在创建物化视图的时候就生成数据。
BUILD DEFERRED则在创建时不生成数据,以后根据需要在生成数据。默认为BUILD IMMEDIATE。
(2)查询重写(QueryRewrite):包括ENABLE QUERY REWRITE和DISABLE QUERY REWRITE两种。
分别指出创建的物化视图是否支持查询重写。查询重写是指当对物化视图的基表进行查询时,Oracle会自动判断能否通过查询物化视图来得到结果,如果可以,则避免了聚集或连接操作,而直接从已经计算好的物化视图中读取数据。默认为DISABLEQUERY REWRITE。
(3)刷新(Refresh):指当基表发生了DML操作后,物化视图何时采用哪种方式和基表进行同步。刷新的模式有两种:ON DEMAND和ON COMMIT。
ON DEMAND和ON COMMIT物化视图的区别在于其刷新方法的不同,ON DEMAND指物化视图在用户需要的时候进行刷新,可以手工通过DBMS_MVIEW.REFRESH等方法来进行刷新,也可以通过JOB定时进行刷新,即更新物化视图,以保证和基表数据的一致性;而ON COMMIT是说,一旦基表有了COMMIT,即事务提交,则立刻刷新,立刻更新物化视图,使得数据和基表一致。
对基表,平常的COMMIT在0.01秒内可以完成,但在有了ON COMMIT视图后,居然要6秒。速度减低了很多倍。ON COMMIT视图对基表的影响可见一斑。
1.2 物化视图,根据不同的着重点可以有不同的分类:
1) 按刷新方式分:FAST/COMPLETE/FORCE
2) 按刷新时间的不同:ON DEMAND/ON COMMIT
3) 按是否可更新:UPDATABLE/READ ONLY
4) 按是否支持查询重写:ENABLE QUERY REWRITE/DISABLEQUERY REWRITE
默认情况下,如果没指定刷新方法和刷新模式,则Oracle默认为FORCE和DEMAND。
注意:设置REFRESH ON COMMIT的物化视图不能访问远端对象。
在建立物化视图的时候可以指定ORDER BY语句,使生成的数据按照一定的顺序进行保存。不过这个语句不会写入物化视图的定义中,而且对以后的刷新也无效。
1.3 物化视图有三种刷新方式:COMPLETE、FAST和 FORCE。
1) 完全刷新(COMPLETE)会删除表中所有的记录(如果是单表刷新,可能会采用TRUNCATE的方式),然后根据物化视图中查询语句的定义重新生成物化视图。
2) 快速刷新(FAST)采用增量刷新的机制,只将自上次刷新以后对基表进行的所有操作刷新到物化视图中去。FAST必须创建基于主表的视图日志。
对于增量刷新选项,如果在子查询中存在分析函数,则物化视图不起作用。
3) 采用FORCE方式,Oracle会自动判断是否满足快速刷新的条件,如果满足则进行快速刷新,否则进行完全刷新。
Oracle物化视图的快速刷新机制是通过物化视图日志完成的。Oracle通过一个物化视图日志还可以支持多个物化视图的快速刷新。
物化视图日志根据不同物化视图的快速刷新的需要,可以建立为ROWID或PRIMARY KEY类型的。还可以选择是否包括SEQUENCE、INCLUDING NEW VALUES以及指定列的列表。
1.4 物化视图Refresh子句的其他说明与示例
REFRESH 子句可以包含如下部分:
[refresh [fast|complete|force]
[on demand | commit]
[start with date] [next date]
[with {primary key|rowid}]]
1.4.1 主键和ROWD子句:
WITH PRIMARY KEY选项生成主键物化视图,也就是说物化视图是基于主表的主键,而不是ROWID(对应于ROWID子句). PRIMARY KEY是默认选项,为了生成PRIMARY KEY子句,应该在主表上定义主键,否则应该用基于ROWID的物化视图.
基于ROWID物化视图只有一个单一的主表,不能包括下面任何一项:
(1).Distinct 或者聚合函数.
(2) .Group by,子查询,连接和SET操作
--主键(PrimaryKey)物化视图示例:
在远程数据库表emp上创建主键物化视图:
- CREATEMATERIALIZEDVIEW mv_emp_pk
- REFRESHFASTSTARTWITHSYSDATE
- NEXT SYSDATE + 1/48
- WITHPRIMARYKEY
- ASSELECT * FROM emp@remote_db
CREATEMATERIALIZEDVIEW mv_emp_pk
REFRESHFASTSTARTWITHSYSDATE
NEXT SYSDATE + 1/48
WITHPRIMARYKEY
ASSELECT * FROM emp@remote_db
--当用FAST选项创建物化视图,必须创建基于主表的视图日志,如下:
- CREATEMATERIALIZEDVIEWLOGON emp;
CREATEMATERIALIZEDVIEWLOGON emp;
--Rowid物化视图示例:
下面的语法在远程数据库表emp上创建Rowid物化视图
- CREATEMATERIALIZEDVIEW mv_emp_rowid
- REFRESHWITHROWID
- ASSELECT * FROM emp@remote_db;
- Materializedviewlog created.
CREATEMATERIALIZEDVIEW mv_emp_rowid
REFRESHWITHROWID
ASSELECT * FROM emp@remote_db;
Materializedviewlog created.
--子查询物化视图示例:
在远程数据库表emp上创建基于emp和dept表的子查询物化视图
- CREATEMATERIALIZEDVIEW mv_empdept
- ASSELECT * FROM emp@remote_db e
- WHEREEXISTS
- (SELECT * FROM dept@remote_db d
- WHEREe.dept_no = d.dept_no)
CREATEMATERIALIZEDVIEW mv_empdept
ASSELECT * FROM emp@remote_db e
WHEREEXISTS
(SELECT * FROM dept@remote_db d
WHEREe.dept_no = d.dept_no)
1.4.2 刷新时间
START WITH子句通知数据库完成从主表到本地表第一次复制的时间,应该及时估计下一次运行的时间点, NEXT 子句说明了刷新的间隔时间.
- CREATEMATERIALIZEDVIEW mv_emp_pk
- REFRESHFAST
- STARTWITHSYSDATE
- NEXT SYSDATE + 2
- WITHPRIMARYKEY
- ASSELECT * FROM emp@remote_db;
CREATEMATERIALIZEDVIEW mv_emp_pk
REFRESHFAST
STARTWITHSYSDATE
NEXT SYSDATE + 2
WITHPRIMARYKEY
ASSELECT * FROM emp@remote_db;
在上面的例子中,物化视图数据的第一个副本在创建时生成,以后每两天刷新一次.
- creatematerializedviewMV_LVY_LEVYDETAILDATA
- TABLESPACE ZGMV_DATA
- BUILDDEFERRED
- refreshforce
- ondemand
- startwith to_date('24-11-200518:00:10', 'dd-mm-yyyyhh24:mi:ss')
- nextTRUNC(SYSDATE+1)+18/24
- as
- SELECT * FROM emp@remote_db;
creatematerializedviewMV_LVY_LEVYDETAILDATA
TABLESPACE ZGMV_DATA --保存表空间
BUILDDEFERRED--延迟刷新不立即刷新
refreshforce--如果可以快速刷新则进行快速刷新,否则完全刷新
ondemand--按照指定方式刷新
startwith to_date('24-11-200518:00:10', 'dd-mm-yyyyhh24:mi:ss') --第一次刷新时间
nextTRUNC(SYSDATE+1)+18/24--刷新时间间隔
as
SELECT * FROM emp@remote_db;
1.5 ON PREBUILD TABLE 说明
在创建物化视图时指明ON PREBUILD TABLE语句,可以将物化视图建立在一个已经存在的表上。这种情况下,物化视图和表必须同名。当删除物化视图时,不会删除同名的表。
这种物化视图的查询重写要求参数QUERY_REWRITE_INTEGERITY必须设置为trusted或者stale_tolerated。
1.6 物化视图分区
物化视图可以进行分区。而且基于分区的物化视图可以支持分区变化跟踪(PCT)。具有这种特性的物化视图,当基表进行了分区维护操作后,仍然可以进行快速刷新操作。对于聚集物化视图,可以在GROUP BY列表中使用CUBE或ROLLUP,来建立不同等级的聚集物化视图。
相关内容参考:
Oracle 物化视图日志 与 快速刷新 说明
http://blog.csdn.net/tianlesoftware/article/details/7720580
Oracle 物化视图 详细错误描述 查看方法
http://blog.csdn.net/tianlesoftware/article/details/7719789
Oracle 物化视图 快速刷新 限制 说明
http://blog.csdn.net/tianlesoftware/article/details/7719679
二. 物化视图操作示例
1. 创建物化视图需要的权限:
- GRANT CREATE MATERIALIZED VIEW TO USER_NAME;
GRANT CREATE MATERIALIZED VIEW TO USER_NAME;
2. 在源表建立物化视图日志
- CREATE MATERIALIZED VIEW LOG ON DAVE
- TABLESPACE&BISONCU_SPACE
- WITH PRIMARY KEY;
CREATE MATERIALIZED VIEW LOG ON DAVE
TABLESPACE&BISONCU_SPACE -- 日志空间
WITH PRIMARY KEY; -- 指定为主键类型
3. 授权给中间用户
- GRANT SELECT ON DAVE TO ANQING;
- GRANT SELECT ON MLOG$_DAVE TO ANQING;
GRANT SELECT ON DAVE TO ANQING;
GRANT SELECT ON MLOG$_DAVE TO ANQING;
4. 在目标数据库上创建MATERIALIZED VIEW
- CREATE MATERIALIZED VIEW AICS_DAVE
- TABLESPACE&BISONCS_SPACE
- REFRESH FAST
- ON DEMAND
-
-
- START WITH SYSDATE
-
-
- NEXT SYSDATE+1/24/20
- WITH PRIMARY KEY
-
- DISABLE QUERY REWRITE AS
- SELECT MODEL_ID, STATUS,MODEL_NAME, MANU_ID, DESCRIPTION, CREATE_TIME, UPDATE_TIME, SW_VERSION
- FROM AICS_DAVE@LINK_DAVE;
CREATE MATERIALIZED VIEW AICS_DAVE
TABLESPACE&BISONCS_SPACE
REFRESH FAST
ON DEMAND
--第一次刷新时间
--START WITH to_date('2012-01-01 20:00:00', 'yyyy-mm-dd hh24:mi:ss')
START WITH SYSDATE
--刷新时间间隔。每1天刷新一次,时间为凌晨2点
--NEXT TRUNC(SYSDATE,'dd')+1+2/24
NEXT SYSDATE+1/24/20
WITH PRIMARY KEY
--USING DEFAULT LOCAL ROLLBACKSEGMENT
DISABLE QUERY REWRITE AS
SELECT MODEL_ID, STATUS,MODEL_NAME, MANU_ID, DESCRIPTION, CREATE_TIME, UPDATE_TIME, SW_VERSION
FROM AICS_DAVE@LINK_DAVE;
5. 在目标物化视图上创建索引
- CREATE INDEX IDX_T_DV_CT
- ON AICS_DEV_INFO (CREATE_TIME, UPDATE_TIME)
- TABLESPACE &BISON_IDX;
-
- CREATE INDEX IDX_T_DV_UT
- ON AICS_DEV_INFO (UPDATE_TIME)
- TABLESPACE &BISON_IDX;
-
- CREATE INDEX I_T_DV_MSISDN
- ON AICS_DEV_INFO (MSISDN)
- TABLESPACE &BISON_IDX;
CREATE INDEX IDX_T_DV_CT
ON AICS_DEV_INFO (CREATE_TIME, UPDATE_TIME)
TABLESPACE &BISON_IDX;
CREATE INDEX IDX_T_DV_UT
ON AICS_DEV_INFO (UPDATE_TIME)
TABLESPACE &BISON_IDX;
CREATE INDEX I_T_DV_MSISDN
ON AICS_DEV_INFO (MSISDN)
TABLESPACE &BISON_IDX;
6. 物化视图刷新说明
(1)使用dbms_mview.refresh 手工刷新
如:
- EXEC DBMS_MVIEW.REFRESH('MV_DAVE');
-
-
- EXEC DBMS_MVIEW.REFRESH(LIST => 'MV_DAVE',METHOD => 'c');
- EXEC DBMS_MVIEW.REFRESH('MV_DAVE','C');
-
-
- EXEC DBMS_MVIEW.REFRESH(LIST => 'MV_DAVE',METHOD => 'f');
- EXEC DBMS_MVIEW.REFRESH('MV_DAVE','F');
EXEC DBMS_MVIEW.REFRESH('MV_DAVE');
--完全刷新
EXEC DBMS_MVIEW.REFRESH(LIST => 'MV_DAVE',METHOD => 'c');
EXEC DBMS_MVIEW.REFRESH('MV_DAVE','C');
--快速刷新
EXEC DBMS_MVIEW.REFRESH(LIST => 'MV_DAVE',METHOD => 'f');
EXEC DBMS_MVIEW.REFRESH('MV_DAVE','F');
(2)使用dbms_refresh.refresh 过程来批量刷新MV
如果我们在创建物化视图的过程指定start 和next time的刷新时间,那么Oracle 会自动创建刷新的job,并采用dbms_refresh.refresh 的方式。
使用这种方式刷新之前需要先make refresh group,然后才可以刷新。
Refreshmake 的语法可以参考:
http://docs.oracle.com/cd/B19306_01/server.102/b14227/rarrefreshpac.htm#i94057
示例:
假设存在物化视图MV_T1, MV_T2, MV_T3. 创建refresh group的语法如下:
- SQL> EXEC DBMS_REFRESH.MAKE('REP_TEST', 'MV_T1,MV_T2,MV_T3', SYSDATE, 'SYSDATE+ 1')
-
-
- SQL> EXEC DBMS_REFRESH.REFRESH('REP_TEST')
SQL> EXEC DBMS_REFRESH.MAKE('REP_TEST', 'MV_T1,MV_T2,MV_T3', SYSDATE, 'SYSDATE+ 1')
--刷新整个refresh group 组:
SQL> EXEC DBMS_REFRESH.REFRESH('REP_TEST')
7. 删除物化视图及日志
-
- DROP MATERIALIZED VIEW LOG ON DAVE;
-
- DROP MATERIALIZED VIEW MV_DAVE;
--删除物化视图日志:
DROP MATERIALIZED VIEW LOG ON DAVE;
--删除物化视图
DROP MATERIALIZED VIEW MV_DAVE;
8. 查看物化视图刷新状态信息
- SQL> SELECT MVIEW_NAME, LAST_REFRESH_DATE, STALENESS FROMUSER_MVIEWS;
- SQL> SELECT NAME, LAST_REFRESH FROM USER_MVIEW_REFRESH_TIMES;
SQL> SELECT MVIEW_NAME, LAST_REFRESH_DATE, STALENESS FROMUSER_MVIEWS;
SQL> SELECT NAME, LAST_REFRESH FROM USER_MVIEW_REFRESH_TIMES;
9. 查询物化视图日志:
- SELECT * FROM MLOG$_DAVE;
SELECT * FROM MLOG$_DAVE;
一、忘记除SYS、SYSTEM用户之外的用户的登录密码。
用SYS (或SYSTEM)用户登录: CONN SYS/PASS_WORD AS SYSDBA;
使用如下语句修改用户的密码: ALTER USER user_name IDENTIFIED BY "newpass";
注意:密码不能全是数字。并且不能是数字开头。否则会出现:ORA-00988: 口令缺失或无效
二、忘记SYS用户,或者是SYSTEM用户的密码。
如果是忘记SYSTEM用户的密码,可以用SYS用户登录。然后用ALTER USER 命令修改密码:
CONN SYS/PASS_WORD AS SYSDBA;
ALTER USER SYSTEM IDENTIFIED BY "newpass";
如果是忘记SYS用户的密码,可以用SYSTEM用户登录。然后用ALTER USER 命令修改密码。
CONN SYSTEM/PASS_WORD ;
ALTER USER SYSTEM IDENTIFIED BY "newpass";
三、如果SYS,SYSTEM用户的密码都忘记或是丢失。
可以使用ORAPWD.EXE 工具修改密码。
开始菜单->运行->输入‘CMD’,打开命令提示符窗口,输入如下命令:
orapwd file=D:\oracle10g\database\pwdctcsys.ora password=newpass
这个命令重新生成了数据库的密码文件。密码文件的位置在ORACLE_HOME目录下的\database目录下。
这个密码是修改sys用户的密码。除sys其他用户的密码不会改变。
不过Oracle提供了两种验证方式,一种是OS验证,另一种密码文件验证方式,如果是第一种方式用以下方法修改密码:
sqlplus /nolog;
connect / as sysdba
alter user sys identified by ;
alter user system identified by ;
如果是第二种方法就用上述方式修改,也可以下方法修改密码:
orapwd file=pwdxxx.ora password=你设定的新密码 entries=10
设定完后,重新启动服务,再次登陆就可以了。
oracle 11g
在本机安装完Oracle以后,不记得sys用户的密码了,采用如下方法可以修改密码:
1.打开cmd,输入sqlplus /nolog,回车;输入“conn / as sysdba”;输入“alter user sys identified by 新密码”,注意:新密码最好以字母开头,否则可能出现错误Ora-00988。有了这个方法后,只要自己对oracle服务器有管理员权限,不记得密码的时候就可以随意修改密码了。
2.在命令行执行如下命令:sqlplus “/@服务名 as sysdba”,然后在sqlplus中alter user sys identified by 新密码;
alter user system identified by 新密码;
3.运行到C盘根目录
1)输入:SET ORACLE_SID = 你的SID名称
2)输入:sqlplus/nolog
3)输入:connect/as sysdba
4)输入:alert user sys identified by sys
5)输入:alert user system identified by system
6)更改完成,密码是Oracle数据库初始密码
4.首先,在CMD下输入SQLPLUS/NOLOG然后再在出来的界面中打入CONN/AS SYSDBA,这样就会以本地系统登录的用户为信任用户进入数据库的操作.解决这个问题的方式有两种,一个是:ALTER USER (USERNAME) IDENTIFIED BY “密码”;这个是可以改变USERNAME的密码.当然这个USERNAME必须已经存在的
另一种是:CREATE USER (USERNAME) IDENTIFIED BY “密码”;改变用户权限的命令是:GRANT ROLES TO (USERNAME);以上2种方法都是针对ORACLE 9I的版本 。
5.用orapwd.exe命令,可以修改命令。
orapwd file=’/oracle/pwdsid.ora’ password=123456这个命令是修改sys用户的密码。你把生成的文件覆盖原来的密码文件。除sys其他用户的密码不会改变。
6.su - oracle
sqlplus /nolog
conn / as sysdba
startup (如果数据库不是处于启动状态则启动)
alter user sys identified by 123456
然后就可以使用sys用户密码登陆了
操作系统认证方式
conn / as sysdba
alter user sys identified by xxx
忘记密码处理
登录:sqlplus/as sysdba;
修改:alter user username identified by 密码;
创建新用户
create user 用户名 identified by 密码 default tablespace 表空间名
用户授权
Grant create session to SYSDATA;//授予SYSDATA连接数据的权限
系统权限如下:
Create session:连接数据库
Create sequence:创建序列
Create synonym:创建同名对象
Create table:创建表
Create any table:创建任何模式的表
Drop table:删除表
Create procedure:创建存储过程
Execute any procedure:执行任何模式的存储过程
Create user:创建用户
Create view:创建视图
Drop user:删除用户
Drop any table:删除任何模式的表
向用户授予系统特权
Grant execute any procedure toSYSDATA with admin option
此句意思为为sha用户创建系统特权并且可以用SYSDATA为别的用户授予权限
连接SYSDATA用户connect SYSDATA/admin
可以创建别的用户
查看用户权限
进入SYSDATA用户connect SYSDATA/admin
Select * from user_sys_privs;可以查看到一个列表,
列代表用户名权限是否可以给别的用户附加权限(N、Y)
行代表权限都有那些
撤销用户授予的权限
必须连接connect system/admin
撤销权限revoke是系统撤销权限的关键词
Revoke execute any procedure from SYSDATA;
更改用户密码
进入SYSDATA用户connect SYSDATA/admin
Password输入旧密码输入新密码俩边OK
或者用alter user SYSDATA identified by xinmima
删除用户
Connect system/admin
Drop user SYSDATA;
为用户授予角色
Grant dba to SYSDATA;
Grant connect to SYSDATA;
Linux下的plsql创建用户
create user SYSDATA identified by admin
Default tablespace user
Temporary tablespace temp;
Granr unlimited tablespace to SYSDATA;//权限
Grant dba to SYSDATA;//角色
Grant connect to SYSDATA;
select * from dba_users; //用户表
select * from dba_tablespaces; //表空间
http://database.51cto.com/art/200911/160296.htm
Oracle 权限设置
一、权限分类:
系统权限:系统规定用户使用数据库的权限。(系统权限是对用户而言)。
实体权限:某种权限用户对其它用户的表或视图的存取权限。(是针对表或视图而言的)。
二、系统权限管理:
1、系统权限分类:
DBA: 拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构。
RESOURCE:拥有Resource权限的用户只可以创建实体,不可以创建数据库结构。
CONNECT:拥有Connect权限的用户只可以登录Oracle,不可以创建实体,不可以创建数据库结构。
对于普通用户:授予connect, resource权限。
对于DBA管理用户:授予connect,resource, dba权限。
2、系统权限授权命令:
[系统权限只能由DBA用户授出:sys, system(最开始只能是这两个用户)]
授权命令:SQL> grant connect, resource, dba to 用户名1 [,用户名2]…;
[普通用户通过授权可以具有与system相同的用户权限,但永远不能达到与sys用户相同的权限,system用户的权限也可以被回收。]
例:
SQL> connect system/manager
SQL> Create user user50 identified by user50;
SQL> grant connect, resource to user50;
查询用户拥有哪里权限:
SQL> select * from dba_role_privs;
SQL> select * from dba_sys_privs;
SQL> select * from role_sys_privs;
删除用户:SQL> drop user 用户名 cascade; //加上cascade则将用户连同其创建的东西全部删除
3、系统权限传递:
增加WITH ADMIN OPTION选项,则得到的权限可以传递。
SQL> grant connect, resorce to user50 with admin option; //可以传递所获权限。
4、系统权限回收:系统权限只能由DBA用户回收
命令:SQL> Revoke connect, resource from user50;
说明:
1)如果使用WITH ADMIN OPTION为某个用户授予系统权限,那么对于被这个用户授予相同权限的所有用户来说,取消该用户的系统权限并不会级联取消这些用户的相同权限。
2)系统权限无级联,即A授予B权限,B授予C权限,如果A收回B的权限,C的权限不受影响;系统权限可以跨用户回收,即A可以直接收回C用户的权限。
三、实体权限管理
1、实体权限分类:select, update, insert, alter, index, delete, all //all包括所有权限
execute //执行存储过程权限
user01:
SQL> grant select, update, insert on product to user02;
SQL> grant all on product to user02;
user02:
SQL> select * from user01.product;
// 此时user02查user_tables,不包括user01.product这个表,但如果查all_tables则可以查到,因为他可以访问。
2. 将表的操作权限授予全体用户:
SQL> grant all on product to public; // public表示是所有的用户,这里的all权限不包括drop。
[实体权限数据字典]:
SQL> select owner, table_name from all_tables; // 用户可以查询的表
SQL> select table_name from user_tables; // 用户创建的表
SQL> select grantor, table_schema, table_name, privilege from all_tab_privs; // 获权可以存取的表(被授权的)
SQL> select grantee, owner, table_name, privilege from user_tab_privs; // 授出权限的表(授出的权限)
3. DBA用户可以操作全体用户的任意基表(无需授权,包括删除):
DBA用户:
SQL> Create table stud02.product(
id number(10),
name varchar2(20));
SQL> drop table stud02.emp;
SQL> create table stud02.employee
as
select * from scott.emp;
4. 实体权限传递(with grant option):
user01:
SQL> grant select, update on product to user02 with grant option; // user02得到权限,并可以传递。
5. 实体权限回收:
user01:
SQL>Revoke select, update on product from user02; //传递的权限将全部丢失。
说明
1)如果取消某个用户的对象权限,那么对于这个用户使用WITH GRANT OPTION授予权限的用户来说,同样还会取消这些用户的相同权限,也就是说取消授权时级联的。
Oracle 用户管理
一、创建用户的Profile文件
SQL> create profile student limit // student为资源文件名
FAILED_LOGIN_ATTEMPTS 3 //指定锁定用户的登录失败次数
PASSWORD_LOCK_TIME 5 //指定用户被锁定天数
PASSWORD_LIFE_TIME 30 //指定口令可用天数
二、创建用户
SQL> Create User username
Identified by password
Default Tablespace tablespace
Temporary Tablespace tablespace
Profile profile
Quota integer/unlimited on tablespace;
例:
SQL> Create user acc01
identified by acc01 // 如果密码是数字,请用双引号括起来
default tablespace account
temporary tablespace temp
profile default
quota 50m on account;
SQL> grant connect, resource to acc01;
[*] 查询用户缺省表空间、临时表空间
SQL> select username, default_tablespace, temporary_tablespace from dba_users;
[*] 查询系统资源文件名:
SQL> select * from dba_profiles;
资源文件类似表,一旦创建就会保存在数据库中。
SQL> select username, profile, default_tablespace, temporary_tablespace from dba_users;
SQL> create profile common limit
failed_login_attempts 5
idle_time 5;
SQL> Alter user acc01 profile common;
三、修改用户:
SQL> Alter User 用户名
Identified 口令
Default Tablespace tablespace
Temporary Tablespace tablespace
Profile profile
Quota integer/unlimited on tablespace;
1、修改口令字:
SQL>Alter user acc01 identified by “12345″;
2、修改用户缺省表空间:
SQL> Alter user acc01 default tablespace users;
3、修改用户临时表空间
SQL> Alter user acc01 temporary tablespace temp_data;
4、强制用户修改口令字:
SQL> Alter user acc01 password expire;
5、将用户加锁
SQL> Alter user acc01 account lock; // 加锁
SQL> Alter user acc01 account unlock; // 解锁
四、删除用户
SQL>drop user 用户名; //用户没有建任何实体
SQL> drop user 用户名 CASCADE; // 将用户及其所建实体全部删除
*1. 当前正连接的用户不得删除。
五、监视用户:
1、查询用户会话信息:
SQL> select username, sid, serial#, machine from v$session;
2、删除用户会话信息:
SQL> Alter system kill session ‘sid, serial#’;
3、查询用户SQL语句:
SQL> select user_name, sql_text from v$open_cursor;
Oracle 角色管理
一、何为角色
角色。角色是一组权限的集合,将角色赋给一个用户,这个用户就拥有了这个角色中的所有权限。
二、系统预定义角色
预定义角色是在数据库安装后,系统自动创建的一些常用的角色。下介简单的介绍一下这些预定角色。角色所包含的权限可以用以下语句查询:
sql>select * from role_sys_privs where role=’角色名’;
1.CONNECT, RESOURCE, DBA
这些预定义角色主要是为了向后兼容。其主要是用于数据库管理。oracle建议用户自己设计数据库管理和安全的权限规划,而不要简单的使用这些预定角色。将来的版本中这些角色可能不会作为预定义角色。
2.DELETE_CATALOG_ROLE, EXECUTE_CATALOG_ROLE, SELECT_CATALOG_ROLE
这些角色主要用于访问数据字典视图和包。
3.EXP_FULL_DATABASE, IMP_FULL_DATABASE
这两个角色用于数据导入导出工具的使用。
4.AQ_USER_ROLE, AQ_ADMINISTRATOR_ROLE
AQ:Advanced Query。这两个角色用于oracle高级查询功能。
5. SNMPAGENT
用于oracle enterprise manager和Intelligent Agent
6.RECOVERY_CATALOG_OWNER
用于创建拥有恢复库的用户。关于恢复库的信息,参考oracle文档《Oracle9i User-Managed Backup and Recovery Guide》
7.HS_ADMIN_ROLE
A DBA using Oracle’s heterogeneous services feature needs this role to access appropriate tables in the data dictionary.
三、管理角色
1.建一个角色
sql>create role role1;
2.授权给角色
sql>grant create any table,create procedure to role1;
3.授予角色给用户
sql>grant role1 to user1;
4.查看角色所包含的权限
sql>select * from role_sys_privs;
5.创建带有口令以角色(在生效带有口令的角色时必须提供口令)
sql>create role role1 identified by password1;
6.修改角色:是否需要口令
sql>alter role role1 not identified;
sql>alter role role1 identified by password1;
7.设置当前用户要生效的角色
(注:角色的生效是一个什么概念呢?假设用户a有b1,b2,b3三个角色,那么如果b1未生效,则b1所包含的权限对于a来讲是不拥有的,只有角色生效了,角色内的权限才作用于用户,最大可生效角色数由参数MAX_ENABLED_ROLES设定;在用户登录后,oracle将所有直接赋给用户的权限和用户默认角色中的权限赋给用户。)
sql>set role role1;//使role1生效
sql>set role role,role2;//使role1,role2生效
sql>set role role1 identified by password1;//使用带有口令的role1生效
sql>set role all;//使用该用户的所有角色生效
sql>set role none;//设置所有角色失效
sql>set role all except role1;//除role1外的该用户的所有其它角色生效。
sql>select * from SESSION_ROLES;//查看当前用户的生效的角色。
8.修改指定用户,设置其默认角色
sql>alter user user1 default role role1;
sql>alter user user1 default role all except role1;
详见oracle参考文档
9.删除角色
sql>drop role role1;
角色删除后,原来拥用该角色的用户就不再拥有该角色了,相应的权限也就没有了。
说明:
1)无法使用WITH GRANT OPTION为角色授予对象权限
2)可以使用WITH ADMIN OPTION 为角色授予系统权限,取消时不是级联
function uniteTab(tableId,col) {
//col-- 需要合并单元格的列 1开始
var tb=document.getElementById(tableId);
tb.style.display='';
var i = 0;
var j = 0;
var rowCount = tb.rows.length; // 行数
var colCount = tb.rows[0].cells.length; // 列数
var obj1 = null;
var obj2 = null;
//为每个单元格命名
for (i = 0; i < rowCount; i++) {
for (j = 0; j < colCount; j++) {
tb.rows[i].cells[j].id = "tb__" + i.toString() + "_" + j.toString();
}
}
//合并行
for (i = 0; i < colCount; i++) {
if (i == colLength) break;
obj1 = document.getElementById("tb__0_" + i.toString())
for (j = 1; j < rowCount; j++) {
obj2 = document.getElementById("tb__" + j.toString() + "_" + i.toString());
if (obj1.innerText == obj2.innerText) {
obj1.rowSpan++;
obj2.parentNode.removeChild(obj2);
} else {
obj1 = document.getElementById("tb__" + j.toString() + "_" + i.toString());
}
}
}
//合并列
for (i = 0; i < rowCount; i++) {
colCount = tb.rows[i].cells.length;
obj1 = document.getElementById(tb.rows[i].cells[0].id);
for (j = 1; j < colCount; j++) {
if (j >= colLength) break;
if (obj1.colSpan >= colLength) break;
obj2 = document.getElementById(tb.rows[i].cells[j].id);
if (obj1.innerText == obj2.innerText) {
obj1.colSpan++;
obj2.parentNode.removeChild(obj2);
j = j - 1;
}
else {
obj1 = obj2;
j = j + obj1.rowSpan;
}
}
}
}
1.3. 分区表索引的使用: 分区表和一般表一样可以建立索引,分区表可以创建局部索引和全局索引。当分区中出现许多事务并且要保证所有分区中的数据记录的唯一性时采用全局索引。
1.3.1. 局部索引分区的建立:
以下为引用的内容: SQL> create index dinya_idx_t on dinya_test(item_id) 2 local 3 ( 4 partition idx_1 tablespace dinya_space01, 5 partition idx_2 tablespace dinya_space02, 6 partition idx_3 tablespace dinya_space03 7 ); Index created. SQL> |
看查询的执行计划,从下面的执行计划可以看出,系统已经使用了索引:
以下为引用的内容: SQL> select * from dinya_test partition(part_01) t where t.item_id=12; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=187) 1 0 TABLE ACCESS (BY LOCAL INDEX ROWID) OF ’DINYA_TEST’ (Cost= 2 Card=1 Bytes=187) 2 1 INDEX (RANGE SCAN) OF ’DINYA_IDX_T’ (NON-UNIQUE) (Cost=1 Card=1) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 4 consistent gets 0 physical reads 0 redo size 334 bytes sent via SQL*Net to client 309 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client
1 sorts (memory) 0 sorts (disk) 2 rows processed SQL> |
1.3.2. 全局索引分区的建立
全局索引建立时global 子句允许指定索引的范围值,这个范围值为索引字段的范围值:
以下为引用的内容: SQL> create index dinya_idx_t on dinya_test(item_id) 2 global partition by range(item_id) 3 ( 4 partition idx_1 values less than (1000) tablespace dinya_space01, 5 partition idx_2 values less than (10000) tablespace dinya_space02, 6 partition idx_3 values less than (maxvalue) tablespace dinya_space03 7 ); Index created. SQL> |
本例中对表的item_id字段建立索引分区,当然也可以不指定索引分区名直接对整个表建立索引,如:
以下为引用的内容: SQL> create index dinya_idx_t on dinya_test(item_id); Index created. SQL> |
同样的,对全局索引根据执行计划可以看出索引已经可以使用:
以下为引用的内容: SQL> select * from dinya_test t where t.item_id=12; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=3 Bytes=561) 1 0 TABLE ACCESS (BY GLOBAL INDEX ROWID) OF ’DINYA_TEST’ (Cost =2 Card=3 Bytes=561) 2 1 INDEX (RANGE SCAN) OF ’DINYA_IDX_T’ (NON-UNIQUE) (Cost=1 Card=3) Statistics ---------------------------------------------------------- 5 recursive calls 0 db block gets 10 consistent gets 0 physical reads
0 redo size 420 bytes sent via SQL*Net to client 309 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 3 sorts (memory) 0 sorts (disk) 5 rows processed SQL> |
1.4. 分区表的维护:
了解了分区表的建立、索引的建立、表和索引的使用后,在应用的还要经常对分区进行维护和管理。日常维护和管理的内容包括:增加一个分区,合并一个分区及删除分区等等。下面以范围分区为例说明增加、合并、删除分区的一般操作:
1.4.1. 增加一个分区:
以下为引用的内容: SQL> alter table dinya_test 2 add partition part_04 values less than(to_date(’2012-01-01’,’yyyy-mm-dd’)) tablespace dinya_spa ce03; Table altered. SQL> |
增加一个分区的时候,增加的分区的条件必须大于现有分区的最大值,否则系统将提示ORA-14074 partition bound must collate higher than that of the last partition 错误。
1.4.2. 合并一个分区:
以下为引用的内容: SQL> alter table dinya_test merge partitions part_01,part_02 into partition part_02; Table altered. SQL> |
在本例中将原有的表的part_01分区和part_02分区进行了合并,合并后的分区为part_02,如果在合并的时候把合并后的分区定为part_01的时候,系统将提示ORA-14275 cannot reuse lower-bound partition as resulting partition 错误。
1.4.3. 删除分区:
以下为引用的内容: SQL> alter table dinya_test drop partition part_01; Table altered. SQL> |
删除分区表的一个分区后,查询该表的数据时显示,该分区中的数据已全部丢失,所以执行删除分区动作时要慎重,确保先备份数据后再执行,或将分区合并。
1.5. 总结: 需要说明的是,本文在举例说名分区表事务操作的时候,都指定了分区,因为指定了分区,系统在执行的时候则只操作该分区的记录,提高了数据处理的速度。不要指定分区直接操作数据也是可以的。在分区表上建索引及多索引的使用和非分区表一样。此外,因为在维护分区的时候可能对分区的索引会产生一定的影响,可能需要在维护之后重建索引,相关内容请参考分区表索引部分的文档
1.2. 分区表操作
以上了解了三种分区表的建表方法,下面将使用实际的数据并针对按日期的范围分区来测试分区表的数据记录的操作。
1.2.1. 插入记录:
以下为引用的内容: SQL> insert into dinya_test values(1,12,’BOOKS’,sysdate); 1 row created. SQL> insert into dinya_test values(2,12, ’BOOKS’,sysdate+30); 1 row created.
SQL> insert into dinya_test values(3,12, ’BOOKS’,to_date(’2006-05-30’,’yyyy-mm-dd’)); 1 row created. SQL> insert into dinya_test values(4,12, ’BOOKS’,to_date(’2007-06-23’,’yyyy-mm-dd’)); 1 row created. SQL> insert into dinya_test values(5,12, ’BOOKS’,to_date(’2011-02-26’,’yyyy-mm-dd’)); 1 row created. SQL> insert into dinya_test values(6,12, ’BOOKS’,to_date(’2011-04-30’,’yyyy-mm-dd’)); 1 row created. SQL> commit; Commit complete. SQL> |
按上面的建表结果,2006年前的数据将存储在第一个分区part_01上,而2006年到2010年的交易数据将存储在第二个分区part_02上,2010年以后的记录存储在第三个分区part_03上。
1.2.2. 查询分区表记录:
以下为引用的内容: SQL> select * from dinya_test partition(part_01); TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE -------------------------------------------------------------------------------- 1 12 BOOKS 2005-1-14 14:19: 2 12 BOOKS 2005-2-13 14:19: SQL> SQL> select * from dinya_test partition(part_02); TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE -------------------------------------------------------------------------------- 3 12 BOOKS 2006-5-30 4 12 BOOKS 2007-6-23 SQL> SQL> select * from dinya_test partition(part_03); TRANSACTION_ID ITEM_ID ITEM_DESCRIPTION TRANSACTION_DATE -------------------------------------------------------------------------------- 5 12 BOOKS 2011-2-26 6 12 BOOKS 2011-4-30 SQL> |
从查询的结果可以看出,插入的数据已经根据交易时间范围存储在不同的分区中。这里是指定了分区的查询,当然也可以不指定分区,直接执行select * from dinya_test查询全部记录。
在也检索的数据量很大的时候,指定分区会大大提高检索速度。
1.2.3. 更新分区表的记录:
以下为引用的内容: SQL> update dinya_test partition(part_01) t set t.item_description=’DESK’ where t.transaction_id=1; 1 row updated. SQL> commit; Commit complete. SQL> |
这里将第一个分区中的交易ID=1的记录中的item_description字段更新为“DESK”,可以看到已经成功更新了一条记录。但是当更新的时候指定了分区,而根据查询的记录不在该分区中时,将不会更新数据,请看下面的例子:
以下为引用的内容: SQL> update dinya_test partition(part_01) t set t.item_description=’DESK’ where t.transaction_id=6; 0 rows updated. SQL> commit; Commit complete. SQL> |
指定了在第一个分区中更新记录,但是条件中限制交易ID为6,而查询全表,交易ID为6的记录在第三个分区中,这样该条语句将不会更新记录。
1.2.4. 删除分区表记录:
以下为引用的内容: SQL> delete from dinya_test partition(part_02) t where t.transaction_id=4; 1 row deleted. SQL> commit; Commit complete. SQL> |
上面例子删除了第二个分区part_02中的交易记录ID为4的一条记录,和更新数据相同,如果指定了分区,而条件中的数据又不在该分区中时,将不会删除任何数据。
摘要:在大量业务数据处理的项目中,可以考虑使用分区表来提高应用系统的性能并方便数据管理,本文详细介绍了分区表的使用。
在大型的企业应用或企业级的数据库应用中,要处理的数据量通常可以达到几十到几百GB,有的甚至可以到TB级。虽然存储介质和数据处理技术的发展也很快,但是仍然不能满足用户的需求,为了使用户的大量的数据在读写操作和查询中速度更快,Oracle提供了对表和索引进行分区的技术,以改善大型应用系统的性能。
使用分区的优点:
·增强可用性:如果表的某个分区出现故障,表在其他分区的数据仍然可用;
·维护方便:如果表的某个分区出现故障,需要修复数据,只修复该分区即可;
·均衡I/O:可以把不同的分区映射到磁盘以平衡I/O,改善整个系统性能;
·改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索速度。
Oracle数据库提供对表或索引的分区方法有三种:
·范围分区
·Hash分区(散列分区)
·复合分区
下面将以实例的方式分别对这三种分区方法来说明分区表的使用。为了测试方便,我们先建三个表空间。
以下为引用的内容: create tablespace dinya_space01 datafile ’/test/demo/oracle/demodata/dinya01.dnf’ size 50M create tablespace dinya_space01 datafile ’/test/demo/oracle/demodata/dinya02.dnf’ size 50M create tablespace dinya_space01 datafile ’/test/demo/oracle/demodata/dinya03.dnf’ size 50M |
1.1. 分区表的创建 1.1.1. 范围分区
范围分区就是对数据表中的某个值的范围进行分区,根据某个值的范围,决定将该数据存储在哪个分区上。如根据序号分区,根据业务记录的创建日期进行分区等。
需求描述:有一个物料交易表,表名:material_transactions。该表将来可能有千万级的数据记录数。要求在建该表的时候使用分区表。这时候我们可以使用序号分区三个区,每个区中预计存储三千万的数据,也可以使用日期分区,如每五年的数据存储在一个分区上。
根据交易记录的序号分区建表:
以下为引用的内容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null, 5 item_description varchar2(300), 6 transaction_date date not null 7 ) 8 partition by range (transaction_id) 9 ( 10 partition part_01 values less than(30000000) tablespace dinya_space01, 11 partition part_02 values less than(60000000) tablespace dinya_space02, 12 partition part_03 values less than(maxvalue) tablespace dinya_space03 13 ); Table created. |
建表成功,根据交易的序号,交易ID在三千万以下的记录将存储在第一个表空间dinya_space01中,分区名为:par_01,在三千万到六千万之间的记录存储在第二个表空间:
dinya_space02中,分区名为:par_02,而交易ID在六千万以上的记录存储在第三个表空间dinya_space03中,分区名为par_03.
根据交易日期分区建表:
以下为引用的内容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null,
5 item_description varchar2(300), 6 transaction_date date not null 7 ) 8 partition by range (transaction_date) 9 ( 10 partition part_01 values less than(to_date(’2006-01-01’,’yyyy-mm-dd’)) tablespace dinya_space01, 11 partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’)) tablespace dinya_space02, 12 partition part_03 values less than(maxvalue) tablespace dinya_space03 13 ); Table created. |
这样我们就分别建了以交易序号和交易日期来分区的分区表。每次插入数据的时候,系统将根据指定的字段的值来自动将记录存储到制定的分区(表空间)中。
当然,我们还可以根据需求,使用两个字段的范围分布来分区,如partition by range ( transaction_id ,transaction_date), 分区条件中的值也做相应的改变,请读者自行测试。
1.1.2. Hash分区(散列分区)
散列分区为通过指定分区编号来均匀分布数据的一种分区类型,因为通过在I/O设备上进行散列分区,使得这些分区大小一致。如将物料交易表的数据根据交易ID散列地存放在指定的三个表空间中:
以下为引用的内容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null, 5 item_description varchar2(300), 6 transaction_date date 7 ) 8 partition by hash(transaction_id) 9 ( 10 partition part_01 tablespace dinya_space01, 11 partition part_02 tablespace dinya_space02, 12 partition part_03 tablespace dinya_space03 13 ); Table created. |
建表成功,此时插入数据,系统将按transaction_id将记录散列地插入三个分区中,这里也就是三个不同的表空间中。
1.1.3. 复合分区
有时候我们需要根据范围分区后,每个分区内的数据再散列地分布在几个表空间中,这样我们就要使用复合分区。复合分区是先使用范围分区,然后在每个分区内再使用散列分区的一种分区方法,如将物料交易的记录按时间分区,然后每个分区中的数据分三个子分区,将数据散列地存储在三个指定的表空间中:
以下为引用的内容: SQL> create table dinya_test 2 ( 3 transaction_id number primary key, 4 item_id number(8) not null, 5 item_description varchar2(300), 6 transaction_date date 7 ) 8 partition by range(transaction_date)subpartition by hash(transaction_id) 9 subpartitions 3 store in (dinya_space01,dinya_space02,dinya_space03) 10 ( 11 partition part_01 values less than(to_date(’2006-01-01’,’yyyy-mm-dd’)), 12 partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’)), 13 partition part_03 values less than(maxvalue) 14 ); Table created. |
该例中,先是根据交易日期进行范围分区,然后根据交易的ID将记录散列地存储在三个表空间中。
三种策略
Hibernate支持三种基本的继承映射策略:
-
每个类分层结构一张表(table per class hierarchy)
-
每个子类一张表(table per subclass)
-
每个具体类一张表(table per concrete class)
此外,Hibernate还支持第四种稍有不同的多态映射策略:
对于同一个继承层次内的不同分支,可以采用不同的映射策略,然后用隐式多 态来完成跨越整个层次的多态。但是在同一个<class>
根元素 下,Hibernate不支持混合了元素<subclass>
、 <joined-subclass>
和<union-subclass>
的映射。在同一个<class>
元素下,可以混合使用 “每个类分层结构一张表”(table per hierarchy) 和“每个子类一张表”(table per subclass) 这两种映射策略,这是通过结合元素<subclass>
和 <join>
来实现的(见后)。
在多个映射文件中,可以直接在hibernate-mapping
根下定义subclass
,union-subclass
和joined-subclass
。也就是说,你可以仅加入一个新的映射文件来扩展类层次。你必须在subclass的映射中指明extends
属性,给出一个之前定义的超类的名字。注意,在以前,这一功能对映射文件的顺序有严格的要求,从Hibernate 3开始,使用extends关键字的时侯,对映射文件的顺序不再有要求;但在每个映射文件里,超类必须在子类之前定义。
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>
9.1.1. 每个类分层结构一张表(Table per class hierarchy)
假设我们有接口Payment
和它的几个实现类: CreditCardPayment
, CashPayment
, 和ChequePayment
。则“每个类分层结构一张表”(Table per class hierarchy)的映射代码如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<property name="creditCardType" column="CCTYPE"/>
...
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
采用这种策略只需要一张表即可。它有一个很大的限制:要求那些由子类定义的字段, 如CCTYPE
,不能有非空(NOT NULL)
约束。
9.1.2. 每个子类一张表(Table per subclass)
对于上例中的几个类而言,采用“每个子类一张表”的映射策略,代码如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
...
</joined-subclass>
</class>
需要四张表。三个子类表通过主键关联到超类表(因而关系模型实际上是一对一关联)。
9.1.3. 每个子类一张表(Table per subclass),使用辨别标志(Discriminator)
注意,对“每个子类一张表”的映射策略,Hibernate的实现不需要辨别字段,而其他 的对象/关系映射工具使用了一种不同于Hibernate的实现方法,该方法要求在超类 表中有一个类型辨别字段(type discriminator column)。Hibernate采用的方法更 难实现,但从关系(数据库)的角度来看,按理说它更正确。若你愿意使用带有辨别字 段的“每个子类一张表”的策略,你可以结合使用<subclass>
与<join>
,如下所示:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
<join table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
<join table="CHEQUE_PAYMENT" fetch="select">
<key column="PAYMENT_ID"/>
...
</join>
</subclass>
</class>
可选的声明fetch="select"
,是用来告诉Hibernate,在查询超类时, 不要使用外部连接(outer join)来抓取子类ChequePayment
的数据。
9.1.4. 混合使用“每个类分层结构一张表”和“每个子类一张表”
你甚至可以采取如下方法混和使用“每个类分层结构一张表”和“每个子类一张表”这两种策略:
<class name="Payment" table="PAYMENT">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="PAYMENT_TYPE" type="string"/>
<property name="amount" column="AMOUNT"/>
...
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
<join table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</join>
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
对上述任何一种映射策略而言,指向根类Payment
的 关联是使用<many-to-one>
进行映射的。
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
9.1.5. 每个具体类一张表(Table per concrete class)
对于“每个具体类一张表”的映射策略,可以采用两种方法。第一种方法是使用 <union-subclass>
。
<class name="Payment">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="sequence"/>
</id>
<property name="amount" column="AMOUNT"/>
...
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<property name="creditCardType" column="CCTYPE"/>
...
</union-subclass>
<union-subclass name="CashPayment" table="CASH_PAYMENT">
...
</union-subclass>
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
...
</union-subclass>
</class>
这里涉及三张与子类相关的表。每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。
这种方式的局限在于,如果一个属性在超类中做了映射,其字段名必须与所有子类 表中定义的相同。(我们可能会在Hibernate的后续发布版本中放宽此限制。) 不允许在联合子类(union subclass)的继承层次中使用标识生成器策略(identity generator strategy), 实际上, 主键的种子(primary key seed)不得不为同一继承层次中的全部被联合子类所共用.
假若超类是抽象类,请使用abstract="true"
。当然,假若它不是抽象的,需要一个额外的表(上面的例子中,默认是PAYMENT
),来保存超类的实例。
9.1.6. Table per concrete class, using implicit polymorphism
9.1.6. Table per concrete class, using implicit polymorphism
另一种可供选择的方法是采用隐式多态:
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CREDIT_AMOUNT"/>
...
</class>
<class name="CashPayment" table="CASH_PAYMENT">
<id name="id" type="long" column="CASH_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CASH_AMOUNT"/>
...
</class>
<class name="ChequePayment" table="CHEQUE_PAYMENT">
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
<generator class="native"/>
</id>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</class>
注意,我们没有在任何地方明确的提及接口Payment
。同时注意 Payment
的属性在每个子类中都进行了映射。如果你想避免重复, 可以考虑使用XML实体(例如:位于DOCTYPE
声明内的 [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]
和映射中的&allproperties;
)。
这种方法的缺陷在于,在Hibernate执行多态查询时(polymorphic queries)无法生成带 UNION
的SQL语句。
对于这种映射策略而言,通常用<any>
来实现到 Payment
的多态关联映射。
<any name="payment" meta-type="string" id-type="long">
<meta-value value="CREDIT" class="CreditCardPayment"/>
<meta-value value="CASH" class="CashPayment"/>
<meta-value value="CHEQUE" class="ChequePayment"/>
<column name="PAYMENT_CLASS"/>
<column name="PAYMENT_ID"/>
</any>
对这一映射还有一点需要注意。因为每个子类都在各自独立的元素<class>
中映射(并且Payment
只是一个接口),每个子类可以很容易的成为另一 个继承体系中的一部分!(你仍然可以对接口Payment
使用多态查询。)
<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
<generator class="native"/>
</id>
<discriminator column="CREDIT_CARD" type="string"/>
<property name="amount" column="CREDIT_AMOUNT"/>
...
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
<subclass name="VisaPayment" discriminator-value="VISA"/>
</class>
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
<id name="id" type="long" column="TXN_ID">
<generator class="native"/>
</id>
...
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CASH_AMOUNT"/>
...
</joined-subclass>
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
<key column="PAYMENT_ID"/>
<property name="amount" column="CHEQUE_AMOUNT"/>
...
</joined-subclass>
</class>
我们还是没有明确的提到Payment
。 如果我们针对接口Payment
执行查询 ——如from Payment
—— Hibernate 自动返回CreditCardPayment
(和它的子类,因为 它们也实现了接口Payment
)、 CashPayment
和Chequepayment
的实例, 但不返回NonelectronicTransaction
的实例。
JVM优化配置《一》
OOM这个缩写就是Java程序开发过程中让人最头痛的问题:Out of Memory。在很多开发人员的开发过程中,或多或少的都会遇到这类问题,这类问题定位比较困难,往往需要根据经验来判断可能出现问题的代码。原因主要是 两个:对象没有被释放(多种情况引起,往往是比较隐蔽的引用导致被Hold而无法被回收)。另一种就是真的Memory不够用了,需要增加JVM的 Heap来满足应用程序的需求。最近有同事发的关于解决OOM的问题,让我了解了原来OOM除了在JVM Heap不够时会发生,在Native Heap不够的时候也会发生,同时JVM Heap和Native Heap存在着相互影响和平衡的关系,因此就仔细的去看了关于OOM和JVM配置优化的内容。
OOM
在 其他语言类似于C,Delphi等等由于内存都是由自己分配和管理,因此内存泄露的问题比较常见,同时也是很头痛的一件事情。而Java的对象生命周期管 理都是JVM来做的,简化了开发人员的非业务逻辑的处理,但是这种自动管理回收机制也是基于一些规则的,而违背了这些规则的时候,就会造成所谓的 “Memory Leak”。
OOM(Java Heap)
错误提示:java.lang.OutOfMemoryError。
这 类OOM是由于JVM分配的给应用的Heap Memory已经被耗尽,可能是因为应用在高负荷的情况下的却需要很大的内存,因此可以通过修改JVM参数来增加Java Heap Memory(不过也不能无限制增加,后面那种OOM有可能就是因为这个原因而产生)。另一种情况是因为应用程序使用对象或者资源没有释放,导致内存消耗 持续增加,最后出现OOM,这类问题引起的原因往往是应用已不需要的对象还被其他有效对象所引用,那么就无法释放,可能是业务代码逻辑造成的(异常处理不 够例如IO等资源),也可能是对于第三方开源项目中资源释放了解不够导致使用以后资源没有释放(例如JDBC的ResultSet等)。
几个容易出现问题的场景:
1.应用的缓存或者Collection:如果应用要缓存Java对象或者是在一个Collection中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证Cache的大小不会无限制增加。
2.生命周期较长的对象:尽量简短对象的生命周期,现在采用对象的创建释放代价已经很低,同时作了很好的优化,要比创建一个对象长期反复使用要好。如果能够设置超时的情景下,尽量设置超时。
3.类似于JDBC的Connection Pool,在使用Pool中的对象以后需要释放并返回,不然就会造成Pool的不断增大,在其他Pool中使用也是一样。同样ResultSet,IO这类资源的释放都需要注意。
解决的方法就是查找错误或者是增加Java Heap Memory。对于此类问题检测工具相当多,这里就不做介绍了。
OOM(Native Heap)
错误提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space。
Native Heap Memory是JVM 内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把 握最好不要使用,以防出现内存泄露问题。JVM 使用Native Heap Memory用来优化代码载入(JTI代码生成),临时对象空间申请,以及JVM内部的一些操作。这次同事在压力测试中遇到的问题就是这类OOM,也就是 这类Memory耗尽。同样这类OOM产生的问题也是分成正常使用耗尽和无释放资源耗尽两类。无释放资源耗尽很多时候不是程序员自身的原因,可能是引用的 第三方包的缺陷,例如很多人遇到的Oracle 9 JDBC驱动在低版本中有内存泄露的问题。要确定这类问题,就需要去观察Native Heap Memory的增长和使用情况,在服务器应用起来以后,运行一段时间后JVM对于Native Heap Memory的使用会达到一个稳定的阶段,此时可以看看什么操作对于Native Heap Memory操作频繁,而且使得Native Heap Memory增长,对于Native Heap Memory的情况我还没有找到办法去检测,现在能够看到的就是为JVM启动时候增加-verbose:jni参数来观察对于Native Heap Memory的操作。另一种情况就是正常消耗Native Heap Memory,对于Native Heap Memory的使用主要取决于JVM代码生成,线程创建,用于优化的临时代码和对象产生。当正常耗尽Native Heap Memory时,那么就需要增加Native Heap Memory,此时就会和我们前面提到增加java Heap Memory的情况出现矛盾。
应用内存组合
对 于应用来说,可分配的内存受到OS的限制,不同的OS对进程所能访问虚拟内存地址区间直接影响对于应用内存的分配,32位的操作系统通常最大支持4G的内 存寻址,而Linux一般为3G,Windows为2G。然而这些大小的内存并不会全部给JVM的Java Heap使用,它主要会分成三部分:Java Heap,Native Heap,载入资源和类库等所占用的内存。那么由此可见,Native Heap和 Java Heap大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作,因此如果通过命令行去配置,那么需要确切的了解应用使用情况,否则 采用默认配置自动监测会更好的优化应用使用情况。
同样要注意的就是进程的虚拟内存和机器的实际内存还是有区别的,对于机器来说实际内存以及硬盘提供的虚拟内存都是提供给机器上所有进程使用的,因此在设置JVM参数时,它的虚拟内存绝对不应该超过实际内存的大小。
《二》
这 里首先要说明的是这里提到的JVM是Sun的HotSpot JVM 5和以上的版本。性能优化在应用方面可以有很多手段,包括Cache,多线程,各种算法等等。通常情况下是不建议在没有任何统计和分析的情况下去手动配置 JVM的参数来调整性能,因为在JVM 5以上已经作了根据机器和OS的情况自动配置合适参数的算法,基本能够满足大部分的情况,当然这种自动适配只是一种通用的方式,如果说真的要达到最优,那 么还是需要根据实际的使用情况来手动的配置各种参数设置,提高性能。
JVM能够对性能产生影响的最大部分就是对于内存的管理。从jdk 1.5以后内存管理和分配有了很多的改善和提高。
内存分配以及管理的几个基本概念和参数说明:
Java Hotspot Mode:
server 和 client两种模式,如果不配置,JVM会根据应用服务器硬件配置自动选择模式,server模式启动比较慢,但是运行期速度得到了优化,client启动比较快,但是运行期响应没有server模式的优化,适合于个人PC的服务开发和测试。
Garbage Collector Policy:
在Jdk 1.5的时候已经提供了三种GC,除了原来提供的串行GC(SerialGC)以外,还提供了两种新的GC:ParallelGC和 ConcMarkSweepGC。ParallelGC采用了多线程并行管理和回收垃圾对象,提高了回收效率,提高了服务器的吞吐量,适合于多处理器的服 务器。ConcMarkSweepGC采用的是并发方式来管理和回收垃圾对象,降低垃圾回收产生的响应暂停时间。这里说一下并发和并行的区别,并发指的是 多个进程并行执行垃圾回收,那么可以很好的利用多处理器,而并行指的是应用程序不需要暂停可以和垃圾回收线程并发工作。串行GC适合小型应用和单处理器系 统(无需多线程交互,效率比较高),后两者适合大型系统。
使用方式就是在参数配置中增加-XX:+UseParallelGC等方式来设置。
对于这部分的配置在网上有很多的实例可以参考,不过最终采用哪一种GC还是要根据具体的情况来分析和选择。
Heap:
OOM的 各种经历已经让每一个架构师开发人员看到了了解Heap的重要性。OOM已经是Heap的临界点,不得不引起注意,然而Heap对于性能的潜在影响并未被 引起重视,不过和GC配置一样,在没有对使用情况作仔细分析和研究的情况下,贸然的去修改Heap配置,可能适得其反,这里就来看一下Heap的一些概念 和对于性能的影响。
我们的应用所能够得到的最大的Heap受三部分因素的制约:数据处理 模型(32位或者64位操作系统),系统地虚拟内存总数和系统的物理内存总数。首先Heap的大小不能超过不同操作系统的进程寻址范围,当前大部分系统最 高限度是4G,Windows通常是2G,Linux通常是3G。系统的虚拟内存也是分配的依据,首先是不能超过,然后由于操作系统支持硬盘来做部分的虚 拟内存,如果设置过大,那么对于应用响应来说势必有影响。再则就是要考虑同一台服务器上运行多个Java虚拟机所消耗的资源总合也不能超过可用资源。就和 前面OOM分析中的一样,其实由于OS的数据处理模型的限制,机器本身的硬件内存资源和虚拟内存资源并不一定会匹配,那么在有限的资源下如何调整好资源分 配,对于应用来说尤为重要。
关于Heap的几个参数设置:
说了Heap的有限资源问题以后,就来看看如何通过配置去改变JVM对于Heap的分配。下面所说的主要是对于Java Heap的分配,那么在申请了Java Heap以后,剩下的可用资源就会被使用到Native Heap。
Xms: java heap初始化时的大小。默认情况是机器物理内存的1/64。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低启动速度,分配多了也浪费。
Xmx:java heap的 最大值,默认是机器物理内存的1/4,最大也就到1G。这个值决定了最多可用的Java Heap Memory,分配过少就会在应用需要大量内存作缓存或者零时对象时出现OOM的问题,如果分配过大,那么就会产生上文提到的第二类OOM。所以如何配置 还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。
Xmn:java heap新 生代的空间大小。在GC模型中,根据对象的生命周期的长短,产生了内存分代的设计:青年代(内部也分成三部分,类似于整体划分的作用,可以通过配置来设置 比例),老年代,持久代。每一代的管理和回收策略都不相同,最为活跃的就是青年代,同时这部分的内存分配和管理效率也是最高。通常情况下,对于内存的申请 优先在新生代中申请,当内存不够时会整理新生代,当整理以后还是不能满足申请的内存,就会向老年代移动一些生命周期较长的对象。这种整理和移动会消耗资 源,同时降低系统运行响应能力,因此如果青年代设置的过小,就会频繁的整理和移动,对性能造成影响。那是否把年青代设置的越大越好,其实不然,年青代采用 的是复制搜集算法,这种算法必须停止所有应用程序线程,服务器线程切换时间就会成为应用响应的瓶颈(当然永远不用收集那么就不存在这个问题)。老年代采用 的是串行标记收集的方式,并发收集可以减少对于应用的影响。
Xss:线程堆栈最大值。允许更多的虚拟内存空间地址被Java Heap使用。
以下是sun公司的性能优化白皮书中提到的几个例子:
1.对于吞吐量的调优。机器配置:4G的内存,32个线程并发能力。
java
-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-Xmx3800m -Xms3800m
配置了最大Java Heap来充分利用系统内存。
-Xmn2g
创建足够大的青年代(可以并行被回收)充分利用系统内存,防止将短期对象复制到老年代。
-Xss128
减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。
-XX:+UseParallelGC
采用并行垃圾收集器对年青代的内存进行收集,提高效率。
-XX:ParallelGCThreads=20
减少垃圾收集线程,默认是和服务器可支持的线程最大并发数相同,往往不需要配置到最大值。
2
.尝试采用对老年代并行收集
java
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-Xmx3550m -Xms3550m
内存分配被减小,因为ParallelOldGC会增加对于Native Heap的需求,因此需要减小Java Heap来满足需求。
-XX:+UseParallelOldGC
采用对于老年代并发收集的策略,可以提高收集效率。
3
.提高吞吐量,减少应用停顿时间
java
-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
选择了并发标记交换收集器,它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31
表示在青年代中Eden和Survivor比例,设置增加了Survivor的大小,越大的survivor空间可以允许短期对象尽量在年青代消亡。
-XX:TargetSurvivorRatio=90
允许90%的空间被占用,超过默认的50%,提高对于survivor的使用率。
类似的例子网上很多,这儿就不在列下来了,最终是否采取自己配置来替换默认配置还是要根据虚拟机的使用情况来分析和配置。
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。
具体如下图:
根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:
第一种方式:每个Bean都有一个代理
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定义事务管理器(声明式的事务) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<!– 配置DAO –>
<bean id=”userDaoTarget” class=”com.bluesky.spring.dao.UserDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”userDao”
class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”>
<!– 配置事务管理器 –>
<property name=”transactionManager” ref=”transactionManager” />
<property name=”target” ref=”userDaoTarget” />
<property name=”proxyInterfaces” value=”com.bluesky.spring.dao.GeneratorDao” />
<!– 配置事务属性 –>
<property name=”transactionAttributes”>
<props>
<prop key=”*”>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
第二种方式:所有Bean共享一个代理基类
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定义事务管理器(声明式的事务) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”transactionBase”
class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”
lazy-init=”true” abstract=”true”>
<!– 配置事务管理器 –>
<property name=”transactionManager” ref=”transactionManager” />
<!– 配置事务属性 –>
<property name=”transactionAttributes”>
<props>
<prop key=”*”>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!– 配置DAO –>
<bean id=”userDaoTarget” class=”com.bluesky.spring.dao.UserDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”userDao” parent=”transactionBase” >
<property name=”target” ref=”userDaoTarget” />
</bean>
</beans>
第三种方式:使用拦截器
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定义事务管理器(声明式的事务) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<bean id=”transactionInterceptor”
class=”org.springframework.transaction.interceptor.TransactionInterceptor”>
<property name=”transactionManager” ref=”transactionManager” />
<!– 配置事务属性 –>
<property name=”transactionAttributes”>
<props>
<prop key=”*”>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class=”org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator”>
<property name=”beanNames”>
<list>
<value>*Dao</value>
</list>
</property>
<property name=”interceptorNames”>
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!– 配置DAO –>
<bean id=”userDao” class=”com.bluesky.spring.dao.UserDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
</beans>
第四种方式:使用tx标签配置的拦截器
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd”>
<context:annotation-config />
<context:component-scan base-package=”com.bluesky” />
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定义事务管理器(声明式的事务) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
<tx:advice id=”txAdvice” transaction-manager=”transactionManager”>
<tx:attributes>
<tx:method name=”*” propagation=”REQUIRED” />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id=”interceptorPointCuts”
expression=”execution(* com.bluesky.spring.dao.*.*(..))” />
<aop:advisor advice-ref=”txAdvice”
pointcut-ref=”interceptorPointCuts” />
</aop:config>
</beans>
第五种方式:全注解
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:context=”http://www.springframework.org/schema/context”
xmlns:aop=”http://www.springframework.org/schema/aop”
xmlns:tx=”http://www.springframework.org/schema/tx”
xsi:schemaLocation=”http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd”>
<context:annotation-config />
<context:component-scan base-package=”com.bluesky” />
<tx:annotation-driven transaction-manager=”transactionManager”/>
<bean id=”sessionFactory”
class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”>
<property name=”configLocation” value=”classpath:hibernate.cfg.xml” />
<property name=”configurationClass” value=”org.hibernate.cfg.AnnotationConfiguration” />
</bean>
<!– 定义事务管理器(声明式的事务) –>
<bean id=”transactionManager”
class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory” />
</bean>
</beans>
此时在DAO上需加上@Transactional注解,如下:
package com.bluesky.spring.dao;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;
import com.bluesky.spring.domain.User;
@Transactional
@Component(“userDao”)
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public List<User> listUsers() {
return this.getSession().createQuery(“from User”).list();
}
}
集群概念
1. 两大关键特性
集群是一组协同工作的服务实体,用以提供比单一服务实体更具扩展性与可用性的服务平台。在客户端看来,一个集群就象是一个服务实体,但事实上集群由一组服务实体组成。与单一服务实体相比较,集群提供了以下两个关键特性:
· 可扩展性--集群的性能不限于单一的服务实体,新的服务实体可以动态地加入到集群,从而增强集群的性能。
· 高可用性--集群通过服务实体冗余使客户端免于轻易遇到out of service的警告。在集群中,同样的服务可以由多个服务实体提供。如果一个服务实体失败了,另一个服务实体会接管失败的服务实体。集群提供的从一个出错的服务实体恢复到另一个服务实体的功能增强了应用的可用性。
2. 两大能力
为了具有可扩展性和高可用性特点,集群的必须具备以下两大能力:
· 负载均衡--负载均衡能把任务比较均衡地分布到集群环境下的计算和网络资源。
· 错误恢复--由于某种原因,执行某个任务的资源出现故障,另一服务实体中执行同一任务的资源接着完成任务。这种由于一个实体中的资源不能工作,另一个实体中的资源透明的继续完成任务的过程叫错误恢复。
负载均衡和错误恢复都要求各服务实体中有执行同一任务的资源存在,而且对于同一任务的各个资源来说,执行任务所需的信息视图(信息上下文)必须是一样的。
3. 两大技术
实现集群务必要有以下两大技术:
· 集群地址--集群由多个服务实体组成,集群客户端通过访问集群的集群地址获取集群内部各服务实体的功能。具有单一集群地址(也叫单一影像)是集群的一个基本特征。维护集群地址的设置被称为负载均衡器。负载均衡器内部负责管理各个服务实体的加入和退出,外部负责集群地址向内部服务实体地址的转换。有的负载均衡器实现真正的负载均衡算法,有的只支持任务的转换。只实现任务转换的负载均衡器适用于支持ACTIVE-STANDBY的集群环境,在那里,集群中只有一个服务实体工作,当正在工作的服务实体发生故障时,负载均衡器把后来的任务转向另外一个服务实体。
· 内部通信--为了能协同工作、实现负载均衡和错误恢复,集群各实体间必须时常通信,比如负载均衡器对服务实体心跳测试信息、服务实体间任务执行上下文信息的通信。
具有同一个集群地址使得客户端能访问集群提供的计算服务,一个集群地址下隐藏了各个服务实体的内部地址,使得客户要求的计算服务能在各个服务实体之间分布。内部通信是集群能正常运转的基础,它使得集群具有均衡负载和错误恢复的能力。
集群分类
Linux集群主要分成三大类( 高可用集群, 负载均衡集群,科学计算集群)
高可用集群( High Availability Cluster)
负载均衡集群(Load Balance Cluster)
科学计算集群(High Performance Computing Cluster)
================================================
具体包括:
Linux High Availability 高可用集群
(普通两节点双机热备,多节点HA集群,RAC, shared, share-nothing集群等)
Linux Load Balance 负载均衡集群
(LVS等....)
Linux High Performance Computing 高性能科学计算集群
(Beowulf 类集群....)
分布式存储
其他类linux集群
(如Openmosix, rendering farm 等..)
详细介绍
1. 高可用集群(High Availability Cluster)
常见的就是2个节点做成的HA集群,有很多通俗的不科学的名称,比如"双机热备", "双机互备", "双机".
高可用集群解决的是保障用户的应用程序持续对外提供服务的能力。 (请注意高可用集群既不是用来保护业务数据的,保护的是用户的业务程序对外不间断提供服务,把因软件/硬件/人为造成的故障对业务的影响降低到最小程度)。
2. 负载均衡集群(Load Balance Cluster)
负载均衡系统:集群中所有的节点都处于活动状态,它们分摊系统的工作负载。一般Web服务器集群、数据库集群和应用服务器集群都属于这种类型。
负载均衡集群一般用于相应网络请求的网页服务器,数据库服务器。这种集群可以在接到请求时,检查接受请求较少,不繁忙的服务器,并把请求转到这些服务器上。从检查其他服务器状态这一点上看,负载均衡和容错集群很接近,不同之处是数量上更多。
3. 科学计算集群(High Performance Computing Cluster)
高性能计算(High Perfermance Computing)集群,简称HPC集群。这类集群致力于提供单个计算机所不能提供的强大的计算能力。
高性能计算分类
高吞吐计算(High-throughput Computing)
有一类高性能计算,可以把它分成若干可以并行的子任务,而且各个子任务彼此间没有什么关联。象在家搜寻外星人( SETI@HOME -- Search for Extraterrestrial Intelligence at Home )就是这一类型应用。这一项目是利用Internet上的闲置的计算资源来搜寻外星人。SETI项目的服务器将一组数据和数据模式发给Internet上参加SETI的计算节点,计算节点在给定的数据上用给定的模式进行搜索,然后将搜索的结果发给服务器。服务器负责将从各个计算节点返回的数据汇集成完整的数据。因为这种类型应用的一个共同特征是在海量数据上搜索某些模式,所以把这类计算称为高吞吐计算。所谓的Internet计算都属于这一类。按照Flynn的分类,高吞吐计算属于SIMD(Single Instruction/Multiple Data)的范畴。
分布计算(Distributed Computing)
另一类计算刚好和高吞吐计算相反,它们虽然可以给分成若干并行的子任务,但是子任务间联系很紧密,需要大量的数据交换。按照Flynn的分类,分布式的高性能计算属于MIMD(Multiple Instruction/Multiple Data)的范畴。
4. 分布式(集群)与集群的联系与区别
分布式是指将不同的业务分布在不同的地方。
而集群指的是将几台服务器集中在一起,实现同一业务。
分布式中的每一个节点,都可以做集群。
而集群并不一定就是分布式的。
举例:就比如新浪网,访问的人多了,他可以做一个群集,前面放一个响应服务器,后面几台服务器完成同一业务,如果有业务访问的时候,响应服务器看哪台服务器的负载不是很重,就将给哪一台去完成。
而分布式,从窄意上理解,也跟集群差不多, 但是它的组织比较松散,不像集群,有一个组织性,一台服务器垮了,其它的服务器可以顶上来。
分布式的每一个节点,都完成不同的业务,一个节点垮了,哪这个业务就不可访问了。
设计模式遵循的一般原则:
1.开-闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。
2.里氏代换原则(Liskov Substitution Principle,常缩写为.LSP)
(1).由Barbar Liskov(芭芭拉.里氏)提出,是继承复用的基石。
(2).严格表达:如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换称o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型.
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别.只有衍生类可以替换基类,软件单位的功能才能不受影响,基类才能真正被复用,而衍生类也能够在基类的基础上增加新功能。
(3).反过来的代换不成立
(4).<墨子.小取>中说:"白马,马也; 乘白马,乘马也.骊马(黑马),马也;乘骊马,乘马也."
(5).该类西方著名的例程为:正方形是否是长方形的子类(答案是"否")。类似的还有椭圆和圆的关系。
(6).应当尽量从抽象类继承,而不从具体类继承,一般而言,如果有两个具体类A,B有继承关系,那么一个最简单的修改方案是建立一个抽象类C,然后让类A和B成为抽象类C的子类.即如果有一个由继承关系形成的登记结构的话,那么在等级结构的树形图上面所有的树叶节点都应当是具体类;而所有的树枝节点都应当是抽象类或者接口.
(7)."基于契约设计(Design By Constract),简称DBC"这项技术对LISKOV代换原则提供了支持.该项技术Bertrand Meyer伯特兰做过详细的介绍:
使用DBC,类的编写者显式地规定针对该类的契约.客户代码的编写者可以通过该契约获悉可以依赖的行为方式.契约是通过每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的.要使一个方法得以执行,前置条件必须为真.执行完毕后,该方法要保证后置条件为真.就是说,在重新声明派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件.
3.依赖倒置原则(Dependence Inversion Principle),要求客户端依赖于抽象耦合.
(1)表述:抽象不应当依赖于细节,细节应当依赖于抽象.(Program to an interface, not an implementaction)
(2)表述二:针对接口编程的意思是说,应当使用接口和抽象类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等.不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明,参量类型声明,方法的返还类型声明,以及数据类型的转换等.
要保证做到这一点,一个具体的类应等只实现接口和抽象类中声明过的方法,而不应当给出多余的方法.
只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括参量的类型声明,方法返还类型的声明,属性变量的类型声明等.
(3)接口与抽象的区别就在于抽象类可以提供某些方法的部分实现,而接口则不可以,这也大概是抽象类唯一的优点.如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就都得到得到了这个新的具体方法,而接口做不到这一点.如果向一个接口加入了一个新的方法的话,所有实现这个接口的类就全部不能通过编译了,因为它们都没有实现这个新声明的方法.这显然是接口的一个缺点.
(4)一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的登记结构中,而由于一般语言都限制一个类只能从最多一个超类继承,因此将抽象作为类型定义工具的效能大打折扣.
反过来,看接口,就会发现任何一个实现了一个接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个接口.
(5)从代码重构的角度上讲,将一个单独的具体类重构成一个接口的实现是很容易的,只需要声明一个接口,并将重要的方法添加到接口声明中,然后在具体类定义语句中加上保留字以继承于该接口就行了.
而作为一个已有的具体类添加一个抽象类作为抽象类型不那么容易,因为这个具体类有可能已经有一个超类.这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新的抽象类必定处于整个类型等级结构的最上端,从而使登记结构中的所有成员都会受到影响.
(6)接口是定义混合类型的理想工具,所为混合类型,就是在一个类的主类型之外的次要类型.一个混合类型表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为.
(7)联合使用接口和抽象类:
由于抽象类具有提供缺省实现的优点,而接口具有其他所有优点,所以联合使用两者就是一个很好的选择.
首先,声明类型的工作仍然接口承担的,但是同时给出的还有一个抽象类,为这个接口给出一个缺省实现.其他同属于这个抽象类型的具体类可以选择实现这个接口,也可以选择继承自这个抽象类.如果一个具体类直接实现这个接口的话,它就必须自行实现所有的接口;相反,如果它继承自抽象类的话,它可以省去一些不必要的的方法,因为它可以从抽象类中自动得到这些方法的缺省实现;如果需要向接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法.这其实就是缺省适配器模式(Defaule Adapter).
(8)什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节的改变而改变的真理. 它是系统内部的系统____隐喻.
4.接口隔离原则(Interface Segregation Principle, ISP)
(1)一个类对另外一个类的依赖是建立在最小的接口上。
(2)使用多个专门的接口比使用单一的总接口要好.根据客户需要的不同,而为不同的客户端提供不同的服务是一种应当得到鼓励的做法.就像"看人下菜碟"一样,要看客人是谁,再提供不同档次的饭菜.
(3)胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系.当一个客户程序要求该胖接口进行一个改动时,会影响到所有其他的客户程序.因此客户程序应该仅仅依赖他们实际需要调用的方法.
5.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.
6.迪米特法则(Law of Demeter LoD)又叫做最少知识原则(Least Knowledge Principle,LKP),就是说,一个对象应当对其他对象有尽可能少的了了解.
迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则[LIEB89][LIEB86].这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.
没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:
(1)只与你直接的朋友们通信(Only talk to your immediate friends)
(2)不要跟"陌生人"说话(Don't talk to strangers)
(3)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.
就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
7.单一职责原则(Simple responsibility pinciple SRP)
就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.
另外:常说的OO五大原则就是指其中的 :1、单一职责原则;2、开放闭合原则;3、里氏替换原则;4、依赖倒置原则;5、接口隔离原则。
Mysql分页采用limt关键字
select * from t_order limit 5,10; #返回第6-15行数据
select * from t_order limit 5; #返回前5行
select * from t_order limit 0,5; #返回前5行
Mssql 2000分页采用top关键字(20005以上版本也支持关键字rownum)
Select top 10 * from t_order where id not in (select id from t_order where id>5 ); //返回第6到15行数据
其中10表示取10记录 5表示从第5条记录开始取
Oracle分页
①采用rownum关键字(三层嵌套)
SELECT * FROM(
SELECT A.*,ROWNUM num FROM
(SELECT * FROM t_order)A
WHERE
ROWNUM<=15)
WHERE num>=5;--返回第5-15行数据
②采用row_number解析函数进行分页(效率更高)
SELECT xx.* FROM(
SELECT t.*,row_number() over(ORDER BY o_id)AS num
FROM t_order t
)xx
WHERE num BETWEEN 5 AND 15;
--返回第5-15行数据
解析函数能用格式
函数() over(pertion by 字段 order by 字段);
Pertion 按照某个字段分区
Order 按照勒个字段排序
Struts1和Struts2的区别和对比:
Action 类:
• Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口。
• Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。
线程模式:
• Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
• Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
Servlet 依赖:
• Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
• Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。
可测性:
• 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
• Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。
捕获输入:
• Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经 常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存 在的JavaBean(仍然会导致有冗余的javabean)。
• Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。
表达式语言:
• Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
• Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"Object Graph Notation Language" (OGNL).
绑定值到页面(view):
• Struts 1使用标准JSP机制把对象绑定到页面中来访问。
• Struts 2 使用 "ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。
类型转换:
• Struts 1 ActionForm 属性通常都是String类型。Struts1使用Commons-Beanutils进行类型转换。每个类一个转换器,对每一个实例来说是不可配置的。
• Struts2 使用OGNL进行类型转换。提供基本和常用对象的转换器。
校验:
• Struts 1支持在ActionForm的validate方法中手动校验,或者通过Commons Validator的扩展来校验。同一个类可以有不同的校验内容,但不能校验子对象。
• Struts2支持通过validate方法和XWork校验框架来进行校验。XWork校验框架使用为属性类类型定义的校验和内容校验,来支持chain校验子属性
Action执行的控制:
• Struts1支持每一个模块有单独的Request Processors(生命周期),但是模块中的所有Action必须共享相同的生命周期。
• Struts2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。堆栈能够根据需要和不同的Action一起使用。
摘要: 我们看看Spring中的事务处理的代码,使用Spring管理事务有声明式和编程式两种方式,声明式事务处理通过AOP的实现把事物管理代码作为方面封装来横向插入到业务代码中,使得事务管理代码和业务代码解藕。在这种方式我们结合IoC容器和Spirng已有的FactoryBean来对事务管理进行属性配置,比如传播行为,隔离级别等。其中最简单的方式就是通过配置TransactionProxyFactoryB...
阅读全文
前面我们分析了Spring AOP实现中得到Proxy对象的过程,下面我们看看在Spring AOP中拦截器链是怎样被调用的,也就是Proxy模式是怎样起作用的,或者说Spring是怎样为我们提供AOP功能的;
在JdkDynamicAopProxy中生成Proxy对象的时候:
- return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
这里的this参数对应的是InvocationHandler对象,这里我们的JdkDynamicAopProxy实现了这个接口,也就是说当Proxy对象的函数被调用的时候,这个InvocationHandler的invoke方法会被作为回调函数调用,下面我们看看这个方法的实现:
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- MethodInvocation invocation = null;
- Object oldProxy = null;
- boolean setProxyContext = false;
-
- TargetSource targetSource = this.advised.targetSource;
- Class targetClass = null;
- Object target = null;
-
- try {
-
-
-
- if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
-
-
- return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE;
- }
- if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
-
- return new Integer(hashCode());
- }
- if (Advised.class == method.getDeclaringClass()) {
-
- return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
- }
-
- Object retVal = null;
-
- if (this.advised.exposeProxy) {
-
- oldProxy = AopContext.setCurrentProxy(proxy);
- setProxyContext = true;
- }
-
-
-
-
- target = targetSource.getTarget();
- if (target != null) {
- targetClass = target.getClass();
- }
-
-
-
- List chain = this.advised.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
- this.advised, proxy, method, targetClass);
-
-
-
-
- if (chain.isEmpty()) {
-
-
-
- retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
- }
- else {
-
-
-
-
-
- invocation = new ReflectiveMethodInvocation(
- proxy, target, method, args, targetClass, chain);
-
-
-
- retVal = invocation.proceed();
- }
-
-
- if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy)) {
-
-
-
- retVal = proxy;
- }
- return retVal;
- }
- finally {
- if (target != null && !targetSource.isStatic()) {
-
- targetSource.releaseTarget(target);
- }
-
- if (setProxyContext) {
-
- AopContext.setCurrentProxy(oldProxy);
- }
- }
- }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation = null;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class targetClass = null;
Object target = null;
try {
// Try special rules for equals() method and implementation of the
// Advised AOP configuration interface.
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// What if equals throws exception!?
// This class implements the equals(Object) method itself.
return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE;
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// This class implements the hashCode() method itself.
return new Integer(hashCode());
}
if (Advised.class == method.getDeclaringClass()) {
// service invocations on ProxyConfig with the proxy config
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal = null;
if (this.advised.exposeProxy) {
// make invocation available if necessary
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be <code>null</code>. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
// 这里是得到目标对象的地方,当然这个目标对象可能来自于一个实例池或者是一个简单的JAVA对象
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// get the interception chain for this method
// 这里获得定义好的拦截器链
List chain = this.advised.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this.advised, proxy, method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
// 如果没有设定拦截器,那么我们就直接调用目标的对应方法
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
// invocation = advised.getMethodInvocationFactory().getMethodInvocation(
// proxy, method, targetClass, target, args, chain, advised);
// 如果有拦截器的设定,那么需要调用拦截器之后才调用目标对象的相应方法
// 这里通过构造一个ReflectiveMethodInvocation来实现,下面我们会看这个ReflectiveMethodInvocation类
invocation = new ReflectiveMethodInvocation(
proxy, target, method, args, targetClass, chain);
// proceed to the joinpoint through the interceptor chain
// 这里通过ReflectiveMethodInvocation来调用拦截器链和相应的目标方法
retVal = invocation.proceed();
}
// massage return value if necessary
if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy)) {
// Special case: it returned "this" and the return type of the method is type-compatible
// Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// must have come from TargetSource
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// restore old proxy
AopContext.setCurrentProxy(oldProxy);
}
}
}
我们先看看目标对象方法的调用,这里是通过AopUtils的方法调用 - 使用反射机制来对目标对象的方法进行调用:
- public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
- throws Throwable {
-
-
-
- try {
- if (!Modifier.isPublic(method.getModifiers()) ||
- !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
- method.setAccessible(true);
- }
- return method.invoke(target, args);
- }
- catch (InvocationTargetException ex) {
-
-
- throw ex.getTargetException();
- }
- catch (IllegalArgumentException ex) {
- throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
- method + "] on target [" + target + "]", ex);
- }
- catch (IllegalAccessException ex) {
- throw new AopInvocationException("Couldn't access method: " + method, ex);
- }
- }
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
throws Throwable {
// Use reflection to invoke the method.
// 利用放射机制得到相应的方法,并且调用invoke
try {
if (!Modifier.isPublic(method.getModifiers()) ||
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
method.setAccessible(true);
}
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
// We must rethrow it. The client won't see the interceptor.
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
method + "] on target [" + target + "]", ex);
}
catch (IllegalAccessException ex) {
throw new AopInvocationException("Couldn't access method: " + method, ex);
}
}
对拦截器链的调用处理是在ReflectiveMethodInvocation里实现的:
- public Object proceed() throws Throwable {
-
-
- if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()) {
- return invokeJoinpoint();
- }
-
- Object interceptorOrInterceptionAdvice =
- this.interceptorsAndDynamicMethodMatchers.get(this.currentInterceptorIndex);
- if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
-
-
-
- InterceptorAndDynamicMethodMatcher dm =
- (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
- if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
- return dm.interceptor.invoke(nextInvocation());
- }
- else {
-
-
-
- this.currentInterceptorIndex++;
- return proceed();
- }
- }
- else {
-
-
- return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(nextInvocation());
- }
- }
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
// 这里直接调用目标对象的方法,没有拦截器的调用或者拦截器已经调用完了,这个currentInterceptorIndex的初始值是0
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
// 这里获得相应的拦截器,如果拦截器可以匹配的上的话,那就调用拦截器的invoke方法
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(nextInvocation());
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
// 如果拦截器匹配不上,那就调用下一个拦截器,这个时候拦截器链的位置指示后移并迭代调用当前的proceed方法
this.currentInterceptorIndex++;
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(nextInvocation());
}
}
这里把当前的拦截器链以及在拦截器链的位置标志都clone到一个MethodInvocation对象了,作用是当前的拦截器执行完之后,会继续沿着得到这个拦截器链执行下面的拦截行为,也就是会迭代的调用上面这个proceed:
- private ReflectiveMethodInvocation nextInvocation() throws CloneNotSupportedException {
- ReflectiveMethodInvocation invocation = (ReflectiveMethodInvocation) clone();
- invocation.currentInterceptorIndex = this.currentInterceptorIndex + 1;
- invocation.parent = this;
- return invocation;
- }
private ReflectiveMethodInvocation nextInvocation() throws CloneNotSupportedException {
ReflectiveMethodInvocation invocation = (ReflectiveMethodInvocation) clone();
invocation.currentInterceptorIndex = this.currentInterceptorIndex + 1;
invocation.parent = this;
return invocation;
}
这里的nextInvocation就已经包含了当前的拦截链的基本信息,我们看到在Interceptor中的实现比如TransactionInterceptor的实现中:
- public Object invoke(final MethodInvocation invocation) throws Throwable {
- ......
- try {
-
- retVal = invocation.proceed();
- }
- ......
- else {
- try {
- Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr,
- new TransactionCallback() {
- public Object doInTransaction(TransactionStatus status) {
-
- TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status);
-
- try {
- return invocation.proceed();
- }
- ......
- }
public Object invoke(final MethodInvocation invocation) throws Throwable {
......//这里是TransactionInterceptor插入的事务处理代码,我们会在后面分析事务处理实现的时候进行分析
try {
//这里是对配置的拦截器链进行迭代处理的调用
retVal = invocation.proceed();
}
......//省略了和事务处理的异常处理代码 ,也是TransactionInterceptor插入的处理
else {
try {
Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr,
new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
//这里是TransactionInterceptor插入对事务处理的代码
TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status);
//这里是对配置的拦截器链进行迭代处理的调用,接着顺着拦截器进行处理
try {
return invocation.proceed();
}
......//省略了和事务处理的异常处理代码 ,也是TransactionInterceptor插入的处理
}
从上面的分析我们看到了Spring AOP的基本实现,比如Spring怎样得到Proxy,怎样利用JAVA Proxy以及反射机制对用户定义的拦截器链进行处理。
下面我们来看看Spring的AOP的一些相关代码是怎么得到Proxy的,让我们我们先看看AOP和Spring AOP的一些基本概念:
Advice:
通知,制定在连接点做什么,在Sping中,他主要描述Spring围绕方法调用注入的额外的行为,Spring提供的通知类型有:
before advice,AfterReturningAdvice,ThrowAdvice,MethodBeforeAdvice,这些都是Spring AOP定义的接口类,具体的动作实现需要用户程序来完成。
Pointcut:
切点,其决定一个advice应该应用于哪个连接点,也就是需要插入额外处理的地方的集合,例如,被某个advice作为目标的一组方法。Spring pointcut通常意味着标示方法,可以选择一组方法调用作为pointcut,Spring提供了具体的切点来给用户使用,比如正则表达式切点 JdkRegexpMethodPointcut通过正则表达式对方法名进行匹配,其通过使用 AbstractJdkRegexpMethodPointcut中的对MethodMatcher接口的实现来完成pointcut功能:
- public final boolean matches(Method method, Class targetClass) {
-
- String patt = method.getDeclaringClass().getName() + "." + method.getName();
- for (int i = 0; i < this.patterns.length; i++) {
-
- boolean matched = matches(patt, i);
- if (matched) {
- for (int j = 0; j < this.excludedPatterns.length; j++) {
- boolean excluded = matchesExclusion(patt, j);
- if(excluded) {
- return false;
- }
- }
- return true;
- }
- }
- return false;
- }
public final boolean matches(Method method, Class targetClass) {
//这里通过放射得到方法的全名
String patt = method.getDeclaringClass().getName() + "." + method.getName();
for (int i = 0; i < this.patterns.length; i++) {
// 这里是判断是否和方法名是否匹配的代码
boolean matched = matches(patt, i);
if (matched) {
for (int j = 0; j < this.excludedPatterns.length; j++) {
boolean excluded = matchesExclusion(patt, j);
if(excluded) {
return false;
}
}
return true;
}
}
return false;
}
在JDKRegexpMethodPointcut中通过JDK中的正则表达式匹配来完成pointcut的最终确定:
- protected boolean matches(String pattern, int patternIndex) {
- Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
- return matcher.matches();
- }
protected boolean matches(String pattern, int patternIndex) {
Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
return matcher.matches();
}
Advisor:
当我们完成额外的动作设计(advice)和额外动作插入点的设计(pointcut)以后,我们需要一个对象把他们结合起来,这就是通知器 - advisor,定义应该在哪里应用哪个通知。Advisor的实现有:DefaultPointcutAdvisor他有两个属性advice和 pointcut来让我们配置advice和pointcut。
接着我们就可以通过ProxyFactoryBean来配置我们的代理对象和方面行为,在ProxyFactoryBean中有interceptorNames来配置已经定义好的通知器-advisor,虽然这里的名字叫做interceptNames,但实际上是供我们配置advisor的地方,具体的代理实现通过JDK 的Proxy或者CGLIB来完成。因为ProxyFactoryBean是一个FactoryBean,在ProxyFactoryBean中我们通过getObject()可以直接得到代理对象:
- public Object getObject() throws BeansException {
-
- initializeAdvisorChain();
- if (isSingleton()) {
-
- return getSingletonInstance();
- }
- else {
- .......
-
- return newPrototypeInstance();
- }
- }
public Object getObject() throws BeansException {
//这里初始化通知器链
initializeAdvisorChain();
if (isSingleton()) {
//根据定义需要生成单件的Proxy
return getSingletonInstance();
}
else {
.......
//这里根据定义需要生成Prototype类型的Proxy
return newPrototypeInstance();
}
}
我们看看怎样生成单件的代理对象:
- private synchronized Object getSingletonInstance() {
- if (this.singletonInstance == null) {
- this.targetSource = freshTargetSource();
- if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
-
- setInterfaces(ClassUtils.getAllInterfacesForClass(this.targetSource.getTargetClass()));
- }
-
- super.setFrozen(this.freezeProxy);
-
- this.singletonInstance = getProxy(createAopProxy());
-
-
- addListener(this);
- }
- return this.singletonInstance;
- }
-
-
- protected Object getProxy(AopProxy aopProxy) {
- return aopProxy.getProxy(this.beanClassLoader);
- }
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// 这里设置代理对象的接口
setInterfaces(ClassUtils.getAllInterfacesForClass(this.targetSource.getTargetClass()));
}
// Eagerly initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
// 注意这里的方法会使用ProxyFactory来生成我们需要的Proxy
this.singletonInstance = getProxy(createAopProxy());
// We must listen to superclass advice change events to recache the singleton
// instance if necessary.
addListener(this);
}
return this.singletonInstance;
}
//使用createAopProxy放回的AopProxy来得到代理对象。
protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.beanClassLoader);
}
ProxyFactoryBean的父类是AdvisedSupport,Spring使用AopProxy接口把AOP代理的实现与框架的其他部分分离开来;在AdvisedSupport中通过这样的方式来得到AopProxy,当然这里需要得到AopProxyFactory的帮助 - 下面我们看到Spring为我们提供的实现,来帮助我们方便的从JDK或者cglib中得到我们想要的代理对象:
- protected synchronized AopProxy createAopProxy() {
- if (!this.isActive) {
- activate();
- }
- return getAopProxyFactory().createAopProxy(this);
- }
protected synchronized AopProxy createAopProxy() {
if (!this.isActive) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
而在ProxyConfig中对使用的AopProxyFactory做了定义:
-
-
- private transient AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
//这个DefaultAopProxyFactory是Spring用来生成AopProxy的地方,
//当然了它包含JDK和Cglib两种实现方式。
private transient AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
其中在DefaultAopProxyFactory中是这样生成AopProxy的:
- public AopProxy createAopProxy(AdvisedSupport advisedSupport) throws AopConfigException {
-
- if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass() ||
- advisedSupport.getProxiedInterfaces().length == 0) {
-
- if (!cglibAvailable) {
- throw new AopConfigException(
- "Cannot proxy target class because CGLIB2 is not available. " +
- "Add CGLIB to the class path or specify proxy interfaces.");
- }
-
- return CglibProxyFactory.createCglibProxy(advisedSupport);
- }
- else {
-
- return new JdkDynamicAopProxy(advisedSupport);
- }
- }
public AopProxy createAopProxy(AdvisedSupport advisedSupport) throws AopConfigException {
//首先考虑使用cglib来实现代理对象,当然如果同时目标对象不是接口的实现类的话
if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass() ||
advisedSupport.getProxiedInterfaces().length == 0) {
//这里判断如果不存在cglib库,直接抛出异常。
if (!cglibAvailable) {
throw new AopConfigException(
"Cannot proxy target class because CGLIB2 is not available. " +
"Add CGLIB to the class path or specify proxy interfaces.");
}
// 这里使用Cglib来生成Proxy,如果target不是接口的实现的话,返回cglib类型的AopProxy
return CglibProxyFactory.createCglibProxy(advisedSupport);
}
else {
// 这里使用JDK来生成Proxy,返回JDK类型的AopProxy
return new JdkDynamicAopProxy(advisedSupport);
}
}
于是我们就可以看到其中的代理对象可以由JDK或者Cglib来生成,我们看到JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,在JdkDynamicAopProxy实现中我们可以看到Proxy是怎样生成的:
- public Object getProxy(ClassLoader classLoader) {
- if (logger.isDebugEnabled()) {
- Class targetClass = this.advised.getTargetSource().getTargetClass();
- logger.debug("Creating JDK dynamic proxy" +
- (targetClass != null ? " for [" + targetClass.getName() + "]" : ""));
- }
- Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
- findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
-
- return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
- }
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
Class targetClass = this.advised.getTargetSource().getTargetClass();
logger.debug("Creating JDK dynamic proxy" +
(targetClass != null ? " for [" + targetClass.getName() + "]" : ""));
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//这里我们调用JDK Proxy来生成需要的Proxy实例
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
这样用Proxy包装target之后,通过ProxyFactoryBean得到对其方法的调用就被Proxy拦截了, ProxyFactoryBean的getObject()方法得到的实际上是一个Proxy了,我们的target对象已经被封装了。对 ProxyFactoryBean这个工厂bean而言,其生产出来的对象是封装了目标对象的代理对象。
下面我们看看Spring JDBC相关的实现,
在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,一般而言这种Template中都是通过回调函数CallBack类的使用来完成功能的,客户需要在回调接口中实现自己需要的定制行为,比如使用客户想要用的SQL语句等。不过往往Spring通过这种回调函数的实现已经为我们提供了许多现成的方法供客户使用。一般来说回调函数的用法采用匿名类的方式来实现,比如:
- JdbcTemplate = new JdbcTemplate(datasource);
- jdbcTemplate.execute(new CallBack(){
- public CallbackInterfacedoInAction(){
- ......
-
- }
- }
JdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.execute(new CallBack(){
public CallbackInterfacedoInAction(){
......
//用户定义的代码或者说Spring替我们实现的代码
}
}
在模板中嵌入的是需要客户化的代码,由Spring来作或者需要客户程序亲自动手完成。下面让我们具体看看在JdbcTemplate中的代码是怎样完成使命的,我们举JdbcTemplate.execute()为例,这个方法是在JdbcTemplate中被其他方法调用的基本方法之一,客户程序往往用这个方法来执行基本的SQL语句:
- public Object execute(ConnectionCallback action) throws DataAccessException {
-
- Connection con = DataSourceUtils.getConnection(getDataSource());
- try {
- Connection conToUse = con;
-
- if (this.nativeJdbcExtractor != null) {
-
- conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
- }
- else {
-
- conToUse = createConnectionProxy(con);
- }
-
- return action.doInConnection(conToUse);
- }
- catch (SQLException ex) {
-
-
- DataSourceUtils.releaseConnection(con, getDataSource());
- con = null;
- throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
- }
- finally {
-
- DataSourceUtils.releaseConnection(con, getDataSource());
- }
- }
public Object execute(ConnectionCallback action) throws DataAccessException {
//这里得到数据库联接
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
//有些特殊的数据库,需要我们使用特别的方法取得datasource
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
//这里调用的是传递进来的匿名类的方法,也就是用户程序需要实现CallBack接口的地方。
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
//如果捕捉到数据库异常,把数据库联接释放,同时抛出一个经过Spring转换过的Spring数据库异常,
//我们知道,Spring做了一个有意义的工作是把这些数据库异常统一到自己的异常体系里了。
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
//最后不管怎样都会把数据库连接释放
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
对于JdbcTemplate中给出的其他方法,比如query,update,execute等的实现,我们看看query():
- public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
- throws DataAccessException {
- ..........
-
- return execute(psc, new PreparedStatementCallback() {
- public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
-
- ResultSet rs = null;
- try {
-
- if (pss != null) {
- pss.setValues(ps);
- }
-
- rs = ps.executeQuery();
- ResultSet rsToUse = rs;
- if (nativeJdbcExtractor != null) {
- rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
- }
-
- return rse.extractData(rsToUse);
- }
- finally {
-
- JdbcUtils.closeResultSet(rs);
- if (pss instanceof ParameterDisposer) {
- ((ParameterDisposer) pss).cleanupParameters();
- }
- }
- }
- });
- }
public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
throws DataAccessException {
..........
//这里调用了我们上面看到的execute()基本方法,然而这里的回调实现是Spring为我们完成的查询过程
return execute(psc, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
//准备查询结果集
ResultSet rs = null;
try {
//这里配置SQL参数
if (pss != null) {
pss.setValues(ps);
}
//这里执行的SQL查询
rs = ps.executeQuery();
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
//返回需要的记录集合
return rse.extractData(rsToUse);
}
finally {
//最后关闭查询的纪录集,对数据库连接的释放在execute()中释放,就像我们在上面分析的看到那样。
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
辅助类DataSourceUtils来用来对数据库连接进行管理的主要工具,比如打开和关闭数据库连接等基本操作:
- public static Connection doGetConnection(DataSource dataSource) throws SQLException {
-
- ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
- if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
- conHolder.requested();
- if (!conHolder.hasConnection()) {
- logger.debug("Fetching resumed JDBC Connection from DataSource");
- conHolder.setConnection(dataSource.getConnection());
- }
- return conHolder.getConnection();
- }
-
- logger.debug("Fetching JDBC Connection from DataSource");
- Connection con = dataSource.getConnection();
-
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- logger.debug("Registering transaction synchronization for JDBC Connection");
-
-
- ConnectionHolder holderToUse = conHolder;
- if (holderToUse == null) {
- holderToUse = new ConnectionHolder(con);
- }
- else {
- holderToUse.setConnection(con);
- }
- holderToUse.requested();
- TransactionSynchronizationManager.registerSynchronization(
- new ConnectionSynchronization(holderToUse, dataSource));
- holderToUse.setSynchronizedWithTransaction(true);
- if (holderToUse != conHolder) {
- TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
- }
- }
-
- return con;
- }
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
//把对数据库连接放到事务管理里面进行管理
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// 这里得到需要的数据库连接,在配置文件中定义好的。
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
那我们实际的DataSource对象是怎样得到的?很清楚我们需要在上下文中进行配置:它作为JdbcTemplate父类JdbcAccessor的属性存在:
- public abstract class JdbcAccessor implements InitializingBean {
-
-
- private DataSource dataSource;
-
-
- private SQLExceptionTranslator exceptionTranslator;
-
- private boolean lazyInit = true;
-
- ........
- }
public abstract class JdbcAccessor implements InitializingBean {
/** 这里是我们依赖注入数据库数据源的地方。 */
private DataSource dataSource;
/** Helper to translate SQL exceptions to DataAccessExceptions */
private SQLExceptionTranslator exceptionTranslator;
private boolean lazyInit = true;
........
}
而对于DataSource的缓冲池实现,我们通过定义Apache Jakarta Commons DBCP或者C3P0提供的DataSource来完成,然后只要在上下文中配置好就可以使用了。从上面我们看到JdbcTemplate提供了许多简单查询和更新功能,但是如果需要更高层次的抽象,以及更面向对象的方法来访问数据库。Spring为我们提供了org.springframework.jdbc.object包,这里面包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure等类,这些类都是Spring JDBC应用程序可以使用的主要类,但我们要注意使用这些类的时候,用户需要为他们配置好一个JdbcTemplate作为其基本的操作的实现。
比如说我们使用MappingSqlQuery来将表数据直接映射到一个对象集合 - 具体可以参考书中的例子
1.我们需要建立DataSource和sql语句并建立持有这些对象的MappingSqlQuery对象
2.然后我们需要定义传递的SqlParameter,具体的实现我们在MappingSqlQuery的父类RdbmsOperation中可以找到:
- public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
-
- if (isCompiled()) {
- throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
- }
-
- this.declaredParameters.add(param);
public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
//如果声明已经被编译过,则该声明无效
if (isCompiled()) {
throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
}
//这里对参数值进行声明定义
this.declaredParameters.add(param);
}
而这个declareParameters维护的是一个列表:
-
- private List declaredParameters = new LinkedList();
/** List of SqlParameter objects */
private List declaredParameters = new LinkedList();
这个列表在以后compile的过程中会被使用。
3.然后用户程序需要实现MappingSqlQuery的mapRow接口,将具体的ResultSet数据生成我们需要的对象,这是我们迭代使用的方法。1,2,3步实际上为我们定义好了一个迭代的基本单元作为操作模板。
4.在应用程序,我们直接调用execute()方法得到我们需要的对象列表,列表中的每一个对象的数据来自于执行SQL语句得到记录集的每一条记录,事实上执行的execute在父类SqlQuery中起作用:
- public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException {
- validateNamedParameters(paramMap);
- Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap);
- RowMapper rowMapper = newRowMapper(parameters, context);
- String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql(), new MapSqlParameterSource(paramMap));
-
- return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
- }
public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException {
validateNamedParameters(paramMap);
Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap);
RowMapper rowMapper = newRowMapper(parameters, context);
String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql(), new MapSqlParameterSource(paramMap));
//我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数据库的查询操作,所以我们说JdbcTemplate是基本的操作类。
return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
}
在这里我们可以看到template模式的精彩应用和对JdbcTemplate的灵活使用。通过使用它,我们免去了手工迭代ResultSet并将其中的数据转化为对象列表的重复过程。在这里我们只需要定义SQL语句和SqlParameter - 如果需要的话,往往SQL语句就常常能够满足我们的要求了。这是灵活使用JdbcTemplate的一个很好的例子。
Spring还为其他数据库操作提供了许多服务,比如使用SqlUpdate插入和更新数据库,使用UpdatableSqlQuery更新ResultSet,生成主键,调用存储过程等。
书中还给出了对BLOB数据和CLOB数据进行数据库操作的例子:
对BLOB数据的操作通过LobHander来完成,通过调用JdbcTemplate和RDBMS都可以进行操作:
在JdbcTemplate中,具体的调用可以参考书中的例子 - 是通过以下调用起作用的:
- public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
- return execute(new SimplePreparedStatementCreator(sql), action);
- }
public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action);
}
然后通过对实现PreparedStatementCallback接口的AbstractLobCreatingPreparedStatementCallback的回调函数来完成:
- public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
- LobCreator lobCreator = this.lobHandler.getLobCreator();
- try {
-
- setValues(ps, lobCreator);
- return new Integer(ps.executeUpdate());
- }
- finally {
- lobCreator.close();
- }
- }
-
- protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)
- throws SQLException, DataAccessException;
public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
LobCreator lobCreator = this.lobHandler.getLobCreator();
try {
//这是一个模板方法,具体需要由客户程序实现
setValues(ps, lobCreator);
return new Integer(ps.executeUpdate());
}
finally {
lobCreator.close();
}
}
//定义的需要客户程序实现的虚函数
protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)
throws SQLException, DataAccessException;
而我们注意到setValues()是一个需要实现的抽象方法,应用程序通过实现setValues来定义自己的操作 - 在setValues中调用lobCreator.setBlobAsBinaryStrem()。让我们看看具体的BLOB操作在LobCreator是怎样完成的,我们一般使用DefaultLobCreator作为BLOB操作的驱动:
- public void setBlobAsBinaryStream(
- PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
- throws SQLException {
-
- ps.setBinaryStream(paramIndex, binaryStream, contentLength);
- ........
- }
public void setBlobAsBinaryStream(
PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
throws SQLException {
//通过JDBC来完成对BLOB数据的操作,对Oracle,Spring提供了OracleLobHandler来支持BLOB操作。
ps.setBinaryStream(paramIndex, binaryStream, contentLength);
........
}
上面提到的是零零碎碎的Spring JDBC使用的例子,可以看到使用Spring JDBC可以帮助我们完成许多数据库的操作。Spring对数据库操作最基本的服务是通过JdbcTeamplate和他常用的回调函数来实现的,在此之上,又提供了许多RMDB的操作来帮助我们更便利的对数据库的数据进行操作 - 注意这里没有引入向Hibernate这样的O/R方案。对这些O/R方案的支持,Spring由其他包来完成服务。
书中还提到关于execute和update方法之间的区别,update方法返回的是受影响的记录数目的一个计数,并且如果传入参数的话,使用的是java.sql.PreparedStatement,而execute方法总是使用 java.sql.Statement,不接受参数,而且他不返回受影响记录的计数,更适合于创建和丢弃表的语句,而update方法更适合于插入,更新和删除操作,这也是我们在使用时需要注意的。
摘要: 上面我们分析了IOC容器本身的实现,下面我们看看在典型的web环境中,Spring IOC容器是怎样被载入和起作用的。 简单的说,在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web ...
阅读全文
摘要: 在认真学习Rod.Johnson的三部曲之一:<<Professional Java Development with the spring framework>>,顺便也看了看源代码想知道个究竟,抛砖引玉,有兴趣的同志一起讨论研究吧! 以下内容引自博客:http://jiwenke-spring.blogspot.com/,欢迎指导:) 在Spring中,IOC容器的重要...
阅读全文
众所周知,技术不再是困扰CIO职业前途的必备要件之一。但是,这不是说CIO可以不关心技术了。相反,CIO可以不精通某种技术,但是,一定要对这种技术的优缺点以及发展趋势有个清晰的了解。因为在信息化管理软件选型或者信息化战略制定的时候,还是离不开技术,除了对软件本身进行选型之外,还需要考虑其采用的技术的选型。
具体的来说,CIO在技术选型上,要考虑所采用的技术是否稳定、扩展性是否较好、跨平台的性能等等。在软件选型的时候,不仅要考虑软件的功能,也要考虑其所采用的技术,只有如此,CIO才能轻松应对后续的变化。
技术选型标准一:跨平台性 随着微软打击盗版力度的加强,越来越多的企业开始考虑操作系统的使用成本。确实,对于大部分企业来说,已经习惯使用免费的软件,若让他们掏腰包,去买正版的操作系统与OFFICE办公软件,而且这些费用还不低,他们不一定会肯。随着微软盗版力度的加强,企业一定会千方百计的去寻找微软操作系统与办公软件的替代品。恰好,这个就给开源的LINUX操作系统与OPEOFFICE办公软件提供了发展的机会。
其实,根据使用经验,这个开源的操作系统与办公软件,其性能方面并不比微软的操作系统与办公软件差。而只是员工以前在学校中学的都是微软的产品,所以,对于LINUX系统上的软件操作并不时很熟悉,所以,会对他有排斥心理。另一方面,开源的LINUX操作系统与办公软件在界面的友好性上可能还是跟微软的产品有所差别,但是,这并不影响实用。而且,LINUX操作系统,在业界的评论上,其稳定性要比微软的操作系统要好,而且,其受到病毒袭击的机率也比微软的操作系统少得多。
所以,借着微软打击盗版力度的加强,恰好给了一个LINUX系统普及的机会。相信随着企业对于LINUX系统认识的加强,会有越来越多的企业采用这个开源的LINUX系统。
但是,这对企业或许是一件好事情,可是,对于CIO来说,却是一个头疼的事情。为什么呢?因为现在很多的管理软件,如ERP、财务管理软件等等,都是在微软操作系统的平台上开发的。由于平台开发技术的限制,这些为微软操作系统量身定制的信息化管理系统,无法在LINUX平台上运行顺畅,有的甚至是不兼容的。为此,企业若向放弃使用微软的操作系统,虽然可以节省操作系统上的授权费用,但是,也必将面临着这些管理软件的取舍问题。
所以,为了我们CIO后续在操作系统的选型上没有这么多的限制,我们从现在开始,就要看到这个趋势,在信息化管理软件选型的时候,要注意软件的跨平台性能。简单地说,我们选择的信息化管理软件不仅要在微软的操作系统上能够跑的顺畅,而且,在LINUX系统平台上也要能够运行顺利,甚至还能够支持苹果等操作系统。只有如此,下次我们在操作系统转型的时候,才不会受到这些信息化管理软件的限制。
一般来说,现在一些基于JAVA开发的、WEB模式的信息化管理软件,基本上都能够实现多平台的兼容。而且,现在这也是信息化管理软件发展的趋势。以后支持单一平台的信息化管理软件,就像以前只有单一语言的信息化管理软件一样,市场份额将会逐渐缩小。
技术选型标准二:扩展性与集成性的考虑 信息化管理系统现在已经发展成为一个体系。为什么这么说呢?现在企业中所采纳的信息化管理系统,已经不是以前单一的一个财务管理软件。如某些企业,已经有了CRM客户关系管理系统、ERP企业资源计划系统、OA办公自动化系统等等。现在要成功实施一个信息化项目,已经不是一件难事,大家都有了丰富的信息化项目实施经验。像以前所说的“上ERP系统是找死,不上ERP系统是等死”的现状已经一去不复返。现在ERP实施的成功率已经非常的高。
所以,现在信息化项目成功上线已经不是我们CIO所追求的最高目标,而是最基本的目标。现在我们CIO所要考虑的,是系统的集成问题。如何把企业先后实施的各个信息化项目有效的集成起来,减少信息化项目上的重复投资,提高信息化管理的效果,这已经是考验我们CIO能力的一个非常有效的手段。
现在SOA系统,一个系统集成的平台,在信息化管理中,如一匹黑马,独树一帜。从这里就可以看出,企业对于信息化系统整合的迫切需求。
为此,我们CIO在信息化管理软件进行选型的时候,就需要考虑这个系统集成性的问题。要考虑我们所即将采用的信息化管理软件,有否提供丰富的接口,可以跟其他信息化管理系统进行交流。
虽然现在还没有统一的标准,SOA发展也只是起步,没有成熟,还有很多需要改善的地方。但是,我们CIO需要有高瞻远瞩的目光,在信息化管理软件选型的时候,需要关注软件开发商有没有这方面的尝试。如公布自己的接口的详细参数信息,如此的话,即使后面没有统一的集成平台,则通过少量的开发,也可以把两个或者两个以上的操作系统平台整合起来。
另外关于系统整合还有一个偷懒的方法。就是我们在软件选型的时候,要看看其软件开发商有没有形成一个信息化管理的体系产品。如企业现在可能只需要采用ERP系统,但是我们在CIO选型的时候,需要考虑这个软件开发商是否还开发了CRM系统或者工作六管理系统。因为他们的这些系统之间往往可以实现很好的集成。如此的话,下次若需要其他的信息化管理软件的时候,我们也可以考虑购买他们的产品,如此的话,就可以非常轻松的实现各大信息化管理系统的集成。
系统的集成是未来信息化管理软件发展的一个趋势,所以,为了后续工作的方便,我们在选择第一个信息化管理软件的时候,就需要有软件集成的思想。只有如此,后续企业需要软件集成的时候,我们才不会手忙脚乱。
技术选型标准三:技术稳定性的考虑 软件企业跟生产或者商品企业对于技术的追求可能有所区别。软件企业更多的关注是这个技术会否过时,他们往往希望利用“时髦”来推销他们的新技术产品。而对于生产企业来说,时髦并不是企业所关注的重点,他们更加关注的是这门技术的成熟程度,是否会像微软操作系统一样,隔几天就需要打补丁或者软件升级。
在我们CIO严重,最适合企业的技术不时往往不是那种刚出炉的技术,虽然他们比较先进,解决了以前产品中的种种缺陷。但是,他们也有一个致命的缺点,就是不够稳定。隔三差五的会出现几个漏洞。而这真是作为CIO最不能够忍受的。因为在企业信息化管理利用中,可能你做的好人家不一定主意,但是,一出现什么问题人家就会抓住你不妨。这大概就叫做“好事不出门、坏事传千里”吧。
各位CIO在软件选型的时候,考虑技术因素的时候,不要太关注与技术是否够时髦,会不会过时的问题;而应该更多的考虑,这门技术或者产品,是否稳定,用户群是否过大。
在信息化管理上,要学会模仿,而不是争做“第一个吃螃蟹的人”。在信息化管理行业,变数太多。第一个吃螃蟹的人,大部分是死的比较惨的人。若我们等到别人先试用了之后,产品或者技术被改善的差不多了,然后我们再去采用这种技术或者产品,则就可以最大限度的避免由此带来的风险。
技术选型有时候比产品功能选型更加难。因为技术选系的话,需要考虑的因素太多,而且现在技术的更新换代实在让人有点目不暇接。现在比较流行的不 B/S模式说不定哪一天也会像以前C/S模式一样被其他模式所代替。而功能选型的话,至少功能是可以收集的,只需要细心与时间,一般不会有多大的误差。
现在接触的信息化项目越来越多,由此带来的感受也就越来越深。你如果把现在的企业当作临时的中转站,你可能不会有这么大的顾虑,可能会以为只需要考虑未来的几年的时间就可以了。但是,你若想在这家企业中终老,那么,考虑的因素就会比较多,什么软件的集成、软件的扩展性、技术的稳定性与先进行之间的矛盾与取舍,都是我们CIO所需要关注的内容。
所以,信息化技术日新月异的发展,给我们CIO带来了发展的机遇,但是,也给我们带来的巨大的压力。
摘要: 1.文本框焦点问题onBlur:当失去输入焦点后产生该事件onFocus:当输入获得焦点后,产生该文件Onchange:当文字值改变时,产生该事件Onselect:当文字加亮后,产生该文件<input type="text" value="mm" onfocus="if(value=='mm) {value=''}" onblur="if(value=='') {value='mm'}"&g...
阅读全文
为了使web应用能使用saas模式的大规模访问,必须实现应用的集群部署.要实现集群部署主要需要实现session共享机制,使得多台应用服务器之间会话统一, tomcat等多数服务都采用了session复制技术实现session的共享.
session复制技术的问题:
(1)技术复杂,必须在同一种中间件之间完成(如:tomcat-tomcat之间).
(2)在节点持续增多的情况下,session复制带来的性能损失会快速增加.特别是当session中保存了较大的对象,而且对象变化较快时,性能下降更加显著.这种特性使得web应用的水平扩展受到了限制.
session共享的另一种思路就是把session集中起来管理,首先想到的是采用数据库来集中存储session,但数据库是文件存储相对内存慢了一个数量级,同时这势必加大数据库系统的负担.所以需要一种既速度快又能远程集中存储的服务,所以就想到了memcached.
memcached是什么?
memcached是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。
memcached能缓存什么?
通过在内存里维护一个统一的巨大的hash表,Memcached能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。
memcached快么?
非常快。memcached使用了libevent(如果可以的话,在linux下使用epoll)来均衡任何数量的打开链接,使用非阻塞的网络I/O,对内部对象实现引用计数(因此,针对多样的客户端,对象可以处在多样的状态), 使用自己的页块分配器和哈希表, 因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1).。
Danga Interactive为提升Danga Interactive的速度研发了memcached。目前,LiveJournal.com每天已经在向一百万用户提供多达两千万次的页面访问。而这些,是由一个由web服务器和数据库服务器组成的集群完成的。memcached几乎完全放弃了任何数据都从数据库读取的方式,同时,它还缩短了用户查看页面的速度、更好的资源分配方式,以及memcache失效时对数据库的访问速度。
memcached的特点
memcached的缓存是一种分布式的,可以让不同主机上的多个用户同时访问, 因此解决了共享内存只能单机应用的局限,更不会出现使用数据库做类似事情的时候,磁盘开销和阻塞的发生。
使用memcached来存储session有两种方案:
(1)直接通过tomcat6的扩展机制实现.
参考: http://www.javaeye.com/topic/81641
(2)通过自己编写filter实现.
考虑到系统的扩展,我们采用这种方案.这样可以使session共享机制和中间件脱钩.
参考: http://www.javaeye.com/topic/82565
主要思路:
(1)继承重构HttpServletRequestWrapper,HttpSessionWrapper类,覆盖原来和session存取相关的方法呢,都通过SessionService类来实现.
(2)使用filter拦截cookie中的sessionId,通过sessionId构造新的HttpServletRequestWrapper对象,传给后面的应用.
(3)SessionService连接memcached服务,以sessionId作为key,存取的对象是一个map.map的内容即为session的内容.
使用过程注意几个问题和改进思路:
1、memcache的内存应该足够大,这样不会出现用户session从Cache中被清除的问题(可以关闭memcached的对象退出机制)。
2、如果session的读取比写入要多很多,可以在memcache前再加一个Oscache等本地缓存,减少对memcache的读操作,从而减小网络开销,提高性能。
3、如果用户非常多,可以使用memcached组,通过set方法中带hashCode,插入到某个memcached服务器
对于session的清除有几种方案:
(1)可以在凌晨人最少的时候,对memcached做一次清空。(简单)
(2)保存在缓存中的对象设置一个失效时间,通过过滤器获取sessionId的值,定期刷新memcached中的对象.长时间没有被刷新的对象自动被清除.(相对复杂,消耗资源)
提出问题:为啥要有双缓冲队列?
引用09年9月《程序员》上的一句话:双缓冲队列就是冲着同步/互斥的开销来的。我们知道,在多个线程并发访问同一个资源的时候,需要特别注意线程的同步问题。稍稍不注意,哦活,程序结果不正确了。最经典的就是“银行取钱”的例子,想想,都跟现金挂上钩了,看来这真不容忽视。
今天我们要谈的不是如何去给资源加锁解锁来解决同步问题,今天的重点在于,如何将线程同步的开销降低到我们力所能及的程度。如果你觉得,你可以通过增加硬件资源来弥补程序开销,那么,你将不可能成为一个优秀的程序员。
进入正题,先引入一个例子,两个实体:一个是玩具工厂,它的工作就是不停地生产玩具;另外一个实体就是小孩,它的工作就是不停地从工厂拿玩具。小孩不可能直接到工厂去“拿”玩具吧?呵呵,妈妈是绝对不会放心的。所以,我们有一个“搬运工”,搬运工自然要具备“存放”的功能,不然他怎么将玩具带给小孩呢,是吧。所以,我们先将搬运工定义为一个List,用来存放工厂生产出来的玩具。
代码如下
玩具类,定义一个玩具实体
public class Toy {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
接下来是玩具工厂,它得“不停地”生产,所以,把它设计成一个线程类
玩具工厂,将玩具对象,放到list中
public class Factory extends Thread{
public void run(){
while(true){
Toy t = new Toy ();
t.setName("玩具");
synchronized (Tools.lT){
Tools.lT.add(t);
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
注意到,在上面这段代码当中,必须得用synchronized (Tools.lT)将Tools.lT给加锁。不然会出现线程同步问题(开销就在这里)。
再接下来,看看小孩是怎么“玩”玩具的:
小孩类,从list中取出玩具对象
public class Kid extends Thread {
long time1 = System.currentTimeMillis();
int count = 0;
public void run() {
while(true){
synchronized(Tools.lT){
if(Tools.lT.size()!=0)
Tools.lT.remove(0);
}
count++;
if(count==100000){
javax.swing.JOptionPane.showMessageDialog(null, "用时间: "+(System.currentTimeMillis()-time1));
System.exit(0);
}
}
}
}
当list不为空的时候,将list中的玩具对象,一个一个取出来,玩完!这个小孩可真够厉害的,呵呵。可以想象为,该类的工作就是不停地向list中取出玩具对象。OK,再来编写方法,如下
主方法
public class MainTest {
/**
* @param args
*/
public static void main(String[] args) {
Factory f = new Factory();
f.start();
Kid k = new Kid();
k.start();
}
}
最后是Tools类,他里面有个静态的List对象:
Tools类
public class Tools {
public static List<Toy>lT = new ArrayList<Toy>(10000);
}
这样,我们运行一下主方法,看看我们这位“天才”玩完100000个玩具,得花销多少的时间。
好,31毫秒。
这是我们的第一种解决方法,下面再来看第二种解决方法:
其实我们在Factory类和Kid类中都进行了同步处理,这样一来,浪费了很多时间,到底是不是这样的呢?我们可不可以直接用一个不用处理线程同步的容器来放Toy类对象呢?这样以来是不是就可以节省很多开销了?这个想法是有道理的,但是,事实是不是这样的呢?马上实践!
代码就不具体贴出来了,只是我们在Tools类中用到的是一个如下的对象
Public static LinkedBlockingQueue<Toy> lT= new LinkedBlockingQueue<Toy>(1000);
对,阻塞队列,这样我们就只管往里面取,从里面拿了,不用自己考虑同步问题,Factory类和Kid类中也不同特意去加关键字进行同步了。
那么这种方案的结果是多少呢?同样是100000个玩具,看结果
哦哦,变成16毫秒了,着实提高了不少效果呢。看来,在处理同步的时候挤时间,是有发展空间的,呵呵。
等等,有人要发话了,你在这磨叽了半天,还是没有说什么是双缓冲啊,对!有了前面的两种方案,我们再来看看“双缓冲队列”。
所谓双缓冲队列,自然起码要有两个队列吧,呵呵,在这个例子中,我们可以设计两个List来存放工厂生产出来的玩具对象。
下面分析一下:
两个List,一个用来存,一个用来取。有点迷糊?就是有一个listP从工厂那里得到玩具对象,另外一个listT就专门把它得到的玩具对象送去给 Kid类处理。当listT变成空的了以后,再将listP中在这段时间内取到的所有玩具对象放到listT中,好,这完了之后,他们两个就又各自干各自的去了:listP再去取,listT再去送。这样是不是就减少了很多次的线程同步呢?至少,在它们交换之前,listP是完全被工厂类线程占有,listT是完全被Kid类线程占有的,不用处理同步。只有在listT放完了,没得给了,再去跟ListP换过来,这个时候就要处理同步了。
跟实际联系一下,有两个搬运工A,B,A在工厂,专门从工厂取玩具;B在小孩子身边,专门送玩具给小孩玩。当B身上没有玩具了,自然要回A那里,把A身上的玩具全部拿过来,再来送给小孩玩。在A还有玩具的时候,A和B是在各自的线程里被处理的,即A在拿,B在给。不用担心同步问题。
这样以来,处理同步问题的次数是不是大大减少了呢?没错,就是这样的。那么怎么跟代码结合呢?
我们要设计一个监视线程,监视listP是不是空了,要是空了,把它同步起来,把listT也同步起来,让他们交换。完了就各自干各自的了。
我们来看看这个监视类:
public class DoubleBufferList {
private List<Object> lP;
private List<Object> lT;
private int gap;
/**
* 构造方法
*
* @param lP
* 用来存放对象的队列
* @param lT
* 用来取对象的队列
* @param gap
* 交换的间隔
*/
public DoubleBufferList(List lP, List lT, int gap) {
this.lP = lP;
this.lT = lT;
this.gap = gap;
}
public void check() {
Runnable runner = new Runnable() {
public void run() {
while (true) {
if (lT.size() == 0) {
synchronized (lT) {
synchronized (lP) {
lT.addAll(lP);
}
lP.clear();
}
}
try {
Thread.sleep(gap);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runner);
thread.start();
}
}
这个类中的线程方法就是用来交换的,目的就是为了减少处理同步的次数。这种方案中,Facotry类和Kid类跟前面单个List的情况差不多,就不再给出了。只是有一点要注意,Facory类中将玩具对象是放到了lP中,而Kid类中,也只是从lT中去取对象。看看Tools类中,多了一个变量:
Tools类,声明两个队列
public static List<Toy>lT = new ArrayList<Toy>(10000);
public static List<Toy>lP = new ArrayList<Toy>(10000);
同样是让我们的“天才”玩完100000个玩具,来看看运行需要的时间:
哈哈,似乎跟我们上面的第二种方案,单阻塞队列,没有太大的差异。怎么解释呢?
不用着急,来,我将额定的玩具量后多加个“0”,让他玩完1000000个!改一下单阻塞队列方案的输出结果,给他们一个标记。再来看看结果:
效果出来了吧,我们再加大量,让他们同时处理10000000个玩具对象:
充分说明,使用双缓冲队列,比单缓冲阻塞队列的效果要好,更别说单缓冲队列了。
总结:
从上面的分析,我们可以得知,在处理线程同步的时候,是要花费我们的时间的,虽然在有些时候,这样的花费是我们可以接受的,但是在很多情况下,如果我们能注意到这样的浪费,并且及时地完善我们的程序,这样可以更大限度地提高我们程序的运行效率。尤其是在大的程序里面,这样的效果体现得更明显。而往往越大的系统,对性能的要求也就越高。
android在处理一写图片资源的时候,会进行一些类型的转换,现在有空整理一下:
1、Drawable → Bitmap 的简单方法
((BitmapDrawable)res.getDrawable(R.drawable.youricon)).getBitmap();
2、Drawable → Bitmap
Java代码
public static Bitmap drawableToBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap
.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
//canvas.setBitmap(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
3.Bitmap→Drawable 的简单方法
BitmapDrawable bitmapDrawable = (BitmapDrawable)bitmap;
Drawable drawable = (Drawable)bitmapDrawable;
Bitmap bitmap = new Bitmap (...);
Drawable drawable = new BitmapDrawable(bitmap);
3、从资源中获取Bitmap
Java代码
Resources res=getResources();
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic);
4、Bitmap → byte[]
Java代码
private byte[] Bitmap2Bytes(Bitmap bm){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
5、 byte[] → Bitmap
Java代码
private Bitmap Bytes2Bimap(byte[] b){
if(b.length!=0){
return BitmapFactory.decodeByteArray(b, 0, b.length);
}
else {
return null;
}
}
修改TabHost默认样式
TabHost是Android提供的一个容器组件,利用它可以轻松地实现TAB界面,如下图所示:
但很多时候,默认的TAB样式并不符合软件的整体风格,这时候该怎么办呢?其实,我们可以编写XML对其样式进行修改。下面修改后的效果图:
1. TabHost布局文件 main.xml
<TabHost
android:id="@+id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="30dip"
android:background="#a0a0a0"
android:layout_weight="0" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<ListView
android:id="@+id/user_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
<ListView
android:id="@+id/article_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
<ListView
android:id="@+id/feed_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
<ListView
android:id="@+id/book_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
</FrameLayout>
</LinearLayout>
</TabHost>
FrameLayout里有四个ListView 分别对应用户、文章、频道、图书。
TabWidget和FrameLayout的ID不能自己定义修改。
2. Activity后台代码
RelativeLayout articleTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
TextView articleTabLabel = (TextView) articleTab.findViewById(R.id.tab_label);
articleTabLabel.setText("文章");
RelativeLayout feedTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
TextView feedTabLabel = (TextView) feedTab.findViewById(R.id.tab_label);
feedTabLabel.setText("频道");
RelativeLayout bookTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
TextView bookTabLabel = (TextView) bookTab.findViewById(R.id.tab_label);
bookTabLabel.setText("图书");
TabHost tabHost = (TabHost) findViewById(R.id.tabhost);
tabHost.setup();
tabHost.addTab(tabHost.newTabSpec("user").setIndicator(userTab).setContent(R.id.user_list));
tabHost.addTab(tabHost.newTabSpec("article").setIndicator(articleTab).setContent(R.id.article_list));
tabHost.addTab(tabHost.newTabSpec("feed").setIndicator(feedTab).setContent(R.id.feed_list));
tabHost.addTab(tabHost.newTabSpec("book").setIndicator(bookTab).setContent(R.id.book_list));
|
TabHost创建出来以后,必须先setup一下,tabHost.setup();
setIndicator方法设置的View其实就对应了TAB中的一个个选项卡,它们都是通过一个叫minitab的布局文件inflate出来的
3. 选项卡布局文件minitab.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="5dip"
android:paddingRight="5dip">
<TextView android:id="@+id/tab_label"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:textColor="#000000"
android:textStyle="bold"
android:background="@drawable/minitab" />
</RelativeLayout>
drawable/minitab是一个selector,指定了Tab选项卡的背景颜色。
4. selector文件 minitab.xml
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:state_selected="false"
android:drawable="@drawable/minitab_unselected"
>
</item>
<item
android:state_selected="true"
android:drawable="@drawable/minitab_default"
>
</item>
</selector>
minitab_unselected是一浅蓝色背景图片
minitab_default是一白色背景图片
摘 要 Java规则引擎是一种嵌入在Java程序中的组件,它的任务是把当前提交给引擎的Java数据对象与加载在引擎中的业务规则进行测试和比对,激活那些符合当前数据状态下的业务规则,根据业务规则中声明的执行逻辑,触发应用程序中对应的操作。
引言
目前,Java社区推动并发展了一种引人注目的新技术——Java规则引擎(Rule Engine)。利用它就可以在应用系统中分离商业决策者的商业决策逻辑和应用开发者的技术决策,并把这些商业决策放在中心数据库或其他统一的地方,让它们能在运行时可以动态地管理和修改,从而为企业保持灵活性和竞争力提供有效的技术支持。
规则引擎的原理
1、基于规则的专家系统(RBES)简介
Java规则引擎起源于基于规则的专家系统,而基于规则的专家系统又是专家系统的其中一个分支。专家系统属于人工智能的范畴,它模仿人类的推理方式,使用试探性的方法进行推理,并使用人类能理解的术语解释和证明它的推理结论。为了更深入地了解Java规则引擎,下面简要地介绍基于规则的专家系统。RBES包括三部分:Rule Base(knowledge base)、Working Memory(fact base)和Inference Engine。它们的结构如下系统所示:
图1 基于规则的专家系统构成
如图1所示,推理引擎包括三部分:模式匹配器(Pattern Matcher)、议程(Agenda)和执行引擎(Execution Engine)。推理引擎通过决定哪些规则满足事实或目标,并授予规则优先级,满足事实或目标的规则被加入议程。模式匹配器决定选择执行哪个规则,何时执行规则;议程管理模式匹配器挑选出来的规则的执行次序;执行引擎负责执行规则和其他动作。
和人类的思维相对应,推理引擎存在两者推理方式:演绎法(Forward-Chaining)和归纳法(Backward-Chaining)。演绎法从一个初始的事实出发,不断地应用规则得出结论(或执行指定的动作)。而归纳法则是根据假设,不断地寻找符合假设的事实。Rete算法是目前效率最高的一个Forward-Chaining推理算法,许多Java规则引擎都是基于Rete算法来进行推理计算的。
推理引擎的推理步骤如下:
(1)将初始数据(fact)输入Working Memory。
(2)使用Pattern Matcher比较规则库(rule base)中的规则(rule)和数据(fact)。
(3)如果执行规则存在冲突(conflict),即同时激活了多个规则,将冲突的规则放入冲突集合。
(4)解决冲突,将激活的规则按顺序放入Agenda。
(5)使用执行引擎执行Agenda中的规则。重复步骤2至5,直到执行完毕所有Agenda中的规则。
上述即是规则引擎的原始架构,Java规则引擎就是从这一原始架构演变而来的。
2、规则引擎相关构件
规则引擎是一种根据规则中包含的指定过滤条件,判断其能否匹配运行时刻的实时条件来执行规则中所规定的动作的引擎。与规则引擎相关的有四个基本概念,为更好地理解规则引擎的工作原理,下面将对这些概念进行逐一介绍。
1)信息元(Information Unit)
信息元是规则引擎的基本建筑块,它是一个包含了特定事件的所有信息的对象。这些信息包括:消息、产生事件的应用程序标识、事件产生事件、信息元类型、相关规则集、通用方法、通用属性以及一些系统相关信息等等。
2)信息服务(Information Services)
信息服务产生信息元对象。每个信息服务产生它自己类型相对应的信息元对象。即特定信息服务根据信息元所产生每个信息元对象有相同的格式,但可以有不同的属性和规则集。需要注意的是,在一台机器上可以运行许多不同的信息服务,还可以运行同一信息服务的不同实例。但无论如何,每个信息服务只产生它自己类型相对应的信息元。
3)规则集(Rule Set)
顾名思义,规则集就是许多规则的集合。每条规则包含一个条件过滤器和多个动作。一个条件过滤器可以包含多个过滤条件。条件过滤器是多个布尔表达式的组合,其组合结果仍然是一个布尔类型的。在程序运行时,动作将会在条件过滤器值为真的情况下执行。除了一般的执行动作,还有三类比较特别的动作,它们分别是:放弃动作(Discard Action)、包含动作(Include Action)和使信息元对象内容持久化的动作。前两种动作类型的区别将在2.3规则引擎工作机制小节介绍。
4)队列管理器(Queue Manager)
队列管理器用来管理来自不同信息服务的信息元对象的队列。
下面将研究规则引擎的这些相关构件是如何协同工作的。
如图2所示,处理过程分为四个阶段进行:信息服务接受事件并将其转化为信息元,然后这些信息元被传给队列管理器,最后规则引擎接收这些信息元并应用它们自身携带的规则加以执行,直到队列管理器中不再有信息元。
图2 处理过程协作图
3、规则引擎的工作机制
下面专门研究规则引擎的内部处理过程。如图3所示,规则引擎从队列管理器中依次接收信息元,然后依规则的定义顺序检查信息元所带规则集中的规则。如图所示,规则引擎检查第一个规则并对其条件过滤器求值,如果值为假,所有与此规则相关的动作皆被忽略并继续执行下一条规则。如果第二条规则的过滤器值为真,所有与此规则相关的动作皆依定义顺序执行,执行完毕继续下一条规则。该信息元中的所有规则执行完毕后,信息元将被销毁,然后从队列管理器接收下一个信息元。在这个过程中并未考虑两个特殊动作:放弃动作(Discard Action)和包含动作(Include Action)。放弃动作如果被执行,将会跳过其所在信息元中接下来的所有规则,并销毁所在信息元,规则引擎继续接收队列管理器中的下一个信息元。包含动作其实就是动作中包含其它现存规则集的动作。包含动作如果被执行,规则引擎将暂停并进入被包含的规则集,执行完毕后,规则引擎还会返回原来暂停的地方继续执行。这一过程将递归进行。
图3 规则引擎工作机制
Java规则引擎的工作机制与上述规则引擎机制十分类似,只不过对上述概念进行了重新包装组合。Java规则引擎对提交给引擎的Java数据对象进行检索,根据这些对象的当前属性值和它们之间的关系,从加载到引擎的规则集中发现符合条件的规则,创建这些规则的执行实例。这些实例将在引擎接到执行指令时、依照某种优先序依次执行。一般来讲,Java规则引擎内部由下面几个部分构成:工作内存(Working Memory)即工作区,用于存放被引擎引用的数据对象集合;规则执行队列,用于存放被激活的规则执行实例;静态规则区,用于存放所有被加载的业务规则,这些规则将按照某种数据结构组织,当工作区中的数据发生改变后,引擎需要迅速根据工作区中的对象现状,调整规则执行队列中的规则执行实例。Java规则引擎的结构示意图如图4所示。
图4 Java规则引擎工作机制
当引擎执行时,会根据规则执行队列中的优先顺序逐条执行规则执行实例,由于规则的执行部分可能会改变工作区的数据对象,从而会使队列中的某些规则执行实例因为条件改变而失效,必须从队列中撤销,也可能会激活原来不满足条件的规则,生成新的规则执行实例进入队列。于是就产生了一种“动态”的规则执行链,形成规则的推理机制。这种规则的“链式”反应完全是由工作区中的数据驱动的。
任何一个规则引擎都需要很好地解决规则的推理机制和规则条件匹配的效率问题。规则条件匹配的效率决定了引擎的性能,引擎需要迅速测试工作区中的数据对象,从加载的规则集中发现符合条件的规则,生成规则执行实例。1982年美国卡耐基·梅隆大学的Charles L. Forgy发明了一种叫Rete算法,很好地解决了这方面的问题。目前世界顶尖的商用业务规则引擎产品基本上都使用Rete算法。
Java规则引擎API——JSR-94
为了使规则引擎技术标准化,Java社区制定了Java规则引擎API(JSR94)规范。它为Java平台访问规则引擎定义了一些简单的API。
Java规则引擎API在javax.rules包中定义,是访问规则引擎的标准企业级API。Java规则引擎API允许客户程序使用统一的方式和不同厂商的规则引擎产品交互,就如同使用JDBC编写独立于厂商访问不同的数据库产品一样。Java规则引擎API包括创建和管理规则集合的机制,在工作区中添加,删除和修改对象的机制,以及初始化,重置和执行规则引擎的机制。
1、Java规则引擎API体系结构
Java规则引擎API主要由两大类API组成: 规则管理API(The Rules Administrator API)和运行时客户API(The Runtime Client API)。
1)规则管理API
规则管理API在javax.rules.admin中定义,包含装载规则以及与规则对应的动作(执行集 execution sets)以及实例化规则引擎。规则可以从外部资源中装载,比如URI,Input streams, XML streams和readers等等。同时规则管理API还提供了注册和取消注册执行集以及对执行集进行维护的机制。使用admin包定义规则有助于对客户访问运行规则进行控制管理,它通过在执行集上定义许可权使得未经授权的用户无法访问受控规则。
规则管理API使用类RuleServiceProvider来获得规则管理器(RuleAdministrator)接口的实例。该接口提供方法注册和取消注册执行集。规则管理器提供了本地和远程的RuleExecutionSetProvider,它负责创建规则执行集(RuleExecutionSet)。规则执行集可以从如XML streams, binary streams等来源中创建。这些数据来源及其内容经汇集和序列化后传送到远程的运行规则引擎的服务器上。在大多数应用程序中,远程规则引擎或远程规则数据来源的情况并不多。为了避免这些情况中的网络开销,API规定了可以从运行在同一JVM中规则库中读取数据的本地RuleExecutionSetProvider。规则执行集接口除了拥有能够获得有关规则执行集的方法,还有能够检索在规则执行集中定义的所有规则对象。这使得客户能够知道规则集中的规则对象并且按照自己需要来使用它们。
2)运行时客户API
运行时API在javax.rules包中定义,为规则引擎用户运行规则获得结果提供了类和方法。运行时客户只能访问那些使用规则管理API注册过的规则,运行时API帮助用户获得规则会话,并在这个会话中执行规则。
运行时API提供了对厂商规则引擎API的访问方法,这类似于JDBC。类RuleServiceProvider提供了对具体规则引擎实现的运行时和管理API的访问,规则引擎厂商通过该类将其规则引擎实现提供给客户,并获得RuleServiceProvider唯一标识规则引擎的URL。此URL的标准用法是使用类似于“com.mycompany.myrulesengine.rules.RuleServiceProvider”这样的Internet域名空间,这保证了访问URL的唯一性。类RuleServiceProvider内部实现了规则管理和运行时访问所需的接口。所有的RuleServiceProvider要想被客户所访问都必须用RuleServiceProviderManager进行注册,注册方式类似于JDBC API的DriverManager和Driver。
运行时接口是运行时API的关键部分。运行时接口提供了用于创建规则会话(RuleSession)的方法,规则会话是用来运行规则的。运行时API同时也提供了访问在service provider注册过的所有规则执行集(RuleExecutionSets)。规则会话接口定义了客户使用的会话的类型,客户根据自己运行规则的方式可以选择使用有状态会话或者无状态会话。无状态会话的工作方式就像一个无状态会话bean。客户可以发送单个输入对象或一列对象来获得输出对象。当客户需要一个与规则引擎间的专用会话时,有状态会话就很有用。输入的对象通过addObject() 方法可以加入到会话当中。同一个会话当中可以加入多个对象。对话中已有对象可以通过使用updateObject()方法得到更新。只要客户与规则引擎间的会话依然存在,会话中的对象就不会丢失。
RuleExecutionSetMetaData接口提供给客户让其查找规则执行集的元数据(metadata)。元数据通过规则会话接口(RuleSession Interface)提供给用户。
2、Java规则引擎API安全问题
规则引擎API将管理API和运行时API加以分开,从而为这些包提供了较好粒度的安全控制。规则引擎API并没有提供明显的安全机制,它可以和J2EE规范中定义的标准安全API联合使用。安全可以由以下机制提供,如Java 认证和授权服务 (JAAS),Java加密扩展(JCE),Java安全套接字扩展(JSSE),或者其它定制的安全API。使用JAAS可以定义规则执行集的许可权限,从而只有授权用户才能访问。
3、异常与日志
规则引擎API定义了javax.rules.RuleException作为规则引擎异常层次的根类。所有其它异常都继承于这个根类。规则引擎中定义的异常都是受控制的异常(checked exceptions),所以捕获异常的任务就交给了规则引擎。规则引擎API没有提供明确的日志机制,但是它建议将Java Logging API用于规则引擎API。
JSR 94 为规则引擎提供了公用标准API,仅仅为实现规则管理API和运行时API提供了指导规范,并没有提供规则和动作该如何定义以及该用什么语言定义规则,也没有为规则引擎如何读和评价规则提供技术性指导。
结束语
规则引擎技术为管理多变的业务逻辑提供了一种解决方案。规则引擎既可以管理应用层的业务逻辑又可以使表示层的页面流程可订制。这就给软件架构师设计大型信息系统提供了一项新的选择。而Java规则引擎在Java社区制定标准规范以后必将获得更大发展。
Drools规则引擎初学入门实例HelloWorld
(1)下载eclipse(www.eclipse.org),如果是一般的java开发,下载Eclipse IDE for Java Developers就行了,解压后即可使用;
(2)下载Drools(http://jboss.org/drools/downloads.html),目前最新版本是Drools 4.0.7 Binaries,下载后解压即可;
(3)之后下载eclipse的Drools插件,版本跟eclipse对应,目前有Drools 4.0.7 Eclipse 3.2 Workbench和Drools 4.0.7 Eclipse Europa 3.3 Workbench两种。
Drools插件解压后,将里面的org.drools.eclipse_4.0.7.jar文件copy到eclipse的plugins目录中,重启eclipse,在工具栏可以看到一个 图标,这就是Drools的工作台,之后就可通过这个按钮创建Drools resource文件了。
(4)开始Hello World
Java文件:DroolsTest.java
package com.sample;
import java.io.InputStreamReader;
import java.io.Reader;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.compiler.PackageBuilder;
import org.drools.rule.Package;
/**
* This is a sample file to launch a rule package from a rule source file.
*/
public class DroolsTest {
public static final void main(String[] args) {
try {
//load up the rulebase
RuleBase ruleBase = readRule();
WorkingMemory workingMemory = ruleBase.newStatefulSession();
//go !
Message message = new Message();
message.setMessage( "Hello World" );
message.setStatus( Message.HELLO );
workingMemory.insert( message );
workingMemory.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* Please note that this is the "low level" rule assembly API.
*/
private static RuleBase readRule() throws Exception {
//read in the source
Reader source = new InputStreamReader( DroolsTest.class.getResourceAsStream( "/Sample.drl" ) );
//optionally read in the DSL (if you are using it).
//Reader dsl = new InputStreamReader( DroolsTest.class.getResourceAsStream( "/mylang.dsl" ) );
//Use package builder to build up a rule package.
//An alternative lower level class called "DrlParser" can also be used...
PackageBuilder builder = new PackageBuilder();
//this wil parse and compile in one step
//NOTE: There are 2 methods here, the one argument one is for normal DRL.
builder.addPackageFromDrl( source );
//Use the following instead of above if you are using a DSL:
//builder.addPackageFromDrl( source, dsl );
//get the compiled package (which is serializable)
Package pkg = builder.getPackage();
//add the package to a rulebase (deploy the rule package).
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage( pkg );
return ruleBase;
}
public static class Message {
public static final int HELLO = 0;
public static final int GOODBYE = 1;
public static final int GAME_OVER = 2;
private String message;
private int status;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public int getStatus() {
return this.status;
}
public void setStatus( int status ) {
this.status = status;
}
}
}
选择插件Drools按钮里的"New Rule resource",建立规则(rule)文件:Sample.drl
package com.sample
import com.sample.DroolsTest.Message;
rule "Hello World"
when
m : Message( status == Message.HELLO, message : message )
then
System.out.println( message );
m.setMessage( "Goodbye cruel world" );
m.setStatus( Message.GOODBYE );
update( m );
end
rule "GoodBye"
no-loop true
when
m : Message( status == Message.GOODBYE, message : message )
then
System.out.println( message );
m.setStatus(Message.GAME_OVER);
m.setMessage("game over now!");
update( m );
end
rule "game over"
when
m : Message( status == Message.GAME_OVER)
then
System.out.println( m.getMessage() );
end
注意:文件要放在相应的包里,然后编译—执行,当时出现了错误,查找资料,还需要加载包,包括:
<1> Drools 4.0.7目录下的drools-core-4.0.7.jar,drools-compiler-4.0.7.jar
<2> Drools 4.0.7\lib目录下的antlr-runtime-3.0.jar,mvel-1.3.1-java1.4.jar
<3>以及eclipse\plugins目录下的org.eclipse.jdt.core_3.2.3.v_686_R32x.jar(不同版本,包名会稍有不同)。
重新运行,应该就不会有错了。执行结果如下:
Hello World
Goodbye cruel world
game over now!
Oracle JOB 间隔时间详解
INTERVAL参数设置:
每天运行一次 'SYSDATE + 1'
每小时运行一次 'SYSDATE + 1/24'
每10分钟运行一次 'SYSDATE + 10/(60*24)'
每30秒运行一次 'SYSDATE + 30/(60*24*60)'
每隔一星期运行一次 'SYSDATE + 7'
每个月最后一天运行一次 'TRUNC(LAST_DAY(ADD_MONTHS(SYSDATE,1))) + 23/24'
每年1月1号零时 'TRUNC(LAST_DAY(TO_DATE(EXTRACT(YEAR from SYSDATE)||'12'||'01','YYYY-MM-DD'))+1)'
每天午夜12点 'TRUNC(SYSDATE + 1)'
每天早上8点30分 'TRUNC(SYSDATE + 1) + (8*60+30)/(24*60)'
每星期二中午12点 'NEXT_DAY(TRUNC(SYSDATE ), ''TUESDAY'' ) + 12/24'
每个月第一天的午夜12点 'TRUNC(LAST_DAY(SYSDATE ) + 1)'
每个月最后一天的23点 'TRUNC (LAST_DAY (SYSDATE)) + 23 / 24'
每个季度最后一天的晚上11点 'TRUNC(ADD_MONTHS(SYSDATE + 2/24, 3 ), 'Q' ) -1/24'
每星期六和日早上6点10分 'TRUNC(LEAST(NEXT_DAY(SYSDATE, ''S ......
- 堆大小设置
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
典型设置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
- java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
- 回收器选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。
- 吞吐量优先的并行收集器
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
典型配置:
- java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
- 响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
- 辅助信息
JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
- -XX:+PrintGC
输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
- -XX:+PrintGCDetails
输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
- -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
- -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
输出形式:Application time: 0.5291524 seconds
- -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
输出形式:Total time for which application threads were stopped: 0.0468229 seconds
- -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
输出形式:
34.702: [GC {Heap before gc invocations=7:
def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]
- -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
- 常见配置汇总
- 堆设置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置持久代大小
- 收集器设置
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
- 垃圾回收统计信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
- 并行收集器设置
- -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
- -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
- -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
- 并发收集器设置
- -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
四、调优总结
- 年轻代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
- 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
- 年老代大小选择
- 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息
- 持久代并发收集次数
- 传统GC信息
- 花在年轻代和年老代回收上的时间比例
减少年轻代和年老代花费的时间,一般会提高应用的效率
- 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
- 较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
- -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
- -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
数据类型
Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用类型包括:类类型,接口类型和数组。
堆与栈
堆和栈是程序运行的关键,很有必要把他们的关系说清楚。
栈是运行时的单位,而堆是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。
为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?
第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。
第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。
第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。
在Java中,Main函数就是栈的起始点,也是程序的起始点。
程序要运行总是有一个起点的。同C语言一样,java中的Main就是那个起点。无论什么java程序,找到main就找到了程序执行的入口:)
堆中存什么?栈中存什么?
堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。
为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。
Java中的参数传递时传值呢?还是传引用?
要说明这个问题,先要明确两点:
1. 不要试图与C进行类比,Java中没有指针的概念
2. 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。
明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。
但是传引用的错觉是如何造成的呢?在运行栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。
对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。
堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为可能。
Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时栈中保存的信息都是方法返回的记录点。
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
若要提高效率,可以考虑全文检索。
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
13.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21.避免频繁创建和删除临时表,以减少系统表资源的消耗。
22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免大事务操作,提高系统并发能力。
30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
在很多大型应用中都会对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据
存储在哪个数据库实例中,以及从哪个数据库提取数据。
Figure 1 数据分割及多数据库架构
通常这种多数据源的逻辑会渗透到业务逻辑中,同时也会给我们使用的数据访问API诸如Hibernate和iBatis等带来不便(需要指定多个SessionFactory或SqlMapClient实例来对应多个DataSource)。
Figure 2 多数据源的选择逻辑渗透至客户端
解决方案
Figure 3 采用Proxy模式来封装数据源选择逻辑
通过采用Proxy模式我们在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。
Client提供选择所需的上下文(因为这是Client所知道的),由虚拟的DataSource根据Client提供的上下文来实现数据源的选择。
Spring2.x的版本中提供了实现这种方式的基本框架,虚拟的DataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey()在其中封装数据源的选择逻辑。
实例:
publicclass DynamicDataSource extends AbstractRoutingDataSource {
static Logger log = Logger.getLogger("DynamicDataSource");
@Override
protected Object determineCurrentLookupKey() {
String userId=(String)DbContextHolder.getContext();
Integer dataSourceId=getDataSourceIdByUserId(userId);
return dataSourceId;
}
}
|
实例中通过UserId来决定数据存放在哪个数据库中。
配置文件示例:
<bean id="dataSource" class="com.bitfone.smartdm.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="0" value-ref="dataSource0"/>
<entry key="1" value-ref="dataSource1"/>
<entry key="2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource0"/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:com/bitfone/smartdm/dao/sqlmap/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserInfoDAO" class="com.bitfone.smartdm.dao.impl.UserInfoDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
|
摘要:
通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池。
数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连...
阅读全文
1.所需包
(1) jfreechart-1.0.8a.jar
(2) jcommon-1.0.12.jar
2.运行环境
JDK 1.5
3.源代码
import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.io.FileOutputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.StackedBarRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
/**
* 实际取色的时候一定要16位的,这样比较准确
*
* @author new
*/
public class CreateChartServiceImpl
{
private static final String CHART_PATH = "E:/test/";
public static void main(String[] args)
{
// TODO Auto-generated method stub
CreateChartServiceImpl pm = new CreateChartServiceImpl();
// 生成饼状图
pm.makePieChart();
// 生成单组柱状图
pm.makeBarChart();
// 生成多组柱状图
pm.makeBarGroupChart();
// 生成堆积柱状图
pm.makeStackedBarChart();
// 生成折线图
pm.makeLineAndShapeChart();
}
/**
* 生成折线图
*/
public void makeLineAndShapeChart()
{
double[][] data = new double[][]
{
{ 672, 766, 223, 540, 126 },
{ 325, 521, 210, 340, 106 },
{ 332, 256, 523, 240, 526 } };
String[] rowKeys =
{ "苹果", "梨子", "葡萄" };
String[] columnKeys =
{ "北京", "上海", "广州", "成都", "深圳" };
CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
createTimeXYChar("折线图", "x轴", "y轴", dataset, "lineAndShap.png");
}
/**
* 生成分组的柱状图
*/
public void makeBarGroupChart()
{
double[][] data = new double[][]
{
{ 672, 766, 223, 540, 126 },
{ 325, 521, 210, 340, 106 },
{ 332, 256, 523, 240, 526 } };
String[] rowKeys =
{ "苹果", "梨子", "葡萄" };
String[] columnKeys =
{ "北京", "上海", "广州", "成都", "深圳" };
CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
createBarChart(dataset, "x坐标", "y坐标", "柱状图", "barGroup.png");
}
/**
* 生成柱状图
*/
public void makeBarChart()
{
double[][] data = new double[][]
{
{ 672, 766, 223, 540, 126 } };
String[] rowKeys =
{ "苹果" };
String[] columnKeys =
{ "北京", "上海", "广州", "成都", "深圳" };
CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
createBarChart(dataset, "x坐标", "y坐标", "柱状图", "bar.png");
}
/**
* 生成堆栈柱状图
*/
public void makeStackedBarChart()
{
double[][] data = new double[][]
{
{ 0.21, 0.66, 0.23, 0.40, 0.26 },
{ 0.25, 0.21, 0.10, 0.40, 0.16 } };
String[] rowKeys =
{ "苹果", "梨子" };
String[] columnKeys =
{ "北京", "上海", "广州", "成都", "深圳" };
CategoryDataset dataset = getBarData(data, rowKeys, columnKeys);
createStackedBarChart(dataset, "x坐标", "y坐标", "柱状图", "stsckedBar.png");
}
/**
* 生成饼状图
*/
public void makePieChart()
{
double[] data =
{ 9, 91 };
String[] keys =
{ "失败率", "成功率" };
createValidityComparePimChar(getDataPieSetByUtil(data, keys), "饼状图",
"pie2.png", keys);
}
// 柱状图,折线图 数据集
public CategoryDataset getBarData(double[][] data, String[] rowKeys,
String[] columnKeys)
{
return DatasetUtilities
.createCategoryDataset(rowKeys, columnKeys, data);
}
// 饼状图 数据集
public PieDataset getDataPieSetByUtil(double[] data,
String[] datadescription)
{
if (data != null && datadescription != null)
{
if (data.length == datadescription.length)
{
DefaultPieDataset dataset = new DefaultPieDataset();
for (int i = 0; i < data.length; i++)
{
dataset.setValue(datadescription[i], data[i]);
}
return dataset;
}
}
return null;
}
/**
* 柱状图
*
*@param dataset 数据集
* @param xName x轴的说明(如种类,时间等)
* @param yName y轴的说明(如速度,时间等)
* @param chartTitle 图标题
* @param charName 生成图片的名字
* @return
*/
public String createBarChart(CategoryDataset dataset, String xName,
String yName, String chartTitle, String charName)
{
JFreeChart chart = ChartFactory.createBarChart(chartTitle, // 图表标题
xName, // 目录轴的显示标签
yName, // 数值轴的显示标签
dataset, // 数据集
PlotOrientation.VERTICAL, // 图表方向:水平、垂直
true, // 是否显示图例(对于简单的柱状图必须是false)
false, // 是否生成工具
false // 是否生成URL链接
);
Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
/*
* VALUE_TEXT_ANTIALIAS_OFF表示将文字的抗锯齿关闭,
* 使用的关闭抗锯齿后,字体尽量选择12到14号的宋体字,这样文字最清晰好看
*/
// chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
chart.setTextAntiAlias(false);
chart.setBackgroundPaint(Color.white);
// create plot
CategoryPlot plot = chart.getCategoryPlot();
// 设置横虚线可见
plot.setRangeGridlinesVisible(true);
// 虚线色彩
plot.setRangeGridlinePaint(Color.gray);
// 数据轴精度
NumberAxis vn = (NumberAxis) plot.getRangeAxis();
// vn.setAutoRangeIncludesZero(true);
DecimalFormat df = new DecimalFormat("#0.00");
vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式
// x轴设置
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setLabelFont(labelFont);// 轴标题
domainAxis.setTickLabelFont(labelFont);// 轴数值
// Lable(Math.PI/3.0)度倾斜
// domainAxis.setCategoryLabelPositions(CategoryLabelPositions
// .createUpRotationLabelPositions(Math.PI / 3.0));
domainAxis.setMaximumCategoryLabelWidthRatio(0.6f);// 横轴上的 Lable 是否完整显示
// 设置距离图片左端距离
domainAxis.setLowerMargin(0.1);
// 设置距离图片右端距离
domainAxis.setUpperMargin(0.1);
// 设置 columnKey 是否间隔显示
// domainAxis.setSkipCategoryLabelsToFit(true);
plot.setDomainAxis(domainAxis);
// 设置柱图背景色(注意,系统取色的时候要使用16位的模式来查看颜色编码,这样比较准确)
plot.setBackgroundPaint(new Color(255, 255, 204));
// y轴设置
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setLabelFont(labelFont);
rangeAxis.setTickLabelFont(labelFont);
// 设置最高的一个 Item 与图片顶端的距离
rangeAxis.setUpperMargin(0.15);
// 设置最低的一个 Item 与图片底端的距离
rangeAxis.setLowerMargin(0.15);
plot.setRangeAxis(rangeAxis);
BarRenderer renderer = new BarRenderer();
// 设置柱子宽度
renderer.setMaximumBarWidth(0.05);
// 设置柱子高度
renderer.setMinimumBarLength(0.2);
// 设置柱子边框颜色
renderer.setBaseOutlinePaint(Color.BLACK);
// 设置柱子边框可见
renderer.setDrawBarOutline(true);
// // 设置柱的颜色
renderer.setSeriesPaint(0, new Color(204, 255, 255));
renderer.setSeriesPaint(1, new Color(153, 204, 255));
renderer.setSeriesPaint(2, new Color(51, 204, 204));
// 设置每个地区所包含的平行柱的之间距离
renderer.setItemMargin(0.0);
// 显示每个柱的数值,并修改该数值的字体属性
renderer.setIncludeBaseInRange(true);
renderer
.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setBaseItemLabelsVisible(true);
plot.setRenderer(renderer);
// 设置柱的透明度
plot.setForegroundAlpha(1.0f);
FileOutputStream fos_jpg = null;
try
{
isChartPathExist(CHART_PATH);
String chartName = CHART_PATH + charName;
fos_jpg = new FileOutputStream(chartName);
ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 500, true, 10);
return chartName;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try
{
fos_jpg.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* 横向图
*
* @param dataset 数据集
* @param xName x轴的说明(如种类,时间等)
* @param yName y轴的说明(如速度,时间等)
* @param chartTitle 图标题
* @param charName 生成图片的名字
* @return
*/
public String createHorizontalBarChart(CategoryDataset dataset,
String xName, String yName, String chartTitle, String charName)
{
JFreeChart chart = ChartFactory.createBarChart(chartTitle, // 图表标题
xName, // 目录轴的显示标签
yName, // 数值轴的显示标签
dataset, // 数据集
PlotOrientation.VERTICAL, // 图表方向:水平、垂直
true, // 是否显示图例(对于简单的柱状图必须是false)
false, // 是否生成工具
false // 是否生成URL链接
);
CategoryPlot plot = chart.getCategoryPlot();
// 数据轴精度
NumberAxis vn = (NumberAxis) plot.getRangeAxis();
//设置刻度必须从0开始
// vn.setAutoRangeIncludesZero(true);
DecimalFormat df = new DecimalFormat("#0.00");
vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45); // 横轴上的
// Lable
Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
domainAxis.setLabelFont(labelFont);// 轴标题
domainAxis.setTickLabelFont(labelFont);// 轴数值
domainAxis.setMaximumCategoryLabelWidthRatio(0.8f);// 横轴上的 Lable 是否完整显示
// domainAxis.setVerticalCategoryLabels(false);
plot.setDomainAxis(domainAxis);
ValueAxis rangeAxis = plot.getRangeAxis();
// 设置最高的一个 Item 与图片顶端的距离
rangeAxis.setUpperMargin(0.15);
// 设置最低的一个 Item 与图片底端的距离
rangeAxis.setLowerMargin(0.15);
plot.setRangeAxis(rangeAxis);
BarRenderer renderer = new BarRenderer();
// 设置柱子宽度
renderer.setMaximumBarWidth(0.03);
// 设置柱子高度
renderer.setMinimumBarLength(30);
renderer.setBaseOutlinePaint(Color.BLACK);
// 设置柱的颜色
renderer.setSeriesPaint(0, Color.GREEN);
renderer.setSeriesPaint(1, new Color(0, 0, 255));
// 设置每个地区所包含的平行柱的之间距离
renderer.setItemMargin(0.5);
// 显示每个柱的数值,并修改该数值的字体属性
renderer
.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
// 设置柱的数值可见
renderer.setBaseItemLabelsVisible(true);
plot.setRenderer(renderer);
// 设置柱的透明度
plot.setForegroundAlpha(0.6f);
FileOutputStream fos_jpg = null;
try
{
isChartPathExist(CHART_PATH);
String chartName = CHART_PATH + charName;
fos_jpg = new FileOutputStream(chartName);
ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 500, true, 10);
return chartName;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try
{
fos_jpg.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* 饼状图
*
* @param dataset 数据集
* @param chartTitle 图标题
* @param charName 生成图的名字
* @param pieKeys 分饼的名字集
* @return
*/
public String createValidityComparePimChar(PieDataset dataset,
String chartTitle, String charName, String[] pieKeys)
{
JFreeChart chart = ChartFactory.createPieChart3D(chartTitle, // chart
// title
dataset,// data
true,// include legend
true, false);
// 使下说明标签字体清晰,去锯齿类似于
// chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);的效果
chart.setTextAntiAlias(false);
// 图片背景色
chart.setBackgroundPaint(Color.white);
// 设置图标题的字体重新设置title
Font font = new Font("隶书", Font.BOLD, 25);
TextTitle title = new TextTitle(chartTitle);
title.setFont(font);
chart.setTitle(title);
PiePlot3D plot = (PiePlot3D) chart.getPlot();
// 图片中显示百分比:默认方式
// 指定饼图轮廓线的颜色
// plot.setBaseSectionOutlinePaint(Color.BLACK);
// plot.setBaseSectionPaint(Color.BLACK);
// 设置无数据时的信息
plot.setNoDataMessage("无对应的数据,请重新查询。");
// 设置无数据时的信息显示颜色
plot.setNoDataMessagePaint(Color.red);
// 图片中显示百分比:自定义方式,{0} 表示选项, {1} 表示数值, {2} 表示所占比例 ,小数点后两位
plot.setLabelGenerator(new StandardPieSectionLabelGenerator(
"{0}={1}({2})", NumberFormat.getNumberInstance(),
new DecimalFormat("0.00%")));
// 图例显示百分比:自定义方式, {0} 表示选项, {1} 表示数值, {2} 表示所占比例
plot.setLegendLabelGenerator(new StandardPieSectionLabelGenerator(
"{0}={1}({2})"));
plot.setLabelFont(new Font("SansSerif", Font.TRUETYPE_FONT, 12));
// 指定图片的透明度(0.0-1.0)
plot.setForegroundAlpha(0.65f);
// 指定显示的饼图上圆形(false)还椭圆形(true)
plot.setCircular(false, true);
// 设置第一个 饼块section 的开始位置,默认是12点钟方向
plot.setStartAngle(90);
// // 设置分饼颜色
plot.setSectionPaint(pieKeys[0], new Color(244, 194, 144));
plot.setSectionPaint(pieKeys[1], new Color(144, 233, 144));
FileOutputStream fos_jpg = null;
try
{
// 文件夹不存在则创建
isChartPathExist(CHART_PATH);
String chartName = CHART_PATH + charName;
fos_jpg = new FileOutputStream(chartName);
// 高宽的设置影响椭圆饼图的形状
ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 230);
return chartName;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try
{
fos_jpg.close();
System.out.println("create pie-chart.");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* 判断文件夹是否存在,如果不存在则新建
* @param chartPath
*/
private void isChartPathExist(String chartPath)
{
File file = new File(chartPath);
if (!file.exists())
{
file.mkdirs();
// log.info("CHART_PATH="+CHART_PATH+"create.");
}
}
/**
* 折线图
*
* @param chartTitle
* @param x
* @param y
* @param xyDataset
* @param charName
* @return
*/
public String createTimeXYChar(String chartTitle, String x, String y,
CategoryDataset xyDataset, String charName)
{
JFreeChart chart = ChartFactory.createLineChart(chartTitle, x, y,
xyDataset, PlotOrientation.VERTICAL, true, true, false);
chart.setTextAntiAlias(false);
chart.setBackgroundPaint(Color.WHITE);
// 设置图标题的字体重新设置title
Font font = new Font("隶书", Font.BOLD, 25);
TextTitle title = new TextTitle(chartTitle);
title.setFont(font);
chart.setTitle(title);
// 设置面板字体
Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
chart.setBackgroundPaint(Color.WHITE);
CategoryPlot categoryplot = (CategoryPlot) chart.getPlot();
// x轴 // 分类轴网格是否可见
categoryplot.setDomainGridlinesVisible(true);
// y轴 //数据轴网格是否可见
categoryplot.setRangeGridlinesVisible(true);
categoryplot.setRangeGridlinePaint(Color.WHITE);// 虚线色彩
categoryplot.setDomainGridlinePaint(Color.WHITE);// 虚线色彩
categoryplot.setBackgroundPaint(Color.lightGray);
// 设置轴和面板之间的距离
// categoryplot.setAxisOffset(new RectangleInsets(5D, 5D, 5D, 5D));
CategoryAxis domainAxis = categoryplot.getDomainAxis();
domainAxis.setLabelFont(labelFont);// 轴标题
domainAxis.setTickLabelFont(labelFont);// 轴数值
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45); // 横轴上的
// Lable
// 45度倾斜
// 设置距离图片左端距离
domainAxis.setLowerMargin(0.0);
// 设置距离图片右端距离
domainAxis.setUpperMargin(0.0);
NumberAxis numberaxis = (NumberAxis) categoryplot.getRangeAxis();
numberaxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
numberaxis.setAutoRangeIncludesZero(true);
// 获得renderer 注意这里是下嗍造型到lineandshaperenderer!!
LineAndShapeRenderer lineandshaperenderer = (LineAndShapeRenderer) categoryplot
.getRenderer();
lineandshaperenderer.setBaseShapesVisible(true); // series 点(即数据点)可见
lineandshaperenderer.setBaseLinesVisible(true); // series 点(即数据点)间有连线可见
// 显示折点数据
// lineandshaperenderer.setBaseItemLabelGenerator(new
// StandardCategoryItemLabelGenerator());
// lineandshaperenderer.setBaseItemLabelsVisible(true);
FileOutputStream fos_jpg = null;
try
{
isChartPathExist(CHART_PATH);
String chartName = CHART_PATH + charName;
fos_jpg = new FileOutputStream(chartName);
// 将报表保存为png文件
ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 510);
return chartName;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try
{
fos_jpg.close();
System.out.println("create time-createTimeXYChar.");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* 堆栈柱状图
*
* @param dataset
* @param xName
* @param yName
* @param chartTitle
* @param charName
* @return
*/
public String createStackedBarChart(CategoryDataset dataset, String xName,
String yName, String chartTitle, String charName)
{
// 1:得到 CategoryDataset
// 2:JFreeChart对象
JFreeChart chart = ChartFactory.createStackedBarChart(chartTitle, // 图表标题
xName, // 目录轴的显示标签
yName, // 数值轴的显示标签
dataset, // 数据集
PlotOrientation.VERTICAL, // 图表方向:水平、垂直
true, // 是否显示图例(对于简单的柱状图必须是false)
false, // 是否生成工具
false // 是否生成URL链接
);
// 图例字体清晰
chart.setTextAntiAlias(false);
chart.setBackgroundPaint(Color.WHITE);
// 2 .2 主标题对象 主标题对象是 TextTitle 类型
chart
.setTitle(new TextTitle(chartTitle, new Font("隶书", Font.BOLD,
25)));
// 2 .2.1:设置中文
// x,y轴坐标字体
Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
// 2 .3 Plot 对象 Plot 对象是图形的绘制结构对象
CategoryPlot plot = chart.getCategoryPlot();
// 设置横虚线可见
plot.setRangeGridlinesVisible(true);
// 虚线色彩
plot.setRangeGridlinePaint(Color.gray);
// 数据轴精度
NumberAxis vn = (NumberAxis) plot.getRangeAxis();
// 设置最大值是1
vn.setUpperBound(1);
// 设置数据轴坐标从0开始
// vn.setAutoRangeIncludesZero(true);
// 数据显示格式是百分比
DecimalFormat df = new DecimalFormat("0.00%");
vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式
// DomainAxis (区域轴,相当于 x 轴), RangeAxis (范围轴,相当于 y 轴)
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setLabelFont(labelFont);// 轴标题
domainAxis.setTickLabelFont(labelFont);// 轴数值
// x轴坐标太长,建议设置倾斜,如下两种方式选其一,两种效果相同
// 倾斜(1)横轴上的 Lable 45度倾斜
// domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
// 倾斜(2)Lable(Math.PI 3.0)度倾斜
// domainAxis.setCategoryLabelPositions(CategoryLabelPositions
// .createUpRotationLabelPositions(Math.PI / 3.0));
domainAxis.setMaximumCategoryLabelWidthRatio(0.6f);// 横轴上的 Lable 是否完整显示
plot.setDomainAxis(domainAxis);
// y轴设置
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setLabelFont(labelFont);
rangeAxis.setTickLabelFont(labelFont);
// 设置最高的一个 Item 与图片顶端的距离
rangeAxis.setUpperMargin(0.15);
// 设置最低的一个 Item 与图片底端的距离
rangeAxis.setLowerMargin(0.15);
plot.setRangeAxis(rangeAxis);
// Renderer 对象是图形的绘制单元
StackedBarRenderer renderer = new StackedBarRenderer();
// 设置柱子宽度
renderer.setMaximumBarWidth(0.05);
// 设置柱子高度
renderer.setMinimumBarLength(0.1);
//设置柱的边框颜色
renderer.setBaseOutlinePaint(Color.BLACK);
//设置柱的边框可见
renderer.setDrawBarOutline(true);
// // 设置柱的颜色(可设定也可默认)
renderer.setSeriesPaint(0, new Color(204, 255, 204));
renderer.setSeriesPaint(1, new Color(255, 204, 153));
// 设置每个地区所包含的平行柱的之间距离
renderer.setItemMargin(0.4);
plot.setRenderer(renderer);
// 设置柱的透明度(如果是3D的必须设置才能达到立体效果,如果是2D的设置则使颜色变淡)
// plot.setForegroundAlpha(0.65f);
FileOutputStream fos_jpg = null;
try
{
isChartPathExist(CHART_PATH);
String chartName = CHART_PATH + charName;
fos_jpg = new FileOutputStream(chartName);
ChartUtilities.writeChartAsPNG(fos_jpg, chart, 500, 500, true, 10);
return chartName;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try
{
fos_jpg.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
很高兴看到阿里云的成立。这意味着阿里已经把对互联网技术的投入提高到了的战略高度。过去经常听工程师抱怨阿里不是一家技术公司。现在再没有理由可以这样抱怨了。但是要实现这个战略,没有技术储备是不行的。招聘和培养工程师显然是目前集团各子公司同时面临的一个令人头痛的难题。
由于曾经在硅谷工作过,我常想,为什么硅谷有这么多40岁以上的工程师,而国内30岁以上的就已经寥寥无几了?为什么硅谷的工程师的技术寿命可以这么长?为什么他们可以不浮躁,不急功近利呢?阿里要走102年,阿里的工程师可以一起走多远呢?
在国内,有2-3年工作经历的工程师就可以算有经验的了。工作了5年以上的工程师往往会考虑向管理岗位转型,或者向业务转型。中国目前处于高度发展的阶段。很多企业缺乏管理人才,工作5年就被提吧为干部很正常。但留下的后遗症是30岁以上的优秀技术人才极度缺乏。
在硅谷,5年以下工作经验的人都算是初级的。一般高级工程师需要5年以上的工作经验,架构师一般需要10年以上的工作经验。这还不算上大部分硅谷的工程师都有计算机硕士学位。毕业的时候一般已经是24,25岁了。再工作10年,35岁才升为架构师是非常正常的。然而,公司里的架构师有限。其实大部分 40岁的工程师仍然在一线工作,比如写程序,做测试,进行项目管理等。
美国硅谷是计算机人才集中的地方,也是创业公司群集的地方。在硅谷,从只有几个人到几十个人的创业公司比比皆是。他们的共同梦想就是经过几年的奋斗,通过技术的创新,再次缔造像英特尔,苹果,思科,甲骨文,雅虎,Google,Facebook等这样的神话。即使创造不了神话,也可以通过IPO或者被收购的途径创造财富。在这样的环境中,公司对管理人才的需求同样是非常大的,但为什么仍然有大量的工程师“无动于衷”,仍然从事着技术活儿呢?
我认为有两个主要原因。
一个是外因。在美国,管理岗位的待遇和技术岗位待遇相差不大。特别在崇尚技术的硅谷,经理的地位并不比工程师高,甚至更低。比如架构师在公司里的重要性往往要超过经理。因此管理岗位的“诱惑”并不大。在这样一种技术氛围中,走技术路线很正常。
但是即使在这样一个技术环境中,硅谷对管理人才依然需要。当工程师表现出色时,也有很多机会转成管理岗位。然而相当一部分工程师会主动放弃这样的机会,而继续干他们的技术活儿。这就是内因在驱动了。技术工作和管理工作的本质区别是,前者面对的是系统(软件,硬件等),而后者面对的是人。系统问题再难,只要有足够的时间和资源,一般都可以解决。越难的问题,解决之后越有成就感。而人的问题,有时候看似很简单,却解决不了。是人,总要有头疼脑热,生病的时候。是人,免不了产生情绪,从而影响工作。有人的地方,就会有矛盾,就会有摩擦。简单地讲,系统会按照事先设定的逻辑运行,是死的,因此往往可控,可规划。而人是活的,不是输入几条命令就可以控制的,而是需要沟通,需要感情的。因此,大部分硅谷的工程师很“聪明”。他们主动选择“简单”地工作。白天好好地工作,晚上好好地生活。何必去“自寻烦恼”,转做管理呢。
其实不光是硅谷的,其它地区的工程师都有一个共同的性格特点,追求简单,追求完美,思维方式上比较理性和逻辑性,看问题比较趋向于非黑即白。这样的性格非常适合做技术工作,可是我们中国的工程师有时候偏偏看不到自己的这个特点。
不想当元帅的士兵不是好士兵。工程师希望向管理方向发展是非常正常的。但问题是为什么和怎样?我碰到过不只一个工程师告诉我,希望转做管理的原因是担心今后年级大了,技术能力跟不上了。我觉得非常可笑。这就好比是一个士兵说:我杀敌本领不行,不适合上战场,那就让我做军官吧。一个没做过士兵的元帅肯定不是好元帅。其实做技术和当兵毕竟不同,不是靠体力吃饭的。年级大点往往是优势。
我觉得走技术路线对工程师性格的人是一条捷径。如果能静下心来仔细钻研技术,一定能在某个方面做得比别人好。这里的关键是好奇心和耐心。在今天这样的信息时代,找到答案并不是一件难事。难就难在有没有好奇心和耐心去找。比如,Java程序员天天都用到String这个类型。但有没有想过为什么 Java语言里有String和StringBuffer两种字符串类型,而不是一种?有没有去看过String和StringBuffer的源代码?再例如,天天做网站和HTTP打交道,有没有看过HTTP协议?有没有尝试过不用浏览器,wget等工具,而用最原始的telnet方式来访问网站?看看这 HTTP的头里到底是什么东东?在我面试过的工程师中,做过这几件事的人不到5%。
一旦了解得比别人深,就容易看到问题本质,产生信心,激发乐趣。这时候你的解决方案就比别人漂亮,逐渐建立起了影响力,成为了“专家”。因此公司里的疑难杂症会主动找上门来。你就比别人得到了更多的解决问题的机会,从而更快地提升能力。一旦进入良性循环,你的进步就比别人快,但付出的却不一定比别人多。这时候你已经走上了捷径。
在技术人才极度缺乏的中国,在众人盲目追求管理岗位的那点虚荣的今天,如果你的性格是工程师类型的,走技术路线其实是非常适合的。如果你才毕业,那你是最幸福的。你可以给自己制定3个甚至4个五年计划。例如5年打基础,10年变专家,15年国内知名,20年世界闻名。如果你已经奔三或者三十出头,那你快成熟了,但离开花结果还早呢。不信你看看下面几位我们都熟悉的人。
拉里-沃尔(Larry Wall)33岁时出版了《Perl语言编程》一书。之前他是一个系统管理员。
互联网之父温特-瑟夫(Vint Cerf)在发明TCP/IP时,已经35岁。
万维网之父蒂姆·伯纳斯—李(Tim Berners-Lee)在37岁时才发明了万维网(WWW)。
丹尼斯-里奇(Dennis Ritchie)的《C程序设计语言》一书出版时,他37岁。
Java之父詹姆斯·戈士林(James Gosling)40岁时才因为发明Java而成名。
苹果公司创始人之一史蒂夫?沃兹尼艾克(Steven Wozniak)在今年年初以首席科学家的身份加入一家创业公司,研发基于高速闪存技术的存储。他如今已经59岁了。
本文来源:百度博客
Sql Server 中一个非常强大的日期格式化函数
Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AM
Select CONVERT(varchar(100), GETDATE(), 1): 05/16/06
Select CONVERT(varchar(100), GETDATE(), 2): 06.05.16
Select CONVERT(varchar(100), GETDATE(), 3): 16/05/06
Select CONVERT(varchar(100), GETDATE(), 4): 16.05.06
Select CONVERT(varchar(100), GETDATE(), 5): 16-05-06
Select CONVERT(varchar(100), GETDATE(), 6): 16 05 06
Select CONVERT(varchar(100), GETDATE(), 7): 05 16, 06
Select CONVERT(varchar(100), GETDATE(), 8): 10:57:46
Select CONVERT(varchar(100), GETDATE(), 9): 05 16 2006 10:57:46:827AM
Select CONVERT(varchar(100), GETDATE(), 10): 05-16-06
Select CONVERT(varchar(100), GETDATE(), 11): 06/05/16
Select CONVERT(varchar(100), GETDATE(), 12): 060516
Select CONVERT(varchar(100), GETDATE(), 13): 16 05 2006 10:57:46:937
Select CONVERT(varchar(100), GETDATE(), 14): 10:57:46:967
Select CONVERT(varchar(100), GETDATE(), 20): 2006-05-16 10:57:47
Select CONVERT(varchar(100), GETDATE(), 21): 2006-05-16 10:57:47.157
Select CONVERT(varchar(100), GETDATE(), 22): 05/16/06 10:57:47 AM
Select CONVERT(varchar(100), GETDATE(), 23): 2006-05-16
Select CONVERT(varchar(100), GETDATE(), 24): 10:57:47
Select CONVERT(varchar(100), GETDATE(), 25): 2006-05-16 10:57:47.250
Select CONVERT(varchar(100), GETDATE(), 100): 05 16 2006 10:57AM
Select CONVERT(varchar(100), GETDATE(), 101): 05/16/2006
Select CONVERT(varchar(100), GETDATE(), 102): 2006.05.16
Select CONVERT(varchar(100), GETDATE(), 103): 16/05/2006
Select CONVERT(varchar(100), GETDATE(), 104): 16.05.2006
Select CONVERT(varchar(100), GETDATE(), 105): 16-05-2006
Select CONVERT(varchar(100), GETDATE(), 106): 16 05 2006
Select CONVERT(varchar(100), GETDATE(), 107): 05 16, 2006
Select CONVERT(varchar(100), GETDATE(), 108): 10:57:49
Select CONVERT(varchar(100), GETDATE(), 109): 05 16 2006 10:57:49:437AM
Select CONVERT(varchar(100), GETDATE(), 110): 05-16-2006
Select CONVERT(varchar(100), GETDATE(), 111): 2006/05/16
Select CONVERT(varchar(100), GETDATE(), 112): 20060516
Select CONVERT(varchar(100), GETDATE(), 113): 16 05 2006 10:57:49:513
Select CONVERT(varchar(100), GETDATE(), 114): 10:57:49:547
Select CONVERT(varchar(100), GETDATE(), 120): 2006-05-16 10:57:49
Select CONVERT(varchar(100), GETDATE(), 121): 2006-05-16 10:57:49.700
Select CONVERT(varchar(100), GETDATE(), 126): 2006-05-16T10:57:49.827
Select CONVERT(varchar(100), GETDATE(), 130): 18 ???? ?????? 1427 10:57:49:907AM
Select CONVERT(varchar(100), GETDATE(), 131): 18/04/1427 10:57:49:920AM
DBMS_JOB系统包是Oracle“任务队列”子系统的API编程接口。DBMS_JOB包对于任务队列提供了下面这些功能:提交并且执行一个任务、改变任务的执行参数以及删除或者临时挂起任务等。
DBMS_JOB包是由ORACLE_HOME目录下的rdbms/admin子目录下的DBMSJOB.SQL和PRVTJOB.PLB 这两个脚本文件创建的。这两个文件被CATPROC.SQL脚本文件调用,而CATPROC.SQL这个文件一般是在数据库创建后立即执行的。脚本为DBMS_JOB包创建了一个公共同义词,并给该包授予了公共的可执行权限,所以所有的Oracle用户均可以使用这个包。
下面几个数据字典视图是关于任务队列信息的,主要有DBA_JOBS, USER_JOBS和DBA_JOBS_RUNNING。这些字典视图是由名为CATJOBQ.SQL的脚本文件创建的。该脚本文件和创建DBMS_JOB包的脚本文件一样在ORACLE_HOME目录的rdbms/admin子目录中,同样也是由脚本文件CATPROC.SQL调用。
最后,要使任务队列能正常运行,还必须启动它自己专有的后台过程。启动后台过程是通过在初始化文件init*.ora(实例不同,初始化文件名也略有不同)中设置初始化参数来进行的。下面就是该参数:
JOB_QUEUE_PROCESSES = n
其中,n可以是0到36之间的任何一个数。除了该参数以外,还有几个关于任务队列的初始化参数,本文后面将会对其进行详细讨论。
DBMS_JOB包中包含有许多过程,见表1所示。
表1 DBMS_JOB包
名称 |
类型 |
描述 |
DBMS_JOB.ISUBMIT |
过程 |
提交一个新任务,用户指定一个任务号 |
DBMS_JOB.SUBMIT |
过程 |
提交一个新任务,系统指定一个任务号 |
DBMS_JOB.REMOVE |
过程 |
从队列中删除一个已经存在的任务 |
DBMS_JOB.CHANGE |
过程 |
更改用户设定的任务参数 |
DBMS_JOB.WHAT |
过程 |
更改PL/SQL任务定义 |
DBMS_JOB.NEXT_DATE |
过程 |
更改任务下一次运行时间 |
DBMS_JOB.INTERVAL |
过程 |
更改任务运行的时间间隔 |
DBMS_JOB.BROKEN |
过程 |
将任务挂起,不让其重复运行 |
DBMS_JOB.RUN |
过程 |
在当前会话中立即执行任务 |
DBMS_JOB.USER_EXPORT |
过程 |
创建文字字符串,用于重新创建一个任务 |
三、DBMS_JOB包参数
DBMS_JOB包中所有的过程都有一组相同的公共参数,用于定义任务,任务的运行时间以及任务定时运行的时间间隔。这些公共任务定义参数见表2所示。
表2 DBMS_JOB过程的公共参数
名称 |
类型 |
注释 |
Job |
BINARY_INTEGER |
任务的唯一识别号 |
What |
VARCHAR2 |
作为任务执行的PL/SQL代码 |
Next_date |
VARCHAR2 |
任务下一次运行的时间 |
Interval |
VARCHAR2 |
日期表达式,用来计算下一次任务运行的时间 |
下面我们来详细讨论这些参数的意义及用法。
1、job
参数job是一个整数,用来唯一地标示一个任务。该参数既可由用户指定也可由系统自动赋予,这完全取决于提交任务时选用了那一个任务提交过程。DBMS_JOB.SUBMIT过程通过获得序列SYS.JOBSEQ的下一个值来自动赋予一个任务号。该任务号是作为一个OUT参数返回的,所以调用者随后可以识别出提交的任务。而DBMS_JOB.ISUBMIT过程则由调用者给任务指定一个识别号,这时候,任务号的唯一性就完全取决于调用者了。
除了删除或者重新提交任务,一般来说任务号是不能改变的。即使当数据库被导出或者被导入这样极端的情况,任务号也将被保留下来。所以在执行含有任务的数据的导入/导出操作时很可能会发生任务号冲突的现象。
2、what
what参数是一个可以转化为合法PL/SQL调用的字符串,该调用将被任务队列自动执行。在what参数中,如果使用文字字符串,则该字符串必须用单引号括起来。 what参数也可以使用包含我们所需要字符串值的VARCHAR2变量。实际的PL/SQL调用必须用分号隔开。在PL/SQL调用中如果要嵌入文字字符串,则必须使用两个单引号。
what参数的长度在Oracle7.3中限制在2000个字节以内,在Oracle 8.0以后,扩大到了4000个字节,这对于一般的应用已完全足够。该参数的值一般情况下都是对一个PL/SQL存储过程的调用。在实际应用中,尽管可以使用大匿名Pl/SQL块,但建议大家最好不要这样使用。还有一个实际经验就是最好将存储过程调用封装在一个匿名块中,这样可以避免一些比较莫名错误的产生。我来举一个例子,一般情况下,what参数可以这样引用:
what =>’my_procedure(parameter1);’ |
但是比较安全的引用,应该这样写:
what =>’begin my_procedure(parameter1); end;’ |
任何时候,我们只要通过更改what参数就可以达到更改任务定义的目的。但是有一点需要注意,通过改变what参数来改变任务定义时,用户当前的会话设置也被记录下来并成为任务运行环境的一部分。如果当前会话设置和最初提交任务时的会话设置不同,就有可能改变任务的运行行为。意识到这个潜在的副作用是非常重要的,无论何时只要应用到任何DBMS_JOB过程中的what参数时就一定要确保会话设置的正确。
3、next_date
Next_date参数是用来调度任务队列中该任务下一次运行的时间。这个参数对于DBMS_JOB.SUBMIT和DBMS_JOB.BROKEN这两个过程确省为系统当前时间,也就是说任务将立即运行。
当将一个任务的next_date参数赋值为null时,则该任务下一次运行的时间将被指定为4000年1月1日,也就是说该任务将永远不再运行。在大多数情况下,这可能是我们不愿意看到的情形。但是,换一个角度来考虑,如果想在任务队列中保留该任务而又不想让其运行,将next_date设置为null却是一个非常简单的办法。
Next_date也可以设置为过去的一个时间。这里要注意,系统任务的执行顺序是根据它们下一次的执行时间来确定的,于是将next_date参数设置回去就可以达到将该任务排在任务队列前面的目的。这在任务队列进程不能跟上将要执行的任务并且一个特定的任务需要尽快执行时是非常有用的。
4、Interval
Internal参数是一个表示Oracle合法日期表达式的字符串。这个日期字符串的值在每次任务被执行时算出,算出的日期表达式有两种可能,要么是未来的一个时间要么就是null。这里要强调一点:很多开发者都没有意识到next_date是在一个任务开始时算出的,而不是在任务成功完成时算出的。
当任务成功完成时,系统通过更新任务队列目录表将前面算出的next_date值置为下一次任务要运行的时间。当由interval表达式算出next_date是null时,任务自动从任务队列中移出,不会再继续执行。因此,如果传递一个null值给interval参数,则该任务仅仅执行一次。
通过给interval参数赋各种不同的值,可以设计出复杂运行时间计划的任务。本文后面的“任务间隔和日期算法”将对interval表达式进行详细讨论,并给出一个实际有用interval表达式的例子。
四、任务队列架构和运行环境
任务队列在Oracle系统中其实是一个子系统,它具有自己特定的后台过程和目录表。该子系统设计的目的是为了能不在用户干预下自动运行PL/SQL过程。
1、任务队列后台过程
任务队列(SNP)后台过程随着Oracle实例的启动而同时启动。在文章前面已经谈到初始化文件init.ora中的参数JOB_QUEUE_PROCESSES,用来设置有几个队列过程。这里设置了几个过程,系统中就会有几个SNP过程被启动。JOB_QUEUE_PROCESSES这个参数,可以是0到36中的任何一个数,也就是说对于每个Oracle实例最多可以有36个SNP过程,也可以不支持队列过程(=0)。在大多数操作系统中,SNP三个字母常作为过程名的一部分出现。如,在unix系统中,如果该Oracle实例名为ora8,有三个任务队列过程,则这三个任务队列过程名称为:
ora_ora8_snp0
ora_ora8_snp1
ora_ora8_snp2 |
SNP后台过程和其他的Oracle后台过程的一个重要区别就是杀掉一个SNP过程不会影响到Oracle实例。当一个任务队列过程失控或者消耗太多的资源时,就可以将其杀掉,当然这种情况不是经常遇到的。当一个SNP过程被杀掉或者失败时,Oracle就自动启动一个新的SNP过程来代替它。
2、有关任务队列的初始化参数
初始化文件init.ora中的几个参数控制着任务队列后台的运行,下面我们将对其进行详细讨论。
(1)、JOB_QUEUE_INTERVAL
任务队列过程定期唤醒并检查任务队列目录表是否有任务需要执行。参数JOB_QUEUE_INTERVAL决定SNP过程两次检查目录表之间“休眠”多长时间(单位为秒)。间隔设的太小会造成由于SNP过程不断检查目录表而导致不必要的系统吞吐量。相反如果间隔设得太大,SNP过程在特定的时间没有被唤醒,那个时间的任务就不会能被运行。最佳的时间间隔设置要综合考虑系统环境中不同的任务,60秒的确省设置可以满足大多数的应用。
(2)、JOB_QUEUE_KEEP_CONNECTIONS
除了前面介绍的JOB_QUEUE_PROCESS和JOB_QUEUE_INTERVAL两个参数以外,影响SNP后台过程行为的第三个参数是JOB_QUEUE_KEEP_CONNECTIONS。当该参数为TRUE时,SNP过程在两个任务的运行期间(也就是休眠期间),仍然和Oracle保持开放的连接。相反,如果为FALSE时,SNP过程将和数据库断开连接,当唤醒时刻到来时又重新连接并检查任务队列。
选择这两种方法中的那一种,主要是考虑任务队列的有效性和数据库关闭方法。长期保持连接的效率比较高,但任务队列会受到正常关闭数据库的影响。这是因为任务队列过程对于服务器管理器看来和一个普通用户的过程没有什么不同,而正常的关闭数据库需要让所有的用户都断开连接。而断开连接和重新连接又给数据库增加了负荷,但是可定期地使数据库没有可连接SNP过程,也就可以使数据库正常关闭。对于有很多任务或者是任务重复执行的时间间隔较短(一个小时或者更少)的环境,一般将JOB_QUEUE_KEEP_CONNECTIOONS设置为TRUE,并修改关闭数据库的脚本为立即关闭。对于严格要求采用正常方式关闭的数据库或者是任务较少,重复间隔较长的环境,一般将该参数设置为FALSE。最好,要提醒一句,SNP过程仅在没有任何任务运行时才断开,这种情况下,那些需要比较长时间运行的任务SNP将在它们的生命周期内一致保持开放的连接,这就延迟了正常关闭数据库的时间。
3、建立运行环境
当SNP过程唤醒时,它首先查看任务队列目录中所有的任务是否当前的时间超过了下一次运行的日期时间。SNP检测到需要该时间立即执行的任务后,这些任务按照下一次执行日期的顺序依次执行。当SNP过程开始执行一个任务时,其过程如下:
- 以任务所有者的用户名开始一个新的数据库会话。
- 当任务第一次提交或是最后一次被修改时,更改会话NLS设置和目前就绪的任务相匹配。
- 通过interval日期表达式和系统时间,计算下一次执行时间。
- 执行任务定义的PL/SQL
- 如果运行成功,任务的下一次执行日期(next_date)被更新,否则,失败计数加1。
- 经过JOB_QUEUS_INTERVAL秒后,又到了另一个任务的运行时间,重复上面的过程。
在前两步中,SNP过程创建了一个模仿用户运行任务定义的PL/SQL的会话环境。然而,这个模仿的运行环境并不是和用户实际会话环境完全一样,需要注意以下两点:第一,在任务提交时任何可用的非确省角色都将在任务运行环境中不可用。因此,那些想从非确省角色中取得权限的任务不能提交,用户确省角色的修改可以通过在任务未来运行期间动态修改来完成。第二,任何任务定义本身或者过程执行中需要的数据库联接都必须完全满足远程的用户名和密码。SNP过程不能在没有显式指明口令的情况下初始化一个远程会话。显然,SNP过程不能假定将本地用户的口令作为远程运行环境会话设置的一部分。
提交的任务如果运行失败会怎么样呢?当任务运行失败时,SNP过程在1分钟后将再次试图运行该任务。如果这次运行又失败了,下一次尝试将在2分钟后进行,再下一次在4分钟以后。任务队列每次加倍重试间隔直到它超过了正常的运行间隔。在连续16次失败后,任务就被标记为中断的(broken),如果没有用户干预,任务队列将不再重复执行。
五、任务队列字典表和视图
任务队列中的任务信息可以通过表3所示的几个字典视图来查看,这些视图是由CATJOBQ.sql脚本创建的。表4和5是各个视图每个字段的含义。
表3. 任务队列中关于任务的数据字典视图
视图名 |
描述 |
DBA_JOBS |
本数据库中定义到任务队列中的任务 |
DBA_JOBS_RUNNING |
目前正在运行的任务 |
USER_JOBS |
当前用户拥有的任务 |
表4. DBA_JOBS 和 USER_JOBS.字典视图的字段含义
字段(列) |
类型 |
描述 |
JOB |
NUMBER |
任务的唯一标示号 |
LOG_USER |
VARCHAR2(30) |
提交任务的用户 |
PRIV_USER |
VARCHAR2(30) |
赋予任务权限的用户 |
SCHEMA_USER |
VARCHAR2(30) |
对任务作语法分析的用户模式 |
LAST_DATE |
DATE |
最后一次成功运行任务的时间 |
LAST_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的last_date日期的小时,分钟和秒 |
THIS_DATE |
DATE |
正在运行任务的开始时间,如果没有运行任务则为null |
THIS_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的this_date日期的小时,分钟和秒 |
NEXT_DATE |
DATE |
下一次定时运行任务的时间 |
NEXT_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的next_date日期的小时,分钟和秒 |
TOTAL_TIME |
NUMBER |
该任务运行所需要的总时间,单位为秒 |
BROKEN |
VARCHAR2(1) |
标志参数,Y标示任务中断,以后不会运行 |
INTERVAL |
VARCHAR2(200) |
用于计算下一运行时间的表达式 |
FAILURES |
NUMBER |
任务运行连续没有成功的次数 |
WHAT |
VARCHAR2(2000) |
执行任务的PL/SQL块 |
CURRENT_SESSION_LABEL |
RAW MLSLABEL |
该任务的信任Oracle会话符 |
CLEARANCE_HI |
RAW MLSLABEL |
该任务可信任的Oracle最大间隙 |
CLEARANCE_LO |
RAW MLSLABEL |
该任务可信任的Oracle最小间隙 |
NLS_ENV |
VARCHAR2(2000) |
任务运行的NLS会话设置 |
MISC_ENV |
RAW(32) |
任务运行的其他一些会话参数 |
表 5. 视图DBA_JOBS_RUNNING的字段含义
列 |
数据类型 |
描述 |
SID |
NUMBER |
目前正在运行任务的会话ID |
JOB |
NUMBER |
任务的唯一标示符 |
FAILURES |
NUMBER |
连续不成功执行的累计次数 |
LAST_DATE |
DATE |
最后一次成功执行的日期 |
LAST_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的last_date日期的小时,分钟和秒 |
THIS_DATE |
DATE |
目前正在运行任务的开始日期 |
THIS_SEC |
VARCHAR2(8) |
如HH24:MM:SS格式的this_date日期的小时,分钟和秒 |
六、任务重复运行间隔和间隔设计算法
任务重复运行的时间间隔取决于interval参数中设置的日期表达式。下面就来详细谈谈该如何设置interval参数才能准确满足我们的任务需求。一般来讲,对于一个任务的定时执行,有三种定时要求。
- 在一个特定的时间间隔后,重复运行该任务。
- 在特定的日期和时间运行任务。
- 任务成功完成后,下一次执行应该在一个特定的时间间隔之后。
第一种调度任务需求的日期算法比较简单,即'SYSDATE+n',这里n是一个以天为单位的时间间隔。表6给出了一些这种时间间隔设置的例子。
表6 一些简单的interval参数设置例子
描述 |
Interval参数值 |
每天运行一次 |
'SYSDATE + 1' |
每小时运行一次 |
'SYSDATE + 1/24' |
每10分钟运行一次 |
'SYSDATE + 10/(60*24)' |
每30秒运行一次 |
'SYSDATE + 30/(60*24*60)' |
每隔一星期运行一次 |
'SYSDATE + 7' |
不再运行该任务并删除它 |
NULL |
表6所示的任务间隔表达式不能保证任务的下一次运行时间在一个特定的日期或者时间,仅仅能够指定一个任务两次运行之间的时间间隔。例如,如果一个任务第一次运行是在凌晨12点,interval指定为'SYSDATE + 1',则该任务将被计划在第二天的凌晨12点执行。但是,如果某用户在下午4点手工(DBMS_JOB.RUN)执行了该任务,那么该任务将被重新定时到第二天的下午4点。还有一个可能的原因是如果数据库关闭或者说任务队列非常的忙以至于任务不能在计划的那个时间点准时执行。在这种情况下,任务将试图尽快运行,也就是说只要数据库一打开或者是任务队列不忙就开始执行,但是这时,运行时间已经从原来的提交时间漂移到了后来真正的运行时间。这种下一次运行时间的不断“漂移”是采用简单时间间隔表达式的典型特征。
第二种调度任务需求相对于第一种就需要更复杂的时间间隔(interval)表达式,表7是一些要求在特定的时间运行任务的interval设置例子。
表 7. 定时到特定日期或时间的任务例子
描述 |
INTERVAL参数值 |
每天午夜12点 |
'TRUNC(SYSDATE + 1)' |
每天早上8点30分 |
'TRUNC(SYSDATE + 1) + (8*60+30)/(24*60)' |
每星期二中午12点 |
'NEXT_DAY(TRUNC(SYSDATE ), ''TUESDAY'' ) + 12/24' |
每个月第一天的午夜12点 |
'TRUNC(LAST_DAY(SYSDATE ) + 1)' |
每个季度最后一天的晚上11点 |
'TRUNC(ADD_MONTHS(SYSDATE + 2/24, 3 ), 'Q' ) -1/24' |
每星期六和日早上6点10分 |
'TRUNC(LEAST(NEXT_DAY(SYSDATE, ''SATURDAY"), NEXT_DAY(SYSDATE, "SUNDAY"))) + (6×60+10)/(24×60)' |
第三种调度任务需求无论通过怎样设置interval日期表达式也不能满足要求。这时因为一个任务的下一次运行时间在任务开始时才计算,而在此时是不知道任务在何时结束的。遇到这种情况怎么办呢?当然办法肯定是有的,我们可以通过为任务队列写过程的办法来实现。这里我只是简单介绍以下,可以在前一个任务队列执行的过程中,取得任务完成的系统时间,然后加上指定的时间间隔,拿这个时间来控制下一个要执行的任务。这里有一个前提条件,就是目前运行的任务本身必须要严格遵守自己的时间计划。
结论
Oracle中的定时任务是在Oracle系统中是一个非常重要的子系统,运用得当,可以极大的提高我们的系统运行和维护能力。而Oracle数据复制的延迟事务队列管理完全是基于Oracle的队列任务,对其的深刻理解有助于我们更好地管理数据复制。
本文所要用到的工具或jar主要有: Acrobat 8 这个主要用来制作PDF模板、eclipse这个看你喜欢咯(你用其他也行) 、
itext.jar、
还有为了解决中文的输出问题,需要多下载一个名为iTextAsian.jar的JAR包。这个包里面定义了与中文输出相关的一些文件。
好了,需要做的就是这些了,简单的PDF生成这里就不再作介绍了,本文主要讲解如何使用PDF模板。
我们先来看看制作出来的效果:
上图表格上及表格中的数据是动态添加进去的,页数为两页(为节约版面现只显示一页)
两页都是用的同一模板的,
1、 模板的制作:
我主要使用的是Acrobat8.0,上面所用到的模板是由 周工作报告 模板修改而来的,如果想学习如何新建一个新的模板,大家可以参照下这里吧!
http://lxy19791111.javaeye.com/blog/102848
2、 取得每个表单域的名字
模板制作好后,要插入数据首先就要知道需要插在模板中位置,
-
- FileOutputStream fos = new FileOutputStream("c:/test/Pdf.pdf");
-
- String TemplatePDF ="c:/test/PdfTemplate.pdf";
- PdfReader reader = new PdfReader(TemplatePDF);
- PdfStamper stamp = new PdfStamper(reader,fos);
- AcroFields form = stamp.getAcroFields();
- for (Iterator it = form.getFields().keySet().iterator(); it
- .hasNext();) {
- System.out.println(it.next());
- }
//需要生成后的PDF
FileOutputStream fos = new FileOutputStream("c:/test/Pdf.pdf");
//PDF模板路径
String TemplatePDF ="c:/test/PdfTemplate.pdf";
PdfReader reader = new PdfReader(TemplatePDF);
PdfStamper stamp = new PdfStamper(reader,fos);
AcroFields form = stamp.getAcroFields();
for (Iterator it = form.getFields().keySet().iterator(); it
.hasNext();) {
System.out.println(it.next());
}
这个是打印后的部分结果:
我们只取后面那个命名就行,如"星期四[3]"
当然,模板是你自己定义,文本域的命名你当然知道了,这里只是作个简单介绍而已。
3、下面是插入数据及PDF合并的代码:
- package com.golden.info.test;
-
- import java.io.ByteArrayOutputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.util.Date;
-
- import com.lowagie.text.Document;
- import com.lowagie.text.DocumentException;
- import com.lowagie.text.pdf.AcroFields;
- import com.lowagie.text.pdf.PdfCopy;
- import com.lowagie.text.pdf.PdfImportedPage;
- import com.lowagie.text.pdf.PdfReader;
- import com.lowagie.text.pdf.PdfStamper;
- public class TestPdfTemplate {
- public static void main(String[] args) {
- try {
- int count = 8;
- int pageCount = 4;
- int index = 1;
- int page = 0;
-
- if (count >= pageCount && count % pageCount == 0) {
- page = count / pageCount;
- } else {
- page = count / pageCount + 1;
- }
- String TemplatePDF = "c:/test/PdfTemplate.pdf";
- FileOutputStream fos = new FileOutputStream("c:/test/Pdf.pdf");
-
- ByteArrayOutputStream baos[] = new ByteArrayOutputStream[page];
-
- for (int item = 0; item < page; item++) {
- baos[item] = new ByteArrayOutputStream();
- PdfReader reader = new PdfReader(TemplatePDF);
- PdfStamper stamp = new PdfStamper(reader, baos[item]);
- AcroFields form = stamp.getAcroFields();
- form.setField("DepartmnetNmae", "蓝飞");
- form.setField("qq", "252462807");
- form.setField("pageNumber", "第" + (item + 1) + "页,共" + page
- + "页");
- if (count % pageCount != 0 && item == page - 1) {
- System.out.println("====pageCount+" + pageCount + "=====");
- pageCount = count % pageCount;
- }
-
- for (int j = 0; j < pageCount; j++) {
- form.setField("ProjectTask[" + j + "]", index + "");
- form.setField("星期一[" + j + "]", "星期一[" + index + "]");
- form.setField("星期二[" + j + "]", "星期二[" + index + "]");
- form.setField("星期三[" + j + "]", "星期三[" + index + "]");
- form.setField("星期四[" + j + "]", "星期四[" + index + "]");
- form.setField("星期五[" + j + "]", "星期五[" + index + "]");
- form.setField("星期六[" + j + "]", "星期六[" + index + "]");
- form.setField("星期日[" + j + "]", "星期日[" + index + "]");
- form.setField("意见[" + j + "]", "同意[" + j + "]");
- index++;
- }
- stamp.setFormFlattening(true);
- stamp.close();
- }
- Document doc = new Document();
- PdfCopy pdfCopy = new PdfCopy(doc, fos);
- doc.open();
- PdfImportedPage impPage = null;
-
- for (int i = 0; i < page; i++) {
- impPage = pdfCopy.getImportedPage(new PdfReader(baos[i]
- .toByteArray()), 1);
- pdfCopy.addPage(impPage);
- }
- doc.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (DocumentException e) {
- e.printStackTrace();
- }
-
- }
- }
package com.golden.info.test;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
public class TestPdfTemplate {
public static void main(String[] args) {
try {
int count = 8;// 总记录数
int pageCount = 4;// 每页记录数
int index = 1; // 表格序号
int page = 0;// 总共页数
/** 主要控制总共的页数*/
if (count >= pageCount && count % pageCount == 0) {
page = count / pageCount;
} else {
page = count / pageCount + 1;
}
String TemplatePDF = "c:/test/PdfTemplate.pdf";//设置模板路径
FileOutputStream fos = new FileOutputStream("c:/test/Pdf.pdf");//需要生成PDF
ByteArrayOutputStream baos[] = new ByteArrayOutputStream[page];//用于存储每页生成PDF流
/** 向PDF模板中插入数据 */
for (int item = 0; item < page; item++) {
baos[item] = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(TemplatePDF);
PdfStamper stamp = new PdfStamper(reader, baos[item]);
AcroFields form = stamp.getAcroFields();
form.setField("DepartmnetNmae", "蓝飞");//插入的数据都为字符类型
form.setField("qq", "252462807");
form.setField("pageNumber", "第" + (item + 1) + "页,共" + page
+ "页");
if (count % pageCount != 0 && item == page - 1) {
System.out.println("====pageCount+" + pageCount + "=====");
pageCount = count % pageCount;
}
/**因为PDF中的表格其实是众多的文本域组成,就是一个数组,所以把它循环出来就可以了*/
for (int j = 0; j < pageCount; j++) {
form.setField("ProjectTask[" + j + "]", index + "");
form.setField("星期一[" + j + "]", "星期一[" + index + "]");
form.setField("星期二[" + j + "]", "星期二[" + index + "]");
form.setField("星期三[" + j + "]", "星期三[" + index + "]");
form.setField("星期四[" + j + "]", "星期四[" + index + "]");
form.setField("星期五[" + j + "]", "星期五[" + index + "]");
form.setField("星期六[" + j + "]", "星期六[" + index + "]");
form.setField("星期日[" + j + "]", "星期日[" + index + "]");
form.setField("意见[" + j + "]", "同意[" + j + "]");
index++;
}
stamp.setFormFlattening(true); // 千万不漏了这句啊, */
stamp.close();
}
Document doc = new Document();
PdfCopy pdfCopy = new PdfCopy(doc, fos);
doc.open();
PdfImportedPage impPage = null;
/**取出之前保存的每页内容*/
for (int i = 0; i < page; i++) {
impPage = pdfCopy.getImportedPage(new PdfReader(baos[i]
.toByteArray()), 1);
pdfCopy.addPage(impPage);
}
doc.close();//当文件拷贝 记得关闭doc
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
至于,生于PDF后,想打印出来,只要调用以下代码就行了
-
- try{
- Executable ex = new Executable();
- ex.openDocument("c:/test/Pdf.pdf");
- ex.printDocument("c:/test/Pdf.pdf");
- }catch(IOException e){
- e.printStackTrace();
- }
try{
Executable ex = new Executable();
ex.openDocument("c:/test/Pdf.pdf");
ex.printDocument("c:/test/Pdf.pdf");
}catch(IOException e){
e.printStackTrace();
}
到这里,运用上面的那些代码,就完成了PDF模板输出报表.
(PDF模板、代码跟运行结果在附件里)
有错误之处请指正.
也希望这篇文章可以帮到您.
摘要: 有个朋友的项目需要用到 PDF 报表下载,之前我只做过 Excel 的,相信再做一次 PDF 的下载一定很有趣吧。在网上找了一大圈,似乎 iText 比较符合我的要求,而且这个工具很早很早以前就有了,生命力很旺盛。进入 iText 的主页(http://www.lowagie.com/iText/),发现作者很勤劳,最近2个月都有新版本发布。哪知道现在高兴得太早了,一堆问题接踵而至。
下载倒...
阅读全文
iText简介
iText是一个开放源码的Java类库,可以用来方便地生成PDF文件。大家通过访问http://sourceforge.net/project/showfiles.php?group_id=15255&release_id=167948下载最新版本的类库,下载完成之后会得到一个.jar包,把这个包加入JDK的classpath即可使用。如果生成的PDF文件中需要出现中文、日文、韩文字符,则还需要通过访问http://itext.sourceforge.net/downloads/iTextAsian.jar下载iTextAsian.jar包。
关于iText类库的使用,http://www.lowagie.com/iText/tutorial/index.html有比较详细的教程。该教程从入门开始,比较系统地介绍了在PDF文件中放入文字、图片、表格等的方法和技巧。读完这片教程,大致就可以做一些从简单到复杂的PDF文件了。不过,试图通过教程解决在生成PDF文件过程中遇到的所有困难无疑是一种奢望。所以,阅读iText的api文档显得非常重要。读者在下载类库的同时,也可以下载类库的文档。
可参考资料 :
http://dev.csdn.net/article/62/62119.shtm http://myowndream.blog.com.cn/archives/2007/2089386.shtml
http://tag.csdn.net/tag/itext.xml
一 HelloWorld实例
以下是上述教程中一个最简单的例子,这个例子刻画了通过iText生成PDF文件的一般程序框架。读者只需要在document.open();和document.close();两条语句中间加入自己希望放在PDF文件中的内容即可。该例子只在PDF文件中加了“Hello World“一行文字。
Document document = new Document();
try{
PdfWriter.getInstance(document, new FileOutputStream ("Chap0101.pdf"));
document.open();
document.add(new Paragraph("Hello World"));
}catch(DocumentException de){
System.err.println(de.getMessage());
}catch(IOException ioe){
System.err.println(ioe.getMessage());
}
document.close();
可以看到一个PDF文件的输出,总共只需要5个步骤
a.创建一个Document实例
Document document = new Document();
b.将Document实例和文件输出流用PdfWriter类绑定在一起
PdfWriter.getInstance(document,new FileOutputStream("HelloWorld.pdf"));
c.打开文档
document.open();
d.在文档中添加文字
document.add(new Paragraph("Hello World"));
e.关闭文档
document.close();
这样5个步骤,就可以生成一个PDF文档了。
然而在PDF中指定文字、图画、表格的位置是一件非常麻烦的事情。除了不断地在程序中修改位置、然后运行程序、生成PDF文件、观察元素在PDF中的位置是否合理这样的过程以外,似乎还没有其它更好的方法。
二。如何使用jsp、servlet输出iText生成的pdf?
如果每次都在服务端生成一个PDF文件给用户,不仅麻烦,而且浪费服务器资源,最好的方法就是以二进制流的形式输送到客户端。
1)JSP输出:
<%@ page import="java.io.*,java.awt.Color,com.lowagie.text.*,com.lowagie.text.pdf.*"%>
<%
response.setContentType( "application/pdf" );
Document document = new Document();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
PdfWriter writer=PdfWriter.getInstance( document, buffer );
document.open();
document.add(new Paragraph("Hello World"));
document.close();
DataOutput output = new DataOutputStream( response.getOutputStream() );
byte[] bytes = buffer.toByteArray();
response.setContentLength(bytes.length);
for( int i = 0; i < bytes.length; i++ ){
output.writeByte( bytes[i] );
}
%>
2)servlet输出,稍微改造下就可以使用在struts中:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException,ServletException{
Document document = new Document(PageSize.A4, 36,36,36,36);
ByteArrayOutputStream ba = new ByteArrayOutputStream();
try{
PdfWriter writer = PdfWriter.getInstance(document, ba);
document.open();
document.add(new Paragraph("Hello World"));
}catch(DocumentException de){
de.printStackTrace();
System.err.println("A Document error:" +de.getMessage());
}
document.close();
response.setContentType("application/pdf");
response.setContentLength(ba.size());
ServletOutputStream out = response.getOutputStream();
ba.writeTo(out);
out.flush();
}
三。如何输出中文?
首先需要下载iTextAsian.jar包,可以到iText的主站上下,ireport也是需要这个包的。然后定义中文字体:
private static final Font getChineseFont() {
Font FontChinese = null;
try {
BaseFont bfChinese = BaseFont.createFont("STSong-Light",
"UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
FontChinese = new Font(bfChinese, 12, Font.NORMAL);
} catch (DocumentException de) {
System.err.println(de.getMessage());
} catch (IOException ioe) {
System.err.println(ioe.getMessage());
}
return FontChinese;
}
我将产生中文字体封装在方法内,自定义一个段落PDFParagraph继承自Paragraph,默认使用中文字体:
class PDFParagraph extends Paragraph {
public PDFParagraph(String content) {
super(content, getChineseFont());
}
}
使用的时候就可以简化了:
Paragraph par = new PDFParagraph("你好");
四。如何设置PDF横向显示和打印?
Rectangle rectPageSize = new Rectangle(PageSize.A4);// 定义A4页面大小
rectPageSize = rectPageSize.rotate();// 加上这句可以实现A4页面的横置
Document doc = new Document(rectPageSize,50,50,50,50);//4个参数,设置了页面的4个边距
五。如何设置跨行和跨列?
使用PdfPTable和PdfPCell 是没办法实现跨行的,只能跨列,要跨行使用com.lowagie.text.Table和com.lowagie.text.Cell类,Cell类有两个方法:setRowspan()和setColspan()。
六。如何设置单元格边界宽度?
Cell类的系列setBorderWidthXXXX()方法,比如setBorderWidthTop(),setBorderWidthRight()等
七。如何设置表头?
希望每一页都有表头,可以通过设置表头来实现。对于PdfPTable类来说,可以这样设置:
PdfPTable table = new PdfPTable(3);
table.setHeaderRows(2); // 设置了头两行为表格头
而对于om.lowagie.text.Table类,需要在添加完所有表头的单元格后加上一句代码:
table.endHeaders();
八。如何设置列宽?
Table table = new Table(8);
float[] widths = { 0.10f, 0.15f, 0.21f, 0.22f, 0.08f, 0.08f, 0.10f,
0.06f };
table.setWidths(widths);
上面的代码设置了一个有8列的表格,通过一个float数组设置列宽,这里是百分比。
九。单元格内段落文字居中和换行?
居中通过Cell类来设置,一开始我以为设置段落对齐就可以了,没想到是需要设置单元格:
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
转义符\n实现。在我的这个应用中,因为数据库取出的数据是为了显示在html上的,所以有很多<br>标签,可以使用正则表达式替换成"\n"
"<br>1.测试<br>2.测试2".replaceAll("<br>|</br>","\n");
十。如何显示页码?
复杂的页码显示和水印添加,需要使用到PdfPageEventHelper、PdfTemplate等辅助类,具体的例子参见iText的文档,如果只是为了简单的显示页数,可以使用下面的代码:
HeaderFooter footer = new HeaderFooter(new Phrase("页码:",getChineseFont()), true);
footer.setBorder(Rectangle.NO_BORDER);
document.setFooter(footer);
document.open();
你可能注意到了,添加footer需要在document.open之前。
摘要: 数据库连接池的原理机制
数据库连接池的原理机制
...
阅读全文
方法一:
在目前绝大部分数据库有分布式查询的需要。下面简单的介绍如何在oracle中配置实现跨库访问。
比如现在有2个数据库服务器,安装了2个数据库。数据库server A和B。现在来实现在A库中访问B的数据库。
第一步、配置A服务器端的tnsnames.ora文件(TNSNAMES.ORA Network Configuration File),该文件存放的位置为:
$ORACLE_HOME/network/admin/tnsnames.ora
需要在该文件中增加对B库的配置项,格式如下
ZBCDB3 =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 10.1.50.6)(PORT = 1523))
)
(CONNECT_DATA =
(SERVICE_NAME = zbcdb3)
)
)
若在A库的另外一台客户端来访问B的数据库的话,同时也应该修改客户端的相应的文件。
第二步、在A服务器的一个库中建立B的一个数据的DBLINK。语法如下:
create database link dcmdb connect to dcmdb identified by dcmoptr using 'zbcdb3' ;
然后可以实现分布式查询:
select * from tabname@dcmdb where 1=1;
方法二:
首先创建数据库链接:
CREATE PUBLIC DATABASE LINK 数据链名称 CONNECT TO 登陆用户名 IDENTIFIED BY 密码 USING '(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 对方Oracle服务器的IP地址)(PORT = 端口号))
)
(CONNECT_DATA =
(SERVICE_NAME = 对方Oracle服务器服务名)
)
)'
其中 数据链名称 为添加到本地Oracle数据库控制台(Oracle Enterprise Manager Console)树节点的服务名
要查询对方数据库的表TableName语句如下:
SELECT 字段名 FROM TableName@数据链名称;
复制表数据:
insert into 表名(字段名) (SELECT 字段名 FROM TableName@数据链名称);
通常,你需要获得当前日期和计算一些其他的日期,例如,你的程序可能需要判断一个月的第一天或者最后一天。你们大部分人大概都知道怎样把日期进行分割(年、月、日等),然后仅仅用分割出来的年、月、日等放在几个函数中计算出自己所需要的日期!在这篇文章里,我将告诉你如何使用DATEADD和DATEDIFF函数来计算出在你的程序中可能你要用到的一些不同日期。
在使用本文中的例子之前,你必须注意以下的问题。大部分可能不是所有例子在不同的机器上执行的结果可能不一样,这完全由哪一天是一个星期的第一天这个设置决定。第一天(DATEFIRST)设定决定了你的系统使用哪一天作为一周的第一天。所有以下的例子都是以星期天作为一周的第一天来建立,也就是第一天设置为7。假如你的第一天设置不一样,你可能需要调整这些例子,使它和不同的第一天设置相符合。你可以通过@@DATEFIRST函数来检查第一天设置。
为了理解这些例子,我们先复习一下DATEDIFF和DATEADD函数。DATEDIFF函数计算两个日期之间的小时、天、周、月、年等时间间隔总数。DATEADD函数计算一个日期通过给时间间隔加减来获得一个新的日期。要了解更多的DATEDIFF和DATEADD函数以及时间间隔可以阅读微软联机帮助。
使用DATEDIFF和DATEADD函数来计算日期,和本来从当前日期转换到你需要的日期的考虑方法有点不同。你必须从时间间隔这个方面来考虑。比如,从当前日期到你要得到的日期之间有多少时间间隔,或者,从今天到某一天(比如1900-1-1)之间有多少时间间隔,等等。理解怎样着眼于时间间隔有助于你轻松的理解我的不同的日期计算例子。
一个月的第一天
第一个例子,我将告诉你如何从当前日期去这个月的最后一天。请注意:这个例子以及这篇文章中的其他例子都将只使用DATEDIFF和DATEADD函数来计算我们想要的日期。每一个例子都将通过计算但前的时间间隔,然后进行加减来得到想要计算的日期。
这是计算一个月第一天的SQL 脚本:
SELECT DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)
我们把这个语句分开来看看它是如何工作的。最核心的函数是getdate(),大部分人都知道这个是返回当前的日期和时间的函数。下一个执行的函数DATEDIFF(mm,0,getdate())是计算当前日期和“1900-01-01 00:00:00.000”这个日期之间的月数。记住:时期和时间变量和毫秒一样是从“1900-01-01 00:00:00.000”开始计算的。这就是为什么你可以在DATEDIFF函数中指定第一个时间表达式为“0”。下一个函数是DATEADD,增加当前日期到“1900-01-01”的月数。通过增加预定义的日期“1900-01-01”和当前日期的月数,我们可以获得这个月的第一天。另外,计算出来的日期的时间部分将会是“00:00:00.000”。
这个计算的技巧是先计算当前日期到“1900-01-01”的时间间隔数,然后把它加到“1900-01-01”上来获得特殊的日期,这个技巧可以用来计算很多不同的日期。下一个例子也是用这个技巧从当前日期来产生不同的日期。
本周的星期一
这里我是用周(wk)的时间间隔来计算哪一天是本周的星期一。
SELECT DATEADD(wk, DATEDIFF(wk,0,getdate()), 0)
一年的第一天
现在用年(yy)的时间间隔来显示这一年的第一天。
SELECT DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)
季度的第一天
假如你要计算这个季度的第一天,这个例子告诉你该如何做。
SELECT DATEADD(qq, DATEDIFF(qq,0,getdate()), 0)
当天的半夜
曾经需要通过getdate()函数为了返回时间值截掉时间部分,就会考虑到当前日期是不是在半夜。假如这样,这个例子使用DATEDIFF和DATEADD函数来获得半夜的时间点。
SELECT DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)
深入DATEDIFF和DATEADD函数计算
你可以明白,通过使用简单的DATEDIFF和DATEADD函数计算,你可以发现很多不同的可能有意义的日期。
目前为止的所有例子只是仅仅计算当前的时间和“1900-01-01”之间的时间间隔数量,然后把它加到“1900-01-01”的时间间隔上来计算出日期。假定你修改时间间隔的数量,或者使用不同的时间间隔来调用DATEADD函数,或者减去时间间隔而不是增加,那么通过这些小的调整你可以发现和多不同的日期。
这里有四个例子使用另外一个DATEADD函数来计算最后一天来分别替换DATEADD函数前后两个时间间隔。
上个月的最后一天
这是一个计算上个月最后一天的例子。它通过从一个月的最后一天这个例子上减去3毫秒来获得。有一点要记住,在Sql Server中时间是精确到3毫秒。这就是为什么我需要减去3毫秒来获得我要的日期和时间。
SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))
计算出来的日期的时间部分包含了一个Sql Server可以记录的一天的最后时刻(“23:59:59:997”)的时间。
去年的最后一天
连接上面的例子,为了要得到去年的最后一天,你需要在今年的第一天上减去3毫秒。
SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0))
本月的最后一天
现在,为了获得本月的最后一天,我需要稍微修改一下获得上个月的最后一天的语句。修改需要给用DATEDIFF比较当前日期和“1900-01-01”返回的时间间隔上加1。通过加1个月,我计算出下个月的第一天,然后减去3毫秒,这样就计算出了这个月的最后一天。这是计算本月最后一天的SQL脚本。
SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0))
本年的最后一天
你现在应该掌握这个的做法,这是计算本年最后一天脚本
SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate())+1, 0))。
本月的第一个星期一
好了,现在是最后一个例子。这里我要计算这个月的第一个星期一。这是计算的脚本。
select DATEADD(wk, DATEDIFF(wk,0,
dateadd(dd,6-datepart(day,getdate()),getdate())
), 0)
在这个例子里,我使用了“本周的星期一”的脚本,并作了一点点修改。修改的部分是把原来脚本中“getdate()”部分替换成计算本月的第6天,在计算中用本月的第6天来替换当前日期使得计算可以获得这个月的第一个星期一。
总结
我希望这些例子可以在你用DATEADD和DATEDIFF函数计算日期时给你一点启发。通过使用这个计算日期的时间间隔的数学方法,我发现为了显示两个日期之间间隔的有用历法是有价值的。注意,这只是计算出这些日期的一种方法。要牢记,还有很多方法可以得到相同的计算结果。假如你有其他的方法,那很不错,要是你没有,我希望这些例子可以给你一些启发,当你要用DATEADD和DATEDIFF函数计算你程序可能要用到的日期时。
---------------------------------------------------------------
附录,其他日期处理方法
1)去掉时分秒
declare @ datetime
set @ = getdate() --'2003-7-1 10:00:00'
SELECT @,DATEADD(day, DATEDIFF(day,0,@), 0)
2)显示星期几
select datename(weekday,getdate())
3)如何取得某个月的天数
declare @m int
set @m=2 --月份
select datediff(day,'2003-'+cast(@m as varchar)+'-15' ,'2003-'+cast(@m+1 as varchar)+'-15')
另外,取得本月天数
select datediff(day,cast(month(GetDate()) as varchar)+'-'+cast(month(GetDate()) as varchar)+'-15' ,cast(month(GetDate()) as varchar)+'-'+cast(month(GetDate())+1 as varchar)+'-15')
或者使用计算本月的最后一天的脚本,然后用DAY函数区最后一天
SELECT Day(dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0)))
4)判断是否闰年:
SELECT case day(dateadd(mm, 2, dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)))) when 28 then '平年' else '闰年' end
或者
select case datediff(day,datename(year,getdate())+'-02-01',dateadd(mm,1,datename(year,getdate())+'-02-01'))
when 28 then '平年' else '闰年' end
5)一个季度多少天
declare @m tinyint,@time smalldatetime
select @m=month(getdate())
select @m=case when @m between 1 and 3 then 1
when @m between 4 and 6 then 4
when @m between 7 and 9 then 7
else 10 end
select @time=datename(year,getdate())+'-'+convert(varchar(10),@m)+'-01'
select datediff(day,@time,dateadd(mm,3,@time))
Oracle与DB2获取当前系统时间的方法:
Oracle中系统时间比较熟悉 是select sysdate from dual;
db2中则是 SELECT CURRENT TIMESTAMP FROM SYSIBM.SYSDUMMY1
还有
select CURRENT DATE from SYSIBM.SYSDUMMY1;获得日期
select CURRENT TIME from SYSIBM.SYSDUMMY1;获得时间
查询
SELECT语句用于从数据库中查询数据,当在PL/SQL中使用SELECT语句时,要与INTO子句一起使用,查询的返回值被赋予INTO子句中的变量,变量的声明是在DELCARE中。SELECT INTO语法如下:
SELECT [DISTICT|ALL]{*|column[,column,...]}
INTO (variable[,variable,...] |record)
FROM {table|(sub-query)}[alias]
WHERE............
PL/SQL中SELECT语句只返回一行数据。如果超过一行数据,那么就要使用显式游标(对游标的讨论我们将在后面进行),INTO子句中要有与SELECT子句中相同列数量的变量。INTO子句中也可以是记录变量。
%TYPE属性
在PL/SQL中可以将变量和常量声明为内建或用户定义的数据类型,以引用一个列名,同时继承他的数据类型和大小。这种动态赋值方法是非常有用的,比如变量引用的列的数据类型和大小改变了,如果使用了%TYPE,那么用户就不必修改代码,否则就必须修改代码。
例:
v_empno SCOTT.EMP.EMPNO%TYPE;
v_salary EMP.SALARY%TYPE;
不但列名可以使用%TYPE,而且变量、游标、记录,或声明的常量都可以使用%TYPE。这对于定义相同数据类型的变量非常有用。
DELCARE
V_A NUMBER(5):=10;
V_B V_A%TYPE:=15;
V_C V_A%TYPE;
BEGIN
DBMS_OUTPUT.PUT_LINE
('V_A='||V_A||'V_B='||V_B||'V_C='||V_C);
END
SQL>/
V_A=10 V_B=15 V_C=
PL/SQL procedure successfully completed.
SQL>
其他DML语句
其它操作数据的DML语句是:INSERT、UPDATE、DELETE和LOCK TABLE,这些语句在PL/SQL中的语法与在SQL中的语法相同。我们在前面已经讨论过DML语句的使用这里就不再重复了。在DML语句中可以使用任何在DECLARE部分声明的变量,如果是嵌套块,那么要注意变量的作用范围。
例:
CREATE OR REPLACE PROCEDURE FIRE_EMPLOYEE (pempno in number)
AS
v_ename EMP.ENAME%TYPE;
BEGIN
SELECT ename INTO v_ename
FROM emp
WHERE empno=p_empno;
INSERT INTO FORMER_EMP(EMPNO,ENAME)
VALUES (p_empno,v_ename);
DELETE FROM emp
WHERE empno=p_empno;
UPDATE former_emp
SET date_deleted=SYSDATE
WHERE empno=p_empno;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee Number Not Found!');
END
DML语句的结果
当执行一条DML语句后,DML语句的结果保存在四个游标属性中,这些属性用于控制程序流程或者了解程序的状态。当运行DML语句时,PL/SQL打开一个内建游标并处理结果,游标是维护查询结果的内存中的一个区域,游标在运行DML语句时打开,完成后关闭。隐式游标只使用SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT三个属性.SQL%FOUND,SQL%NOTFOUND是布尔值,SQL%ROWCOUNT是整数值。
SQL%FOUND和SQL%NOTFOUND
在执行任何DML语句前SQL%FOUND和SQL%NOTFOUND的值都是NULL,在执行DML语句后,SQL%FOUND的属性值将是:
. TRUE :INSERT
. TRUE :DELETE和UPDATE,至少有一行被DELETE或UPDATE.
. TRUE :SELECT INTO至少返回一行
当SQL%FOUND为TRUE时,SQL%NOTFOUND为FALSE。
SQL%ROWCOUNT
在执行任何DML语句之前,SQL%ROWCOUNT的值都是NULL,对于SELECT INTO语句,如果执行成功,SQL%ROWCOUNT的值为1,如果没有成功,SQL%ROWCOUNT的值为0,同时产生一个异常NO_DATA_FOUND.
SQL%ISOPEN
SQL%ISOPEN是一个布尔值,如果游标打开,则为TRUE, 如果游标关闭,则为FALSE.对于隐式游标而言SQL%ISOPEN总是FALSE,这是因为隐式游标在DML语句执行时打开,结束时就立即关闭。
事务控制语句
事务是一个工作的逻辑单元可以包括一个或多个DML语句,事物控制帮助用户保证数据的一致性。如果事务控制逻辑单元中的任何一个DML语句失败,那么整个事务都将回滚,在PL/SQL中用户可以明确地使用COMMIT、ROLLBACK、SAVEPOINT以及SET TRANSACTION语句。
COMMIT语句终止事务,永久保存数据库的变化,同时释放所有LOCK,ROLLBACK终止现行事务释放所有LOCK,但不保存数据库的任何变化,SAVEPOINT用于设置中间点,当事务调用过多的
数据库操作时,中间点是非常有用的,SET TRANSACTION用于设置事务属性,比如read-write和隔离级等。
显式游标
当查询返回结果超过一行时,就需要一个显式游标,此时用户不能使用select into语句。PL/SQL管理隐式游标,当查询开始时隐式游标打开,查询结束时隐式游标自动关闭。显式游标在PL/SQL块的声明部分声明,在执行部分或异常处理部分打开,取数据,关闭。
使用游标
这里要做一个声明,我们所说的游标通常是指显式游标,因此从现在起没有特别指明的情况,我们所说的游标都是指显式游标。要在程序中使用游标,必须首先声明游标。
声明游标
语法:
CURSOR cursor_name IS select_statement;
在PL/SQL中游标名是一个未声明变量,不能给游标名赋值或用于表达式中。
例:
DELCARE
CURSOR C_EMP IS SELECT empno,ename,salary
FROM emp
WHERE salary>2000
ORDER BY ename;
........
BEGIN
在游标定义中SELECT语句中不一定非要表可以是视图,也可以从多个表或视图中选择的列,甚至可以使用*来选择所有的列 。
打开游标
使用游标中的值之前应该首先打开游标,打开游标初始化查询处理。打开游标的语法是:
OPEN cursor_name
cursor_name是在声明部分定义的游标名。
例:
OPEN C_EMP;
关闭游标
语法:
CLOSE cursor_name
例:
CLOSE C_EMP;
从游标提取数据
从游标得到一行数据使用FETCH命令。每一次提取数据后,游标都指向结果集的下一行。语法如下:
FETCH cursor_name INTO variable[,variable,...]
对于SELECT定义的游标的每一列,FETCH变量列表都应该有一个变量与之相对应,变量的类型也要相同。
例:
SET SERVERIUTPUT ON
DECLARE
v_ename EMP.ENAME%TYPE;
v_salary EMP.SALARY%TYPE;
CURSOR c_emp IS SELECT ename,salary FROM emp;
BEGIN
OPEN c_emp;
FETCH c_emp INTO v_ename,v_salary;
DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary);
FETCH c_emp INTO v_ename,v_salary;
DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary);
FETCH c_emp INTO v_ename,v_salary;
DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary);
CLOSE c_emp;
END
这段代码无疑是非常麻烦的,如果有多行返回结果,可以使用循环并用游标属性为结束循环的条件,以这种方式提取数据,程序的可读性和简洁性都大为提高,下面我们使用循环重新写上面的程序:
SET SERVERIUTPUT ON
DECLARE
v_ename EMP.ENAME%TYPE;
v_salary EMP.SALARY%TYPE;
CURSOR c_emp IS SELECT ename,salary FROM emp;
BEGIN
OPEN c_emp;
LOOP
FETCH c_emp INTO v_ename,v_salary;
EXIT WHEN c_emp%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary);
END
记录变量
定义一个记录变量使用TYPE命令和%ROWTYPE,关于%ROWsTYPE的更多信息请参阅相关资料。
记录变量用于从游标中提取数据行,当游标选择很多列的时候,那么使用记录比为每列声明一个变量要方便得多。
当在表上使用%ROWTYPE并将从游标中取出的值放入记录中时,如果要选择表中所有列,那么在SELECT子句中使用*比将所有列名列出来要
安全得多。
例:
SET SERVERIUTPUT ON
DECLARE
R_emp EMP%ROWTYPE;
CURSOR c_emp IS SELECT * FROM emp;
BEGIN
OPEN c_emp;
LOOP
FETCH c_emp INTO r_emp;
EXIT WHEN c_emp%NOTFOUND;
DBMS_OUT.PUT.PUT_LINE('Salary of Employee'||r_emp.ename||'is'|| r_emp.salary);
END LOOP;
CLOSE c_emp;
END;
%ROWTYPE也可以用游标名来定义,这样的话就必须要首先声明游标:
SET SERVERIUTPUT ON
DECLARE
CURSOR c_emp IS SELECT ename,salary FROM emp;
R_emp c_emp%ROWTYPE;
BEGIN
OPEN c_emp;
LOOP
FETCH c_emp INTO r_emp;
EXIT WHEN c_emp%NOTFOUND;
DBMS_OUT.PUT.PUT_LINE('Salary of Employee'||r_emp.ename||'is'|| r_emp.salary);
END LOOP;
CLOSE c_emp;
END;
带参数的游标
与
存储过程和函数相似,可以将参数传递给游标并在查询中使用。这对于处理在某种条件下打开游标的情况非常有用。它的语法如下:
CURSOR cursor_name[(parameter[,parameter],...)] IS select_statement;
定义参数的语法如下:
Parameter_name [IN] data_type[{:=|DEFAULT} value]
与存储过程不同的是,游标只能接受传递的值,而不能返回值。参数只定义数据类型,没有大小。
另外可以给参数设定一个缺省值,当没有参数值传递给游标时,就使用缺省值。游标中定义的参数只是一个占位符,在别处引用该参数不一定可靠。
在打开游标时给参数赋值,语法如下:
OPEN cursor_name[value[,value]....];
参数值可以是文字或变量。
例:
DECALRE
CURSOR c_dept IS SELECT * FROM dept ORDER BY deptno;
CURSOR c_emp (p_dept VARACHAR2) IS
SELECT ename,salary
FROM emp
WHERE deptno=p_dept
ORDER BY ename
r_dept DEPT%ROWTYPE;
v_ename EMP.ENAME%TYPE;
v_salary EMP.SALARY%TYPE;
v_tot_salary EMP.SALARY%TYPE;
BEGIN
OPEN c_dept;
LOOP
FETCH c_dept INTO r_dept;
EXIT WHEN c_dept%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname);
v_tot_salary:=0;
OPEN c_emp(r_dept.deptno);
LOOP
FETCH c_emp INTO v_ename,v_salary;
EXIT WHEN c_emp%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Name:'|| v_ename||' salary:'||v_salary);
v_tot_salary:=v_tot_salary+v_salary;
END LOOP;
CLOSE c_emp;
DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary);
END LOOP;
CLOSE c_dept;
END;
游标FOR循环
在大多数时候我们在设计程序的时候都遵循下面的步骤:
1、打开游标
2、开始循环
3、从游标中取值
4、检查那一行被返回
5、处理
6、关闭循环
7、关闭游标
可以简单的把这一类代码称为游标用于循环。但还有一种循环与这种类型不相同,这就是FOR循环,用于FOR循环的游标按照正常的声明方式声明,它的优点在于不需要显式的打开、关闭、取数据,测试数据的存在、定义存放数据的变量等等。游标FOR循环的语法如下:
FOR record_name IN
(corsor_name[(parameter[,parameter]...)]
| (query_difinition)
LOOP
statements
END LOOP;
下面我们用for循环重写上面的例子:
DECALRE
CURSOR c_dept IS SELECT deptno,dname FROM dept ORDER BY deptno;
CURSOR c_emp (p_dept VARACHAR2) IS
SELECT ename,salary
FROM emp
WHERE deptno=p_dept
ORDER BY ename
v_tot_salary EMP.SALARY%TYPE;
BEGIN
FOR r_dept IN c_dept LOOP
DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname);
v_tot_salary:=0;
FOR r_emp IN c_emp(r_dept.deptno) LOOP
DBMS_OUTPUT.PUT_LINE('Name:' || v_ename || 'salary:' || v_salary);
v_tot_salary:=v_tot_salary+v_salary;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary);
END LOOP;
END;
在游标FOR循环中使用查询
在游标FOR循环中可以定义查询,由于没有显式声明所以游标没有名字,记录名通过游标查询来定义。
DECALRE
v_tot_salary EMP.SALARY%TYPE;
BEGIN
FOR r_dept IN (SELECT deptno,dname FROM dept ORDER BY deptno) LOOP
DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname);
v_tot_salary:=0;
FOR r_emp IN (SELECT ename,salary
FROM emp
WHERE deptno=p_dept
ORDER BY ename) LOOP
DBMS_OUTPUT.PUT_LINE('Name:'|| v_ename||' salary:'||v_salary);
v_tot_salary:=v_tot_salary+v_salary;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary);
END LOOP;
END;
游标中的子查询
语法如下:
CURSOR C1 IS SELECT * FROM emp
WHERE deptno NOT IN (SELECT deptno
FROM dept
WHERE dname!='ACCOUNTING');
可以看出与SQL中的子查询没有什么区别。
游标中的更新和删除
在PL/SQL中依然可以使用UPDATE和DELETE语句更新或删除数据行。显式游标只有在需要获得多行数据的情况下使用。PL/SQL提供了仅仅使用游标就可以执行删除或更新记录的方法。
UPDATE或DELETE语句中的WHERE CURRENT OF子串专门处理要执行UPDATE或DELETE操作的表中取出的最近的数据。要使用这个方法,在声明游标时必须使用FOR UPDATE子串,当对话使用FOR UPDATE子串打开一个游标时,所有返回集中的数据行都将处于行级(ROW-LEVEL)独占式锁定,其他对象只能查询这些数据行,不能进行UPDATE、DELETE或SELECT...FOR UPDATE操作。
语法:
FOR UPDATE [OF [schema.]table.column[,[schema.]table.column]..
[nowait]
在多表查询中,使用OF子句来锁定特定的表,如果忽略了OF子句,那么所有表中选择的数据行都将被锁定。如果这些数据行已经被其他会话锁定,那么正常情况下ORACLE将等待,直到数据行解锁。
在UPDATE和DELETE中使用WHERE CURRENT OF子串的语法如下:
WHERE{CURRENT OF cursor_name|search_condition}
例:
DELCARE
CURSOR c1 IS SELECT empno,salary
FROM emp
WHERE comm IS NULL
FOR UPDATE OF comm;
v_comm NUMBER(10,2);
BEGIN
FOR r1 IN c1 LOOP
IF r1.salary<500 THEN
v_comm:=r1.salary*0.25;
ELSEIF r1.salary<1000 THEN
v_comm:=r1.salary*0.20;
ELSEIF r1.salary<3000 THEN
v_comm:=r1.salary*0.15;
ELSE
v_comm:=r1.salary*0.12;
END IF;
UPDATE emp;
SET comm=v_comm
WHERE CURRENT OF c1l;
END LOOP;
END
触发器一 触发器介绍
触发器是一种特殊的存储过程,它在插入,删除或修改特定表中
的数据时触发执行,它比数据库本身标准的功能有更精细和更复杂的
数据控制能力。数据库触发器有以下的作用:
* 安全性。可以基于数据库的值使用户具有操作数据库的某种权利。
# 可以基于时间限制用户的操作,例如不允许下班后和节假日
修改数据库数据。
# 可以基于数据库中的数据限制用户的操作,例如不允许股票
的价格的升幅一次超过10%。
* 审计。可以跟踪用户对数据库的操作。
# 审计用户操作数据库的语句。
# 把用户对数据库的更新写入审计表。
* 实现复杂的数据完整性规则。
# 实现非标准的数据完整性检查和约束。触发器可产生比规则
更为复杂的限制。与规则不同,触发器可以引用列或数据库对
象。例如,触发器可回退任何企图吃进超过自己保证金的期货。
# 提供可变的缺省值。
* 实现复杂的非标准的数据库相关完整性规则。触发器可以对数
据库中相关的表进行连环更新。例如,在auths表author_code列上的
删除触发器可导致相应删除在其它表中的与之匹配的行。
# 在修改或删除时级联修改或删除其它表中的与之匹配的行。
# 在修改或删除时把其它表中的与之匹配的行设成NULL值。
# 在修改或删除时把其它表中的与之匹配的行级联设成缺省值。
# 触发器能够拒绝或回退那些破坏相关完整性的变化,取消试
图进行数据更新的事务。当插入一个与其主健不匹配的外部键
时,这种触发器会起作用。例如,可以在books.author_code
列上生成一个插入触发器,如果新值与auths.author_code列
中的某值不匹配时,插入被回退。
* 同步实时地复制表中的数据。
* 自动计算数据值,如果数据的值达到了一定的要求,则进行特
定的处理。例如,如果公司的帐号上的资金低于5万元则立即给财务人
员发送警告数据。
ORACLE与SYBASE数据库的触发器有一定的区别,下面将分别讲述
这两种数据库触发器的作用和写法。
二 ORACLE 触发器
ORACLE产生数据库触发器的语法为:
create [or replace] trigger 触发器名 触发时间 触发事件
on 表名
[for each row]
pl/sql 语句
其中:
触发器名:触发器对象的名称。由于触发器是数据库自动执行
的,因此该名称只是一个名称,没有实质的用途。
触发时间:指明触发器何时执行,该值可取:
before---表示在数据库动作之前触发器执行;
after---表示在数据库动作之后出发器执行。
触发事件:指明哪些数据库动作会触发此触发器:
insert:数据库插入会触发此触发器;
update:数据库修改会触发此触发器;
delete:数据库删除会触发此触发器。
表 名:数据库触发器所在的表。
for each row:对表的每一行触发器执行一次。如果没有这一
选项,则只对整个表执行一次。
举例:下面的触发器在更新表auths之前触发,目的是不允许在
周末修改表:
create trigger auth_secure
before insert or update or delete //对整表更新前触发
on auths
begin
if(to_char(sysdate,'DY')='SUN'
RAISE_APPLICATION_ERROR(-20600,'不能在周末修改表auths');
end if;
end
三 SYBASE数据库触发器
SYBASE数据库触发器的作用与ORACLE非常类似,仅有较小的差异。
SYBASE产生触发器的语法为:
CREATE TRIGGER 触发器名
ON 表名
FOR INSERT,UPDATE,DELETE
AS
SQL_statement |
FOR INSERT,UPDATE
AS
IF UPDATE(column_name) [AND|OR UPDATE(column_name)]...
SQL_statements
上面FOR子句用来指定在触发器上的哪些数据更新命令可激活该
触发器。IF UPDATE子句检查对指定列的操作类型,在IF UPDATE子句
中可指定多个列。
与ORACLE不同,对于每条SQL语句,触发器只执行一次。触发器
在数据更新语句完成以后立即执行。触发器和启动它的语句被当作一
个事务处理,事务可以在触发器中回退。
下面举例说明SYBASE触发器的写法。
create trigger forinsert_books
on books
for insert
as
if(select count(*) from auths,inserted
where auths.author_code=insert.author_code)!=@@rowcount
begin
rollback transaction
print "books 表中 author_code 列的值在auths 表中不存在。"
end
----------------------------------------------------------------------------------------------------------
存储过程一 存储过程介绍
存储过程是由流控制和SQL语句书写的过程,这个过程经编译和优化
后存储在数据库服务器中,使用时只要调用即可。在ORACLE中,若干个
有联系的过程可以组合在一起构成程序包。
使用存储过程有以下的优点:
* 存储过程的能力大大增强了SQL语言的功能和灵活性。存储过程可
以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的
运算。
* 可保证数据的安全性和完整性。
# 通过存储过程可以使没有权限的用户在控制之下间接地存取数据
库,从而保证数据的安全。
# 通过存储过程可以使相关的动作在一起发生,从而可以维护数据
库的完整性。
* 再运行存储过程前,数据库已对其进行了语法和句法分析,并给出
了优化执行方案。这种已经编译好的过程可极大地改善SQL语句的性能。
由于执行SQL语句的大部分工作已经完成,所以存储过程能以极快的速度执
行。
* 可以降低网络的通信量。
* 使体现企业规则的运算程序放入数据库服务器中,以便:
# 集中控制。
# 当企业规则发生变化时在服务器中改变存储过程即可,无须修改
任何应用程序。企业规则的特点是要经常变化,如果把体现企业规则的运
算程序放入应用程序中,则当企业规则发生变化时,就需要修改应用程序
工作量非常之大(修改、发行和安装应用程序)。如果把体现企业规则的
运算放入存储过程中,则当企业规则发生变化时,只要修改存储过程就可
以了,应用程序无须任何变化。
不同数据库存储过程的写法不一,在后面的讲座中将分别介绍ORACLE
和SYBASE存储过程的用法。
二 ORACLE 的存储过程
ORACLE 创建存储过程的语法为:
create [or replace] procedure 过程名
参数1 [in|out|in out] 数据类型
[,参数2 [in|out|in out] 数据类型]...
{is|as} pl/sql 语句
下面举例说明ORACLE数据库存储过程的写法和用法。
可以建立一个存储过程,每当用户修改数据库的重要数据时,即把
用户的用户名、日期和操作类型记录下来:
create procedure update_log is
begin
insert into update_log_tab(use_name,update_date,operation)
values(user,sysdate,'update'
end;
可以在恰当的位置调用这个存储过程来记录用户对表的修改。例如下面在
表sal_comm上建立一个修改触发器,每当用户修改此表后,用户的名称、修改
时间和操作即被记录在了表update_log_tab中:
create trigger audit_update
after update on sal_comm
for each row
begin
update_log
end
三 Sybase的存储过程
尽管Sybase存储过程的功能和写法与ORACLE类似,但他们之间还是
有一定的差别。下面讲述SYBASE的存储过程。
SYBASE可以用CREATE PROCedure命令生成存储过程:
CREATE PROCedure 存储过程名 [;number]
[[(] @parameter_name datatype [=default] [OUTput]
[, @parameter_name datatype [=default] [OUTput]]...[)]]
[WITH RECOMPILE]
AS SQL_statements
下面是一个查询作者编码、名称和生日的存储过程:
create proc p_auths @author_code varchar(10)
as
select author_code, name, birthdate
from auths
where author_code=@author_code
下面执行过程p_auths:
p_auths @author_code=A00001
在CREATE PROC语句中,可以为参数赋缺省值,该值可以是任何常量。
当用户不提供参数值时,该值便作为参数值提供给过程。
Sybase的存储过程是集中存储在SQL Server中的预先定义且已经编译好的事务。存储过程由SQL语句和流程控制语句组成。
它的功能包括:接受参数;调用另一过程;返回一个状态值给调用过程或批处理,指示调用成功或失败;返回若干个参数值给调
用过程或批处理,为调用者提供动态结果;在远程SQL Server中运行等。
存储过程的性能特
1.存储过程是预编译过的,这就意味着它与普通的SQL语句或批处理的SQL语句不同。当首次运行一个存储过程时,SQL Server的查询处理器对其进行分析,在排除了语法错误之后形成存储在系统中的可执行方案。由于查询处理的大部分工作已经完成,所以存储过程执行速度很快。
2.存储过程和待处理的数据都放在同一台运行SQL Server的计算机上,使用存储过程查询当地的数据,效率自然很高。
3.存储过程一般多由Client端通过存储过程的名字进行调用,即跨网传送的只是存储过程的名字及少量的参数(如果有的话),而不是构成存储过程的许多SQL语句,因此可以减少网络传输量,加快系统响应速度。
4.存储过程还有着如同C语言子函数那样的被调用和返回值的方便特性。所以,存储过程大大增强了SQL语言的功能、效率和灵活性。掌握和应用好存储过程,对进一步发挥Sybase数据库系统的强大功能有着重要的意义。
存储过程的语法规则
建立存储过程的语法规则为:
CREATE PROCedure[owner.]procedurename[;number]
[[(]@parameter_name datatype[=default][OUTput]
[,@parameter_name datatype[=default][OUTput]]...[)]]
[WITH RECOMPILE]
AS SQL_statements
使用存储过程的语法规则为:
[EXECute][@return-status=]
[[[server.]database.]owner.]procedurename[;number]
[[@parameter_name=]value|[@parameter_name=]@varialbe[OUT put]
[,[@parameter_name=]value|[@parameter_name=]@variable[OU Tput]...]]
[WITH RECOMPILE]
下面简要介绍这两个命令的常用选项以及建立和使用存储过程的要点,关于选项的更为详细的说明请参考有关手册。
1.[[[server.]database.]owner.]procedure_name:存储过程的名字。
2.@parameter_name datatype[=default][OUTput]:形式参数(形参)的名称、类型。dfault是赋予的缺省值(可选),OUTput指定本参数为输出参数(可选)。形参是存储过程中的自变量,可以有多个,名字必须以@打头,最长30个字符。
3.SQL_statements:定义存储过程功能的SQL语句。
4.@return_status:接受存储过程返回状态值的变量。
5.[@parameter_name=]value:实际参数(实参),@parameter_name 为实参的名称(可选)。如果某个实参以@parameter_name=value提供, 那么随后的实参也都要采用这一形式提供。
6.[@parameter_name=]@varialbe[OUTput]:将变量@varialbe中的值作为实参传递给形参@parameter_name(可选),如果变量@varialb e是用来接受返回的参数值,则选项OUTput不可缺少。
存储过程的建立和使用
我们将通过几个例子进行介绍。
假设有一个用下述语句生成的技能工资表RS_LS_GZ_JiNeng:
create table RS_LS_GZ_JiNeng /*技能工资表*/
(GeRen_id char(4), /*个人代码*/
RiQi smalldatetime, /*执行日期*/
YuanYin_id char(1) null, /*变动原因代码*/
JinE smallmoney) /*技能工资金额*/
该表存储着某单位员工多年来技能工资的历史档案。
例1.如果要查询全体员工的技能工资变动历史,则可先建立一个存储过程p_RsGz_JiNeng_All:
create procedure p_RsGz_JiNeng_All
as
select *
from RS_LS_GZ_JiNeng
order by GeRenid,RiQi
然后用批处理语句调用存储过程p_RsGz_JiNeng_All进行查询:
execute p_RsGz_JiNeng_All
本例只显示查询到的数据,无输入、输出参量,是最简单的一个存储过程。
例2.如果要查询某人技能工资的变动历史,可建立另一个存储过程p_RsGz_JiNeng:
create procedure p_RsGz_JiNeng
@c_GeRenId char(4)
as
select *from RS_LS_GZ_JiNeng
where GeRen_id=@c_GeRenId
order by RiQi
之后用批处理语句调用存储过程p_Rs_Gz_JiNeng进行查询:
declare @GeRenId char(4)
select @GeRenId="0135" /*设要查询员工的个人代码为"0135" */
execute p_RsGz_JeNeng @c_GeRenId=@GeRenId
存储过程p_RsGz_JiNeng中定义了一个形参@c_GeRenId,是字符型变量。在调用该过程的批处理中,既可以用具体的值也可以用变量作为实参。用变量作实参(如本例)时,必须用delare语句加以说明。注意,在批处理的调用过程语句@c_GeRenId=@GeRenId中的@c_GeRenId是存储过程p_RsGz_JiNeng中的形参名,不是批处理中的变量,所以不能将它列入d eclare语句的变量单中。
例3.如果要计算当月工资,就必须从工资历史中查出员工距离当前最近的一次技能工资变动的结果:
create procedure p_RsGz_JiNeng_Slt
(@c_GeRenId char(4),@sm_JinE smallmoney output)
as
select @sm_JinE=JinE
from RS_LS_GZ_JiNeng
where RiQi=(select max(RiQi)
from RS_LS_GZ_JiNeng
where GeRenid=@c-GeRenId)/*找出历史记录中距离当前最近的日期*/
调用存储过程p_RsGz_JiNeng_Slt进行查询:
declare @GeRenId char(4),@JinE smallmoney
select @GeRenid="0135"/*设要查询员工的个人代码为"0135"*/
select @JinE=0
execute p_RsGz_JiNeng_slt @c_GeRenId=@GeRenId,@sm_JinE=@ JinE output
这里,变量@JinE用来存储过程形参@sm_JinE传回的金额。在调用过程语句中,@sm_JiE = @JinE output中的output不可省略。否则, 变量@JinE将得不到形参传回的数值而始终为零(等于初值)。
例4.查到了个人代码为"0135"员工的技能工资就显示其历史纪录,查不到则显示一条出错信息。
create procedure p_RsGz_JiNeng_Rtn
@c_GeRenId char(4)
as
declare @ErrCode smallint
select @ErrCode=0
if exists(select* from RS-LS-GZ-JiNeng
where GeRenid=@c-GeRenId)
begin
select *
from RS_LS_GZ_JiNeng
whrer GeRen_id=@c_GeRenId
order by RiQi
return @ErrCode
end
esle
begin
select @ErrCode=1
return @ErrCode
end
调用存储过程p_RsGz_JiNeng_Rtn:
declare @GeRenId char(4),@RtnCode smallint
select @GeRenId="0135"
select @RtnCode=0
execute @RtnCode=p_RsGz_JiNeng_Rtn @c_GeRenId=@GeRenId
if @RtnCode=1
print"No this one!"
存储过程p_RsGz_JiNeng_Rtn向调用者返回一个存储在变量@ErrC ode里的值,这个值被称为状态值,它向调用者反映存储过程执行的成败状态。在本例中,如果查不到指定员工技能工资的任何记录时,就认为"查无此人",返回出错状态值1。否则,返回成功状态值0。
调用过程的批处理语句使用变量@RtnCode存储返回的状态值,一旦检出存储过程p_RsG_ JiNeng_Rtn返回了错误标志(@RtnCode=1),就显示一条信息"No this one!"。
小结
上述四个例子简要介绍了存储过程常用的几种形式,从中我们已经可以领略到它的编程特色以及使用上的灵活性和方便性。
虽然上述例子在调用存储过程时都是用SQL的批处理语句实现的, 但并不意味着这是唯一的方法。例如在存储过程中调用存储过程(即所谓过程嵌套)的现象就很常见。另外,在其它Sybase数据库开发系统(如PowerBuilder)的script语句中调用Sybase的存储过程也非常普遍。
需求描述:目前远程服务器有两个数据库AA和BB,两个数据库的数据库版本都为10.2.1.0.1,两个数据库中的用户不一样,但数据库中的表结构都是一样的,数据库AA表中的数据比数据库BB表中的数据多很多,现在需要把数据库AA中几十张表的数据迁移到数据库BB的表中。如果在数据库AA中存在的数据,但在数据库BB中没有的数据,则需要把这些数据导入到数据库BB中。
比如:数据库AA 的数据库名称为AA,所有的表都在A_UserName用户下,改用户的密码为A_Password;
数据库BB 的数据库名称为BB,所有的表都在B_UserName用户下,改用户的密码为B_Password;
假设本机是一台windows系统的PC机,装有Oracle数据库10.2.1.0.1的版本(服务器版或客户端版都可以),在该系统已经配置好数据库的监听器($ORACLE_HOME\NETWORK\ADMIN\tnsnames.ora).
下面导出、导入两个表为例子(当然几十个表也是没有问题的)
1 导出数据库AA中两表的所有数据:
D:\>expA_UserName/A_Password@AAtables=(DH_REP_WORK,DRILL_ADM_YEAR_TARGET) file=20081029 log=20081029.log
Export: Release 10.2.0.1.0 - Production on 星期三 10月 29 16:11:20 2008
Copyright (c) 1982, 2005, Oracle. All rights reserved. |
连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production With the Partitioning, OLAP and Data Mining options
|
已导出 ZHS16GBK 字符集和 AL16UTF16 NCHAR 字符集
即将导出指定的表通过常规路径...
. . 正在导出表 DH_REP_WORK导出了 1 行
. . 正在导出表 DRILL_ADM_YEAR_TARGET导出了 40 行
成功终止导出, 没有出现警告。 |
从上面可以看出来成功导出来远程数据库AA中两表的数据。
2 导入到数据库BB中(请先看下面的错误原因)
D:\>impB_UserName/B_Password@BBtables=(DH_REP_WORK,DRILL_ADM_YEAR_TARGET) file=20081029 log=20081029imp.log
Import: Release 10.2.0.1.0 - Production on 星期三 10月 29 16:13:33 2008
Copyright (c) 1982, 2005, Oracle. All rights reserved.
连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production With the Partitioning, OLAP and Data Mining options
经由常规路径由 EXPORT:V10.02.01 创建的导出文件IMP-00013: 只有 DBA 才能导入由其他 DBA 导出的文件IMP-00000: 未成功终止导入
不能导入原来是B_UserName用户没有具有DBA角色的权限。
3、登陆到数据库BB中,用超级用户sys给B_UserName用户授权
SQL> grant dba to B_UserName;
Grant succeeded. |
4 、现在重新导入到数据库BB中,为了减少日志的错误使用ignore=yes(忽略错误),rows=y是导入表中的数据,grants=no表示不导入授权(假如在数据库AA中表DH_REP_WORK授权给了test用户,但数据库BB中没有test用户,则不用执行授权给test用户)。
D:\>imp B_UserName/B_Password@BB tables=(DH_REP_WORK,DRILL_ADM_YEAR_TARGET) rows=y ignore=yes grants=no file=20081029 log=20081029Imp.log
执行上面的语句,可能会有些错误提示,一般是在数据库BB中的已经存在该数据,由于主键的唯一性,所以不能插入。但数据库AA中存在、但数据库BB中不存在的数据就可以顺利的插入到数据库BB中了。
5、登陆到数据库BB中,用超级用户sys回收B_UserName用户dba的角色:
SQL> revoke dba from B_UserName;
Revoke succeeded. |
从两个表达式返回一个非 null 值。
语法
NVL(eExpression1, eExpression2)
参数
eExpression1, eExpression2
如果 eExpression1 的计算结果为 null 值,则 NVL( ) 返回 eExpression2。如果 eExpression1 的计算结果不是 null 值,则返回 eExpression1。eExpression1 和 eExpression2 可以是任意一种数据类型。如果 eExpression1 与 eExpression2 的结果皆为 null 值,则 NVL( ) 返回 .NULL.。
返回值类型
字符型、日期型、日期时间型、数值型、货币型、逻辑型或 null 值
说明
在不支持 null 值或 null 值无关紧要的情况下,可以使用 NVL( ) 来移去计算或操作中的 null 值。
select nvl(a.name,'空得') as name from student a join school b on a.ID=b.ID
注意:两个参数得类型要匹配
实际的写法可以这样,如果在对材料做入库的时候有个入库金额的字段rkje,然后在做累加操作的时候可以这样写:
rkje=nvl(rkje,0.00)+"+rkje2_1+",表示如果开始数据库值是null的话就用0.00表示,然后再累加新的数据rkje2_1。
去掉字符串的前后空格
代码 :
function trim(str){
return str.replace(/^\ +|\ +$/ig,"");
}
Java实现四舍五入方法
方法一 :
double source = 3.129;
java.text.DecimalFormat df = new java.text.DecimalFormat("#.00"); // 保留小数点后两位
String aveprice=df.format(source);
方法二 :
double source = 3.129;
BigDecimal bd = new BigDecimal(source);
bd.setScale(2,BigDecimal.ROUND_HALF_EVEN).floatValue() ; // 保留小数点后两位
各种数据库取表前N条记录的写法:
1.Oracle
Select * from table where rowNum<=n
2.Informix
Select first n* from table
3.DB2
Select * row_number() over(order by col1 desc) as rownum where rownum<=n
DB2
Select column from table fetch first n rows only
4.SqlServer
Select top n* from table
5.sysbase
Select top n* from table
6.mysql
Select * from table limit n
1.什么是模式?
模式,即pattern,其实就是解决某一类问题的方法论,你把解决某类问题的方法总结归纳到理论高度,这就是模式。
Alexander给出的经典定义是:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需再重复相同的工作。
模式有不同的领域,建筑领域有建筑模式,软件设计领域也有设计模式。当一个领域逐渐成熟的时候,自然会出现很多模式。
什么是框架?
框架,即framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单地说就是使用别人搭建好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。
2。为什么要用模式?
因为模式是一种指导,在一个良好的指导下,有助于你完成任务,有助于你作出一个优良的设计方案,达到事半功倍的效果。而且会得到解决问题的最佳办法。
为什么要使用框架?
因为软件系统发展到今天已经很复杂了,特别是服务器端软件,设计到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础的工作,你只需要集中精力完成系统业务逻辑设计。而且框架一般是成熟,稳健的,它可以处理系统很多细节的问题,比如,事物处理,安全性,数据流控制等问题。还有框架一般都是经过很多人使用,所以结构很好,所以扩展性也好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。
框架一般处在低层的应用平台(如J2EE)和高层业务逻辑之间的中间层。
软件为什么要分层?
为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于扩展,易于分配资源。。。
1. oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键
<table border oncontextmenu=return(false)><td>no</table> 可用于Table
2. <body onselectstart="return false"> 取消选取、防止复制
3. onpaste="return false" 不准粘贴
4. oncopy="return false;" oncut="return false;" 防止复制
5. <link rel="Shortcut Icon" href="favicon.ico"> IE地址栏前换成自己的图标
6. <link rel="Bookmark" href="favicon.ico"> 可以在收藏夹中显示出你的图标
7. <input style="ime-mode:disabled"> 关闭输入法
8. 永远都会带着框架
<script language="JavaScript"><!--
if (window == top)top.location.href = "frames.htm"; //frames.htm为框架网页
// --></script>
9. 防止被人frame
<SCRIPT LANGUAGE=JAVASCRIPT><!--
if (top.location != self.location)top.location=self.location;
// --></SCRIPT>
10. 网页将不能被另存为
<noscript><*** src="/*.html>";</***></noscript>
11. <input type=button value="/查看网页源代码
onclick="window.location = "view-source:"+ "http://www.pconline.com.cn"">
12.删除时确认
<a href=""javascript :if(confirm("确实要删除吗?"))location="boos.asp?&areyou=删除&page=1"">删除</a>
13. 取得控件的绝对位置
//Javascript
<script language="Javascript">
function getIE(e){
var t=e.offsetTop;
var l=e.offsetLeft;
while(e=e.offsetParent){
t+=e.offsetTop;
l+=e.offsetLeft;
}
alert("top="+t+"/nleft="+l);
}
</script>
//VBScript
<script language="VBScript"><!--
function getIE()
dim t,l,a,b
set a=document.all.img1
t=document.all.img1.offsetTop
l=document.all.img1.offsetLeft
while a.tagName<>"BODY"
set a = a.offsetParent
t=t+a.offsetTop
l=l+a.offsetLeft
wend
msgbox "top="&t&chr(13)&"left="&l,64,"得到控件的位置"
end function
--></script>
14. 光标是停在文本框文字的最后
<script language="javascript">
function cc()
{
var e = event.srcElement;
var r =e.createTextRange();
r.moveStart("character",e.value.length);
r.collapse(true);
r.select();
}
</script>
<input type=text name=text1 value="123" onfocus="cc()">
15. 判断上一页的来源
javascript :
document.referrer
16. 最小化、最大化、关闭窗口
<object id=hh1 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">
<param name="Command" value="Minimize"></object>
<object id=hh2 classid="clsid:ADB880A6-D8FF-11CF-9377-00AA003B7A11">
<param name="Command" value="Maximize"></object>
<OBJECT id=hh3 classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">
<PARAM NAME="Command" value="/Close"></OBJECT>
<input type=button value="/最小化 onclick=hh1.Click()>
<input type=button value="/blog/最大化 onclick=hh2.Click()>
<input type=button value=关闭 onclick=hh3.Click()>
本例适用于IE
17.屏蔽功能键Shift,Alt,Ctrl
<script>
function look(){
if(event.shiftKey)
alert("禁止按Shift键!"); //可以换成ALT CTRL
}
document.onkeydown=look;
</script>
18. 网页不会被缓存
<META HTTP-EQUIV="pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
<META HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
或者<META HTTP-EQUIV="expires" CONTENT="0">
19.怎样让表单没有凹凸感?
<input type=text style="""border:1 solid #000000">
或
<input type=text style="border-left:none; border-right:none; border-top:none; border-bottom:
1 solid #000000"></textarea>
20.<div><span>&<layer>的区别?
<div>(division)用来定义大段的页面元素,会产生转行
<span>用来定义同一行内的元素,跟<div>的唯一区别是不产生转行
<layer>是ns的标记,ie不支持,相当于<div>
21.让弹出窗口总是在最上面:
<body onblur="this.focus();">
22.不要滚动条?
让竖条没有:
<body style="overflow:scroll;overflow-y:hidden">
</body>
让横条没有:
<body style="overflow:scroll;overflow-x:hidden">
</body>
两个都去掉?更简单了
<body scroll="no">
</body>
23.怎样去掉图片链接点击后,图片周围的虚线?
<a href="#" onFocus="this.blur()"><img src="/logo.jpg" border=0></a>
24.电子邮件处理提交表单
<form name="form1" method="post" action=mailto:****@***.com
enctype="text/plain">
<input type=submit>
</form>
25.在打开的子窗口刷新父窗口的代码里如何写?
window.opener.location.reload()
26.如何设定打开页面的大小
<body onload="top.resizeTo(300,200);">
打开页面的位置<body onload="top.moveBy(300,200);">
27.在页面中如何加入不是满铺的背景图片,拉动页面时背景图不动
<STYLE>
body
{background-image:url(/logo.gif); background-repeat:no-repeat;
background-position:center;background-attachment: fixed}
</STYLE>
28. 检查一段字符串是否全由数字组成
<script language="Javascript"><!--
function checkNum(str){return str.match(//D/)==null}
alert(checkNum("1232142141"))
alert(checkNum("123214214a1"))
// --></script>
29. 获得一个窗口的大小
document.body.clientWidth; document.body.clientHeight
30. 怎么判断是否是字符
if (/[^/x00-/xff]/g.test(s)) alert("含有汉字");
else alert("全是字符");
31.TEXTAREA自适应文字行数的多少
<textarea rows=1 name=s1 cols=27 onpropertychange
="this.style.posHeight=this.scrollHeight">
</textarea>
32. 日期减去天数等于第二个日期
<script language=Javascript>
function cc(dd,dadd)
{
//可以加上错误处理
var a = new Date(dd)
a = a.valueOf()
a = a - dadd * 24 * 60 * 60 * 1000
a = new Date(a)
alert(a.getFullYear() + "年" + (a.getMonth() + 1) + "月" + a.getDate() + "日")
}
cc("12/23/2002",2)
</script>
33. 选择了哪一个Radio
<HTML><script language="vbscript">
function checkme()
for each ob in radio1
if ob.checked then
window.alert ob.value
next
end function
</script><BODY>
<INPUT name="radio1" type="radio" value="/style" checked>Style
<INPUT name="radio1" type="radio" value="/blog/barcode">Barcode
<INPUT type="button" value="check" onclick="checkme()">
</BODY></HTML>
34.脚本永不出错
<SCRIPT LANGUAGE="JavaScript">
<!-- Hide
function killErrors() {
return true;
}
window.onerror = killErrors;
// -->
</SCRIPT>
35.ENTER键可以让光标移到下一个输入框
<input onkeydown="if(event.keyCode==13)event.keyCode=9">
36. 检测某个网站的链接速度:
把如下代码加入<body>区域中:
<script language=Javascript>
tim=1
setInterval("tim++",100)
b=1
var autourl=new Array()
autourl[1]=1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>www.njcatv.net"
autourl[2]="javacool.3322.net"
autourl[3]=1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>www.sina.com.cn"
autourl[4]="www.nuaa.edu.cn"
autourl[5]=1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>www.cctv.com"
function butt(){
***("<form name=autof>")
for(var i=1;i<autourl.length;i++)
***("<input type=text name=txt"+i+" size=10 value="/测试中……> =》<input type=text
name=url"+i+" size=40> =》<input type=button value="/blog/GO
onclick=window.open(this.form.url"+i+".value)><br>")
***("<input type=submit value=刷新></form>")
}
butt()
function auto(url){
document.forms[0]["url"+b].value=url
if(tim>200)
{document.forms[0]["txt"+b].value="/链接超时"}
else
{document.forms[0]["txt"+b].value="/blog/时间"+tim/10+"秒"}
b++
}
function run(){for(var i=1;i<autourl.length;i++)***("<img src=http://"+autourl+"/"+Math.random()+" width=1 height=1
onerror=auto("http://"+autourl+"")>")}
run()</script>
37. 各种样式的光标
auto :标准光标
default :标准箭头
hand :手形光标
wait :等待光标
text :I形光标
vertical-text :水平I形光标
no-drop :不可拖动光标
not-allowed :无效光标
help :?帮助光标
all-scroll :三角方向标
move :移动标
crosshair :十字标
e-resize
n-resize
nw-resize
w-resize
s-resize
se-resize
sw-resize
38.页面进入和退出的特效
进入页面<meta http-equiv="Page-Enter" content="revealTrans(duration=x, transition=y)">
推出页面<meta http-equiv="Page-Exit" content="revealTrans(duration=x, transition=y)">
这个是页面被载入和调出时的一些特效。duration表示特效的持续时间,以秒为单位。transition表示使用哪种特效,取值为1-23:
0 矩形缩小
1 矩形扩大
2 圆形缩小
3 圆形扩大
4 下到上刷新
5 上到下刷新
6 左到右刷新
7 右到左刷新
8 竖百叶窗
9 横百叶窗
10 错位横百叶窗
11 错位竖百叶窗
12 点扩散
13 左右到中间刷新
14 中间到左右刷新
15 中间到上下
16 上下到中间
17 右下到左上
18 右上到左下
19 左上到右下
20 左下到右上
21 横条
22 竖条
23 以上22种随机选择一种
39.在规定时间内跳转
<META http-equiv=V="REFRESH" content="5;URL=http://www.51js.com">
40.网页是否被检索
<meta name="ROBOTS" content="属性值">
其中属性值有以下一些:
属性值为"all": 文件将被检索,且页上链接可被查询;
属性值为"none": 文件不被检索,而且不查询页上的链接;
属性值为"index": 文件将被检索;
属性值为"follow": 查询页上的链接;
属性值为"noindex": 文件不检索,但可被查询链接;
属性值为"nofollow": 文件不被检索,但可查询页上的链接。
41、email地址的分割
把如下代码加入<body>区域中
<a href="mailto:webmaster@sina.com">webmaster@sina.com</a>
42、流动边框效果的表格
把如下代码加入<body>区域中
<SCRIPT>
l=Array(6,7,8,9,'a','b','b','c','d','e','f')
Nx=5;Ny=35
t="<table border=0 cellspacing=0 cellpadding=0 height="+((Nx+2)*16)+"><tr>"
for(x=Nx;x<Nx+Ny;x++)
t+="<td width=16 id=a_mo"+x+"> </td>"
t+="</tr><tr><td width=10 id=a_mo"+(Nx-1)+"> </td><td colspan="+(Ny-2)+" rowspan="+(Nx)+"> </td><td width=16 id=a_mo"+(Nx+Ny)+"></td></tr>"
for(x=2;x<=Nx;x++)
t+="<tr><td width=16 id=a_mo"+(Nx-x)+"> </td><td width=16 id=a_mo"+(Ny+Nx+x-1)+"> </td></tr>"
t+="<tr>"
for(x=Ny;x>0;x--)
t+="<td width=16 id=a_mo"+(x+Nx*2+Ny-1)+"> </td>"
***(t+"</tr></table>")
var N=Nx*2+Ny*2
function f1(y){
for(i=0;i<N;i++){
c=(i+y)%20;if(c>10)c=20-c
document.all["a_mo"+(i)].bgColor=""""#0000"+l[c]+l[c]+"'"}
y++
setTimeout('f1('+y+')','1')}
f1(1)
</SCRIPT>
43、JavaScript主页弹出窗口技巧
窗口中间弹出
<script>
window.open("http://www.cctv.com","","width=400,height=240,top="+(screen.availHeight-240)/2+",left="+(screen.availWidth-400)/2);
</script>
============
<html>
<head>
<script language="LiveScript">
function WinOpen() {
msg=open("","DisplayWindow","toolbar=no,directories=no,menubar=no");
msg.***("<HEAD><TITLE>哈 罗!</TITLE></HEAD>");
msg.***("<CENTER><H1>酷 毙 了!</H1><h2>这 是<B>JavaScript</B>所 开 的 视 窗!</h2></CENTER>");
}
</script>
</head>
<body>
<form>
<input type="button" name="Button1" value="Push me" onclick="WinOpen()">
</form>
</body>
</html>
==============
一、在下面的代码中,你只要单击打开一个窗口,即可链接到赛迪网。而当你想关闭时,只要单击一下即可关闭刚才打开的窗口。
代码如下:
<SCRIPT language="JavaScript">
<!--
function openclk() {
another=open('1000){this.resized=true;this.style.width=1000;}" align=absMiddle border=0>http://www.ccidnet.com','NewWindow');
}
function closeclk() {
another.close();
}
//-->
</SCRIPT>
<FORM>
<INPUT TYPE="BUTTON" NAME="open" value="/打开一个窗口" onClick="openclk()">
<BR>
<INPUT TYPE="BUTTON" NAME="close" value="/blog/关闭这个窗口" onClick="closeclk()">
</FORM>
二、上面的代码也太静了,为何不来点动感呢?如果能给页面来个降落效果那该多好啊!
代码如下:
<script>
function drop(n) {
if(self.moveBy){
self.moveBy (0,-900);
for(i = n; i > 0; i--){
self.moveBy(0,3);
}
for(j = 8; j > 0; j--){
self.moveBy(0,j);
self.moveBy(j,0);
self.moveBy(0,-j);
self.moveBy(-j,0);
}
}
}
</script>
<body onLoad="drop(300)">
三、讨厌很多网站总是按照默认窗口打开,如果你能随心所欲控制打开的窗口那该多好。
代码如下:
<SCRIPT LANGUAGE="JavaScript">
<!-- Begin
function popupPage(l, t, w, h) {
var windowprops = "location=no,scrollbars=no,menubars=no,toolbars=no,resizable=yes" +
",left=" + l + ",top=" + t + ",width=" + w + ",height=" + h;
var URL = "http://www.80cn.com";
popup = window.open(URL,"MenuPopup",windowprops);
}
// End -->
</script>
<table>
<tr>
<td>
<form name=popupform>
<pre>
打开页面的参数<br>
离开左边的距离: <input type=text name=left size=2 maxlength=4> pixels
离开右边的距离: <input type=text name=top size=2 maxlength=4> pixels
窗口的宽度: <input type=text name=width size=2 maxlength=4> pixels
窗口的高度: <input type=text name=height size=2 maxlength=4> pixels
</pre>
<center>
<input type=button value="打开这个窗口!" onClick="popupPage(this.form.left.value, this.form.top.value, this.form.width.value,
this.form.height.value)">
</center>
</form>
</td>
</tr>
</table>你只要在相对应的对话框中输入一个数值即可,将要打开的页面的窗口控制得很好。
44、页面的打开移动
把如下代码加入<body>区域中
<SCRIPT LANGUAGE="JavaScript">
<!-- Begin
for (t = 2; t > 0; t--) {
for (x = 20; x > 0; x--) {
for (y = 10; y > 0; y--) {
parent.moveBy(0,-x);
}
}
for (x = 20; x > 0; x--) {
for (y = 10; y > 0; y--) {
parent.moveBy(0,x);
}
}
for (x = 20; x > 0; x--) {
for (y = 10; y > 0; y--) {
parent.moveBy(x,0);
}
}
for (x = 20; x > 0; x--) {
for (y = 10; y > 0; y--) {
parent.moveBy(-x,0);
}
}
}
//-->
// End -->
</script>
45、显示个人客户端机器的日期和时间
<script language="LiveScript">
<!-- Hiding
today = new Date()
***("现 在 时 间 是: ",today.getHours(),":",today.getMinutes())
***("<br>今 天 日 期 为: ", today.getMonth()+1,"/",today.getDate(),"/",today.getYear());
// end hiding contents -->
</script>
46、自动的为你每次产生最後修改的日期了:
<html>
<body>
This is a simple HTML- page.
<br>
Last changes:
<script language="LiveScript">
<!-- hide script from old browsers
***(document.lastModified)
// end hiding contents -->
</script>
</body>
</html>
47、不能为空和邮件地址的约束:
<html>
<head>
<script language="JavaScript">
<!-- Hide
function test1(form) {
if (form.text1.value == "")
alert("您 没 写 上 任 何 东 西, 请 再 输 入 一 次 !")
else {
alert("嗨 "+form.text1.value+"! 您 已 输 入 完 成 !");
}
}
function test2(form) {
if (form.text2.value == "" ||
form.text2.value.indexOf('@', 0) == -1)
alert("这 不 是 正 确 的 e-mail address! 请 再 输 入 一 次 !");
else alert("您 已 输 入 完 成 !");
}
// -->
</script>
</head>
<body>
<form name="first">
Enter your name:<br>
<input type="text" name="text1">
<input type="button" name="button1" value="输 入 测 试" onClick="test1(this.form)">
<P>
Enter your e-mail address:<br>
<input type="text" name="text2">
<input type="button" name="button2" value="输 入 测 试" onClick="test2(this.form)">
</body>
48、跑马灯
<html>
<head>
<script language="JavaScript">
<!-- Hide
var scrtxt="怎麽样 ! 很酷吧 ! 您也可以试试."+"Here goes your message the visitors to your
page will "+"look at for hours in pure fascination...";
var lentxt=scrtxt.length;
var width=100;
var pos=1-width;
function scroll() {
pos++;
var scroller="";
if (pos==lentxt) {
pos=1-width;
}
if (pos<0) {
for (var i=1; i<=Math.abs(pos); i++) {
scroller=scroller+" ";}
scroller=scroller+scrtxt.substring(0,width-i+1);
}
else {
scroller=scroller+scrtxt.substring(pos,width+pos);
}
window.status = scroller;
setTimeout("scroll()",150);
}
//-->
</script>
</head>
<body onLoad="scroll();return true;">
这里可显示您的网页 !
</body>
</html>
49、在网页中用按钮来控制前页,后页和主页的显示。
<html>
<body>
<FORM NAME="buttonbar">
<INPUT TYPE="button" VALUE="Back" onClick="history.back()">
<INPUT TYPE="button" VALUE="JS- Home" onClick="location='script.html'">
<INPUT TYPE="button" VALUE="Next" onCLick="history.forward()">
</FORM>
</body>
</html>
50、查看某网址的源代码
把如下代码加入<body>区域中
<SCRIPT>
function add()
{
var ress=document.forms[0].luxiaoqing.value
window.location="view-source:"+ress;
}
</SCRIPT>
输入要查看源代码的URL地址:
<FORM><input type="text" name="luxiaoqing" size=40 value="http://"></FORM>
<FORM><br>
<INPUT type="button" value="查看源代码" onClick=add()>
</FORM>
51、title显示日期
把如下代码加入<body>区域中:
<script language="JavaScript1.2">
<!--hide
var isnMonth = new
Array("1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月");
var isnDay = new
Array("星期日","星期一","星期二","星期三","星期四","星期五","星期六","星期日");
today = new Date () ;
Year=today.getYear();
Date=today.getDate();
if (document.all)
document.title="今天是: "+Year+"年"+isnMonth[today.getMonth()]+Date+"日"+isnDay[today.getDay()]
//--hide-->
</script>
52、显示所有链接
把如下代码加入<body>区域中
<script language="JavaScript1.2">
<!--
function extractlinks(){
var links=document.all.tags("A")
var total=links.length
var win2=window.open("","","menubar,scrollbars,toolbar")
win2.***("<font size='2'>一共有"+total+"个连接</font><br>")
for (i=0;i<total;i++){
win2.***("<font size='2'>"+links[i].outerHTML+"</font><br>")
}
}
//-->
</script>
<input type="button" onClick="extractlinks()" value="显示所有的连接">
53、回车键换行
把如下代码加入<body>区域中
<script type="text/javascript">
function handleEnter (field, event) {
var keyCode = event.keyCode ? event.keyCode : event.which ?
event.which : event.charCode;
if (keyCode == 13) {
var i;
for (i = 0; i < field.form.elements.length; i++)
if (field == field.form.elements[i])
break;
i = (i + 1) % field.form.elements.length;
field.form.elements[i].focus();
return false;
}
else
return true;
}
</script>
<form>
<input type="text" onkeypress="return handleEnter(this, event)"><br>
<input type="text" onkeypress="return handleEnter(this, event)"><br>
<textarea>回车换行
54、确认后提交
把如下代码加入<body>区域中
<SCRIPT LANGUAGE="JavaScript">
<!--
function msg(){
if (confirm("你确认要提交嘛!"))
document.lnman.submit()
}
//-->
</SCRIPT>
<form name="lnman" method="post" action="">
<p>
<input type="text" name="textfield" value="确认后提交">
</p>
<p>
<input type="button" name="Submit" value="提交" onclick="msg();">
</p>
</form>
55、改变表格的内容
把如下代码加入<body>区域中
<script ***script>
var arr=new Array()
arr[0]="一一一一一";
arr[1]="二二二二二";
arr[2]="三三三三三";
</script>
<select onchange="zz.cells[this.selectedIndex].innerHTML=arr[this.selectedIndex]">
<option value=a>改变第一格</option>
<option value=a>改变第二格</option>
<option value=a>改变第三格</option>
</select>
<table id=zz border=1>
<tr height=20>
<td width=150>第一格</td>
<td width=150>第二格</td>
<td width=150>第三格</td>
</tr>
</table>
摘要: struts+spring+hibernate的web应用<一> 架构搭建
开发工具:
Eclipse 3.2 + NLpack1-eclipse-SDK-3.2.1-win32( 中文语言包 )
插件:
tomcatPluginV31 :用于 tomcat 项目建立和管理。
Properties Editor : struts 中的资源国际化的中文编辑插件,不需要...
阅读全文
软件环境:
1、Windows NT4.0+ORACLE 8.0.4
2、ORACLE安装路径为:C:"ORANT
问题提出:
1、当我们想要为一个表创建唯一索引时,如果该表有重复的记录,则无法创建成功。
方法原理:
1、Oracle中,每一条记录都有一个rowid,rowid在整个数据库中是唯一的,
rowid确定了每条记录是在ORACLE中的哪一个数据文件、块、行上。
2、在重复的记录中,可能所有列的内容都相同,但rowid不会相同,所以只要确定出重复记录中
那些具有最大rowid的就可以了,其余全部删除。
3、以下语句用到了3项技巧:rowid、子查询、别名。
实现方法:
SQL> create table a (
2 bm char(4), --编码
3 mc varchar2(20) --名称
4 )
5 /
表已建立.
SQL> insert into a values('1111','1111');
SQL> insert into a values('1112','1111');
SQL> insert into a values('1113','1111');
SQL> insert into a values('1114','1111');
SQL> insert into a select * from a;
插入4个记录.
SQL> commit;
完全提交.
SQL> select rowid,bm,mc from a;
ROWID BM MC
------------------ ---- -------
000000D5.0000.0002 1111 1111
000000D5.0001.0002 1112 1111
000000D5.0002.0002 1113 1111
000000D5.0003.0002 1114 1111
000000D5.0004.0002 1111 1111
000000D5.0005.0002 1112 1111
000000D5.0006.0002 1113 1111
000000D5.0007.0002 1114 1111
查询到8记录.
查出重复记录
SQL> select rowid,bm,mc from a where a.rowid!=(select max(rowid) from a b where a.bm=b.bm and a.mc=b.mc);
ROWID BM MC
------------------ ---- --------------------
000000D5.0000.0002 1111 1111
000000D5.0001.0002 1112 1111
000000D5.0002.0002 1113 1111
000000D5.0003.0002 1114 1111
删除重复记录
SQL> delete from a a where a.rowid!=(select max(rowid) from a b where a.bm=b.bm and a.mc=b.mc);
删除4个记录.
SQL> select rowid,bm,mc from a;
ROWID BM MC
------------------ ---- --------------------
000000D5.0004.0002 1111 1111
000000D5.0005.0002 1112 1111
000000D5.0006.0002 1113 1111
000000D5.0007.0002 1114 1111
come from :: http://dev.csdn.net/article/59/59333.shtm
-测试数据
/*-----------------------------
select * from tt
-----------------------------*/
id pid
----------- -----------
1 1
1 1
2 2
3 3
3 3
3 3
(所影响的行数为 6 行)
首先,如何查询table中有重复记录
select *,count(1) as rownum
from tt
group by id, pid
having count(1) > 1
id pid rownum
----------- ----------- -----------
1 1 2
3 3 3
(所影响的行数为 2 行)
方法一:使用distinct和临时表
if object_id('tempdb..#tmp') is not null
drop table #tmp
select distinct * into #tmp from tt
truncate table tt
insert into tt select * from #tmp
方法二:添加标识列
alter table tt add NewID int identity(1,1)
go
delete from tt where exists(select 1 from tt a where a.newid>tt.newid and tt.id=a.id and tt.pid=a.pid)
go
alter table tt drop column NewID
go
--测试结果
/*-----------------------------
select * from tt
-----------------------------*/
id pid
----------- -----------
1 1
2 2
3 3
(所影响的行数为 3 行)
*---*-- * 8 8 * * * * 8* * * * 8 8 *
USE CEO
CREATE TABLE TT
(
TTNO CHAR(4),
TTNAME VARCHAR(10)
)
INSERT INTO TT (TTNO,TTNAME) VALUES ('1425','WHERE')
INSERT INTO TT (TTNO,TTNAME) VALUES ('1425','WHERE')
INSERT INTO TT (TTNO,TTNAME) VALUES ('1424','WHEREIS')
INSERT INTO TT (TTNO,TTNAME) VALUES ('1435','WHEREIS')
INSERT INTO TT (TTNO,TTNAME) VALUES ('1435','WHEREIS')
方法二:添加标识列(最有效方法)
alter table tt add newid2 int identity(1,1)
go
delete from tt where exists( select 1 from tt a where a.newid2>tt.newid2 and tt.ttno=a.ttno and tt.ttname=a.ttname)
alter table tt drop column newid2
go
select * from tt
开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明:
"^The": 开头一定要有"The"字符串;
"of despair$": 结尾一定要有"of despair" 的字符串;
那么,
"^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。
"notice": 匹配包含notice的字符串。
你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。
接着,说说 '*', '+',和 '?',
他们用来表示一个字符可以出现的次数或者顺序. 他们分别表示:
- "zero or more"相当于{0,},
- "one or more"相当于{1,},
- "zero or one."相当于{0,1}, 这里是一些例子:
- "ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等);
- "ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等.);
- "ab?":和ab{0,1}同义,可以没有或者只有一个b;
- "a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串.
要点, '*', '+',和 '?'只管它前面那个字符.
你也可以在大括号里面限制字符出现的个数,比如
- "ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb");
- "ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等.);
- "ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb").
现在我们把一定几个字符放到小括号里,比如:
- "a(bc)*": 匹配 a 后面跟0个或者一个"bc";
- "a(bc){1,5}": 一个到5个 "bc."
还有一个字符 '│', 相当于OR 操作:
"hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串;
"(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串;
"(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串;
一个点('.')可以代表所有的单一字符,不包括"\n"
如果,要匹配包括"\n"在内的所有单个字符,怎么办?
对了,用'[\n.]'这种模式.
"a.[0-9]": 一个a加一个字符再加一个0到9的数字
"^.{3}$": 三个任意字符结尾 .
中括号括住的内容只匹配一个单一的字符
"[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样);
"[a-d]": 匹配'a' 到'd'的单个字符 (和"a│b│c│d" 还有 "[abcd]"效果一样); 一般我们都用[a-zA-Z]来指定字符为一个大小写英文
"^[a-zA-Z]": 匹配以大小写字母开头的字符串
"[0-9]%": 匹配含有 形如 x% 的字符串
",[a-zA-Z0-9]$": 匹配以逗号再加一个数字或字母结尾的字符串
你也可以把你不想要得字符列在中括号里,你只需要在总括号里面使用'^' 作为开头 "%[^a-zA-Z]%" 匹配含有两个百分号里面有一个非字母的字符串.
要点:^用在中括号开头的时候,就表示排除括号里的字符。为了PHP能够解释,你必须在这些字符面前后加'',并且将一些字符转义.
不要忘记在中括号里面的字符是这条规路的例外?在中括号里面, 所有的特殊字符,包括(''), 都将失去他们的特殊性质 "[*\+?{}.]"匹配含有这些字符的字符串.
还有,正如regx的手册告诉我们: "如果列表里含有 ']', 最好把它作为列表里的第一个字符(可能跟在'^'后面). 如果含有'-', 最好把它放在最前面或者最后面, or 或者一个范围的第二个结束点[a-d-0-9]中间的‘-’将有效.
看了上面的例子,你对{n,m}应该理解了吧.要注意的是,n和m都不能为负整数,而且n总是小于m. 这样,才能 最少匹配n次且最多匹配m次. 如"p{1,5}"将匹配 "pvpppppp"中的前五个p.
下面说说以\开头的
\b 书上说他是用来匹配一个单词边界,就是...比如've\b',可以匹配love里的ve而不匹配very里有ve
\B 正好和上面的\b相反.例子我就不举了
.....突然想起来....可以到http://www.phpv.net/article.php/251 看看其它用\ 开头的语法
好,我们来做个应用:
如何构建一个模式来匹配 货币数量 的输入
构建一个匹配模式去检查输入的信息是否为一个表示money的数字。我们认为一个表示money的数量有四种方式: "10000.00" 和 "10,000.00",或者没有小数部分, "10000" and "10,000". 现在让我们开始构建这个匹配模式:
这是所变量必须以非0的数字开头.但这也意味着 单一的 "0" 也不能通过测试. 以下是解决的方法:
"只有0和不以0开头的数字与之匹配",我们也可以允许一个负号在数字之前:
这就是: "0 或者 一个以0开头 且可能 有一个负号在前面的数字." 好了,现在让我们别那么严谨,允许以0开头.现在让我们放弃 负号 , 因为我们在表示钱币的时候并不需要用到. 我们现在指定 模式 用来匹配小数部分:
这暗示匹配的字符串必须最少以一个阿拉伯数字开头. 但是注意,在上面模式中 "10." 是不匹配的, 只有 "10" 和 "10.2" 才可以. (你知道为什么吗)
我们上面指定小数点后面必须有两位小数.如果你认为这样太苛刻,你可以改成:
这将允许小数点后面有一到两个字符. 现在我们加上用来增加可读性的逗号(每隔三位), 我们可以这样表示:
^[0-9]{1,3}(,[0-9]{3})*(\.[0-9]{1,2})?$ |
不要忘记 '+' 可以被 '*' 替代 如果你想允许空白字符串被输入话 (为什么?). 也不要忘记反斜杆 ’\’ 在php字符串中可能会出现错误 (很普遍的错误).
现在,我们已经可以确认字符串了, 我们现在把所有逗号都去掉 str_replace(",", "", $money) 然后在把类型看成 double然后我们就可以通过他做数学计算了.
再来一个:
构造检查email的正则表达式
在一个完整的email地址中有三个部分:
1. 用户名 (在 '@' 左边的一切),
2.'@',
3. 服务器名(就是剩下那部分).
用户名可以含有大小写字母阿拉伯数字,句号 ('.'), 减号('-'), and 下划线 ('_'). 服务器名字也是符合这个规则,当然下划线除外.
现在, 用户名的开始和结束都不能是句点. 服务器也是这样. 还有你不能有两个连续的句点他们之间至少存在一个字符,好现在我们来看一下怎么为用户名写一个匹配模式:
现在还不能允许句号的存在. 我们把它加上:
^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$ |
上面的意思就是说: "以至少一个规范字符(除了.)开头,后面跟着0个或者多个以点开始的字符串."
简单化一点, 我们可以用 eregi()取代 ereg().eregi()对大小写不敏感, 我们就不需要指定两个范围 "a-z" 和 "A-Z" ? 只需要指定一个就可以了:
^[_a-z0-9-]+(\.[_a-z0-9-]+)*$ |
后面的服务器名字也是一样,但要去掉下划线:
^[a-z0-9-]+(\.[a-z0-9-]+)*$ |
好. 现在只需要用”@”把两部分连接:
^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ |
这就是完整的email认证匹配模式了,只需要调用
eregi(‘^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ ’,$eamil) |
就可以得到是否为email了.
正则表达式的其他用法
提取字符串
ereg() and eregi() 有一个特性是允许用户通过正则表达式去提取字符串的一部分(具体用法你可以阅读手册). 比如说,我们想从 path/URL 提取文件名 ? 下面的代码就是你需要:
ereg("([^\\/]*)$", $pathOrUrl, $regs);
echo $regs[1]; |
高级的代换
ereg_replace() 和 eregi_replace()也是非常有用的: 假如我们想把所有的间隔负号都替换成逗号:
ereg_replace("[ \n\r\t]+", ",", trim($str)); |
最后,我把另一串检查EMAIL的正则表达式让看文章的你来分析一下.
"^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.'@'.'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$" |
如果能方便的读懂,那这篇文章的目的就达到了.
DB2中简单存储过程 selectAllUsers
CREATE procedure selectAllUsers
DYNAMIC RESULT SETS 1
BEGIN
DECLARE temp_cursor1 CURSOR WITH RETURN TO CLIENT FOR
SELECT * FROM test;
OPEN temp_cursor1;
END;
映射文件中关于存储过程内容如下
<hibernate-mapping package="com.unmi.vo">
<class name="Test" table="TEST">
............
</class>
<sql-query callable="true" name="selectAllUsers">
<return alias="aa" class="Test">
<return-property name="oborqt" column="OBORQT"/>
<return-property name="moorqt" column="MOORQT"/>
<return-property name="roschn" column="ROSCHN"/>
<return-property name="plandate" column="PLANDATE"/>
</return>
{ ? = call selectAllUsers() }
</sql-query>
</hibernate-mapping>
{ ? = call selectAllUsers() } 也可以写成{ call selectAllUsers() },
如果有参数就写成
{ ? = call selectAllUsers(?,?,?) }
代码中对query设置相应位置上的值就OK
Java调用关键代码如下
Session session = HibernateUtil.currentSession();
Query query = session.getNamedQuery("selectAllUsers");
List list = query.list();
System.out.println(list);
要求你的存储过程必须能返回记录集,否则要出错
如果你的存储过程是完成非查询任务就应该在配置文件用以下三个标签
<sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
<sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>
——此文章摘自
《Java程序员面试宝典》定价:46元 特价:38.9元 购买>>
回答智力测试的一些基本方法如下。
(1)排除法
把一些无关的问题先予以排除,可以确定的问题先确定,尽可能缩小未知的范围,以便于问题的分析和解决。这种思维方式在我们的工作和生活中都是很有用处的。
(2)递推法
由已知条件层层向下分析,要确保每一步都能准确无误。可能会有几个分支,应本着先易后难的原则,先从简单的一支入手。
(3)倒推法
从问题最后的结果开始,一步一步往前推,直到求出问题的答案。有些问题用此法解起来很简单,如用其他方法则很难。
(4)假设法
对给定的问题,先做一个或一些假设,然后根据已给的条件进行分析,如果出现与题目给的条件有矛盾的情况,说明假设错误,可再做另一个或另一些假设。如果结果只有两种可能,那么问题就已经解决了。在科学史上,“假设”曾起了极大的作用。
(5)计算法
有些问题必须经计算才能解决。要注意的是,智力测验中的问题往往含有隐含的条件,有时给出的数是无用的。
(6)分析法
这是最基本的方法。各种方法常常要用到分析法。可以说,分析能力的高低,是一个人的智力水平的体现。分析能力不仅是先天性的,在很大程度上取决于后天的训练,应养成对客观事物进行分析的良好习惯。
(7)作图法
根据问题中已知的条件,采用适当的方法画出图形,有助于问题的解决。有些问题,在没画图之前,会觉得无处下手,画了图后就一目了然了。
(8)综合法
事实上,许多问题都要运用几种不同的方法才能解决。所谓综合法,就是综合各种方法(包括前述各种方法以外的方法)去解决某些问题。
面试例题1:100美元哪里去了?
3个朋友住进了一家宾馆。结账时,账单总计3 000美元。3个朋友每人分摊1 000美元,并把这3 000美元如数交给了服务员,委托他代到总台交账。但在交账时,正逢宾馆实施价格优惠,总台退还给服务员500美元,实收2 500美元。服务员从这500美元退款中扣下了200美元,只退还3个客人300美元。3个客人平分了这300美元,每人取回了100美元。这样,3个客人每人实际支付900美元,共支付2 700美元,加上服务员扣的200美元,共计2 900美元,那么这100美元的差额到哪里去了?
答案:这道题纯粹是文字游戏,但是如果你的头脑不够清晰,很可能把你搞糊涂了。客人实际支付2 700美元,就等于总台实际结收的2 500美元加上服务员克扣的200美元。在这2 700美元上加上200美元是毫无道理的,而在这2 700美元上加退回的300美元,这是有道理的,因为这等于客人原先交给服务员的3 000美元。
面试例题2:击鼠标比赛现在开始!参赛者有拉尔夫、威利和保罗。
拉尔夫10秒钟能击10下鼠标,威利20秒钟能击20下鼠标,保罗5秒钟能击5下鼠标。以上各人所用的时间是这样计算的:从第一击开始,到最后一击结束。
他们是否打平手?如果不是,谁最先击完40下鼠标?
解析:n秒钟击n下鼠标其实是击第一下鼠标时才开始计时的,实际上击n-1下需要n秒钟,那么若击40下鼠标,拉尔夫需要(40-1)/(9/10)=39/0.9秒,威利需要(40-1)/(19/20)=39/0.95秒,保罗需要(40-1)/(4/5)=39/0.8秒,因此威利先击完。
答案:威利先击完。
面试例题3:父亲打电话给女儿,要她替自己买一些生活用品,同时告诉她,钱放在书桌上的一个信封里。女儿找到信封,看见上面写着98,以为信封内有98元,就把钱拿出来,数也没数放进书包里。在商店里,她买了90元的东西,付款时才发现,她不仅没有剩下8元,反而差了4元。回到家里,她把这事告诉了父亲,怀疑父亲把钱点错了。父亲笑着说,他并没有数错,错在女儿身上。
问:女儿错在什么地方?
答案:拿倒了,86看成是98了。
面试例题4:3个孩子翻衣兜,他们把兜里所有的钱都掏出来,看看一共有多少钱。结果一共有320日元。其中有两枚硬币是100日元的,两枚是50日元的,两枚是10日元的。每一个孩子所带的硬币中没有相同的。而且,没带100日元硬币的孩子也没带10日元的硬币,没带50日元硬币的孩子也没带100日元的硬币。你能弄清楚这3个日本孩子原来各自带了什么硬币吗?
答案:第一个小孩:100,50,10;第二个小孩:100,50;第三个小孩:10。
面试例题5:有一种小虫,每隔2秒钟分裂一次。分裂后的2只新的小虫经过2秒钟后又会分裂。如果最初某瓶中只有一只小虫,那么2秒后变2只,再过2秒后就变4只……2分钟后,正好满满一瓶小虫。假设这个瓶内最初放入2只这样的小虫。
问:经过多少时间后,正巧也是满满的一瓶?
答案:经过1分58秒时间,也正巧是满满一瓶。因为从一只虫蜕变为2只虫只需2秒钟。在瓶内只有一只虫子的情况下,经过2秒钟后就变成2只。这时的情况和瓶内一开始就有2只虫子的情况是一样的。出现这两种情况的时间差是2秒钟。所以,经过1分58秒后,也正好是满满一瓶。
面试例题6:斯芬克斯是古代希腊神话中的带翅膀的狮子女魔。传说她在底比斯附近要人猜谜,猜不出来就要杀人。一次,她要底比斯王子猜谜:“有一种动物,早上4条腿,中午2条腿,晚上3条腿,是什么动物?”聪明的王子说:“是人。”他猜中了。
如果你是现代的斯芬克斯,会提出什么样的问题呢?比如,1和0之间加上什么符号才可以使得到的数比0大又比1小呢?你知道吗?
答案:0.1
面试例题7:你让工人为你工作7天,给工人的回报是一根金条。金条平分成相连的7段,你必须在每天结束时给他们一段金条,如果只许你两次把金条弄断,你如何给你的工人付费?你让工人为你工作7天,给工人的回报是一根金条。金条平分成相连的7段,你必须在每天结束时给他们一段金条,如果只许你两次把金条弄断,你如何给你的工人付费?
答案:两次弄断就应分成三份,我把金条分成1/7、2/7和4/7三份。这样,第1天我就可以给他1/7;第2天我给他2/7,让他找回我1/7;第3天我就再给他1/7,加上原先的2/7就是3/7;第4天我给他那块4/7,让他找回那两块1/7和2/7的金条;第5天,再给他1/7;第6天和第2天一样;第7天给他找回的那个1/7。
面试题8:对一批编号为1-100全部开关朝上(开)的灯进行以下操作:凡是1的倍数反方向拨一次开关;2的倍数反方向又拨一次开关;3的倍数反方向又拨一次开关。。。。。问:最后为关熄状态的灯的编号。
答案:
1,4,9,16,25,36,47,64,81,100关
面试题9:
小明和小强都是王老师的学生。王老师的生日是m月n日,2人都指导王老师的生日是下列10组中的一天,王老师把m值告诉了小明,把n值告诉了小强。王老师文他们知道他的生日是那一天吗?
3月4 日,3月5 日,3月9 日
6月 4日,6月 7日
9月 1日,9月 5日
12月 1日,12月 2日,12月 8日
小明说:我不知道的话,小强肯定也不知道
小强说:笨来我不知道,但是现在我知道了
小明说:哦,那我也知道了
情根据上面的对话推断出王老师的生日是哪一天?
答案:9月1号准没错
首先小明是不知道的,所以不可能在某月的 2号,7号,8号,9号.去掉一些日子后;简化如下
3月4 日,3月5 日,
6月 4日,
9月 1日,9月 5日
12月 1日,
小强说他不知道,小明也不会知道.即小强知道的月数不会在有 2号,7号,8号,9号 的这些月数,因为如果在这些月数,小明是有可能知道的.最后只剩下 9月1日 和 9月5日.
这时候小明也清楚M是9月了,小强也因此知道N是1号.
神的交通工具是什么------神奇(骑)宝贝
什么动物可以贴在墙上------海豹(报)
什么颜色最会模仿------红(磨坊)模仿
什么鸡最慢------尼可基(鸡)曼
辣妹什么地方最香------腊梅处处香
哪位古人跑得最快------曹操(说曹操曹操就到)
什么动物没有方向感------麋鹿(迷路)
茉莉花、太阳花、玫瑰花,哪一朵花最没力------茉莉花[好一朵美丽(没力)的茉莉花]
猴子最付厌什么钱------平行线[没有相交(香蕉)]
象皮、老虎皮、狮子つ囊桓霰冉喜?-----象皮擦(象皮差)
木鱼掉进海里会变成什么------虱目鱼(湿木鱼)
哪一位艺人讲的笑话最冷------蔡依琳(衣淋湿就冷)
狼、老虎和狮子谁玩游戏一定会被淘汰------狼,桃太郎(淘汰狼)
孔子有三位徒弟子贡、子路和子游,请问哪一位不是人------子路(指鹿为马)
布跟纸怕什么------不怕一万只怕万一 (布怕一万纸怕万一)
麒麟飞到北极会变成什么------冰淇淋(冰麒麟)
哪个历史人物游泳必定沉下去------阿斗,扶(浮)不起的阿斗
星星、月亮、太阳哪一个是哑巴------星星,歌中有「天上的星星不说话 」
铅笔姓什么------萧,削(萧)铅笔
糖果是公的还是母的------母的,因为它会生蚂蚁
请问哪一种花没有孩子------五月花, 五月花卫生纸(未生子)
左和右,谁喜欢独奏,谁又比较好------左solo, 右so good (左手锣,右手鼓)
为什么蚕宝宝很有钱------蚕会结茧(节俭)
周瑜与诸葛亮的母亲分别姓什么------[既]生瑜,[何]生亮
和谁交往最辛苦------莉莉,粒粒(莉莉)皆辛苦
蝴蝶、蚂蚁、蜘蛛、蜈蚣,哪一个没有领到酬劳------蜈蚣,无功(蜈蚣)不受禄
哪位历史人物最欠扁------苏武,苏武牧羊北海边(被海扁)
123456789哪个数字最勤劳,哪个数字最懒惰------1最勤劳2最懒惰(一不做二不休)
谁家没有电话------天衣(天衣无缝 phone)
怎样使麻雀安静下来------压它一下(鸦雀无声)
哪一家的路最窄------冤家(冤家路窄)
红豆的小孩是谁------南国(红豆生南国)
有位妈妈生了连体婴,姐姐叫玛丽,那么妹妹叫什么------梦露,玛丽莲(连)梦露
正则表达式用于字符串处理、表单验证等场合,实用高效。现将一些常用的表达式收集于此,以备不时之需。
匹配中文字符的正则表达式: [\u4e00-\u9fa5]
评注:匹配中文还真是个头疼的事,有了这个表达式就好办了
匹配双字节字符(包括汉字在内):[^\x00-\xff]
评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)
匹配空白行的正则表达式:\n\s*\r
评注:可以用来删除空白行
匹配HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? />
评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力
匹配首尾空白字符的正则表达式:^\s*|\s*$
评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
评注:表单验证时很实用
匹配网址URL的正则表达式:[a-zA-z]+://[^\s]*
评注:网上流传的版本功能很有限,上面这个基本可以满足需求
匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
评注:表单验证时很实用
匹配国内电话号码:\d{3}-\d{8}|\d{4}-\d{7}
评注:匹配形式如 0511-4405222 或 021-87888822
匹配腾讯QQ号:[1-9][0-9]{4,}
评注:腾讯QQ号从10000开始
匹配中国邮政编码:[1-9]\d{5}(?!\d)
评注:中国邮政编码为6位数字
匹配身份证:\d{15}|\d{18}
评注:中国的身份证为15位或18位
匹配ip地址:\d+\.\d+\.\d+\.\d+
评注:提取ip地址时有用
匹配特定数字:
^[1-9]\d*$ //匹配正整数
^-[1-9]\d*$ //匹配负整数
^-?[1-9]\d*$ //匹配整数
^[1-9]\d*|0$ //匹配非负整数(正整数 + 0)
^-[1-9]\d*|0$ //匹配非正整数(负整数 + 0)
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ //匹配正浮点数
^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ //匹配负浮点数
^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ //匹配浮点数
^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ //匹配非负浮点数(正浮点数 + 0)
^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$ //匹配非正浮点数(负浮点数 + 0)
评注:处理大量数据时有用,具体应用时注意修正
匹配特定字符串:
^[A-Za-z]+$ //匹配由26个英文字母组成的字符串
^[A-Z]+$ //匹配由26个英文字母的大写组成的字符串
^[a-z]+$ //匹配由26个英文字母的小写组成的字符串
^[A-Za-z0-9]+$ //匹配由数字和26个英文字母组成的字符串
^\w+$ //匹配由数字、26个英文字母或者下划线组成的字符串
评注:最基本也是最常用的一些表达式
原载地址:http://lifesinger.3322.org/myblog/?p=185
数据库是面向事务的设计,数据仓库是面向主题设计的。数据库一般存储在线交易数据,数据仓库存储的一般是历史数据。
数据库设计是尽量避免冗余,一般采用符合范式的规则来设计,数据仓库在设计是有意引入冗余,采用反范式的方式来设计。
数据库是为捕获数据而设计,数据仓库是为分析数据而设计,它的两个基本的元素是维表和事实表。(维是看问题的角度,比如时间,部门,维表放的就是这些东西的定义,事实表里放着要查询的数据,同时有维的ID)
数据仓库,是在数据库已经大量存在的情况下,为了进一步挖掘数据资源、为了决策需要而产生的,它决不是所谓的“大型数据库”。那么,数据仓库与传统数据库比较,有哪些不同呢?让我们先看看W.H.Inmon关于数据仓库的定义:面向主题的、集成的、与时间相关且不可修改的数据集合。
“面向主题的”:传统数据库主要是为应用程序进行数据处理,未必按照同一主题存储数据;数据仓库侧重于数据分析工作,是按照主题存储的。这一点,类似于传统农贸市场与超市的区别—市场里面,白菜、萝卜、香菜会在一个摊位上,如果它们是一个小贩卖的;而超市里,白菜、萝卜、香菜则各自一块。也就是说,市场里的菜(数据)是按照小贩(应用程序)归堆(存储)的,超市里面则是按照菜的类型(同主题)归堆的。
“与时间相关”:数据库保存信息的时候,并不强调一定有时间信息。数据仓库则不同,出于决策的需要,数据仓库中的数据都要标明时间属性。决策中,时间属性很重要。同样都是累计购买过九车产品的顾客,一位是最近三个月购买九车,一位是最近一年从未买过,这对于决策者意义是不同的。
“不可修改”:数据仓库中的数据并不是最新的,而是来源于其它数据源。数据仓库反映的是历史信息,并不是很多数据库处理的那种日常事务数据(有的数据库例如电信计费数据库甚至处理实时信息)。因此,数据仓库中的数据是极少或根本不修改的;当然,向数据仓库添加数据是允许的。
数据仓库的出现,并不是要取代数据库。目前,大部分数据仓库还是用关系数据库管理系统来管理的。可以说,数据库、数据仓库相辅相成、各有千秋
补充一下,数据仓库的方案建设的目的,是为前端查询和分析作为基础,由于有较大的冗余,所以需要的存储也较大。为了更好地为前端应用服务,数据仓库必须有如下几点优点,否则是失败的数据仓库方案。
1.效率足够高。客户要求的分析数据一般分为日、周、月、季、年等,可以看出,日为周期的数据要求的效率最高,要求24小时甚至12小时内,客户能看到昨天的数据分析。由于有的企业每日的数据量很大,设计不好的数据仓库经常会出问题,延迟1-3日才能给出数据,显然不行的。
2.数据质量。客户要看各种信息,肯定要准确的数据,但由于数据仓库流程至少分为3步,2次ETL,复杂的架构会更多层次,那么由于数据源有脏数据或者代码不严谨,都可以导致数据失真,客户看到错误的信息就可能导致分析出错误的决策,造成损失,而不是效益。
3.扩展性。之所以有的大型数据仓库系统架构设计复杂,是因为考虑到了未来3-5年的扩展性,这样的话,客户不用太快花钱去重建数据仓库系统,就能很稳定运行。主要体现在数据建模的合理性,数据仓库方案中多出一些中间层,使海量数据流有足够的缓冲,不至于数据量大很多,就运行不起来了.
ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析的依据
ETL是BI项目最重要的一个环节,通常情况下ETL会花掉整个项目的1/3的时间,ETL设计的好坏直接关接到BI项目的成败。ETL也是一个长期的过程,只有不断的发现问题并解决问题,才能使ETL运行效率更高,为项目后期开发提供准确的数据。
ETL的设计分三部分:数据抽取、数据的清洗转换、数据的加载。在设计ETL的时候也是从这三部分出发。数据的抽取是从各个不同的数据源抽取到ODS中(这个过程也可以做一些数据的清洗和转换),在抽取的过程中需要挑选不同的抽取方法,尽可能的提高ETL的运行效率。ETL三个部分中,花费时间最长的是T(清洗、转换)的部分,一般情况下这部分工作量是整个ETL的2/3。数据的加载一般在数据清洗完了之后直接写入DW中去。
ETL的实现有多种方法,常用的有三种,第一种是借助ETL工具如Oracle的OWB、SQL server 2000的DTS、SQL Server2005的SSIS服务、informatic等实现,第二种是SQL方式实现,第三种是ETL工具和SQL相结合。前两种方法各有优缺点,借助工具可以快速的建立起ETL工程,屏蔽复杂的编码任务,提高速度,降低难度,但是欠缺灵活性。SQL的方法优点是灵活,提高ETL运行效率,但是编码复杂,对技术要求比较高。第三种是综合了前面二种的优点,极大的提高ETL的开发速度和效率。
数据的抽取
数据的抽取需要在调研阶段做大量工作,首先要搞清楚以下几个问题:数据是从几个业务系统中来?各个业务系统的数据库服务器运行什么DBMS?是否存在手工数据,手工数据量有多大?是否存在非结构化的数据?等等类似问题,当收集完这些信息之后才可以进行数据抽取的设计。
1、与存放DW的数据库系统相同的数据源处理方法
这一类数源在设计比较容易,一般情况下,DBMS(包括SQLServer,Oracle)都会提供数据库链接功能,在DW数据库服务器和原业务系统之间建立直接的链接关系就可以写Select 语句直接访问。
2、与DW数据库系统不同的数据源的处理方法。
这一类数据源一般情况下也可以通过ODBC的方式建立数据库链接,如SQL Server和Oracle之间。如果不能建立数据库链接,可以有两种方式完成,一种是通过工具将源数据导出成.txt或者是.xls文件,然后再将这些源系统文件导入到ODS中。另外一种方法通过程序接口来完成。
3、对于文件类型数据源(.txt,,xls),可以培训业务人员利用数据库工具将这些数据导入到指定的数据库,然后从指定的数据库抽取。或者可以借助工具实现,如SQL SERVER 2005 的SSIS服务的平面数据源和平面目标等组件导入ODS中去。
4、增量更新问题
对于数据量大的系统,必须考虑增量抽取。一般情况,业务系统会记录业务发生的时间,可以用作增量的标志,每次抽取之前首先判断ODS中记录最大的时间,然后根据这个时间去业务系统取大于这个时间的所有记录。利用业务系统的时间戳,一般情况下,业务系统没有或者部分有时间戳。
数据的清洗转换
一般情况下,数据仓库分为ODS、DW两部分,通常的做法是从业务系统到ODS做清洗,将脏数据和不完整数据过滤掉,再从ODS到DW的过程中转换,进行一些业务规则的计算和聚合。
1、数据清洗
数据清洗的任务是过滤那些不符合要求的数据,将过滤的结果交给业务主管部门,确认是否过滤掉还是由业务单位修正之后再进行抽取。不符合要求的数据主要是有不完整的数据、错误的数据和重复的数据三大类。
- A、不完整的数据,其特征是是一些应该有的信息缺失,如供应商的名称,分公司的名称,客户的区域信息缺失、业务系统中主表与明细表不能匹配等。需要将这一类数据过滤出来,按缺失的内容分别写入不同Excel文件向客户提交,要求在规定的时间内补全。补全后才写入数据仓库。
- B、错误的数据,产生原因是业务系统不够健全,在接收输入后没有进行判断直接写入后台数据库造成的,比如数值数据输成全角数字字符、字符串数据后面有一个回车、日期格式不正确、日期越界等。这一类数据也要分类,对于类似于全角字符、数据前后有不面见字符的问题只能写SQL的方式找出来,然后要求客户在业务系统修正之后抽取;日期格式不正确的或者是日期越界的这一类错误会导致ETL运行失败,这一类错误需要去业务系统数据库用SQL的方式挑出来,交给业务主管部门要求限期修正,修正之后再抽取。
- C、重复的数据,特别是维表中比较常见,将重复的数据的记录所有字段导出来,让客户确认并整理。
数据清洗是一个反复的过程,不可能在几天内完成,只有不断的发现问题,解决问题。对于是否过滤、是否修正一般要求客户确认;对于过滤掉的数据,写入Excel文件或者将过滤数据写入数据表,在ETL开发的初期可以每天向业务单位发送过滤数据的邮件,促使他们尽快的修正错误,同时也可以作为将来验证数据的依据。数据清洗需要注意的是不要将有用的数据过滤掉了,对于每个过滤规则认真进行验证,并要用户确认才行。
2、数据转换
数据转换的任务主要是进行不一致的数据转换、数据粒度的转换和一些商务规则的计算。
- A、不一致数据转换,这个过程是一个整合的过程,将不同业务系统的相同类型的数据统一,比如同一个供应商在结算系统的编码是XX0001,而在CRM中编码是YY0001,这样在抽取过来之后统一转换成一个编码。
- B、数据粒度的转换,业务系统一般存储非常明细的数据,而数据仓库中的数据是用来分析的,不需要非常明细的数据,一般情况下,会将业务系统数据按照数据仓库粒度进行聚合。
- C、商务规则的计算,不同的企业有不同的业务规则,不同的数据指标,这些指标有的时候不是简单的加加减减就能完成,这个时候需要在ETL中将这些数据指标计算好了之后存储在数据仓库中,供分析使用。
ETL日志与警告发送
1、ETL日志,记录日志的目的是随时可以知道ETL运行情况,如果出错了,出错在那里。
ETL日志分为三类。第一类是执行过程日志,是在ETL执行过程中每执行一步的记录,记录每次运行每一步骤的起始时间,影响了多少行数据,流水账形式。第二类是错误日志,当某个模块出错的时候需要写错误日志,记录每次出错的时间,出错的模块以及出错的信息等。第三类日志是总体日志,只记录ETL开始时间,结束时间是否成功信息。
如果使用ETL工具,工具会自动产生一些日志,这一类日志也可以作为ETL日志的一部分。
2、警告发送
ETL出错了,不仅要写ETL出错日志而且要向系统管理员发送警告,发送警告的方式有多种,常用的就是给系统管理员发送邮件,并附上出错的信息,方便管理员排查错误。
海量数据库的查询优化及分页算法方案
随着“金盾工程”建设的逐步深入和公安信息化的高速发展,公安计算机应用系统被广泛应用在各警种、各部门。与此同时,应用系统体系的核心、系统数据的存放地――数据库也随着实际应用而急剧膨胀,一些大规模的系统,如人口系统的数据甚至超过了1000万条,可谓海量。那么,如何实现快速地从这些超大容量的数据库中提取数据(查询)、分析、统计以及提取数据后进行数据分页已成为各地系统管理员和数据库管理员亟待解决的难题。
在以下的文章中,我将以“办公自动化”系统为例,探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页。以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构:
CREATE TABLE [dbo].[TGongwen] ( --TGongwen是红头文件表名
[Gid] [int] IDENTITY (1, 1) NOT NULL ,
--本表的id号,也是主键
[title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
--红头文件的标题
[fariqi] [datetime] NULL ,
--发布日期
[neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,
--发布用户
[reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,
--需要浏览的用户。每个用户中间用分隔符“,”分开
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
下面,我们来往数据库中添加1000万条数据:
declare @i int
set @i=1
while @i<=250000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最先的25万条记录')
set @i=@i+1
end
GO
declare @i int
set @i=1
while @i<=250000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','办公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是中间的25万条记录')
set @i=@i+1
end
GO
declare @h int
set @h=1
while @h<=100
begin
declare @i int
set @i=2002
while @i<=2003
begin
declare @j int
set @j=0
while @j<50
begin
declare @k int
set @k=0
while @k<50
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是最后的50万条记录')
set @k=@k+1
end
set @j=@j+1
end
set @i=@i+1
end
set @h=@h+1
end
GO
declare @i int
set @i=1
while @i<=9000000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最后添加的900万条记录')
set @i=@i+1000000
end
GO
通过以上语句,我们创建了25万条由通信科于2004年2月5日发布的记录,25万条由办公室于2004年9月6日发布的记录,2002年和2003年各100个2500条相同日期、不同分秒的由通信科发布的记录(共50万条),还有由通信科于2004年5月5日发布的900万条记录,合计1000万条。
一、因情制宜,建立“适当”的索引
建立“适当”的索引是实现查询优化的首要前提。
索引(index)是除表之外另一重要的、用户定义的存储在物理介质上的数据结构。当根据索引码的值搜索数据时,索引提供了对数据的快速访问。事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用“适当”的索引的效果就越来越明显。注意,在这句话中,我们用了“适当”这个词,这是因为,如果使用索引时不认真考虑其实现过程,索引既可以提高也会破坏数据库的工作性能。
(一)深入浅出理解索引结构
实际上,您可以把索引理解为一种特殊的目录。微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引、簇集索引)和非聚集索引(nonclustered index,也称非聚类索引、非簇集索引)。下面,我们举例来说明一下聚集索引和非聚集索引的区别:
其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。
我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。
如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。
我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。
通过以上例子,我们可以理解到什么是“聚集索引”和“非聚集索引”。
进一步引申一下,我们可以很容易的理解:每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序。
(二)何时使用聚集索引或非聚集索引
下面的表总结了何时使用聚集索引或非聚集索引(很重要)。
动作描述
使用聚集索引
使用非聚集索引
列经常被分组排序
应
应
返回某范围内的数据
应
不应
一个或极少不同值
不应
不应
小数目的不同值
应
不应
大数目的不同值
不应
应
频繁更新的列
不应
应
外键列
应
应
主键列
应
应
频繁修改索引列
不应
应
事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。
(三)结合实际,谈索引使用的误区
理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引,但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区,以便于大家掌握索引建立的方法。
1、主键就是聚集索引
这种想法笔者认为是极端错误的,是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。
通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但笔者认为这样做意义不大。
显而易见,聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加珍贵。
从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。
在办公自动化系统中,无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。
通常,办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况,但如果您的系统已建立了很长时间,并且数据量很大,那么,每次每个用户打开首页的时候都进行一次全表扫描,这样做意义是不大的,绝大多数的用户1个月前的文件都已经浏览过了,这样做只能徒增数据库的开销而已。事实上,我们完全可以让用户打开系统首页时,数据库仅仅查询这个用户近3个月来未阅览的文件,通过“日期”这个字段来限制表扫描,提高查询速度。如果您的办公自动化系统已经建立的2年,那么您的首页显示速度理论上将是原来速度8倍,甚至更快。
在这里之所以提到“理论上”三字,是因为如果您的聚集索引还是盲目地建在ID这个主键上时,您的查询速度是没有这么高的,即使您在“日期”这个字段上建立的索引(非聚合索引)。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现(3个月内的数据为25万条):
(1)仅在主键上建立聚集索引,并且不划分时间段:
Select gid,fariqi,neibuyonghu,title from tgongwen
用时:128470毫秒(即:128秒)
(2)在主键上建立聚集索引,在fariq上建立非聚集索引:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用时:53763毫秒(54秒)
(3)将聚合索引建立在日期列(fariqi)上:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用时:2423毫秒(2秒)
虽然每条语句提取出来的都是25万条数据,各种情况的差异却是巨大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有1000万容量的话,把主键建立在ID列上,就像以上的第1、2种情况,在网页上的表现就是超时,根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。
得出以上速度的方法是:在各个select语句前加:declare @d datetime
set @d=getdate()
并在select语句后加:
select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())
2、只要建立索引就能显著提高查询速度
事实上,我们可以发现上面的例子中,第2、3条语句完全相同,且建立索引的字段也相同;不同的仅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查询速度却有着天壤之别。所以,并非是在任何字段上简单地建立索引就能提高查询速度。
从建表的语句中,我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中,我们每天都会发几个文件,这几个文件的发文日期就相同,这完全符合建立聚集索引要求的:“既不能绝大多数都相同,又不能只有极少数相同”的规则。由此看来,我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。
3、把所有需要提高查询速度的字段都加进聚集索引,以提高查询速度
上面已经谈到:在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要,我们可以把他们合并起来,建立一个复合索引(compound index)。
很多人认为只要把任何字段加进聚集索引,就能提高查询速度,也有人感到迷惑:如果把复合的聚集索引字段分开查询,那么查询速度会减慢吗?带着这个问题,我们来看一下以下的查询速度(结果集都是25万条数据):(日期列fariqi首先排在复合聚集索引的起始列,用户名neibuyonghu排在后列)
(1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'
查询速度:2513毫秒
(2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='办公室'
查询速度:2516毫秒
(3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='办公室'
查询速度:60280毫秒
从以上试验中,我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的,甚至比用上全部的复合索引列还要略快(在查询结果集数目一样的情况下);而如果仅用复合聚集索引的非起始列作为查询条件的话,这个索引是不起任何作用的。当然,语句1、2的查询速度一样是因为查询的条目数一样,如果复合索引的所有列都用上,而且查询结果少的话,这样就会形成“索引覆盖”,因而性能可以达到最优。同时,请记住:无论您是否经常使用聚合索引的其他列,但其前导列一定要是使用最频繁的列。
(四)其他书上没有的索引使用经验总结
1、用聚合索引比用不是聚合索引的主键速度快
下面是实例语句:(都是提取25万条数据)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
使用时间:3326毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000
使用时间:4470毫秒
这里,用聚合索引比用不是聚合索引的主键速度快了近1/4。
2、用聚合索引比用一般的主键作order by时速度快,特别是在小数据量情况下
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi
用时:12936
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid
用时:18843
这里,用聚合索引比用一般的主键作order by时,速度快了3/10。事实上,如果数据量很小的话,用聚集索引作为排序列要比使用非聚集索引速度快得明显的多;而数据量如果很大的话,如10万以上,则二者的速度差别不明显。
3、使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'
用时:6343毫秒(提取100万条)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'
用时:3170毫秒(提取50万条)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
用时:3326毫秒(和上句的结果一模一样。如果采集的数量一样,那么用大于号和等于号是一样的)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6'
用时:3280毫秒
4 、日期列不会因为有分秒的输入而减慢查询速度
下面的例子中,共有100万条数据,2004年1月1日以后的数据有50万条,但只有两个不同的日期,日期精确到日;之前有数据50万条,有5000个不同的日期,日期精确到秒。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi
用时:6390毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi
用时:6453毫秒
(五)其他注意事项
“水可载舟,亦可覆舟”,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引,数据库就要做更多的工作。过多的索引甚至会导致索引碎片。
所以说,我们要建立一个“适当”的索引体系,特别是对聚合索引的创建,更应精益求精,以使您的数据库能得到高性能的发挥。
当然,在实践中,作为一个尽职的数据库管理员,您还要多测试一些方案,找出哪种方案效率最高、最为有效。
二、改善SQL语句
很多人不知道SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解。比如:
select * from table1 where name='zhangsan' and tID > 10000
和执行:
select * from table1 where tID > 10000 and name='zhangsan'
一些人不知道以上两条语句的执行效率是否一样,因为如果简单的从语句先后上看,这两个语句的确是不一样,如果tID是一个聚合索引,那么后一句仅仅从表的10000条以后的记录中查找就行了;而前一句则要先从全表中查找看有几个name='zhangsan'的,而后再根据限制条件条件tID>10000来提出查询结果。
事实上,这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”,它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间,也就是说,它能实现自动优化。
虽然查询优化器可以根据where子句自动的进行查询优化,但大家仍然有必要了解一下“查询优化器”的工作原理,如非这样,有时查询优化器就会不按照您的本意进行快速查询。
在查询分析阶段,查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。
SARG的定义:用于限制搜索的一个操作,因为它通常是指一个特定的匹配,一个值得范围内的匹配或者两个以上条件的AND连接。形式如下:
列名 操作符 <常数 或 变量>
或
<常数 或 变量> 操作符列名
列名可以出现在操作符的一边,而常数或变量出现在操作符的另一边。如:
Name=’张三’
价格>5000
5000<价格
Name=’张三’ and 价格>5000
如果一个表达式不能满足SARG的形式,那它就无法限制搜索的范围了,也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。
介绍完SARG后,我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同的经验:
1、Like语句是否属于SARG取决于所使用的通配符的类型
如:name like ‘张%’ ,这就属于SARG
而:name like ‘%张’ ,就不属于SARG。
原因是通配符%在字符串的开通使得索引无法使用。
2、or 会引起全表扫描
Name=’张三’ and 价格>5000 符号SARG,而:Name=’张三’ or 价格>5000 则不符合SARG。使用or会引起全表扫描。
3、非操作符、函数引起的不满足SARG形式的语句
不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外还有函数。下面就是几个不满足SARG形式的例子:
ABS(价格)<5000
Name like ‘%三’
有些表达式,如:
WHERE 价格*2>5000
SQL SERVER也会认为是SARG,SQL SERVER会将此式转化为:
WHERE 价格>2500/2
但我们不推荐这样使用,因为有时SQL SERVER不能保证这种转化与原始表达式是完全等价的。
4、IN 的作用相当与OR
语句:
Select * from table1 where tid in (2,3)
和
Select * from table1 where tid=2 or tid=3
是一样的,都会引起全表扫描,如果tid上有索引,其索引也会失效。
5、尽量少用NOT
6、exists 和 in 的执行效率是一样的
很多资料上都显示说,exists要比in的执行效率要高,同时应尽可能的用not exists来代替not in。但事实上,我试验了一下,发现二者无论是前面带不带not,二者之间的执行效率都是一样的。因为涉及子查询,我们试验这次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVER的statistics I/O状态打开。
(1)select title,price from titles where title_id in (select title_id from sales where qty>30)
该句的执行结果为:
表 'sales'。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。
表 'titles'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。
(2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)
第二句的执行结果为:
表 'sales'。扫描计数 18,逻辑读 56 次,物理读 0 次,预读 0 次。
表 'titles'。扫描计数 1,逻辑读 2 次,物理读 0 次,预读 0 次。
我们从此可以看到用exists和用in的执行效率是一样的。
7、用函数charindex()和前面加通配符%的LIKE执行效率一样
前面,我们谈到,如果在LIKE前面加上通配符%,那么将会引起全表扫描,所以其执行效率是低下的。但有的资料介绍说,用函数charindex()来代替LIKE速度会有大的提升,经我试验,发现这种说明也是错误的:
select gid,title,fariqi,reader from tgongwen where charindex('刑侦支队',reader)>0 and fariqi>'2004-5-5'
用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。
select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑侦支队' + '%' and fariqi>'2004-5-5'
用时:7秒,另外:扫描计数 4,逻辑读 7155 次,物理读 0 次,预读 0 次。
8、union并不绝对比or的执行效率高
我们前面已经谈到了在where子句中使用or会引起全表扫描,一般的,我所见过的资料都是推荐这里用union来代替or。事实证明,这种说法对于大部分都是适用的。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000
用时:68秒。扫描计数 1,逻辑读 404008 次,物理读 283 次,预读 392163 次。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
union
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000
用时:9秒。扫描计数 8,逻辑读 67489 次,物理读 216 次,预读 7499 次。
看来,用union在通常情况下比用or的效率要高的多。
但经过试验,笔者发现如果or两边的查询列是一样的话,那么用union则反倒和用or的执行速度差很多,虽然这里union扫描的是索引,而or扫描的是全表。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'
用时:6423毫秒。扫描计数 2,逻辑读 14726 次,物理读 1 次,预读 7176 次。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
union
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-2-5'
用时:11640毫秒。扫描计数 8,逻辑读 14806 次,物理读 108 次,预读 1144 次。
9、字段提取要按照“需多少、提多少”的原则,避免“select *”
我们来做一个试验:
select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用时:4673毫秒
select top 10000 gid,fariqi,title from tgongwen order by gid desc
用时:1376毫秒
select top 10000 gid,fariqi from tgongwen order by gid desc
用时:80毫秒
由此看来,我们每少提取一个字段,数据的提取速度就会有相应的提升。提升的速度还要看您舍弃的字段的大小来判断。
10、count(*)不比count(字段)慢
某些资料上说:用*会统计所有列,显然要比一个世界的列名效率低。这种说法其实是没有根据的。我们来看:
select count(*) from Tgongwen
用时:1500毫秒
select count(gid) from Tgongwen
用时:1483毫秒
select count(fariqi) from Tgongwen
用时:3140毫秒
select count(title) from Tgongwen
用时:52050毫秒
从以上可以看出,如果用count(*)和用count(主键)的速度是相当的,而count(*)却比其他任何除主键以外的字段汇总速度要快,而且字段越长,汇总的速度就越慢。我想,如果用count(*), SQL SERVER可能会自动查找最小字段来汇总的。当然,如果您直接写count(主键)将会来的更直接些。
11、order by按聚集索引列排序效率最高
我们来看:(gid是主键,fariqi是聚合索引列)
select top 10000 gid,fariqi,reader,title from tgongwen
用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc
用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc
用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc
用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。
从以上我们可以看出,不排序的速度以及逻辑读次数都是和“order by 聚集索引列” 的速度是相当的,但这些都比“order by 非聚集索引列”的查询速度是快得多的。
同时,按照某个字段进行排序的时候,无论是正序还是倒序,速度是基本相当的。
12、高效的TOP
事实上,在查询和提取超大容量的数据集时,影响数据库响应时间的最大因素不是数据查找,而是物理的I/0操作。如:
select top 10 * from (
select top 10000 gid,fariqi,title from tgongwen
where neibuyonghu='办公室'
order by gid desc) as a
order by gid asc
这条语句,从理论上讲,整条语句的执行时间应该比子句的执行时间长,但事实相反。因为,子句执行后返回的是10000条记录,而整条语句仅返回10条语句,所以影响数据库响应时间最大的因素是物理I/O操作。而限制物理I/O操作此处的最有效方法之一就是使用TOP关键词了。TOP关键词是SQL SERVER中经过系统优化过的一个用来提取前几条或前几个百分比数据的词。经笔者在实践中的应用,发现TOP确实很好用,效率也很高。但这个词在另外一个大型数据库ORACLE中却没有,这不能说不是一个遗憾,虽然在ORACLE中可以用其他方法(如:rownumber)来解决。在以后的关于“实现千万级数据的分页显示存储过程”的讨论中,我们就将用到TOP这个关键词。
到此为止,我们上面讨论了如何实现从大容量的数据库中快速地查询出您所需要的数据方法。当然,我们介绍的这些方法都是“软”方法,在实践中,我们还要考虑各种“硬”因素,如:网络性能、服务器的性能、操作系统的性能,甚至网卡、交换机等。
三、实现小数据量和海量数据的通用分页显示存储过程
建立一个web 应用,分页浏览功能必不可少。这个问题是数据库处理中十分常见的问题。经典的数据分页方法是:ADO 纪录集分页法,也就是利用ADO自带的分页功能(利用游标)来实现分页。但这种分页方法仅适用于较小数据量的情形,因为游标本身有缺点:游标是存放在内存中,很费内存。游标一建立,就将相关的记录锁住,直到取消游标。游标提供了对特定集合中逐行扫描的手段,一般使用游标来逐行遍历数据,根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标(大的数据集合)循环很容易使程序进入一个漫长的等待甚至死机。
更重要的是,对于非常大的数据模型而言,分页检索时,如果按照传统的每次都加载整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的数据,而非检索所有的数据,然后单步执行当前行。
最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过程”。这个存储过程用了游标,由于游标的局限性,所以这个方法并没有得到大家的普遍认可。
后来,网上有人改造了此存储过程,下面的存储过程就是结合我们的办公自动化实例写的分页存储过程:
CREATE procedure pagination1
(@pagesize int, --页面大小,如每页存储20条记录
@pageindex int --当前页码
)
as
set nocount on
begin
declare @indextable table(id int identity(1,1),nid int) --定义表变量
declare @PageLowerBound int --定义此页的底码
declare @PageUpperBound int --定义此页的顶码
set @PageLowerBound=(@pageindex-1)*@pagesize
set @PageUpperBound=@PageLowerBound+@pagesize
set rowcount @PageUpperBound
insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc
select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid
and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id
end
set nocount off
以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然,在这个过程中,您也可以把其中的表变量写成临时表:CREATE TABLE #Temp。但很明显,在SQL SERVER中,用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时,感觉非常的不错,速度也比原来的ADO的好。但后来,我又发现了比此方法更好的方法。
笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》,全文如下:
从publish 表中取出第 n 条到第 m 条的记录:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
(SELECT TOP n-1 id
FROM publish))
id 为publish 表的关键字
我当时看到这篇文章的时候,真的是精神为之一振,觉得思路非常得好。等到后来,我在作办公自动化系统(ASP.NET+ C#+SQL SERVER)的时候,忽然想起了这篇文章,我想如果把这个语句改造一下,这就可能是一个非常好的分页存储过程。于是我就满网上找这篇文章,没想到,文章还没找到,却找到了一篇根据此语句写的一个分页存储过程,这个存储过程也是目前较为流行的一种分页存储过程,我很后悔没有争先把这段文字改造成存储过程:
CREATE PROCEDURE pagination2
(
@SQL nVARCHAR(4000), --不带排序语句的SQL语句
@Page int, --页码
@RecsPerPage int, --每页容纳的记录数
@ID VARCHAR(255), --需要排序的不重复的ID号
@Sort VARCHAR(255) --排序字段及规则
)
AS
DECLARE @Str nVARCHAR(4000)
SET @Str='SELECT TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+') T WHERE T.'+@ID+'NOT IN
(SELECT TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM ('+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort
PRINT @Str
EXEC sp_ExecuteSql @Str
GO
其实,以上语句可以简化为:
SELECT TOP 页大小 *
FROM Table1
WHERE (ID NOT IN
(SELECT TOP 页大小*页数 id
FROM 表
ORDER BY id))
ORDER BY ID
但这个存储过程有一个致命的缺点,就是它含有NOT IN字样。虽然我可以把它改造为:
SELECT TOP 页大小 *
FROM Table1
WHERE not exists
(select * from (select top (页大小*页数) * from table1 order by id) b where b.id=a.id )
order by id
即,用not exists来代替not in,但我们前面已经谈过了,二者的执行效率实际上是没有区别的。
既便如此,用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。
虽然用not exists并不能挽救上个存储过程的效率,但使用SQL SERVER中的TOP关键字却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集,而我们在前面也已经提到了TOP的优势,通过TOP 即可实现对数据量的控制。
在分页算法中,影响我们查询速度的关键因素有两点:TOP和NOT IN。TOP可以提高我们的查询速度,而NOT IN会减慢我们的查询速度,所以要提高我们整个分页算法的速度,就要彻底改造NOT IN,同其他方法来替代它。
我们知道,几乎任何字段,我们都可以通过max(字段)或min(字段)来提取某个字段中的最大或最小值,所以如果这个字段不重复,那么就可以利用这些不重复的字段的max或min作为分水岭,使其成为分页算法中分开每页的参照物。在这里,我们可以用操作符“>”或“<”号来完成这个使命,使查询语句符合SARG形式。如:
Select top 10 * from table1 where id>200
于是就有了如下分页方案:
select top 页大小 *
from table1
where id>
(select max (id) from
(select top ((页码-1)*页大小) id from table1 order by id) as T
)
order by id
在选择即不重复值,又容易分辨大小的列时,我们通常会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表,在以GID(GID是主键,但并不是聚集索引。)为排序列、提取gid,fariqi,title字段,分别以第1、10、100、500、1000、1万、10万、25万、50万页为例,测试以上三种分页方案的执行速度:(单位:毫秒)
页 码
方案1
方案2
方案3
1
60
30
76
10
46
16
63
100
1076
720
130
500
540
12943
83
1000
17110
470
250
1万
24796
4500
140
10万
38326
42283
1553
25万
28140
128720
2330
50万
121686
127846
7168
从上表中,我们可以看出,三种存储过程在执行100页以下的分页命令时,都是可以信任的,速度都很好。但第一种方案在执行分页1000页以上后,速度就降了下来。第二种方案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势,后劲仍然很足。
在确定了第三种分页方案后,我们可以据此写一个存储过程。大家知道SQL SERVER的存储过程是事先编译好的SQL语句,它的执行效率要比通过WEB页面传来的SQL语句的执行效率要高。下面的存储过程不仅含有分页方案,还会根据页面传来的参数来确定是否进行数据总数统计。
-- 获取指定页的数据
CREATE PROCEDURE pagination3
@tblName varchar(255), -- 表名
@strGetFields varchar(1000) = '*', -- 需要返回的列
@fldName varchar(255)='', -- 排序的字段名
@PageSize int = 10, -- 页尺寸
@PageIndex int = 1, -- 页码
@doCount bit = 0, -- 返回记录总数, 非 0 值则返回
@OrderType bit = 0, -- 设置排序类型, 非 0 值则降序
@strWhere varchar(1500) = '' -- 查询条件 (注意: 不要加 where)
AS
declare @strSQL varchar(5000) -- 主语句
declare @strTmp varchar(110) -- 临时变量
declare @strOrder varchar(400) -- 排序类型
if @doCount != 0
begin
if @strWhere !=''
set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere
else
set @strSQL = "select count(*) as Total from [" + @tblName + "]"
end
--以上代码的意思是如果@doCount传递过来的不是0,就执行总数统计。以下的所有代码都是@doCount为0的情况
else
begin
if @OrderType != 0
begin
set @strTmp = "<(select min"
set @strOrder = " order by [" + @fldName +"] desc"
--如果@OrderType不是0,就执行降序,这句很重要!
end
else
begin
set @strTmp = ">(select max"
set @strOrder = " order by [" + @fldName +"] asc"
end
if @PageIndex = 1
begin
if @strWhere != ''
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from [" + @tblName + "] where " + @strWhere + " " + @strOrder
else
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["+ @tblName + "] "+ @strOrder
--如果是第一页就执行以上代码,这样会加快执行速度
end
else
begin
--以下代码赋予了@strSQL以真正执行的SQL代码
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["
+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder
if @strWhere != ''
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["
+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["
+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["
+ @fldName + "] from [" + @tblName + "] where " + @strWhere + " "
+ @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder
end
end
exec (@strSQL)
GO
上面的这个存储过程是一个通用的存储过程,其注释已写在其中了。
在大数据量的情况下,特别是在查询最后几页的时候,查询时间一般不会超过9秒;而用其他存储过程,在实践中就会导致超时,所以这个存储过程非常适用于大容量数据库的查询。
笔者希望能够通过对以上存储过程的解析,能给大家带来一定的启示,并给工作带来一定的效率提升,同时希望同行提出更优秀的实时数据分页算法。
四、聚集索引的重要性和如何选择聚集索引
在上一节的标题中,笔者写的是:实现小数据量和海量数据的通用分页显示存储过程。这是因为在将本存储过程应用于“办公自动化”系统的实践中时,笔者发现这第三种存储过程在小数据量的情况下,有如下现象:
1、分页速度一般维持在1秒和3秒之间。
2、在查询最后一页时,速度一般为5秒至8秒,哪怕分页总数只有3页或30万页。
虽然在超大容量情况下,这个分页的实现过程是很快的,但在分前几页时,这个1-3秒的速度比起第一种甚至没有经过优化的分页方法速度还要慢,借用户的话说就是“还没有ACCESS数据库速度快”,这个认识足以导致用户放弃使用您开发的系统。
笔者就此分析了一下,原来产生这种现象的症结是如此的简单,但又如此的重要:排序的字段不是聚集索引!
本篇文章的题目是:“查询优化及分页算法方案”。笔者只所以把“查询优化”和“分页算法”这两个联系不是很大的论题放在一起,就是因为二者都需要一个非常重要的东西――聚集索引。
在前面的讨论中我们已经提到了,聚集索引有两个最大的优势:
1、以最快的速度缩小查询范围。
2、以最快的速度进行字段排序。
第1条多用在查询优化时,而第2条多用在进行分页时的数据排序。
而聚集索引在每个表内又只能建立一个,这使得聚集索引显得更加的重要。聚集索引的挑选可以说是实现“查询优化”和“高效分页”的最关键因素。
但要既使聚集索引列既符合查询列的需要,又符合排序列的需要,这通常是一个矛盾。
笔者前面“索引”的讨论中,将fariqi,即用户发文日期作为了聚集索引的起始列,日期的精确度为“日”。这种作法的优点,前面已经提到了,在进行划时间段的快速查询中,比用ID主键列有很大的优势。
但在分页时,由于这个聚集索引列存在着重复记录,所以无法使用max或min来最为分页的参照物,进而无法实现更为高效的排序。而如果将ID主键列作为聚集索引,那么聚集索引除了用以排序之外,没有任何用处,实际上是浪费了聚集索引这个宝贵的资源。
为解决这个矛盾,笔者后来又添加了一个日期列,其默认值为getdate()。用户在写入记录时,这个列自动写入当时的时间,时间精确到毫秒。即使这样,为了避免可能性很小的重合,还要在此列上创建UNIQUE约束。将此日期列作为聚集索引列。
有了这个时间型聚集索引列之后,用户就既可以用这个列查找用户在插入数据时的某个时间段的查询,又可以作为唯一列来实现max或min,成为分页算法的参照物。
经过这样的优化,笔者发现,无论是大数据量的情况下还是小数据量的情况下,分页速度一般都是几十毫秒,甚至0毫秒。而用日期段缩小范围的查询速度比原来也没有任何迟钝。
聚集索引是如此的重要和珍贵,所以笔者总结了一下,一定要将聚集索引建立在:
1、您最频繁使用的、用以缩小查询范围的字段上;
2、您最频繁使用的、需要排序的字段上。
结束语:
本篇文章汇集了笔者近段在使用数据库方面的心得,是在做“办公自动化”系统时实践经验的积累。希望这篇文章不仅能够给大家的工作带来一定的帮助,也希望能让大家能够体会到分析问题的方法;最重要的是,希望这篇文章能够抛砖引玉,掀起大家的学习和讨论的兴趣,以共同促进,共同为公安科技强警事业和金盾工程做出自己最大的努力。
最后需要说明的是,在试验中,我发现用户在进行大数据量查询的时候,对数据库速度影响最大的不是内存大小,而是CPU。在我的P4 2.4机器上试验的时候,查看“资源管理器”,CPU经常出现持续到100%的现象,而内存用量却并没有改变或者说没有大的改变。即使在我们的HP ML 350 G3服务器上试验时,CPU峰值也能达到90%,一般持续在70%左右。
导读
本文是我对学习jwsdp-1.2时所做笔记的整理,其中主要是一些指导性的内容,并没有多少概念以及原理的介绍,读者可能觉得略显简单,如果想要学习基本概念可以参考网上有关Web Service的资料。本文例子所使用的开发环境是WindowXP+JWSDP-1.2。
一.Web Service简介
1.定义
由两部分组成
·SOAP--Web Service之间的基本通信协议。
·WSDL--Web Service描述语言,它定义了Web Service做什么,怎么做和查询的信息。
2.简单的Web Service实现
包含四个基本步骤
·创建Web Service的商业逻辑(通常是一些Java类)
·将这些Java类部署到一个SOAP服务器上
·生成客户访问代码
·部署客户应用
注意:WSDL等文件的生成通常是利用厂商提供的工具来完成
3.WSDL解析
WSDL描述语言一般包含三部分
·What部分--包括了type、message和portType元素
Type:定义了Web Service使用的数据结构(使用XML Schema定义)
Message:一个Message是SOAP的基本通信元素。每个Message可以有一个或多个Part,每个Part代表一个参数。
PortType:消息汇总为不同的操作并归入到一个被称为portType的实体中。一个portType代表一个接口(Web Service支 持的操作集合),每个Web Service可以有多个接口,它们都使用portType表示。每个操作又包含了input和 output部分。
·How部分--包含binding元素
binding元素将portType绑定到特定的通信协议上(如HTTP上的SOAP协议)
·Where部分--由service元素组成
它将portType,binding以及Web Service实际的位置(URI)放在一起描述
4.客户端
通常Web Service可以有三种类型的客户
·商业伙伴(Business Partner)--包括分发商,零售商以及大型消费者)
此类客户通过SOAP、WSDL、ebXML、UDDI等XML技术与Web Service连接
·瘦客户--包括Web浏览器、PDA以及无线设备
该类客户通常经由轻量协议(如HTTP)与Web Service连接
·肥客户--包括Applet、各类应用以及现存系统
通常使用重量级协议(如IIOP)连接Web Service
二.使用JAX-RPC开发Web Service
1.JAX-RPC支持的数据类型
JAX-RPC除了支持Java的基本数据类型外还支持一些自定义对象,但这些对象有一些条件限制
·有缺省构造函数的对象
·没有实现java.rmi.Remote接口
·字段必须是JAX-RPC支持的类型
·公有字段不能声明为final或transient
·非公有字段必须有对应的setter和getter方法
2.使用JAX-RPC创建Web Service
·基本步骤
A. 编写服务端接口并实现
一个服务的end-point有一些规定:必须实现java.rmi.Remot接口而且每个方法需要抛出RemoteException异常。
B. 编译、生成并且将所有服务需要的类和文件打包成WAR文件
C. 部署包含服务的WAR文件
·如何创建服务
A. 编译服务所需的类文件
B. 生成服务所需文件
可以使用wscompile工具生成model.gz文件,它包含了描述服务的内部数据结构命令如下
wscompile -define -d build -nd build -classpath build config.xml
-model build/model.gz
define标志告诉工具读取服务的 endpoint接口并且创建WSDL文件。-d和-nd标志告诉工具将输出文件写入指定的目录build。工具需要读以下的config.xml文件
<?xml version=”1.0” encoding=”UTF-8”?>
<configuration xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/config”>
<service
name=”HelloService”
targetNamespace=”urn:Star”
typeNamespace=”urn:Star”
packageName=”helloservice”>
<interface name=”helloservice.HelloIF”/>
</service>
</configuration>
该文件告诉wscompile创建model文件所需的信息
·服务名称:MyHelloService
·WSDL名字空间:urn:Star
·HelloService的所有类在包helloservice中
·服务的端点(endpoint)接口:helloservice.HelloIF
C. 将服务打包为WAR文件
WEB-INF/classes/hello/HelloIF.class
WEB-INF/classes/hello/HelloImpl.class
WEB-INF/jaxrpc-ri.xml
WEB-INF/model.gz
WEB-INF/web.xml
jaxrpc-ri.xml文件如下所述
<?xml version=”1.0” encoding=”UTF-8”?>
<webServices xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/dd”
version=”1.0”
targetNamespaceBase=”urn:Star”
typeNamespaceBase=”urn:Star”
urlPatternBase=”webservice”>
<endpoint name=”Hello”
displayName=”HelloWorld Service”
description=”A simple web service”
interface=”helloservice.HelloIF”
model=”/WEB-INF/model.gz”
implementation=”helloservice.HelloImpl”/>
<endpointMapping endpointName=”Hello” urlPattern=”/hello”/>
</webServices>
D. 处理WAR文件
使用命令行
wsdeploy -o hello-jaxrpc.war hello-jaxrpc-original.war
wsdeploy工具完成以下几个任务
·读 hello-jaxrpc-original.war作为输入
·从jaxrpc-ri.xml文件中获得信息
·为服务生成tie classes
·生成名为HelloService.wsdl的WSDL文件
·将tie classes和HelloService.wsdl文件打包到新的war文件中
E. 在服务器上部署服务
如果你使用的是TOMCAT,你可以将WAR文件拷贝到webapps目录下,然后可以在
<http://localhost:8080/[context]/[servicename>上看是否配置成功
·如何使用JAX-RPC创建Web Service客户端
通常有三种类型的客户:Static Stub、Dynamic Proxy和Dynamic Invocation Interface(DII)
Static Stub客户
·生成Stub
通过使用config-wsdl.xml和wscompile工具,可以生成stub
wscompile -gen:client -d build -classpath build config-wsdl.xml
config-wsdl.xml文件如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl location="http://localhost:8080/helloWS/hello?WSDL"
packageName="staticstub"/>
</configuration>
wscompile工具读取服务器上的WSDL文件并生成stub
·编写静态客户代码
Stub stub=(Stub)(new HelloService_Impl().getHelloIFPort());
HelloIF hello=(HelloIF)stub;
Hello.sayHello(“starchu”);
注意:HelloService_Impl类由wscompile生成
·编译代码
·运行客户端(需要包含saaj API和JAX-RPC API运行)
Dynamic Proxy客户
·生成接口
通过使用config-wsdl.xml文件和wscompile工具,可以生成客户所需的接口
wscompile -import -d build -classpath build config-wsdl.xml
config-wsdl.xml和前面列出的文件内容相同。
·编写动态客户代码
ServiceFactory factory=ServiceFactory.newInstance();
URL wsdlUrl=new URL(“<your web service wsdl url>”);
Service service=factory.createService(wsdlUrl,
new QName(“urn:Star”,”HelloService”));
HelloIF hello=(HelloIF)service.getPort(
new QName(“urn:Star”,”HelloIFPort”),HelloIF.class);
Hello.sayHello(“starchu”);
注意:这里不再需要静态客户代码的HelloService_Impl类
·编译代码
·运行客户端(需要包含saaj API和JAX-RPC API运行)
Dynamic Invocation Interface客户
这个方法为我们提供了更有弹性的客户调用方式,客户代码不在需要由wscompile工具生成的运行时类,当然这种代码更加复杂。具体步骤如下:
·创建ServiceFactory实例
ServiceFactory factory=ServiceFactory.newInstance();
·创建Service(利用服务名的Qname)
Service service=factory.createService(new QName(“HelloService”));
·创建Call对象(使用端点接口的Qname)
Call call=service.createCall(new QName(“HelloIF”));
·设置端点的地址和一些Call对象属性
call.setTargetEndpointAddress(args[0]);
call.setProperty(Call.SOAPACTION_USE_PROPERTY,new Boolean(true));
call.setProperty(Call.SOAPACTION_URI_PROPERTY,””);
call.setProperty(“javax.xml.rpc.encodingstyle.namespace.uri”,
“http://schemas.xmlsoap.org/soap/encoding/”);
·设置远程调用的返回类型、操作名和参数
QName stringType=new Qname(“http://www.w3.org/2001/XMLSchema”,”string”)
call.setReturnType(stringType);
call.setOperationName(new Qname(“urn:Star”,”sayHello”));
call.addParameter(“String_1”,stringType,ParameterMode.IN);
·调用call的invoke方法
String [] param={ “ starchu “ };
String retValue=call.invoke(param);
·编译代码并对Main方法设置<http://localhost:8080/helloWS/hello参数(服务器需有效>)
3.SOAP Message Handler的例子
通常使用JAX-RPC建立的Web Service并不需要开发人员自己处理SOAP消息,但是JAX-RPC提供了一种机制可以使程序员获得这种处理能力,这就是所谓的消息处理器。总的来说,像日志和加解密功能可以通过SOAP消息处理器实现,除此之外,你根本不需要处理SOAP消息。
·基本Handler处理过程
SOAP请求
·客户端处理器在请求消息发送到服务器前被调用
·服务端处理器在请求消息分发到端点前被调用
SOAP应答
·服务端处理器在应答消息发送回客户前被调用
·客户端处理器在应答消息转换成Java方法返回前被调用
SOAP错误
处理过程与SOAP应答的方式一样
注意:处理器可以在任意端组成处理器链
A.Handler基本编程模型
服务端
·编写服务端点接口代码、实现服务并且实现服务端处理器类
·创建jaxrpc-ri.xml文件,以便wscompile使用,其中包含了Handler的信息
·创建web.xml文件
·编译所有代码
·将文件打包为WAR文件
·用wsdeploy工具将原始war文件替换为完整可部署的war文件
·在服务器上部署war文件
客户端
·编写客户程序以及客户端处理器代码
·创建config.xml文件以便wscompile使用,它包含了客户端处理器的信息
·编译代码
·运行wscompile生成服务端点接口和客户类
·编译所有代码,并运行客户应用
B.建立客户端处理器
处理器必须扩展javax.xml.rpc.handler.GenericHandler类并且提供至少两个方法的实现init和getHandlers。此外,你可以利用handleXXX方法处理请求、应答和错误SOAP消息。基本步骤如下
·编写客户端处理器代码
Public class ClientHandler extends GenericHandler{
Public void init(HandlerInfo info){
This.info=info;
}
public QName[] getHeaders(){
return info.getHeaders();
}
public boolean handleRequest(MessageContext context){
SOAPMessageContext smc=(SOAPMessageContext)context;
SOAPMessage message=smc.getMessage();
file://You can use SOAP API to implement your own logic
file://such as logging and encrypt
……
file://Set a logger element in the SOAPHeader
SOAPHeaderElement loggerElement=
header.addHeaderElement(envelope.createName(“loginfo”,
“ns1”,”urn:Star:headprops”));
loggerElement.setMustUnderstand(true);
loggerElement.setValue(“10”);
file://Set a name element in the SOAP Header
SOAPHeaderElement nameElement=
Header.addHeaderElement(envelope.createName(“client”,
“ns1”,”urn:Star:headprops”));
nameElement.addTextNode(“Star chu”);
}
}
·编辑config.xml文件
<?xml version=”1.0” encoding=”UTF-8”?>
<configuration xmlns=”http://java.sun.com/xml/ns/jax-rpc/ri/config”?>
<wsdl location=”http://localhost:8080/handlerWS/handler?WSDL
packageName=”client”>
<handlerChains>
<chain runAt=”client”>
<handler className=”client.ClientHandler”>
<property name=”name” value=”client handler”/>
</handler>
</chain>
</handlerChains></wsdl></configuration>
·编写静态客户
C.建立服务端处理器
·编写服务端处理器(与客户端结构类似)
Public boolean handleRequest(MessageContext context){
SOAPMessageContext smc=(SOAPMessageContext)context;
……
Iterator it=header.examineAllHeaderElements();
While(it.hasNext()){
SOAPElement element=(SOAPElement)it.next();
If(element name is loginfo and must understand it){
element.getValue();
element.detach();
file://Invoke only when the setMustUnderstand(true)
}
}
}
detach方法用来移除元素,这个需求仅当一个元素设置了mustUnderstand属性在必要。
·编辑jaxrpc-ri.xml文件
<?xml version=”1.0” encoding=”UTF-8”?>
<webServices xmlns=”http://java.sun.com/jax-rpc/config/ri/dd”
version=”1.0”
targetNamespaceBase=”urn:Star:wsdl”
typeNamespaceBase=”urn:Star:types”
urlPatternBase=”/handler”>
<endpoint name=”HandlerTest”
displayName=”Handler Test”
description=” … …”
interface=”service.HandlerTest”
model=”/WEB-INF/model.gz”
implementation=”service.HandlerTestImpl”>
<handlerChains>
<chain runAt=”server”>
<handler className=”service.LoggerHandler”
headers=”ns1:loginfo”
xmlns:ns1=”urn:Star:headerprops”>
<property name=”name” value=”Logger”/>
</handler>
<handler className=”service.NameHandler”>
<propery name=”name” value=”Name”/>
</handler>
</chain>
</handlerChains>
</endpoint>
<endpointMapping endpointName=”HandlerTest”
urlPattern=”/handler”/>
</webServices>
在第一个处理器中,XML使用了属性 headers描述头信息。这是因为客户代码告诉服务端,logger头必须被理解,否则客户将收到SOAP错误消息
·生成WAR文件并部署到服务器上
4.源代码
·HelloIF.java(endpoint接口)
package helloservice;
import java.rmi.RemoteException;
import java.rmi.Remote;
public interface HelloIF extends Remote{
public String sayHello(String target) throws RemoteException;
}
·HelloImpl.java
package helloservice;
public class HelloImpl implements HelloIF{
private String message="Hello";
public String sayHello(String target){
return message+target;
}
}
·StaticClient.java
package staticstub;
import javax.xml.rpc.Stub;
public class StaticClient{
private static String endpointAddress;
public static void main(String [] args){
if(args.length!=1){
System.err.println("Usage : java HelloClient [endpoint address]");
System.exit(-1);
}
endpointAddress=args[0];
System.out.println("Connect to :"+endpointAddress);
try{
Stub stub=createStub();
stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,
endpointAddress);
HelloIF hello=(HelloIF)stub;
System.out.println(hello.sayHello(" Starchu!"));
}catch(Exception e){System.err.println(e.toString());}
}
private static Stub createStub(){
return (Stub)(new HelloService_Impl().getHelloIFPort());
}
}
·DynamicClient.java
package dynamicproxy;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.JAXRPCException;
import staticstub.HelloIF;
public class DynamicClient{
private static String wsdl;
private static String namespaceUri="urn:Star:wsdl";
private static String serviceName="HandlerService";
private static String portName="HandlerTestPort";
public static void main(String [] args){
if(args.length!=1){
System.err.println("Usage : java DynamicClient [server Url]");
System.exit(-1);
}
System.out.println("Connect to :"+args[0]);
helloWsdl=args[0]+"?WSDL";
try{
URL wsdlUrl=new URL(wsdl);
ServiceFactory serviceFactory=ServiceFactory.newInstance();
Service service=
serviceFactory.createService(wsdlUrl,
new QName(namespaceUri,serviceName));
HandlerTest proxy=(HandlerTest)service.getPort(
new QName(namespaceUri,portName),HandlerTest.class);
proxy.test();
}catch(Exception e){
System.err.println(e.toString());
}
}
}
·DIIClient.java
package dii;
import javax.xml.rpc.*;
import javax.xml.namespace.*;
public class DIIClient{
private static String qnameService = "HelloService";
private static String qnamePort = "HelloIF";
private static String BODY_NAMESPACE_VALUE ="urn:Star";
private static String ENCODING_STYLE_PROPERTY ="javax.xml.rpc.encodingstyle.namespace.uri";
private static String NS_XSD ="http://www.w3.org/2001/XMLSchema";
private static String URI_ENCODING ="http://schemas.xmlsoap.org/soap/encoding/";
public static void main(String [] args){
try{
ServiceFactory factory=ServiceFactory.newInstance();
Service service=factory.createService(new QName(qnameService));
QName port=new QName(qnamePort);
Call call=service.createCall(port);
call.setTargetEndpointAddress(args[0]);
call.setProperty(Call.SOAPACTION_USE_PROPERTY,new Boolean(true));
call.setProperty(Call.SOAPACTION_URI_PROPERTY,"");
call.setProperty(ENCODING_STYLE_PROPERTY,URI_ENCODING);
QName qnameTypeString=new QName(NS_XSD,"string");
call.setReturnType(qnameTypeString);
call.setOperationName(new QName(BODY_NAMESPACE_VALUE,"sayHello"));
call.addParameter("String_1",qnameTypeString,ParameterMode.IN);
String [] params = { "Starchu" };
System.out.println((String)call.invoke(params));
}catch(Exception e){
System.err.println(e.toString());
}
}
}
·Ant文件build.xml
<project name="helloWS" basedir="." default="deploy">
<property file="build.properties"/>
<property name="build" value="build"/>
<property name="dist" value="${build}\classes"/>
<property name="lib" value="${build}\lib"/>
<property name="src" value="src"/>
<property name="etc" value="${src}\etc"/>
<target name="clean">
<delete dir="${build}"/>
</target>
<target name="init">
<mkdir dir="${build}"/>
<mkdir dir="${dist}"/>
<mkdir dir="${lib}"/>
</target>
<path id="classpath">
<fileset dir="${tomcat.home}">
<include name="jaxrpc/**/*.jar"/>
<include name="jaxb/**/*.jar"/>
<include name="jaxp/**/*.jar"/>
<include name="saaj/**/*.jar"/>
<include name="jwsdp-shared/lib/**/*.jar"/>
</fileset>
<pathelement path="${dist}"/>
<pathelement location="${lib}"/>
</path>
<target name="compile-service" depends="init">
<javac srcdir="${src}" destdir="${dist}" includes="HelloService/**/*.java"/>
</target>
<target name="generate-sei-service" depends="compile-service">
<exec executable="wscompile.bat">
<arg line="-define -d ${build} -nd ${build} -classpath ${dist} ${etc}\config-interface.xml -model ${build}\model.gz"/>
</exec>
<copy todir="${build}">
<fileset dir="${etc}" includes="*.xml"/>
</copy>
</target>
<target name="package-service" depends="generate-sei-service">
<war warfile="${build}\${ant.project.name}-portable.war"
webxml="${build}\web.xml">
<webinf dir="${build}" includes="*.xml,*.gz,*.wsdl" excludes="web.xml"/>
<classes dir="${dist}" includes="**/*.class" defaultexcludes="no"/>
</war>
</target>
<target name="process-war" depends="package-service">
<exec executable="wsdeploy.bat">
<arg line="-o ${ant.project.name}.war ${build}\${ant.project.name}-portable.war"/>
</exec>
</target>
<target name="deploy">
<copy file="${ant.project.name}.war" todir="${server}"/>
</target>
<target name="undeploy">
<delete file="${server}\${ant.project.name}.war"/>
</target>
<!-- Generating Static Client -->
<target name="generate-stubs" depends="init">
<exec executable="wscompile.bat">
<arg line="-gen:client -d ${dist} -classpath ${dist} ${etc}\config-wsdl.xml"/>
</exec>
</target>
<target name="compile-client" depends="generate-stubs">
<javac srcdir="${src}" destdir="${dist}" includes="StaticStub/**/*.java">
<classpath refid="classpath"/>
</javac>
</target>
<target name="package-client" depends="compile-client">
<jar destfile="${lib}\client.jar" basedir="${dist}" excludes="HelloService/**/*.class"/>
</target>
<target name="run-client" depends="package-client">
<java classname="staticstub.HelloClient"
classpathref="classpath"
fork="true">
<sysproperty key="endpoint" value="${endpoint}"/>
<arg value="${server.port.url}"/>
</java>
</target>
<!-- Generating Dynamic Client -->
<target name="generate-interface" depends="init">
<exec executable="wscompile.bat">
<arg line="-import -d ${dist} -classpath ${dist} ${etc}\config-wsdl.xml"/>
</exec>
</target>
<target name="compile-dynamic-client" depends="generate-interface">
<javac srcdir="${src}" destdir="${dist}" includes="DynamicProxy/**/*.java">
<classpath refid="classpath"/>
</javac>
</target>
<target name="package-dynamic-client" depends="compile-dynamic-client">
<jar destfile="${lib}\client.jar" basedir="${dist}" includes="**/HelloIF.class,**/DynamicClient.class"/>
</target>
<target name="run-dynamic-client" depends="package-dynamic-client">
<java classname="dynamicproxy.DynamicClient"
classpathref="classpath"
fork="true">
<sysproperty key="endpoint" value="${endpoint}"/>
<arg value="${server.port.url}"/>
</java>
</target>
<!-- Generating Dynamic Invocation Interface -->
<target name="compile-dii">
<javac srcdir="${src}" destdir="${dist}" includes="DII/**/*.java">
<classpath refid="classpath"/>
</javac>
</target>
<target name="run-dii" depends="compile-dii">
<java classname="dii.DIIClient"
classpathref="classpath"
fork="true">
<sysproperty key="endpoint" value="${endpoint}"/>
<arg value="${server.port.url}"/>
</java>
</target>
</project>
·属性文件(build.xml文件使用)
server=C:/Java/jwsdp-1.2/webapps
tomcat.home=C:/Java/jwsdp-1.2
endpoint=http://localhost:8080/helloWS/hello
server.port.url=http://localhost:8080/helloWS/hello
参考资料
1. Developing Web Service Series <http://www.theserverside.com/resources/article.jsp?l=Systinet-web-services-part-1> www.theserverside.com
2. JWSDP-1.2 Tutorial java.sun.com
谈到职业生涯,最基础的是要看一个人和环境之间的适应性。现代的职业观当然不再局限于一个工作,而是找到那些能让自己发挥能力、技术、能表达自己想法、能在某一方面承担某一角色的环境;而对环境的适应也是因性格的不同而相异的。
一、传统型:这种个性类型的人在事务性的职业中最为常见。这一类人容易组织起来,喜欢和数据型及数字型的事实打交道,喜欢明确的目标,不能接受模棱两可的状态。这些人可以用这一类的词语来表述他们:服从的,有秩序的,有效率的,实际的。如果用不太客气的话说,就是缺乏想像,能自我控制,无灵活性。出纳员就是这种类型的典型代表。
二、艺术型:这种类型与传统型形成最强烈的反差。他们喜欢选择音乐、艺术、文学、戏剧等方面的职业。他们认为自己富有想像力,直觉强,易冲动,好内省,有主见。这一类型的人语言方面的资质强于数学方面。如果用消极一些的语言描述,这类人是感情极丰富的、无组织纪律的。
三、现实主义型:这种类型的人真诚坦率,较稳定,讲求实利,害羞,缺乏洞察力,容易服从。他们一般具有机械方面的能力,乐于从事半技术性的或手工性的职业(如管道工、装配线工人等),这类职业的特点是有连续性的任务需要却很少有社会性的需求,如谈判和说服他人等。
四、社会型:社会型的人与现实主义型的人几乎是相反的两类。这类型喜欢为他人提供信息,帮助他人,喜欢在秩序井然、制度化的工作环境中发展人际关系和工作。这些人除了爱社交之外,还有机智老练、友好、易了解、乐于助人等特点。其个性中较消极的一面是独断专行,爱操纵别人。社会型的人适于从事护理、教学、市场营销、销售、培训与开发等工作。
五、创新型(企业家型):这种类型的人与社会型的人相似之处在于他(她)也喜欢与人合作。其主要的区别是创新型的人喜欢领导和控制他人(而不是区帮助他人),其目的是为了达到特定的组织目标。这种类型的人自信,有雄心,精力充沛,健谈。其个性特点中较消极的一面是专横,权力欲过强,易于冲动。
六、调查研究型:这种类型与创新型几乎相反。这一类型的人为了知识的开发与理解而乐于从事现象的观察与分析工作。这些人思维复杂,有创见,有主见,但无纪律性,不切实际,易于冲动。生物学家、社会学家、数学家多属于这种类型。在商业性组织中,这类人经常担任的是研究与开发职务及咨询参谋之职。这些职务需要的是复杂的分析,而不必去说服取信于他人。
当然,一个人往往不是单一地表现某种类型,常常是两三种类型的组合,但不管怎样,总要往积极的性格方向发展,要让自己选择工作,而不是工作选择自己。
医师:治病+保健最赚钱医生从事的是高科技和高
风险的职业,
人才培养周期也大于其它行业,因此,做医师自然越做越值钱。
律师:向专业领域发展
律师的工作性质类似于医生,属于经验行业。律师今后的发展方向应努力朝某一个专业领域发展,把握该领域的发展趋势,成为某一方面的专家里手。尤其是商业律师、企业法律顾问等专业方向相对容易获得高薪。
教师:应用型+方法论吃香
在许多大中以上城市,教师的收入水平已经超过一般白领。今后,应用型的教师将更容易获得高薪,因此,教师也必须经常走进企业,不断积累“实战”经验,以此丰富自己的知识结构。而中小学教师面对的是有升学压力的学生,故而传授“方法”显得极其重要。
古玩鉴定师:等于“古董”职业
古玩鉴定行业的门槛较高,需要具备相当深厚的中国传统文化知识,而这些知识通过突击不能获得,因此,称该职业为“越老越值钱”的“古董”职业一点也不为过……
精算师:年薪起步价20万
普通和初级财务人员目前供大于求,但高端财务人才却千金难觅。如果想在这个行业发展,考取相应的资格证书只是第一步,丰富的经验能赋予你高效的管理能力、处理突发事件的应急能力。
建筑设计师:稀缺人才
属于稀缺性人才,目前市场上对建筑设计人才大多要求5年以上的工作经验,有多年的研究和实践经验的支持,薪水一定能层层攀高。
咨询顾问:新兴行业
咨询顾问的工作就是替企业诊断“疑难杂症”,并开出“药方”,要用最前沿的眼光为企业做出最具战略意义的分析。
无论你正值豆蔻年华,还是浓情绽放,抑或花期已过,都应该做些胸部保养,因为它是女性的第二张脸。先检查一下这张脸是否足够结实、挺拔、浑圆?皮肤是否柔软细腻有弹性?是否因产后哺乳导致胸部下垂变形?是否因减肥不当导致胸部消瘦缩小?是否因为工作压力大造成胸部皮肤弹性张力减低而松弛、下垂
、变形?是否因年龄关系体内激素分泌减少产生萎缩?总之,无论是先天发育不佳,抑或健康、压力、产后哺乳,更年期等原因造成的不良影响,都会危及到你的玲珑美丽。
紫颖倡导由内而外的自然美丽。采用法国天使丰姿技法与点穴原理创新结合的自然丰胸技法,效果看得见,已为众多女性带来完美曲线。结合传统循经点穴手法,紫颖将这一西方经典的美胸技术融入传统的点穴丰胸之中,由专家指点操作于背部、胸部,疏通经络,加之理性互动按摩,促进血液循环;进一步使胸大肌强健,提高乳房基础质量;促进胸部脂肪细胞吸收更多营养,同时加大脂肪细胞体积;从而达到理想的丰胸目的。更多精彩内容可上WWW.ZIYING.COM.CN。
每年6月是报关员资格全国统一考试的报名时间,那么报关员工作容易找吗?报关员是“金领职业”吗?什么样的人适合从事这一行?
带着这些问题,我们走访了上海报关协会培训部负责人季岳生先生和长发国际货运有限公司空运部陶伟明先生,从行业管理和企业用人的角度,他们给想要入行的人士提供了有价值的建议。
【考试】
门槛不高过关率低
随着我国外贸进出口量的不断扩张,报关员考试的热度也不断在上升。从上海地区报名情况看,2001年有1.1万多人报名,到2003年,人数已经上升到2万多人。
季岳生认为,单从报名的热度上评价这个行业的热度有一定片面性。可以看到,接连几年上升的报名人数,一方面与外贸行业的人才需求息息相关,另一方面,也与目前不容乐观的大中专毕业生就业形势相映衬,在增加的报名人数中,大专生、“三校生”占到了很大比例。
同时,每年报关员考试的过关率并不高,全国平均仅在10%左右,即使是直接从上海海关教育培训中心出来的考生,能一次通过考试的也只有23%。报关员考试最大的难度在“报关实务”部分,这个内容占到考试内容的60%,很多没有实际操作经验的人往往在这上面丢分。
不过,也有人指出,以中专及高中学历为界的报名门槛使得考生队伍平均素质不高,报关员考试通过率较低也是在所难免的,有志于此者不必让通过率吓倒了。
【求职】
证书为经验锦上添花
由于报关员考试报名热度不断升温,报关员考试培训也成了“香饽饽”,不少培训机构表示,只要考到证,工作就一片光明。
对此,季岳生先生认为应该客观地来看待报关员证书的分量。一方面,据他的了解,目前在上海,拥有报关员证书而找不到工作的人几乎没有,他表示:“如果有找不到工作的,可以到报关协会来联系,我们可以帮他们与企业牵个线。”但另一方面,季先生承认,报关员证书能带来的职业前景并非像一些培训机构所说的如此光明,毕竟这个市场容量是有限度的,特别是随着电子化报关时代的到来,技术升级会降低人工的需求,而对个人技能的要求越来越高。
作为用人单位,长发国际货运有限公司空运部陶伟明先生把报关员证看得更淡。他表示在招聘中,工作经验才是最关键的因素。像他们招聘的空运报关员,更是强调经验,因为航班可不等人,万一有一个环节弄错了,就很难弥补了。如果是新手,多半只能“跟着别人跑”,一些审单工作,往往需要有三五年工作积累的报关员。
【工作】
薪资不均“金饭碗”太沉
社会上流传着“报关员是个金饭碗”的说法,不过,业内人士同时指出,这个说法有点偏颇。
陶先生表示,报关员的工作相当辛苦,由于海关实行的是“5+2”通关,天天不关门,报关员必须跟着市场走,没有固定的休息日,加班是常事。另外,和白领比,报关员工作环境并不理想。通关口岸往往人声鼎沸,而通关手续又繁杂,长期从事这个工作,耐压能力和心态调整非常重要。
季先生从薪资角度提出疑议。据他透露,报关员薪资差别非常大。一般来说,跑单的,月薪1000元左右很正常,而负责制单的,月薪能到三四千元。经验越多,薪水还会往上走。
另外,自理报关员的收入与企业的效益相关,报关员所在的外贸企业效益好,报关员的收入也不低,很多高薪的说法大多从这些公司传出来;比较而言,代理报关公司的薪水就较难保证,代理公司竞争激烈,相互间压价厉害,薪资反而不如自理报关员。
季先生也坦言,在就业难背景下,这个行业确实在迅速扩张中,因此,短期之间,报关员职业的热度不会退。
优秀报关员基本条件
1.熟悉外贸知识,了解产品属性和贸易流程,正确填写报关单。
2.熟悉海关各类政策、条款及相关流程,及时了解海关政策调整。
3.要具有出色的人际沟通能力,这样在办理通关手续时,能清楚回答海关问题,解释疑惑。
4.工作耐心细致,把准备工作做在前面,避免返工,加速通关。
5.良好的廉政意识,切忌对海关工作人员行贿。
面对电脑时间长了不好,那该怎么办?其实每天四杯茶,不但可以对抗辐射的侵害,还可以保护眼睛。
1、上午一杯绿茶:绿茶中含强效的抗氧化剂以及维生素C,不但可以清除体内的自由基,还能分泌出对抗紧张压力的激素。绿茶中所含的少量咖啡因可以刺激中枢神经,振奋精神。不过最好在白天饮用,以免影响睡眠。
2、下午一杯菊花茶:菊花有明目清肝的作用,有些人就干脆用菊花加上枸杞一起泡来喝,或是在菊花茶中加入蜂蜜,都对解郁有帮助。
3、疲劳了一杯枸杞茶:枸杞子含有丰富的β胡萝卜素、维生素B1、维生素C、钙、铁,具有补肝、益肾、明目的作用。其本身具有甜味,可以泡茶也可以像葡萄干一样作零食,对解决电脑族眼睛涩、疲劳都有功效。
4、晚间一杯决明茶:决明子有清热、明目、补脑髓、镇肝气、益筋骨的作用。
喜庆祥和的2007“金猪”年刚至,新华保险隆重推出一款集快速见利、祝寿养老、高额保障、分红增值等多重功能于一体、特别适合在春节期间销售的拳头产品。该产品充分吸收了目前市场上热销的快速返还型产品特色,保险期间至被保险人88周岁保单生效对应日。产品的交费期限分10年交、20年交两种,其中10年交投保年龄为30天以上-50周岁,20年交投保年龄为30天以上-45周岁。
富贵人生两全保险(A款)(分红型)产品具有快速见利、六六祝寿、年金养老、高额保障、分红增值和特别豁免六大特点。分别是:
快速见利:66周岁前每两年领取有效保额的8%,补充日常开销。
六六祝寿:66周岁领取100%有效保额作为祝寿金,实现晚年心愿。
年金养老:66周岁开始每年领取有效保额的8%,以年金形式补充养老基金。
高额保障:66周岁前300%有效保额的身价保障;随着家庭责任的减轻,66周岁开始还拥有150%有效保额的身价保障。
分红增值:通过保额分红,复利递增,使领取金和身价保障不断提高,保值增值。
特别豁免:全残免交续期保费,继续享受所有保障利益。
富贵人生两全保险(A款)(分红型)产品到底能给客户带来什么利益呢?笔者专程走访了新华保险公司产品专家,据其介绍,该产品设计科学、合理,可解决客户的零用、保障、养老和人生规划四大需求。分别为:1、零用。提供零花钱。66周岁前有效保额8%的领取可补贴日常开支,或实现固定的小额开销规划,如门诊医疗金、年度专项体检金、家庭旅游金等。2、保障。提供高额保障。66周岁前300%有效保额的身价保障,66周岁开始150%有效保额的身价保障,抵御未知风险。3、养老。66周岁祝寿金和以后每年有效保额8%的年金领取,补充养老金。4、人生规划。以现有资金,规划未来不同阶段的生活,抵御各种未知风险。
该理财专家还介绍了富贵人生产品与众不同的五大优势:一是返还基础不同。“富贵人生”是以有效保额作为返还基础,可有效缓解通货膨胀压力。二是返还频率和额度不同。“富贵人生”设计更科学合理。 66周岁前每两年返有效保额的8%,既解决客户的部分现金领取需求,又不会对账户的长期收益造成很大影响;66周岁后每年返有效保额的8%,有效补充养老金。三是祝寿金设计不同。“富贵人生”领取适时,设计切合客户退休后规划生活的需求,66周岁返还100%有效保险金额,为客户开始一段新的人生提供资金支持。四是身故保障的设计不同。“富贵人生”66周岁前300%有效保额的身价保障,66周岁开始150%有效保额的保障,切合客户不同阶段的人生责任。五是独具保费豁免功能。“富贵人生”设计更加人性化,特别增加了全残豁免保费的功能,面临人生变故,无须继续交费,仍然可以享受全部保险利益。
据理财专家介绍,当前百姓投资渠道狭窄,房地产投资风险加剧,股市及基金专业性强,投资市场正处于不太明朗、潜藏风险的状况之下,新华保险《富贵人生两全保险(A款)(分红型)》的面世为广大市民理财提供了一个新的选择。
同样是分红险,保额、保费差不多,分到的红利却相差十余倍?目前,在加息预期下,分红型保险成为大热险种,但其实,目前,市场上的分红型保险产品虽然同样名曰“分红险”,可实质上却使用两种不同的分红方式,一种是现金分红,一种是保额分红,市民在选择分红险的时候,要结合自己的实际情况进行挑选。
两种分红方式
美式分红:即通常所说的现金分红,中国人寿(行情论坛)、平安保险(行情论坛)、友邦保险等大多数保险公司均采用美式分红。
英式分红:即通常所说的保额分红,新华人寿、信诚人寿、太平人寿的一些产品则采用英式分红。这两款产品的分红之所以差异如此之大,就在于它们根本采取了不同的分红方式。
案例
保费相近 红利相差数百元
市民赵先生去年投保了一份分红型寿险,分20年交保费,年缴保费3600元,保险金额10万元,最新接到的一份保险公司的分红通知让赵先生不禁大为欣喜,今年的分红有900多元,按照自己3600元的投入,相当于一年的收益率超过了20%!保险的回报率竟然有这么高?
可在赵先生印象中,自己的朋友小唐同样是去年投保了一款分红型寿险,保险金额10万元,一年要缴保费3000多元,拿到的分红也就只有几十元而已。
同样是分红型保险,为什么红利的差距如此悬殊?赵先生颇费思量。
两种分红方式的计算
以投保人同样买10万元保额分红险为例,20年缴,年保费3000元,假设分红率均为1%。
若采用美式分红,保险公司以现金价值的多少为依据计算分红,并以现金的形式派发当年分红。粗略来看,第一年的年终分红为3000×1%=30元;第二年为3000×2×1%=60元,以此类推。
而若采取英式分红,保险公司根据保额计算分红,并将当年分红自动累加到保户的保额上,将红利变成保额,复利递增。
举例来看,年终分红可以这样计算,假定每年分红率为1%,由于保险金额为10万元,第一年的年度红利为10万元×1%=1000元;第二年的年度红利为(10万元+1000元)×1%=1010元;而第三年的年度红利为(10万元+1000元+1010元)×1%=1020.1元,以此类推。
现金分红 PK 保额分红
收益性
保额分红短期收益有优势
从计算红利的基数来看,现金分红的计算基数为“保单的现金价值”,保额分红的计算基数是保险金额,由于投保初期保单的现金价值比较小,因此,现金分红险投保初期现金红利很小,没有保额分红的优势。但从长期看,随着所交保费越来越多,与保额分红的差异会逐步缩小。
从“分红率”来看,由于现金分红,每年派发现金红利需要较高流动性,可能会制约保险公司投资收益的空间,而保额分红派发的红利会直接增加到保额上,这样保险公司可以增加长期资产的投资比例,在某种程度上可增加投资收益,使被保险人能保持较高且稳定的投资收益率。
保障性
保额分红保障性较高
从保障的角度来看,保额分红的优势则比较明显。
保额分红是以保额为基础进行分红,同时,会将当期红利增加到保单的现有保额之上。这就相当于使投保人在保障期内,无需核保和申请增加保额,保额自动增加,可在一定程度上缓解因通胀导致的保障贬值。
灵活性
现金分红可留也可取
保额分红的红利领取不如现金分红灵活。
以赵先生和小唐的例子来看,第一年的分红,赵先生所获得的红利是900多元,而小唐的分红只有几十元,但由于这笔钱转成了保额,赵先生实际上是无法从保险公司拿到这笔钱的,只能在发生保险事故、保险期满或退保时,才能拿回所分配的红利。
小唐所能获得的红利看起来不多,但采取现金分红的方式,红利性质比较灵活,可留也可取,只要小唐想领取现金红利,就可以拿到这笔分红。而目前现金分红险可提供累计生息、抵交保费、购买缴清增额等方式。
建议
流动性要求高选现金分红
如果市民不急于将红利取出,可选择保额分红产品,长期将获得更高的保障,并有望获得更好的收益。但如市民对现金的流动性要求较高,则可选择现金分红产品,灵活性更高。
此外,不管是“现金分红”还是“保额分红”,能否给投保人带来更多的利益,关键还在于保险公司的运营情况。如果保险公司的资金运营能取得更高的收益率,分红自然也会水涨船高,市民不要被分红方式这种表面上的差别所迷惑,而更应该关注所投保的保险公司的运营能力。
- 凭出生日期的数字测出内在性格和潜在力量的测验,看似不可思议,但近来在日本十分流行,而且被测者都发现能找到准确而奇妙的答案,不信?玩过就知。
玩法:请从你出生年、月、日中,找出不同的数字。例如:1978年12月9日,就有(2个1)(1个2)(1个7)(1个8)和(2个9),然后请参阅下文,就会知道你的感情表达能力、思维能力、失恋治疗能力等内在玄妙了。
分析:
(1)代表感情表达能力
(1个1):你属于固执而不懂表达感情的人,故经常暗恋人。由于你都算理智,甚少被情所伤。
(2个1):你善于表达感情,面对心仪对象,往往能大胆示爱。由于你喜怒形于色,恋爱过程亦见顺利。
(3个1):你不易透露心底秘密,往往经过深思熟虑,才会将事情告知他人,所以做你的情人要有十足耐性。
(4个1):你十分敏感,情绪起伏不定,毫不掩饰自己的喜怒哀乐,容易意志消沉,需要情人不时地鼓励。
(5个1):你极度情绪化,容易伤害别人,作为你的情人,一定要对你耐心关怀,才能彼此沟通无阻。
(2)代表直觉度
(1个2):你懂得顾及别人的感受,善于洞悉别人的想法,是一个可靠的朋友和情人。
(2个2):你善解人意,乐于助人,爱付出。对于异性来说,你的细心体贴甚具吸引力。
(3个2或以上):你的直觉一般,幸好反应能力强。你喜欢多姿多彩的生活,对神秘的爱情也心向往之。
(3)代表思维能力和想象力
(1个3):超强的想象力令你能散发独特的魅力,而你同时向往浪漫而甜蜜的恋爱。
(2个3):言行常超出常规,常胡思乱想。与爱侣相处时,经常云游太虚,令对方觉得无趣。
(3个3或以上):你智商很高,思维清晰,无法忍受单调的生活,若没机会发挥才能,会变得精神紧张。
(4)代表行动力
(1个4):热情澎湃,言出必行,自信十足,你会大胆表露内心感情,性欲亦旺盛。
(2个4或以上):做人缺乏自信心,对于爱侣忠心耿耿。不会有越轨的念头,亦期望另一半对你专心不二。
(5)代表意志坚定度
(1个5):思想单纯,即使情人见异思迁,你亦不会放弃,希望有守得云开见月明的一日。
(2个5):你的意志并不坚定,容易半途而废,往往事倍功半,想好好发挥才能,最好将精力放在创意活动上。
(3个5或以上):内心有着无法克制的热情,做事冲动,决不会改变自己决定的事情,还要另一半听你指示。
(6)代表自我价值
(1个6):你天性敏感,喜欢被别人欣赏,只有这样,你才能感受到自己存在的价值。
(2个6):你多愁善感,缺乏自信,伴侣对你的爱护,是你发挥才能的推动力。
(3个6或以上):你有绝对的自信心,为了令自己与众不同,永远全力以赴,喜欢出风头。
(7)代表失恋治疗能力
(1个7):谈恋爱时,你会为对方周全考虑,失恋治疗能力亦强,对人欢笑,背人垂泪。
(2个7):由于你每次恋爱都很投入,故失恋时,往往伤得很深,需要向别人倾诉,才能解开郁结。
(3个7或以上):你不易爱上人,但一旦恋爱,会是十分专情的情人。如果被抛弃,你会对曾经的一切念念不忘。
(8)代表智力和逻辑性
(1个8):你智力一般,但逻辑性强,做事喜欢循序渐进,不喜欢预期以外的变化。
(2个8或以上):你聪明独立,表达能力强,有决断能力,有领导才华,做事往往得心应手。
(9)代表体贴度
(1个9):你尝试理解别人对感情的渴求,然后尽量配合。
(2个9或以上):无论智力或精力,你都非常旺盛,但缺点是经常沉醉于自己的想法中,令情人觉得你难以捉摸。
(10)代表精神力量
(1个0):你一生的时间和精神力量,均被情人及朋友瓜分,紧记要在友情和爱情之间找出一个平衡点。
(2个0或以上):你的自我精神极强,在逆境时往往能看出事情的真相,甚少作出错误决定
1,开户
2,第一次交易,
3,查询份额,
4,计算收益,
5,赎回,转换
6,选择基金
7,合理规避风险
8,投资心态
9,投资原则
10,投资组合
11,封闭基金
首先,这是完全写给新人看的扫盲帖,老基民不必看了谢谢。
基金销售分为代销和直销。代销一般在银行和证券公司,直销都是直接在基金公司买卖,可以在基金公司的销售部门或者网站买到。直销较之代销有以下优势:其一,手续费低,一般为0.6%,有的只有0.3%,比代销便宜一半左右;其二,直销的基金可以在母公司的其他基金中自由转换,而且也有费率优惠,一般都是单向收费,只需要申购费,赎回费免除,基金公司称这个费率为“转换费”,很多基金公司转换收费非常优惠;其三,转换节省时间,当天就可以确认,省去了赎回和重新申购的资金在途时间,节约了时间成本。这就是直销的3大好处。当然,直销也有很多不方便的地方,比如,开户麻烦,特别是对新人来说,逐个去基金公司网站开户是一件很麻烦的事,而且,很多人担心网上交易的安全问题。其实我个人的经验,网上交易还是很安全的,并没有我们想象的那么危险。在这里我只说如何买直销基金。尽量写简单些,让每个初次接触基金的人都能明白。
1.开户
首先准备一张银行卡,最好开通网银。如果没有网银的,农卡可以通过电子支付卡支付,也很安全的。其他银行卡没用过,不清楚了。下面以农行卡申请电子支付方式为例说明(如果你已经开通网银,这段跳过):
首先申请一张农卡用户,登陆www.95599.cn农行网站申请一张电子支付卡,,过程按照网站指示操做。先登陆农行网站,然后在申请电子支付卡表格,需要填写的信息是:卡号,密码,图形码。登陆到你的银行卡帐户页面里,然后在你的帐户里申请电子卡,需要填写的信息是:银行卡卡号,身份证,CVD号,(就是你的银行卡正面的3个独立的数字),接下来的两项不用管。每日使用限次(根据个人爱好,如果你一天内买的基金很多,就设高点,我一天一般最多买3只,所以设定的是3)总支付次数限次(可以高点,比如999)。电子卡时效,可以设长点,1年或者半年。设置好后退出。此时你已申请到9559打头的一个数码,这个号码就是电子交易号。这个号码可以不记,银行系统会替你记录的,下次交易支付时,会自动弹出。
然后找到你想买的那家基金公司,在它的网站上找到“网上交易”,进入页面后找到“开立新户”。然后按照提示进行操作(注意填写真实资料)。到了确认支付手段时,会自动跳转到你填写的那家银行的网页上,一般会提示使用“客户证书”或“电子支付卡”支付,如果你有网银,选“客户证书”支付,没有的可以选电子支付卡,这样刚才申请的电子卡就派上用场了。输入支付密码,确认支付成功,一般会自动跳转到基金公司网站,如果没跳转,你可以点“通知客户支付成功”,也会转回来。
回到基金公司网站,接着填写完资料提交就可以了。这时候,它会提示你你的交易帐号会在T+N开通,你可以查询。老实说交易帐号没什么用,只是你用来登陆系统交易的证书,由于基金公司同时提供身份证登陆方式,比这个更好记,所以可以不用理会这个。
2,第一次交易
开好帐户后可以马上登陆交易了。在基金公司网站上找到“网上交易”-登陆方式选择:开户证件,填写你的身份证(或其他开户证件),密码填写你开户时留的8位交易密码。
登陆进去后,就到了基金公司给你设定的个人交易界面了,这是你的天地。找到交易项,里边有申购,认购,赎回,转换等等。我们只说申购,点申购,找到你想买的基金,然后直接填写申购金额,费用支付方式一般选先付。填写完毕,提交付款,付款时会自动跳转到你帮定的那张银行卡上,和确认支付手段一样,选择合适的支付方式进行支付就可以了。支付完毕,注意保留交易流水号,这是你一旦出现意外时和基金公司交涉的证据。只有付款成功的交易才是有效的交易,只提申请而没付款或者付款时出现意外的,都视做交易失败。一般的意外情况有:网银客户证书到期,电子支付卡到期,网络出现问题,交易提交失败等等。这种情况是不会转款的,所以没关系,重买操作一次。网银客户证书到期,需要到银行去办理。电子支付卡到期,先登陆你的银行卡帐户,先注销原来过期的电子卡,然后重新申请一张就可以了。基金公司实行T+1确认原则,一般情况,第二个交易日就可以在你的基金帐户里看到你申购的基金份额了,最晚的如华夏基金,第3个交易日就可以看到。通常,第三个交易日就可以赎回或者做转换操作。
3,查询份额。
上边说过了,申购成功后,一般会在第二个交易日查询到你申购的基金份额。所谓的交易日,就是你提出申请的那一天。基金公司和交易所工作日同步的。周6-7休息,周1-5早9:30-下午3:00工作日。由于网上直销是7*24小时全天候交易的,,所以就有了“交易日”,也就是“T”的概念。在正常交易日里,3点前算当天的,3点后算第二天的。从星期5下午3点后知道星期一3点前,都算一个交易日,星期1的。
首先,需要登陆你的基金公司帐号,然后就可以在里边看到你申购到的基金份额,每日单位净值,每日帐面资产等信息。有的公司在帐户初始页面就可以看到,而像华夏基金就比较麻烦,必须在它的帐户查询里找“基金份额查询”才可以看到。
4,计算收益
收益的计算其实每天都有基金公司替你在做,没必要自己去做。不过很多新人喜欢每天自己去看看自己当天的收益是多少,其实这是一种不好的习惯,这个会让你患得患失。老想着会亏,而忘记了基金是需要长期持有的。
基金净值及其每天的增长都是实时公布的,可以去基金公司网站看,或者去一些专业基金网站去看也可以.由于版规限制,这里不提.
计算收益,新人喜欢看涨了多少钱,老基民都是看涨了多少比率。其实这个很简单,高净值的基金,增长绝对值很容易高过低净值基金,关键是增长比率的大小。
基金帐户里的收益一般会在第二天9:30以后计算出来并合并到你的本金里的,这个时段之后你看到的都是合并了你的收益的最新金额。
5,赎回,转换
这个需要首先登陆到你的基金帐号里才可完成的操作。在“交易”栏里。需要注意的是,新申购的基金,只有在T+3日以后(包括第3日)才可以赎回或转换。(认购的基金除外)这里有个技巧,如果你要赎回一只基金(特指股基),可以先把它转成货币基金,然后再赎回。一般股票基金是T+2-7日到帐,就是你的钱会在你提出赎回的第2-7个交易日里到达你事先绑定的银行卡里。而同一公司的货币基金一般是T+1-2到帐。先把股基转货基,需要等2天可以操作(比如你今天转,下下个交易日就可以从货币基金赎回)从股基到货币在到赎回到帐一般是3-4天,节省时间不说,在货币基金的2天里,还可以享受货币基金的收益。
一般货币基金买卖是不收费的,股票基金转货币基金,按赎回费率计算,货币转股基,按申购费率计算。有些公司还有其他优惠。
6,选择基金
如何选择一只有潜力的基金和适合自己的基金,是一件很困难的事,也是需要很大工夫的。很多新人喜欢听别人的意见,或者看最近几天那只基金涨的快,就选那只,选基金完全可以自己做主。
首先,你得明白自己需要什么,也就是说你可以承受多大的风险。基金种类很多,有偏股的,配置的,复制指数的,债券的,货币的等等。货币基金是很好的现金管理工具,适合把一些零花钱放里边,总体上比存银行收益高(零花钱你不可能存定期)。债券基金收益高低完全取决于它打新股的能力,因为现在市场上就没有什么好的债券。一般风险也不大,年收益在10%左右。指数基金很有潜力,美国等成熟市场里,许多主动型基金跑不赢这种被动基金已经成为一种时尚。不过指数基金需要长期看好市场才行,熊市不要买。配置基金有相当比例债券配置,听说比较安全,不过我还没看出来。偏股基金最受大家推崇,不过你随时都会有坐过山车的感觉。
对应以上种类的基金,可以选择适合自己的投资,我要说的是,如何在偏股基金众多“个基”中选基金。我的方法是,首先要对市场后市有一个中期的判断,对未来市场热点的把握要有自己的观点。这些观点从那里来呢?建议你去多学习最近的市场机构评论,多看几家,虽然不是每家都准确,但是大家一致看好的版块一般是不会错的。比如前段时间很多机构预测钢铁行业景气回升,之后钢铁股都有很好的表现。当你看好某个行业的时候,就基本确定了选择的范围,接下来去找到和你的判断相符合的基金。很多基金定期都会发布下个阶段投资策略报告的,看看他们投资策略里有没有你看好的那个版块,是不是重点投资,如果是,那就买它吧,没错的。
从3000之后的行情,发动机是指数期货和一季报,特别是大盘蓝酬股的季报,所以我觉得大蓝酬是个很好的机会,在3000点前我建仓易50指数基金,我的理由是:上证50是中国最优秀的大盘蓝酬的集中地,完全符合我对后市的判断,所以就买了。这段时间收益不错。同期的嘉实300等其他指数基金也很好,当然最好的是易基深100ETF,只是我没开立证券帐户,买不了,遗憾。
7,合理规避风险
这里我要说的有两个风险,系统风险和市场风险。
系统风险是在交易过程形成的,主要是交易双方沟通出现问题时才会出现。比如在网上交易中,由于网速的问题,交易过程断线了,或者准备操作时基金公司网页涌堵进不去造成的损失等等。首先要找一家信的过的基金公司,对那些有黑幕管理不规范的公司最好远离,再怎么说本金安全才是最重要的。有些公司存在同公司基金利益输送,和私募基金勾结抬轿等等问题,这些公司最好不要碰了,哪怕它有基金收益很高也不要买。其次,尽量提前完成操作,每天2:30之后基金网站一般都很堵。
市场风险对长期投资者而言是不存在的。对喜欢打波段的人来说,这个很重要。特别是新人,都喜欢频繁的进出。当市场上出现很多看空的声音而大盘还在上涨的,八成不久就要调整了,不过深度的大小谁也说不好。如果真要规避这个风险,那就在看空已经很多而大盘还在涨的阶段坚决出吧,不要太贪。机构预测不算很准确,毕竟要比我们准确些。
如果你是单纯的规避风险,就不要赎回股基,而是转换成货币基金,这样下次要买时方便,也方便你纠正自己的错误(卖完后悔的,最多第3天再买进)节省时间。
8,投资心态
这需要很长的时间去培养。我只说一点,基金是理财,不是股票,需要长期持有,胜不骄败不馁,要能沉的住气。亏只是暂时的,持有10年以上的基金出现亏损的概率趋向于0。其实股票也是一样,股票是什么,是投资者对所投公司的所有者权证,买了股票你就成了它的股东,就像合伙做生意一样,把钱较给它,你得给它时间让他有时间开展业务赚钱,这个过程可能会出现亏损(好比股票暂时的下跌),不过只要公司业务是正常的,赚钱是肯定的,这个过程需要时间。基金是靠投资股票赚钱的,同样的道理,你得给你选择的基金经理足够的时间,让他去完成他要做的。所以我觉得看好一只基金,除非它的投资策略偏离了你当初选择它的原因,否则就放心交给他让他去搭理吧。
9,投资原则
借用刘彦斌的“五个一”。第一个,一生你要恪守量入为出;第二个,不要让债务缠住你的一生;第三个一,永远不要想一夜暴富;第四个,一夫一妻一个小孩;第五个,一生做好一项投资。
我再补充:不要让自己没有退路,不要贷款买基金,要留够应急的钱。用来投资的钱一定是你现在不急着用的。急用的钱只能放货币基金里。也不要去做自己不熟悉的东西,比如期货。
10,投资组合
这个说大点,投资不全是基金,还有股票,房产,期货,黄金,收藏,保险,银行存款等等。保险是一定要买点的,银行存款是不保险的(他连通货膨胀都跑不赢,能说安全吗?),收藏是一群大忽悠在瞎呗,黄金保值是不天方夜谭,个人投资期货是要死人的,房产是需长期投资的,股票(包括基金)才是最好的投资。中国经济长期看好,中国的公司在迅速长大,搭上中国经济快车,连外国姥都明白的道理。
大的组合应该是一部分保险(很必要),一部分基金,一部分蓝酬股和必要的现金储备。
小的组合,现金储备全放货币基金里。指数基金配备很必要,债券基金风险小,留点,其他选股基。一半开放基金,一半封闭基金。现在什么最安全,封闭基金最安全。
11,封闭基金
现在封闭基金的折价率高的出奇,这为我们提供了很大的套利空间。指数期货出来之后,封闭基金可以通过期货投资提前锁定部分收益,这样可以降低封闭基金蕴涵的部分市场风险,封基的价值回归过程完全可以预见,我看好。指数期货的套利保值作用会让折价率回归到正常水平,5%以内,不排除个别优秀封闭基金溢价交易.长期投资,封闭基金安全系数更高!
有很多朋友问我该如何区分这三种数据,我首先根据WFMC的规范以及个人的理解把这三种数据的定义给出来,然后在每个定义后边有具体的解释:
工作流控制数据(Workflow Control Data):由工作流管理系统或工作流机管理的内部数据。[解释]工作流执行服务器维护内部控制数据,来确定过程实例或活动实例的状态,并支持其他内部状态信息。这种内部控制数据不能被访问,也不能进行转换。但用户可以通过接口对某些数据进行查询。
==============
工作流相关数据(Workflow Relevant Data):工作流管理系统用来判断过程中状态转移是否可以执行的数据。[解释]工作流管理系统使用工作流相关数据来判断转移条件是否满足,并选择下一个要执行的活动。这些数据能被工作流应用程序访问,这些数据也需要通过工作流执行软件在活动间传递。
==============
工作流应用数据(Workflow Application Data):应用程序的具体数据,并且不能被工作流管理系统访问。[解释]工作流应用程序数据不能被工作流执行软件所使用,只与应用程序或者用户任务的执行相关。应用程序与其需要用到的工作流相关或应用程序数据间的关系,会在工作流定义中说明。
其实没必要分三种,分工作流数据和业务数据就好了。
激进型:重点关注指数基金
市场展开了一轮强势的逼空行情,上证指数(行情论坛)已突破3500点,场外资金正源源不断的流入股市,后市仍可积极看多,投资组合中可全仓股票型基金,重点关注指数型基金,本周推荐诺德价值优势和大成300,诺德价值优势基金是诺德基金管理公司发行的第一只基金,作为一家新成立的合资基金管理公司,其外资股东为大名鼎鼎的美国独立资产管理公司———诺德·安博特公司,该基金在产品设计、投资理念、风险控制等方面均参照诺德·安博特美国产品的相关参数,并结合中国国情。大成300是大成基金公司管理的以沪深300(行情论坛)指数为标的的指数型基金,该基金成立刚满一年,累积收益率就超过140%,高于同期成立的其他主动投资基金的业绩,显示出指数化投资在牛市中的优势。而数据显示,今年第一季度沪深300指数上涨了36.29%,明显高于上证指数19.01%和上证50指数26.75%的涨幅,作为即将推出的股指期货的标的指数,沪深300指数的优势已经显现出来了。近期该基金进行了每10份6.1元的滚动分红,分红后基金单位净值回到1元附近。
稳健型:七成投资债券基金
指数在一片惊叹声中越涨越高,稳健型投资者应适当的获利了解,落袋为安,建议投资比例为股票型基金占30%,债券型基金占70%。本周重点推荐基金金鑫(行情资料)和银河银信添利,基金金鑫是国泰基金管理公司管理的一只封闭式基金,2014年到期,当前折价率仍接近20%,该基金重仓持有已经停牌近一年的双汇发展,有较大的套利空间。银河银信添利基金以债券投资为主,但可以适当比例的参与新股申购,在满足本金稳妥与良好流动性的前提下,尽可能的追求基金资产的长期稳定增值,是稳健型投资者的不错选择。
保守型:货币基金是避风港
对于保守型投资者,当前近乎疯狂的市场还是少参与为妙,应将资金主要投资于绝对安全的货币市场基金以及申购新股上,建议投资比例为股票型基金占10%,货币型基金占90%。本周推荐友邦华泰上证红利ETF(行情净值) 和南方现金增利,推荐友邦华泰上证红利ETF是友邦华泰基金管理公司旗下的一只以上证红利指数(行情论坛)为标的指数的交易型指数基金,上证红利指数成份股是由上交所股息率最高、现金分红最多的50只股票组成,买入该基金就如同买入了一只高分红的超级大盘蓝筹股,保守型投资者可小比例的参与。南方现金增利是我国规模最大的货币型基金之一,南方基金管理公司在固定收益类资产的管理上有着丰富的经验,由于当前新股的发行频率较高,投资者可进行申购新股和申购货币基金的滚动操作,在本金毫无任何风险的情况下,充分提高资金的利用率。
不同风格类型的基金组合
组合类型 配置基金品种 基金类型 配置比例 基金品种简评
激进型组合 诺德价值优势 股票型 50% 诺德基金管理公司发行的第一只基金,作为一家新成立的合资基金管理公司,其外资股东为大名鼎鼎的美国独立资产管理公司——— 诺德·安博特公司,该基金在产品设计、投资理念、风险控制等方面均参照诺德·安博特美国产品的相关参数,并结合中国国情。
稳健型组合 基金金鑫 股票型 30% 国泰基金管理公司管理的一只封闭式基金,2014年到期,当前折价率仍接近20%,该基金重仓持有已经停牌近一年的双汇发展,有较大的套利空间。
银河银信添利 债券型 70% 该基金以债券投资为主,但可以适当比例的参与新股申购,在满足本金稳妥与良好流动性的前提下,尽可能的追求基金资产的长期稳定增值。
保守型组合 友邦华泰上证红利ETF 股票型 10% 友邦华泰基金管理公司旗下的一只以上证红利指数为标的指数的交易型指数基金,上证红利指数成份股是由上交所股息率最高、现金分红最多的50只股票组成,买入该基金就如同买入了一只高分红的超级大盘蓝筹股。
南方现金增利 货币型 90% 我国规模和品牌都属一流的货币市场基金。
编者按:应博友的需求,特此借用融通基金公司的一篇文章来阐述指数基金投资的优势与策略。以下的文章也是选自工商银行广东省分行《财富管家》杂志,在此特别鸣谢融通基金公司和工商银行的支持。关于最新的指数型基金分析文章,我将在本周推出,以供博友参阅。
投资有风险,责任需自担!!!
“股神”忠告:买指数基金
不买指数基金是你的错
说这话的,是有“股神”之称的巴菲特。
巴菲特在伯克希尔公司2004年度报告中写道:“成本低廉的指数基金也许是过去35年最能帮投资者赚钱的工具,但是大多数投资者却经历着从高峰到谷底的心理路程,就是因为他们没有选择既省力又省钱的指数基金,其投资业绩要么是非常普通,要么是非常糟糕。”
纵为“股神”,巴菲特还是在2003年、2004年连续两年输给市场。2004年,伯克希尔公司净资产收益率为10.5%,低于同年标准普尔指数10.9%的收益率;2003年伯克希尔净资产收益率为21%,与标准普尔指数相比落后了7.7%。
巴菲特并不是第一个意识到指数基金投资价值的。在此之前,有专家曾做过这样一个统计,在大部分时间里,曾有3/4以上的主动型基金,绩效不如标准普尔500指数。美国第二大基金公司-----先锋公司主席鲍格尔根据1942年至1997年间的基金表现研究发现,标准普尔500指数几乎年平均值超过积极型共同基金的平均水平约1.3%;又根据1978年至1999年间的基金表现研究发现,在基金管理人中,有79%的业绩比不上指数基金的业绩。
从长期来看,指数基金能够取得优于70%的业绩,已被国内外学者的实证研究所证明。而从短期来看,牛市更要买指数基金,道理极为简单:因为主动投资的基金对市场热点的把握很难做到次次准确,反而是以指数为投资标的的被动型基金更具优势。
牛市投资指数基金
2006年第三季度基金季报显示,90%的基金经理认为A股市场主要指数在2007年将会走高。因此,无论投资者是偏好中短线的波段操作,还是着眼于长期投资,现在都是投资指数基金的时候。
事实上,牛市投资指数基金,已经被越来越多的投资者所认同。据有关机构统计,自2003年以来的历次行情中,指数基金的净值增长均稳居前列。开放式基金今年三季度报告显示,融通巨潮100指数基金、融通深证100指数基金分别三季度申购比例分别为240.5%、59.7%,净申购比例在所有开放式基金中分别居第二位和第七位。融通基金管理公司也成为惟一有两只基金三季度申购份额比例居前十的公司。
据业内人士透露,融通旗下的这两只指数基金在第四季度继续保持高比例的净申购。
挑选指数的四大攻略
从2003年第1只指数基金问世以来,短短3年中,已有13只指数基金上市,预计今年还将有一批指数基金登场。指数基金将越来越多,那么如何选择指数基金呢,业内专家给出选择指数基金的四点建议。
选择指数基金开发和管理能力强的基金公司。指数基金尽管属于被动投资,但选择指数标的、降低跟踪误差、减少交易操作等,仍需要基金公司有较强的管理能力。目前,国内已有10家基金公司推出了指数基金。其中融通、华安、易方达各管理两只指数基金,经验相对丰富。
选择所跟踪指数表现优越的基金。指数基金的业绩表现决定于它所选择标的指数。所以我们需要评估现有指数的市场品质。目前,国内历史较长的指数有深证100、上证180、中标300以及新华A200指数;新生代指数有沪深300、上证50和巨潮100。以2005年以来股市的3个上涨阶段表现来看,深证100和沪深300表现强劲,其次是巨潮100和中标300。与之相对应的便是融通深证100、易方达深证100ETF、融通巨潮100、嘉实沪深300、大成沪深300等基金。
选择跟踪误差小的基金。评价指数型基金及基金经理,不能过分看重短期业绩,而应将跟踪误差当作重要指标。某指数基金取得超出跟踪指数20%的业绩,实际只能算是一只主动性股票基金,而不能被看作是被动型指数基金。只有既能较好地控制跟踪误差范围又能取得超越标的指数业绩当属优秀的指数基金。有研究表明,近一个季度、近一年以及成立以来的“超越标的指数的涨幅/跟踪误差”指标值,融通深证100、长城久泰和易方达50、银华-道琼斯88都是胜出的。
选择分红回报能力强的基金。尽管从长期来看,指数是上涨的,但指数不可能总是上涨,牛短熊长是证券市场规律之一。指数基金的被动性投资特性,似乎让它无法主动规避市场风险。其实不然,通过在市场高位兑现盈利进行分红,指数基金将有效减少熊市所遭受的损失。同时,选择一只分红纪录良好的指数基金,也有助于改善持有人的现金流状况,满足持有人落袋为安的要求。截至11月20日,13只指数基金中,融通系的两只指数基金------融通巨潮100、融通深证100分别以0.54元/份、0.46元/份的分红纪录居前两位,在所有开放式中也分别排名第三和第八。融通巨潮100自2005年5月成立以来的分红,是两年期定期存款的20多倍。
名词解释
指数基金 以拟合目标指数、跟踪目标指数变化为原则,实现与市场同步成长的基金品种。它的投资采取拟合目标指数收益率的投资策略,分散投资于目标指数的成分股,力求股票组合的收益率拟合该目标指数所代表的资本市场的平均收益率。
指数基金与其他基金的区别在于,它跟踪股票和债券市场业绩,所遵循的策略稳定,它在证券市场上的优势不仅包括有效规避非系统风险、交易费用低廉和延迟纳税,而且还具有监控投入少和操作简便的特点,因此,从长期来看,其投资业绩优于其他基金。
美国是指数基金最发达的国家。先锋集团率先于1976年在美国创造第一只指数基金———先锋500指数基金。指数基金的产生,造就了美国证券投资业的革命,迫使众多竞争者设计出低费用的产品迎接挑战。到目前为止,美国证券市场上已经有超过400种指数基金,而且每年还在以很快的速度增长,最新的指数基金产品是交易所交易基金(ETFs)。
1、封闭式基金具备双重估值优势---重仓持有的大蓝筹股估值较低以及自身折价率较高的双重优势,再加上可能出台的强制性分红措施将迫使其交易价格向净值回归,封闭式基金将具备较好的投资价值和风险防御能力。
2、随着中国金融期货交易所成立,即将推出的国内股指期货交易有望激活812亿份封闭式基金市场,使得封闭式基金折价率有下降的趋势,其套利年化收益率回归到合理水平。
3、股指期货做空制度的出现有望给投资者一种全新的封闭式基金套利交易模式,即买空股指期货,同时买入封闭式基金,用对冲交易来套取封闭式基金的折价空间。事实上,针对封闭式基金和股指期货的这种套利交易,已经在英美等国不同程度地促进了封闭式基金的折价率回归理性。
4、融资融券业务开闸,还使得封闭式基金将成为融资融券业务抵押品,并且享有与国债相同的高折算比例80%,其稳定收益将成为券商该项业务最有价值的筹码。
5、管理层关于封闭式基金改革的设想,新型封闭式基金的即将推出,为现有的封闭式基金提供了行情发动的契机。
6、交易手续费低于开放式基金,且流动性好于开放式基金。
7、分红不收利息,所有的分红款都是持有人的,不用像股票那样缴税20%。
8、买了基金之后不用像买股票那样整天提心吊胆盯盘,你只要放心睡觉即可,有基金经理替你打点。
我从1月中旬开始定投,计划每月投3000块。听别人说,定投虽然平均了你的收益,但同时也平均了你的风险,真是这样吗?
投资者 王先生
王先生,你好!
投资基金有两种方式:一为“单笔投资”,一为“定期定额”(或称定投)。单笔投资是一次拿出一笔金额,选择适当时点买进基金,并在市场高点时获利了结;定期定额则是指每隔一段时间,通常是每一个月,投资固定金额于固定的基金上,不必在乎进场时点,也不必在意市场价格起伏,时间到了就固定投资。
股市短期波动难以预测,一些新进入的投资者,如果买不对时点,短期就要承受亏损的心理压力。分批买入,虽不能保证买在低点,但保证了不买在高点,长期投资之后,只要找一个相对好的时机卖出基金,获利的机会应是很大的。
以摩根富林明在香港的一只基金为例,1990年至今的十几年间,JF东方基金先后经历了亚洲金融危机、科技股泡沫等大的市场波动,基金净值也曾有过缩水40%的记录,但时至今日,十几年如一日坚持扣款的投资人仍能获得7%的年复利回报。
哪种基金更适合定期定投呢?根据海外市场长期经验,选择波动性较高的基金品种(如股票型基金),与波动性较低的基金品种(如债券型基金)相比,效果更好。摩根富林明在香港有两只基金:JF东方基金和JF太平洋证券基金,二者1990年至今的净值增长率差不多,均在520%左右,但期间后者的波动较大,假如从1990年起同样定投两只基金,投资波幅较大的基金效果更好。
一些投资人误以为定期定额投资要办理赎回时,只能将所持有的基金全部赎回结清。其实定期定额也可以部分赎回,或是部分转换。如果临时需要资金,而市场后续走势仍然看好,投资人不必一次赎回全部的基金,可以赎回部分基金,其它份额可以继续持有等到趋势明朗再决定。
确定投资目标之后,基金投资者开始着手组建自己的基金组合。基金组合需要多少只基金才是最好呢?其实数量并不重要,构建基金组合最终要实现的就是分散投资。在基金组合中,核心组合占据了主要位置,并对整个投资组合的最终风险回报起着决定性因素。
什么是核心组合?核心组合是投资者持有投资组合中,用以实现其投资目的的主要部分,它是整个投资组合的坚实基础。如果将构建基金组合比作盖房子,构建核心组合则是打地基。地基是否牢固决定了房子以后的稳固,因此构建核心组合也对投资者最终投资目标的实现起着决定性的影响。
如何选择核心组合
核心基金应该是业绩稳定、长期波动性不大的基金。这就要排除那些回报波动很大的基金,例如前一年回报为前几名,第二年却跌至倒数几名的基金。许多投资者,特别是风险承受能力较差的投资者,往往在基金业绩差的时候就赎回了,因此不容易长期坚持持有。业绩稳定的基金虽然往往不是那些最抢眼的基金,但却起着组合稳定器的功能,在组合中其他基金回报大幅下降的时候,可以减少整个组合回报的下降幅度。
对于长期投资的组合,不妨选择大盘风格的基金作为核心基金组合。因为大盘基金投资的大多是的大盘股票,而大盘股票通常比小盘股票的波动性小。在大盘基金中,哪种风格的基金更适合作为核心基金呢?平衡型基金兼有价值型和成长型的特征,波动也较小,不失为好的选择。此外,基金经理投资策略比较分散的基金,往往波动性也比较小,比较适合担任核心组合中的角色。
对于中短期投资的组合,可以选择债券型基金成为核心基金组合。投资者应选择那些持有高信用级别、期限较短的债券的基金,因为信用级别较高的债券风险较小,期限较短的债券波动也较小。也就是说,选择债券型基金作为核心组合的时候也是挑选那些业绩稳定、波动性小的基金。现时,国内有的债券型基金持有一定的股票或者可转债(行情论坛),从长期看股票和可转债的波动风险会比普通债券大,因此投资者应当避免选择股票和可转债持有比例较高的债券型基金作为核心组合成员,而尽量挑选纯债比例较高的基金。
核心组合比例应超半仓
核心组合的比例可以占整个组合的70%—80%,甚至100%,因人而异,也没有固定的标准,具体需结合投资者的投资目标和风险承受能力来决定。但是核心组合最好占投资组合的一半以上,因为它是投资者赖以实现投资目标的主要部分。如果投资者的投资风格比较保守,可以仅投资在核心组合上,或者大部分投资在核心组合上。
投资组合中,核心组合之外的为非核心组合。非核心组合可以投资在中小盘基金、行业基金或是投资策略比较集中的基金。非核心组合通常波动较大,却可带来较高的回报。虽然非核心组合并不是投资组合中必需的部分,但可以帮助提升整个组合的回报以及增加投资的分散性。
优秀的基金产品在于能够通过主动投资管理,追求超越大盘的业绩表现。这说明基金投资不仅要有收益,更要获得超越市场平均水准的超额收益。将这一投资理念量化后贯彻到基金产品中来,就是要通过主动管理的方式,追求詹森指数(或称阿尔法值)的最大化,来创造基金投资超额收益的最大化。只有战胜了市场基准组合获得超额收益,才是专家理财概念的最佳诠释。投资者只有投资这样的基金产品,才能真正达到委托理财,获得最大收益的目的。
这里的核心概念,詹森指数实际上是对基金超额收益大小的一种衡量。这种衡量综合考虑了基金收益与风险因素,比单纯的考虑基金收益大小要更科学。一般在进行基金业绩评价时,基金收益是比较简单的指标;如果要求指标考虑到基金风险因素,则有詹森指数(Jensen)、特雷诺指数(Treynor)以及夏普(Sharpe)指数等综合性评价指标。
詹森指数是测定证券组合经营绩效的一种指标,是证券组合的实际期望收益率与位于证券市场线上的证券组合的期望收益率之差。1968年,美国经济学家迈克尔Ÿ詹森(Michael C. Jensen)发表了《1945-1964年间共同基金的业绩》一文,提出了这个以资本资产定价模型(CAPM)为基础的业绩衡量指数,它能评估基金的业绩优于基准的程度,通过比较考察期基金收益率与由定价模型CAPM得出的预期收益率之差,即基金的实际收益超过它所承受风险对应的预期收益的部分来评价基金,此差额部分就是与基金经理业绩直接相关的收益。
用等式表示此概念就是:
基金实际收益= 詹森指数(超额收益)+ 因承受市场风险所得收益
因此,詹森指数所代表的就是基金业绩中超过市场基准组合所获得的超额收益。即詹森指数>0,表明基金的业绩表现优于市场基准组合,大得越多,业绩越好;反之,如果詹森指数〈 0,则表明其绩效不好。
之所以要提到综合性业绩评价指标,是因为检验投资基金能否战胜市场不是一件容易的事情,不能只简单地比较基金净值和市场指数的增长率大小,而应该综合考虑收益和风险两个方面。投资基金的收益通常用一段时期内资产净值的平均增长率表示。基金的风险则分为绝对风险和相对风险,前者是指基金资产净值的绝对波动情况,用净值增长率的标准差表示;后者是指基金资产净值相对市场指数波动的敏感程度,用基金的贝塔系数表示。一般来说,收益越高,风险越大;收益越低,风险也相对较小。
此外,在比较不同基金的投资收益时,用特雷诺指数和夏普指数可对其进行排序,而詹森指数优于这二者的地方在于可以告诉我们各基金表现优于基准组合的具体大小。詹森指数法直接建立在诺贝尔经济学奖成果资本资产定价理论基础之上。按照这一理论,随机选取的投资组合,其阿尔法值应该等于零。如果某一投资组合的阿尔法值显著大于零,则表明其业绩好于大市;如果投资组合的阿尔法值显著小于零,则表明其业绩落后于大盘。可见,詹森指数的特点是在度量基金业绩时引入了市场基准指数,能够较好地反映基金关于市场的相对表现。
自此我们得知,综合考虑收益和风险双方面因素后,衡量基金相对业绩(即能否战胜市场)的合理方法应该是从其收益中减掉与风险相关的那部分超额收益,即詹森指数所代表的内容,这也就是为什么要在基金投资中突出詹森指数的涵义。
因此,投资者可以参考詹森指数,来对基金投资的期望收益与证券市场的期望收益进行比较。投资基金可能在某一段时期收益是一个负值,但这并不表示这个基金不好。只要在这一阶段詹森指数为正,尽管基金的收益是一个负值,我们还是可以认为这个基金是一个优秀的开放式基金;相反,即使某一段时期投资者所购买的开放式基金有显示的现金收益,但如果它的詹森指数是一个负值,那么就表示投资者所购买的开放式基金是一个劣质的开放式基金,因为别的投资者100元能赚20元,而这个基金管理人只能帮投资者赚10元,投资者应当考虑重新选择新的基金。由于将基金收益与获得这种收益所承担的风险进行了综合考虑,詹森指数相对于不考虑风险因素的绝对收益率指标而言,更为科学,也更具有可比性。将詹森指数的概念运用于基金投资中,追求詹森指数的极大化,也就是追求基金超额收益的极大化,是基金投资业绩超越市场组合的最优体现。
- 热点变换难把握是目前股市主要特征。如只偏爱某一类风格“明星”基金,今年或要频频与市场热点擦身而过。基民不妨考虑一种“明星+热点”式股票基金投资策略,即将80%核心资产投资于经过考验的明星基金,长期持有追求稳定收益;而将剩余20%据热点配置于“卫星基金”来。
案 例:踩错热点错失20%收益
去年8月底,小肖投资了一只重仓持有大盘蓝筹股的大盘股基金,受益于去年下半年大盘蓝筹股的飙升行情,短短4个月就大赚了近50%。
可今年一季度,中小盘成长股成为振荡行情中的热点,重仓投资中小盘成长股的小盘股基金整体涨幅居前,大盘股基金却普遍表现沉寂。(名词解释:大盘股基金、小盘股基金和大盘、小盘基金分别指什么?)
在今年一季度,最牛的小盘股基金——华夏大盘(净值持仓)精选净值上涨了近61%,众多小盘股基金累计净值增长率超过40%,而小肖所投资的那只大盘股基金仅仅上涨了不足25%。
观察了两个多月,小肖终于耐不住,在3月底将手中的大盘股基金换成了一只今年一季度涨约46%的小盘股基金。可谁曾想,近几天,大盘蓝筹股重新走强,自己抛弃掉的那只大盘股基金近一周来累计净值上涨了超过7%,重又回到涨幅榜前列,而那只小盘股基金上周涨幅只有6%,小肖不仅蚀了手续费,一周时间内还少赚了1%。
而从年初到现在,如果照着小肖的操作反着来,先投小盘股基金,再投大盘股基金,多赚个20%并非难事。
难 题:热点变化或成常态
小肖的尴尬并非特例。热点变换已成为目前股市的主要特征,与之相对应,不同操作风格的基金开始轮流坐上业绩佼佼者的宝座,于是依然按照去年的操作策略,只偏爱某一类“明星”基金的基民,鲜有大赚者。
据统计,一季度,累计净值涨幅前三名的开放式基金——华夏大盘精选、华夏中小板ETF及益民红利成长都是精选中小盘成长类股投资的小盘股基金,一季度累计净值分别上涨60.91%、45.73%和42.72%,远高于23.37%的平均水平。
而在3月底大盘蓝筹股回暖的背景下,大盘股基金近期再现崛起迹象。上周,业绩涨幅居前十位的开放式基金中,南方稳健贰号、上投中国优势、易方达价值精选等大盘股基金再度现身。
对策:80%“明星”基金+20%“热点”基金
面对新的市场特征,基民该怎样选基金,才能踩准股市的热点,在规避风险的前提下,尽可能地获得高收益呢?
其实,在2007年新的市场环境下,基民不妨考虑一种“核心+卫星”,即“明星+热点”的策略来配置自己的股票基金资产。此策略就是将准备投资于股票基金的资产分为两部分,一部分是核心资产,即将股票基金资产中的80%投资于经过牛熊市考验的明星基金,长期持有,以追求长期的稳定收益。
另一部分是非核心资产,即将剩余20%的资产配置于“卫星基金”,“卫星基金”可以依据市场的热点来选择,在市场热点切换中进行调整。这样通过核心基金的稳定和卫星基金的灵活,既可以弥补热点交替中核心基金可能存在的弱势,追求额外的收益,又可避免整个股票基金投资组合的波动过大、风险过高。
方案一:大盘股基金+小盘基金
大盘蓝筹回归、基金规模大型化、股指期货即将推出,种种因素可能导致大盘蓝筹股成为市场持续关注的重点;而中小盘成长股则有超常规增长的潜力,高成长性中蕴涵着投资机会。
因此,基民可供参考的“核心+卫星”的资产配置方案之一是80%核心资产配置于大盘股基金,20%的资产配置于小盘股及其他风格基金。
具体到基金品种的选择,按照“核心基金以追求长期的稳定收益为目的”这一标准,基民可考虑选择1至2只业绩持续居前的基金作为核心基金,我们通过比较最近2年和1年的开放式股票基金的业绩,筛选出5只过往业绩较好且持续稳定的基金供基民参考(表一)。
表一:近两年业绩持续优异的部分开放式基金 |
基金种类 |
基金名称 |
4月6日净值 |
最近一年回报率 |
最近两年回报率 |
股票型 |
华夏大盘精选 |
3.842元 |
255.14% |
106.46% |
|
上投摩根中国优势 |
3.1578元 |
171.88% |
97.07% |
|
富国天益价值 |
1.9573元 |
159.57% |
84.39% |
混合型 |
海富通精选 |
2.7238元 |
146.07% |
70.85% |
华安宝利(净值持仓)配置 |
1.483元 |
125.91% |
69.33% |
注:数据来源于Morningstar晨星,截止日期2007年4月6日。 |
而按照“卫星基金”追求市场热点的主要使命,基民可选择1只资金规模较小、近期表现突出的基金作为卫星基金,此类基金规模小,便于根据市场热点的转换灵活地调整仓位。我们也以此标准筛选出5只基金供基民参考(表二)。
表二:今年以来业绩表现优异的小盘开放式基金 |
基金简称 |
06年12月31日净值 |
07年4月6日净值 |
期间分红 |
区间回报率 |
06年底总份额 |
华夏中小板ETF |
1.194元 |
1.74元 |
0 |
% |
19.65亿 |
益民红利成长 |
1.1193元 |
1.4475元 |
0.15元 |
% |
8.75亿 |
华安宏利(净值持仓) |
1.3976元 |
1.8783元 |
0.08元 |
% |
16.38亿 |
中邮核心精选 |
1.3151元 |
1.4675元 |
0.351元 |
% |
16.34亿 |
广发小盘 |
1.3101元 |
1.7802元 |
0 |
35.88% |
17.32亿 |
方案二:指数基金+小盘基金
此外,在今年这种热点难以把握的市况下,指数基金完全复制或部分复制指数,净值表现与相应指数紧密相连,投资指数基金,不存在择股风险,且长期来看,基民至少可以获得市场平均收益。
因此,基民可供参考的“核心+卫星”的资产配置方案之二是80%核心资产配置于指数基金,长期持有,20%的卫星资产根据市场热点配置于一些业绩表现优异的主动型基金,以争取超越市场的超额收益。
具体到基金品种的选择,考虑到即将推出的股指期货以沪深300(行情论坛)指数为标的,基民目前可选择与沪深300指数相关程度较高的指数基金作为核心基金。
据了解,目前市场与此对应的指数基金有嘉实300(行情净值)和大成300。除此以外,沪深两市还有多只指数基金,如上证50ETF、上证180ETF、深100ETF等ETF,以及如华安MSCI、基金景福等指数型基金,其业绩表现和沪深300指数的相关性较高。
而“卫星基金”的选择则可同方案一。
偏股型:投资重头
现在很多基金投资者都对明年的基金涨势寄予厚望,而偏股型基金是基金大军中对大势反映最灵敏,收益最显著的领域。因此,把握好偏股型基金的组合至关重要。
股票精选型基金:由于通常都是各大基金公司品牌基金,拥有比较优秀、经验丰富的基金经理,因此,以“股票精选型”命名的基金在市场上都有相对较好的表现。
推荐关注:交银施罗德股票精选型基金、泰达荷银行业精选型基金、上投摩根中国优势、易基策略基金、富国天益、富国天瑞
成长型基金:主要投资于高成长性行业,或具有成长特质,成长空间的企业股票。这类基金往往专注于成长性企业,挖掘具有高成长特性的行业和上市公司,积极投资,以获得较高的超额收益。
推荐关注:泰达荷银成长、交银施罗德成长、上投摩根成长先锋
周期型基金:此类基金紧紧把握板块轮动的特点和行业发展的周期,及时将投资风格适应于环境变化下的市场发展周期。
推荐关注:泰达荷银周期基金、上投摩根阿尔法
以上推荐的三大类偏股型的基金,今年建议以股票精选型基金持有为主,配合成长型基金和周期型基金以5:3:2的比例进行配比。
混合型:择优持有
这类基金把风险和收益同样强调,在一个尽可能广的投资范围内合理配置资产,有效地分散风险,实现经风险调整后的收益的最大化。由于混合型基金能够活跃于股市、债市之间,也让投资者更能体会其稳健风格。
推荐关注:华安宝利,交银施罗德稳健
指数/LOF:灵活持有
指数基金:国内的指数基金整体实力不如股票基金,但阶段热点大家可以尝试,加倍谨慎。
推荐关注:银华88,鹏华50,易方达50
LOF基金:最近的LOF基金同样也出现了疯涨行情。LOF基金在关注基金经营绩效外,由于赋予投资者场内、场外两种交易模式,也就具备了投资套利空间,但发展不够成熟稳定,谨慎投入。
推荐关注:富国天惠
债券/货币:风险性低
债券基金:投资者可以运用混合型基金的投资理念,在股市低迷的时候,作为替代股票型基金的投资产品。目前收益大致在3%-8%之间。
推荐关注:华夏债券基金、博时稳定价值债券投资基金
货币基金:由于货币基金的每天计息,赎回T+2到账的特点决定了其高流动性。目前平均收益大致在2%的年利率左右。
尊敬的公司领导:
您们好!
首先祝公司在新的一年中蓬勃发展,取得更加优异的成绩;祝愿公司上下所有领导和员工身体健康,万事如意!我为能在****工作过感到无比的荣幸和自豪,我会怀念在这里的每时每刻,感谢公司给了我这么一个机会。在公司这一年多里,公司领导和周围同事给了我无比的照顾和关怀,让我深切的感受到了公司倡导的人情化管理给我们带来的亲切和温馨。我深信,公司将沿着通往胜利彼岸的高速轨道飞速前进,将一如既往的在*******领域领航!
我是06年初带着满身激情来到北京的,原打算在我们伟大的首都能有一番作为,现在看来这些远大抱负都将离我远去了。也许我是一个弱者,不能去面对困难与挫折,没能很好的预料到即将到来的暴风雨,以至于现在让我措手不及,不敢面对,面对重重压力,我只能选择逃避。毕竟我不单单是我,我还有我的家庭,有我的父母和妻子,我不能太过于自私,不能为了自己的事业而忽略了他们的感受。一切的理想和抱负都是过眼云烟,在这繁忙的首都我终将迷失方向。回家过着一份安静平和与世无争的生活也许是我最好的选择。在家里我能照顾我年迈的双亲,能和妻子相濡以沫恩恩爱爱,能很好的养育我的孩子们,但是在北京这一切都无法做到,面对疯涨的房价,高额的房租,攀升的物价,想在北京安个家永远只是那些被上帝宠坏了的人所能实现的,而我只能痴心妄想。我不想眼睁睁的看着自己的双亲老去,而自己不能很好的尽到做儿子的职责,不想忍受等我有所成时子欲养而父不在的那种痛苦;我不想看着妻子和我在北京居无定所,瓢泼不定的一天天老去,这会让我一辈子永远愧对我的妻子;我不想让我的孩子在刚出生时就不知道他的爸爸在哪,不想让他成为时下最为关注的留守儿童,如果不能尽到一个父亲的责任,我会永远对不住我的孩子……所有这一切让我不得不重新考虑自己的人生,我想我最好的选择就是回家,虽然那会让我留下终生的遗憾,但是我想做一个好儿子,好丈夫,好爸爸比什么都更重要,况且回家我照样可以有自己的一番事业,是金子到哪都会闪光。
这段时间,我会把自己负责的****项目那块很好的完成,会把自己的活交接完毕。如果我的离去给公司带来了好多不便,我表示万分的歉意,希望公司领导考虑考虑我的实际情况给予理解。我真诚的恳请公司领导批准我的辞职申请,在这里我表示衷心的感谢,同时祝愿公司不断壮大发展!
此致
敬礼!
***:**
2007年3月13日
不知不觉做
华为外包项目已一年多了,曾在华为常驻过,也曾负责过项目的测试,感觉对华为外包项目的测试流程较熟悉,故写些心得来与大家分享。
如果竞标成功,项目就开始要启动了。
华为方会提供一份CRS(客户需求)和SOW(工作任务书),华为方派人过来进行需求培训,这时该项目的测试组长也要参与到项目需求的培训和评审,也就是测试工作应该从需求开始介入。
项目经理编写《项目计划》,开发人员产出《SRS》,这时测试组长就要根据SOW开始编写《测试计划》,其中包括人员,软件硬件资源,测试点,集成顺序,进度安排和风险识别等内容。
《测试计划》编写完成后需要进行评审,参与人员有项目经理,测试经理和华为方人员,测试组长需要根据评审意见修改《测试计划》,并上传到VSS上,由配置管理员管理。
待开发人员把《SRS》归纳好并打了基线,测试组长开始组织测试成员编写《测试方案》,测试方案要求根据《SRS》上的每个需求点设计出包括需求点简介,测试思路和详细测试方法三部分的方案。《测试方案》编写完成后也需要进行评审,评审人员包括项目经理,开发人员,测试经理,测试组长,测试成员和华为方;如果华为方不在公司,就需要测试组长把《测试方案》发送给华为进行评审,并返回评审结果。测试组长组织测试成员修改测试方案,直到华为方评审通过后才进入下个阶段――编写测试用例。
测试用例是根据《测试方案》来编写的,通过《测试方案》阶段,测试人员对整个系统需求有了详细的理解。这时开始编写用例才能保证用例的可执行和对需求的覆盖。测试用例需要包括测试项,用例级别,预置条件,操作步骤和预期结果。其中操作步骤和预期结果需要编写详细和明确。测试用例应该覆盖测试方案,而测试方案又覆盖了测试需求点,这样才能保证客户需求不遗漏。同样,测试用例也需要通过开发人员,测试人员和华为方的评审,测试组长也需要组织测试人员对测试用例进行修改,直到华为方评审通过。
在我们编写测试用例的阶段,开发人员基本完成代码的编写,同时完成单元测试。华为的外包项目一般是一次性集成,所以软件转测试部后直接进行系统测试。测试部对刚转过来的测试版本进行预测试,如果软件未实现CheckList清单上的10%,测试部会把该版本打回。否则,软件转测试部进行系统测试。根据《测试计划》进度安排,测试组长进行多轮次的测试,每轮测试完成后测试组长需要编写测试报告,其中包括用例执行通过情况,缺陷分布情况,缺陷产生原因,测试中的风险等等,这时测试人员就修改增加测试用例。待到开发修改完bug并转来新的测试版本,测试部开始进行第二轮的系统测试,首先回归完问题单,再继续进行测试,编写第二轮的测试报告,如此循环下去,直到系统测试结束。在系统测试期间,测试人员还需要编写验收手册,验收用例和资料测试用例等。
完成系统测试后,软件就开始转到华为进行验收测试,其中大概测试半个月,一般会要求测试部派人到华为方进行协助测试,并发回问题单给公司开发人员修改。
如果验收发现的缺陷率在SOW规定的范围内,那么验收成功,华为方付钱给公司,项目结束。如果超过规定的缺陷率,那么公司可能要罚钱了,整个项目组的成员(包括开发和测试)都可能要罚了。这种情况也会有,如果按照流程做事,概率不会很大。
测试流程的规范是很重要的,但是如果要成为优秀的测试人员只知道流程还是不够的,需要学习的东西还很多,包括熟悉相关测试业务,计算机专业知识(linux,oracle,tcp/ip等),开发的架构和语言,性能测试和系统瓶颈分析、调优等。还有性格(细心,耐心)和人际沟通能力也是很重要的决定条件。任重而道远,我刚起步,希望大家一起在测试的路上互励互勉。
本文结合自己的经验,从实践的角度,对项目软件的分析工作从7个方面进行了阐述,并指出一些容易失误的做法。希望能对从事分析工作的同仁有所参考。
软件从使用范围的角度,可分为项目软件和产品软件。
项目软件:即针对特定某个客户的要求,并仅为其使用的软件。又称工程软件,特点是有明确的合同,严格的工期,约定的维护期等。如"XXX公司XXX系统"。
产品软件:即针对某一领域客户的共有需求而开发的软件。特点是通用、功能丰富而冗余,通过一次性的购买行为获得等。如操作系统软件、数据库软件、CAD软件等。
本文就项目软件的需求分析,结合自身的体会,提出一些看法和建议。
1、 依据分析阶段确定合适的客户方配合人员
这一点对于获取真正的用户要求非常重要。通常,客户方会组织专工以上层次的人或在单位小有名气的计算机能手来和开发方分析人员配合,共同整理需求。
应该对客户方配合人员进行分类,层次化,使之和分析的各阶段相对应。
分析的初期,即总体分析阶段,需要得到整体意义上的轮廓需求,此时,应与客户方总工以上层次的人员进行交流,这一层次的人,对未来的系统会有完整的描绘,可以划分出子系统,及其之间的关系,这也是高级管理层对系统的期望。可以以此作为纲领性的文档指导进一步的分析,并约束后续的分析过程,避免需求范围漫无边际的扩大;
专业系统分析阶段,通常,客户单位都会有专业分工,彼此之间既相互独立,又会在某些点上发生联系。此阶段应与客户方专工层次的人员进行深入的讨论。这一层次的人,对自己的专业相当熟悉,对专业内的需求会非常到位,大都年富力强,有相当的阅历和理解能力,甚至自己都可以绘制业务流图,总结业务功能点。对他们应充分鼓励,尽量调动其积极性;
系统关联分析阶段,在各专业系统得到充分分析的基础上,紧接着就要理清它们之间的关系,这是提升需求档次的关键阶段,也是高级领导层和专工都关心的阶段。通常,客户单位都会有一些零散的软件,如财务软件,部颁软件等,这些专业软件都发挥着重要的作用,但都是些信息孤岛,客户会很自然的希望能把这些信息融合到整个系统中来,为更多的人所共享。同时,也希望数据能够在各专业系统间顺畅的流动,从而减少重复劳动,提高工作效率。此阶段应把总工层和专工层召集到一起,共同理清系统间的接口。
经过这三个阶段,对需求的描述将比较准确和完整。
2、 多方位描述同一需求
有一些需求贯穿了从基层人员到高层领导,对此需求应该从各个角度、各个方位给以描述,总结之后才能得到完整的表达,否则可能会漏掉一些信息。这也为后续的设计工作打好了基础。
比如,在设备管理类软件中,有一个概念叫"缺陷",指由于材料老化或外力作用,使得设备处于不正常的运行状态,但还没有到立刻就酿成"事故"的程度,但如不及时检修,就可能出事。对于设备缺陷业务,就涉及到从班组人员到领导,上上下下对此都非常关心,但各层次的人关心的侧重点却不尽相同:领导关心"消缺率"(即缺陷消除率)、"消缺及时率";专工关心缺陷类型和处理方法;班组人员关心消缺工作的人员安排及时间地点。缺陷的业务处理流程依赖于"设备缺陷单"(用于记录缺陷及消除情况),如果仅仅局限于从由基层得到的设备缺陷单上的数据结构(设备名称、缺陷发现人、发现时间、二级单位确认时间、缺陷性质、安排消缺时间、消缺人员、消除日期、处理方法),无法满足专工层的分析要求:对设备的缺陷情况按类型、零部件、型号、生产厂家等分类统计,为设备采购时作为选型参考、调整设备及其零部件的检修周期以减少缺陷发生的频率等,因此需要在原来的设备缺陷单上增加一些相关信息。
3、 清晰化每一数据项
由于需求将作为设计的基础,弄清所有的数据项的来龙去脉对于设计是必不可少的。不能有模糊不清的地方,同时通过对数据项来源的分析,可以让分析人员更清楚的看到数据的流动情况,也会发现一些新的需求点。
4、 充分挖掘潜在需求
由于分析人员对软件技术非常熟悉,一些由于技术所带来的潜在需求对于客户来说,一般很难被发现。不实现这些需求,对整个系统也没什么实质性的影响;实现这些需求,则会锦上添花。
对这些潜在需求,会有两种处理方式:告诉客户,客户会得到启发,可能进一步提出新的需求,会有一些更大胆的想法,从而扩大了需求范围,增加了工作量,甚至会影响到工期;不告诉客户,等客户想到了再说。
这些需求如果对于产品软件,可能会是一个卖点,要尽可能的去挖掘。但对项目软件,考虑各种风险,有时候可能会回避,或对客户隐瞒。
我觉得,不管是否告诉客户,分析人员还是应该去挖掘,最起码可以作为自己的知识积累。
5、 采用科学的分析报告模板
分析完成后,需要形成《需求分析报告》,应采用规范科学的报告模板,通过ISO或CMM的企业,其模板大都非常详尽,不仅仅作为报告模板,还可以指导分析过程。
比如,我所在的企业除了有规范的需求分析报告编写指南、报告模板,还有"需求分析矩阵"和"需求变更报告"用于管理需求和控制变更。
6、 积累领域知识
领域知识对于分析人员很重要,这些知识的广度和深度影响分析结果的准确性和分析进度。分析人员应该通过各种途径去获取这些,不断积累,并进行比较和总结。
7、 抱着学习与指导并存的态度
面对一个新的客户时,分析人员首先必须抱着谦逊的学习的态度,把这看成是丰富领域知识的机会。但并非客户单位的任何层次的人都有值得学习的东西,随着分析人员接触的领域客户不断增多,分析人员对该领域的理解也会越来越深,逐渐会成长为领域专家,会有很多地方超过客户对领域的理解,此时,要以自己的深入理解去指导客户,说服客户,甚至纠正客户的一些错误的认识,得到客户的信任与尊敬,这对迅速顺利的完成需求分析会很有帮助。
8、 误区
在进行需求分析的时候,容易陷入一些误区,导致分析结果不理想。
分析结果越复杂越好
这是技术型分析人员经常碰到的情况,认为分析出错综复杂的关系,花哨的图表才能显示出分析水平高,其实,分析经常要经历"简单-复杂-简单"的过程,前一个简单表现为分析人员"认为简单";随着分析的深入,原以为简单的问题会越来越复杂;最后,经过概括、消化、分解,使得需求简单明了。
必须一次到位
由于项目工期紧,或者客户单位地理位置偏远,不想反复去现场,希望通过一次需求分析就能得到完整的不再改变的结果。有这种情况时,表现为分析人员对客户方配合人员穷追猛问,或坚持要求配合人员做出保证,承诺需求范围不再扩大等等。结果往往是双方关系紧张,配合人员怕担责任,提出过多的灵活的、可配置的一些要求,无端增加了后续设计和编码的工作量。一次到位的想法是不现实的,随着开发工作的进行,用户经常会提出以前没想到的需求,或者更改已有的需求。需求必然有一个迭代的过程,变是不可避免的,关键是对于变化的控制,比如通过正规而繁复的流程提高需求变化时客户付出的代价:告知客户如此变化将会使工期延长,或需要追加资金等等,尽管对于"软件属于买方市场"的现状来说,开发方往往叫不起这个板,但这样的控制还是有一定的效果的。
客户的需求必须全部满足
陷入这一误区的分析人员,往往自己的领域知识欠缺,对客户的需求是否合理,缺乏分辨能力,只能由客户牵者走,这样会带来很大的风险:造成需求冗余,项目返工,更有对需求变化失去控制的危险,随着项目的开展,整个开发团队会越来越痛苦。所以必须加深自己的领域知识,变被动接受为主动引导,进而拒绝客户的不合理需求。
以上所述仅为个人体会,都是些做分析时的基本要求,要做好需求分析工作,还有赖于其他很多因素,如分析方法及辅助分析工具的掌握程度、个人交际能力的高低、语言沟通能力的高低等等,欢迎同行广泛交流,共同进步
中国人大都喜欢用武侠小说来比较软件开发,但是在实战武功中,只有葵花宝典才是最厉害的,也只有掌握了葵花宝典,才能称为“不败”。
但什么才是软件开发的葵花宝典? 让我们先从一些现象出发。我们的前提是,软件开发是一项智力密集型劳动。对于智力密集型劳动,我们观察到的现象是,个体的表现差异很大,团队的表现差异很大,组织的表现差异很大,国家的表现差异很大。这不象体力占主要的劳动,象百米王跑百米的速度也仅比我快50%。但在棋类运动中,一个高手可以车轮战数位低手,而且毫无例外地将他们一一击败!
这些智力运动员表现出的特点是,计算精确而且速度快。其行为很象东方不败。虽然关于葵花宝典的传说很多,但最准确的描述只有一个字“快”。东方不败已经快到了吓人的地步。就象卡斯帕罗夫已快到了深蓝的地步。
有一则关于物理学家玻尔的轶事,有一次玻尔在普林斯顿大学听两个年青教授演讲他们的工作成果。期间玻尔突然发言说,如果照你们的研究算下去,会得到一个很有意思的推论。结果两个年青教授回去计算了两天,果然得出了同样的结论。玻尔是如何做到这样快的?
在软件开发中,我们同样注意到这样一种高手,他们可以每天写出一千行左右的高品质代码。他们可以运用已有的一些软件包,迅速完成一个新的产品。他们可以在很短的时间内,学会一项新的程序语言或是新技术。他们表现出一种神奇的速度。
在武侠小说中,所有的高手都有一些凡人不能企及的表现。象张无忌学太极,用龙爪手击败龙爪手名家;乔峰用太祖长拳击败天下英雄;姑苏慕容以其人之道还治其人之身,令狐冲一剑剌瞎十几双眼睛等等。我认为,之所以他们能做到这样,关键是在于他们快。
快并不意味着不准或品质差。快与品质并不矛盾。
高手的快,其实包含着很高的品质在其中。如果你因为高手的快,就质疑其品质,那就相当于在问:东方不败出手那么快,会不会刺不准?东方不败并不满足于刺死对手,他会在对手身上刺朵花。他把杀人变成了艺术。准确来说,他真正的兴趣不在杀人,而在于艺术。
退一步说,就算东方不败第一击有点偏差,他稍作修正后,马上跟上的第二第三击,也会击中他想击中的地方。在武功差的对手剑还没拨出来的时候,他已杀死对方并刺上了一朵花。
所以真正的软件高手,他并不满足于他的代码能有效地工作了,他认为编程是艺术,并醉心于其中。在低手能写出一个版本的时间里,他已经写出了第十版。其品质当然不可同日而语。就象一个九段棋手,在给定的时间里,他能计算十种可能,并将每种可能计算到100手之后,从中选择一种最有利的下法。低手岂有苟全的机会?
高手写软件总是不停地在重构(refactoring)。高手喜欢迭代式开发。高手说,增量就是打补丁,迭代就是推倒重来。对于软件这种东西,写一遍它可能ok(做到这一点也不容易),写十遍就是一个伟大的产品,再多写一遍它就更伟大些。
高手快的诀窍在于他很熟悉各种东西。高手看书很快,因为每一本新书里,值得他好好看的新技术只有一两章的内容。他能迅速看完,并准确领会这本书的中心思想和价值。而对于一个新手,每句话都是新的,他都需要去理解,每一段例子,他都需要去试。
很少看到一种100%全新的技术或理论。就象java language specification里说的,java没有使用任何新技术,用的都是业界久经考验的技术。对于高手来说,那些技术都是他所熟悉的。自然,很快他就从一个c++高手变成了java高手。如果一个编程新手学java,学两年也不如一个高手学两个月的。高手学新东西快。
高手写代码速度快。统计结果说,人均每人月的有效代码速度大概是300至400行。但那是业界平均生产效率。对于高手来说,这个数字太低了。每天写300至400行是完全有可能的。因为在写代码时,所有知识都已具备,已经没有任何需要他多花时间的事情了。他甚至很少需要debug。
高手重用代码的能力很强,熟悉新的api的速度很快。这也是因为,他曾经使用过很多的api,重用过很多的代码。他知道哪些是可用的,哪些有缺陷。他既过用qt,也用过gtk+,也用过windows api & mfc,也用过awt & swing。新的api对他来说,也是老熟人。
高手喜欢用轻量级的工具,象vi,notepad,最多到ultraedit这样复杂的。高手用这种工具写出很多的东西。这些工具就象东方不败的针。那根针已具有神奇的魔力,有时候它可以当激光枪来用。
对于一些重量级的工具,高手虽不常用,但一经使出也威力大于常人。如果让东方不败用剑,最厉害的剑术名家也会败得很难看。高手其实用过很多的重量级工具,而且深知其优缺点。所以使出来,就会把威力发挥到最大,而把缺陷减少到最小。而低手则不然,总是把缺陷加以大大的发扬而浑不知其精髓何在。就象很多人学用uml、rup、xp、design pattern那样。
高手所学博杂且融会贯通。高手做什么都快,当低手还在一愁莫展的时候,高手已经圆满解决问题,去干别的事去了。
相信你有一点点想成为高手了。但是有一个问题亟等解决,那就是“欲练神功,必先自宫”的问题。这一点其实是有比喻意义的。就是说,你必需抛弃一些世俗的人们很看重的东西。有诗为证:
世人都晓高手好,只是寂寞受不了
世人都晓高手好, 只有名利忘不了
世人都晓高手好, 只有金钱一定要
世人都晓高手好, 天下美女都要抱
世人都晓高手好, 不写代码最最好
高手的武功不是一朝一夕练成的。还记得玻尔那件轶事吗,玻尔回答说,他年青时也计算过很多的问题。在很多计算的基础上,高手能培养起一种感觉。高手不写代码就能做设计是因为他以前写了很多的代码。而且他们会保持写代码,以保证自已的水平不下降。想一想九段高手是如何练成的。最难做到的是能忍受十年磨一剑的寂寞。别人在父母那里撒娇时,他们在一旁用功。十年磨一剑,剑就成了东方不败的针。
在你下定决心要做高手之后,也就是下定决心抛弃那些世俗的追求之后,也就是你下决心忍受那些来自于庸俗的人的白眼、攻击和谩骂之后,你就具备了练成神功的必要条件。
事实上其实你不必一开始就练神功,一开始大家可能是为了钱,房子,汽车,美女才编程序的,然而后来艺术就从中产生了。那时高手就不再关注那些东西了。卓别林曾说过,他开始进入那个圈子也是为了钱,后来艺术就从中产生了。当然,也有人一开始是为了艺术,后来变成为了钱。
所谓三十而立,就是说到了三十,你找到了你的真爱,值得用一生去追求的那种。比如说有的人到了三十认为这一辈子应该赚尽可能多的钱,这也没什么不好,也可以把赚钱本身变成一种艺术,所谓资本运作是也。所以在三十以前,有些私心杂念没什么。三十以后还这样是可耻的。而我,想做一个程序员。
每个人做自己最喜欢的事。这个世界需要程序员,也需要资本运作。所有真正的程序员,他最喜欢的事是编程和他自已。如果他后来去做ceo去了,不再编程,只说明他本来不是一个真正的程序员。
在成为高手的路上,要有热情,要循序渐进,要持之以恒。
要靠自己,书要快快地看。要试图迅速理解其主旨。其实你快快看所接受的信息量,与慢慢看接受的差不多。能明白多少很大程度上取决于你的功底。以后用到再回过头来看。一本对你来说新东西太多的书,不要指望看一次就全理解吸收。就象很多功力不够的人看design patterns那本书一样。慢慢看还不如找到多种 信息来源,都快快看一遍。对于一个完全陌生的领域,只看一本书很远远不够的。
要靠自已,事要快快做。有一个朋友,几年前我介绍他去玩玩linux,他也表示想玩,但他现在还没碰过。他失去了很多机会。
平时要有意识提高自己写代码的速度,其实你一天写15行有效代码,与你写50行有效代码,其品质是差不多的。你应该把那些业界平均水平抛诸脑后,把超越自己做为唯一目标。等到你写了很多各式各样的代码,你的水平就不一般了。一个老师曾向我介绍他的学英语的决窍,他说你去啃原版小说,啃到50 本,就和一般人有很大距离了。就是这个理。如果你写得太慢,怎么能写得多?水平怎么能提高?
要靠自己,学很多别人怕学的东西。低手总会说:这么多东西怎么学得过来啊。于是就少学或不学。这样就成不了高手了。高手有非常广的知识面,有很丰富的经验。知道很多低手不知道的事。玩过很多低手听都没听过的东西。
要靠自己,努力满足客户的各种需求。个人技能是在满足客户的各种需求的过程中提高的。比如你喜欢用delphi,客户说一定要用vb,那你就答应他,然后把自己培养成为vb的高手。用户的需求看似**,但对你是一个机会。
怎样才能做到看书快,写代码快,学新东西快,一个显而易见的途径就是将工作并行化。你在一台机器上make时,同时可以在看别的文档和聊天。对于计算机是这样,对人也是这样。如果你只能串行地处理问题,你的速度将提高有限。你的大脑有很大潜力可挖,它应该是一个多任务分时系统。努力减少它 idle的时间。搞经济的samuelson被人称为human brain main frame,可见他的大脑有多快。
让你的思维快起来,你就会区别于那些反应迟钝的人。如果你不能让人生的道路变长,就让它变宽。这世界变化快,需要你变得比它快才行。
这样加快并不会让你短命,相反,你有更多的时间来享受生活和锻炼身体。你的生活将更有品质,更丰富,更有意义。面对变化,你将立于不败之地。我们都是和自己赛跑的人,需要跑得比昨天的自己更快。
一、什么是系统分析
在具体的研究需求分析之前,我们先了解一下软件工程这个概念。软件工程分为三个层次,过程层、方法层、工具层。在最基础的过程层,最重要的就是一组被称为关键过程区域(KPAs)的框架(KPA的概念在讨论CMM的书中有详细的概念说明)。关键过程区域构成了软件项目的管理控制的基础,并且确立了上下文各区域的关系,其中规定了技术方法的采用、工程产品的,模型、文档、数据、报告、表格等,等的产生、里程碑的建立、质量的保证及变化的适当管理。方法层主要是过程在技术上的实现。它解决的问题是如何做。软件工程方法涵盖了一系列的任务:需求分析、设计、编程、测试、维护。同时他还包括了一组基本原则,控制了每一个的关键过程区域。工具层就很好理解了,他对过程层和方法层提供了自动和半自动的支持。这些辅助工具就称为CASE。事实上需求分析是跨越了软件工程的三个层次的。这一点是和其他的过程是一样的。
可以看到需求分析的位置,它是我们软件开发的第一步。是对用户需求的定义,对软件系统的描述。系统分析的任务:将用户的业务逻辑转化为程序逻辑,计算时间和成本。根据开发人员的理论知识和实际的经验,人们会采用各种满足实际情况的系统分析、开发方法、步骤以及文档等等。一般情况下,在系统分析书中应该有以下内容(视项目而定):
1、 系统需求说明 说明系统是一个什么样的系统,用市场上现有的系统来类比,用客户(或是我们自己)需要一个什么样的系统进行说明,力求完整。并对系统的发展可扩充性进行描述(现在没有哪个系统是一次OK的)。说明与现有的系统有什么相同什么不同,说明未来系统的发展方面以及可移值性等能预见的事情。
2、 系统资源说明 对系统所需要的软件、硬件资源进行说明。描述系统所需要的所有的TCO成本。包括人员、时间、设备、系统、一次性投入资金、持续性投入资金这样的所有资源。
3、 系统可行性分析 对系统的实施中的资源进行分析,说明投入的合理性和必然性,对其中的所有不可预见性的投入进行合理的量化说明,来说明系统的实施的可行性。
二、系统分析员与程序员
大家应该对这两个词很熟悉了,但是对词里包含的意义可能并不是特别清楚。首先必须说明的是,程序员和系统分析员不存在谁高级谁低级的分别,他们是两种职业,对职业技能的要求完全不同。所以厉害的程序员就是系统分析员的说法是不对的。当然,系统分析员的技能要求他必须要懂得如何写程序,但是他的重心在于如何把一个很大的项目切割成适合个人的小块,然后将这些小块组织起来。程序员的职责就是如何更好更快的实现这些小块。
三、系统分析的方法和工具
UML全称:Unified Modeling Language,统一建模语言,是面向对象的建模语言,主要用于软件系统的面向对象建模。
UML是以面向对象图的方式来描述任何类型的系统,具有很广泛的应用领域。特别是在建立软件系统模型中,它支持从系统需求、系统分析到系统设计的整个建模过程。由于UML建模是一门专门的科学,而我们这门课程的任务是数据库系统开发,所以对于UML我们将有限的注意力集中在认识UML各种图示上。
可以使用Rational Rose 2003来建立UML模型
1) 建立角色
2) 创建用例
3) 创建角色用例关系图
4) 创建时序图
5) 创建协作图
四、QQ文本图形留言器系统分析的实现举例
1)需求分析总体图:
2)各模块细分分析图:
显示模块需求分析图
查询模块需求分析图
添加数据模块需求分析图
安全设置模块分析图
系统设置模块分析图
3)基本功能模块流程图(举例)
在这样的分析基础上,再进行编程,我们就可以有规律可依,做到有条不紊了。
Java Excel是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。使用该API非Windows操作系统也可以通过纯Java应用来处理Excel数据表。因为是使用Java编写的,所以我们在Web应用中可以通过JSP、Servlet来调用API实现对Excel数据表的访问。
现在发布的稳定版本是V2.0,提供以下功能:
- 从Excel 95、97、2000等格式的文件中读取数据;
- 读取Excel公式(可以读取Excel 97以后的公式);
- 生成Excel数据表(格式为Excel 97);
- 支持字体、数字、日期的格式化;
- 支持单元格的阴影操作,以及颜色操作;
- 修改已经存在的数据表;
现在还不支持以下功能,但不久就会提供了:
- 不能够读取图表信息;
- 可以读,但是不能生成公式,任何类型公式最后的计算值都可以读出;
应用示例
1 从Excel文件读取数据表
Java Excel API既可以从本地文件系统的一个文件(.xls),也可以从输入流中读取Excel数据表。读取Excel数据表的第一步是创建Workbook(术语:工作薄),下面的代码片段举例说明了应该如何操作:(完整代码见ExcelReading.java)
import java.io.*;
import jxl.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//直接从本地文件创建Workbook
//从输入流创建Workbook
InputStream is = new FileInputStream(sourcefile);
jxl.Workbook rwb = Workbook.getWorkbook(is);
}
catch (Exception e)
{
e.printStackTrace();
}
|
一旦创建了Workbook,我们就可以通过它来访问Excel Sheet(术语:工作表)。参考下面的代码片段:
//获取第一张Sheet表
Sheet rs = rwb.getSheet(0);
|
我们既可能通过Sheet的名称来访问它,也可以通过下标来访问它。如果通过下标来访问的话,要注意的一点是下标从0开始,就像数组一样。
一旦得到了Sheet,我们就可以通过它来访问Excel Cell(术语:单元格)。参考下面的代码片段:
//获取第一行,第一列的值
Cell c00 = rs.getCell(0, 0);
String strc00 = c00.getContents();
//获取第一行,第二列的值
Cell c10 = rs.getCell(1, 0);
String strc10 = c10.getContents();
//获取第二行,第二列的值
Cell c11 = rs.getCell(1, 1);
String strc11 = c11.getContents();
System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());
|
如果仅仅是取得Cell的值,我们可以方便地通过getContents()方法,它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型,Cell(1, 0)是数字型,Cell(1,1)是日期型,通过getContents(),三种类型的返回值都是字符型。
如果有需要知道Cell内容的确切类型,API也提供了一系列的方法。参考下面的代码片段:
String strc00 = null;
double strc10 = 0.00;
Date strc11 = null;
Cell c00 = rs.getCell(0, 0);
Cell c10 = rs.getCell(1, 0);
Cell c11 = rs.getCell(1, 1);
if(c00.getType() == CellType.LABEL)
{
LabelCell labelc00 = (LabelCell)c00;
strc00 = labelc00.getString();
}
if(c10.getType() == CellType.NUMBER)
{
NmberCell numc10 = (NumberCell)c10;
strc10 = numc10.getValue();
}
if(c11.getType() == CellType.DATE)
{
DateCell datec11 = (DateCell)c11;
strc11 = datec11.getDate();
}
System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());
|
在得到Cell对象后,通过getType()方法可以获得该单元格的类型,然后与API提供的基本类型相匹配,强制转换成相应的类型,最后调用相应的取值方法getXXX(),就可以得到确定类型的值。API提供了以下基本类型,与Excel的数据格式相对应,如下图所示:
每种类型的具体意义,请参见Java Excel API Document。
当你完成对Excel电子表格数据的处理后,一定要使用close()方法来关闭先前创建的对象,以释放读取数据表的过程中所占用的内存空间,在读取大量数据时显得尤为重要。参考如下代码片段:
//操作完成时,关闭对象,释放占用的内存空间
rwb.close();
|
Java Excel API提供了许多访问Excel数据表的方法,在这里我只简要地介绍几个常用的方法,其它的方法请参考附录中的Java Excel API Document。
Workbook类提供的方法
1. int getNumberOfSheets()
获得工作薄(Workbook)中工作表(Sheet)的个数,示例:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
int sheets = rwb.getNumberOfSheets();
|
2. Sheet[] getSheets()
返回工作薄(Workbook)中工作表(Sheet)对象数组,示例:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
Sheet[] sheets = rwb.getSheets();
|
3. String getVersion()
返回正在使用的API的版本号,好像是没什么太大的作用。
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
String apiVersion = rwb.getVersion();
|
Sheet接口提供的方法
1) String getName()
获取Sheet的名称,示例:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
String sheetName = rs.getName();
|
2) int getColumns()
获取Sheet表中所包含的总列数,示例:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsColumns = rs.getColumns();
|
3) Cell[] getColumn(int column)
获取某一列的所有单元格,返回的是单元格对象数组,示例:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getColumn(0);
|
4) int getRows()
获取Sheet表中所包含的总行数,示例:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsRows = rs.getRows();
|
5) Cell[] getRow(int row)
获取某一行的所有单元格,返回的是单元格对象数组,示例子:
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getRow(0);
|
6) Cell getCell(int column, int row)
获取指定单元格的对象引用,需要注意的是它的两个参数,第一个是列数,第二个是行数,这与通常的行、列组合有些不同。
jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell cell = rs.getCell(0, 0);
|
2 生成新的Excel工作薄
下面的代码主要是向大家介绍如何生成简单的Excel工作表,在这里单元格的内容是不带任何修饰的(如:字体,颜色等等),所有的内容都作为字符串写入。(完整代码见ExcelWriting.java)
与读取Excel工作表相似,首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象,这里要注意的是,只能通过API提供的工厂方法来创建Workbook,而不能使用WritableWorkbook的构造函数,因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下:
import java.io.*;
import jxl.*;
import jxl.write.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//Method 1:创建可写入的Excel工作薄
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile));
//Method 2:将WritableWorkbook直接写入到输出流
/*
OutputStream os = new FileOutputStream(targetfile);
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
*/
}
catch (Exception e)
{
e.printStackTrace();
}
|
API提供了两种方式来处理可写入的输出流,一种是直接生成本地文件,如果文件名不带全路径的话,缺省的文件会定位在当前目录,如果文件名带有全路径的话,则生成的Excel文件则会定位在相应的目录;另外一种是将Excel对象直接写入到输出流,例如:用户通过浏览器来访问Web服务器,如果HTTP头设置正确的话,浏览器自动调用客户端的Excel应用程序,来显示动态生成的Excel电子表格。
接下来就是要创建工作表,创建工作表的方法与创建工作薄的方法几乎一样,同样是通过工厂模式方法获得相应的对象,该方法需要两个参数,一个是工作表的名称,另一个是工作表在工作薄中的位置,参考下面的代码片段:
//创建Excel工作表
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);
|
"这锅也支好了,材料也准备齐全了,可以开始下锅了!",现在要做的只是实例化API所提供的Excel基本数据类型,并将它们添加到工作表中就可以了,参考下面的代码片段:
//1.添加Label对象
jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
ws.addCell(labelC);
//添加带有字型Formatting的对象
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
ws.addCell(labelCF);
//添加带有字体颜色Formatting的对象
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED);
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC);
ws.addCell(labelCF);
//2.添加Number对象
jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
ws.addCell(labelN);
//添加带有formatting的Number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF);
//3.添加Boolean对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB);
//4.添加DateTime对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
ws.addCell(labelDT);
//添加带有formatting的DateFormat对象
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
ws.addCell(labelDTF);
|
这里有两点大家要引起大家的注意。第一点,在构造单元格时,单元格在工作表中的位置就已经确定了。一旦创建后,单元格的位置是不能够变更的,尽管单元格的内容是可以改变的。第二点,单元格的定位是按照下面这样的规律(column, row),而且下标都是从0开始,例如,A1被存储在(0, 0),B1被存储在(1, 0)。
最后,不要忘记关闭打开的Excel工作薄对象,以释放占用的内存,参见下面的代码片段:
//写入Exel工作表
wwb.write();
//关闭Excel工作薄对象
wwb.close();
|
这可能与读取Excel文件的操作有少少不同,在关闭Excel对象之前,你必须要先调用write()方法,因为先前的操作都是存储在缓存中的,所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象,那么只能得到一张空的工作薄了。
3 拷贝、更新Excel工作薄
接下来简要介绍一下如何更新一个已经存在的工作薄,主要是下面二步操作,第一步是构造只读的Excel工作薄,第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄,参考下面的代码片段:(完整代码见ExcelModifying.java)
//创建只读的Excel工作薄的对象
jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile));
//创建可写入的Excel工作薄对象
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile), rw);
//读取第一张工作表
jxl.write.WritableSheet ws = wwb.getSheet(0);
//获得第一个单元格对象
jxl.write.WritableCell wc = ws.getWritableCell(0, 0);
//判断单元格的类型, 做出相应的转化
if(wc.getType() == CellType.LABEL)
{
Label l = (Label)wc;
l.setString("The value has been modified.");
}
//写入Excel对象
wwb.write();
//关闭可写入的Excel对象
wwb.close();
//关闭只读的Excel对象
rw.close();
|
之所以使用这种方式构建Excel对象,完全是因为效率的原因,因为上面的示例才是API的主要应用。为了提高性能,在读取工作表时,与数据相关的一些输出信息,所有的格式信息,如:字体、颜色等等,是不被处理的,因为我们的目的是获得行数据的值,既使没有了修饰,也不会对行数据的值产生什么影响。唯一的不利之处就是,在内存中会同时保存两个同样的工作表,这样当工作表体积比较大时,会占用相当大的内存,但现在好像内存的大小并不是什么关键因素了。
一旦获得了可写入的工作表对象,我们就可以对单元格对象进行更新的操作了,在这里我们不必调用API提供的add()方法,因为单元格已经于工作表当中,所以我们只需要调用相应的setXXX()方法,就可以完成更新的操作了。
尽单元格原有的格式化修饰是不能去掉的,我们还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现。
新生成的工作表对象是可写入的,我们除了更新原有的单元格外,还可以添加新的单元格到工作表中,这与示例2的操作是完全一样的。
最后,不要忘记调用write()方法,将更新的内容写入到文件中,然后关闭工作薄对象,这里有两个工作薄对象要关闭,一个是只读的,另外一个是可写入的。
要往xls文件里面写入数据的时候需要注意的是第一要新建一个xls文件
OutputStream os=new FileOutputStream("c:\\excel2.xls");
再建完这个文件的时候再建立工作文件
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(os));
如果这个文件已经存在,那么我们可以在这个文件里面加入一个sheet为了和以前的数据进行分开;
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);
在createSheet方法里前面的参数是sheet名,后面是要操作的sheet号
接下来就可以往这个文件里面写入数据了
写入数据的时候注意的格式
(1)添加的字体样式
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
WritableFont()方法里参数说明:
这个方法算是一个容器,可以放进去好多属性
第一个: TIMES是字体大小,他写的是18
第二个: BOLD是判断是否为斜体,选择true时为斜体
第三个: ARIAL
第四个: UnderlineStyle.NO_UNDERLINE 下划线
第五个: jxl.format.Colour.RED 字体颜色是红色的
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell",wcfF);
ws.addCell(labelC);
在Label()方法里面有三个参数
第一个是代表列数,
第二是代表行数,
第三个代表要写入的内容
第四个是可选项,是输入这个label里面的样式
然后通过写sheet的方法addCell()把内容写进sheet里面。
(2)添加带有formatting的Number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
(3)添加Number对象
(3.1)显示number对象数据的格式
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF);
Number()方法参数说明:
前两上表示输入的位置
第三个表示输入的内容
(4)添加Boolean对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB);
(5)添加DateTime对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
ws.addCell(labelDT);
DateTime()方法的参数说明
前两个表示输入的位置
第三个表示输入的当前时间
(6)添加带有formatting的DateFormat对象
这个显示当前时间的所有信息,包括年月日小时分秒
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
ws.addCell(labelDTF);
(7)添加带有字体颜色Formatting的对象
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED);
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
import="jxl.format.*
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL,20,WritableFont.BOLD,false,UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.GREEN);
(8)设置单元格样式
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
wcfFC.setBackGround(jxl.format.Colour.RED);//设置单元格的颜色为红色
wcfFC = new jxl.write.Label(6,0,"i love china",wcfFC);
代码如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE> New Document </TITLE>
<META NAME="Generator" CONTENT="EditPlus">
<META NAME="Author" CONTENT="">
<META NAME="Keywords" CONTENT="">
<META NAME="Description" CONTENT="">
</HEAD>
<BODY>
<script language="javascript">
todayDate = new Date();
date = todayDate.getDate();
month= todayDate.getMonth() +1;
year= todayDate.getYear();
document.write(" 今天是")
document.write("<br>")
if(navigator.appName == "Netscape")
{
document.write(1900+year);
document.write("年");
document.write(month);
document.write("月");
document.write(date);
document.write("日");
document.write("<br>")
}
if(navigator.appVersion.indexOf("MSIE") != -1)
{
document.write(year);
document.write("年");
document.write(month);
document.write("月");
document.write(date);
document.write("日");
document.write("<br>")
}
if (todayDate.getDay() == 5) document.write(" 星期五")
if (todayDate.getDay() == 6) document.write(" 星期六")
if (todayDate.getDay() == 0) document.write(" 星期日")
if (todayDate.getDay() == 1) document.write(" 星期一")
if (todayDate.getDay() == 2) document.write(" 星期二")
if (todayDate.getDay() == 3) document.write(" 星期三")
if (todayDate.getDay() == 4) document.write(" 星期四")
</script>
</BODY>
</HTML>
源代码解释:
todayDate = new Date();
|
当定义一个新的对象时,通常使用“new”操作符。在这里,就是创建了日期对象。 |
date = todayDate.getDate();
|
getDate()是Date对象的一种方法,其功能是获得当前的日期。 |
month= todayDate.getMonth() + 1 ; ; |
getMonth()是Date对象的一种方法,其功能是获得当前的日期,由于月份是从0开始的,所以这里要“+1”。 |
year= todayDate.getYear() |
getYear()是Date对象的一种方法,其功能是获得当前的年份。 |
document.write("今天是") document.write("<br>") |
输出“今天是” |
if(navigator.appName == "Netscape") { document.write(1900+year); document.write("年"); document.write(month); document.write("月"); document.write(date); document.write("日"); document.write("<br> ") } |
如果浏览器是Netscape,输出今天是“year”+“年”+“month”+“月”+“date”+“日”,其中年要加1900。 |
if(navigator.appVersion.indexOf("MSIE") != -1) { document.write(year); document.write("年"); document.write(month); document.write("月"); document.write(date); document.write("日"); document.write("<br> ") } |
如果浏览器是IE,直接输出今天是“year”+“年”+“month”+“月”+“date”+“日”。 |
document.write("") ; |
在“日期”与“星期”之间输入一个空格。 |
if (todayDate.getDay() == 5) document.write("星期五"); if (todayDate.getDay() == 6) document.write("星期六"); if (todayDate.getDay() == 0) document.write("星期日"); if (todayDate.getDay() == 1) document.write("星期一"); if (todayDate.getDay() == 2) document.write("星期二"); if (todayDate.getDay() == 3) document.write("星期三"); if (todayDate.getDay() == 4) document.write("星期四") |
getDay()是Date对象的一种方法,其功能是获得当前是星期几。document.write输出今天是“星期几”。 |
|
一,其实随机色就是RGB分别取三个0-256的随机数,
然后把你想要改的对象的样式设置成这种颜色就行了。
var
rndColor
=
"
rgb(
"
+
Math.round(Math.random()
*
256
)
+
"
,
"
+
Math.round(Math.random()
*
256
)
+
"
,
"
+
Math.round(Math.random()
*
256
)
+
"
)
"
;
document.getElementById(
"
myData
"
).style.color
=
rndColor;
二,利用javascript中的setInterval()函数,定时给某一个控件(如:span)的更换字体颜色样式。
在javascript中,element.style.color属性允许使用rgb(x,y,z)函数,这样我们就可以生成三个随机数填充,以达到随机 变颜色的效果。我们可以用Math.random()来获得随机数,然后再利用Math.round()来得到一个小于当前值的最大整数。
RGB的值的随机范围是0-255,所以每个随机值是:Math.round(Math.random()*256)。
<span id="myData">Hello world</span>
<script language="javascript">
window.onload = function(){
setInterval( rndMyData, 1000 );
}
function rndMyData(){
var rndColor = "rgb(" + Math.round(Math.random()*256) + "," + Math.round(Math.random()*256) + "," + Math.round(Math.random()*256) + ")";
document.getElementById( "myData" ).style.color = rndColor;
}
</script>
很多架构师都是从好的开发人员逐步过渡而来的,但并非每个好的开发人员都希望成为架构师,而且他们并不是都适合做架构师。无论您是打算进行职业转型的开发人员,还是寻找能承担体系结构设计责任的合适人选的经理,都务必对此转型过程有个清楚的了解。本文将讨论从实现专家到架构师的过渡过程。
在寻找优秀的指挥的时候,您首先要找的是一名优秀的音乐演奏家。但并非每个音乐演奏家都能成为优秀的指挥。架构师的专业发展方面也与此类似。越来越多的 IT 组织开始认识到良好软件体系结构的重要性,架构师职业正迅速发展为 IT 内一个独立的门类。由于要从相当小的候选范围内招募架构师,因此这就给管理带来了一些新挑战。即使人力资源部门找到了候选者,针对经验进行的筛选也比其他门类更为严格。跨越这些障碍的最快方式是要认识到,大部分好的架构师同时也是好的开发人员,因此寻找架构师人才时可能首先应该从普通开发人员中找起。招聘人员在对候选者(内部或外部)进行详细审查时,应该考虑这个观点。不过,对此资源进行挑选可能比较麻烦,因为只有极少的优秀开发人员具有成为架构师的特征或愿望。
本文列出了开发人员成为架构师要进行的工作。我将从可能考虑进行此转型的开发人员和评估进行此转型的开发人员的经理这两个方面来探讨这一问题。我还将提供一系列在做出这些决策时要考虑的因素。
个人特征
软件开发团队和管理层之间的联系始终是 IT 中的一个关键所在。二者都倾向于以完全不同的方式考虑给定的问题。大部分相关技术都是讨论项目经理应如何跟踪和解释开发人员的进度和问题。但沟通不足的情况仍然非常普遍,而且这是项目失败的首要原因。好的架构师是解决这个问题的最有效办法。架构师的主要责任是提供开发人员和项目经理之间的共用沟通媒体。他们负责让业务规则及需求与工程实践及限制相适应,以确保成功。以下是成功架构师的一些主要特征。
愿意并有能力进行沟通:在开发人员中发现架构师的最有价值标准是有效的沟通。您需要技术娴熟、经验丰富的开发人员,这样的人员需要有就项目中的业务相关问题进行沟通的经历。架构师经常必须对理解方面的差距进行预计,然后才能有所贡献。他们必须愿意克服困难来确保技术和业务观点的融合。他们并不必对意见交换工作进行计划和协调;这仍然主要是项目经理的工作。他们的任务是确定表述系统设计时的最佳工具和构件,以促进有效的意见交换。他们必须能够判断当前方法显得不足而需要采用新方法的情况。写作技能也非常重要,还需要具有制作草图的技能或使用制图软件的能力。
具有处理谈判细节方面的经验:架构师经常需要负责讨论系统开发的技术折衷方案。优先级的冲突可能会带来实践限制、风险规避或可能导致在各个不同业务组之间需求不同。优秀的架构师能够有效地评估技术可能性,并能在不损失项目的主要价值的前提下制订开发计划来处理各种利害关系和限制。这与前面讨论的沟通技能紧密相关,但同时也要体现架构师的技术能力。好的架构师候选者应该是经常帮助对有争议的讨论进行引导的人,能够使讨论得出新的想法,而不会使其在一个位置停滞不前。
自觉主动;积极解决设计问题:架构师的日常工作目标经常并不明确。很多开发人员直接参考功能规范来列出任务清单。架构师通常则是向这些开发人员提供所需结构的人员,以便尽可能提高工作效率。好的候选者不仅进行沟通方面的工作,而且也会预计各种设计问题并加以解决——通常在没有任何具体指示的情况下自觉进行。无论所分配的职责如何,积极参与项目的开发人员都有机会从一起工作的人员中脱颖而出。
抽象思维和分析:架构师必须能够理解表述模糊的概念并将其变成相关各方能够理解的项目构件。他们必须能够理解抽象概念,并以具体的语言对其进行沟通。开发人员中好的候选者经常要求或自己主动解释开发生命周期中容易混淆的问题。他们能迅速评估各种想法并将其纳入后续工作的操作建议中。
开发人员经常具有很强的数学能力,而好的架构师则倾向于表现出更强的口头表达能力。管理人员经常说开发人员具有“工程意识”,而这是一个用于评估架构师的非常有意义的方面。架构师应该具有很强的解决技术问题的能力,但还必须能够准确获知更为全面的人员如何与技术交互的信息。这要求具有某种形式的抽象思维(而不再是代码的细节),这种思维能力可能较难形成。
有些人认为,某种级别的正式教育是成为优秀开发人员的必备条件之一,我并不同意这种精英论。我遇到了很多高中就辍学的优秀开发人员。不过,对于体系结构设计工作,我的个人经验以及我对所需能力的认识都让我相信,好的架构师通常至少获得了一个有挑战性的学士学位。
跟踪生命周期
好的架构师通常有在具备定义良好的软件开发生命周期(Software Development Life Cycle,SDLC)的组织工作的经验。架构师必须理解在其所属专业内最重要的操作过程。这并不意味着需要有其他前提,例如,并不需要高能力成熟度模型(Capability Maturity Model,CMM)级别的工作经验。好的架构师可能来自使用 SDLC 的多个小型迭代的极限编程(Extreme Programming,XP)方法的组织。务必注意各种传统软件开发操作,如 Michael A. Jackson 的方法:Jackson 结构编程(Jackson Structured Programming,JSP)和 Jackson 系统开发(Jackson System Development,JSD)。Jackson 的研究对架构师职业发展的意义就像 Donald Knuth 的研究对程序员一样重要。架构师可以偏爱任何经典的、经过时间考验的软件系统开发方法。
SDLC 也可以成为评估架构师合适人选的有用机制。每个 SDLC 阶段都具有能提供相关线索的特征。SDLC 包含很多小的变体,但在此部分,我将使用几乎所有方法的公共基础部分。下面的列表详细说明了 SDLC 的各个阶段,并列出了好的架构师候选者在每个阶段表现出来的特征。
- 分析:在分析期间,好的架构师会考虑非技术影响,以便了解需求和将在其中进行开发的环境。架构师可为风险评估任务带来广泛的软件经验供参考。寻找具有丰富经验的开发人员,以帮助业务部门理解技术人员正确解释需求所需的信息。寻找在开发的早期阶段能够预计可能遇到的问题的开发人员。
- 设计:在高级设计期间,好的架构师会收集问题空间的各个抽象元素,并就其进行沟通,以便开发团队草拟将要开发的系统的相关图表。架构师负责将需求谨慎地映射到所得到的系统体系结构的功能。在详细设计期间,他们所扮演的角色并不是核心角色,但为了根据整个系统的规则对特定模块的元素进行审查,仍然需要他们。寻找善于让团队能够预计设计决策对最终系统的影响的开发人员。寻找善于确定一些最佳构件来促进与技术和非技术受众沟通设计问题的开发人员。
- 实现:在实现期间,架构师对项目进行引导,以确保其符合系统体系结构。他们在一线评估技术更改请求,并确定如何对设计进行调整,以最好地处理此类请求。架构师还要密切了解开发人员的进度,特别要跟踪系统中模块间的集成点的状态。寻找经常对讨论进行引导来连接多个子系统的开发人员。寻找项目经理可以依赖其快速地进行与更改和出现的问题相关的风险评估的开发人员。
- 测试:架构师对系统集成和用户接受度测试进行指导,并负责评估进度的正确沟通的持续测试结果。寻找理解错误模式且善于将测试复查结果转换为行动计划的开发人员。
- 维护:在维护期间,架构师将发起关于系统集成的讨论。无论处理 IT 基础设施问题,还是确保部门之间的技术合作,架构师都必须完全理解应用程序,必须快速学习姊妹应用程序的体系结构,而且必须就集成点和风险进行有效沟通。寻找具有系统集成经验且表现出快速掌握全貌的能力的开发人员。系统集成是一项独特的任务。
架构师培养建议
有些组织能比其他组织更有效地进行架构师培养。如果充分考虑到招聘此类新专业人才的困难,努力促成能鼓励开发人员发展为架构师的环境是非常明智的策略。但务必避免对不愿意或不适合走这条路的开发人员进行处罚。组织应该为开发人员制订多条发展路线,包括那些愿意继续担任开发人员的人。对架构师而言,资深开发人员不可或缺。他们可以实现系统中最关键的模块。通过对其他开发人员进行代码检查和测试支持,他们可帮助确保总体软件质量,而如果质量不能保证,即使最好的体系结构也毫无用处。
组织应制订个人评估程序,以鼓励开发人员考虑其职业目标,其中要包含体系结构设计的选项。应该鼓励经理在其下属中寻找体系结构设计人才。应该实现指导计划,让架构师与希望成为架构师的开发人员协作工作。应该鼓励开发人员通过参加各种协会、撰写文章和参加会议,从而参与到专业领域中来。通过这样参与进来,可帮助开发人员从新的角度理解系统,并帮助他们更好地就其认识进行沟通。这样还能培养可提高效率的重要创新想法。
结束语
开发人员一旦迈出了通向体系结构设计专业方向的第一步,就可以利用很多资源来获得帮助,其中包括很多来自 IBM 的资源。有时候,此过程的最困难的部分就是第一步,而本文提供了一些线索和提示,经理和开发人员可以利用其来评估应该鼓励哪些人努力成为架构师。
由于Jboss默认的数据库为HypersonicDB,很多人,包括我,对它都不是十分了解,只知道它是java写的,使用起来不是很方便,本人最近在学EJB,所以研究了一下。
我在网上找了很多资料包括什么《JBOSS 数据源设置大全》啊,一类的文章,都不能成功将默认的数据库改为Oricle10g(别的数据库能否成功我没有试过),也就是我用的数据库,所以自己查阅了国外的一些BBS,总结了一下经验,贡献给chinajavalab。
1.删除Jboss\server\default\deploy下的hsql-ds.xml,从Jboss\docs\examples\jca中复制一个oracle-ds.xml过来,修改里面的属性;connection-url,driver-class,user-name,pssword,其中jndi-name最好设置成DefaultDS,这样可以方便后面几个xml文件的修改
2.删除Jboss\server\default\deploy\jms下的一切有关HSQL的文件,共有两个,
从Jboss\docs\examples\jms中复制一个oracle-jdbc2-service.xml过来,将<depends>中的name改为DefaultDS,或者你自己设置的jndi-name
3.进入Jboss\server\default\conf,如果你将jndi-name设置成DefaultDS的话,就不用设置login-config.xml了,否则要设置,将里面的DefaultDS更换成你的jndi-name,后面的xml文件也要注意jndi-name,后面就不再提示了
4.修改jboss-service.xml,将<!--attribute name="Pad">true</attribute--> 的注释去掉
5.修改standardjbosscmp-jdbc.xml,将datasource-mapping设置成Oracle9i,这里不管你用的是9i还是10g都要设置成9i,jboss4中目前还没有10g的相关内容,我用的是10g,没有问题的,关键是你的dirver包一定要是class12.jar,并将它放进Jboss\server\default\lib中
6.修改standardjaws.xml,将type-mapping设置为Oracle9i,增加
<type-mapping-definition>
<name>Oracle9i</name>
<mapping>
.
.
中间的内容到standardjbosscmp-jdbc.xml中的Oracle9i里找,复制过来
,只要其中<mapping>的内容,其他不要
.
.
</mapping>
</type-mapping-definition>
好了,重新启动你的jboss,部署一个EJB试一试,如果有异常,表示你还没有配置正确
Sun发布的JDK/JRE有两种版本,一种是.rpm.bin格式的,另一种则是.bin格式的,前者我没有试,但是我想应该是适合于rpm的,可能会安装到/usr里面去,而且自动化程度可以高一些。后者则完全是绿色软件,只是做一个解压的动作。下面我就来讲后者的安装全攻略。
1、首先我们要到Sun的网站上去下载JDK/JRE(点这里进入),最新的正式版本已经是6.0(也就是1.6),当然老的版本Sun也仍然提供下载,点上面的“Previous Releases”就可以找到了。下载.bin文件,下面假设这个文件的名字是jdk1.x.bin。
2、把安装文件解压出来。假设我们下载的文件保存在/opt下。
打开终端,运行以下命令:
引用:
cd /opt
chmod a+x jdk1.x.bin
./jdk1.x.bin
你会看到版权提示,不管它,按空格键翻页。然后会提示你是否同意版权协议[yes/no],此时输入yes,回车,安装程序就会把文件解压到当前目录下的jdk1.x这样的目录下面(JRE应该大体相同)。
3、让JDK/JRE支持中文。由于默认安装的JDK/JRE不带中文字体,不支持中文显示,所以我们要自行修改某些字体相关的配置,让它们支持中文。
设定字体有两种方法:
第一种方法是把你的中文字体目录做个连接到jdk/jre/lib/fonts里面,这种方法很简便。看命令:
引用:
cd /opt/jdk1.x/jre/lib/fonts
ln -s /usr/share/fonts/truetype/windows fallback (假设我们的中文字体放在/usr/share/fonts/truetype/windows目录里,这个目录里我放的是从Windows那边copy过来的字体)
为什么要做fallback这个连接,我也是从网上看到的,我想应该是Sun做的设定吧,设定JDK/JRE在运行时会到这个目录里去找那些非西方字体。这种方法对JDK/JRE 1.4/1.5/1.6都适用,但是由于没有在fontconfig.properties文件里面详细设定字体,所以这种方法显示出来的字体很难看。
第二种方法是把配置好的fontconfig.properties做个连接到jdk1.x/jre/lib里面。看命令:
引用:
cd /opt/jdk1.x/jre/lib
ln -s /etc/java/fontconfig.properties (假设我们的fontconfig.properties放在/etc/java目录里)
这种方法对JDK/JRE 1.4/1.5/1.6都适用,只不过1.4版本的文件名是font.properties而不是fontconfig.properties。当然你也可以直接把fontconfig.properties文件复制到/opt/jdk1.x/jre/lib里面,这样就不用做连接,但是如果你同时安装几个不同版本的JDK,还是做连接比较方便。在下面我会把我配置好的font.properties和fontconfig.properties的内容贴出来,大家稍作修改就可以用了。
3、让Web浏览器支持Java插件(也就是支持Java Applets)。
做一个连接就可以了。看命令:
引用:
cd /usr/lib/firefox/plugins (Ubuntu的firefox插件目录在这里,其它版本以此参考)
ln -s /opt/jdk1.x/jre/plugin/i386/ns7/libjavaplugin_oji.so
然后运行firefox,在地址栏里打入about:plugins,回车,可以看到firefox的插件列表里已经有了Java插件。
如果你用的是其它的浏览器,方法大体也差不多,就是进入浏览器的plugins目录,做一个连接。不过要注意的是,如果你用的浏览器是 mozilla 1.4/netscape 7.0以上的版本,用上面的命令没问题,但是如果你用的浏览器是mozilla 1.2/netscape 7.0以下的版本,Sun有提供另一个插件。这样的话,命令就要改一下了:
引用:
cd /usr/lib/mozilla/plugins
ln -s /opt/jdk1.x/jre/plugin/i386/ns7-gcc29/libjavaplugin_oji.so
4、让Web浏览器支持Java Web Start程序。(可选安装)
如果你不知道Java Web Start程序是什么,看这里:
http://www.stcore.com/java/2006/06/18/1150640682d28890.html
所谓安装,其实就是添加一个mimetype(类似于文件关联),让浏览器知道,遇到Java Web Start程序该用什么程序来处理。
对应mozilla/netscape浏览器的方法:
点击菜单:Edit->Preferences->Navigator->Helper Applications
然后新建一个mimetype:
mimetype是:application/x-java-jnlp-file
extention是:jnlp
关联程序是:/opt/jdk1.x/jre/bin/javaws
对应firefox浏览器的方法:
由于firefox没有直接添加mimetype的方法,所以要改的话需要安装一个Mime Type Editor扩展,看这里:
http://forums.mozine.org/index.php?showtopic=5521
5、为firefox浏览器加入Java Console菜单项。(可选安装)
mozilla/netscape装好java插件之后就有Java Console菜单项,可以方便地调用Java控制台,这对程序员调试程序有用。但是firefox还没有这个菜单项,添加的方法就是解压一个zip文件到firefox/extension目录。现在我们就来添加,看命令:
引用:
cd /usr/lib/firefox/extensions
unzip /opt/jdk1.x/jre/lib/deploy/ffjcext.zip
重启firefox,就可以看到工具菜单里多了一个Java Console菜单项。
JDK/JRE 1.5及以下版本并没有提供这个firefox扩展,如果要安装的话到这里安装:
https://addons.mozilla.org/firefox/141/
6、把Java工具加入系统菜单。(可选安装)
Ubuntu自带的JDK/JRE会在系统菜单中添加两个Java工具,就是Java Plugin Control Panel和Java Policy Tool。下面我们也为自己安装的JDK/JRE添加两个菜单项。
在Ubuntu的主菜单上点击右键->编辑菜单->首选项->新建项目:
第一项:
图标是:/opt/jdk1.x/jre/plugin/desktop/sun_java.png
名称是:Java Plugin Control Panel (这个随便写)
命令是:/opt/jdk1.x/jre/bin/ControlPanel
第二项:
图标是:/opt/jdk1.x/jre/plugin/desktop/sun_java.png
名称是:Java Policy Tool (这个随便写)
命令是:/opt/jdk1.x/jre/bin/policytool
7、添加JAVA_HOME/JRE_HOME环境变量。(Java开发人员必备)
这里以最常用的bash命令解释器为例,编辑用户目录下的.bashrc或.profile文件。如果你想在所有用户的shell下都生效,就编辑/etc/profile文件。同样都是加入以下内容:
引用:
export JAVA_HOME=/opt/jdk1.x
export JRE_HOME=/opt/jdk1.x/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
至此,Sun JDK/JRE在Linux上的安装过程结束。
DAO 类是线程安全的,它的所有操作都通过调用 DbExecutor 对象来执行。每次操作,DAO 都会从 DbExecutorFactory 中获得一个 DbExecutor 对象。DAO 要做的就是保证做完操作之后都要执行 DbExecutor 对象的 close 方法。
DbExecutor 是一个接口,它的大部分方法和 DAO 差不多。它包含一个数据库连接,当连接关闭时,DbExecutor 对象的生命周期也就结束了。OraDbExecutor 是 DbExecutor 的一个实现。
OraDbExecutor.java - 构造函数
- public OraDbExecutor(String dsName, Connection conn) {
- this.dsName = dsName;
- this.connection = conn;
- }
当创建 DbExecutor 实例时,DbExecutorFactory 先从 ConnectionFactoryBuilder 获得一个 ConnectionFactory 对象,然后从ConnectionFactory 获取一个数据库连接,用来创建 DbExecutor。当然,连接每个数据库的 ConnectionFactory 对象只有一个。
ConnectionFactoryBuilder.java - build()
- publicsynchronized ConnectionFactory build(String dsName) throws ConfigErrorException {
- if (factoryCache.get(dsName) == null) {
- DataSourceCollection sources = Configurator.getDataSources();
- DataSource ds = sources.getDataSource(dsName);
- if (ds == null) {
- thrownew ConfigErrorException("没有找到数据源 " + dsName);
- }
- factoryCache.put(dsName, buildFactory(ds));
- }
- return (ConnectionFactory) factoryCache.get(dsName);
- }
创建了一个 DAO 类用来做所有的事情,包括查询。查询方法如下:
java 代码 - public List query(Class clazz, String sql, List params) throws DAOException;
第一个参数是用来接受封装的类。 有时候仅查询一个字段,根本不用封装:
java 代码 - public List query(String sql, List params) throws DAOException;
如果封装,就要考虑查询结果字段到对象属性的映射关系。根据公司的数据库设计规范, 单词之间用下划线连起来。所以简单的替换一下就可以了。比如 USER_NAME 字段就映 射到 userName 属性。
如果查询结果中的字段有多,那这个字段的值就只好丢弃;如果类的属性有多,也不会给 它赋值。
分页查询。有时候会用到分页查询,所以添加了一个方法:
java 代码 - public Page queryPage(Class clazz, String sql, List params, int pageIndex, int pageSize)
- throws DAOException;
Page 对象除了包含查询结果外还有 totalNum 属性,表示查询总结果数。
表格封装的查询。有时候查询语句是动态生成的,没法确定查询结果的字段个数,用类来 封装显然不合适。于是定义了一个通用的表格结构用来封装,并添加了 queryTable() 方法: java 代码 - public DataTable queryTable(String sql) throws DAOException;
- public DataTable queryTable(String sql, List params, int pageIndex, int pageSize)
- throws DAOException;
DataTable 对象包含的其实就是一堆 HashMap。不过它同 Page 一样有一个查询总结果数, 而且添加了一些方法方便提取数据。这种查询的使用方式如下:
java 代码 - DAO dao = DAO.getDAO(SOURCE_NAME);
- DataTable table = dao.queryTable("select * from tt_test");
- assertEquals(6, table.getColumns());
- Map row = table.query("name", "张三丰");
- assertNotNull(row);
静态字段作为映射关系配置。有些字典表或配置表,一开始就被全部读入并缓存起来。为 了少写代码,DAO 提供将静态字段 TN 作为表名来查询的方式。如果类中存在 String 类型的 静态字段 TN,则可以使用这个方法:
java 代码 - public List query(Class clazz) throws DAOException;
|
没有哪个 DAO 能够包揽所有的数据库管理。每种 DAO 都有各自的定位。我们公司的项目有这样一些特点:
- 所用数据库都是 Oracle;
- 使用一些已有的数据库表;
- 查询语句要经过优化,DBA 要对其字斟句酌;
- 同时连接多个数据库。
我们的项目大都会用到一些其他系统现有的表。有的表包含四五十个字段,而对于某些业务逻辑来讲只需要查询一两个字段的值。DBA 强烈反对“select * ”,对关联查询、嵌套查询的性能要求也很严格。所以像 Hibernate 这样自动生成 SQL 语句的 DAO 自然不敢用。我们需要自己设计一个 DAO。
本着够用就行的原则,我们设计的 DAO (下面简称 DAO)先定下一个很低的目标:实现数据库连接管理,SQL 语句由使用者提供,将查询结果进行简单的封装。
数据库连接管理的具体设计是:
1、使用 XML 配置文件配置数据库连接;
2、支持 JDBC 和 JNDI(主要针对 WebLogic 的连接池) 两种方式创建数据库连接;
3、对同时连接多个数据库进行管理;
4、使用者不需关心数据库连接的创建和关闭。
对 XML 配置文件的设计:
DAO 配置文件用来配置数据库连接。鉴于当前的目标,DAO 没有映射关系配置。
配置文件将数据库抽象为“数据源”(DataSource),DAO 管理的是数据源。一个项目中可以存在多个数据源。数据源包含一个或多个连接配置,但运行时只会启用其中的一个,这样是为了方便修改配置,像 Rails 的 数据库配置文件中同时配置了开发环境、测试环境和产品环境三中数据库连接,一样的道理。
连接配置有 JNDI 和 JDBC 两种类型,分别需要不同的参数。JNDI 配置需要 JNDI 服务器、InitialContextFactory 类和 JNDI 名称三个参数,而JDBC 配置需要 Driver、url、用户名和密码四个参数。密码暂时不考虑加密,采用明文的方式。下面是一个配置文件的例子:
xml 代码
- <?xml version="1.0" encoding="GB2312" ?>
- <!DOCTYPE dao-config PUBLIC
- "-//Chinacreator, Ltd.//Data Access Object Library 1.2//CN" "dao-config.dtd">
-
- <dao-config>
-
- <datasource name="local_mysql" connection="default">
- <description>数据源1</description>
- <connection name="default" type="jdbc">
- <property name="driver" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost/test"/>
- <property name="username" value="user"/>
- <property name="password" value="user"/>
- </connection>
- </datasource>
-
- <datasource name="demo" connection="jdbc_connection">
- <description>数据源2</description>
-
- <connection name="jndi_connection" type="jndi">
- <description>JNDI 方式</description>
- <property name="driver" value="weblogic.jndi.WLInitialContextFactory"/>
- <property name="server" value="t3://127.0.0.1:7010"/>
- <property name="jndiname" value="jndi/name"/>
- </connection>
-
- <connection name="jdbc_connection" type="jdbc">
- <description>JDBC 方式</description>
- <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
- <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:SidName"/>
- <property name="username" value="username"/>
- <property name="password" value="password"/>
- </connection>
-
- <connection name="pooled_jdbc" type="jdbc">
- <description>带连接池的 JDBC 方式</description>
- <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
- <property name="url" value="jdbc:oracle:thin:@172.16.168.85:1521:ora9i01"/>
- <property name="username" value="username"/>
- <property name="password" value="password"/>
- <property name="usepool" value="true"/>
- <property name="poolsize" value="2"/>
- </connection>
- </datasource>
- </dao-config>
那么使用者如何使用 DAO 执行 SQL 呢?下面是一个最简单的例子:
java 代码
- DAO dao = DAO.getDAO("demo");
- List list = dao.query("select areaname from tb_pub_area_code");
- assertEquals("长沙", list.get(0));
在这个例子中,DAO 根据配置文件找到数据源“demo”(17行),再根据数据源的 connection 属性找到名为
"jdbc_connection" 的连接配置(27行),然后连接到
"jdbc:oracle:thin:@127.0.0.1:1521:SidName" (30行)进行查询。
下面是当前网页应用程序应该出现的地方:
基于表单的交互
表单是很慢的,非常慢。尝试编辑位于
del.icio.us
上面的一个书签?点击编辑链接打开一个编辑书签的表单页面,然后编辑你的内容并点击提交按钮等待整个提交过程结束,最后返回上一页并向下滚动到你刚才编辑的书签那里查看内容是否已经正确更改。那
AJAX
呢?点击编辑链接马上开始更改标签内容,点击提交按钮开始异步传输标签编辑的内容并立即看到更改后的内容而无需重载整个页面。
深层树状导航
总而言之,带有深层树状导航的应用程序通常是一个噩梦。在大多数情况中简单平直的拓扑结构以及搜索
/
标记可以很好的工作。但是如果一个应用程序真正使用深层树状导航,使用
JavaScript
来管理拓扑
ui(user interface
用户接口
)
,则使用
Ajax
懒加载深层数据可以降低服务器的负载。举例来说,为了阅读一个只有一行的结果来加载整个一个新页面是非常耗时的。
实时用户对用户通讯
在一个允许用户创建实时讨论的信息公告系统中,
迫使用户一次又一次的更新完页面看到答复是非常愚蠢的。回复应该是实时的,用户不应被迫总是去痴迷于刷新操作。即使是
gmail
这个已经对以前像
hotmail/yahoo mail
的收件箱刷新,刷新收件箱标记的操作有所改进,也并没有充分的使用
Ajax
的功能来提示有新邮件到达。
投票、是否选择、等级评价
如果
Ajax
提交过程没有一个协调的
UI
提示是非常糟糕的,通过使用
Ajax
来提交一个调查或是否选择可以减少提交过程等待的痛苦。通过减少点击的等待时间,
Ajax
应用程序变得越来越有交互性
-
如果要用
40
秒来提交一个投票,除非非常在意的话大多数人会选择放弃。如果只花
1
秒呢,非常大比例的人会乐于参加投票的。(我在
Netflix versus
有
2008
张电影投票在
IMDb.com
有
210
张电影投票)
过滤和复杂数据操作
应用一个过滤、按日期排序、按日期和姓名排序、打开或关闭过滤器等等。任何一种高交换型操作应该交给
JavaScript
来处理而不是通过向服务器来提交一系列的请求。在查找或者操作大量数据的时候带来的视图上的改变最多不会超过
30
秒,
Ajax
真的使这些操作加速了。
普通录入时的提示/
自动补齐
一些软件
/JavaScript
是擅长于帮助用户完成键入相同的文字或可以预测的文字的工作的。在
del.icio.us
和
Gmail
中该功能是非常有益的,可以用来快速增加标记
/email
等。
对于一个频繁使用的应用程序诸如网页邮件客户端或博客阅读器来说,用户有充足的时间来学习如何使用新的UI概念但是他们却无法接受一个非常缓慢的反应速度。这种应用为Ajax变的更加普及起到了一个完美的杠杆作用。随着用户使用频率的增加,更多的Ajax部件应该加强用户的使用体验。
但是对于网页应用程序来说,把每件事甚至任何事都用JavaScript来实现也是没有意义的。Ajax只是针对一些特定的环境才能带来显著的帮助。在Ajax出现之前网页应用程序已经可以工作的很好了并且目前在网页开发中Ajax还存在着许多的缺陷和缺点。就算不从服务器端取得一个异步的信息数据流一个平直的html网页日志也可以工作的很好。对于文档或文档之间的跳转来说,老旧的纯HTML仍然是最好的选择。简单或很少使用的应用程序就算不用JavaScript同样可以很好的工作。
下面是一些不应该用到Ajax
的地方:
简单的表单
就算表单是
Ajax
技术的最大受益人,一个简单内容的表单,或提交订货单,或一次性的很少用到的表单都不应该使用以
Ajax
驱动的表单提交机制。总的来说,如果一个表单不是很长用,或已经工作的很好,那么就算使用
Ajax
也没有什么帮助。
搜索
实时搜索带来的痛苦要远大于他带来的帮助。这就是为什么
Google Suggest
还处于
beta
测试而并没有放在主页上的原因。在
Start.com Live.com
上搜索的时候你是不能使用返回按钮来查看上一次搜索或返回上一页的。或许还没有人来完成这项工作,但是完成这个工作应该是很困难的至少是不太明知的或者会因此带来更多的麻烦。(译注:现在已经有很多开源的框架可以实现历史记录功能)
基本导航
总的来说,使用
Ajax
为一个基础的网站
/
程序做导航是一个可怕的念头。谁会把用来使自己的程序变的更好的时间花在编写代码模仿浏览器的行为上面?在基础页面中导航的操作中
JavaScript
是没有用的。
替换大量的信息
Ajax
可以不用整页刷新来动态更新页面中改变的一小部分。但是如果一页上的大部分内容都需要更新,那为什么不从服务器那里获得一个新页面呢?
显示操作
虽然看上去
Ajax
是一个纯
UI
技术,其实不是这样的。他实际上是一个数据同步、操作、传输的技术。要想得到一个稳定的干净的网页程序,不使用
Ajax/JavaScript来直接完成用户接口是明智的。JavaScript可以分散分布并简单的操作XHTML/HTML DOM,根据CSS规则来决定如何让UI显示数据。查看
来查看如何使用
CSS
来替代
JavaScript
来控制数据的显示。
无用的网页小部件
滑块选择控件、拖拽控件、弹性控件(此处原文为
bouncies
,不知指为何物?)、鼠标样式、天气预报控件,这些小部件应该可以被更直接的控件代替或者为了整洁干脆整个去掉。为了选择一种颜色,也许滑块选择控件可以选择一个正确的阴影颜色,但是在一个商店中选择一个价格,使用滑块选择控件选到分这个单位对于用户来说有点过分。
建立表:
CREATE TABLE [TestTable] (
[ID] [int] IDENTITY (1, 1) NOT NULL ,
[FirstName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
[LastName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
[Country] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
[Note] [nvarchar] (2000) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
插入数据:(2万条,用更多的数据测试会明显一些)
SET IDENTITY_INSERT TestTable ON
declare @i int
set @i=1
while @i<=20000
begin
insert into TestTable([id], FirstName, LastName, Country,Note) values(@i, 'FirstName_XXX','LastName_XXX','Country_XXX','Note_XXX')
set @i=@i+1
end
SET IDENTITY_INSERT TestTable OFF
-------------------------------------
分页方案一:(利用Not In和SELECT TOP分页)
语句形式:
SELECT TOP 10 *
FROM TestTable
WHERE (ID NOT IN
(SELECT TOP 20 id
FROM TestTable
ORDER BY id))
ORDER BY ID
SELECT TOP 页大小 *
FROM TestTable
WHERE (ID NOT IN
(SELECT TOP 页大小*页数 id
FROM 表
ORDER BY id))
ORDER BY ID
-------------------------------------
分页方案二:(利用ID大于多少和SELECT TOP分页)
语句形式:
SELECT TOP 10 *
FROM TestTable
WHERE (ID >
(SELECT MAX(id)
FROM (SELECT TOP 20 id
FROM TestTable
ORDER BY id) AS T))
ORDER BY ID
SELECT TOP 页大小 *
FROM TestTable
WHERE (ID >
(SELECT MAX(id)
FROM (SELECT TOP 页大小*页数 id
FROM 表
ORDER BY id) AS T))
ORDER BY ID
-------------------------------------
分页方案三:(利用SQL的游标存储过程分页)
create procedure XiaoZhengGe
@sqlstr nvarchar(4000), --查询字符串
@currentpage int, --第N页
@pagesize int --每页行数
as
set nocount on
declare @P1 int, --P1是游标的id
@rowcount int
exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@rowcount=@rowcount output
select ceiling(1.0*@rowcount/@pagesize) as 总页数--,@rowcount as 总行数,@currentpage as 当前页
set @currentpage=(@currentpage-1)*@pagesize+1
exec sp_cursorfetch @P1,16,@currentpage,@pagesize
exec sp_cursorclose @P1
set nocount off
其它的方案:如果没有主键,可以用临时表,也可以用方案三做,但是效率会低。
建议优化的时候,加上主键和索引,查询效率会提高。
通过SQL 查询分析器,显示比较:我的结论是:
分页方案二:(利用ID大于多少和SELECT TOP分页)效率最高,需要拼接SQL语句
分页方案一:(利用Not In和SELECT TOP分页) 效率次之,需要拼接SQL语句
分页方案三:(利用SQL的游标存储过程分页) 效率最差,但是最为通用
在实际情况中,要具体分析。
这些天开发一个项目,服务器是tomcat,操作系统是xp,采用的是MVC架构,模式是采用Facade模式,总是出现乱码,自己也解决了好多 天,同事也帮忙解决,也参考了网上众多网友的文章和意见,总算是搞定。但是好记性不如烂笔杆,所以特意记下,以防止自己遗忘,同时也给那些遇到同样问题的 人提供一个好的参考途径:
(一) JSP页面上是中文,但是看的是后是乱码:
解决的办法就是在JSP页面的编码的地方<%@ page language="java" contentType="text/html;charset=GBK" %>,因为Jsp转成Java文件时的编码问题,默认的话有的服务器是ISO-8859-1,如果一个JSP中直接输入了中文,Jsp把它当作 ISO8859-1来处理是肯定有问题的,这一点,我们可以通过查看Jasper所生成的Java中间文件来确认
(二) 当用Request对象获取客户提交的汉字代码的时候,会出现乱码:
解决的办法是:要配置一个filter,也就是一个Servelet的过滤器,代码如下:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
/**
* Example filter that sets the character encoding to be used in parsing the
* incoming request
*/
public class SetCharacterEncodingFilter implements Filter {
/**
* Take this filter out of service.
*/
public void destroy() {
}
/**
* Select and set (if specified) the character encoding to be used to
* interpret request parameters for this request.
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)throws IOException, ServletException {
request.setCharacterEncoding("GBK");
// 传递控制到下一个过滤器
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
配置web.xml
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>SetCharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果你的还是出现这种情况的话你就往下看看是不是你出现了第四中情况,你的Form提交的数据是不是用get提交的,一般来说用post提交的话是没有问题的,如果是的话,你就看看第四中解决的办法。
还有就是对含有汉字字符的信息进行处理,处理的代码是:
package dbJavaBean;
public class CodingConvert
{
public CodingConvert()
{
//
}
public String toGb(String uniStr){
String gbStr = "";
if(uniStr == null){
uniStr = "";
}
try{
byte[] tempByte = uniStr.getBytes("ISO8859_1");
gbStr = new String(tempByte,"GB2312");
}
catch(Exception ex){
}
return gbStr;
}
public String toUni(String gbStr){
String uniStr = "";
if(gbStr == null){
gbStr = "";
}
try{
byte[] tempByte = gbStr.getBytes("GB2312");
uniStr = new String(tempByte,"ISO8859_1");
}catch(Exception ex){
}
return uniStr;
}
}
你也可以在直接的转换,首先你将获取的字符串用ISO-8859-1进行编码,然后将这个编码存放到一个字节数组中,然后将这个数组转化成字符串对象就可以了,例如:
String str=request.getParameter(“girl”);
Byte B[]=str.getBytes(“ISO-8859-1”);
Str=new String(B);
通过上述转换的话,提交的任何信息都能正确的显示。
(三) 在Formget请求在服务端用request. getParameter(“name”)时返回的是乱码;按tomcat的做法设置Filter也没有用或者用 request.setCharacterEncoding("GBK");也不管用问题是出在处理参数传递的方法上:如果在servlet中用 doGet(HttpServletRequest request, HttpServletResponse response)方法进行处理的话前面即使是写了:
request.setCharacterEncoding("GBK");
response.setContentType("text/html;charset=GBK");
也是不起作用的,返回的中文还是乱码!!!如果把这个函数改成doPost(HttpServletRequest request, HttpServletResponse response)一切就OK了。
同样,在用两个JSP页面处理表单输入之所以能显示中文是因为用的是post方法传递的,改成get方法依旧不行。
由此可见在servlet中用doGet()方法或是在JSP中用get方法进行处理要注意。这毕竟涉及到要通过浏览器传递参数信息,很有可能引起常用字符集的冲突或是不匹配。
解决的办法是:
1) 打开tomcat的server.xml文件,找到区块,加入如下一行:
URIEncoding=”GBK”
完整的应如下:
<Connector port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="GBK"/>
2)重启tomcat,一切OK。
需要加入的原因大家可以去研究 $TOMCAT_HOME/webapps/tomcat-docs/config/http.html下的这个文件就可以知道原因了。需要注意的是:这 个地方如果你要是用UTF-8的时候在传递的过程中在Tomcat中也是要出现乱码的情况,如果不行的话就换别的字符集。
(四) JSP页面上有中文,按钮上面也有中文,但是通过服务器查看页面的时候出现乱码:
解决的办法是:首先在JSP文件中不应该直接包含本地化的消息文本,而是应该通过<bean:message>标签从Resource Bundle中获得文本。应该把你的中文文本放到Application.properties文件中,这个文件放在WEB-INF/classes/* 下,例如我在页面里有姓名,年龄两个label,我首先就是要建一个Application.properties,里面的内容应该是name=”姓名” age=”年龄”,然后我把这个文件放到WEB-INF/classes/properties/下,接下来根据 Application.properties文件,对他进行编码转化,创建一个中文资源文件,假定名字是 Application_cn.properties。在JDK中提供了native2ascii命令,他能够实现字符编码的转换。在DOS环境中找到你 放置Application.properties的这个文件的目录,在DOS环境中执行一下命令,将生成按GBK编码的中文资源文件 Application_cn.properties:native2ascii ?Cencoding gbk Application.properties Application_cn.properties执行以上命令以后将生成如下内容的Application_cn.properties文件: name=\u59d3\u540d age=\u5e74\u9f84,在Struts-config.xml中配置:<message-resources parameter="properties.Application_cn"/>。到这一步,基本上完成了一大半,接着你就要在JSP页面上写 <%@ page language="java" contentType="text/html;charset=GBK" %>,到名字的那个label是要写<bean:message key=”name”>,这样的化在页面上出现的时候就会出现中文的姓名,年龄这个也是一样,按钮上汉字的处理也是同样的。
(五) 写入到数据库是乱码:
解决的方法:要配置一个filter,也就是一个Servelet的过滤器,代码如同第二种时候一样。
如果你是通过JDBC直接链接数据库的时候,配置的代码如下:jdbc:mysql://localhost:3306/workshopdb? useUnicode=true&characterEncoding=GBK,这样保证到数据库中的代码是不是乱码。
如果你是通过数据源链接的化你不能按照这样的写法了,首先你就要写在配置文件中,在tomcat 5.0.19中配置数据源的地方是在C:\Tomcat 5.0\conf\Catalina\localhost这个下面,我建立的工程是workshop,放置的目录是webapp下面, workshop.xml的配置文件如下:
<!-- insert this Context element into server.xml -->
<Context path="/workshop" docBase="workshop" debug="0"
reloadable="true" >
<Resource name="jdbc/WorkshopDB"
auth="Container"
type="javax.sql.DataSource" />
<ResourceParams name="jdbc/WorkshopDB">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>100</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>30</value>
</parameter>
<parameter>
<name>maxWait</name>
<value>10000</value>
</parameter>
<parameter>
<name>username</name>
<value>root</value>
</parameter>
<parameter>
<name>password</name>
<value></value>
</parameter>
<!-- Class name for mm.mysql JDBC driver -->
<parameter>
<name>driverClassName</name>
<value>com.mysql.jdbc.Driver</value>
</parameter>
<parameter>
<name>url</name>
<value><![CDATA[jdbc:mysql://localhost:3306/workshopdb
?useUnicode=true&characterEncoding=GBK]]></value>
</parameter>
</ResourceParams>
</Context>
粗体的地方要特别的注意,和JDBC直接链接的时候是有区别的,如果你是配置正确的化,当你输入中文的时候到数据库中就是中文了,有一点要注意的是你在显 示数据的页面也是要用<%@ page language="java" contentType="text/html;charset=GBK" %>这行代码的。需要注意的是有的前台的人员在写代码的是后用Dreamver写的,写了一个Form的时候把他改成了一个jsp,这样有一个地方 要注意了,那就是在Dreamver中Action的提交方式是request的,你需要把他该过来,因为在jsp的提交的过程中紧紧就是POST和 GET两种方式,但是这两种方式提交的代码在编码方面还是有很大不同的,这个在后面的地方进行说明。3
以上就是我在开发系统中解决中文的问题,不知道能不能解决大家的问题,时间匆忙,没有及时完善,文笔也不是很好,有些地方估计是词不达意。大家可以给我意见,希望能共同进步。
Oracle数据库有三种标准的备份方法,它们分别是导出/导入(EXP/IMP)、热备份和冷备份。导出备件是一种逻辑备份,冷备份和热备份是物理备份。
一、 导出/导入(Export/Import)
利用Export可将数据从数据库中提取出来,利用Import则可将提取出来的数据送回到Oracle数据库中去。
1、 简单导出数据(Export)和导入数据(Import)
Oracle支持三种方式类型的输出:
(1)、表方式(T方式),将指定表的数据导出。
(2)、用户方式(U方式),将指定用户的所有对象及数据导出。
(3)、全库方式(Full方式),瘵数据库中的所有对象导出。
数据导入(Import)的过程是数据导出(Export)的逆过程,分别将数据文件导入数据库和将数据库数据导出到数据文件。
2、 增量导出/导入
增量导出是一种常用的数据备份方法,它只能对整个数据库来实施,并且必须作为SYSTEM来导出。在进行此种导出时,系统不要求回答任何问题。导出文件名缺省为export.dmp,如果不希望自己的输出文件定名为export.dmp,必须在命令行中指出要用的文件名。
增量导出包括三种类型:
(1)、“完全”增量导出(Complete)
即备份三个数据库,比如:
exp system/manager inctype=complete file=040731.dmp
(2)、“增量型”增量导出
备份上一次备份后改变的数据,比如:
exp system/manager inctype=incremental file=040731.dmp
(3)、“累积型”增量导出
累计型导出方式是导出自上次“完全”导出之后数据库中变化了的信息。比如:
exp system/manager inctype=cumulative file=040731.dmp
数据库管理员可以排定一个备份日程表,用数据导出的三个不同方式合理高效的完成。
比如数据库的被封任务可以做如下安排:
星期一:完全备份(A)
星期二:增量导出(B)
星期三:增量导出(C)
星期四:增量导出(D)
星期五:累计导出(E)
星期六:增量导出(F)
星期日:增量导出(G)
如果在星期日,数据库遭到意外破坏,数据库管理员可按一下步骤来回复数据库:
第一步:用命令CREATE DATABASE重新生成数据库结构;
第二步:创建一个足够大的附加回滚。
第三步:完全增量导入A:
imp system/manager inctype=RESTORE FULL=y FILE=A
第四步:累计增量导入E:
imp system/manager inctype=RESTORE FULL=Y FILE=E
第五步:最近增量导入F:
imp system/manager inctype=RESTORE FULL=Y FILE=F
二、 冷备份
冷备份发生在数据库已经正常关闭的情况下,当正常关闭时会提供给我们一个完整的数据库。冷备份时将关键性文件拷贝到另外的位置的一种说法。对于备份Oracle信息而言,冷备份时最快和最安全的方法。冷备份的优点是:
1、 是非常快速的备份方法(只需拷文件)
2、 容易归档(简单拷贝即可)
3、 容易恢复到某个时间点上(只需将文件再拷贝回去)
4、 能与归档方法相结合,做数据库“最佳状态”的恢复。
5、 低度维护,高度安全。
但冷备份也有如下不足:
1、 单独使用时,只能提供到“某一时间点上”的恢复。
2、 再实施备份的全过程中,数据库必须要作备份而不能作其他工作。也就是说,在冷备份过程中,数据库必须是关闭状态。
3、 若磁盘空间有限,只能拷贝到磁带等其他外部存储设备上,速度会很慢。
4、 不能按表或按用户恢复。
如果可能的话(主要看效率),应将信息备份到磁盘上,然后启动数据库(使用户可以工作)并将备份的信息拷贝到磁带上(拷贝的同时,数据库也可以工作)。冷备份中必须拷贝的文件包括:
1、 所有数据文件
2、 所有控制文件
3、 所有联机REDO LOG文件
4、 Init.ora文件(可选)
值得注意的使冷备份必须在数据库关闭的情况下进行,当数据库处于打开状态时,执行数据库文件系统备份是无效的。
下面是作冷备份的完整例子。
(1) 关闭数据库
sqlplus /nolog
sql>connect /as sysdba
sql>shutdown normal;
(2) 用拷贝命令备份全部的时间文件、重做日志文件、控制文件、初始化参数文件
sql>cp
(3) 重启Oracle数据库
sql>startup
三、 热备份
热备份是在数据库运行的情况下,采用archivelog mode方式备份数据库的方法。所以,如果你有昨天夜里的一个冷备份而且又有今天的热备份文件,在发生问题时,就可以利用这些资料恢复更多的信息。热备份要求数据库在Archivelog方式下操作,并需要大量的档案空间。一旦数据库运行在archivelog状态下,就可以做备份了。热备份的命令文件由三部分组成:
1. 数据文件一个表空间一个表空间的备份。
(1) 设置表空间为备份状态
(2) 备份表空间的数据文件
(3) 回复表空间为正常状态
2. 备份归档log文件
(1) 临时停止归档进程
(2) log下那些在archive rede log目标目录中的文件
(3) 重新启动archive进程
(4) 备份归档的redo log文件
3. 用alter database bachup controlfile命令来备份控制文件
热备份的优点是:
1. 可在表空间或数据库文件级备份,备份的时间短。
2. 备份时数据库仍可使用。
3. 可达到秒级恢复(恢复到某一时间点上)。
4. 可对几乎所有数据库实体做恢复
5. 恢复是快速的,在大多数情况下爱数据库仍工作时恢复。
热备份的不足是:
1. 不能出错,否则后果严重
2. 若热备份不成功,所得结果不可用于时间点的恢复
3. 因难于维护,所以要特别仔细小心,不允许“以失败告终”。
代码清单
import java.io.*;
import java.net.*;
import java.util.*;
/**
* <p>title: 个人开发的api</p>
* <p>description: 将指定的http网络资源在本地以文件形式存放</p>
* <p>copyright: copyright (c) 2004</p>
* <p>company: newsky</p>
* @author magicliao
* @version 1.0
*/
public class httpget {
public final static boolean debug = true;//调试用
private static int buffer_size = 8096;//缓冲区大小
private vector vdownload = new vector();//url列表
private vector vfilelist = new vector();//下载后的保存文件名列表
/**
* 构造方法
*/
public httpget() {
}
/**
* 清除下载列表
*/
public void resetlist() {
vdownload.clear();
vfilelist.clear();
}
/**
* 增加下载列表项
*
* @param url string
* @param filename string
*/
public void additem(string url, string filename) {
vdownload.add(url);
vfilelist.add(filename);
}
/**
* 根据列表下载资源
*/
public void downloadbylist() {
string url = null;
string filename = null;
//按列表顺序保存资源
for (int i = 0; i < vdownload.size(); i++) {
url = (string) vdownload.get(i);
filename = (string) vfilelist.get(i);
try {
savetofile(url, filename);
}
catch (ioexception err) {
if (debug) {
system.out.println("资源[" + url + "]下载失败!!!");
}
}
}
if (debug) {
system.out.println("下载完成!!!");
}
}
/**
* 将http资源另存为文件
*
* @param desturl string
* @param filename string
* @throws exception
*/
public void savetofile(string desturl, string filename) throws ioexception {
fileoutputstream fos = null;
bufferedinputstream bis = null;
httpurlconnection httpurl = null;
url url = null;
byte[] buf = new byte[buffer_size];
int size = 0;
//建立链接
url = new url(desturl);
httpurl = (httpurlconnection) url.openconnection();
//连接指定的资源
httpurl.connect();
//获取网络输入流
bis = new bufferedinputstream(httpurl.getinputstream());
//建立文件
fos = new fileoutputstream(filename);
if (this.debug)
system.out.println("正在获取链接[" + desturl + "]的内容...\n将其保存为文件[" + filename + "]");
//保存文件
while ( (size = bis.read(buf)) != -1)
fos.write(buf, 0, size);
fos.close();
bis.close();
httpurl.disconnect();
}
/**
* 设置代理服务器
*
* @param proxy string
* @param proxyport string
*/
public void setproxyserver(string proxy, string proxyport) {
//设置代理服务器
system.getproperties().put("proxyset", "true");
system.getproperties().put("proxyhost", proxy);
system.getproperties().put("proxyport", proxyport);
}
/**
* 设置认证用户名与密码
*
* @param uid string
* @param pwd string
*/
public void setauthenticator(string uid, string pwd) {
authenticator.setdefault(new myauthenticator(uid, pwd));
}
/**
* 主方法(用于测试)
*
* @param argv string[]
*/
public static void main(string argv[]) {
httpget oinstance = new httpget();
try {
//增加下载列表(此处用户可以写入自己代码来增加下载列表)
oinstance.additem("http://www.ebook.com/java/网络编程001.zip","./网络编程1.zip");
oinstance.additem("http://www.ebook.com/java/网络编程002.zip","./网络编程2.zip");
oinstance.additem("http://www.ebook.com/java/网络编程003.zip","./网络编程3.zip");
oinstance.additem("http://www.ebook.com/java/网络编程004.zip","./网络编程4.zip");
oinstance.additem("http://www.ebook.com/java/网络编程005.zip","./网络编程5.zip");
oinstance.additem("http://www.ebook.com/java/网络编程006.zip","./网络编程6.zip");
oinstance.additem("http://www.ebook.com/java/网络编程007.zip","./网络编程7.zip");
//开始下载
oinstance.downloadbylist();
}
catch (exception err) {
system.out.println(err.getmessage());
}
}
}
摘要: 系统,三种角色:教师,学生,管理员,我想让他们的登陆都在一个界面下自动识别,而无需进行身份选择,登陆后,他们将分别到各自的admin.jsp,stu.jsp,teacher.jsp 在数据库中的表结构如下(很多属性略): id--- user---password--type---about
type是用来存储用户的类别,分别有a,t,s分别对应三种角色 about对应的是acegi里所需要的e...
阅读全文
做数据仓库系统,ETL是关键的一环。说大了,ETL是数据整合解决方案,说小了,就是倒数据的工具。回忆一下工作这么些年来,处理数据迁移、转换的工作倒还真的不少。但是那些工作基本上是一次性工作或者很小数据量,使用access、DTS或是自己编个小程序搞定。可是在数据仓库系统中,ETL上升到了一定的理论高度,和原来小打小闹的工具使用不同了。究竟什么不同,从名字上就可以看到,人家已经将倒数据的过程分成3个步骤,E、T、L分别代表抽取、转换和装载。
其实ETL过程就是数据流动的过程,从不同的数据源流向不同的目标数据。但在数据仓库中,ETL有几个特点,一是数据同步,它不是一次性倒完数据就拉到,它是经常性的活动,按照固定周期运行的,甚至现在还有人提出了实时ETL的概念。二是数据量,一般都是巨大的,值得你将数据流动的过程拆分成E、T和L。
现在有很多成熟的工具提供ETL功能,例如datastage、powermart等,且不说他们的好坏。从应用角度来说,ETL的过程其实不是非常复杂,这些工具给数据仓库工程带来和很大的便利性,特别是开发的便利和维护的便利。但另一方面,开发人员容易迷失在这些工具中。举个例子,VB是一种非常简单的语言并且也是非常易用的编程工具,上手特别快,但是真正VB的高手有多少?微软设计的产品通常有个原则是“将使用者当作傻瓜”,在这个原则下,微软的东西确实非常好用,但是对于开发者,如果你自己也将自己当作傻瓜,那就真的傻了。ETL工具也是一样,这些工具为我们提供图形化界面,让我们将主要的精力放在规则上,以期提高开发效率。从使用效果来说,确实使用这些工具能够非常快速地构建一个job来处理某个数据,不过从整体来看,并不见得他的整体效率会高多少。问题主要不是出在工具上,而是在设计、开发人员上。他们迷失在工具中,没有去探求ETL的本质。
可以说这些工具应用了这么长时间,在这么多项目、环境中应用,它必然有它成功之处,它必定体现了ETL的本质。如果我们不透过表面这些工具的简单使用去看它背后蕴涵的思想,最终我们作出来的东西也就是一个个独立的job,将他们整合起来仍然有巨大的工作量。大家都知道“理论与实践相结合”,如果在一个领域有所超越,必须要在理论水平上达到一定的高度
探求ETL本质之一
ETL的过程就是数据流动的过程,从不同异构数据源流向统一的目标数据。其间,数据的抽取、清洗、转换和装载形成串行或并行的过程。ETL的核心还是在于T这个过程,也就是转换,而抽取和装载一般可以作为转换的输入和输出,或者,它们作为一个单独的部件,其复杂度没有转换部件高。和OLTP系统中不同,那里充满这单条记录的insert、update和select等操作,ETL过程一般都是批量操作,例如它的装载多采用批量装载工具,一般都是DBMS系统自身附带的工具,例如Oracle SQLLoader和DB2的autoloader等。
ETL本身有一些特点,在一些工具中都有体现,下面以datastage和powermart举例来说。
1、静态的ETL单元和动态的ETL单元实例;一次转换指明了某种格式的数据如何格式化成另一种格式的数据,对于数据源的物理形式在设计时可以不用指定,它可以在运行时,当这个ETL单元创建一个实例时才指定。对于静态和动态的ETL单元,Datastage没有严格区分,它的一个Job就是实现这个功能,在早期版本,一个Job同时不能运行两次,所以一个Job相当于一个实例,在后期版本,它支持multiple instances,而且还不是默认选项。Powermart中将这两个概念加以区分,静态的叫做Mapping,动态运行时叫做Session。
2、ETL元数据;元数据是描述数据的数据,他的含义非常广泛,这里仅指ETL的元数据。主要包括每次转换前后的数据结构和转换的规则。ETL元数据还包括形式参数的管理,形式参数的ETL单元定义的参数,相对还有实参,它是运行时指定的参数,实参不在元数据管理范围之内。
3、数据流程的控制;要有可视化的流程编辑工具,提供流程定义和流程监控功能。流程调度的最小单位是ETL单元实例,ETL单元是不能在细分的ETL过程,当然这由开发者来控制,例如可以将抽取、转换放在一个ETL单元中,那样这个抽取和转换只能同时运行,而如果将他们分作两个单元,可以分别运行,这有利于错误恢复操作。当然,ETL单元究竟应该细分到什么程度应该依据具体应用来看,目前还没有找到很好的细分策略。比如,我们可以规定将装载一个表的功能作为一个ETL单元,但是不可否认,这样的ETL单元之间会有很多共同的操作,例如两个单元共用一个Hash表,要将这个Hash表装入内存两次。
4、转换规则的定义方法;提供函数集提供常用规则方法,提供规则定义语言描述规则。
5、对数据的快速索引;一般都是利用Hash技术,将参照关系表提前装入内存,在转换时查找这个hash表。Datastage中有Hash文件技术,Powermart也有类似的Lookup功能。
探求ETL本质之二(分类)
昨在IT-Director上阅读一篇报告,关于ETL产品分类的。一般来说,我们眼中的ETL工具都是价格昂贵,能够处理海量数据的家伙,但是这是其中的一种。它可以分成4种,针对不同的需求,主要是从转换规则的复杂度和数据量大小来看。它们包括
1、交互式运行环境,你可以指定数据源、目标数据,指定规则,立马ETL。这种交互式的操作无疑非常方便,但是只能适合小数据量和复杂度不高的ETL过程,因为一旦规则复杂了,可能需要语言级的描述,不能简简单单拖拖拽拽就可以的。还有数据量的问题,这种交互式必然建立在解释型语言基础上,另外他的灵活性必然要牺牲一定的性能为代价。所以如果要处理海量数据的话,每次读取一条记录,每次对规则进行解释执行,每次在写入一条记录,这对性能影响是非常大的。
2、专门编码型的,它提供了一个基于某种语言的程序框架,你可以不必将编程精力放在一些周边的功能上,例如读文件功能、写数据库的功能,而将精力主要放在规则的实现上面。这种近似手工代码的性能肯定是没话说,除非你的编程技巧不过关(这也是不可忽视的因素之一)。对于处理大数据量,处理复杂转换逻辑,这种方式的ETL实现是非常直观的。
3、代码生成器型的,它就像是一个ETL代码生成器,提供简单的图形化界面操作,让你拖拖拽拽将转换规则都设定好,其实他的后台都是生成基于某种语言的程序,要运行这个ETL过程,必须要编译才行。Datastage就是类似这样的产品,设计好的job必须要编译,这避免了每次转换的解释执行,但是不知道它生成的中间语言是什么。以前我设计的ETL工具大挪移其实也是归属于这一类,它提供了界面让用户编写规则,最后生成C++语言,编译后即可运行。这类工具的特点就是要在界面上下狠功夫,必须让用户轻松定义一个ETL过程,提供丰富的插件来完成读、写和转换函数。大挪移在这方面就太弱了,规则必须手写,而且要写成标准c++语法,这未免还是有点难为最终用户了,还不如做成一个专业编码型的产品呢。另外一点,这类工具必须提供面向专家应用的功能,因为它不可能考虑到所有的转换规则和所有的读写,一方面提供插件接口来让第三方编写特定的插件,另一方面还有提供特定语言来实现高级功能。例如Datastage提供一种类Basic的语言,不过他的Job的脚本化实现好像就做的不太好,只能手工绘制job,而不能编程实现Job。
4、最后还有一种类型叫做数据集线器,顾名思义,他就是像Hub一样地工作。将这种类型分出来和上面几种分类在标准上有所差异,上面三种更多指ETL实现的方法,此类主要从数据处理角度。目前有一些产品属于EAI(Enterprise Application Integration),它的数据集成主要是一种准实时性。所以这类产品就像Hub一样,不断接收各种异构数据源来的数据,经过处理,在实施发送到不同的目标数据中去。
虽然,这些类看似各又千秋,特别在BI项目中,面对海量数据的ETL时,中间两种的选择就开始了,在选择过程中,必须要考虑到开发效率、维护方面、性能、学习曲线、人员技能等各方面因素,当然还有最重要也是最现实的因素就是客户的意象。
探求ETL本质之三(转换)
ETL探求之一中提到,ETL过程最复杂的部分就是T,这个转换过程,T过程究竟有哪些类型呢?
一、宏观输入输出
从对数据源的整个宏观处理分,看看一个ETL过程的输入输出,可以分成下面几类:
1、大小交,这种处理在数据清洗过程是常见了,例如从数据源到ODS阶段,如果数据仓库采用维度建模,而且维度基本采用代理键的话,必然存在代码到此键值的转换。如果用SQL实现,必然需要将一个大表和一堆小表都Join起来,当然如果使用ETL工具的话,一般都是先将小表读入内存中再处理。这种情况,输出数据的粒度和大表一样。
2、大大交,大表和大表之间关联也是一个重要的课题,当然其中要有一个主表,在逻辑上,应当是主表Left Join辅表。大表之间的关联存在最大的问题就是性能和稳定性,对于海量数据来说,必须有优化的方法来处理他们的关联,另外,对于大数据的处理无疑会占用太多的系统资源,出错的几率非常大,如何做到有效错误恢复也是个问题。对于这种情况,我们建议还是尽量将大表拆分成适度的稍小一点的表,形成大小交的类型。这类情况的输出数据粒度和主表一样。
3、站着进来,躺着出去。事务系统中为了提高系统灵活性和扩展性,很多信息放在代码表中维护,所以它的“事实表”就是一种窄表,而在数据仓库中,通常要进行宽化,从行变成列,所以称这种处理情况叫做“站着进来,躺着出去”。大家对Decode肯定不陌生,这是进行宽表化常见的手段之一。窄表变宽表的过程主要体现在对窄表中那个代码字段的操作。这种情况,窄表是输入,宽表是输出,宽表的粒度必定要比窄表粗一些,就粗在那个代码字段上。
4、聚集。数据仓库中重要的任务就是沉淀数据,聚集是必不可少的操作,它是粗化数据粒度的过程。聚集本身其实很简单,就是类似SQL中Group by的操作,选取特定字段(维度),对度量字段再使用某种聚集函数。但是对于大数据量情况下,聚集算法的优化仍是探究的一个课题。例如是直接使用SQL的Group by,还是先排序,在处理。
二、微观规则
从数据的转换的微观细节分,可以分成下面的几个基本类型,当然还有一些复杂的组合情况,例如先运算,在参照转换的规则,这种基于基本类型组合的情况就不在此列了。ETL的规则是依赖目标数据的,目标数据有多少字段,就有多少条规则。
1、直接映射,原来是什么就是什么,原封不动照搬过来,对这样的规则,如果数据源字段和目标字段长度或精度不符,需要特别注意看是否真的可以直接映射还是需要做一些简单运算。
2、字段运算,数据源的一个或多个字段进行数学运算得到的目标字段,这种规则一般对数值型字段而言。
3、参照转换,在转换中通常要用数据源的一个或多个字段作为Key,去一个关联数组中去搜索特定值,而且应该只能得到唯一值。这个关联数组使用Hash算法实现是比较合适也是最常见的,在整个ETL开始之前,它就装入内存,对性能提高的帮助非常大。
4、字符串处理,从数据源某个字符串字段中经常可以获取特定信息,例如身份证号。而且,经常会有数值型值以字符串形式体现。对字符串的操作通常有类型转换、字符串截取等。但是由于字符类型字段的随意性也造成了脏数据的隐患,所以在处理这种规则的时候,一定要加上异常处理。
5、空值判断,对于空值的处理是数据仓库中一个常见问题,是将它作为脏数据还是作为特定一种维成员?这恐怕还要看应用的情况,也是需要进一步探求的。但是无论怎样,对于可能有NULL值的字段,不要采用“直接映射”的规则类型,必须对空值进行判断,目前我们的建议是将它转换成特定的值。
6、日期转换,在数据仓库中日期值一般都会有特定的,不同于日期类型值的表示方法,例如使用8位整型20040801表示日期。而在数据源中,这种字段基本都是日期类型的,所以对于这样的规则,需要一些共通函数来处理将日期转换为8位日期值、6位月份值等。
7、日期运算,基于日期,我们通常会计算日差、月差、时长等。一般数据库提供的日期运算函数都是基于日期型的,而在数据仓库中采用特定类型来表示日期的话,必须有一套自己的日期运算函数集。
8、聚集运算,对于事实表中的度量字段,他们通常是通过数据源一个或多个字段运用聚集函数得来的,这些聚集函数为SQL标准中,包括sum,count,avg,min,max。
9、既定取值,这种规则和以上各种类型规则的差别就在于它不依赖于数据源字段,对目标字段取一个固定的或是依赖系统的值。
探求ETL本质之四(数据质量)
“不要绝对的数据准确,但要知道为什么不准确。”
这是我们在构建BI系统是对数据准确性的要求。确实,对绝对的数据准确谁也没有把握,不仅是系统集成商,包括客户也是无法确定。准确的东西需要一个标准,但首先要保证这个标准是准确的,至少现在还没有这样一个标准。客户会提出一个相对标准,例如将你的OLAP数据结果和报表结果对比。虽然这是一种不太公平的比较,你也只好认了吧。
首先在数据源那里,已经很难保证数据质量了,这一点也是事实。在这一层有哪些可能原因导致数据质量问题?可以分为下面几类:
1、数据格式错误,例如缺失数据、数据值超出范围或是数据格式非法等。要知道对于同样处理大数据量的数据源系统,他们通常会舍弃一些数据库自身的检查机制,例如字段约束等。他们尽可能将数据检查在入库前保证,但是这一点是很难确保的。这类情况诸如身份证号码、手机号、非日期类型的日期字段等。
2、数据一致性,同样,数据源系统为了性能的考虑,会在一定程度上舍弃外键约束,这通常会导致数据不一致。例如在帐务表中会出现一个用户表中没有的用户ID,在例如有些代码在代码表中找不到等。
3、业务逻辑的合理性,这一点很难说对与错。通常,数据源系统的设计并不是非常严谨,例如让用户开户日期晚于用户销户日期都是有可能发生的,一个用户表中存在多个用户ID也是有可能发生的。对这种情况,有什么办法吗?
构建一个BI系统,要做到完全理解数据源系统根本就是不可能的。特别是数据源系统在交付后,有更多维护人员的即兴发挥,那更是要花大量的时间去寻找原因。以前曾经争辩过设计人员对规则描述的问题,有人提出要在ETL开始之前务必将所有的规则弄得一清二楚。我并不同意这样的意见,倒是认为在ETL过程要有处理这些质量有问题数据的保证。一定要正面这些脏数据,是丢弃还是处理,无法逃避。如果没有质量保证,那么在这个过程中,错误会逐渐放大,抛开数据源质量问题,我们再来看看ETL过程中哪些因素对数据准确性产生重大影响。
1、规则描述错误。上面提到对设计人员对数据源系统理解的不充分,导致规则理解错误,这是一方面。另一方面,是规则的描述,如果无二义性地描述规则也是要探求的一个课题。规则是依附于目标字段的,在探求之三中,提到规则的分类。但是规则总不能总是用文字描述,必须有严格的数学表达方式。我甚至想过,如果设计人员能够使用某种规则语言来描述,那么我们的ETL单元就可以自动生成、同步,省去很多手工操作了。
2、ETL开发错误。即时规则很明确,ETL开发的过程中也会发生一些错误,例如逻辑错误、书写错误等。例如对于一个分段值,开区间闭区间是需要指定的,但是常常开发人员没注意,一个大于等于号写成大于号就导致数据错误。
3、人为处理错误。在整体ETL流程没有完成之前,为了图省事,通常会手工运行ETL过程,这其中一个重大的问题就是你不会按照正常流程去运行了,而是按照自己的理解去运行,发生的错误可能是误删了数据、重复装载数据等。
探求ETL本质之五(质量保证)
上回提到ETL数据质量问题,这是无法根治的,只能采取特定的手段去尽量避免,而且必须要定义出度量方法来衡量数据的质量是好还是坏。对于数据源的质量,客户对此应该更加关心,如果在这个源头不能保证比较干净的数据,那么后面的分析功能的可信度也都成问题。数据源系统也在不断进化过程中,客户的操作也在逐渐规范中,BI系统也同样如此。本文探讨一下对数据源质量和ETL处理质量的应对方法。
如何应对数据源的质量问题?记得在onteldatastage列表中也讨论过一个话题-"-1的处理",在数据仓库模型维表中,通常有一条-1记录,表示“未知”,这个未知含义可广了,任何可能出错的数据,NULL数据甚至是规则没有涵盖到的数据,都转成-1。这是一种处理脏数据的方法,但这也是一种掩盖事实的方法。就好像写一个函数FileOpen(filename),返回一个错误码,当然,你可以只返回一种错误码,如-1,但这是一种不好的设计,对于调用者来说,他需要依据这个错误码进行某些判断,例如是文件不存在,还是读取权限不够,都有相应的处理逻辑。数据仓库中也是一样,所以,建议将不同的数据质量类型处理结果分别转换成不同的值,譬如,在转换后,-1表示参照不上,-2表示NULL数据等。不过这仅仅对付了上回提到的第一类错误,数据格式错误。对于数据一致性和业务逻辑合理性问题,这仍有待探求。但这里有一个原则就是“必须在数据仓库中反应数据源的质量”。
对于ETL过程中产生的质量问题,必须有保障手段。从以往的经验看,没有保障手段给实施人员带来麻烦重重。实施人员对于反复装载数据一定不会陌生,甚至是最后数据留到最后的Cube,才发现了第一步ETL其实已经错了。这个保障手段就是数据验证机制,当然,它的目的是能够在ETL过程中监控数据质量,产生报警。这个模块要将实施人员当作是最终用户,可以说他们是数据验证机制的直接收益者。
首先,必须有一个对质量的度量方法,什么是高质什么是低质,不能靠感官感觉,但这却是在没有度量方法条件下通常的做法。那经营分析系统来说,联通总部曾提出测试规范,这其实就是一种度量方法,例如指标的误差范围不能高于5%等,对系统本身来说其实必须要有这样的度量方法,先不要说这个度量方法是否科学。对于ETL数据处理质量,他的度量方法应该比联通总部测试规范定义的方法更要严格,因为他更多将BI系统看作一个黑盒子,从数据源到展现的数据误差允许一定的误差。而ETL数据处理质量度量是一种白盒的度量,要注重每一步过程。因此理论上,要求输入输出的指标应该完全一致。但是我们必须正面完全一致只是理想,对于有误差的数据,必须找到原因。
在质量度量方法的前提下,就可以建立一个数据验证框架。此框架依据总量、分量数据稽核方法,该方法在高的《数据仓库中的数据稽核技术》一文中已经指出。作为补充,下面提出几点功能上的建议:
1、提供前端。将开发实施人员当作用户,同样也要为之提供友好的用户界面。《稽核技术》一文中指出测试报告的形式,这种形式还是要依赖人为判断,在一堆数据中去找规律。到不如用OLAP的方式提供界面,不光是加上测试统计出来的指标结果,并且配合度量方法的计算。例如误差率,对于误差率为大于0的指标,就要好好查一下原因了。
2、提供框架。数据验证不是一次性工作,而是每次ETL过程中都必须做的。因此,必须有一个框架,自动化验证过程,并提供扩展手段,让实施人员能够增加验证范围。有了这样一个框架,其实它起到规范化操作的作用,开发实施人员可以将主要精力放在验证脚本的编写上,而不必过多关注验证如何融合到流程中,如何展现等工作。为此,要设计一套表,类似于DM表,每次验证结果数据都记录其中,并且自动触发多维分析的数据装载、发布等。这样,实施人员可以在每次装载,甚至在流程过程中就可以观察数据的误差率。特别是,如果数据仓库的模型能够统一起来,甚至数据验证脚本都可以确定下来,剩下的就是规范流程了。
3、规范流程。上回提到有一种ETL数据质量问题是由于人工处理导致的,其中最主要原因还是流程不规范。开发实施人员运行单独一个ETL单元是很方便的,虽然以前曾建议一个ETL单元必须是“可重入”的,这能够解决误删数据,重复装载数据问题。但要记住数据验证也是在流程当中,要让数据验证能够日常运作,就不要让实施者感觉到他的存在。总的来说,规范流程是提高实施效率的关键工作,这也是以后要继续探求的。
探求ETL本质之六(元数据漫谈)
对于元数据(Metadata)的定义到目前为止没有什么特别精彩的,这个概念非常广,一般都是这样定义,“元数据是描述数据的数据(Data about Data)”,这造成一种递归定义,就像问小强住在哪里,答,在旺财隔壁。按照这样的定义,元数据所描述的数据是什么呢?还是元数据。这样就可能有元元元...元数据。我还听说过一种对元数据,如果说数据是一抽屉档案,那么元数据就是分类标签。那它和索引有什么区别?
元数据体现是一种抽象,哲学家从古至今都在抽象这个世界,力图找到世界的本质。抽象不是一层关系,它是一种逐步由具体到一般的过程。例如我->男人->人->哺乳动物->生物这就是一个抽象过程,你要是在软件业混会发现这个例子很常见,面向对象方法就是这样一种抽象过程。它对世界中的事物、过程进行抽象,使用面向对象方法,构建一套对象模型。同样在面向对象方法中,类是对象的抽象,接口又是对类的抽象。因此,我认为可以将“元”和“抽象”换一下,叫抽象数据是不是好理解一些。
常听到这样的话,“xx领导的讲话高屋建瓴,给我们后面的工作指引的清晰的方向”,这个成语“高屋建瓴”,站在10楼往下到水,居高临下,能砸死人,这是指站在一定的高度看待事物,这个一定的高度就是指他有够“元”。在设计模式中,强调要对接口编程,就是说你不要处理这类对象和那类对象的交互,而要处理这个接口和那个接口的交互,先别管他们内部是怎么干的。
元数据存在的意义也在于此,虽然上面说了一通都撤到哲学上去,但这个词必须还是要结合软件设计中看,我不知道在别的领域是不是存在Metadata这样的叫法,虽然我相信别的领域必然有类似的东东。元数据的存在就是要做到在更高抽象一层设计软件。这肯定有好处,什么灵活性啊,扩展性啊,可维护性啊,都能得到提高,而且架构清晰,只是弯弯太多,要是从下往上看,太复杂了。很早以前,我曾看过backorifice的代码,我靠,一个简单的功能,从这个类转到父类,又转到父类,很不理解,为什么一个简单的功能不在一个类的方法中实现就拉到了呢?现在想想,还真不能这样,这虽然使代码容易看懂了,但是结构确实混乱的,那他只能干现在的事,如果有什么功能扩展,这些代码就废了。
我从98年刚工作时就开始接触元数据的概念,当时叫做元数据驱动的系统架构,后来在QiDSS中也用到这个概念构建QiNavigator,但是现在觉得元数据也没啥,不就是建一堆表描述界面的元素,再利用这些数据自动生成界面吗。到了数据仓库系统中,这个概念更强了,是数据仓库中一个重要的部分。但是至今,我还是认为这个概念过于玄乎,看不到实际的东西,市面上有一些元数据管理的东西,但是从应用情况就得知,用的不多。之所以玄乎,就是因为抽象层次没有分清楚,关键就是对于元数据的分类(这种分类就是一种抽象过程)和元数据的使用。你可以将元数据抽象成0和1,但是那样对你的业务有用吗?必须还得抽象到适合的程度,最后问题还是“度”。
数据仓库系统的元数据作用如何?还不就是使系统自动运转,易于管理吗?要做到这一步,可没必要将系统抽象到太极、两仪、八卦之类的,业界也曾定义过一些元数据规范,向CWM、XMI等等,可以借鉴,不过俺对此也是不精通的说,以后再说
摘要: 我们要做到不但会写SQL,还要做到写出性能优良的SQL,以下为笔者学习、摘录、并汇总部分资料与大家分享!
(1)
选择最有效率的表名顺序
(
只在基于规则的优化器中有效
)
:
ORACLE
的解析器按照从右到左的顺序处理
FROM
子...
阅读全文
摘要: 操作符优化
IN 操作符
用
IN
...
阅读全文
比如我们想对某人的消费项目进行汇总,对应以下两个表:Theme 与 ThemeDetail
Theme 的记录为:
ThemeID(int) ThemeName(varchar[10])
1 就餐
2 出差
3 乘车
4 其它
ThemeDetail 的记录为:
DetailID(int) ThemeID(int) Price(money)
1 1 12.5
2 1 5
3 1 6
4 2 11
5 2 17
6 3 8
其中 Theme 中的 ThemeID 与 ThemeDetail 中的 ThemeID 是一对多的关系,对 ThemeDetail 表的理解如下:“就餐”费用为 12.5 + 5 + 6 = 23.5 元,“出差”费用为 11 + 17 = 28 元,“乘车”费用为 8 = 8 元,“其它”费用不存在,视为 0 处理,对应的 SQL 语句可以这样表示:
SELECT TOP 100 PERCENT dbo.Theme.ThemeName, ISNULL(SUM(dbo.ThemeDetail.Price), 0)
AS TotalPrice
FROM dbo.Theme INNERJOIN
dbo.ThemeDetail ON dbo.Theme.ThemeID = dbo.ThemeDetail.ThemeID
GROUP BY dbo.Theme.ThemeName, dbo.Theme.ThemeID
ORDER BY dbo.Theme.ThemeID
执行结果如下:
ThemeName TotalPrice
就餐 23.5
出差 28
乘车 8
对于消费记录不存的记录如果就这样不显示它的话,使用内联的方法就可以满足要求了,但是我们现在需要对 Theme 中的每一项均做统计,也包括“其它”项,于是我们应该采用另一种方法来实现,这就是左外联的方法,相应的 SQL 语句可以这样表示:
SELECT TOP 100 PERCENT dbo.Theme.ThemeName, ISNULL(SUM(dbo.ThemeDetail.Price), 0)
AS TotalPrice
FROM dbo.Theme LEFTOUTER JOIN
dbo.ThemeDetail ON dbo.Theme.ThemeID = dbo.ThemeDetail.ThemeID
GROUP BY dbo.Theme.ThemeName, dbo.Theme.ThemeID
ORDER BY dbo.Theme.ThemeID
执行结果如下:
ThemeName TotalPrice
就餐 23.5
出差 28
乘车 8
其它 0
这样是不是就满足了我们的要求呢!
/*
我测试了 10 万条记录后发现速度并非兄弟所说的那样,测试代码如下
*/
declare @StartTime datetime
set @StartTime = getdate()
SELECT TOP 100 PERCENT ThemeName, ISNULL(SUM(Price), 0) AS TotalPrice
FROM Theme LEFT OUTER JOIN ThemeDetail ON Theme.ThemeID = ThemeDetail.ThemeID
GROUP BY ThemeName, Theme.ThemeID
ORDER BY Theme.ThemeID
select datediff(millisecond,@StartTime,getdate())
set @StartTime = getdate()
SELECT TOP 100 PERCENT ThemeName, ISNULL((SELECT SUM(ThemeDetail.Price)
FROM ThemeDetail
WHERE ThemeDetail.ThemeID = Theme.ThemeID), 0) AS TotalPrice
FROM Theme
GROUP BY ThemeName, ThemeID
ORDER BY ThemeID
select datediff(millisecond,@StartTime,getdate())
/* 测试结果如下,精确到毫秒,最后是平均值 */
次数 时间 时间
----------------------
1 93 110
2 93 93
3 93 110
4 93 93
5 93 110
6 93 110
7 93 93
8 93 93
9 93 93
10 93 126
11 110 110
12 93 126
13 96 123
14 93 126
15 93 110
16 93 123
17 106 96
18 93 93
19 106 96
20 93 93
----------------------
95.3 106.35
1)如何分别行货和水货:
这一点对于那些不了解IBM的朋友们还是非常有必要了解的,否则非常有可能被JS以水充行的糊弄过去,IBM行货和水货非常容易分辨,在不打开包装箱的情况下就可以很轻松的看到。在不打开包装箱的情况下,看包装箱侧面的白色标签,上面有一串诸如2662 B2C的编码,如果最后一位是C,那么这台机器就是行货,如果最后一位是U、H、A、J等编号,那么这台机器是水货无疑。这是打开包装箱前的第一项任务,如果没有问题,那么就可以打开包装箱看看了。
2)核对BIOS、包装箱和机身的序列号:
这一点是至关重要的,这是保证整机不被狸猫换太子的非常关键的一步。具体步骤如下:在不插外界电源的情况下开机,在出现IBM LOGO的时候按住F1键,在出现的页面中找到S/N这一项,将其和包装箱的S/N号相比较,看看是否吻合,如果吻合,就可以退出BIOS,将机身反过来,找到背部的条形码标签,看看上面的序列号是否和包装箱上的相同,如果吻合就可以进入系统了。一般情况下,水货的机器是已经解包的(WIN XP不经过注册就可以进行系统),即可以进入相对应的操作系统了,这是由于从香港带过关的时候防止机器被扣下所作的一些应对措施,如果是这种情况,那么直接点击开始,选择程序中的ThinkPad Utilities选项,点击控制台程序,里面的最后一个页面里也有S/N号,将他和包装箱的相比较看是否吻合,如果这几项全部没有问题,OK,说明机器整体来看没有太大的问题。如果说系统还没有解包,那么先解包再进行上面的程序,这一点非常重要,不要因为系统没有解包就跳过这一步,这是不可取的。这一步进行完就可以进行下一步了。
3)放下机器,核对装箱单
装箱单一般放在正面打开盒子的夹层里面,上面写明了包装箱内的所有物品,包括一些广告宣传单,检查装箱单就是防止部件被JS克扣的重要环节,细心的对照装箱单上的所有内容,与包装箱内的每一个部件相对比,如果缺少任何一个部件,那么就要和JS交涉了,你的机器和有可能被JS克扣下某些部件,最容易被克扣的部件是:小红帽、红点包。另外,IBM的笔记本没有系统恢复盘,这点和其他品牌的产品有些不同。细心的对照完每一项就可以将包装箱放在一边来看机器了。
4)电池的充电次数
业界只有IBM和SONY能够直接的看到笔记本电脑电池的充电次数,这一点为用户提供了不少的方便,并且可以成为我们分辨机器是否被人使用过的非常好的办法,看充电次数的方法如下:点击开始,选择程序中的ThinkPad Utilities选项,选中BATTERY INFORMATION,点击Stutas Detail页面,其中的Cycle Count就是这块电池的充电次数,一般来说行货的电池充电次数都在0-1次之间,如果超过这个数就可能有问题,水货的话一般也不能超过三次,否则的话也可能有问题,如果充电次数在几十次,那么看都不要看,这台机器肯定有问题
5)查询保修日期
IBM的机器一般都带有1年到3年的保修,这一点是IBM用户的便利之处,也是挑机的时候非常重要的一步,具体的方法是向JS要一根网线或者电话线,然后到如下网址http://www-3.ibm.com/pc/support/site.wss/warranty/warranty.vm,在打开的页面中输入包装箱上的序列号,即可,你会看到这台机器的销售国家(行货的话应该写明销往中国),还有这台机器的保修截止日期,和机器被面的机器出厂日期相对照,看是否有问题,没有问题的话就可以下线进行下一步了。
6)火眼金睛查坏点
坏点是非常影响屏幕显示效果的,并且坏点的出现是不可逆的,如何保证自己的机器没有坏点哪,在开箱之前其实就应该和JS协商好,如果出现坏点该如何解决,一般是可以要求换货,但是行货可能会有些不同,一般是坏点的数量在一定的范围内都属于正常范围内,所以说这一步比较适合购买水货的朋友。具体的步骤如下:如果您没有做任何准备,没有专用的软件来进行测试的话,用肉眼也是完全可以达到挑选的效果的,首先删除桌面上的所有快捷方式图标,右键单击桌面,选择属性,在背景选项中,选择图片为无,然后在右下角的颜色中挑选各种颜色,主要要选择红白蓝黑绿五中,每切换到一种颜色,就用肉眼仔细地看屏幕有没有和显示颜色不同的点,没有的话就切换到另外一种颜色,直到进行完所有的颜色察看。如果您事先准备了专门的软件,比如NOKIA的屏幕测试程序,就可以直接运行提来进行检测,效果相同。如果几项测试中都没有发现坏点,那么恭喜您,屏幕比较完美。
7)需要注意的其他事项
以上的这些项目都进行完之后就应该看看一些小的细节了,比如,看看机器的进风口有没有很多的灰尘,键盘是否有亮光,外壳有没有划伤的痕迹,边角有没有撞痕,外观方面也就这么多了。如果是光软互换机器,就可以抽出光驱,看看光驱是否全新(主要看铁板的地方有没有手指印),插拔的接口是否很新,没问题的话插进机器了,拿张光碟读一下,将VCD播放的刻度来回拖一下看反映是否灵敏,如果没有问题就可以交钱了,记住要让JS开具一张有公章的收据,写清楚序列号,省得以后麻烦。还有就是要向JS索要境外发票,一般来说发票会在卖出机器的半个月后给你。交完钱后有可能的话在JS的卖场内用螺丝刀拆开硬盘和内存插槽,看看这两个配件上是否有IBM的专用标签和FRU号码,如果JS给你的机器没有这两项的话,就当场和他交涉换货,因为这说明这两个配件被偷梁换柱了。如果以上的种种都没有问题,就可以拿机器走人了,大可不必去繁琐的测试USB接口、并口等等I/O接口,一般来说这些部件是不会出现问题的,如果出现问题大可以直接拿境外发票向蓝快要求保修服务。
相比其他品牌的笔记本电脑,IBM笔记本有着在挑选时的独特优势,比如有软件的支持和标准的规则,挑选的时候只要做到笔者所说的这几点,一般都可以买到称心如意的IBM笔记本电脑,但是还是要提醒朋友们的是,心细才是正确的,切不可麻痹大意。OK,笔者祝大家都能挑选到没有任何问题的IBM笔记本。
本文是开发基于spring的web应用的入门文章,前端采用Struts MVC框架,中间层采用spring,后台采用Hibernate。
本文包含以下内容:
·配置Hibernate和事务
·装载Spring的applicationContext.xml文件
·建立业务层和DAO之间的依赖关系
·将Spring应用到Struts中
简介 这个例子是建立一个简单的web应用,叫MyUsers,完成用户管理操作,包含简单的数据库增,删,查,该即CRUD(新建,访问,更新,删除)操作。这是一个三层的web应用,通过Action(Struts)访问业务层,业务层访问DAO。图一简要说明了该应用的总体结构。图上的数字说明了流程顺序-从web(UserAction)到中间层(UserManager),再到数据访问层(UserDAO),然后将结果返回。
Spring层的真正强大在于它的声明型事务处理,帮定和对持久层支持(例如Hiberate和iBATIS)
以下下是完成这个例子的步骤:
1. 安装Eclipse插件
2. 数据库建表
3. 配置Hibernate和Spring
4. 建立Hibernate DAO接口的实现类
5. 运行测试类,测试DAO的CRUD操作
6. 创建一个处理类,声明事务
7. 创建web层的Action和model
8. 运行Action的测试类测试CRUD操作
9. 创建jsp文件通过浏览器进行CRUD操作
10. 通过浏览器校验jsp
安装eclipse插件 1. Hibernate插件http://www.binamics.com/hibernatesync
2. Spring插件http://springframework.sourceforge.net/spring-ide/eclipse/updatesite/
3. MyEclipse插件(破解版)
4. Tomcat插件. tanghan
5. 其他插件包括xml,jsp,
数据库建表create table app_user(id number not null primary,firstname vchar(32),lastname vchar(32)); |
新建项目 新建一个web project,新建后的目录结构同时包含了新建文件夹page用于放jsp文件,和源文件夹test用于放junit测试文件。同时将用到的包,包括struts,hibernate,spring都导入到lib目录下。
创建持久层O/R mapping
1. 在src/com.jandar.model下用hibernate插件从数据库导出app_user的.hbm.xml文件改名为User.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" > <hibernate-mapping package="com.jandar.model"> <class name="User" table="APP_USER"> <id column="ID" name="id" type="integer" >
<generator class="assigned" />
</id>
<property column="LASTNAME" length="10" name="lastname" not-null="false" type="string" />
<property column="FIRSTNAME" length="10" name="firstname" not-null="true" type="string" />
</class> </hibernate-mapping> |
2. 通过hibernate synchronizer->synchronizer file生成User.java文件,User对象对应于数据库中的app_user表
注:在eclipse下自动生成的对象文件不完全相同,相同的是每个对象文件必须实现Serializable接口,必需又toString和hashCode方法;
import java.io.Serializable; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle;
public class BaseObject implements Serializable { public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); }
public boolean equals(Object o) { return EqualsBuilder.reflectionEquals(this, o); }
public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } }
public class User extends BaseObject { private Long id; private String firstName; private String lastName;
/** * @return Returns the id. */
public Long getId() { return id; }
/** * @param id The id to set. */
public void setId(Long id) { this.id = id; }
/** * @return Returns the firstName. */
public String getFirstName() { return firstName; }
/** * @param firstName The firstName to set. */
public void setFirstName(String firstName) { this.firstName = firstName; }
/** * @return Returns the lastName. */
public String getLastName() { return lastName; }
/** * @param lastName The lastName to set. */
public void setLastName(String lastName) { this.lastName = lastName; } } |
创建DAO访问对象 1. 在src/com.jandar.service.dao新建IDAO.java接口,所有的DAO都继承该接口
package com.jandar.services.dao;
public interface IDAO {
} |
2. 在src/com.jandar.service.dao下新建IUserDAO.java接口
public interface IUserDAO extends DAO { List getUsers(); User getUser(Integer userid); void saveUser(User user); void removeUser(Integer id); } |
该接口提供了访问对象的方法,
3. 在src/com.jandar.service.dao.hibernate下新建UserDAOHiberante.java
import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.orm.hibernate.support.HibernateDaoSupport; import com.jandar.model.User; import com.jandar.service.dao.IUserDAO;
public class UserDaoHibernate extends HibernateDaoSupport implements IUserDAO {
private Log log=LogFactory.getLog(UserDaoHibernate.class); /* (非 Javadoc) * @see com.jandar.dao.IUserDAO#getUsers() */
public List getUsers() { return getHibernateTemplate().find("from User"); }
/* (非 Javadoc) * @see com.jandar.dao.IUserDAO#getUser(java.lang.Long) */
public User getUser(Integer id) { // TODO 自动生成方法存根 return (User) getHibernateTemplate().get(User.class,id); }
/* (非 Javadoc) * @see com.jandar.dao.IUserDAO#saveUser(com.jandar.model.User) */
public void saveUser(User user) { log.debug("xxxxxxx"); System.out.println("yyyy"); getHibernateTemplate().saveOrUpdate(user); if(log.isDebugEnabled()) { log.debug("userId set to "+user.getId()); } }
/* (非 Javadoc) * @see com.jandar.dao.IUserDAO#removeUser(java.lang.Long) */
public void removeUser(Integer id) { Object user=getHibernateTemplate().load(User.class,id); getHibernateTemplate().delete(user); if(log.isDebugEnabled()){ log.debug("del user "+id); } } } |
在这个类中实现了IUserDAO接口的方法,并且继承了HibernateDAOSupport类。这个类的作用是通过hibernate访问、操作对象,进而实现对数据库的操作。
这个*头司机太强劲了,要做生意额朋友好好看看。。。
我要从徐家汇赶去机场,于是匆匆结束了一个会议,在美罗大厦前搜索出租车。一辆大众发现了我,非常专业的、径直的停在我的面前。这一停,于是有了后面的这个让我深感震撼的故事,象上了一堂生动的MBA案例课。为了忠实于这名出租车司机的原意,我凭记忆尽量重复他原来的话。
“去哪里……好的,机场。我在徐家汇就喜欢做美罗大厦的生意。这里我只做两个地方。美罗大厦,均瑶大厦。你知道吗?接到你之前,我在美罗大厦门口兜了两圈,终于被我看到你了!从写字楼里出来的,肯定去的不近~~~”
“哦?你很有方法嘛!”我附和了一下。
“做出租车司机,也要用科学的方法。”他说。我一愣,顿时很有些兴趣“什么科学的方法?”
“要懂得统计。我做过精确的计算。我说给你听啊。我每天开17个小时的车,每小时成本34.5元……”
“怎么算出来的?”我追问。
“你算啊,我每天要交380元,油费大概210元左右。一天17小时,平均每小时固定成本22元,交给公司,平均每小时12.5元油费。这是不是就是34.5元?”,我有些惊讶。我打了10年的车,第一次听到有出租车司机这么计算成本。以前的司机都和我说,每公里成本0.3元,另外每天交多少钱之类的。
“成本是不能按公里算的,只能按时间算。你看,计价器有一个“检查”功能。你可以看到一天的详细记录。我做过数据分析,每次载客之间的空驶时间平均为7分钟。如果上来一个起步价,10元,大概要开10分钟。也就是每一个10元的客人要花17分钟的成本,就是9.8元。不赚钱啊!如果说做浦东、杭州、青浦的客人是吃饭,做10元的客人连吃菜都算不上,只能算是撒了些味精。”
强!这位师傅听上去真不象出租车司机,到象是一位成本核算师。“那你怎么办呢?”我更感兴趣了,继续问。看来去机场的路上还能学到新东西。
“千万不能被客户拉了满街跑。而是通过选择停车的地点,时间,和客户,主动地决定你要去的地方。”我非常惊讶,这听上去很有意思。“有人说做出租车司机是靠运气吃饭的职业。我以为不是。你要站在客户的位置上,从客户的角度去思考。”这句话听上去很专业,有点象很多商业管理培训老师说的“putyourselfintoothers\'shoes.”
“给你举个例子,医院门口,一个拿着药的,一个拿着脸盆的,你带哪一个。”我想了想,说不知道。
“你要带那个拿脸盆的。一般人病小痛的到医院看一看,拿点药,不一定会去很远的医院。拿着脸盆打车的,那是出院的。住院哪有不死人的?今天二楼的谁死了,明天三楼又死了一个。从医院出来的人通常会有一种重获新生的感觉,重新认识生命的意义,健康才最重要。那天这个说:走,去青浦。眼睛都不眨一下。你说他会打车到人民广场,再去做青浦线吗?绝对不会!”
我不由得开始佩服。
“再给你举个例子。那天人民广场,三个人在前面招手。一个年轻女子,拿着小包,刚买完东西。还有一对青年男女,一看就是逛街的。第三个是个里面穿绒衬衫的,外面羽绒服的男子,拿着笔记本包。我看一个人只要3秒钟。我毫不犹豫地停在这个男子面前。这个男的上车后说:延安高架、南北高架~~~还没说后面就忍不住问,为什么你毫不犹豫地开到我面前?前面还有两个人,他们要是想上车,我也不好意思和他们抢。我回答说,中午的时候,还有十几分钟就1点了。那个女孩子是中午溜出来买东西的,估计公司很近;那对男女是游客,没拿什么东西,不会去很远;你是出去办事的,拿着笔记本包,一看就是公务。而且这个时候出去,估计应该不会近。那个男的就说,你说对了,去宝山。”
“那些在超市门口,地铁口打车,穿着睡衣的人可能去很远吗?可能去机场吗?机场也不会让她进啊。”
有道理!我越听越有意思。
“很多司机都抱怨,生意不好做啊,油价又涨了啊,都从别人身上找原因。我说,你永远从别人身上找原因,你永远不能提高。从自己身上找找看,问题出在哪里。”这话听起来好熟,好像是“如果你不能改变世界,就改变你自己”,或者StevenCorvey的“影响圈和关注圈”的翻版。“有一次,在南丹路一个人拦车,去田林。后来又有一次,一个人在南丹路拦车,还是去田林。我就问了,怎么你们从南丹路出来的人,很多都是去田林呢?人家说,在南丹路有一个公共汽车总站,我们都是坐公共汽车从浦东到这里,然后搭车去田林的。我恍然大悟。比如你看我们开过的这条路,没有写字楼,没有酒店,什么都没有,只有公共汽车站,站在这里拦车的多半都是刚下公共汽车的,再选择一条最短路经打车。在这里拦车的客户通常不会高于15元。”
“所以我说,态度决定一切!”我听十几个总裁讲过这句话,第一次听出租车司机这么说。
“要用科学的方法,统计学来做生意。天天等在地铁站口排队,怎么能赚到钱?每个月就赚500块钱怎么养活老婆孩子?这就是在谋杀啊!慢性谋杀你的全家。要用知识武装自己。学习知识可以把一个人变成聪明的人,一个聪明的人学习知识可以变成很聪明的人。一个很聪明的人学习知识,可以变成天才。”
“有一次一个人打车去火车站,问怎么走。他说这么这么走。我说慢,上高架,再这么这么走。他说,这就绕远了。我说,没关系,你经常走你有经验,你那么走50块,你按我的走法,等里程表50块了,我就翻表。你只给50快就好了,多的算我的。按你说的那么走要50分钟,我带你这么走只要25分钟。最后,按我的路走,多走了4公里,快了25分钟,我只收了50块。乘客很高兴,省了10元钱左右。这4公里对我来说就是1块多钱的油钱。我相当于用1元多钱买了25分钟。我刚才说了,我一小时的成本34.5块,我多合算啊!”
“在大众公司,一般一个司机3、4千,拿回家。做的好的大概5千左右。顶级的司机大概每月能有7000。全大众2万个司机,大概只有2-3个司机,万里挑一,每月能拿到8000以上。我就是这2-3个人中间的一个。而且很稳定,基本不会大的波动。”
太强了!到此为止,我越来越佩服这个出租车司机。
“我常常说我是一个快乐的车夫。有人说,你是因为赚的钱多,所以当然快乐。我对他们说,你们正好错了。是因为我有快乐、积极的心态,所以赚的钱多。”
说的多好啊!
“要懂得体味工作带给你的美。堵在人民广场的时候,很多司机抱怨,又堵车了!真是倒霉。千万不要这样,用心体会一下这个城市的美,外面有很多漂亮的女孩子经过,非常现代的高楼大厦,虽然买不起,但是却可以用欣赏的眼光去享受。开车去机场,看着两边的绿色,冬天是白色的,多美啊。再看看里程表,100多了,就更美了!每一样工作都有她美丽的地方,我们要懂得从工作中体会这种美丽。”
“我10年前是强生公司的总教练。8年前在公司作过三个不同部门的部门经理。后来我不干了,一个月就3、5千块,没意思。就主动来做司机。我愿意做一个快乐的车夫。哈哈哈哈。”
到了机场,我给他留了一张名片,说:“你有没有兴趣这个星期五,到我办公室,给微软的员工讲一讲你怎么开出租车的?你就当打着表,60公里一小时,你讲多久,我就付你多少钱。给我电话。”
我迫不及待的在飞机上记录下他这堂生动的MBA课。
工作是生存的饭碗,考研是投资未来的手段,辞职考研是砸掉饭碗来换取改变人生命运的可能机会。做任何事都要考虑成本,自费读研总计十几万元的经济成本(包括隐型成本)的巨大压力让你在走每一步时都会怀疑自己是否踩的塌实。而由此带来的精神压力曾让无数研友出师未捷身先死。总结一下很多前辈的经验以及自己的教训,这里为每一个在辞职与否的天堂地狱间徘徊的兄弟们提出几点忠告:
1.关于动机,所有的应届生都把我们辞职的看作最大的对手,原因是背水一战的勇气和置之死地而后生的气概让多少漫不经心的他们感到畏惧。或者说是哀兵必胜的缘故吧。不是说动机一定要有多“纯”,在我看来,无论是在原单位混不下去也好,在公司里感到前途无望也好,毕业几年后迫切想回到学校再读几年书也好……只要你有强烈的改变目前生活状态的欲望以及在还看的进书的年纪在搏一把的勇气,我想,你已经成功了一半。
2.所报的学校需量力而行,不是每个人都能象乔丹一样飞身扣蓝,樱木花道的小人物上篮也是2分。
3.你说你很有激情,这一点我们坚信。有激情的人们多的是,关键是这种激情能保持多久。那个部队叫石光荣老爷子用行动告诉了我们坚忍不拔才是通往成功的人间正道。面对辞职的压力请时刻挺住。
4.对于所报专业是否有钱途,网友的争论希望不要引起你情绪的波动,记住考试和入行还有很长的路要走。无聊的单身汉有时会美好的遐想未来的儿子可能会是个省长,我们对此的建议是找到孩子他妈才是当务之及。法硕版有个著名ID的签名就是“有前途的不是专业,而是人。”
5.不要真的以为研究生的毕业证能彻底改变一个人的命运,我们只是认为增大了这种可能性而已。考试只是考试,命运不是一张证书就能轻易改变的。投资也有亏钱的风险,看看股市就知道了。也许日后你的工作甚至没有现在的好,但我相信你的人生境界已经上了一层台阶
6.订个学习计划,虽然你我都知道这东西和那些戒烟计划,减肥计划一样,效用并不大,但至少你还能知道考试在几月进行。
7.翻烂教材,所有的教材使用价值也就6个月。考完后80%的可能会被别的要求上进的同志借走,而90%的可能这些同志不会真正看一遍,因为他们也是很忙的。
8.养成听课记笔记的好习惯,网上会有热心的网友张贴笔记,不过雷峰叔叔自己也要考试,我们没权利让他们坚持到底。
9.结交一些考友,炎热的夏天,很多人都有听课瞌睡的习惯。但重要的是我们睡醒了还会继续战斗,而你的那些考友们至少会让你补笔记,或干脆拿针扎醒你。另有他们在,你临阵脱逃的可能性会大幅下降。
10.过多的辅导书浪费的不是你的金钱,而是你的时间。当然如果你非要看到身边堆满了辅导教材,才能心满意足的看的进书的话。那还是去买吧,我们知道每个人都有不同的癖好。
11.翻看历年考卷往往会觉得容易,当然等你拿到考卷会发现那是一种错觉。有时看70年代的英国足总杯录象,我都怀疑他们是否干得过申花,当然这也是种错觉。
12.为了考试你不得不放弃一些娱乐活动,这的确应该。光荣转业老兵们都是这样过来的。今年的世青赛,建议只看看中国队的比赛吧,这应该是一种爱国行为。
13.进考场建议带叠面巾纸,看到综合题的一刹那,很多同志会出一身冷汗,很正常,这是一种生理现象。但切记别将汗水留在考卷上,我们已经付了考务费,没必要再白送我们的汗水。
14.考完之后对答案。有必要吗?如果很多人和你答案一样,可能你还是错的,80%的同志是明年还会回来的;当然更多的可能是答案五花八门,对此我们的解释是不幸的家庭各有各的不幸。
15.记住别人的经验永远是别人的,各人情况不同而已。我朋友的女友看了不下10遍《逆风飞扬》,最后她还是换了个有钱的男友,我们认为这可能更适合她飞扬,毕竟可以顺风,干吗还要逆风。
一、读大学,究竟读什么?
大学生和非大学生最主要的区别绝对不在于是否掌握了一门专业技能……一个经过独立思考而坚持错误观点的人比一个不假思索而接受正确观点的人更值得肯定……草木可以在校园年复一年地生长,而我们却注定要很快被另外一群人替代……尽管每次网到鱼的不过是一个网眼,但要想捕到鱼,就必须要编织一张网……
二、人生规划:三岔路口的抉择
不走弯路就是捷径……仕途,商界,学术。在这人生的三岔路口,你将何去何从……与其跟一百个人去竞争五个职位,不如跟一个人去竞争一个职位……学术精神天然的应当与尘嚣和喧哗保持足够的距离……商场不忌讳任何神话。你也完全可能成为下一个传奇……
三、专业无冷热,学校无高低
没有哪个用人单位会认为你代表了你的学校或者你的专业……既然是概率,就存在不止一种可能性……如果是选择学术,冷门专业比热门专业更容易获得成就……跨专业几乎早已成为一种流行一种时尚……大学之间的实力之争到了考研考场和人才市场原来是那样的微不足道……
四、不可一业不专,不可只专一业
千招会,不如一招熟……十个百分之十并不是百分之百,而是零……在这个现实的社会,真正实现个人价值才是最体面最有面子最有尊严的事情……要想知道需要学什么,最好的方式就是留意招聘信息……很多专业因为不具备专长的有效性,所以成为了屠龙之术……为什么不将“买一送一”的促销思维运用到求职应聘的过程中来呢……
五、不逃课的学生不是好学生
什么课都不逃,跟什么课都逃掉没什么两样……读大学,关键是学会思考问题的方法……逃课没有错,但是不要逃错课……英语角绝对不是学英语的地方……为了英语丢了专业,那就舍本逐末了……招聘单位是用人才的地方,而不是培养人才的地方……既要逃课,又要让老师给高分……
六、勤工俭学的辩证法
对于贫困生来说,首先要做的不是挣钱,而是省钱……大部分女生将电脑当成了影碟机,大部分男生将电脑当成了游戏机……在这个处女膜都可以随意伪造的年代,还有什么值得轻易相信……态度决定一切……当学习下降到次要的地位,大学生就只能说是兼职的学生了……
七、做事不如做人,人脉决定成败
学问好不如做事好,做事好不如做人好……会说话,就能减少奋斗三十年……一个人有多少钱并不是指他拥有多少钱的所有权,而是指他拥有多少钱的使用权……一个人赚的钱,12.5%是靠自身的知识,87.5%则来自人脉关系……三十岁以前靠专业赚钱,三十岁以后拿人脉赚钱……你和世界上的任何一个人之间只隔着四个人……
八、互联网:倚天剑与达摩克利斯之剑
花两个小时就写出一篇天衣无缝的优秀毕业论文……在互联网领域创业的技术门槛并不高,关键的是市场眼光和营销能力……轻舞飞扬已经红颜薄命了,而痞子蔡却继续跟别的女孩发生着一次又一次的亲密接触……很多大学生的网友遍布祖国大江南北,可他们却从未主动向周围的人说一声:你好,我们可以聊聊吗……
九、恋爱:花开堪折方须折
爱情是不期而至的,可以期待,但不可以制造……越是寂寞,越要警惕爱情……既然单身是可耻的,那西门庆是不是应该被评为宋朝十大杰出青年……花开堪折方须折,莫让鲜花败残枝……一个有一万块钱的人为你花掉一百元,你只占了他的百分之一;而一个只有十块钱的人为你花掉十块,你就成了他的全部……
十、性:上帝死了,众神在堕落
爱要说,爱要做……我只有在肉体一下一下的撞击中才感到快乐。经过之后,将是更大的寂寞更大的空虚……为何要让别人的虚荣成为对自己的伤害……当她机械地躺在床上张开双腿,她的父母正在憧憬着女儿的未来……一朝春尽红颜老,花落人亡两不知……
十一、考研:痛苦的安乐死
没有比浪费青春更失败的事情了……研究生扩招的速度是30%,也就意味着硕士学历贬值的速度是30%……同样是付出三年的努力,你可以让E1的值增加1,也可以让E2的值增加2甚至增加3……读完硕士或博士并不等于工作能力更强……面对13.54万的成本,你还会毫不犹豫地投资读研究生吗……努力就会有结果,但不一定是好结果……
十二、留学:“海龟”变“海带”
月薪2500元的工作,居然引得三个“海归”硕士争相竞聘……对于某些专业而言,去美国留学和去埃塞俄比亚留学没什么两样……既然全世界的公司都想到中国的市场上来瓜分蛋糕,为什么中国人还要一门心思到国外去留学然后给外国人打工……
十三、非统招:养卑照样处优
她在中国信息产业界创下了几项纪录。她被称为中国的“打工皇后”。而她不过是一名自考大专生……要想把曾经输掉的东西赢回来,就必须把自己比别人少付出的努力补上来……非统招生不但要有一定的实力,而且必须掌握一定的技巧,做到扬长避短出奇制胜……路在脚下。好走,走好……
十四、毕业:十面埋伏的陷阱
母校不把自己当母亲,你又何必把自己当儿女……听辅导班不过是花钱买踏实……人才市场就是一个地雷阵……通过多种方式求职固然没有错,但是千万不要饥不择食……只要用人单位一说要你交钱,你掉头就走便是了……这年头立字尚且不足以为据,更何况一个口头约定……
十五、求职:做人不要太厚道
求职简历必须突出自己的核心竞争力……求职的时候大可不必像严守一那样“有一说一”……一个人说假话并不难,难的是把假话说到底,并且不露一丝破绽……在填写自己的特长时,一定要尽可能详细……一份求职简历只要用一张A4纸做个表格就足够了……面试其实是有规律的,每次面试的时候只要背标准答案就行了……
十六、骑一头能找千里马的驴
美国铁路两条铁轨之间的标准距离是4英尺8.5英寸,为什么呢?因为两匹马臀部之间的宽度是4英尺8.5英寸……垃圾是放错位置的人才……世界上最大的悲剧莫过于有太多的年轻人从来没有发现自己真正想做什么……中小型企业或许能够让你得到更充分的锻炼……从基层做起并不意味着可以从基层的每一个职位做起……要“钱途”,更要前途……
十七、写字楼政治:白领必修课
大公司是做人,小公司是做事……职员能否得到提升,很大程度不在于是否努力,而在于老板对你的赏识程度……公司的事情和秘密永远比你想象的还要复杂和深奥……在适当的时候装糊涂不但是必要的,而且是睿智的……就把你的同事当成一群你可以叫得出名字的陌生人好了……
十八、创业:29岁以前做富翁
瘦死的骆驼比马大……撑死胆大的,饿死胆小的……不再是“大鱼吃小鱼”,而是“快鱼吃慢鱼”……对于趋势的把握是一个创业者最重要的能力……高科技行业留给毕业生的空间已经很小……欲速则不达。在创业以前通过给别人打工而积累经验是非常必要的……市场永远比产品更重要……钱不够花,怎么办?第一,看菜吃饭;第二,借鸡生蛋……
伟大的伊拉克和她的人民以及全人类:
很多人都知道我是个忠诚、诚实、公正、拥有智慧和决断力的人,我关爱他人、珍视国家和人民的财富,事实上我还拥有博大的胸怀,包容所有分歧。
作为你们的领袖和兄弟,我从不向专制屈服,遵从民众的意愿,是引领人民的旗帜。这是你们对领袖的要求,也是未来你们的领袖需要具备的。
我将把灵魂献给真主安拉,如果他愿意,他将把我的灵魂带到天堂。我们需要耐心,由真主来决定如何对待那些不公正的国家。
不要忘记,真主已经让你们变成了友爱、宽容和兄弟般共存的典范。
我希望你们不要憎恨,因为憎恨会让人无法保持公正,会让你们变得盲目,关上思想的所有大门,无法做出正确判断。
我也希望你们要把侵略伊拉克的那些国家的决策者和人民区别对待,不要去怨恨那些国家的人民。
你们必须清楚,在这些国家里面也有人支持你们反抗侵略,他们中还有一部分人自愿为我(萨达姆)进行辩护。这些人向我道别时会止不住落泪。
你们要原谅那些有悔过之心的人们,无论他们现在是在伊拉克还是身在别处。
忠诚的伊拉克民众,我向你们道别。但是我会和慈悲的真主安拉在一起,他会来拯救那些虔诚寻求他的庇护的人,他不会让信徒们失望。真主是伟大的,我们的国家万岁,我们伟大的人民万岁!
伊拉克万岁,巴勒斯坦万岁,圣战和穆斯林游击队万岁!
萨达姆最后补充说明:
我写这封信是因为律师告诉我由侵略者建立的所谓的法庭将给我说些遗言的机会。
但法庭和主审法官之前却没有给我们机会来说任何的话,甚至是没有出示任何证据和相关解释就进行了宣判。
职业规划已深入人心,并成为一种新的商机。
年底已近,很多人都开始为自己进行明年的职业发展规划。职业顾问提醒,无论是刚出校门的学生,还是在职场拼杀多年的“老”人,在新一年度工作即将开始时,都需要制订一个明确的、切实可行的规划。
1.如何制订规划
职场人发展规划设计
一、分析角色加以定位。
白玲工作室首席咨询师白玲说,制订一个明确的实施计划,首先要明确给自己定位。一定要明确根据计划你要做什么;应该清楚地知道自己的职业环境,自己将会有怎样的发展机遇;不论未来是就业或者创业,都需要为自己的未来预留发展空间。
体现个人价值首先要明确个人价值。要清楚自己究竟想做什么,能做什么。所有的职场中人都应自问:我的定位是什么,核心竞争力有哪些,身价有多少?这些可以凭借自己的职业大环境来做评估,衡量并确定自己在该行业领域内的薪资价值。一般来说,衡量个人价值一方面根据自己的市场竞争力,另一方面则是市场需求。构成竞争力的基本要素是个人素质(包括:知识、经验、技能、阅历及解决问题、处理人际关系的能力)、工作绩效、职位高低、知名度等。
二、根据自己的特点和现实条件,确立自己的职业生涯目标。
某著名职业规划专家说,对于职场中人来说,工作有连续性和阶段性之分。很多人在每年的过渡中都不会对自己的职业发展有清醒而详细的规划统筹。制订规划时,应从职业发展前景和职业环境上着手。是否计划改变自己的职业环境?是否计划改善自己的职位?是否计划增长自己的薪资等等问题都应该纳入自己的考虑范围,并做出详细指标。
三、详细分解目标,制订可操作的短期目标与相应的教育或培训计划。
从小职员一跃成为老总的可能性实在微乎其微,那么制订能逐步实现的阶梯性可操作目标,无疑是每个职场人士最切实可行的职业规划方案。按季度进行时间划分是操作性最为便利的方式。同时要注意,制订细化目标是明智之举,但如果目标过于细碎,却并不利于职业前景发展的顺利操作。因为不可预知因素和其他职场上的旁枝琐节会打乱自己的发展计划。
四、根据个人需要和现实变化,不断调整职业发展目标与计划。
职场上常说,计划赶不上变化。对于自己碰到的问题和环境,需要及时调整发展规划,一成不变的发展计划有时形同虚设。
求职者规划设计
前程无忧高级副总裁简思怀介绍说,对于明年计划找工作的人来说,制订可行的发展规划是必要的。
一、直接瞄准招聘公司,从专业的招聘网站、报纸等媒体上挑选出那些适合自己的职位和企业。
二、告诉熟人自己的职业计划,让他?她?们帮助自己认识相关的人,并通过他们了解现存的空缺职位。如果是刚从学校毕业,或者即将毕业,尽可能与自己的老师探讨未来之路,相信没有谁比导师更了解你的能力、志向和兴趣。
三、不要轻视小公司。大部分空缺职位来自于规模小、但增长迅速的公司,尽管大公司通常在招聘员工时会大造声势,愿意花大钱,但是去小公司而且是前景不错的小公司求职,成功率要高得多,个人发挥余地也相对大得多。所以,对正在迅速壮大中的小公司特别关注一点,它们不端架子,你也容易与公司高层接触。
四、如果是面临求职的人,可以找一个求职伙伴。艰难、孤独的求职过程,很容易让人感到失望、丧气,你必须面对“被拒绝”的窘境。但是,伙伴间可以互相鼓励、安慰。
五、设计在线简历。求职者可以借助网络了解公司、行业现状,一则可以确定自己的求职方向,二则了解一下行业、公司的文化特色、用人要求。另外,无论个人目前是否急于换工作,紧跟行业的发展步伐在任何时候都是必需的。
六、珍惜HR经理向你发出的每一次信息,让用人方对自己留下好印象。求职者甚至可以事先准备一份提纲,设想人事经理会对简历中的哪一部分感兴趣,把想要表达的内容有条理地罗列下来。
七、总结挫败的原因。尽管大多数时候人事经理不会告知落选的原因,但是自己还得不断去反省自己的求职过程。聪明的办法是替自己设计多套求职方案,每一套方案有不同的侧重。在求职的过程中,许多意料外的小事会令你猝不及防,所以好的求职方法有时比自身能力还要重要。
八、做份找工作的预算。求职的过程需要在收集招聘信息、车程甚至在职业咨询和着装上有所花费,所以,特别是你现在还未找到工作时,“算计”一下,手头的钱能够支持自己搜索工作多久。
毕业生职业规划设计
告别校园走向社会,第一步就是为自己的职业生涯作一个科学合理的规划。北京高校毕业生就业指导中心主任任占忠指出,职业生涯设计应结合主客观条件,遵循四个步骤:
一、确定志向。有了明确的职业发展方向是毕业生走向社会就业的第一要素。明确方向也是事业成功的基本前提。确定自己想要什么,然后沿着这个方向去努力。
二、准确自我评估和分析客观条件。毕业生职业规划中,进行准确的自我定位非常重要。这一工作其实并不是已经面对“临门一脚”的毕业生才应考虑的问题,所有在校大学生都应该注重这方面的观察和总结。对自己的评估应包括:兴趣、性格、技能、特长、思维方式等,要将自我认识和他人评价相结合。外部要分析社会环境。各种职业环境和组织环境,应注意环境条件的特点、发展变化情况、自己与环境的关系、环境对自己有利与不利的因素等等。只有调整好自身条件与客观条件的接洽度,才能在职业发展规划中避害趋利,使职业生涯规划更具实际意义。
三、职业目标制订要合理。从目前的就业环境来看,选择职业发展目标时,切忌贪高贪快。要保证目标适中,同时也不可过高或过低,并将长期目标和短期目标结合起来,通过不断实现短期目标最终实现长远目标。
四、制定行动计划、考核措施,并进行评估、回馈和调整。确定了职业发展目标后,要通过一系列发展规划来确保目标实现。职业生涯发展中,会经常发生变化,考虑到影响职业生涯规划的因素很多,对职业生涯设计的评估与修订也很必要。修订的内容可以包括职业的重新选择、职业生涯路线的重新选择、人生目标的修正、实施措施与计划的变更等。
2.如何落实规划
制订好一系列的职业发展规划后,如何将其最终落实是每个规划制订者所必须考虑并面对的一个问题。做一个好的计划若没有实施上的细则,就无法保证计划顺利进行。
角色分析
当一个初步的职业规划方案已经成型时,如果制订者目前已在一个单位工作,那么,对他来说进一步的提升非常重要。首先要做的则是进行角色分析。反思一下这个职业环境对个人的要求和期望是什么,如何使自己在单位中脱颖而出。
大部分人在长期的工作中趋于麻木,对自己的角色并不清晰。但是,就像任何产品在市场中要有其特色的定位和卖点一样,在职者必须让自己有一些过人之处,让自己的价值和成绩得以体现并受到认可。
应对职场变数
根据可锐职业顾问客户服务中心客户情况调查,12月职场的活跃,肯定会带来大量的人才流动。职业人在这个变化的职场中,如何保证自己始终顺风而行是大家关注的焦点。当职业人处在变数,也就是“逆风”状态时该如何表现,将是职业人能否在明年及更长远的未来有足够发展原动力的关键。
面对变数,并会对其产生反应的职业人群往往是一些中层人士。他们会因为暂时的工作稳定性,对职场变化(尤其是职场价值体系变化)的敏感度不断降低。结果就是许多职业人在应对职场变化时,缺乏足够应变能力而造成职业发展困境的出现。
有些人遭遇薪资“封顶”,职业价值却处于下跌状态,发展下去危机四伏;另外更多的人对目前职业状况基本满意,但是不能确定下一阶段该如何进一步发展,找什么样的平台更加适合自己,于是在犹豫和害怕间陷入了职业停滞状态。据介绍,可锐职业顾问的咨询案例中有40%的职业人就是因为无法明确适合的职位目标,又在跳槽中遭遇过滑铁卢,最后不敢面对职场再竞争导致职业停滞的结果。
应对职场纷繁信息和变动选择的成功法则就是必须建立有效的信息整理、分析和筛选系统,再结合自身竞争力合理规划职业生涯。这样才能在职业发展过程中凭借良好的职场敏感度达到职业成功的彼岸。
一、
没有雄心壮志的网站是不可能成为一流的;没有明确目标及相应实施计划的雄心是空洞无用的。明确的目标以明确的定位为前提,在明确成为中国生命科学最大的商务科技综合服务网站的定位之后,XXXX应将宏大的目标落实到计划中,坚定有效地推进实施这个计划,直至达到所期望的效果。
在没有同类网站竞争的前提下,XXXX确实已经成为中国第一,但这个第一是脆弱经不起考验的。一旦有后来者努力追赶,特别是有实业背景的大资金介入,我们一年来的努力会很快被超越,追赶的时间只需半年甚至更短时间。所以,我们不能沉醉于自己吹出来的第一,一定要有紧迫感和危机感,要看到自己的敌人。而目前最大的敌人就是我们自己。
二、
目标、计划和管理密不可分。目标是计划的方向,计划是目标的落实,管理则是保证计划有效实施的手段。因此加强管理是首先要解决的问题。网络公司一般高素质人才较多,任务又多为软指标,如果不从体制上实行有效的管理,则很容易形成"君子国"的局面:谦让客气,互不得罪,顾及面子,赏罚不明。大家凭良心做事,合得来就合,合不来就散伙。缺乏内部竞争和外部压力,也缺乏可以比较的评判指标。这样的团队,这样的企业表面看来一团和气,各自努力,其实是没有竞争力的,经不起外面的风浪。所以一定要在内部建立起一种良好的机制,从外面引入竞争的压力,才能有效地调动每一位员工的积极性,最大限度地发掘内部潜力,形成最大合力向前发展。
三、
管理最简单最关键的一点就是赏罚分明。领导的赏识,薪金的提高,同事的赞誉,自我价值的体现,工作目的的实现,个人要求的满足,能力的充分发挥,意见和建议得到尊重等等,这些都是每一位员工所体会的"赏",反之就是"罚"。管理的艺术就是要巧妙运用这两个手段,引导全体员工朝向目标努力,减少内部的消极因素。不能把管理片面地理解为做太平官,当和事佬,追求表面气氛和谐融洽。也不能想救火队长一样被动应付突发事件。判断管理成功有效与否的唯一标准是能否有效组织资源实现计划和达到目标。所谓资源不仅指资金,更重要的是时间。也就是说能够在最短的时间内达到目的的管理是最有效的管理,可以不择手段和不惜代价。不能及时实现目标的管理是失败的管理,用什么理由都是解释不过去的。管理就是要推进计划落实。因此,凡是有利于计划完成目标实现的,就应该奖励;凡是不利于计划完成目标实现的,就应该惩罚,这是一个判别是非的基本尺度。
四、
采取内部评议制度,就是为了使管理更客观更全面,为企业领导提供重要的考评依据,防止因为领导的了解情况的片面或局限、上下沟通机制不健全、个人性格和表现能力的差异导致不公正的评判,影响员工的积极性和创造性。特别是工作软指标比较多的情况下,互相评议比任何其他方式都显得简单、公正和有效。有道是群众的眼睛是雪亮的,又有"公道自在人心"之说,没有比同事更加彼此了解的了。不怕不识货,就怕货比货。对员工的评价不能等待长期的观察,或者依靠偶然的事件,或者人走了再"盖棺定论"。这样公司的损失就很大了。内部评议就是为了创造一种良好机制,促进协作竞争,相互督促。 有效的合作精神不应该建立在道德品质和个人感情的基础上,更不能靠庸俗关系学来维持。真正的合作精神必须产生于完成工作实现目标的需要。
民主的氛围可以保证上情下达,下情上达。建立公告板是一个好办法,可以让大家畅所欲言,让领导了解情况。 根据实际情况建立各个责任区,指定第一第二责任人,划定各自责任范围,明确责任和具体任务,可以增强责任心,提高荣誉感,防止互相推诿扯皮。也使内部评议更符合实际。
强调计划、评议和督查责任区,是使领导工作也处于接受公开监督的同等地位,保证管理的全面有效运转。这是现代企业管理中最有效的措施。
五、
客观的目标和切实的计划有赖于充分了解外面的发展情况。市场宣传部门要经常研究分析动态和敌情,及时反馈。反馈的意见应尽量减少个人主观因素,一定要有客观、公正、全面的数据和依据。比如同类网站的数量,发展现状,浏览量,注册人数,业绩报告,技术水平,资金背景等。要按时提交市场调研报告,这样才能保证知彼知己。企业的未来很大程度上取决于对对手的了解,对客户需求的分析,对世界潮流的掌握上。只有充分掌握了这些情况,我们的目标方向才真正是有的放矢,不会是想当然,闭门造车。
电子商务方面应建立一支理论和能力兼备的队伍,才能真正把握脉搏,创造先机。否则只能是邯郸学步,人云亦云,永远也不能超越自己的局限。因此要加强学习,提高我们自己的理论知识水平。这样,市场、宣传、商务方面行使监督权利就是有理有据的,有建设性的,而不是凭空臆想、泛无所指的信口开河。应尽力改进只会提意见,不会提建议的不良现象。
六、
目前XXXX的每日平均浏览量为300左右,注册人数1000多人次,每日平均增加10人左右。期刊(5月22日起)订阅人数为13人,每日平均增加1人。这样的效果还不及一般水平的个人网站。这个局面如果不尽快改观,生物引擎的生存发展将是个问题。一定要千方百计地提高浏览人数和注册人数,提高邮件订阅人数。这些数字是开展电子商务的基础。没有一定量的浏览人数和注册人数,电子商务根本无从谈起。这个简单的道理我们许多人至今都没有清醒的认识。反而在要不要增加页面内容,提高服务功能,扩大网站服务面,保持相当数量的稳定浏览人群这些起码的问题上模糊不清。 设想一个目标:在今年年底实现日均浏览人数1000以上,注册人数10000以上,邮件订阅2000以上。这个目标是相当保守的,即使达到也不过勉强及格而已。但按照目前的水平和速度,年底实现这个目标还相当困难。因此,一定要有紧迫感和危机感,要开动脑筋,提高效率,发掘各种创意和努力。特别要注重借助外部力量来快速发展自己,这是一条重要的思路和努力方向。
七、 目前的改版是今后快速发展的第一步,也是最基础的一步。改版后的框架不仅为吸收结合各种创意和力量提供了空间,更重要的是启动人们的想象力和创造性。改版问题解决得如此艰难缓慢是出人意料的,真正体现了我们观念混乱,组织软弱,效率低下。某些关键部位技术力量薄弱,思想和创意贫乏。在这种情况下,不能指望本次改版能一劳永逸地解决问题。所以,应考虑尽快完成改版,尽快进入维护提高阶段。在稳定目前现有栏目的基础上,重点改善和提高资源中心的各项服务功能,增强互动性。
八、 由此可见,全面提升XXXX是一项系统而艰巨的工程,单纯考不断改版,增设栏目,丰富内容不能根本解决问题。首先从管理入手,创造一种良好的内部机制,促进观念、思想和创意的新陈代谢,预防和消除沉浸于事务中导致灵感和触觉退化的现象。同时建立并完善内部考评监督制度,不然再好的创意都会流产,计划也形同虚设。
从现在起,我们应努力在外面寻求力量支持,寻找机会跳跃式发展。要鼓励大视野,大思路,大策划,大发展,不要局限于在枝枝节节的问题上纠缠不清。
我在技术岗位上工作已经有三十多年了,担任IT经理和CIO也已经超过了二十年。在这段时间里,我见过很多优秀的IT经理,其中甚至有一些是非常出色的。当然,工作表现不怎么样的IT经理也见过一些。 我认为,任何IT经理想要拥有成功的IT管理生涯,都必须具备十一个显著特征。尽管会有一些人认为具备这十一个特征还远远不够,也会有一些人觉得技术技能同样重要,凭借自己多年的职场经验,我仍然坚信这十一个显著特征对于今天的IT经理来说是最为重要的。
1、了解公司需求的能力
所有的IT经理都需要知道如何才能够了解公司的需求,因为这同自己的技术职责密切相关。不管自己在公司当中的位置如何,作为一名IT经理,你都有必要了解公司的真正需求,只有在此基础上才能做出“正确的事情”。太多的IT经理在制定“IT议程”之前都没有真正了解公司的目标和需求。迅速掌握了解公司技术需求的能力会让你了解自己更多的职责。如果IT经理在制定计划时由于缺乏了解公司需求的技能而“错过了公司的目标”,公司可能会蒙受数万美元的损失。了解公司的技术需求是IT经理职业生涯发展的一个重要组成部分。
2、制定前景规划的能力
想要成为一名优秀的IT经理,你必须要能够确定自己的目标,并且制定出前景规划,让你手下的员工了解你希望大家通过努力能够取得的成绩。想要成为一名优秀的IT经理,你要担负起自己的职责,领导整个小组向着目标前进。能够制定出计划并且清楚的向自己手下的员工说明的IT经理能够在工作中取得巨大的成功,因为你手下的员工会按照你的要求去做。清楚的向自己手下的员工说明自己的前景规划会让他们了解工作的中心,让他们了解你会带领他们向明确的目标前进。
3、制定计划的能力
一旦知道了自己想要实现的目标,成功的IT经理会知道如何去制定计划,以实现自己的目标。这意味着要正确估计眼前的形势,知道哪些事应该摆在优先的位置,制定出大胆而又切实可行的计划。对于一名想要取得“高度成功”的IT经理来说,制定计划是一个非常基础的步骤。但可惜有太多的IT经理没有能够做到这一点。在这种情况下,他们所取得的工作成绩要比公司有能力取得的成绩小的多。拥有制定计划的能力,通过计划的实行来实现公司的目标会让你承担更多的工作职责。事先制定计划表明你是一个积极主动而不是一个消极被动的人。
4、组建小组的能力
成功的IT经理知道组建一个有深度而又有技能的IT小组的重要性。既要知道如何改进现有的IT小组,又要知道如何白手起家,建立一个新的IT小组。我所见过的所有优秀的IT经理都有根据手头的工作任务建立一个有力的工作小组的能力,有正确预计未来工作需求以使整个小组做好应对新挑战的准备的能力。强有力的职业IT经理能够有效的组建自己手下的员工队伍。他们了解职业生涯的重要性,并且愿意把职业生涯的构建作为一种工具,来组建强有力的工作小组,更加独立的开展工作。
5、集中使用资源的能力
如果想要取得成功,IT经理要集中公司所有的IT工作人员、资金和技术资源,处理最关键最重要的事情。公司技术资源的使用要同公司的需求和目标保持同步,并且要通过多产有效的方式。任何有清楚的职业发展目标的IT经理都知道集中使用资源的重要性。
www.haiyijiao.com 海一角营销人网
6、贯彻“客户服务”理念的能力
对于任何IT公司来说,高水平的客户服务都是非常重要的。成功的IT经理会在员工的心目中贯彻一种客户优先的文化理念——不管是内部用户还是外部客户。优秀的IT经理知道自己职业生涯的存在和发展是由于客户需要他们提供的技术,支持他们提供的服务。这也正是成功的IT经理要同客户建立出色的关系的原因。
7、管理项目的能力
公司能够以可以预见的有效方式制定出项目计划是所有IT经理开展工作的基础。任何IT经理想要取得成功,都要对项目进行有效的管理。不管你的职业生涯发展方向如何,强有力的项目管理技能都能够增加你获得成功的机会。
8、应对变化、进行管理的能力
迅速的发展变化是技术的本质特征。每一个IT经理都要了解如何有效的应对变化、进行管理。不能有效的应对变化会影响IT经理职业生涯的发展。
9、领导员工并激发其工作热情的能力
如果工作热情得不到有效的激发,IT工作人员就无法在工作中发挥出自己最大的能量。成功的IT经理总是能够对员工进行强有力的领导并激发他们的工作热情。成功的管理人员知道如何发挥他人的潜质,这是一项非常重要的技能。
10、有效沟通的能力
成功的IT经理能够通过多种不同的方式同不同的员工进行沟通。通常,职业生涯的成功在很大的程度上依赖于有效的沟通技能。想要成为成功的IT经理,必须能够同各种人进行有效的沟通,既包括技术人员也包括非技术人员,要同高级经理交换对项目进展状况的看法。很多IT经理就是因为做不到这一点而极大的影响了自己的工作成绩。能够在自己的职业生涯中取得最大的成功的IT经理能够同所有人进行有效的沟通:员工、同行、外部客户、销售商和高层管理人员。
11、追踪并衡量工作表现的能力
确定目标并以此来衡量工作表现是非常重要的。成功的IT经理能够通过明确的方式来衡量小组的工作表现,并且通过反馈信息来改进工作表现。
总结
最好的IT经理,也就是那些拥有成功职业生涯的IT经理,拥有上面所提到的每一种技能,能够胜任自己在小组和整个公司中的工作任务。
当然,对于IT经理来说,想要在自己的职业生涯中取得更大的发展和成功,还需要掌握其他的一些技能,例如积极主动的工作以及同销售商进行成功的谈判。但是,如果仔细对成功IT职业生涯所需要的技能进行研究,上面所提到的十一点无疑是最最重要的。
想要在任何公司中打造成功的IT管理生涯都是一项重大的挑战,因为IT经理的角色总是处在不断的变化之中,并且要受到周围所有人的审视和挑剔。如果你能够在个人的职业生涯发展中学习并掌握上面所提到的十一项技能,你就能够取得更大的成功,也会感受到更多的职责。
摘要:
史蒂芬·柯维博士在《高效能人士的七个习惯》一书中阐述了高效能人事所需要具备的七个习惯:积极主动、以终为始、要事第一、双赢思维、知彼解己、统合综效、不断更新;这七个习惯无疑也是优秀项目经理所需要具备的。然而,从作者多年的工作经验来看,作为项目经理这样一个特殊的角色,除了需要具备上述七个习惯外,还需要具备如下与其职位息息相关的六个习惯:目标导向、全局思维、计划先行、关注重点、客户至上和注重团队。本文拟对这六个习惯进行剖析。
正文:
项目经理是项目的领头人,是项目团队灵魂型的人物。项目经理需要具备怎样的良好习惯方能更有助于团队、项目和自我的成功?史蒂芬柯维博士在《高效能人士的七个习惯》一书中阐述了高效能人事所需要具备的七个习惯:积极主动、以终为始、要事第一、双赢思维、知彼解己、统合综效、不断更新(关于这七个习惯的内涵本文不作描述,有兴趣的读者可以去参阅《高效能人士的七个习惯》这本书)。无疑,这七个习惯也是优秀项目经理所需要具备的。然而,从作者多年的工作经验来看,作为项目经理这样一个特殊的角色,除了需要具备上述七个习惯外,还需要具备如下与其职位息息相关的六个习惯:目标导向、全局思维、计划先行、关注重点、客户至上和注重团队。以下对这六个习惯进行剖析。
1、目标导向
做任何事情都需要有清晰的目标,没有目标就没有航向,这其实是很浅显的道理。惟有目标导向,项目才更容易成功。然而,我们有些项目经理在做项目时,会自觉不自觉地出现脱离项目目标的现象,甚至有些项目经理根本就没有弄清楚项目的具体目标。
为了弄清项目目标,我们在项目的启动阶段,需要认真分析《项目合同》、《项目章程》、《项目工作说明书》、《招标投标书》等文件,提炼出项目的真正目标,并随时关注目标有可能发生的变化,始终围绕目标开展工作。
2、全局思维
我们知道,项目的范围、进度、成本始终是项目的三重约束。如何在保证质量的前提下,较好地实现项目的范围、进度和成本这三大目标,是项目经理的一大挑战。因此这就需要项目经理具有全局的观念,学会全面分析项目,在采取行动或进行决策时不要顾此失彼。
当然,每个项目在范围、进度、成本这三大目标上都有不同的侧重,如何去“平衡”这三大目标并适当作出取舍,需要我们根据具体情况进行具体分析。
3、计划优先
“万事始于计划”,计划对于项目活动尤为重要,因为项目具有太多的不确定因素,没有计划将会导致束手无策。
我们知道,项目管理的五大过程组(启动、计划、执行、监控和收尾)一共44个过程,其中计划过程组就包括了21个过程(近一半),足见计划的重要性。有些项目经理以“计划没有变化快”为藉口而忽视甚至放弃做项目计划,实际上是极其错误的行为。我可以肯定地说,没有哪个项目是因为做了计划而失败,更没有哪个项目是因为没做计划而成功。
4、关注重点
我们知道,每个项目都有一条或多条关键路径,关键路径上的活动就一定是需要我们重点关注的工作(当然,非关键路径上的某些活动可能也是重点关注的对象),因为关键路径上的小小延误,对项目的影响可能都是巨大的甚至是致命的。因此,项目经理需要养成“关注重点”的习惯,千万不要“眉毛胡子一把抓”或“捡了芝麻丢了西瓜”。
5、客户至上
“客户是使项目价值得以体现的主体”。项目经理始终需要“心怀客户”,因为我们的项目只有得到了客户的认可,项目才能称得上是真正的成功。项目经理在领导项目建设时,千万不要心中只有“自我”的利益,而对客户提出的期望或要求不关心甚至不理睬,这种做法项目注定是要失败的。《项目管理知识体系指南》明确指出在处理项目冲突时需要采用有利于客户的原则,足以表明“客户至上”的重要性。
需用澄清的是,“客户至上”是需要我们从思想和行动上重视客户的利益和需求,但并不是说客户的任何要求我们都一定要去满足;用户的有些需求,我们完全可以从项目的全局出发,采用有理、有利、有节的原则来分析、响应和处理。
6、注重团队
“人是项目绩效的源泉”,项目经理需要注重人力资源的管理、注重项目团队的建设。
在项目团队的建设过程中,项目经理可以利用“作战室”之类的办公环境来营造一种积极、紧张的工作氛围。实践证明,团队成员的集中办公有利于培养团队的集体荣誉感和团队精神、增强团队的整体战斗力。另外,适当的团队活动也是非常必要的,因为合适的团队活动是团队成员疲惫的“轻松剂”、误解的“冰释良方”。再有,项目经理需要在富有激励作用的认可和奖励制度上下功夫、平时多一些时间和团队成员沟通和谈心等。
特别需要指出的是,在项目团队的建设过程中,项目经理需要多关注人、多关注细节。
项目经理是项目团队的舵手,优秀的项目经理往往能带出优秀的项目团队,进而成功地完成项目工作。怎么才能成为优秀的项目经理,我想我们不妨从上面所谈到的习惯开始,当某一天您已将这些习惯完全溶入到项目管理中时,您定会发现惊喜无处不在
“吃饭”是人维持自身生存的一种本能,有道是“吃相如人相”。日本著名心理学家涩谷昌三经过多年研究发现,一个人在吃饭这个本能行为中的种种不经意的表现的确可以深层次反映一个人的心理。
从找座位方式看性格
在带别人一起上餐厅的时候会环顾四周后找到空位,然后说:“坐那里吧!”带领大家就座的人,不仅判断力卓越,也极具自信,是会直接表达内心想法的人,但也容易流于独断而惹人厌。
带领着大家就座,却发现位子不够或是有他人先到,于是在店里四处徘徊重新寻找。有这种习惯的人判断力欠佳,且会做出错误判断,经常会出现小失败,不过却反而突显个人魅力,乐于配合他人,老实的性格受人欢迎。
总是跟在大家后面的人,是需要别人照顾或依赖心很强的人,凡事不会自己积极主动,却是配合周围人的举止而行动,是那种“不会在意细枝末节”、性格大方的人。
马上去问店员哪里还有空位的人,虽然做事会以合理化的方式往前迈进,不过会有以眼前结果(所有人都就位)为优先,而疏忽喜好与气氛等心理因素的倾向。也有不考虑别人意见与想法的一面。
从点菜方式看是否深思熟虑
速战速决点菜的人下决定速度快,但性子急,有想法太过天真、缺乏深思熟虑的一面,拥有领导者特质,但过于独断且不相信别人,有“凡事求快”、“不想落后于人”的竞争心。
犹豫不决,无法下决定的人太过在意别人,缺乏决断力,会因为胃口太大,对不同事物都转移焦点而迷失。
“跟大家一样”的人没有主见,总是左思右想对自己缺乏自信,跟别人步调一致,行动不积极。
会问别人要点什么的人做事很有要领,个性亲切,虽然计划周详,却不会有更深入的想法,与总是跟人点同样菜的人相同,是“同调性”很高的人,如果一边问别人,一边却点了跟对方不同的菜色,是不在乎他人而自行其道的人。
最后还是点了跟别人一样菜色的人遵从多数意见,希望与别人一样的倾向性很强,不会坚持己见,经常会因为配合别人而改变意见,是难以信赖的人,对自己所属的团体归属意识强烈,不喜欢离开集团或让集团产生混乱。
一次点了一大堆,“这个也要,那个也要”是心浮气躁的人,想法与需要非得直接表达才甘心,有点孩子气,不照顺序来,一次全包,浮躁的态度,可说是对失败的可能性缺乏慎重思考的人,也欠缺“随机应变”的弹性。
从吃法可了解是否在意别人言行
开始想吃别人菜肴的人,善变,无论如何也都无法得到满足感。对自己缺乏自信,经常在意别人的言行,是那种看到别人比自己优秀就会产生自卑感的类型。在人前一副开朗的模样,但一落单后就会想不开,自寻烦恼。
一道一道吃的人,将好几盘菜从最边缘开始照顺序吃的人,眼前该做的事情就会勇敢直前,一旦开始埋头苦干,就毫不在乎周围人的眼光。一件事没有做完,就绝不会心有旁鹜。这样的人可以发挥卓越的集中力,对同时进行多项工作就不拿手了。要他以宽广的视野看待事物,做出权衡的判断是很困难的。在个性上,也有一本正经与顽固的地方。
不管别人,马上吃完的人,极度自私、性急,自以为是,不想配合他人的步调,虽然能处理较具积极与活动性的工作,但都以自己步调来进行。就算造成别人困扰也不以为意,神经超粗;也有的人一旦下定决心,就绝不会动摇信念,虽然态度强硬,但对批评毫不在乎,仍贯彻始终。
从剩菜残留方式,发现个性
吃得乱七八糟的人性格粗枝大叶,任性;盘子干干净净的人做事有计划,一丝不苟;整齐地留下食物的人,在意别人的眼光,对别人很严格;即使只剩一口也不吃完的人,姑息自己,真率。
总结一下成功人士的共同特征。一是有使命感,想为这个民族这个国家做什么事情,所以才会自强不息。二是有责任感,所以愿意挑重担,愿意投入全部精力。三是有包容心,所以能博采众长,我们经常说刘邦和刘备的共同的长处就是善于用人,能够把一些人才团结起来。第四就是有忍耐性,能自甘寂寞,能坚持下去。我认为,前两点是《易经》中乾卦的品德,所以,乾卦的爻辞上说“天行键,君子自强不息!”后两点则是《易经》中坤卦的品德,所以,坤卦的爻辞上说“地势坤,君子厚德载物!”“自强不息,厚德载物”也是我们国家最知名学府——清华大学的校训。
最后我引用国学大师王国维的一段话作为结束语。他说:“古今之成大事业、大学问者,必经过三种之境界:昨夜西风凋碧树。独上高楼,望尽天涯路。此第一境也。”
因为你的人生跟别人不一样,所以你注定了要独上高楼。你在任何一个企业的经历,都是你人生中不可或缺的一段,都是你自己独有的,没有人跟你一样,所以孤独寂寞是必然的,把命运掌握在自己手里,根据自己的人生目标有选择的去做一些事情。
“衣带渐宽终不悔,为伊消得人憔悴。此第二境也。”
你要坚持不懈地朝着你的目标去奋斗,而不是轻易搞两下,就觉得这个井下面没有水,要另找一处挖。
“众里寻他千百度,蓦然回首,那人忽在灯火阑珊处。”
“那人”就是你早早给你画的像,就是你理想中的那个自己,你在一路上独上高楼,耐得住寂寞,坚定信念,不断奋斗,总会有一天,你蓦然回首,看到镜中的自己,你觉得镜子中的这个人还是有点影响力,有点作为的,那时候你就成功了。
朋友们,我觉得今天的社会给我们每一位提供了很广阔的施展自己才能的空间,六七十年代,七八十年代,甚至九十年代初期毕业的人,都是定向分配,要找一份好的工作没有这么的容易!而今天的职场,供你选择的地方很多,机会多得让你眼花缭乱,关键的问题是,你自己是不是一个真正的人才,如果你坚信自己是一个真正的人才,那么,我觉得你也应该坚信,你一定能找到一个好的平台,在这个平台上施展自己,磨练自己,丰富自己,成就自己!把你的人生做成一个令人骄傲得、完美的项目!
退一步讲,如果你没有太大的理想,至少可以沿着我讲的思路去部分尝试,每多一点点尝试,你的成就便可能大那么一点点。所以我讲这些话的时候,没有觉得自己是说教,我觉得我很真诚,因为我信我所讲的,讲我所信的。要把自己铸造成器,我们都还有很长很长的路要走,但是我坚信,我讲的这些是成功的道和法,而不是术。
坚定信念,一定能成功!谢谢大家!
我想,在座的各位,如果你不想碌碌无为地了此残生,如果你没有从政的机会,那么大约你有三条路可走。
第一条路当专家。
沿着你的专业走下去成为某一领域有名的专家、学者,如果你选择这条路,我送你一句话,就是“禁忌之处显风骨”。
第二条路,做职业经理人,当管理者,做大公司的CEO。我也送一句话,叫“高天之外看春秋”。
第三条路就是干脆自己创业当老板。我也送你一句话,是“风雨之后见彩虹”!
有时候一个人可以兼走两条,或者三条都走,但实际上兼走三条成功的人还是很少,但是兼走两条的成功的人却是经常的。
首先,要当专家,就要做到“禁忌之处显风骨”。禁忌之处是不毛之地,别人不敢碰的难题。这个项目别人搞不定,你去搞,这叫去禁忌之处,如果你搞成功了,就显示了你的风骨。所以,要做专家,第一个品质就是敢于迎接挑战、敢于迎难而上,为常人所为之不敢为!当然你也要有一些专业基础,不是傻胆大。常见的一些人,第一次做项目根本找不到做项目的感觉,稀里糊涂应付,到了第二次的时候,努力一下勉强能应付下来,做第三个项目的时候,就觉得做项目没什么意思,乏味了,不想做了,这样的人不具备这样的素质。
所以,专家能够在看似枯燥、重复的事情中找到兴趣点,精益求精,追求尽善尽美!对自己感兴趣的或发现规律的事情,不厌其烦的重复去做,熟能生巧,最后做成专家。
有的员工找我谈话说:“现在老把我放在项目上,一个项目做完就进下一个项目,老是重复同样的工作,觉得这个工作枯燥乏味了,不想干了。”
我就问他,你觉得做这样的事情是无谓的重复?这是对生命的浪费吗?对你来说已经没有任何挑战性了吗?那么,这个领域的专家中,怎么就没有听到你的名字?说明你还没有作到专家的地步,做到专家的人还没有厌倦你就厌倦了,那说明你凡事不能深入进去,浅尝辄止,不具备做专家的潜质。凡是感觉自己工作是事务性的简单重复的人,我觉得你应该好好反思一下,你是不是你投入不够,留心不够,视野太窄,缺乏总结,你将来很有可能最终成为有经历而没有经验的人。这时候你需就要调整一下心态,第一项目和第二项目看似一样,但是你在这个过程中投入的注意力和观察点不一样,项目经理做第一个项目能够勉强应付,第二个就基本能独立做下来,做第三个的时候才能有一些自己的创新思维,体现自己的风格。第四个、第五个项目你可能更加从容…… 到第N个,你会做到近乎完美设计,随心所欲地驾驭。然后,若干个以后,你会达到艺术的境界,甚至到了出神入化的境界。就算工作是重复的,人在工作中的表现,和人在工作中所投入的注意力也是可以不重复的。
我想我们的黄教授,已经把他的工作做到艺术的境界,他能把项目做到完美。今天站在讲台上,我告诉大家我的一个爱好就是做演讲,我曾经把我的一个PPT讲了上百遍,没有一次感到厌倦,我每次都有新的体会和收获,我的内容都会得到丰富和改进,所以我今天站在这里演讲才非常从容。这就是作为专家的出路,你必须往这个方向发展,否则你就会感觉工作缺乏激情,工作是一种累赘。
还有惰于思考,人浮于事,总打算把一些事情应付过去人,是成不了专家的。这样的话,总有一天,你会因为你的应付而悔恨,你应付多了,体会就少了,收获就少了,一份投入就一定有一份回报,尽管这回报很多时候不是以货币形式体现出来的。
想走专家的路的话就要在某一领域有量的积累,才会有质的变化,可惜,很多人等不到足够的量的积累,就改弦易辙了。这个漫画的题目是《这里面没有水,另找一处挖》,我印象中是1983年的高考作文的题,你千万不要拿你的生命做赌注去到处挖井。
量的积累符合蝴蝶效应。也就是说,在初期的时候,你一次努力的没有成果,两次努力的没有成果,多次努力收效不大,但只要坚持下去,当你积累达到一定的程度的时候,会突然跳跃,有一个爆发!空气动力学对蝴蝶效应有专门的迭代函数来解释。
假如你不想做专家,你想做经理人。我现在就是往经理人的路上走。你就要努力做到“高天之外看春秋”。
什么叫“高天之外”。就是站在高处看嘛。就是要换位思考,多角度看问题,员工站在经理的高度考虑问题,就有可能成长成经理。
拿破仑讲的“不想当将军的士兵不是好士兵。”当你打工的时候,你只知道埋头干自己的本职工作,视野太窄,不为上司考虑,不为其他同事考虑,那么,你永远不会有太大的全局观,职业经理人的路对你一定是堵死的。
做经理人需要从I型人转成T型人,T型人需要有组织能力和影响能力。组织能力体现在依靠团队的力量做成一件事,要有影响力让大家能向你看齐,还需要涉猎面广,外行管内行容易孳生官僚主义。要成为通才,还要愿意承担份外的工作,体验不同的角色, 一件事情你可干可不干,要是选择了干,那么你的涉猎面就宽了一点点,经验和体会就多了一点点,影响力也大了一点点。如果你这一生没干过几件自己都很佩服自己的事情,恐怕你也不会有太大的成就。
《菜根谭》里讲了:“立身不高一步立,如尘里振衣,泥中濯足,如何超达?”如果立身不高,就像在尘土里弹衣服上的灰,在泥巴里面洗脚,你永远都跳不出那个泥潭。立身不高,有时候跟见识太少有关系,就像山区里放羊的孩子,问他为什么放羊?放羊是为了取媳妇,为什么要娶媳妇,娶媳妇是为了生孩子,为什么生孩子?生孩子是为了放羊,这是因为没见过山外面更精彩的世界,立身没办法高!今天能来这里参会的人,应该还是有点见识的,还是要立身高一些的。
如果你想做老板,有两种情况,第一种是毕业不久就扭头出去做老板,现在工商注册非常容易,拿点注册资金你就能成为老板。你一旦成为老板,你就知道开门七件事,柴米油盐酱醋茶样样都不容易。我们公司有些员工出去创业,我曾经给他们做了一个三五个人年收入一百万的损益表,给他算完这笔帐,他就放弃创业的想法,因为没有充分准备,没有长远设计的创业实在不是想象的那么容易,很艰难。
我已经大学毕业十好几年了,前不久,碰到我的一个同学,他是一毕业就卖兼容机去了,那时候生意好做,也赚了一点钱,但十几年来他的公司一直没有发展壮大,他发现他的职业发展无路可走了,公司年年都做二三百万,只能勉强维持,搞下去看不到发展的希望,不搞又没饭吃,就只能年复一年惨淡经营,想关了公司去打工,发觉他这样的职业经历在职场上也缺乏竞争力。
所以,有的人一毕业找一个小公司打工,然后在公司体验了几个月,就看到天花板了,感觉开公司太容易了!觉得老板的水平不过如此,他也可以去开公司,于是他也充其量开一个能勉强维持的小公司,因为勉强维持的小公司说白了跟卖鞋的小贩没有什么太大的区别。所以我认为优秀的公司培养优秀的经理人,差的公司培养很多小老板。当你在一个很优秀的公司工作,你始终觉得自己欠缺的东西很多,很多业务需要你去学习。当你有了多年职业经理人的经历之后,再出来当老板,你就可以从容应对很多复杂的情况,你下面的员工才会觉得你这个老板很不错,能把事情做大,跟着你能学不少东西。
创业要有自己的事业理论,创业的目的是为社会做贡献,而不是投机、挣钱。微软的事业理论是让每个办公室和每个家庭的桌上都摆上一台电脑,而且每台电脑都用微软的产品。微软实现了他的理想,为时代进步做了卓越的贡献,也因此,时代成就了微软。企业的使命就是找机会服务于这个社会,推动社会某一领域发展进步,而盈利只是推动了社会发展进步,为社会做出贡献的附属品,是标不是本。尽管大家创业的出发点都是为自己考虑的,但客观上都是服务于社会,为社会做贡献,从而在社会的认可中得到应有的回报,所以,别老想这个事情赚钱,那个事情赚钱,而要想到这个事情社会上有需求,我做这个能满足社会需求,所以我才去做。
做老板更还要有自己的影响力,能知人善用,对内能领导和团结一批真正的人才,在外你还要有好的人脉,关系也是第一生产力。还要具备公司运作管理的管控能力,能够有效控制你的公司和员工,我认为,人生最大的悲哀是为自己不能控制的事情负责任,如果没有驾驭公司资源的能力,你可能就是这种悲哀者。
当你具备这些能力和素质之后,还要够抓住一个为社会的服务机会,毕竟想为社会做出贡献的人太多了,找一个能做的事也不容易。仅凭一腔热情,盲目创业,创业的目的就是为了赚钱,搞投机,我想是没有多大出路的。
做老板就要“风雨之后见彩虹”!你在经理岗位上经历了很多的事情,经历了很多的风雨,才能攒一点企业运作和管理的经验,你大概才知道如何当老板,才能有更大的成功把握。这是第二种创业形式
人生最重要的项目是什么?是把自己铸造成器。1929年,胡适先生在当时的中国公学第十八年级毕业典礼上讲过:“易卜生说:‘你的最大责任是把你这块材料铸造成器。’学问便是铸器的工具。抛弃了学问便是毁了你自己。再会了!你们的母校眼睁睁地要看你们十年之后成什么器。” 我非常喜欢这段话。的确,不管你在学校、在职场,还是自己创业,你毕生最重要的项目就是把自己铸造成器。我自我感觉今天还没有把自己铸造成器,所以我下面的观点跟大家共勉。
凡是要做成一件事情,先要找出这件事情的关键成功要素,什么叫关键成功要素(Critical Success Factors, CSF’s)?就是成功的充分必要条件。换句话说,如果你具备了这几个要素,你的成功将成为必然,翻过来,如果你要成功,你必须具备这几个要素。我归纳了一下,一个人要取得人生的成功,有五个关键成功要素,第一个是要有目标、第二个是要有执行力、第三个是善于权变、第四个是有影响力,第五个是善于总结。
我先说第一个,目标。疼痛和憧憬共同构成你的需求,因为你不满足现状,所以你有“疼痛”感,想改变他,因为你憧憬未来,所以你对自己的未来有个愿景设计,这两者就是你要实现的需求,是目标,人生目标是你对自己、对家人、对朋友、对社会、对上帝的一个承诺,目标是成功的种子,人生没有目标就麻烦了。
为什么呢?心理学认为人和动物的一个最大区别是人有思维,人能够有目的地自我演进,就是说,人对自己将来成为什么样的人,是有设计或者有想法的。比方说,小小的小孩,他就设想自己长大了要当警察、要当科学家、要开飞机等等,这是小孩对自己人生的设计。那么,一只小花狗,它长大了要成为什么样的一只狗,恐怕它不知道。灵长类的人与动物最大的区别就是人从小就能给自己圈定一个目标,或者说划一个方向,然后朝着这个目标去演进,这也是人类为什么能够主宰万物主要原因。大家看这个图片上这个人,很有意思,到处找自己的脑袋,因为他迷失自我,不知道自己应该是一个什么样的人,这就叫盲目演进,你也是样子的话,那你就要反思一下自己是不是有点接近动物了。
我认为人和人之间最大的差距不是小学、初中或者高中拉开的,而是大学毕业以后才拉开的。现在有些家长,孩子很小,就逼着孩子学钢琴、学英语,生怕孩子输在起跑线上,其实人和人的差距是大学毕业后才开始拉开,而且和他上过什么样的大学,受过什么样的教育关系并不大,我们在网上看到北大才子也有街头卖肉的,同时也看到不少没有上过大学的人,也取得了辉煌的成就。这是什么原因?我分析了很多的案例,我觉得最大的区别就是成功人士在大学毕业,走向社会后,还能像小学的时候一样有理想,并一直为之奋斗,而大部分人则是在刚毕业的时候,踌躇满志,意气风发,然后在走向社会,在职场上一次次碰壁后,遭遇了一次次事与愿违之后,便没了棱角,就随遇而安,得过且过,把自己的理想尘封起来,在工作三、四年之后,变得非常迷茫,不知道前方的路怎么走,而恰恰这时候最需要有理想,小学、中学的时候谈理想是空谈,好好学习就行,而毕业后是最需要有理想的时候,却丢掉了理想,所以大学毕业十年后,丢掉理想人和仍然保持理想的人之间的差距就出来了。
我觉得什么时候树立自己的目标都不迟,就害怕长期没有自己的目标,没有目标就随遇而安,得过且过,那么当你七老八十,回顾往事的时候,就一定会为你的碌碌无为而悔恨。
其次讲执行。为什么叫执行?人生路漫漫,只有目标远大、志存高远的人,才能够坚持不懈、始终如一地向既定目标前进,才不为花花世界一时的诱惑所打动,不会为了多挣五百元钱,而选择干自己不喜欢或者跟自己人生目标方向不一致的事情。所以,他在职业选择上,有所为,有所不为,与自己目标方向不一致的事情他不做。相反,如果没有目标,就不存在所做的事情跟目标是否一致的问题,也就谈不上坚持不懈的执行了。所以,只有牢记目标的人,才能在非常复杂的环境下,做出真正适合自己的抉择,才能在关键事情的决策过程中从长远计较,不感情用事。为什么韩信能受跨下之辱?原因是他有更高的人生目标,他希望成就一番大事业,如果他当时感情用事,和那个地痞拼了,同归于尽了,我们就看不到后面的那个雄才大略、功高盖世的韩信了。
第三个讲权变。我觉得权变也非常重要,为什么呢?因为人生绝对不会一帆风顺,如果一帆风顺反倒不正常,人常说,人生不得意者十之八九。你虽然有目标,也有执行的决心,但不一定有很好的机遇和环境,这就是外因!所以,古人特别讲究天、地、人三者之间的和谐,天泛指政策机遇,地就是指客观环境,你必须根据你周围的环境及时调整你的策略、步伐、路线,迂回前进。所以人们总结成功需要的三项素质:有度量去容忍这些你不能改变的事情,有勇气去改变这些可以改变的事情,有智慧去分辨前两者。人生这个大项目,最大的特点是变数多,唯变不变,所以能在一波又一波的大浪冲击后,还能记住自己的目标(或理想),并为之而奋斗的人少之又少,所以成功的人很少。
达尔文说过,万物进化的幸存者,不是属于那些最强大的,或是最聪明的,而是那些最能适应环境变化的。这是我讲的第三点,权变。
第四个叫影响力。凡是能够成就一番事业的人,他都懂得一点,就是努力扩大自己的影响圈。我认为影响力是一个人驾驭社会资源和社会财富的能力。所以,成功人士一般能够有效的影响他人,取得他人的帮助,得到外部资源和支持。要想有影响力就要树立自己的形象,专业、权威、诚信、注重声誉、善于表达都是影响力的重要方面,人过留名,雁过留声,当你要离开这家公司的时候,你要回首看一下,你留了什么样的名?什么样的声?你是声名狼藉的默默离开?还是在依依惜别声中离开,就侧面反映了你在组织中的影响力。
所以,要坚持修炼自己的影响力,本质上,决定你在重要岗位上能否称职是你自己的影响力够不够。比如,你的领导突然让你做副总裁,或者明天让你做国务院总理,你一定不敢去,因为你的影响力没到这个份上,你没有那么大的号召力和威信,上去肯定找死。有影响力的人,能做到在关键的时候,一声令下,大家都唯他的马首是瞻,能发动群众,所以能成就大事业。
最后一个就是善于总结。有一句俗话叫“有钱难买回头望”,提醒我们做完任何事情都要习惯的回头望一望。要不断总结现实与目标的差距,及时改过,要做到君子无二过——尽量不要犯同样的错误,因为人生这个项目毕竟是有时间限制的,你如果犯错误耽误太多了,也影响目标达成。
这里我还告诉大家,经历不等于经验,二者区别非常大!同样两个人都三十多岁、四十多岁了,但是他们看问题的高度,考虑问题的深度会相差很远,为什么呢?就是因为有的人积累了丰富的人生经验,而有的人只能说是有经历。大家做同样一个项目,有的人洋洋洒洒,能从多方面、多角度总结出很多东西,有的人则是猪八戒吃人参果,项目做完了,但是什么体会也谈不上。久而久之,这两个人的差距就出来了,你只有在做一件事情的时候,深入思考、善于总结,那么这件事情过后才能积累下属于你自己的经验!如果囫囵吞枣,被动应付,那绝对不会积累下属于自己的经验!
这就是我讲的人生成功的五大要素。这是我自己的版本,不见于其他的教科书
数据库是面向事务的设计,数据仓库是面向主题设计的。
数据库一般存储在线交易数据,数据仓库存储的一般是历史数据。
数据库设计是尽量避免冗余,一般采用符合范式的规则来设计,数据仓库在设计是有意引入冗余,采用反范式的方式来设计。
数据库是为捕获数据而设计,数据仓库是为分析数据而设计,它的两个基本的元素是维表和事实表。(维是看问题的角度,比如时间,部门,维表放的就是这些东西的定义,事实表里放着要查询的数据,同时有维的ID)
数据仓库,是在数据库已经大量存在的情况下,为了进一步挖掘数据资源、为了决策需要而产生的,它决不是所谓的“大型数据库”。那么,数据仓库与传统数据库比较,有哪些不同呢?让我们先看看W.H.Inmon关于数据仓库的定义:面向主题的、集成的、与时间相关且不可修改的数据集合。
“面向主题的”:传统数据库主要是为应用程序进行数据处理,未必按照同一主题存储数据;数据仓库侧重于数据分析工作,是按照主题存储的。这一点,类似于传统农贸市场与超市的区别—市场里面,白菜、萝卜、香菜会在一个摊位上,如果它们是一个小贩卖的;而超市里,白菜、萝卜、香菜则各自一块。也就是说,市场里的菜(数据)是按照小贩(应用程序)归堆(存储)的,超市里面则是按照菜的类型(同主题)归堆的。
“与时间相关”:数据库保存信息的时候,并不强调一定有时间信息。数据仓库则不同,出于决策的需要,数据仓库中的数据都要标明时间属性。决策中,时间属性很重要。同样都是累计购买过九车产品的顾客,一位是最近三个月购买九车,一位是最近一年从未买过,这对于决策者意义是不同的。
“不可修改”:数据仓库中的数据并不是最新的,而是来源于其它数据源。数据仓库反映的是历史信息,并不是很多数据库处理的那种日常事务数据(有的数据库例如电信计费数据库甚至处理实时信息)。因此,数据仓库中的数据是极少或根本不修改的;当然,向数据仓库添加数据是允许的。
数据仓库的出现,并不是要取代数据库。目前,大部分数据仓库还是用关系数据库管理系统来管理的。可以说,数据库、数据仓库相辅相成、各有千秋
补充一下,数据仓库的方案建设的目的,是为前端查询和分析作为基础,由于有较大的冗余,所以需要的存储也较大。为了更好地为前端应用服务,数据仓库必须有如下几点优点,否则是失败的数据仓库方案。
1.效率足够高。客户要求的分析数据一般分为日、周、月、季、年等,可以看出,日为周期的数据要求的效率最高,要求24小时甚至12小时内,客户能看到昨天的数据分析。由于有的企业每日的数据量很大,设计不好的数据仓库经常会出问题,延迟1-3日才能给出数据,显然不行的。
2.数据质量。客户要看各种信息,肯定要准确的数据,但由于数据仓库流程至少分为3步,2次ETL,复杂的架构会更多层次,那么由于数据源有脏数据或者代码不严谨,都可以导致数据失真,客户看到错误的信息就可能导致分析出错误的决策,造成损失,而不是效益。
3.扩展性。之所以有的大型数据仓库系统架构设计复杂,是因为考虑到了未来3-5年的扩展性,这样的话,客户不用太快花钱去重建数据仓库系统,就能很稳定运行。主要体现在数据建模的合理性,数据仓库方案中多出一些中间层,使海量数据流有足够的缓冲,不至于数据量大很多,就运行不起来了
在20世纪60年代后期引入的面向对象技术引起了一场革命。到20世纪80年代后,面向对象的技术已经成为了行业的主流,其原因多种多样:面向对象不仅简化了界面的开发,而且也提供了一种更加灵活、简单数据处理方法,这种方法从根本上改变了应用程序的构建方法。不再像关系型数据库一样用死板的二维表格来表示数据,对象技术使用类对数据进行描述。一个对象是一个类的实例,就像一颗特定的橡树是橡树类的实例一样。
对象技术使用继承方案,使得类是按等级设计的。“橡树”类能够从更加普遍的类“树”继承数据结构和数据行为。
对象技术能够更好地描述我们所见的世界,面向对象的语言已经被证实在大多数编程领域更加通用。他们使得编程语言更加接近自然语言和多数软件开发领域的主流思想。面向对象是一个新的典范,它的影响将持久而深远。
面向对象的特性很快被添加到各种成熟的语言中,并因此成就了一些语言,如C++。新的面向对象的开发环境出现了,包括Visual Basic,Visual C++,PowerBuilder,Delphi,以及Caché。尽管面向对象的技术在高级开发环境下受到了广泛支持,它还是需要花一定的时间形成正规的课程。而且还需要花更长的时间来构建一个真正的基于对象的世界——我们目前还没有到达这样一个阶段。
万维网上对象技术的发展
随着万维网(World Wide Web)转变为交换各种信息的手段,面向对象的编程语言Java成为Web开发者的最爱。基于C++,Java能够用来创建可以在浏览器执行的小程序(Java applets)。
Sun为了促进Java的发展免费提供Java环境。在短短几年内,成百上万的Java环境被复制下载,Java渗透到世界的每一个角落。同时Java引发了更多的面向对象语言,如JavaScript,C#以及Jscript。Internet的发展也培育了一些新的面向对象语言像Perl和PHP。现在的开发者使用面向对象的技术已经是理所当然的了。
对象的崛起
对象技术影响了软件开发的各个方面。对象建模已经占领了应用建模的市场,标准UML建模方法独占鳌头。
20世纪90年代,面向对象中间件产品的出现为面向对象的应用提供了安全交流服务。当1998年JMS(Java Messaging Serivce)的出现,使得中间件市场向前跨越了一大步。JMS定义了一整套消息传递的应用编程接口(APIs),使得经认证的J2EE应用必须引入JMS服务器。这进一步强化了标准化进程,大大降低了中间件的费用,提供了编写企业范围基于对象的应用程序平台。
XML和Web服务
1998年,HTML,专门用于网页设计的标识语言,经过进一步发展并标准化,创造出了XML(扩展的标识语言)。XML提供了一整套语法,能够用于创建与存储在数据库中定义相似的自定义数据格式,可以。有了XML,程序能够把定义附加在数据上,能够交换数据和数据含义。XML能够使得有特定标准的数据模型(如发票或者购买订单)的定义能够在公司内部或者公司之间进行数据交换。XML引发了Web服务的兴起——在不需要客户定制的情况下,程序能够与其他程序立即交互。现在出现了两种Web服务环境——J2EE和.NET。像SQL一样,XML为程序员提供了获取数据的标准,但XML同时还提供了一种在对象层定义数据的标准语言。XML和对象技术一样迅速成长。结果,数据对象的新标准和基于XML的新的开发产品出现了。
对象数据库——缺失的一环
与软件开发各个环节中对象技术的快速应用形成鲜明对比,对象数据库直到现在才开始逐渐被人们所接受。对象数据的迟缓行动原因有很多。
早期的对象语言没有考虑数据存储。程序在内存数据上工作,数据作为文件存储,当程序下次运行时数据也作为文件被读取。这种方法使得应用程序之间不可能共享数据,数据的恢复、管理、扩展几乎不可能。
目前在市场上已经有大量的面向对象数据库产品:Versant,Objectivity,ObjectStore,GemStone等等。他们为面向对象的开发环境提供了相应的数据存储。这些产品满足了最初的热情,甚至这些产品被期望能够打造一个新的数据库市场——甚至可能成为市场的领袖。
但不幸的是,这些对象数据库出现时,关系型数据库供应商已经积聚了巨大的动力,并占领了大量市场份额。在标准的SQL接口下,访问关系型数据库的面向对象程序很容易写。相反,多数早期的对象数据完全不提供SQL接口,不适合任何查询应用程序。结果,对象数据库在商业上没有建立坚实的基础。他们在应用领域只创建了一个小市场来管理和存储复杂对象如CAD/CAM,电信业、多媒体、人工智能,模拟金融设备、病人诊治跟踪系统以及科学应用。
数据库市场从未特别关注过对象数据库,直到对象定义语言XML出现,这种情况才有所改变,促进了对象数据库的再次呈现,因为他们管理XML定义的数据是最合适的。使用XML,必然会提高存储复杂数据的需求,将进一步引发对象数据库的复苏。
03年9月份InfoWorld公布了一项开发员调查,其中有一个惊奇的结果,89.2%的被调查者说他们使用关系型数据库,52%的被调查者说他们使用面向对象或者XML数据库。当问及有关存储数据的类型时,40.2%的人说他们存储持久的对象,58.9%的人说他们存储XML数据,89%的人说他们存储关系型数据。Baroudi Bloor相信对象数据库比我们想象的用的更加广泛,随着需求的激增,将进一步扩大市场份额。
InfoWorld的调查还显示了面向对象的语言是新应用开发的主流选择。我们相信这些统计数字反映了当今开发员面临的困境。他们需要与他们一直使用的面向对象语言有更好协调性的数据库,但他们有需要关系型数据库所提供的查询能力。
关系型数据库——另一半是如何存在的
只要有程序,就会有数据。IT行业最早具有商业价值之一的就是数据管理。自动的数据管理意味着业务能够扩展、具有竞争力,没有它就不可能。所以毫无疑问机智的商业技术员很早把目光聚集在数据管理市场。在对象数据库产生之前的20年,E.F Codd博士提出的关系型理论找到了出路,开发出商业的关系型数据库产品。在80年中期,在IT领域有一个宗教式的信仰,认为数据的所有理论问题都已经解决,实践的问题也会随之解决。然而,很明显,事实并不是这样。
关系型数据库把数据存储在简单的两维表中,这是一种表达大量数据的有效方法,而且程序员也易于理解。关系型数据库使用SQL建立了一种标准的数据访问语言。关系型数据库有一个逻辑和物理形式清楚的结构,这种结构使得应用程序对数据结构是透明的,而且在很多商业应用程序中工作的很好。
然而,关系理论的基础之一是数据和使用数据的程序能够而且应该是相互独立的。这与对象技术的整个理念是不一致的。对象技术鼓励设计者使用对象而不是表来思考数据。对象和使用对象的方法是不可能彼此分开的。
如果把汽车作为一个复杂的对象来考虑。当你使用汽车时,你使用一辆完整的汽车,作为一个东西——一个对象来使用。与汽车相联系的有很多动作(也就是面向对象术语中的方法)。你驾驶汽车,进行换档,发信号,开车灯,等等。如果汽车是一个对象,这些动作就是对象的方法,他们对汽车而言是基础性的。这些动作独立于汽车的想法是荒唐的。当你把你的车停在车库,你把它作为一个东西来存储。而不是分别在车库中的某些地方存放方向盘,转换器,信号器,车灯。数据和它相对应的处理过程也不能而且也不应该被隔离开来。在对象数据库中他们是不分开的。
事实上,这两种观点各有优缺点。有些处理过程确实是独立于数据的。尤其是访问大量数据的查询操作。简单的查询就是根据一些标准来选取数据,而不关心数据是什么,也不用关心数据是如何被组织的,只要它能快速的被取出就可以了。查询是独立于数据的,但对象方法则不是。
关系型数据库的局限性
关系型数据库有比我们想的更多的局限性。存储和表示一些相当普通的数据结构也是非常困难的。试想一条公交线路——简单,有序的一组站点。关系型数据库以无序的方式存放表,只有创建一个特殊的索引,才能提取有序的数据。对象数据库就没有这个问题,它有有序的数组,不需要索引——这种索引是因为关系数据结构的局限性而要求创建的人工索引。
另一个简单的例子是产品用料单——在制造系统中记录一个产品和它的组件。组件自身也许还有组件,组件的组件还有组件,以此类推。一个关系型数据表不能表达这种部件与部件的部件之间的关系。而这些关系却是重要的数据。查询一个产品数据库,它的所有组件应该是一目了然的。关系型数据库结构使得开发员花费很多的工作来回答这种简单的查询,非常的复杂、困难。与这个例子类似的例子:地图和它的路、河、路标;网站和它的页面、链接以及图像。实际上,搜集的信息越复杂,等级层次和交叉层次就越多,在简单二维表的关系型数据库就越不可能表达清楚。对象数据库没有这样的限制,事实上,他们就是为了解决这个问题而设计的。
虽然关系型数据库发展成熟,在这十年中发展也非常迅猛,但我们还听到一些项目因为所使用的关系数据的性能不是很好而导致失败。通常,是因为关系型数据库物理上存储数据的方法导致的。对开发员而言,为了集合他们所需的数据,他们常常不得不进行这个表与另一个表联接,再与另外的表联接,然后再与另一个表联接。为了提取数据,数据库运行优化程序来判断提取数据的最好方法,然后再提取数据。这样的处理常常要花费很长的时间,结果就大大影响了性能。尽管关系型数据库优化器已经改善了运行时间,但他们还需要比对象数据库更多的处理时间。
关系型数据库和“阻抗不匹配”障碍
关系型数据的一个问题是他们所使用的基本数据结构是一种二维形式的表。在关系理论中,数据应该被组织成规范的表——也就是数据应该按唯一的方式组织,使得程序员能够消除冗余,确保数据变化的一致性。这种设计技术的引入确保了关系表中的数据是一组独立的、通过键相关的数据。这种技术来自集合论的数学理论,但问题是集合论不能表达数据之间所有的关系和结构。
以规范的方式存储数据常常要求程序员在存入数据库之前分解对象,并且重新组织数据,但要使用它是,在使用SQL查询(多重连接)。就像在车库中存储车时,你把它的门、椅子、轮子等等分别卸下来存放。这是非常耗时的,而是也是没有任何意义的。
但面向对象的语言占主导地位时,问题就越发明显了。这个问题通常被称为对象-关系不匹配障碍。这个问题是由于面向对象语言和关系型数据库使用语言的方法不同导致的,结果这个问题只能有程序员自己来解决。事实上,大多数关系型数据库在使用的时候并不是完全规范的,但即使是这样,不匹配问题还是发生,对编程人员的工作造成了很大的困难。我们可以估计使用关系型数据库的面向对象开发员25%到40%的时间用于编写代码来解决对象与关系表的匹配问题。
也许这个根本性困难产生了对对象数据的强烈需求,但多数对象数据库也有一个很大的问题:他们对SQL的支持很少。而许多软件工具需要SQL接口,尤其是商业智能应用。甚至有SQL接口的对象数据库也不能创建用于管理商业智能应用所产生的这类查询机制。
对象-关系数据库
关系型数据库的供应商并没有忽视对象的出现。显然,规范复杂数据是没有意义的。举个极端的例子,如果你要规范一个位图形式的图像——是一系列的象素表示的——你最终要得出一个表,这个表的行是象素,并且主键的属性反映他们的顺序。很明显最好是把这个数据作为一个对象来存储。
他们提出了“对象-关系”数据库的创意,这个创意中保留了关系型数据库的结构,但允许关系表中的列含有一个复杂的对象。这些对象能够捆绑处理复杂数据的处理过程(一种存储过程)。并且SQL能够允许调用与关系型等同的“对象方法”。
这种方法是对数据关系理论的一种嘲弄,事实上,它完全忽略了这个理论,但又允许复杂数据(地图,矢量图,图表,甚至整个表格)被定义为一个项目存放在关系结构中。因此,这些功能被实现并商品化。Informix称它的嵌入过程为DateBlades,Oracle称之为Cartridges。
对象-关系数据库成为存储数据时对象数据库的一种替代方案,但根本的问题它并没有解决。对象-关系数据库还是受不匹配障碍的困扰。
对象数据库与关系型数据库
实践中,对象数据库相对于关系数据库有显著的优势。
他们能更快的运行事务处理程序
他们能够更有效的处理对象
他们能够提供更好的开发效率
他们能够管理更容易
在一些例子中,因为是性能方面的原因,用对象数据库能够替代关系型数据库。在不能存储复杂对象的大规模的业务处理程序中确实是这样的——也许有些人会认为这个必然是关系型数据库的领地。
对象数据库最大的性能优势是他们不必像关系型数据库一样在数据使用之前先连接数据。他们就以使用数据的方式存储数据,这就大大提高了性能。对象数据库能够使用缓存技术,这样就使得在请求数据时数据就已经存放在内存中了。对象数据库在抽取数据时几乎不需要进行优化。
但开发一个新的系统,处理复杂数据如文档、复杂图表、网页、多媒体等的需求不断增长时,这些需求对象数据库可以很好的满足。
当今面向对象的前景
在软件开发的各个方面使用对象技术的人群都在不停地增长。甚至在最后一个领域——数据库——尽管对象数据库还没有取代关系型数据库,这种增长也是十分显著的。InfoWorld报告说52%的开发员在使用对象数据库或者XML数据库(通常也是一种对象数据库)。还有一些选择混合形式的数据库,这种数据库能比较容易地使用对象结构。随着新应用程序开发过程中Web接口成为一个必不可少地部分,Web服务成为应用系统交互地一种可行的机制,构建一个面向对象的世界似乎是当今的现实。
03年9月的InfoWorld调查也显示了使用面向对象语言的程序员几乎无处不在。事实上,尽管有些人宣称使用C语言,但是面向对象的语言还是成为当今90%的程序员的选择。调查也显示了程序员比较喜欢基于Web的应用,易用的对象编程和脚本语言。随着越来越多的有着正规培训的软件工程师进入市场,面向对象技术将成为新应用开发的唯一选择。
结论
也许关系型数据库将继续领导数据库市场,而对象数据库在市场上只占有一席之地。也许对象数据库将进一步提升市场份额,因为他们能够处理当今使用的复杂的数据。然而,我们认为还有其他的可能:数据库技术可能发展出一种真正的混合型产品,这种产品能提供关系接口和对象接口双重优势。我们知道这是有可能的。事实上,至少有一种产品,来自InterSystems的Caché,就是这样一个产品。(Caché数据库,描述他自己时,既不是说是关系型的,也不是说是对象的,而是后关系型数据库)。数据库供应商——不管他们的产品是属于关系型还是对象型——都会朝着这个方向前进的。
这种混合产品的方法包括给数据库提供一个映射层,程序员通过映射层访问数据库。映射层应该基于开发的标准以解决不匹配障碍问题。数据库的调用能够用SQL完成,也可以直接请求对象类或者类的集合。映射层能够把这些调用转换为对数据库的物理数据请求以抽取数据。这种方法将消除不匹配的障碍。
改变任何一种数据类型都是非常大的挑战。对象数据库需要快速索引能力,以从庞大的数据集中抽取数据。在这方面做得比较好的关系型数据库使用位图索引技术,但数据一旦更新,这些索引就需要重新建立。因为这个原因,很少有对象数据有这个功能。对关系数据库而言,他们需要提供更加灵活的物理数据结构。在发展过程中,关系型数据库倾向于在物理层使用表。他们需要放弃这种不灵活的限制,允许存储多种数据结构。数据库使用者将获得最大的收益。试想把对象数据库的优势和关系型数据库的优势整合在一起:
好的处理性能
复杂数据管理
管理简便
快速开发
灵活的查询功能
标准的数据访问接口
更好地适用于商业智能应用
这种混合产品使使用一个数据库引擎成为可能,并且所有应用只有一个数据定义集。Baroudi Bloor相信企业界需要混合式的数据库产品。供应商们必须放弃他们对关系数据库宗教式的倾向,转向更具优势的混合式的数据库,否则的话他们将陷于COBOL以及打孔卡片的深渊而不能自拨。
做数据仓库系统,ETL是关键的一环。说大了,ETL是数据整合解决方案,说小了,就是倒数据的工具。回忆一下工作这么些年来,处理数据迁移、转换的工作倒还真的不少。但是那些工作基本上是一次性工作或者很小数据量,使用access、DTS或是自己编个小程序搞定。可是在数据仓库系统中,ETL上升到了一定的理论高度,和原来小打小闹的工具使用不同了。究竟什么不同,从名字上就可以看到,人家已经将倒数据的过程分成3个步骤,E、T、L分别代表抽取、转换和装载。
其实ETL过程就是数据流动的过程,从不同的数据源流向不同的目标数据。但在数据仓库中,ETL有几个特点,一是数据同步,它不是一次性倒完数据就拉到,它是经常性的活动,按照固定周期运行的,甚至现在还有人提出了实时ETL的概念。二是数据量,一般都是巨大的,值得你将数据流动的过程拆分成E、T和L。
现在有很多成熟的工具提供ETL功能,例如datastage、powermart等,且不说他们的好坏。从应用角度来说,ETL的过程其实不是非常复杂,这些工具给数据仓库工程带来和很大的便利性,特别是开发的便利和维护的便利。但另一方面,开发人员容易迷失在这些工具中。举个例子,VB是一种非常简单的语言并且也是非常易用的编程工具,上手特别快,但是真正VB的高手有多少?微软设计的产品通常有个原则是“将使用者当作傻瓜”,在这个原则下,微软的东西确实非常好用,但是对于开发者,如果你自己也将自己当作傻瓜,那就真的傻了。ETL工具也是一样,这些工具为我们提供图形化界面,让我们将主要的精力放在规则上,以期提高开发效率。从使用效果来说,确实使用这些工具能够非常快速地构建一个job来处理某个数据,不过从整体来看,并不见得他的整体效率会高多少。问题主要不是出在工具上,而是在设计、开发人员上。他们迷失在工具中,没有去探求ETL的本质。
可以说这些工具应用了这么长时间,在这么多项目、环境中应用,它必然有它成功之处,它必定体现了ETL的本质。如果我们不透过表面这些工具的简单使用去看它背后蕴涵的思想,最终我们作出来的东西也就是一个个独立的job,将他们整合起来仍然有巨大的工作量。大家都知道“理论与实践相结合”,如果在一个领域有所超越,必须要在理论水平上达到一定的高度
探求ETL本质之一
ETL的过程就是数据流动的过程,从不同异构数据源流向统一的目标数据。其间,数据的抽取、清洗、转换和装载形成串行或并行的过程。ETL的核心还是在于T这个过程,也就是转换,而抽取和装载一般可以作为转换的输入和输出,或者,它们作为一个单独的部件,其复杂度没有转换部件高。和OLTP系统中不同,那里充满这单条记录的insert、update和select等操作,ETL过程一般都是批量操作,例如它的装载多采用批量装载工具,一般都是DBMS系统自身附带的工具,例如Oracle SQLLoader和DB2的autoloader等。
ETL本身有一些特点,在一些工具中都有体现,下面以datastage和powermart举例来说。
1、静态的ETL单元和动态的ETL单元实例;一次转换指明了某种格式的数据如何格式化成另一种格式的数据,对于数据源的物理形式在设计时可以不用指定,它可以在运行时,当这个ETL单元创建一个实例时才指定。对于静态和动态的ETL单元,Datastage没有严格区分,它的一个Job就是实现这个功能,在早期版本,一个Job同时不能运行两次,所以一个Job相当于一个实例,在后期版本,它支持multiple instances,而且还不是默认选项。Powermart中将这两个概念加以区分,静态的叫做Mapping,动态运行时叫做Session。
2、ETL元数据;元数据是描述数据的数据,他的含义非常广泛,这里仅指ETL的元数据。主要包括每次转换前后的数据结构和转换的规则。ETL元数据还包括形式参数的管理,形式参数的ETL单元定义的参数,相对还有实参,它是运行时指定的参数,实参不在元数据管理范围之内。
3、数据流程的控制;要有可视化的流程编辑工具,提供流程定义和流程监控功能。流程调度的最小单位是ETL单元实例,ETL单元是不能在细分的ETL过程,当然这由开发者来控制,例如可以将抽取、转换放在一个ETL单元中,那样这个抽取和转换只能同时运行,而如果将他们分作两个单元,可以分别运行,这有利于错误恢复操作。当然,ETL单元究竟应该细分到什么程度应该依据具体应用来看,目前还没有找到很好的细分策略。比如,我们可以规定将装载一个表的功能作为一个ETL单元,但是不可否认,这样的ETL单元之间会有很多共同的操作,例如两个单元共用一个Hash表,要将这个Hash表装入内存两次。
4、转换规则的定义方法;提供函数集提供常用规则方法,提供规则定义语言描述规则。
5、对数据的快速索引;一般都是利用Hash技术,将参照关系表提前装入内存,在转换时查找这个hash表。Datastage中有Hash文件技术,Powermart也有类似的Lookup功能。
探求ETL本质之二(分类)
昨在IT-Director上阅读一篇报告,关于ETL产品分类的。一般来说,我们眼中的ETL工具都是价格昂贵,能够处理海量数据的家伙,但是这是其中的一种。它可以分成4种,针对不同的需求,主要是从转换规则的复杂度和数据量大小来看。它们包括
1、交互式运行环境,你可以指定数据源、目标数据,指定规则,立马ETL。这种交互式的操作无疑非常方便,但是只能适合小数据量和复杂度不高的ETL过程,因为一旦规则复杂了,可能需要语言级的描述,不能简简单单拖拖拽拽就可以的。还有数据量的问题,这种交互式必然建立在解释型语言基础上,另外他的灵活性必然要牺牲一定的性能为代价。所以如果要处理海量数据的话,每次读取一条记录,每次对规则进行解释执行,每次在写入一条记录,这对性能影响是非常大的。
2、专门编码型的,它提供了一个基于某种语言的程序框架,你可以不必将编程精力放在一些周边的功能上,例如读文件功能、写数据库的功能,而将精力主要放在规则的实现上面。这种近似手工代码的性能肯定是没话说,除非你的编程技巧不过关(这也是不可忽视的因素之一)。对于处理大数据量,处理复杂转换逻辑,这种方式的ETL实现是非常直观的。
3、代码生成器型的,它就像是一个ETL代码生成器,提供简单的图形化界面操作,让你拖拖拽拽将转换规则都设定好,其实他的后台都是生成基于某种语言的程序,要运行这个ETL过程,必须要编译才行。Datastage就是类似这样的产品,设计好的job必须要编译,这避免了每次转换的解释执行,但是不知道它生成的中间语言是什么。以前我设计的ETL工具大挪移其实也是归属于这一类,它提供了界面让用户编写规则,最后生成C++语言,编译后即可运行。这类工具的特点就是要在界面上下狠功夫,必须让用户轻松定义一个ETL过程,提供丰富的插件来完成读、写和转换函数。大挪移在这方面就太弱了,规则必须手写,而且要写成标准c++语法,这未免还是有点难为最终用户了,还不如做成一个专业编码型的产品呢。另外一点,这类工具必须提供面向专家应用的功能,因为它不可能考虑到所有的转换规则和所有的读写,一方面提供插件接口来让第三方编写特定的插件,另一方面还有提供特定语言来实现高级功能。例如Datastage提供一种类Basic的语言,不过他的Job的脚本化实现好像就做的不太好,只能手工绘制job,而不能编程实现Job。
4、最后还有一种类型叫做数据集线器,顾名思义,他就是像Hub一样地工作。将这种类型分出来和上面几种分类在标准上有所差异,上面三种更多指ETL实现的方法,此类主要从数据处理角度。目前有一些产品属于EAI(Enterprise Application Integration),它的数据集成主要是一种准实时性。所以这类产品就像Hub一样,不断接收各种异构数据源来的数据,经过处理,在实施发送到不同的目标数据中去。
虽然,这些类看似各又千秋,特别在BI项目中,面对海量数据的ETL时,中间两种的选择就开始了,在选择过程中,必须要考虑到开发效率、维护方面、性能、学习曲线、人员技能等各方面因素,当然还有最重要也是最现实的因素就是客户的意象。
探求ETL本质之三(转换)
ETL探求之一中提到,ETL过程最复杂的部分就是T,这个转换过程,T过程究竟有哪些类型呢?
一、宏观输入输出
从对数据源的整个宏观处理分,看看一个ETL过程的输入输出,可以分成下面几类:
1、大小交,这种处理在数据清洗过程是常见了,例如从数据源到ODS阶段,如果数据仓库采用维度建模,而且维度基本采用代理键的话,必然存在代码到此键值的转换。如果用SQL实现,必然需要将一个大表和一堆小表都Join起来,当然如果使用ETL工具的话,一般都是先将小表读入内存中再处理。这种情况,输出数据的粒度和大表一样。
2、大大交,大表和大表之间关联也是一个重要的课题,当然其中要有一个主表,在逻辑上,应当是主表Left Join辅表。大表之间的关联存在最大的问题就是性能和稳定性,对于海量数据来说,必须有优化的方法来处理他们的关联,另外,对于大数据的处理无疑会占用太多的系统资源,出错的几率非常大,如何做到有效错误恢复也是个问题。对于这种情况,我们建议还是尽量将大表拆分成适度的稍小一点的表,形成大小交的类型。这类情况的输出数据粒度和主表一样。
3、站着进来,躺着出去。事务系统中为了提高系统灵活性和扩展性,很多信息放在代码表中维护,所以它的“事实表”就是一种窄表,而在数据仓库中,通常要进行宽化,从行变成列,所以称这种处理情况叫做“站着进来,躺着出去”。大家对Decode肯定不陌生,这是进行宽表化常见的手段之一。窄表变宽表的过程主要体现在对窄表中那个代码字段的操作。这种情况,窄表是输入,宽表是输出,宽表的粒度必定要比窄表粗一些,就粗在那个代码字段上。
4、聚集。数据仓库中重要的任务就是沉淀数据,聚集是必不可少的操作,它是粗化数据粒度的过程。聚集本身其实很简单,就是类似SQL中Group by的操作,选取特定字段(维度),对度量字段再使用某种聚集函数。但是对于大数据量情况下,聚集算法的优化仍是探究的一个课题。例如是直接使用SQL的Group by,还是先排序,在处理。
二、微观规则
从数据的转换的微观细节分,可以分成下面的几个基本类型,当然还有一些复杂的组合情况,例如先运算,在参照转换的规则,这种基于基本类型组合的情况就不在此列了。ETL的规则是依赖目标数据的,目标数据有多少字段,就有多少条规则。
1、直接映射,原来是什么就是什么,原封不动照搬过来,对这样的规则,如果数据源字段和目标字段长度或精度不符,需要特别注意看是否真的可以直接映射还是需要做一些简单运算。
2、字段运算,数据源的一个或多个字段进行数学运算得到的目标字段,这种规则一般对数值型字段而言。
3、参照转换,在转换中通常要用数据源的一个或多个字段作为Key,去一个关联数组中去搜索特定值,而且应该只能得到唯一值。这个关联数组使用Hash算法实现是比较合适也是最常见的,在整个ETL开始之前,它就装入内存,对性能提高的帮助非常大。
4、字符串处理,从数据源某个字符串字段中经常可以获取特定信息,例如身份证号。而且,经常会有数值型值以字符串形式体现。对字符串的操作通常有类型转换、字符串截取等。但是由于字符类型字段的随意性也造成了脏数据的隐患,所以在处理这种规则的时候,一定要加上异常处理。
5、空值判断,对于空值的处理是数据仓库中一个常见问题,是将它作为脏数据还是作为特定一种维成员?这恐怕还要看应用的情况,也是需要进一步探求的。但是无论怎样,对于可能有NULL值的字段,不要采用“直接映射”的规则类型,必须对空值进行判断,目前我们的建议是将它转换成特定的值。
6、日期转换,在数据仓库中日期值一般都会有特定的,不同于日期类型值的表示方法,例如使用8位整型20040801表示日期。而在数据源中,这种字段基本都是日期类型的,所以对于这样的规则,需要一些共通函数来处理将日期转换为8位日期值、6位月份值等。
7、日期运算,基于日期,我们通常会计算日差、月差、时长等。一般数据库提供的日期运算函数都是基于日期型的,而在数据仓库中采用特定类型来表示日期的话,必须有一套自己的日期运算函数集。
8、聚集运算,对于事实表中的度量字段,他们通常是通过数据源一个或多个字段运用聚集函数得来的,这些聚集函数为SQL标准中,包括sum,count,avg,min,max。
9、既定取值,这种规则和以上各种类型规则的差别就在于它不依赖于数据源字段,对目标字段取一个固定的或是依赖系统的值。
探求ETL本质之四(数据质量)
“不要绝对的数据准确,但要知道为什么不准确。”
这是我们在构建BI系统是对数据准确性的要求。确实,对绝对的数据准确谁也没有把握,不仅是系统集成商,包括客户也是无法确定。准确的东西需要一个标准,但首先要保证这个标准是准确的,至少现在还没有这样一个标准。客户会提出一个相对标准,例如将你的OLAP数据结果和报表结果对比。虽然这是一种不太公平的比较,你也只好认了吧。
首先在数据源那里,已经很难保证数据质量了,这一点也是事实。在这一层有哪些可能原因导致数据质量问题?可以分为下面几类:
1、数据格式错误,例如缺失数据、数据值超出范围或是数据格式非法等。要知道对于同样处理大数据量的数据源系统,他们通常会舍弃一些数据库自身的检查机制,例如字段约束等。他们尽可能将数据检查在入库前保证,但是这一点是很难确保的。这类情况诸如身份证号码、手机号、非日期类型的日期字段等。
2、数据一致性,同样,数据源系统为了性能的考虑,会在一定程度上舍弃外键约束,这通常会导致数据不一致。例如在帐务表中会出现一个用户表中没有的用户ID,在例如有些代码在代码表中找不到等。
3、业务逻辑的合理性,这一点很难说对与错。通常,数据源系统的设计并不是非常严谨,例如让用户开户日期晚于用户销户日期都是有可能发生的,一个用户表中存在多个用户ID也是有可能发生的。对这种情况,有什么办法吗?
构建一个BI系统,要做到完全理解数据源系统根本就是不可能的。特别是数据源系统在交付后,有更多维护人员的即兴发挥,那更是要花大量的时间去寻找原因。以前曾经争辩过设计人员对规则描述的问题,有人提出要在ETL开始之前务必将所有的规则弄得一清二楚。我并不同意这样的意见,倒是认为在ETL过程要有处理这些质量有问题数据的保证。一定要正面这些脏数据,是丢弃还是处理,无法逃避。如果没有质量保证,那么在这个过程中,错误会逐渐放大,抛开数据源质量问题,我们再来看看ETL过程中哪些因素对数据准确性产生重大影响。
1、规则描述错误。上面提到对设计人员对数据源系统理解的不充分,导致规则理解错误,这是一方面。另一方面,是规则的描述,如果无二义性地描述规则也是要探求的一个课题。规则是依附于目标字段的,在探求之三中,提到规则的分类。但是规则总不能总是用文字描述,必须有严格的数学表达方式。我甚至想过,如果设计人员能够使用某种规则语言来描述,那么我们的ETL单元就可以自动生成、同步,省去很多手工操作了。
2、ETL开发错误。即时规则很明确,ETL开发的过程中也会发生一些错误,例如逻辑错误、书写错误等。例如对于一个分段值,开区间闭区间是需要指定的,但是常常开发人员没注意,一个大于等于号写成大于号就导致数据错误。
3、人为处理错误。在整体ETL流程没有完成之前,为了图省事,通常会手工运行ETL过程,这其中一个重大的问题就是你不会按照正常流程去运行了,而是按照自己的理解去运行,发生的错误可能是误删了数据、重复装载数据等。
探求ETL本质之五(质量保证)
上回提到ETL数据质量问题,这是无法根治的,只能采取特定的手段去尽量避免,而且必须要定义出度量方法来衡量数据的质量是好还是坏。对于数据源的质量,客户对此应该更加关心,如果在这个源头不能保证比较干净的数据,那么后面的分析功能的可信度也都成问题。数据源系统也在不断进化过程中,客户的操作也在逐渐规范中,BI系统也同样如此。本文探讨一下对数据源质量和ETL处理质量的应对方法。
如何应对数据源的质量问题?记得在onteldatastage列表中也讨论过一个话题-"-1的处理",在数据仓库模型维表中,通常有一条-1记录,表示“未知”,这个未知含义可广了,任何可能出错的数据,NULL数据甚至是规则没有涵盖到的数据,都转成-1。这是一种处理脏数据的方法,但这也是一种掩盖事实的方法。就好像写一个函数FileOpen(filename),返回一个错误码,当然,你可以只返回一种错误码,如-1,但这是一种不好的设计,对于调用者来说,他需要依据这个错误码进行某些判断,例如是文件不存在,还是读取权限不够,都有相应的处理逻辑。数据仓库中也是一样,所以,建议将不同的数据质量类型处理结果分别转换成不同的值,譬如,在转换后,-1表示参照不上,-2表示NULL数据等。不过这仅仅对付了上回提到的第一类错误,数据格式错误。对于数据一致性和业务逻辑合理性问题,这仍有待探求。但这里有一个原则就是“必须在数据仓库中反应数据源的质量”。
对于ETL过程中产生的质量问题,必须有保障手段。从以往的经验看,没有保障手段给实施人员带来麻烦重重。实施人员对于反复装载数据一定不会陌生,甚至是最后数据留到最后的Cube,才发现了第一步ETL其实已经错了。这个保障手段就是数据验证机制,当然,它的目的是能够在ETL过程中监控数据质量,产生报警。这个模块要将实施人员当作是最终用户,可以说他们是数据验证机制的直接收益者。
首先,必须有一个对质量的度量方法,什么是高质什么是低质,不能靠感官感觉,但这却是在没有度量方法条件下通常的做法。那经营分析系统来说,联通总部曾提出测试规范,这其实就是一种度量方法,例如指标的误差范围不能高于5%等,对系统本身来说其实必须要有这样的度量方法,先不要说这个度量方法是否科学。对于ETL数据处理质量,他的度量方法应该比联通总部测试规范定义的方法更要严格,因为他更多将BI系统看作一个黑盒子,从数据源到展现的数据误差允许一定的误差。而ETL数据处理质量度量是一种白盒的度量,要注重每一步过程。因此理论上,要求输入输出的指标应该完全一致。但是我们必须正面完全一致只是理想,对于有误差的数据,必须找到原因。
在质量度量方法的前提下,就可以建立一个数据验证框架。此框架依据总量、分量数据稽核方法,该方法在高的《数据仓库中的数据稽核技术》一文中已经指出。作为补充,下面提出几点功能上的建议:
1、提供前端。将开发实施人员当作用户,同样也要为之提供友好的用户界面。《稽核技术》一文中指出测试报告的形式,这种形式还是要依赖人为判断,在一堆数据中去找规律。到不如用OLAP的方式提供界面,不光是加上测试统计出来的指标结果,并且配合度量方法的计算。例如误差率,对于误差率为大于0的指标,就要好好查一下原因了。
2、提供框架。数据验证不是一次性工作,而是每次ETL过程中都必须做的。因此,必须有一个框架,自动化验证过程,并提供扩展手段,让实施人员能够增加验证范围。有了这样一个框架,其实它起到规范化操作的作用,开发实施人员可以将主要精力放在验证脚本的编写上,而不必过多关注验证如何融合到流程中,如何展现等工作。为此,要设计一套表,类似于DM表,每次验证结果数据都记录其中,并且自动触发多维分析的数据装载、发布等。这样,实施人员可以在每次装载,甚至在流程过程中就可以观察数据的误差率。特别是,如果数据仓库的模型能够统一起来,甚至数据验证脚本都可以确定下来,剩下的就是规范流程了。
3、规范流程。上回提到有一种ETL数据质量问题是由于人工处理导致的,其中最主要原因还是流程不规范。开发实施人员运行单独一个ETL单元是很方便的,虽然以前曾建议一个ETL单元必须是“可重入”的,这能够解决误删数据,重复装载数据问题。但要记住数据验证也是在流程当中,要让数据验证能够日常运作,就不要让实施者感觉到他的存在。总的来说,规范流程是提高实施效率的关键工作,这也是以后要继续探求的。
探求ETL本质之六(元数据漫谈)
对于元数据(Metadata)的定义到目前为止没有什么特别精彩的,这个概念非常广,一般都是这样定义,“元数据是描述数据的数据(Data about Data)”,这造成一种递归定义,就像问小强住在哪里,答,在旺财隔壁。按照这样的定义,元数据所描述的数据是什么呢?还是元数据。这样就可能有元元元...元数据。我还听说过一种对元数据,如果说数据是一抽屉档案,那么元数据就是分类标签。那它和索引有什么区别?
元数据体现是一种抽象,哲学家从古至今都在抽象这个世界,力图找到世界的本质。抽象不是一层关系,它是一种逐步由具体到一般的过程。例如我->男人->人->哺乳动物->生物这就是一个抽象过程,你要是在软件业混会发现这个例子很常见,面向对象方法就是这样一种抽象过程。它对世界中的事物、过程进行抽象,使用面向对象方法,构建一套对象模型。同样在面向对象方法中,类是对象的抽象,接口又是对类的抽象。因此,我认为可以将“元”和“抽象”换一下,叫抽象数据是不是好理解一些。
常听到这样的话,“xx领导的讲话高屋建瓴,给我们后面的工作指引的清晰的方向”,这个成语“高屋建瓴”,站在10楼往下到水,居高临下,能砸死人,这是指站在一定的高度看待事物,这个一定的高度就是指他有够“元”。在设计模式中,强调要对接口编程,就是说你不要处理这类对象和那类对象的交互,而要处理这个接口和那个接口的交互,先别管他们内部是怎么干的。
元数据存在的意义也在于此,虽然上面说了一通都撤到哲学上去,但这个词必须还是要结合软件设计中看,我不知道在别的领域是不是存在Metadata这样的叫法,虽然我相信别的领域必然有类似的东东。元数据的存在就是要做到在更高抽象一层设计软件。这肯定有好处,什么灵活性啊,扩展性啊,可维护性啊,都能得到提高,而且架构清晰,只是弯弯太多,要是从下往上看,太复杂了。很早以前,我曾看过backorifice的代码,我靠,一个简单的功能,从这个类转到父类,又转到父类,很不理解,为什么一个简单的功能不在一个类的方法中实现就拉到了呢?现在想想,还真不能这样,这虽然使代码容易看懂了,但是结构确实混乱的,那他只能干现在的事,如果有什么功能扩展,这些代码就废了。
我从98年刚工作时就开始接触元数据的概念,当时叫做元数据驱动的系统架构,后来在QiDSS中也用到这个概念构建QiNavigator,但是现在觉得元数据也没啥,不就是建一堆表描述界面的元素,再利用这些数据自动生成界面吗。到了数据仓库系统中,这个概念更强了,是数据仓库中一个重要的部分。但是至今,我还是认为这个概念过于玄乎,看不到实际的东西,市面上有一些元数据管理的东西,但是从应用情况就得知,用的不多。之所以玄乎,就是因为抽象层次没有分清楚,关键就是对于元数据的分类(这种分类就是一种抽象过程)和元数据的使用。你可以将元数据抽象成0和1,但是那样对你的业务有用吗?必须还得抽象到适合的程度,最后问题还是“度”。
数据仓库系统的元数据作用如何?还不就是使系统自动运转,易于管理吗?要做到这一步,可没必要将系统抽象到太极、两仪、八卦之类的,业界也曾定义过一些元数据规范,向CWM、XMI等等,可以借鉴,不过俺对此也是不精通的说,以后再说
程序员,是最聪明的一个群体。以前是这样,现在我也同样这么认为。可是,作为最聪明的我们,你又对自己能把握多少呢?
程序员是最不善于言语的。任何辛苦,程序员都藏在了心里。市场人员的辛苦,天天在讲。就连你都,天天听得都感觉他们很辛苦。可是却没有人来说你。你可能会在位置上发发牢骚,可能会暗地里伤神流泪,也可能在自己的博客上和网友分享。可唯独不会向领导反映。可惜,领导向来是谁辛苦给谁加薪水。所以,你最好运气好,遇到一位会体察下情的领导。否则,我只能问你,你能掌握你自己的命运吗?
程序员又是最揉不得沙子的。对于任何开发方面的问题,他们都有自己的独到意见。任何不同的意见,都会让他竭尽全力地去找出其中的漏洞。他们的前辈都是从这条路上,踏过无数人的口水,成长起来的。当你看到的沙子在你的领导身上的时候,你会怎么办呢?是放弃程序员的尊严去漠不关心,还是放下一切,去奋力争取?所以,你最好运气好,遇到会告诉你如何沟通的领导。否则,我只能问你,你能掌握你自己的命运吗?
程序员也是最不知道计划的。经常的加班,并不能说明你对计划掌握很好。反而是说明你被公司剥削地更厉害。你最喜欢的是铺开做所有事,可往往所有事都不能按时提交。刚开始的时候,任务少还好说。当能力提升,任务繁多的时候,这种情况越来越明显。于是,你继续着程序员最经常的行为:加班。加班让你不需要关心晚餐的来源,加班让你不需要关心回家的路费,加班让你不需要寻找男女朋友。当你被加班困的焦头烂额的时候,你会怎么办呢?是继续顶下去,还是放弃跳槽?所以,你最好运气好,遇到一个能帮你统筹时间的领导。否则,我只能问你,你能掌握你自己的命运吗?
程序员是最最乐观的。你要让他估计时间。他往往都是告诉你比最好情况都要好的结果。问他这个系统有多难,哈哈,每一点都很简单。是的,也许每一点确实很简单,可是程序员却总是忘记那些繁杂的关系。你要是领导,会一次一次地失望,一次一次地给他压力。可是,你认为他这样的后果会是什么呢?所以,你只能为他祈祷,能遇到一位会帮助学会如何估算的领导。否则,你只能问他,能把握自己的命运吗?
不管如何,程序员总还是乐观地、不自觉地看着别人身上的沙子。可是除了等待遇到一位好领导,我们还能做什么呢?与其让别人教我们,还不如我们自己去学习。程序员啊,什么时候你能够掌握你自己?
很多人都在叹息自己队伍中高手很少。但是,如果队中真的有一高手,你以为会一帆风顺吗?
凡高手者,往往必然是强势者。他必有其不容质疑的能力体现。一旦这个能力遭到怀疑时,你可能会看到意想不到的破坏力。
凡高手者,往往也是好为人师者。他必然要体现他的影响力,不管有意无意。一旦有不如他意的地方,他可以完全推倒。然后告诉你他会怎么做。当然了。他的做法必然又很多非常正确的地方。这点也无庸置疑。
凡高手者,往往是好争论者。你的意见到他那里,往往会遇到来自各个方面的攻击。他会告诉你什么地方可能有什么问题。也会告诉你他有更好的方法。总之,争论不可避免。
凡高手者,往往又是执着者。也许有时候你会认为你已经完全占有上风。但是他可能会告诉你,“你不管怎么说,我也坚持我的。”不是靠讲道理,就能让他就范的。
凡高手者,往往是孤独者。你往往只看到他风光的一面,却难以体会他的无奈。自己的很多想法,无法与人切磋。说高处不胜寒一点不为过。
凡高手者,往往是最忙的人。除了本组的事务,很可能牵涉到其他各部门的事情。有时候还有很多求教。真正静下心来做该做的,嘿嘿,难啊!
所以,不管幸运还是不幸运,你和一位高手在一起,你可以有十二分的准备。
假如你是一位新手,那你要随时准备接受高手的提问、质疑。他随时可能抓你去,告诉你的问题所在。所以到那个时候,你只要记住一句话:嗯,我就该!
当然了,也可能问你问题,千万不要紧张。高手给你的往往不是压力,更多的是你对你的勉励。因为关注才会指导你。所以,就算错了,也不要紧。
关键在于,错了之后,要将错误做一个非常完整的分析,再对比一下正确的方案。然后统一汇报给高手。不为弥补你原来的失误,而是为了赢取他对你的继续关注。
假如你自己感觉状态很好,嘿嘿,那你要准备好随时进行战斗。因为你的每一次发言都是对高手的挑衅。他会非常容易地进入战斗状态。这个时候,你只要记住一点,不管争论结果如何,你都是赢家。
最最忌讳的就是,将证明自己的观点作为你战斗的目标。因为,在高手看来,他往往能清楚的认识到,自己有可能存在的错误。而他追求的正是追求真理的快感,一旦你的立场错了,你会发现你越是面红耳赤,他却越是兴高采烈。
假如你也自认为高手。别的什么也不说,送你一句话:英雄惺惺相惜!
假如非常不幸,你不是一位高手,就像我。又有一位高手的手下。嘿嘿。我只能告诉你,你很走运。至少,你不是一个人在面对这个问题。
首先要设法满足高手的种种欲望。高手的求知欲,高手的表现欲,高手的自尊欲。尊敬你的高手,是你必须做的第一步。
帮助你的高手,安排他的时间。避免不必要的干扰。
不要随意将你的压力反反复复地传达给你的高手。高手很容易理解压力。不过他很可能会遇到很多自己无法处理的问题。这个时候,你要做的不是谈压力,而是给他解除干扰。
你很可能在某方面做的不好,在你意识到的第一时间和你的高手坦白。他会坚决支持你的。
让高手成为高手。一定要完全体现高手的地位和尊严。否则,干脆就不要高手。这位标杆一定要树好。他好了,你的一帮子人都会好起来。
愿你能像我一样,有一位高手在身旁!那是一件很幸福的事情。
五、 数据接口的实现(见Org.jpg文件)
Org.JSP文件用来在
服务器上运行
Java的类与前台web页之间架起一座桥。取到
中间件的接口作用。
这里分析部分代码:
<%@ page contentType="text/html; charset=GBK" %> <%@ page import="java.sql.*" %> <%@ page import="javax.naming.*" %> <%@ page import="javax.sql.*" %> <%@ page import="tool.*" %> <%@ page import="orgNew.*" %> <%@ page import="org.w3c.dom.*" %> //上面主要是引用一些java类 <% try{ //request.setCharacterEncoding("GBK"); Document doc = XmlTool.createDocumentFromRequest(request); //建立web面文档请求的文档对象 Connection conn = ConnTool.getConnectionFromPool(); //获取请求的方法名 String mode=request.getParameter("mode"); //out.println("ccc"); //如果方法中没有其它参数则读取组织树数据 if(mode == null){ /* int OrgId = Integer.parseInt(request.getParameter("id")); String str = orgManager.getChildOrg(OrgId, conn); out.println(str); */ String str = orgManager.getTree(conn); //out.println(str); out.println(str); }else if(mode.equals("createOrg")){ //如果是createOrg方法则建立一个组织 int parentOrgId = Integer.parseInt(request.getParameter("parentOrgId")); //取出传递来的第一个参数parentOrgId int OrgId = orgManager.createOrg(parentOrgId, conn); //调用orgManager 类的createOrg方法来建立一个组织 out.println(OrgId); //返回结果 } conn.close(); } catch(Exception e){ e.printStackTrace(); } %> |
六、 后台数据的实现 1.
数据结构的定义
这里,我们主要有三个表。一个是组织结构表,一个是人员表person,一个组织人员关联表orgPerson。组织结构表有OrgCode(组织代码)、OrgName(组织名称)、orgId(组织Id), parentOrgId(父Id)。人员表有personCode(人员代码)、personName(人员名称), sex(性别)、personId(人员Id)。orgPerson表有orgId, personId。
2.
数据库的连接
WEB应用程序常用
MySQL作后台数据库,这是因为MySQL简单、高效。这里我们也用MySQL作为数据库。Java中用jdbc连接数据库。下面是连接数据库的CODE:
public static Connection getConnectionFromPool() throws Exception { Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:/erpds"); return ds.getConnection(); }
/** * 取数据库链接对象 * @return Connection 数据库链接对象 * @throws Exception */ /* public static Connection getDirectConnection() throws Exception { Class.forName("com.sybase.jdbc2.jdbc.SybDriver"); String url = "jdbc:sybase:Tds:19.64.13.16:4100/wydb?charset=iso_1"; String user = "sa"; String password = "2860008"; Connection conn = DriverManager.getConnection(url, user, password); return conn; } */ |
3. 业务逻辑层的实现
后台开发我们用Java类来实现。这里我们开发了一个orgNew包,类名为orgManager。此类封装了与数据库操作有关的方法。通过main可调试程序的正确性。
这里给出了新增加一个组织的全部代码和通过XML取得树结构信息的代码,树结构通过递归实现。
package orgNew;// Java类所打的包 import tool.*; import java.sql.*; import java.util.*; // 引用Java类的 public class orgManager { public orgManager() { } public static void main(String[] args) throws Exception { Connection conn = tool.ConnTool.getDirectConnection();// 引用数据访问类 conn.setAutoCommit(false);
orgManager orgManager1 = new orgManager(); orgManager1.createOrg(0, conn); //测试建立组织是否正确 } //建立一个组织 public static int createOrg(int parentOrgId, Connection conn) throws Exception { String sql = "insert into Org (OrgName, parentOrgId) values('新组织', ?)"; PreparedStatement pstat = conn.prepareStatement(sql); pstat.setInt(1, parentOrgId); pstat.executeUpdate(); pstat.close();
Statement stat = conn.createStatement(); String sql2 = "select max(OrgId) from Org"; ResultSet rs = stat.executeQuery(sql2); rs.next(); int OrgId = rs.getInt(1); rs.close(); stat.close(); System.out.println(OrgId); return OrgId; } } //通过递归得到组织信息的XML格式的数据 public static String getTree(Connection conn) throws Exception { StringBuffer ret = new StringBuffer();//定义可缓冲的字符流 ret.append("<?xml version='1.0' encoding='gb2312'?><tree id='0'>");//定义XML格式的头信息 ret.append(" <item child='1' text='组织' id='1' >");//插入结点体。注树结点以item为标记 ret.append(getChildTree(1, conn)); ret.append("</item>");//结点体结束标记 ret.append("</tree>");//树结束标记 return ret.toString();//返回字符流 }
public static String getChildTree(int OrgId, Connection conn) throws Exception { StringBuffer ret = new StringBuffer(); String sql = "select a.OrgId, a.OrgName, a.OrgCode,count(b.parentOrgId) from Org a " + "left join Org b on a.OrgId = b.parentOrgId " + "where a.parentOrgId = ? " + "group by a.OrgId, a.OrgName"; PreparedStatement pstat = conn.prepareStatement(sql); pstat.setInt(1, OrgId); ResultSet rs = pstat.executeQuery(); while (rs.next()) { int childOrgId = rs.getInt(1); String childOrgName = rs.getString(2); String childOrgCode = rs.getString(3); if (childOrgCode == null) { childOrgCode = " "; } if (childOrgName == null) { childOrgName = "新组织"; } int childCount = rs.getInt(3); if (childCount > 0) { childCount = 1; } ret.append("<item child='" + childCount + "' text='" + childOrgName + "' id='" +childOrgId + "' code='"+childOrgCode+"'>"); ret.append(getChildTree(childOrgId, conn)); ret.append("</item>"); } rs.close(); pstat.close(); return ret.toString(); } |
其它代码见orgManager.java文件。
七、 总结 本文件通过一个实例全面介绍了
Ajax开发的各个细节。通过与
J2ee的结合来实现三层分布式开发的层次划分,后台与前端的调用。数据的读取、访问及展现。
通过这个实例,我们可见,Ajax使WEB中的界面与应用分离。数据与呈现分离的分离,有利于分工合作、减少非技术人员对页面的修改造成的WEB应用程序错误、提高效率、也更加适用于现在的发布系统。也可以把以前的一些服务器负担的工作转嫁到客户端,利于客户端闲置的处理能力来处理。
Ajax是传统WEB应用程序的一个转变。以前是服务器每次生成HTML页面并返回给客户端(浏览器)。Ajax理念的出现,揭开了无刷新更新页面时代的序幕,并有代替传统web开发中采用form(表单)递交方式更新web页面的趋势,可以算是一个里程碑。
四、
XML
与XSL文件设计
XML是种可扩展的标记语言,它具有开放的、可扩展的、可自描述的语言结构,它已经成为网上数据和文档传输的标准。XSLT的目的是将信息内容与 Web 显示分离,HTML 通过按抽象概念(如段落、重点和编号列表)定义显示来实现设备独立性。XSLT用来具体显示控件,设置控件风格。
Ajax主要使用XML和XSLT进行数据交换与处理。
1. 树信息的XML文件(见root.xml文件)
XML是标记语言,元素必须成对出现。树结构中以tree为根结点,以item为结点体,属性text指出结点所显示的文本,id指出唯一的所标识号。
<?xml version='1.0' encoding='gb2312'?> <tree id="0"> <item child="1" text="组织" id="1" > </item> </tree> |
这文件并不是必要的,只是为了系统能独立运行才加的。事实如果连接了后台数据是不需要的。只要吧OrgTree.loadXML("root.xml?0")改为OrgTree.loadXML("Org.jsp")就可以了。
2. 人员信息XML文件(见peorson.xml文件)
说明![CDATA[]]可在任何显示任何格式的文本,文本中可插入其它任何字符。这文件也不是必要的。
3. 人员信息展现的xsl文件(见addOrgPerson.xsl文件)
xsl文件同样是XML格式文件。所以一律遵守XML标准。下面对主要的行讲解:
<?xml version="1.0" encoding="gb2312"?> //这是定义xml文件的首行。用来指明版本及字符集 <xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl" language="JavaScript"> //这里定义了stylesheet 元素。并指出其国际命名的组织及语言。 <xsl:template match="/"> <xsl:apply-templates select="peorsones"/> </xsl:template> //上面是匹配的规则。"/"表示从根结开始去匹配。匹配到下面的peorsones标记。这是正则表达式有关的学问。我们只要理解就可以。 <xsl:template match="peorsones"> //当匹配上peorsones时所要做的事情。 <table id="tbList" border="1" width="100%"> //定义一个id为"tbList的表格。此表格是显示在WEB上的 <xsl:for-each select="peorsone"> //循环匹配peorsone <tr> //定义tbList表格的一行,并在行上增加一个叫seqNo的属性名,值为匹配到的seqNo(序号) <xsl:attribute name="seqNo"><xsl:value-of select="@seqNo"/></xsl:attribute> <td> //定义行上的一列,列又去匹配 <xsl:apply-templates select="."/> </td> </tr> </xsl:for-each> </table> </xsl:template>
<xsl:template match="peorsone"> <table border="1" width="100%"> <tr> //定义宽为5%的一列,在该列上插入一个checkbox控件 <td width="5%"> <input type="checkbox" value="on" size="10"></input> </td> //定义一个不显示的列,在该列上插入一个text控件,text的值为匹配到的personId(人员Id) <td style="display:none"> <input type="text" size="25"> <xsl:attribute name="value"><xsl:value-of select="personId"/></xsl:attribute> </input> </td> <td width="30%"> <input type="text" size="20"> <xsl:attribute name="value"><xsl:value-of select="personCode"/></xsl:attribute> </input> </td> <td width="40%"> <input type="text" size="40"> <xsl:attributename="value"><xsl:value-of select="personName"/></xsl:attribute> </input> </td> //定义一个width为28%的列,在该列上插入一个下拉列表select 控件,select的值如果匹配到为0时则为"男",1时则为"女" <td width="28%"> <select size="1"> <option value="0"> <xsl:if test=".[sex=0]"> <xsl:attribute name="selected">true</xsl:attribute> </xsl:if> 男 </option> <option value="1"> <xsl:if test=".[sex=1]"> <xsl:attribute name="selected">true</xsl:attribute> </xsl:if> 女</option> </select> </td> //定义一列,在该列上插入一个button控件,onclick 事件为自定义的方法,该方法传递当前单击的按纽 <td width="*"> <button onclick="openPersonRolePage(this)" style="width: 36; height: 21">角色</button> </td> </tr> </table> </xsl:template> </xsl:stylesheet> |
3. 人员管理的实现
人员可以增加、删除、编辑。同时当选择树结点时应该把人员显示出来供编辑、查看......
1) 增加人员
人员增加实现的原理是在personDom中加入结点peorsone,该结点相当于表的一行,设置属性。同时在peorsone中不继地加入其它结点,代表
数据库的字段,且必须与XLT文件的标号同名。这些结点相当该行的列。最后在表中插入一行,行上插入一列,并显示之。
function addPerson(){ var seqNo = nextSeq; nextSeq++; var peorsonNode = personDom.createNode("1", "peorsone",""); peorsonNode.setAttribute("isNew", "Y"); peorsonNode.setAttribute("isDelete", "N"); peorsonNode.setAttribute("seqNo", seqNo); personDom.documentElement.appendChild(peorsonNode); var PersonId= personDom.createNode("1", "personId", ""); peorsonNode.appendChild(PersonId); var personCode= personDom.createNode("1", "personCode", ""); peorsonNode.appendChild(personCode); var PersonName= personDom.createNode("1", "personName", ""); peorsonNode.appendChild(PersonName); var Sex= personDom.createNode("1", "sex", ""); peorsonNode.appendChild(Sex); var tr = tbList.insertRow(tbList.rows.length); tr.setAttribute("seqNo", seqNo); var td = tr.insertCell(0); td.innerHTML = peorsonNode.transformNode(stylesheet); } |
2) 删除人员
人员删除同样是调用Org.jsp 文件中的deletePerson方法来实现,该方法传递所删除的人员ID。如何确定人员ID是通过读取隐藏的ID,并扫描整个表,看那些被选中。这里我们要注意是提供多项选择的。
function deletePerson(){ for(var i=0; i<tbList.rows.length; i++){ var row=tbList.rows[i].cells[0].children[0].rows[0]; if(row.cells[0].children[0].checked) { var personId=row.cells[1].children[0].value; if(personId>0) { var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=deletePerson&personId=" + personId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); } tbList.deleteRow(i); i--; } } } |
3) 编辑人员
人员修改我们要判定哪些行被修改了。刚增加但没保存的行应该是新增而不是修改的。
function save(){ if( modifyOrg()=="N") { return; } for(var i=0; i<tbList.rows.length; i++) { var row=tbList.rows[i].cells[0].children[0].rows[0]; var personId=row.cells[1].children[0].value; var seqNo = tbList.rows[i].getAttribute("seqNo"); var staffNode = personDom.selectSingleNode("//peorsone[@seqNo='" + seqNo + "']"); var personCode=row.cells[2].children[0].value; var personName=row.cells[3].children[0].value; var sex=row.cells[4].children[0].value; //alert(staffN;ode ); if(staffNode.getAttribute("isNew") == "Y") { createPerson(CurrNodeId,personCode,personName,sex); } else { var strXML = "<?xml version='1.0' encoding='gb2312'?>" + "<data>" + "<personCode><![CDATA[" + personCode+ "]]></personCode>" + "<personName><![CDATA[" + personName + "]]></personName>" + "<sex><![CDATA[" + sex+ "]]></sex>" + "<personId><![CDATA[" + personId+ "]]></personId>" + "</data>"; //alert(strXML ); var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=modifyPerson", false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(strXML ); } } } |
三、 前端页面的主要编码
1. 树的实现
在WEB上实现树结构,同样我们是通过
Ajax来实现的。树上可以显示自定义的图标,可以插入、删除、结点。并且结点可任意移动。这里我们不重点讲树的实现技术,我们已经封装好了,你只要按要求去改动就是了。
1) 键接树型文件
在<head>与</head>之间键接我们的与树有关的文件, 代码如下:
<link rel="STYLESHEET" type="text/css" href="css/dhtmlXTree.css"> <script src="js/dhtmlXCommon.js"></script> <script src="js/dhtmlXTree.js"></script> |
2) 装载方法
在页面的文档打开时装载自定义方法, preLoadImages方法实现树控件的图标定义,doOnLoad实现树控件的图标定义代码如下:
<body onload="preLoadImages();doOnLoad();"> |
3) 编写方法
//doOnLoad实现装载并显示树。设置树属性等。 function doOnLoad(){ OrgTree=new dhtmlXTreeObject(document.getElementById('divTree'),"100%","100%",0); //dhtmlXTreeObject是树对象,通过新建对象,指定树显示的DIV可定义树。 OrgTree.setImagePath("imgs/");//设置树的图片所在位置 OrgTree.setDragHandler();//设置树结点拖动 OrgTree.enableDragAndDrop(true) //设置树结点是否可拖动 OrgTree.setDragHandler(myDragHandler); //设置树结点拖动时所执行的方法 OrgTree.setOnClickHandler(mySelectHandler); //设置树单击时所执行的方法 //OrgTree.setXMLAutoLoading("Org.jsp");//装载树结点数据。数据来源如Org.jsp所返回的XML格式的字符串,数据是动态装载,且当展开时才装载。 OrgTree.loadXML("root.xml?0");//装载树结点数据。数据来源root.xml文件,并且从xml文件的ID号为0处读取数据。 //OrgTree.loadXML("Org.jsp");//装载树结点数据。数据来源如Org.jsp所返回的XML格式的字符串,并且是一次性全部装载数据。 } //preLoadImages方法实现树控件的图标定义 function preLoadImages(){ var imSrcAr = new Array("line1.gif","line2.gif","line3.gif","line4.gif","minus2.gif","minus3.gif", "minus4.gif","plus2.gif","plus3.gif","plus4.gif","book.gif","books_open.gif","books_close.gif", "magazine_open.gif","magazine_close.gif","tombs.gif","tombs_mag.gif","book_titel.gif") var imAr = new Array(0); for(var i=0;i<imSrcAr.length;i++){ imAr[imAr.length] = new Image(); imAr[imAr.length-1].src = "imgs/"+imSrcAr[i] } } |
2. 组织管理的实现
组织可以增加、删除、编辑。同时当选择树结点时应该把组织显示出来供编辑,查看。为了实现这些功能,你只要按要求去改动就是了。
1) 全局变量的定义
许多地方我们要用到一些公共变量,我们在<script>与</script>之间定义全局变量, 代码如下:
var OrgTree = null; //组织树Dom var nextSeq = 0;//人员管理的顺序号(流水号) var personDom;//人员Dom var CurrNodeId;//当前结点Id |
2) 初始化
当页面打开时我们要控件好那部分该显示,那部分要隐藏。且对全局变量的赋值等,组织类型装载。在页面的文档打开时装载自定义方法init(), init方法实现初始化。
init方法实现如下:
function init(){ //定义personDom为一个XMLDOM'对象 personDom= new ActiveXObject('Microsoft.XMLDOM'); personDom.async = false; //定义stylesheet为一个XMLDOM'对象,且stylesheet为personDom确定显示风格 stylesheet = new ActiveXObject('Microsoft.XMLDOM'); stylesheet.async = false; stylesheet.load("addOrgPerson.xsl"); //装载stylesheet的风格定义文件
//装载组织类型数据 var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=GetOrgType", false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.responseText; // alert(retXml); //把组织类型插入下拉列表控件中 var OrgDoc = new ActiveXObject('Microsoft.XMLDOM'); OrgDoc.async = false; OrgDoc.loadXML(retXml); var root = OrgDoc.documentElement; oNodeList = root.childNodes; txtType.options.length =oNodeList.length; for (var i=0; i<oNodeList.length; i++) { Item = oNodeList.item(i); var OrgTypeId=Item.childNodes(0).text; var OrgTypeName=Item.childNodes(1).text; txtType.options[i].value=OrgTypeId; txtType.options[i].text=OrgTypeName; // txtType.options[0]. } } |
3) 编写树拖动及选择结点的方法
// myDragHandler实现树结点拖动时重新指定父子关系。 function myDragHandler(idFrom,idTo){ var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=moveOrg&orgId=" + idFrom + "&newparentOrgId=" + idTo, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.OrgponseText;
return true; } // mySelectHandler实现选择树结点对系统的控制,同时显示组织信息及该组织下的人员。 function mySelectHandler(id){ tbOrg.style.display="block"; divOrgMemo.style.display="none"; divOrgInfo.style.display="none";
if(id==1) { divOrgMemo.style.display="block"; div1.style.display="none"; div2.style.display="none"; div3.style.display="none"; divContent.style.display="none"; div5.style.display="none"; } else { divOrgInfo.style.display="block"; div1.style.display="block"; div2.style.display="block"; div3.style.display="block"; divContent.style.display="block"; div5.style.display="block"; } CurrNodeId=id; //装载组织信息并显示在编码和名称的文本控件上。 loadOrg(id); //装载某组织下人员信息 var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=GetPerson&orgId=" + id, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send();
retXml=xmlhttp.responseText; personDom.loadXML (retXml); //给人员信息的每行加上序号 for(var i=0; i<personDom.documentElement.childNodes.length; i++){ personDom.documentElement.childNodes[i].setAttribute("seqNo", nextSeq); nextSeq++; } //人员信息显示在divContent上面 divContent.innerHTML = personDom.transformNode(stylesheet); }; //装载组织信息并显示在编码和名称的文本控件上。 function loadOrg(OrgId){ if(OrgId == null){ OrgId = OrgTree.getSelectedItemId(); } if(OrgId == ""){ tbOrg.style.display = "none"; return; } var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=loadOrg&OrgId=" + OrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.responseText; var OrgDoc = new ActiveXObject('Microsoft.XMLDOM'); OrgDoc.async = false; OrgDoc.loadXML(retXml); if(OrgId != 1){ txtCode.value = OrgDoc.selectSingleNode("//OrgCode").text; txtName.value = OrgDoc.selectSingleNode("//OrgName").text; } tbOrg.style.display = "block"; } |
4) 建立组织
组织建立主要是通过调用XMLHTTP对象来实现。我们主要学会如何调用XMLHTTP。组织建立应该在后台实现,把组织信息插入
数据库中。这里我们通过JSP来实现。我们的Org.jsp 文件中有个createOrg方法,该方法传递一个父ID。
function createOrg(parentOrgId){ var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=createOrg&parentOrgId=" + parentOrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); retXml=xmlhttp.responseText; var orgId = (new Number(retXml)).toString(); return orgId; } |
5) 删除组织
组织删除同样是调用Org.jsp 文件中的deleteOrg方法来实现,该方法传递所删除的结点ID。
function deleteOrg(){ var OrgId = OrgTree.getSelectedItemId(); var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=deleteOrg&OrgId=" + OrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(); } |
6) 编辑组织
组织修改是调用Org.jsp 文件中的modifyOrg方法来实现,该方法传递所修改的结点ID。同时修改的数据通过自定义的XML格式的字符串传送,这时通过send字符串来实现。修改前数据一律要验证其合法性,并提示错误信息。
function modifyOrg(){ if(OrgTree.getSelectedItemId() == ""){ return "N"; } if(txtCode.value == ""){ alert("请输入编码!"); return "N"; } if(txtName.value == ""){ alert("请输入名称!"); return"N"; } var OrgId = OrgTree.getSelectedItemId(); var OrgKind; //alert(txtType.options[txtType.selectedIndex].value) var strModify = "<?xml version='1.0' encoding='gb2312'?>" + "<data>" + "<OrgCode><![CDATA[" + txtCode.value + "]]></OrgCode>" + "<OrgName><![CDATA[" + txtName.value + "]]></OrgName>" + "<OrgKind><![CDATA[" + txtType.options[txtType.selectedIndex].value+ "]]></OrgKind>" + "</data>"; var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); xmlhttp.open("POST","Org.jsp?mode=modifyOrg&OrgId=" + OrgId, false); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(strModify); OrgTree.setItemText(OrgTree.getSelectedItemId(),txtName.value); } |
一、 概述
AJAX是今年初才问世的新技术,是Asynchronous JavaScript and XML的缩写。它是一组开发Web应用程序的技术,它使浏览器可以为用户提供更为自然的浏览体验。每当需要更新时,客户端Web页面的修改是异步的和逐步增加的。
这样,AJAX在提交Web页面内容时大大提高了用户界面的速度。在基于AJAX的应用程序中没有必要长时间等待整个页面的刷新。页面中需要更新的那部分才进行更改,如果可能的话,更新是在本地完成的,并且是异步的。
J2ee是一种用来开发分布式系统的体系结构。它主要是用Java类开发业务实体。通过JSP来连接应用服务器。
本文开发一个组织机构管理小系统,通过这个实例来介绍如何用Ajax开发WEB应用程序。本系统具有增加、修改、删除组织机构的功能。同时给机构分配人员,能增加、修改、删除人员。
二、 界面设计
树结构是大多软件系统中常采用的结构形式。由于树型结构层次分明、上下级关系清楚、且展开收缩表达信息方便、界面也较美观,所以是大家热衷于用此结构。组织机构管理是一般软件基本具有的。组织机构是指公司的组织结构。集团公司可包括分公子公司,公司下面又有科室。员工归属于所在的公司。系统运行后的界面如下:
orgManager.htm是组织机构管理的主页面。WEB应用程序界面设计是非常重要的。如何布局、么样组织可直接体现一个人的设计水平。
组织机构主要包括树结构、组织机构编辑、人员编辑等三大块,如何分成三块呢,然而一般树型结构的窗体常先二块,树型结构独占一块,另一块又分成上下二部分,上面是机构编码,下面是人员编码。固可以把页面划分成如下图形式:
显然我们是通过表来实现。这是一个二行二列的表,且第一、二行的左边列合并单元格。代码如下:
< TABLE border="1" width="100%" height="100%"> <TR> <TD rowspan="2"></TD> <TD></TD> </TR> <TR> <TD></TD> </TR> </TABLE> |
我们在1区(单元格)上加上一个DIV,因为DIV可以动态地滚动,并且可以插入其它控件。DIV的id为"divTree",且风格设置为溢出时自动滚动,宽与高都为100%,及满区域。代码如下:
<div id="divTree" style="width:100%; height:100%;background-color:#f5f5f5;border :1px solid Silver;overflow:auto;"> </div> |
我们在2区(单元格)上也加上一个DIV,在DIV里再插入一个表格。表格上放下控件,这很简单,就不详细说了。
我们在3区(单元格)上加上一个DIV。此DIV的id为" divContent ",且风格设置为竖直溢出时自动滚动,宽与高都为100%,及满区域,此DIV用来装载人员信息;在DIV里再插入一个表格, 此table的id为" tbList ",是用来输入、显示人员作息,同时在此表中插入一些如checkbox 、text、select等控件。说明,表的第二列是用来放人员唯一编号的,不显示。代码如下:
<div id="divContent" style="height:100%; overflow-y:auto;" width="100%"> <table id="tbList" border="1" width="100%"> <tr seqNo="1"><td> <table border="1" width="100%"> <tr> <td width="5%"><input type="checkbox" value="on"></input> </td> <td width="0%" style="display:none"> <input type="text" size="20"></input></td> <td width="40%"><input type="text" size="20"></input></td> <td width="25%"> <select size="1" name="D1"> <option value="0">男</option> <option selected="true" value="1">女</option> </select> </td> </tr> </table> </td></tr> </table> </div> |
摘要: 一个
Jbpm
员工请假流程的实例
作者:吴大愚
...
阅读全文
摘要: 对
Jbpm
数据库应用的简单分析和在
Mysql
上实现的
demo
吴大愚
...
阅读全文
java开发者 网友 溪涧
多谢了!
最近工作中用到报表,而我在学习JasperReport的过程中遇到了很多问题(主要是国内的资料太少了),网上很少找得到,在此我就把我找到的一些资料和大家共享,希望能对大家有所帮助。
1、JasperReport和iReport的资源,最新版本可以到下面官方网站得到
iReport官方网站:
http://ireport.sourceforge.net
JasperReport官方网站:
http://jasperreports.sourceforge.net
2、安装
1)、JDK的安装,并配置JAVA_HOME
比如我的JAVA_HOME路径如下:
JAVA_HOME D:\Program Files\j2sdk1.4.2_03
2)、由于中文的问题,所以还需要下载:itext-1.02b.jar和iTextAsian.jar包
下载地址:http://itext.sourceforge.net/downloads/iTextAsian.jar
并在CLASSPATH中设置
例如我的CLASSPATH如下:
CLASSPATH
E:\Program Files\Apache Group\Tomcat4.1\webapps\testreport\WEB-INF\lib\itext-1.02b.jar;E:\Program
Files\Apache Group\Tomcat 4.1\webapps\testreport\WEB-INF\lib\iTextAsian.jar;E:\Program Files\Apache
Group\Tomcat 4.1\webapps\testreport\WEB-INF\lib;D:\tools\iReport0.2.3\lib
3)、iReport的安装iReport只要解压就OK,如果没有安装Ant,可以直接在iReport下的noAnt目录下,
运行startup.bat就可以了,这样iReport就可以启动了
4)、JasperReport
Jasperreport不需要任何配置,你只需将下载以后的jar包放到classpath下即可
5)、数据库的JDBC驱动包
加入到CLASSPATH中
3、详细资源
iReport官方提供了一些关于iReport视频,对于初学者很有帮助:
地址:http://ireport.sourceforge.net/docs.html
JasperReport官方提供的使用指南
地址:http://jasperreports.sourceforge.net/tutorial/index.html
JasperReport提供的一些例子:
地址:http://jasperreports.sourceforge.net/samples/index.html
4、常见问题
1)、iReport中提示框输入中文是不能正常显示,请将iReport下lib中的这个包删除tinylaf.jar
2)、在iReport中运行报表时如果出现乱码问题,请检查itext-1.02b.jar和iTextAsian.jar这两个包是否加到CLASSPATH
3)、在jsp或servlet高度报表时出现乱码或不显示,请检查你在报表设计过程中所设置的字体及其编码
比如:pdfname、pdfencoding
5、下面是两个调试例子
Servlet:
import javax.servlet.*;
import javax.servlet.http.*;
import dori.jasper.engine.*;
import java.io.*;
import java.util.*;
import java.sql.*;
/**
* @author Administrator
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class TestReport extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Connection conn = null;
try {
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
conn =
DriverManager.getConnection(
"jdbc:microsoft:sqlserver://192.168.0.10:1433;DatabaseName=am;user=sa;password=sa");
ServletContext servletContext =this.getServletContext();
File reportFile = new File(servletContext.getRealPath("test/iteminfo.jasper"));
Map parameters = new HashMap();
Integer i=new Integer(8);
parameters.put("pjId", i);
byte[] bytes =
JasperRunManager.runReportToPdf(
reportFile.getPath(),
parameters,
conn);
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
ServletOutputStream ouputStream = response.getOutputStream();
ouputStream.write(bytes, 0, bytes.length);
ouputStream.flush();
ouputStream.close();
} catch (JRException jre) {
System.out.println("JRException:" + jre.getMessage());
} catch (Exception e) {
System.out.println("Exception:" + e.getMessage());
}
}
public void doPost(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}