D盘

workspace
posts - 165, comments - 53, trackbacks - 0, articles - 0
  IT博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Spring是一个非常优秀的轻量级框架,通过Spring的IoC容器,我们的关注点便放到了需要实现的业务逻辑上。对AOP的支持则能让我们动态增强业务方法。编写普通的业务逻辑Bean是非常容易而且易于测试的,因为它能脱离J2EE容器(如Servlet,jsp环境)单独进行单元测试。最后的一步便是在Spring框架中将这些业务Bean以XML配置文件的方式组织起来,它们就按照我们预定的目标正常工作了!非常容易!

本文将给出一个基本的Spring入门示例,并演示如何使用Spring的AOP将复杂的业务逻辑分离到每个方面中。

1.开发环境配置
2.编写Bean接口及其实现
3.在Spring中配置Bean并获得Bean的实例
4.编写Advisor以增强ServiceBean
5.总结

1.开发环境配置
首先,需要正确配置Java环境。推荐安装JDK1.4.2,并正确配置环境变量:

JAVA_HOME=<JDK安装目录>
CLASSPATH=.
Path=%JAVA_HOME%\bin;……

我们将使用免费的Eclipse 3.1作为IDE。新建一个Java Project,将Spring的发布包spring.jar以及commons-logging-1.0.4.jar复制到Project目录下,并在Project > Properties中配置好Java Build Path:

 

点击查看大图

2.编写Bean接口及其实现
我们实现一个管理用户的业务Bean。首先定义一个ServiceBean接口,声明一些业务方法:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
/** * Interface of service facade. *  * @author Xuefeng */public interface ServiceBean {    void addUser(String username, String passWord);    void deleteUser(String username);    boolean findUser(String username);    String getPassword(String username);}
然后在MyServiceBean中实现接口:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 *  * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import java.util.*;
public class MyServiceBean implements ServiceBean {
    private String dir;    private Map map = new HashMap();
    public void setUserDir(String dir) {        this.dir = dir;        System.out.println("Set user dir to: " + dir);    }
    public void addUser(String username, String password) {        if(!map.containsKey(username))            map.put(username, password);        else            throw new RuntimeException("User already exist.");    }
    public void deleteUser(String username) {        if(map.remove(username)==null)            throw new RuntimeException("User not exist.");    }
    public boolean findUser(String username) {        return map.containsKey(username);    }
    public String getPassword(String username) {        return (String)map.get(username);    }}

为了简化逻辑,我们使用一个Map保存用户名和口令。

现在,我们已经有了一个业务Bean。要测试它非常容易,因为到目前为止,我们还没有涉及到Spring容器,也没有涉及到任何Web容器(假定这是一个Web应用程序关于用户管理的业务Bean)。完全可以直接进行Unit测试,或者,简单地写个main方法测试:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
public class Main {
    public static void main(String[] args) throws Exception {        ServiceBean service = new MyServiceBean();        service.addUser("bill", "hello");        service.addUser("tom", "goodbye");        service.addUser("tracy", "morning");        System.out.println("tom's password is: " + service.getPassword("tom"));        if(service.findUser("tom")) {            service.deleteUser("tom");        }    }}
执行结果:


3.在Spring中配置Bean并获得Bean的实例
我们已经在一个main方法中实现了业务,不过,将对象的生命周期交给容器管理是更好的办法,我们就不必为初始化对象和销毁对象进行硬编码,从而获得更大的灵活性和可测试性。

想要把ServiceBean交给Spring来管理,我们需要一个XML配置文件。新建一个beans.xml,放到src目录下,确保在classpath中能找到此配置文件,输入以下内容:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans>    <bean id="service" class="com.crackj2ee.example.spring.MyServiceBean" /></beans>
以上XML声明了一个id为service的Bean,默认地,Spring为每个声明的Bean仅创建一个实例,并通过id来引用这个Bean。下面,我们修改main方法,让Spring来管理业务Bean:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;
public class Main {
    public static void main(String[] args) throws Exception {        // init factory:        XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));        // use service bean:        ServiceBean service = (ServiceBean)factory.getBean("service");        service.addUser("bill", "hello");        service.addUser("tom", "goodbye");        service.addUser("tracy", "morning");        System.out.println("tom's password is \"" + service.getPassword("tom") + "\"");        if(service.findUser("tom")) {            service.deleteUser("tom");        }        // close factory:        factory.destroySingletons();    }}

执行结果:
 

由于我们要通过main方法启动Spring环境,因此,首先需要初始化一个BeanFactory。红色部分是初始化Spring的BeanFactory的典型代码,只需要保证beans.xml文件位于classpath中。

然后,在BeanFactory中通过id查找,即可获得相应的Bean的实例,并将其适当转型为合适的接口。

接着,实现一系列业务操作,在应用程序结束前,让Spring销毁所有的Bean实例。

对比上一个版本的Main,可以看出,最大的变化是不需要自己管理Bean的生命周期。另一个好处是在不更改实现类的前提下,动态地为应用程序增加功能。

4.编写Advisor以增强ServiceBean
所谓AOP即是将分散在各个方法处的公共代码提取到一处,并通过类似拦截器的机制实现代码的动态织入。可以简单地想象成,在某个方法的调用前、返回前、调用后和抛出异常时,动态插入自己的代码。在弄清楚Pointcut、Advice之类的术语前,不如编写一个最简单的AOP应用来体验一下。

考虑一下通常的Web应用程序都会有日志记录,我们来编写一个LogAdvisor,对每个业务方法调用前都作一个记录:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import java.lang.reflect.Method;import org.springframework.aop.MethodBeforeAdvice;
public class LogAdvisor implements MethodBeforeAdvice {    public void before(Method m, Object[] args, Object target) throws Throwable {        System.out.println("[Log] " + target.getClass().getName() + "." + m.getName() + "()");    }}
然后,修改beans.xml:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>    <bean id="serviceTarget" class="com.crackj2ee.example.spring.MyServiceBean" />
    <bean id="logAdvisor" class="com.crackj2ee.example.spring.LogAdvisor" />
    <bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean">        <property name="proxyInterfaces"><value>com.crackj2ee.example.spring.ServiceBean</value></property>        <property name="target"><ref local="serviceTarget"/></property>        <property name="interceptorNames">            <list>                <value>logAdvisor</value>            </list>        </property>    </bean></beans>
注意观察修改后的配置文件,我们使用了一个ProxyFactoryBean作为service来与客户端打交道,而真正的业务Bean即MyServiceBean被声明为serviceTarget并作为参数对象传递给ProxyFactoryBean,proxyInterfaces指定了返回的接口类型。对于客户端而言,将感觉不出任何变化,但却动态加入了LogAdvisor,关系如下:
 


运行结果如下,可以很容易看到调用了哪些方法:
 

要截获指定的某些方法也是可以的。下面的例子将修改getPassword()方法的返回值:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;
public class PasswordAdvisor implements MethodInterceptor {    public Object invoke(MethodInvocation invocation) throws Throwable {        Object ret = invocation.proceed();        if(ret==null)            return null;        String password = (String)ret;        StringBuffer encrypt = new StringBuffer(password.length());        for(int i=0; i<password.length(); i++)            encrypt.append('*');        return encrypt.toString();    }}
这个PasswordAdvisor将截获ServiceBean的getPassword()方法的返回值,并将其改为"***"。继续修改beans.xml:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans>    <bean id="serviceTarget" class="com.crackj2ee.example.spring.MyServiceBean" />
    <bean id="logAdvisor" class="com.crackj2ee.example.spring.LogAdvisor" />
    <bean id="passwordAdvisorTarget" class="com.crackj2ee.example.spring.PasswordAdvisor" />
    <bean id="passwordAdvisor" class="org.springframework.aop.support.RegeXPMethodPointcutAdvisor">        <property name="advice">            <ref local="passwordAdvisorTarget"/>        </property>        <property name="patterns">            <list>                <value>.*getPassword</value>            </list>        </property>    </bean>
    <bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean">        <property name="proxyInterfaces"><value>com.crackj2ee.example.spring.ServiceBean</value></property>        <property name="target"><ref local="serviceTarget"/></property>        <property name="interceptorNames">            <list>                <value>logAdvisor</value>                <value>passwordAdvisor</value>            </list>        </property>    </bean></beans>

利用Spring提供的一个RegexMethodPointcutAdvisor可以非常容易地指定要截获的方法。运行结果如下,可以看到返回结果变为"******":
 

还需要继续增强ServiceBean?我们编写一个ExceptionAdvisor,在业务方法抛出异常时能做一些处理:

/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import org.springframework.aop.ThrowsAdvice;
public class ExceptionAdvisor implements ThrowsAdvice {    public void afterThrowing(RuntimeException re) throws Throwable {        System.out.println("[Exception] " + re.getMessage());    }}
将此Advice添加到beans.xml中,然后在业务Bean中删除一个不存在的用户,故意抛出异常:

service.deleteUser("not-exist");
再次运行,注意到ExceptionAdvisor记录下了异常:
 

5.总结
利用Spring非常强大的IoC容器和AOP功能,我们能实现非常灵活的应用,让Spring容器管理业务对象的生命周期,利用AOP增强功能,却不影响业务接口,从而避免更改客户端代码。

为了实现这一目标,必须始终牢记:面向接口编程。而Spring默认的AOP代理也是通过Java的代理接口实现的。虽然Spring也可以用CGLIB实现对普通类的代理,但是,业务对象只要没有接口,就会变得难以扩展、维护和测试。

欢迎来信与作者交流:asklxf@163.com

完整的Eclipse工程:springbasic.rar

(出处:http://www.knowsky.com

 

posted @ 2009-04-17 12:07 巴西木 阅读(452) | 评论 (0)编辑 收藏

要写一些简单的日志到文本文件中,参考了以下两篇文章:
ifstream 和 ofstream( 引用)
C++标准库 之 iostream库的学习笔记(二)fstream库以及ofstream类的使用


ofstream是从内存到硬盘,ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间;

在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:

1、插入器(< < )
  向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout< < " Write Stdout" < < '\n'; 就表示把字符串" Write Stdout" 和换行字符('\n')输出到标准输出流。

2、析取器(> > )
  从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin> > x; 就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。

  在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。下面就把此类的文件操作过程一一道来。

一、打开文件
  在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:

void open(const char* filename,int mode,int access);

参数:

filename:  要打开的文件名
mode:    要打开文件的方式
access:   打开文件的属性
打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下:

ios::app:   以追加的方式打开文件
ios::ate:   文件打开后定位到文件尾,ios:app就包含有此属性
ios::binary:  以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in:    文件以输入方式打开(文件数据输入到内存)
ios::out:   文件以输出方式打开(内存数据输出到文件)
ios::nocreate: 不建立文件,所以文件不存在时打开失败
ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc:  如果文件存在,把文件长度设为0
  可以用“或”把以上属性连接起来,如ios::out|ios::binary

  打开文件的属性取值是:

0:普通文件,打开访问
1:只读文件
2:隐含文件
4:系统文件
  可以用“或”或者“+”把以上属性连接起来 ,如3或1|2就是以只读和隐含属性打开文件。

  例如:以二进制输入方式打开文件c:\config.sys
fstream file1;
file1.open(" c:\\config.sys" ,ios::binary|ios::in,0);

  如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:
file1.open(" c:\\config.sys" ); < => file1.open(" c:\\config.sys" ,ios::in|ios::out,0);

  另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:
fstream file1(" c:\\config.sys" );

  特别提出的是,fstream有两个子类:ifstream(input file stream)和ofstream(outpu file stream),ifstream默认以输入方式打开文件,而ofstream默认以输出方式打开文件。
ifstream file2(" c:\\pdos.def" ); //以输入方式打开文件
ofstream file3(" c:\\x.123" ); //以输出方式打开文件

  所以,在实际应用中,根据需要的不同,选择不同的类来定义:如果想以输入方式打开,就用ifstream来定义;如果想以输出方式打开,就用ofstream来定义;如果想以输入/输出方式来打开,就用fstream来定义。

二、关闭文件
  打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作,如:file1.close(); 就把file1相连的文件关闭。

三、读写文件
  读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了;而对于二进制的读取就要复杂些,下要就详细的介绍这两种方式

  1、文本文件的读写
  文本文件的读写很简单:用插入器(< < )向文件输出;用析取器(> > )从文件输入。假设file1是以输入方式打开,file2以输出打开。示例如下:

  file2< < " I Love You" ; //向文件写入字符串" I Love You"
  int i;
  file1> > i; //从文件输入一个整数值。

  这种方式还有一种简单的格式化能力,比如可以指定输出为16进制等等,具体的格式有以下一些

操纵符 功能 输入/输出
dec 格式化为十进制数值数据 输入和输出
endl 输出一个换行符并刷新此流 输出
ends 输出一个空字符 输出
hex 格式化为十六进制数值数据 输入和输出
oct 格式化为八进制数值数据 输入和输出
setpxecision(int p) 设置浮点数的精度位数 输出

  比如要把123当作十六进制输出:file1< < hex< < 123; 要把3.1415926以5位精度输出:file1< < setpxecision(5)< < 3.1415926。

  2、二进制文件的读写
①put()
  put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put('c'); 就是向流写一个字符'c'。

②get()
  get()函数比较灵活,有3种常用的重载形式:

  一种就是和put()对应的形式:ifstream &get(char & ch); 功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x); 表示从文件中读取一个字符,并把读取的字符保存在x中。

  另一种重载形式的原型是: int get(); 这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get(); 和上例功能是一样的。

  还有一种形式的原型是:ifstream &get(char *buf,int num,char delim='\n');这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符'\n'。例如:

  file2.get(str1,127,'A');     //从文件中读取字符到字符串str1,当遇到字符'A'或读取了127个字符时终止。

③读写数据块
  要读写二进制数据块,使用成员函数read()和write()成员函数,它们原型如下:

    read(unsigned char *buf,int num);
    write(const unsigned char *buf,int num);

  read()从文件中读取 num 个字符到 buf 指向的缓存中,如果在还未读入 num 个字符时就到了文件尾,可以用成员函数 int gcount(); 来取得实际读取的字符数;而 write() 从buf 指向的缓存写 num 个字符到文件中,值得注意的是缓存的类型是 unsigned char *,有时可能需要类型转换。

例:

    unsigned char str1[]=" I Love You" ;
    int n[5];
    ifstream in(" xxx.xxx" );
    ofstream out(" yyy.yyy" );
    out.write(str1,strlen(str1)); //把字符串str1全部写到yyy.yyy中
    in.read((unsigned char*)n,sizeof(n)); //从xxx.xxx中读取指定个整数,注意类型转换
    in.close(); out.close();

四、检测EOF
  成员函数eof()用来检测是否到达文件尾,如果到达文件尾返回非0值,否则返回0。原型是int eof();

例:  if(in.eof())    ShowMessage(" 已经到达文件尾!" );

五、文件定位
  和C的文件操作方式不同的是,C++ I/O系统管理两个与一个文件相联系的指针。一个是读指针,它说明输入操作在文件中的位置;另一个是写指针,它下次写操作的位置。每次执行输入或输出时,相应的指针自动变化。所以,C++的文件定位分为读位置和写位置的定位,对应的成员函数是seekg()和seekp()。seekg()是设置读位置,seekp是设置写位置。它们最通用的形式如下:

    istream &seekg(streamoff offset,seek_dir origin);
    ostream & seekp(streamoff offset,seek_dir origin);

  streamoff定义于 iostream.h 中,定义有偏移量 offset 所能取得的最大值,seek_dir 表示移动的基准位置,是一个有以下值的枚举:

ios::beg:  文件开头
ios::cur:  文件当前位置
ios::end:  文件结尾

  这两个函数一般用于二进制文件,因为文本文件会因为系统对字符的解释而可能与预想的值不同。例:

   file1.seekg(1234,ios::cur);     //把文件的读指针从当前位置向后移1234个字节
   file2.seekp(1234,ios::beg);     //把文件的写指针从文件开头向后移1234个字节



iostream库不仅支持终端设备的输入输出,还支持文件的输入输出,和文件有关的输入输出类声明在fstream头文件中,有三个类负责文件的输入输出

1) ifstream类:从istream类派生。
2) ofstream类:从ostream类派生。
3) fstream类:从iostream类派生。

由于文件的输入输出和键盘鼠标的输入输出是不一样的,一般pc机只有一个键盘设备,所以iostream库内部声明了一个istream类的对象cin,这个对象负责从键盘获取数据,而文件设备在系统中是由许多的,所以iostream库内部无法给你为机器的每个文件都创建一个负责获取数据的ifstream对象和负责写入数据的ofstream对象,所以我们要针对一个文件进行读取或写入数据的时候都要自己创建一个ifstream或ostream类的对象来用。

ofstream类的默认构造函数如下:

ofstream::ofstream(const char* filename, int mode = ios::outint openport = filebuf::openport);

filename是要打开的文件名,
mode是打开的方式,
openport是打开文件的属性。

mode可以设置的方式如下:
ios::app        以追加的方式打开
ios::ate        文件打开后定位到文件尾
ios::binary    以二进制方式打开文件,默认是以文本方式打开
ios::in          文件以读(输入)方式打开
ios::out        文件以写(输出)方式打开
ios::trunc     如果文件存在,则把文件清空。
以上属性用“|”(按位或)连接起来。

openprot属性如下:
0    普通文件
1    只读文件
2    隐含文件
4    系统文件
以上属性可以用加或者按位或方式组织起来,比如1|2和3都代表既是只读又是隐含文件。

在windows操作系统中可以不要第三个参数,如果加入第三个参数,那第三个参数是打开文件的共享方式,也就是打开这个文件时,其他进程是否可以读写该文件。
共享方式参数可以是下面的值:
0x10                   //_SH_DENYRW   Denies   read   and   write   access   to   the   file
0x20                   //_SH_DENYWR   Denies   write   access   to   the   file
0x30                   //_SH_DENYRD   Denies   read   access   to   the   file.
0x40                   //_SH_DENYNO   Permits   read   and   write   access  
其他值都会报 "Invalid   sharing   flag "的错误。

    ofstream hFile("c:\\1.txt", ios::out, _SH_DENYRW); // _SH_DENYRW is deny read and write
    
    
if (!hFile) // if the file could open, hFile is a handle, else is zero
    {
        cout 
<< "write fail!" << endl;
        cout 
<< "access is denies,maybe the file is readonlys,or use deny read opened of other process." << endl;
    }

    
else
    
{
        hFile 
<< "by coderlee writes";
        cout 
<< "write success!" << endl;
    }

    hFile.close(); 
// opened file need close.

上面是写文件的事例代码,先打开文件,然后判断是不是0,如果是0,则提示write fail否则写文件,提示write success.



posted @ 2009-04-15 13:38 巴西木 阅读(39474) | 评论 (6)编辑 收藏

用 C++ 创建简单的 Win32 服务程序

作者:Nigel Thomson(MSDN 技术组)
翻译:NorthTibet

原文出处:Creating a Simple Win32 Service in C++

下载 NTService 例子源代码
下载 NTServCpl 例子源代码
下载 NTServCtrl 例子源代码

摘要

  本文描述如何用 Visual C++ 创建 Windows NT 服务程序。创建该服务仅用到一个 C++ 类,这个类提供服务与操作系统之间一个简单的接口。使用这个类实现自己的服务非常简单,只要改写少数几个基类中的虚拟函数即可。在本文有三个源代码参考例子:

  • NTService 是一个简单的 Win32 服务,它就是用本文所描述的方法建立的;
  • NTServCpl 是一个控制面版程序,用来控制 NTService 服务;
  • NTServCtrl 是一个独立的程序例子,用它可以监控某个 Win32 服务;

简介

  Windows NT 中的服务实际上是一个程序,只要计算机操作系统一启动,服务就可以运行其中。它不需要用户登陆。服务程序是一种与用户无关的任务,比如目录复制,进程监控或网络上供其它机器使用的服务,比如 HTTP 协议支持。
  创建 Windows NT 服务程序并不是很难。但调试某个服务程序不是一件容易的事。就我自己而言,我喜欢用 Visual C++ 编写自己的 C++ 程序。大多数 Win32 服务都是用 C 写的,所以我觉得如果用某个 C++ 类来实现 Win32 服务的基本功能一定很有意思。有了这个 C++ 类,谁要想用 C++ 创建 Win32 服务就是一件很简单的事情了。我为此开发了一个 C++ 基类,用它作为编写 Win32 服务的起点应该没有什么大问题。

创建服务程序除了编写服务代码外,还必须做一些其它额外的编码工作:

  • 在系统日志或应用程序日志中报告警告信息和出错信息,不能用输出到屏幕的方式,因为用户根本就没有登陆。
  • 服务程序的控制即可以通过单独的应用程序,也可以通过控制面版程序。这取决于你的服务实现什么样的通讯机制。
  • 从系统中安装和卸载服务

  大多数服务程序都是使用一个安装程序来安装,而用另外一个程序来卸载。本文我将这些功能内建在服务程序自身当中,使之一体化,这样只分发一个.EXE文件即可。你可以从命令行直接运行服务程序,并且可以随心所欲地安装和卸载或报告其版本信息。NTService 支持下列的命令行参数:

  • -v, 报告服务的名字和版本号;
  • -i, 安装服务;
  • -u, 卸载服务;

默认情况下,当系统启动该服务时没有命令行参数传递。

创建应用程序框架

  我一直都是创建基于 MFC 的应用程序。当我刚接触 Win32 服务程序时,我先是用 Visual C++ AppWizard 创建一个 SDI/MFC 程序。然后去掉其中的文档和视图类、图标以及其它一些无用的东西,只剩下框架。结果到最后什么都去掉了,包括主窗口(服务程序不能有这个东东),什么也没有留下,非常愚蠢。我不得不 又回过头到 AppWizard,并用单个的源文件创建控制台程序,此源文件包含main 入口函数,我将这个文件命名为 NTServApp.cpp。我用此 cpp 扩展而不是用 C,因为我只想用C++ 来写程序,而不是直接用 C。稍后我们会讨论该文件代码实现。
  因为我想用 C++ 类来构建服务,所以我创建了 NTService.h 和 NTService.cpp 文件,用它们来实现 CNTService 基类。我还创建了 MyService.h 和 MyService.cpp 文件用于实现自己的服务类(CMyService),它派生于 CNTService。稍后我们会看到代码。
  建立新工程时,我喜欢尽快看到运行结果,所以我决定服务程序要做的第一件事情是建立一个系统应用程序日志记录。借助这个日志记录机制,我能跟踪服务何时启动, 何时停止等等。我还可以记录服务中发生的任何出错信息。创建这个日志记录比我想象的要复杂得多。

建立日志记录
  我想,既然日志文件是操作系统的一部分,那么肯定有应用程序编程接口(API)来支持建立日志记录。所以我开始搜索 MSDN CD,直到发现 ReportEvent 函数为止。如果你不熟悉这个函数,你可能会想,这个函数应该知道在哪个日志文件建立记录,以及你想要插入的文本信息。没错,这都是它要做的事情,但是为了简化出错信息的国际化,该函数有一个消息 ID 作为参数,并在你提供的消息表中查找消息。所以问题无非是你想将什么消息放入日志,以及如何将这些消息添加到你的应用程序中,下面我们一步一步来做:

  1. 以 .MC 为扩展名创建一个包含消息描述的文本文件。我将它命名为 NTServMsg.mc。该文件的格式非常特别,具体细节参见 Platform SDK 文档;
  2. 针对你的源文件运行消息编译器(mc.exe),默认情况下它创建名为 MSG00001.BIN 的输出文件。编译器还创建一个头文件(在我的例子程序中,该头文件是 NTServMsg.h)和一个.RC 文件(NTServMsg.rc)。只要你修改工程的 .MC 文件就必须重复这一步,所以把工具加到 Visual C++ 的工具菜单里做起来会很方便;
  3. 为工程创建一个 .RC 文件,将 WINDOWS.H 头文件以及消息编译器产生的 .RC 文件包含到其中;
  4. 在主工程头文件中包含消息编译器产生的头文件,以便模块可以存取符号消息名;

  下面让我们仔细一下这些文件,以便弄明白你自己需要创建什么,以及消息编译器要为你创建些什么。我们不用研究整个消息集,只要看看其中一二个如何工作的即可。下面是例子程序消息源文件 NTServMsg.mc 的第一部分:

MessageId=100
SymbolicName=EVMSG_INSTALLED
Language=English
The %1 service was installed.
.
MessageId=
SymbolicName=EVMSG_REMOVED
Language=English
The %1 service was removed.
.
MessageId=
SymbolicName=EVMSG_NOTREMOVED
Language=English
The %1 service could not be removed.
.

  每一条都有一个消息ID,如果不特别设置,那么 ID 的取值就是指其前面所赋的值。每一条还有一个代码中使用的符号名,语言标示符以及消息文本。消息可以跨多个行,并用含有一个句号的单独一行终止。
  消息编译器输出一个库文件,该库文件被用作应用程序的资源,此外还输出两个要在代码中包含的文件。下面是我的 .RC 文件:

// NTServApp.rc
#include <windows.h>
// 包含由消息编译器(MC)产生的消息表资源脚本
#include "NTServMsg.rc"
Here''s the .RC file the message compiler generated:
LANGUAGE 0x9,0x1
1 11 MSG00001.bin

正像你所看到的,这些文件中内容不多!

消息编译器产生的最后一个文件是你要包含到代码中的头文件,下面就是这个头文件的部分内容:

[..........]
//
// MessageId: EVMSG_INSTALLED
//
// MessageText:
//
// The %1 service was installed.
//
#define EVMSG_INSTALLED 0x00000064L
//
// MessageId: EVMSG_REMOVED
//
// MessageText:
//
// The %1 service was removed.
//
#define EVMSG_REMOVED 0x00000065L
[...........]

  你可能已经注意到了有几个消息包含参数替代项(如 %1)。让我们看看将消息写入某个系统日志文件时如何在代码中使用消息ID和参数替代项。以事件日志中记录成功安装信息的部分安装代码为例。也就是 CNTService::IsInstalled 函数部分:

[....]
LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);
[....]

LogEvent 是另一个 CNTService 函数,它使用事件类型(信息,警告或错误),事件消息的 ID,以及形成日志消息的最多三个参数的替代串:

// This function makes an entry into the application event log.
void CNTService::LogEvent(WORD wType, DWORD dwID,
const char* pszS1,
const char* pszS2,
const char* pszS3)
{
const char* ps[3];
ps[0] = pszS1;
ps[1] = pszS2;
ps[2] = pszS3;
int iStr = 0;
for (int i = 0; i < 3; i++) {
if (ps[i] != NULL) iStr++;
}
// Check to see if the event source has been registered,
// and if not then register it now.
if (!m_hEventSource) {
m_hEventSource = ::RegisterEventSource(NULL, // local machine
m_szServiceName); // source name
}
if (m_hEventSource) {
::ReportEvent(m_hEventSource,
wType,
0,
dwID,
NULL, // sid
iStr,
0,
ps,
NULL);
}
}		
如你所见,其主要工作是由 ReportEvent 系统函数处理。

  至此,我们已经可以通过调用 CNTService::LogEvent 在系统日志中记录事件了。接下来我们将考虑创建服务本身的一些代码。

编写服务代码

  为了建构一个简单的 Win32 服务,你需要知道的大多数信息都可以在 Platform SDK 中找到。其中的范例代码都是用C语言写的,并且很好理解。我的 CNTService 类就是基于这些代码。

一个服务主要包括三个函数:
  • main函数,这是代码的入口。我们正是在这里解析任何命令行参数并进行服务的安装,移除,启动等等。
  • 在例子中,提供真正服务代码的入口函数叫 ServiceMain。你可以随便叫它什么。在服务第一次启动的恶时候,将该函数的地址传递给服务管理器。
  • 处理来自服务管理器命令消息的函数。在例子中,这个函数叫 Handler,这个名字可以随意取。

服务回调函数
  因为 ServiceMain 和 Handler 函数都是由系统来调用,所以它们必须遵循操作系统的参数传递规范和调用规范。也就是说,它们不能简单地作为某个 C++ 类的成员函数。这样就给封装带来一些不便,因为我们想把 Win32 服务的功能封装在一个 C++ 类中。为了解决这个问题,我将 ServiceMain 和 Handler 函数创建成 CNTService 类的静态成员。这样就使我得以创建可以由操作系统调用的函数。 但是,这样做还没有完全解决问题,因为系统不允许给被调用的函数传递任何形式的用户数据,所以我们无法确定对 C++ 对象特定实例的 ServiceMain 或 Handler 的调用。用了一个非常简单但有局限的方法来解决这个问题。我创建一个包含 C++ 对象指针的静态变量。这个变量是在该对象首次创建是进行初始化的。这样便限制你每个服务应用只有一个C++对象。我觉得这个限制并不过分。下面是 NTService.h 文件中的声明:

class CNTService
{
[...]
// 静态数据
static CNTService* m_pThis; // nasty hack to get object ptr
[...]
};

下面是初始化 m_pThis 指针的方法:

CNTService::CNTService(const char* szServiceName)
{
// Copy the address of the current object so we can access it from
// the static member callback functions.
// WARNING: This limits the application to only one CNTService object.
m_pThis = this;
[...]
}

CNTService 类

  当我创建 C++ 对象封装 Windows 函数时,我尝试为我封装的每个 Windows API 除了创建成员函数外,还做一些别的工作,我尝试让对象更容易使用,降低实现特定项目所需的代码行数。因此我的对象是基于“我想让这个对象做什么?”而不是“Windows 用这些 APIs 做什么?”
  CNTService 类包含一些用来解析命令行的成员函数,为了处理服务的安装和拆卸以及事件日志的记录,你得在派生类中重写一些虚拟函数来处理服务控制管理器的请求。下面我们将通过本文的例子服务实现来研究这些函数的使用。
  如果你想创建尽可能简单的服务,只需要重写 CNTService::Run 即可,它是你编写代码实现具体服务任务的地方。你还需要实现 main 函数。如果服务需要实现一些初始化。如从注册表读取数据,还需重写 CNTService::OnInit。如果你要向服务发送命令消息 ,那么可以在服务中使用系统函数 ControlService,重写 CNTService::OnUserControl 来处理请求。

在例子应用程序中使用 CNTService
  NTService 在 CMyService 类中实现了它的大多数功能,CMyService 由 CNTService 派生。 MyService.h 头文件如下:

// myservice.h
#include "ntservice.h"
class CMyService : public CNTService
{
public:
CMyService();
virtual BOOL OnInit();
virtual void Run();
virtual BOOL OnUserControl(DWORD dwOpcode);
void SaveStatus();
// Control parameters
int m_iStartParam;
int m_iIncParam;
// Current state
int m_iState;
};

  正像你所看到的,CMyService 改写了 CNTService 的 OnInit、Run 和 OnUserControl。它还有一个函数叫 SaveStatus,这个函数被用于将数据写入注册表,那些成员变量用来保存当前状态。例子服务每隔一定的时间对一个整型变量进行增量处理。开始值和增量值都存在注册表的参数中。这样做并没有别的意图。只是为了简单示范。下面我们看看这个服务是如何实现的。

实现 main 函数

有了从 CNTService 派生的 CMyService,实现 main 函数很简单,请看 NTServApp.cpp 文件:

int main(int argc, char* argv[])
{
// 创建服务对象
CMyService MyService;
// 解析标准参数 (安装, 卸载, 版本等.)
if (!MyService.ParseStandardArgs(argc, argv)) {
// 未发现任何标准参数,所以启动服务,
// 取消下面 DebugBreak 代码行的注释,
// 当服务启动后进入调试器,
//DebugBreak();
MyService.StartService();
}
// 到这里,服务已经停止
return MyService.m_Status.dwWin32ExitCode;
}		
  这里代码不多,但执行后却发生了很多事情,让我们一步一步来看。首先,我们创建一个 MyService 类的实例。构造函数设置初始化状态和服务名字(MyService.cpp):
CMyService::CMyService():CNTService("NT Service Demonstration")
{
m_iStartParam = 0;
m_iIncParam = 1;
m_iState = m_iStartParam;
}

  接着调用 ParseStandardArgs 检查命令行是否包含服务安装(-i)、卸载(-u)以及报告其版本号(-v)的请求。CNTService::ParseStandardArgs 分别调用 CNTService::IsInstalled,CNTService::Install 和 CNTService::Uninstall 来处理这些请求。如果没有可识别的命令行参数,则假设该服务控制管理器试图启动该服务并调用 StartService。该函数直到服务停止运行才返回。当你调试完代码,即可把用于调试的代码行注释掉或删除。

安装和卸载服务
  服务的安装由 CNTService::Install 处理,它用 Win32 服务管理器注册服务并在注册表中建立一个条目以支持服务运行时日志消息。
  服务的卸载由 CNTService::Uninstall 处理,它仅仅通知服务管理器该服务已经不再需要。CNTService::Uninstall 不会删除服务实际的可执行文件。

编写服务代码

  现在我们来编写实现服务的具体代码。对于 NTService 例子,有三个函数要写。他们涉及初始化,运行服务的细节和响应控制请求。

初始化
  注册表有一个给服务用来存储参数的地方:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

  我就是选择这里来存储我的服务配置信息。我创建了一个 Parameters 键,并在此存储我要保存的值。所以当服务启动时,OnInit 函数被调用;这个函数从注册表中读取初始设置。

BOOL CMyService::OnInit()
{
// Read the registry parameters.
// Try opening the registry key:
// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\Parameters
HKEY hkey;
char szKey[1024];
strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\");
strcat(szKey, m_szServiceName);
strcat(szKey, "\\Parameters");
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
szKey,
0,
KEY_QUERY_VALUE,
&hkey) == ERROR_SUCCESS) {
// Yes we are installed.
DWORD dwType = 0;
DWORD dwSize = sizeof(m_iStartParam);
RegQueryValueEx(hkey,
"Start",
NULL,
&dwType,
(BYTE*)&m_iStartParam,
&dwSize);
dwSize = sizeof(m_iIncParam);
RegQueryValueEx(hkey,
"Inc",
NULL,
&dwType,
(BYTE*)&m_iIncParam,
&dwSize);
RegCloseKey(hkey);
}
// Set the initial state.
m_iState = m_iStartParam;
return TRUE;
}		
现在我们有了服务参数,我们便可以运行服务了。

运行服务
当 Run 函数被调用时将执行服务的主体代码。本文例子的这部分很简单:
void CMyService::Run()
{
while (m_bIsRunning) {
// Sleep for a while.
DebugMsg("My service is sleeping (%lu)...", m_iState);
Sleep(1000);
// Update the current state.
m_iState += m_iIncParam;
}
}		
  注意,只要服务不终止,这个函数就不会退出。当有终止服务的请求时,CNTService::m_bIsRunning 标志被置成 FALSE。如果在服务终止时,你要实现清除操作,那么你还可以改写 OnStop 和/或 OnShutdown。

响应控制请求
  你可以用任何适合的方式与服务通讯——命名管道,思想交流,便条等等——对于一些简单的请求,用系统函数 ControlService 很容易实现。CNTService 提供了一个处理器专门用于通过 ControlService 函数发送的非标准消息(也就是用户发送的消息)。本文例子用单一消息在注册表中保存当前服务的状态,以便其它应用程序能看到它。我不建议用这种方法来监控服务,因为它不是最佳方法,这只是比较容易编码实现而已。ControlService 所能处理的用户消息必须在 128 到 255 这个范围。我定义了一个常量 SERVICE_CONTROL_USER,128 作为基值。范围内的用户消息被发送到 CNTService:: OnUserControl,在例子服务中,处理此消息的细节如下:
BOOL CMyService::OnUserControl(DWORD dwOpcode)
{
switch (dwOpcode) {
case SERVICE_CONTROL_USER + 0:
// Save the current status in the registry.
SaveStatus();
return TRUE;
default:
break;
}
return FALSE;   // say not handled
}		
SaveStatus 是一个局部函数,用来在注册表中存储服务状态。

调试 Win32 服务

  main 函数中包含一个对 DebugBreak 的调用,当服务第一次被启动时,它会激活系统调试器。你可以监控来自调试器命令窗口中的服务调试信息。你可以在服务中用 CNTService::DebugMsg 来报告调试期间感兴趣的事件。
  为了调试服务代码,你需要按照 Platform SDK 文档中的要求安装 系统调试器(WinDbg)。你也可以用 Visual Studio 自带的调试器调试 Win32 服务。
  有一点很重要,那就是 当它被服务管理器控制时,你不能终止服务和单步执行,因为服务管理器会让服务请求 超时并终止服务线程。所以你只能让服务吐出消息,跟踪其过程并在调试器窗口查看它们。
  当服务启动后(例如,从控制面板的“服务”中),调试器将在服务线程的挂起后启动。你需要通过单击“Go”按钮或按 F5 让继续运行。然后在调试器中观察服务的运行过程。

下面是启动和终止服务的调试输出例子:
Module Load: WinDebug/NTService.exe (symbol loading deferred)
Thread Create: Process=0, Thread=0
Module Load: C:\NT351\system32\NTDLL.DLL (symbol loading deferred)
Module Load: C:\NT351\system32\KERNEL32.DLL (symbol loading deferred)
Module Load: C:\NT351\system32\ADVAPI32.DLL (symbol loading deferred)
Module Load: C:\NT351\system32\RPCRT4.DLL (symbol loading deferred)
Thread Create: Process=0, Thread=1
*** WARNING: symbols checksum is wrong 0x0005830f 0x0005224f for C:\NT351\symbols\dll\NTDLL.DBG
Module Load: C:\NT351\symbols\dll\NTDLL.DBG (symbols loaded)
Thread Terminate: Process=0, Thread=1, Exit Code=0
Hard coded breakpoint hit
Hard coded breakpoint hit
[](130): CNTService::CNTService()
Module Load: C:\NT351\SYSTEM32\RPCLTC1.DLL (symbol loading deferred)
[NT Service Demonstration](130): Calling StartServiceCtrlDispatcher()
Thread Create: Process=0, Thread=2
[NT Service Demonstration](174): Entering CNTService::ServiceMain()
[NT Service Demonstration](174): Entering CNTService::Initialize()
[NT Service Demonstration](174): CNTService::SetStatus(3026680, 2)
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](174): CNTService::SetStatus(3026680, 4)
[NT Service Demonstration](174): Entering CNTService::Run()
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](130): CNTService::Handler(1)
[NT Service Demonstration](130): Entering CNTService::Stop()
[NT Service Demonstration](130): CNTService::SetStatus(3026680, 3)
[NT Service Demonstration](130): Leaving CNTService::Stop()
[NT Service Demonstration](130): Updating status (3026680, 3)
[NT Service Demonstration](174): Leaving CNTService::Run()
[NT Service Demonstration](174): Leaving CNTService::Initialize()
[NT Service Demonstration](174): Leaving CNTService::ServiceMain()
[NT Service Demonstration](174): CNTService::SetStatus(3026680, 1)
Thread Terminate: Process=0, Thread=2, Exit Code=0
[NT Service Demonstration](130): Returned from StartServiceCtrlDispatcher()
Module Unload: WinDebug/NTService.exe
Module Unload: C:\NT351\system32\NTDLL.DLL
Module Unload: C:\NT351\system32\KERNEL32.DLL
Module Unload: C:\NT351\system32\ADVAPI32.DLL
Module Unload: C:\NT351\system32\RPCRT4.DLL
Module Unload: C:\NT351\SYSTEM32\RPCLTC1.DLL
Thread Terminate: Process=0, Thread=0, Exit Code=0
Process Terminate: Process=0, Exit Code=0
>

总结

  也许用 C++ 创建 Win32 服务并不是最理想的,但使用单一的类来派生你自己的服务的确方便了你的服务开发工作。

 

 

 

PS:手动测试时可能需要 SC 命令的协助

posted @ 2009-04-13 16:41 巴西木 阅读(549) | 评论 (0)编辑 收藏

Oracle数据操作和控制语言详解(一)

SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML, 数据定义语言DDL,数据控制语言DCL。其中用于定义数据的结构,比如 创建、修改或者删除数据库;DCL用于定义数据库用户的权限;在这篇文章中我将详细讲述这两种语言在Oracle中的使用方法。

  DML语言

  DML是SQL的一个子集,主要用于修改数据,下表列出了ORACLE支持的DML语句。

语句 用途
INSERT 向表中添加行
UPDATE 更新存储在表中的数据
DELETE 删除行
SELECT FOR UPDATE 禁止其他用户访问DML语句正在处理的行。
LOCK TABLE 禁止其他用户在表中使用DML语句

  插入数据

  INSERT语句常常用于向表中插入行,行中可以有特殊数据字段,或者可以用子查询从已存在的数据中建立新行。

  列目录是可选的,缺省的列的目录是所有的列名,包括comlumn_id,comlumn_id可以在数据字典视图ALL_TAB_COLUMNS,USER_TAB_COLUMNS,或者DBA_TAB_COLUMNS中找到。

  插入行的数据的数量和数据类型必须和列的数量和数据类型相匹配。不符合列定义的数据类型将对插入值实行隐式数据转换。NULL字符串将一个NULL值插入适当的列中。关键字NULL常常用于表示将某列定义为NULL值。

  下面的两个例子是等价的。

INSERT INTO customers(cust_id,state,post_code)
VALUE('Ariel',NULL,'94501');

  或

INSERT INTO customers(cust_id,state,post_code)
VALUE('Ariel',,'94501');

  更新数据

  UPDATE命令用于修改表中的数据。

UPDATE order_rollup
SET(qty,price)=(SELECT SUM(qty),SUM(price) FROM order_lines WHERE customer_id='KOHL'
WHERE cust_id='KOHL'
AND order_period=TO_DATE('01-Oct-2000')

  删除数据

  DELETE语句用来从表中删除一行或多行数据,该命令包含两个语句:

   1、关键字DELETE FROM后跟准备从中删除数据的表名。

   2、WHERE后跟删除条件

DELETE FROM po_lines
WHERE ship_to_state IN ('TX','NY','IL')
AND order_date<TRUNC(SYSTEM)-90< td>

  清空表

  如果你想删除表中所有数据,清空表,可以考虑使用DDL语言的TRUNCATE语句。TRUNCATE就像没有WHERE子句的DELETE命令一样。TRUNCATE将删除表中所有行。TRUNCATE不是DML语句是DDL语句,他和DELETE右不同的特点。

TRUNCATE TABLE (schema)table DROP(REUSE) STORAGE

  STORAGE子串是可选的,缺省是DROP STORAGE。当使用DROP STORAGE时将缩短表和表索引,将表收缩到最小范围,并重新设置NEXT参数。REUSE STORAGE不会缩短表或者调整NEXT参数。

  TRUNCATE和DELETE有以下几点区别

  1、TRUNCATE在各种表上无论是大的还是小的都非常快。如果有ROLLBACK命令DELETE将被撤销,而TRUNCATE则不会被撤销。

  2、TRUNCATE是一个DDL语言,向其他所有的DDL语言一样,他将被隐式提交,不能对TRUNCATE使用ROLLBACK命令。

  3、TRUNCATE将重新设置高水平线和所有的索引。在对整个表和索引进行完全浏览时,经过TRUNCATE操作后的表比DELETE操作后的表要快得多。

  4、TRUNCATE不能触发任何DELETE触发器。

  5、不能授予任何人清空他人的表的权限。

  6、当表被清空后表和表的索引讲重新设置成初始大小,而delete则不能。

  7、不能清空父表。

  SELECT FOR UPDATE

  select for update语句用于锁定行,阻止其他用户在该行上修改数据。当该行被锁定后其他用户可以用SELECT语句查询该行的数据,但不能修改或锁定该行。

  锁定表

  LOCK语句常常用于锁定整个表。当表被锁定后,大多数DML语言不能在该表上使用。LOCK语法如下:

LOCK schema table IN lock_mode

  其中lock_mode有两个选项:

   share 共享方式

   exclusive 唯一方式

  例:

LOCK TABLE intentory IN EXCLUSIVE MODE

  死锁

  当两个事务都被锁定,并且互相都在等待另一个被解锁,这种情况称为死锁。

  当出现死锁时,ORACLE将检测死锁条件,并返回一个异常。

Oracle数据操作和控制语言详解(二)

事务控制

  事务控制包括协调对相同数据的多个同步的访问。当一个用户改变了另一个用户正在使用的数据时,oracle使用事务控制谁可以操作数据。

  事务

  事务表示工作的一个基本单元,是一系列作为一个单元被成功或不成功操作的SQL语句。在SQL和PL/SQL中有很多语句让程序员控制事务。程序员可以:

   1、显式开始一个事物,选择语句级一致性或事务级一致性

   2、设置撤销回滚点,并回滚到回滚点
 
   3、完成事务永远改变数据或者放弃修改。
  
  事务控制语句

语句 用途
Commit 完成事务,数据修改成功并对其他用户开放
Rollback 撤销事务,撤销所有操作
rollback to savepoint 撤销在设置的回滚点以后的操作
set transaction 响应事务或语句的一致性;特别对于事务使用回滚段

  例:

BEGIN
UPDATE checking
SET balance=balance-5000
WHERE account='Kieesha';

INSERT INTO checking_log(action_date,action,amount)
VALUES (SYSDATE,'Transfer to brokerage',-5000);

UPDATE brokerage
SET cash_balance=cash_balance+5000
WHERE account='Kiesha';

INSERT INTO brokerage_log(action_date,action,amount)
VALUES (SYSDATE,'Tracfer from checking',5000)

COMMIT

EXCEPTION
WHEN OTHERS
ROLLBACK

END

  Savepoint 和 部分回滚(Partial Rollback)

  在SQL和PL/SQL中Savepoint是在一事务范围内的中间标志。经常用于将一个长的事务划分为小的部分。保留点Savepoint可标志长事务中的任何点,允许可回滚该点之后的操作。在应用程序中经常使用Savepoint;例如一过程包含几个函数,在每个函数前可建立一个保留点,如果函数失败,很容易返回到每一个函数开始的情况。在回滚到一个Savepoint之后,该Savepoint之后所获得的数据封锁被释放。为了实现部分回滚可以用带TO Savepoint子句的ROLLBACK语句将事务回滚到指定的位置。

  例

BEGIN

INSERT INTO ATM_LOG(who,when,what,where)
VALUES ('Kiesha',SYSDATE,'Withdrawal of $100','ATM54')
SAVEPOINT ATM_LOGGED;

UPDATE checking
SET balance=balance-100
RETURN balance INTO new_balance;

IF new_balance<0
THEN
ROLLBACK TO ATM_LOGGED;
COMMIT
RAISE insufficient_funda;
END IF

END

  关键字SAVEPOINT是可选的,所以下面两个语句是等价的:

ROLLBACK TO ATM_LOGGED;
ROLLBACK TO SAVEPOINT ATM_LOGGED;

  一致性和事务

  一致性是事物控制的关键慨念。掌握了oracle 的一致性模型,能使您更好的,更恰当的使用事务控制。oracle通过一致性保证数据只有在事务全部完成后才能被用户看见和使用。这项技术对多用户数据库有巨大的作用。

  oracle常常使用语句级(state-level)一致性,保证数据在语句的生命期之间是可见的但不能被改变。事务由多个语句组成,当使用事务时,事物级(transaction-level)一致性在整个事务生命期中保证数据对所有语句都是可见的。

  oracle通过SCN(syatem change number)实施一致性。一个SCN是一个面向时间的数据库内部键。SCN只会增加不会减少,SCN表示了时间上的一个点,每个数据块都有一个SCN,通过比较这个点实施操作。

  事务级一致性

  SET TRANSACTION 的一个作用是确保事务级一致或语句级一致中有一个实施。ORACLE使用这些术语:

   ISOLATION LEVEL READ COMMIT 表示语句级一致

   ISOLATION LEVEL SERIALIZABLE 表示事务级一致。

  例:

SET TRANSACTION ISOLATION LEVEL READ COMMIT;

SET TRANSACTION ISOLATION LEVEL READ COMMIT

  下面的语句也能确保事务级一致:

SET TRANSCATION READ ONLY

  任何企图在只读(READ ONLY)事务中修改数据的操作都会抛出一个异常。但是,READ ONLY事务只能在下列语句中使用:

SELECT(没有FOR UPDATE子句)
LOCK TABLE
SET ROLE
ALTER SYSTEM
ALTER ALARM

  即使没有改变任何数据,READ ONLY事务依然必须使用一个COMMIT或ROLLBACK以结束整个事务。

  SET TRANSCTION的另外一个应用是在回滚时直接使用回滚段(ROLLBACK SEGMENT)。回滚段是ORACLE的一个特殊的数据对象,回滚段的头部包含正在使用该回滚段事务的信息。当用户回滚事务(ROLLBACK)时,ORACLE将会利用回滚段中的数据前影像来将修改的数据恢复到原来的值。oracle用round-robin给事务随机分配回滚段。一个大的事务可以分配任何回滚段,这也许会导致回滚段的大小变得很大。因此要避免让大的事务随机分配回滚段。

  事务以SET TRANSACTION开始,象下面这样:

SET TRANSACTION USE ROLLBACK SEGMENT rb_large;

  rb_large是一个大的回滚段的名称,现在就给一个大的事务分配了一个大的回滚段,其他的小的回滚段将不由动态空间管理,这样就更有效率。

  下面我们看一个例子.我们有一个回滚段表空间大小是2G,在高峰时期需要10个回滚段以满足用户的需要,这些高峰在线用户只有小的事务。一周我们连续运行了4个大的事务,这些事务需要删除和加载数据,每一个撤销需要1G,回滚段的大小如下:

rb_large(initial 100M minextenta 2)

rb1 (initial 1M next minextents 5)
rb2 (initial 1M next minextents 5)
rb3 (initial 1M next minextents 5)
rb4 (initial 1M next minextents 5)
rb5 (initial 1M next minextents 5)
rb6 (initial 1M next minextents 5)
rb7 (initial 1M next minextents 5)
rb8 (initial 1M next minextents 5)
rb9 (initial 1M next minextents 5)
rb10 (initial 1M next minextents 5)

  所有的都非常恰当的安排在2G的表空间中,如果我们缺省的round-robin给事务分配回滚段,4个大事务将有4个独立的回滚段,每个回滚段的大小将是1G,如果这样我们的2G表空间就不够,而数据库管理员就不得不在夜晚2点起来工作,每个事务都由以下面的语句开始:

SET TRANSACTION USE ROLLBACK SEGMENT rb_large

  现在 4个事务重用相同的表空间,保正4个回滚段的表空间在2G以内。数据库管理员可以睡到天亮。

Oracle数据操作和控制语言详解 (三)

建立和修改用户

  CREATE USER 语句将建立一个用户。当一个用户连接到ORACLE数据库时,它必须被验证。ORACLE中验证有三种类型:

   Database

   external

   Global

  缺省是数据库验证,当用户连接到数据库时,oracle将检测用户是否是数据库的合法用户,并且要提供正确的password.external验证,oracle将只检测用户是否是合法用户,password已经被网络或系统验证了。global验证也是只检测是否是合法用户,password由oraclesecurity server验证。

  Database验证用户账号

  数据库验证账号是张好的缺省类型,也是最普通的类型。建立一个账号是piyush,口令是welcome的账号,只需执行下面的命令:

CREATE USE piyush IDENTIFIED BY welcome

  piyush可以通过下面的语句将口令改变为saraswatt:

ALTER USER piyush IDENTIFIED BY saraswati;

  外部验证用户账号

  用户账号进入数据库时可以不提供口令,这种情况下代替数据库识别口令的是客户端操作系统。外部验证账号有时也叫OPS$账号,当他们最初在oracle6开始介绍时,oracle账号都有关键字前缀OPS$,这也就是为什么init.ora 参数os_authent_prefix是OPS$--默认特征与oracle6保持一致。os_authent_prefix定义的字符串必须被预处理为用于Oracle外部识别账号的操作系统账号名。创建操作系统用户appl的语句是:

CREATE USER ops$appl IDENTIFIED EATERNALLY

  但在通常情况下,os_authent_prefix将被设置为空,像下面这样:

CREATE USER appl IDENTIFIED EATERNALLY

  这样效果是一样的,关键字IDENTIFIED EXTERNALLY告诉ORACLE这是一个外部识别账号。

  GLOBAL用户账号

  GLOBAL类型的用户账号数据库不检测口令,而是由X.509目录服务器检测口令。创建一个GLOBAL类型的用户账号的方法是:

CREATE USER scott IDENTIFIED GLOBALLY AS "CN=scott,OU=divisional,O=sybex,C=US"

  关键字IDENTIFIED GLOBALLY AS表示建立的是一个GLOBAL类型的用户账号.

  创建和更改用户账号

  CREATE USER 用于建立用户账号和给用户账号的属性赋值。ALTER USER用于更改用户账号和属性。但CREATE USER语句必须包括用户名和口令。

  有部分属性能用CREATER USER和ALTER USER语句设置,下面对是这些的属性具体描述:

  给用户分配缺省表空间

  表空间(tablespace)是放置表、索引、丛等用户对象的。如果在create user语句中没有包含表空间,那么缺省的是系统表空间。

CREATE USER piyush IDENTIFIED BY saraswati
DEFAULTE TABLESPACE user_data;
ALTER USER manoj DEFAULTE TABLESPACE dev1_data;

  给用户分配临时表空间

  临时表空间,顾名思义是临时存放表、索引等用户对象的临时段。建立方法一样

CREATE USER piyush IDENTIFIED BY saraswati
Temporary TABLESPACE user_data;
ALTER USER manoj Temporary TABLESPACE dev1_data;

  给用户分配表空间的使用定额

  使用定额限制用户在表空间中使用磁盘的数量。定额可以按字节、千字节、兆字节或者无限制来制定。

CREATE USER piyush IDENTIFIED BY saraswati
DEFAULT TABLESPACE user_data
QUOTA UNLIMITED ON user_data
QUOTA 20M ON tools;
ALTER USER manoj QUOTA 2500K ON tools;

  给用户分配一个简表

  简表可以限制用户在会话时消耗的资源。这些资源包括:连接数据库的时间,空闲时间,每次会话的逻辑读数据的数量等等,缺省的简表对资源无限制。

CREATE USER piyush IDENTIFIED BY saraswati
PROFILE TABLESPACE user_data;
ALTER USER manoj Temporary TABLESPACE dev1_data;

  为用户响应指定角色

  这个属性只能由ALTER USER语句设置,试图用CREATE USER语句设置将回返回一个例外。

ALTER USER manoj DEFAULT ROLE ALL EXCEPT salary_adm;

  为用户的password设定到期时间以便在用户下次登录时更改

  当用户的password到期,在下一次登录时将强迫修改password,oracle提示用户输入旧的password,然后输入新的password。这项功能常用于新用户,当新用户用缺省的password登录时必须修改立即修改password.

ALTER USER manoj IDENTIFIED BY welcome;
ALTER USER manoj PASSWORD EXPIRE;

  锁定账号,是用户不能登录

ALTER USER ql AC
COUNT LOCK

  对账号解锁,以便用户能登录数据库

ALTER USER ql ACCOUNT UNLOCK

  权限和角色

  权限允许用户访问属于其它用户的对象或执行程序,ORACLE系统提供三种权限:

   Object 对象级

   System 系统级

   Role 角色级

  这些权限可以授予给用户、特殊用户public或角色,如果授予一个权限给特殊用户"Public"(用户public是oracle预定义的,每个用户享有这个用户享有的权限),那么就意味作将该权限授予了该数据库的所有用户。

  对管理权限而言,角色是一个工具,权限能够被授予给一个角色,角色也能被授予给另一个角色或用户。用户可以通过角色继承权限,除了管理权限外角色服务没有其它目的。权限可以被授予,也可以用同样的方式撤销。

  建立和使用角色

  如前所诉,角色存在的目的就是为了使权限的管理变得轻松。建立角色使用CREATE ROLE语句,他的语法如下:

CREATE ROLE role_name IDENTIFIED BY password
CREATE ROLE role_name IDENTIFIED EXTERNALLY
CREATE ROLE role_name IDENTIFIED GLOBALLY

  缺省情况下建立的角色没有password或者其他的识别。如果使用IDENTIFIED BY 子句建立,那么角色不会自动响应,必须用SET ROLE激活。

SET ROLE role_name IDENTIFIED BY password

  EXTERNALLY和GLOBALLY类型的角色由操作系统和ORACLE Service server验证。通常用户需要权限修改应用程序中使用的表单中的数据,但是只有在应用程序运行时而不是在使用ad hoc工具时,这种上下文敏感安全可以通过有PASSWORD的角色来实现。当用户在应用程序内部连结数据库时,代码将执行SET ROLE命令,通过安全验证。所以用户不需要知道角色的password,也不需要自己输入SET ROLE命令。

  对象权限

  对象权限就是指在表、视图、序列、过程、函数或包等对象上执行特殊动作的权利。有九种不同类型的权限可以授予给用户或角色。如下表:

权限 ALTER DELETE EXECUTE INDEX INSERT READ REFERENCE SELECT UPDATE
Directory no no no no no yes no no no
function no no yes no no no no no no
procedure no no yes no no no no no no
package no no yes no no no no no no
DB Object no no yes no no no no no no
Libary no no yes no no no no no no
Operation no no yes no no no no no no
Sequence yes no no no no no no no no
Table yes yes no yes yes no yes yes yes
Type no no yes no no no no no no
View no yes no no yes no no yes yes

  对象由不止一个权限,特殊权限ALL可以被授予或撤销。如TABLE的ALL权限就包括:

   SELECT,INSERT,UPDATE和DELETE,还有INDEX,ALTER,和REFERENCE。

  如何看这个表我们以ALTER权限为例进行说明

  ALTER权限

  允许执行ALTER TABLE和LOCK TABLE操作,ALTER TABLE可以进行如下操作:

    . 更改表名

    . 增加或删除列

    . 改变列的数据类型或大小

    . 将表转变为分区表

  在SEQUENCE上的ALTER权限允许执行ALTER Sequence语句,重新给sequence分配最小值、增量和缓冲区大小。

  系统权限

  系统权限需要授予者有进行系统级活动的能力,如连接数据库,更改用户会话、建立表或建立用户等等。你可以在数据字典视图SYSTEM_PRIVILEGE_MAP上获得完整的系统权限。对象权限和系统权限都通过GRANT语句授予用户或角色。需要注意的是在授予对象权限时语句应该是WITH GRANT OPTION子句,但在授予系统权象时语句是WITH ADMIN OPTION,所以在你试图授予系统权限时,使用语句WITH GRANT OPTION系统会报告一个错误:ONLY ADMIN OPTION can be specified。在考试中要特别注意这个语法和错误信息。

  角色和角色权限

  角色权限就是将属于用户的权限授予一个角色。任何权限都可以授予给一个角色。授予系统权限给被授予者必须使用WITH_ADMIN_OPTION子句,在会话期间通过SET ROLE语句授予或撤销角色权限。然而,角色权限不能依靠存储在SQL中的权限。如果函数、程序、包、触发器或者方法使用另一个计划拥有的对象,那么就必须直接给对象的拥有者授权,这是因为权限不会在会话之间改变。

  授予和撤销权限

   给用户或者角色授予权限使用GRANT 语句,GRANT语句的语法如下:

GRANT ROLE(或system privilege) TO user(role,Public) WITH ADMIN OPTION(可选)

  对象权限被授予 WITH GRANT OPTION,

  权限和数据字典

  数据字典是ORACLE存储有关数据库结构信息的地方,数据本身存放在其他地方,数据字典由表和视图组成。在考试中关于数据字典最容易考的内容是:查看那一类权限已经被授予。比如DBA_TAB_PRIV包含了用户授予给另一用户的对象权限和在授予时是否带有WITH GRANT OTPION子串的信息。注意DBA_TAB_PRIV不仅仅包含了对表的权限的关系,他还包括函数、包、队列等等上的权限的关系。下表列出了所有的权限和角色的数据字典视图:

  表: 权限的数据字典视图

视图 作用
ALL_COL_PRIVS 表示列上的授权,用户和PUBLIC是被授予者
ALL_COL_PRIVS_MADE 表示列上的授权,用户是属主和被授予者
ALL_COL_RECD 表示列上的授权,用户和PUBLIC是被授予者
ALL_TAB_PRIVS 表示对象上的授权,用户是PUBLIC或被授予者或用户是属主
ALL_TAB_PRIVS_MADE 表示对象上的权限,用户是属主或授予者
ALL_TAB_PRIVS_RECD 表示对象上的权限, 用户是PUBLIC或被授予者
DBA_COL_PRIVS 数据库列上的所有授权
DBA_ROLE_PRIVS 显示已授予用户或其他角色的角色
DBA_SYS_PRIVS 已授予用户或角色的系统权限
DBA_TAB_PRIVS 数据库对象上的所有权限
ROLE_ROLE_PRIVS 显示已授予用户的角色
ROLE_SYS_PRIVS 显示通过角色授予用户的系统权限
ROLE_TAB_PRIVS 显示通过角色授予用户的对象权限
SESSION_PRIVS 显示用户现在可利用的所有系统权限
USER_COL_PRIVS 显示列上的权限,用户是属主、授予者或被授予者
USER_COL_PRIVS_MADE 显示列上已授予的权限,用户是属主或授予者
USER_COL_PRIVS_RECD 显示列上已授予的权限,用户是属主或被授予者
USER_ROLE_PRIVS 显示已授予给用户的所有角色
USER_SYS_PRIVS 显示已授予给用户的所有系统权限
USER_TAB_PRIVS 显示已授予给用户的所有对象权限
USER_TAB_PRIVS_MADE 显示已授予给其他用户的对象权限,用户是属主
USER_TAB_PRIVS_RECD 显示已授予给其他用户的对象权限,用户是被授予者

posted @ 2009-03-26 15:59 巴西木 阅读(296) | 评论 (0)编辑 收藏

转自:http://www.dbform.com/html/2009/662.html


很多人应该知道在基于Oracle数据库的系统实施过程中,有一种高可用性的解决方案,称为MAA(Maximum Availability Architecture),通过RAC和Dataguard等一系列技术和选件搭建一个可以获得最大高可用性的IT环境。


MAA保证了IT系统能够被持续使用,而与此同时,另外一个需求就诞生了,系统不但需要能够持续使用,而且还需要能够安全使用。安全,同样也是IT系统能够符合行业标准的前提条件。面对安全需求,Oracle提出了MSA架构,也就是Maximum Security Architecture,最大安全性架构。


IT系统在哪些方面具有安全性需求呢?


1. 用户管理

应用系统需要有正常的用户登陆以及用户验证,具有权限的用户在自身管理方面是需要注意安全的,以防止被其它人员非法获取登陆或者查看数据的权限。


2. 存取控制

在应用系统中不同的用户应该具有不同的权限,不同权限的用户可以登陆的范围或者查看数据的多少也是不同的。这是上面一步的延展,不同的用户通过安全的机制登陆进系统了,同样需要有另外一种安全机制来控制不同的用户存取不同的数据。


3. 数据加密以及数据遮盖

进一步延展,不同的用户安全登陆系统,并且已经各自安全的查看自己的数据了,那么数据本身是否安全呢?这就需要数据加密或者数据遮盖技术来完成。


4. 监控

这个体系并不仅仅是监控报表,因为那只是事后的工作,也许一个违规的操作出现几天以后,安全报表中才出现记录,哪怕仅仅是几个小时,实际上违规的操作已然发生,产生的危害也许已经是无法挽回的了。因此对于整个应用系统的安全流程,必须要有一个完善的监控体系来实时地给予监控,在违规操作发生的当时就立即予以报警。


那么Oracle提出的MSA架构是如何在各个方面提供相应的技术以及产品呢?同样以上述的四个方面分别阐述。


1. 用户控制

1.1 单一登录


管理多个账户以完成相关的工作任务将导致安全性的薄弱环节和高额的所有权成本。一般地,为完成每天的日常工作,一个员工要管理十四个密码或账户。为了应付“密码过多”的问题,员工可能会把密码抄写下来,或选择一个容易猜中的密码。人们经常忘记密码,并频繁地向帮助部门查询。系统管理员甚至面临者一个更大的问题。系统管理员需要花费大量的时间去管理分布于各种数据库,web服务器和网络上的这些账户。如果一个雇员离开了公司,而系统管理员没有注意删除所有的账户,一些老的账号可能就仍然处于激活状态,这无疑埋下了很大的安全隐患。



单一登录原理图


这个问题的解决方案就是单一登录。单一登录可以使用户通过一个单一的登录过程就能访问所用被授权的应用。如果只管理一个密码或证明书,用户一般就不会把它记录下来,或选择一个不容易被猜中的密码。每个企业都应该有使雇员强制性选择安全密码的安全性策略。认证证书可以集中存储于一个LDAP目录,因此系统管理员可以在一个单独的地方管理这些账户。账户的集中化可以使系统管理员能更迅速地完成追加,删除和修正账户的工作。Oracle通过单点登陆服务套件(Enterprise Single Sign-On suite)集成Oracle互联网目录LDAP提供了单一认证。Oracle也可以与其他第三方单一登录服务器的集成,如Kerberos 和 Netegrity。


1.2 超强认证

通过互联网你可能看不到正在与谁做生意,所以在数字世界中存在着一种可以让系统识别身份的机制。用户名和密码的组合是一个系统认证和识别用户的最普遍或最基本的方式。一些企业认为,这种基础的,单向的认证方式并不十分安全,特别是当用户需要管理几个密码时更是如此。另一种方式,或者是认证的更安全的方式是所谓的超强认证。我们可以给出几个超强认证的例子,如X.509数字证书,证明卡和生物认证设备。Oracle 通过身份管理(COREid)结合第三方全面支持基本认证和多种超强认证。比如支持以下具有工业标准的高级认证方式:
Kerberos
RADIUS (Remote Authentication Dial-In User Service)
Secure Sockets Layer
PKI  


2. 存取控制

2.1 精细的访问控制

互联网和在线服务目前已要求服务器能够实现精细的访问控制。如果您考虑数据库服务器将管理互联网上多团体用户的数据,数据库必须保证用户只能访问与它相关的数据,而不涉及其他的数据。例如,在线银行业务服务需要保证客户所访问的账户信息只能是他们自己的,而不能看到其他客户的信息。


能够控制对敏感信息的访问对需要满足隐私性需求的应用也由帮助。例如,医疗应用软件必须保证医生只能看到他们自己病人的记录。


因为数据库在传统上是在表的级别上分配权限,而不会控制在单独的记录或更低的级别,附加的应用代码可能被要求取得更精细的访问控制。传统方法的问题是如果用户利用应用之外的方式,如特别的查询工具等访问数据库,就可以绕过安全性设置。而且,维护复杂的访问控制代码会导致高额的开发和管理成本。



Oracle10g数据库的标签安全保护最敏感数据


Oracle10g数据库的标签安全性组件(Label Security)是一个精细访问控制或行级别安全性的无限的解决方案。建立在Oracle虚拟个人数据库的工具包基础上的Oracle标签安全性向数据行中追加了一个特别的标记(标签),可以达到行级别的安全性。例如,在线服务可以使用一个订阅标签,它可以将存储于相同数据库服务器中的各企业的数据更安全有效的分离开。


简单的Label Security设置方法参看我的这篇文章 - 利用OLS实现行级安全性 Step By Step。


2.2内部控制

企业IT系统的内部安全管理的需求来源于传统的管理用户的模糊定义,尤其是某些特权用户的定义(比如DBA等),原先只是希望他们管理数据库表结构或本部门的数据,但由于其传统的定义使这类用户可以访问所有的应用数据,甚至敏感数据(如企业HR部门的员工工资和个人信息,Finance部门的重要业务数据等);同时,随着企业系统的发展,信息整合带来不可避免的数据库合并,大量的子系统的特权用户的访问权限被简单地扩展到了所有系统。





Oracle Database Vault正是为解决上面这些敏感的安全问题而诞生的, Oracle Databse 10g R2 企业版的新增安全选件,Database  Vault安全选件可以控制什么人,在什么时间,在什么地点或是什么样的应用程序来访问,保护我们的业务避免来自于内部的用户恶意的攻击。对职责进行严格分离,甚至在我们的系统管理员中,Oracle  Database Vault选件额外增加强大的可预见性的控制功能帮助企业或公司满足今天严格法规和敏感的需求。Vault是目前业内最早的相关安全的产品,引入了几个新的强大的安全特性(Realms领域、Factor代理、Rule规则等)来限制对数据库的访问,即便是一些超级用户或特权也是如此。这些特点可应用于一个灵活的,易于定制的样式去增强我们的授权需要,而不需对客户已存在的应用做任何修改。


3. 数据加密以及数据遮盖

3.1 加密系统

通过互联网从事商务活动需要通过网络传输信息。任何人,不论是黑客,解密高手还是不诚实的雇员,都可以下载一个信息包的嗅探器,当信用卡等这类敏感信息在网络中传输时,利用嗅探器可以捕获它们。不怀好意的用户也可以利用一些途径获得对存储于服务器中的数据的非法访问。为了防止对数据的非法访问,可以采用加密系统,利用信息的不规则性保护数据。它涉及数学的计算方法和密钥。公共密钥的基础结构是最普遍采用的方法。它为加密,数据整合,数字认证和数字签名提供了技术。



Oracle10g数据库的高级安全性组件保证网络传输数据安全


当数据在网络中的所有层,包括用户层,中间层和数据库层传输时,Oracle提供了网络中端到端的加密。Oracle应用服务器提供了网络中用户层和中间层的加密。Oracle数据库的高级安全性组件(Advanced Security)提供了网络中中间层和数据库层的加密。为了强化性能,Oracle通过Oracle与RSA的BSAFE库的集成,支持BHAPI的接口。Oracle也对存储于数据库内部的数据提供加密。对于非常敏感信息如信用卡号的加密,如果有对数据非法访问的企图或摆脱数据库控制的企图,如通过操作系统,可在数据库中追加一个额外的保护层。


3.2 备份安全

为了信息的延续性,信息保存的时间要求越来越长,因此备份数据的安全也受到了很大的挑战,一方面,企业使用大量的备份介质来备份数据;一方面,企业不断地从备份数据中恢复以便使用;同时,企业会对大量的备份筛选,淘汰过时的备份,这些复杂的数据备份管理过程中,信息的安全问题也日益突出。


如果企业选择将备份数据保存在磁盘中,那么TDE(Transparent Data Encryption)是应该首选考虑的,TDE可以在包含敏感数据的列,表,表空间上进行加密,加密之后的数据备份到磁盘上,备份文件中的信息也同样是加密的,这样即使备份文件意外丢失,也几乎没有可能通过恢复或者直接扫描裸文件的方法从备份文件中获得敏感数据。


在云计算十分受到追捧的今天,将自己的备份数据存储在第三方提供的云计算存储(比如Amazon S3)中,如何才能放心,如何才能保证其中的敏感数据不被剽窃,TDE无疑是理想的选择。


如果企业选择直接将数据备份到磁带中,那么Oracle Secure Backup提供方便、安全可靠的磁带备份解决方案。





为了更好地管理备份数据的安全,Oracle通过10g 之后版本中诞生的最新选件Secure Backup来很好地管理与控制备份介质尤其是磁带备份的安全问题。Secure Backup为数据库备份提供了中央磁带备份管理平台,简化了Oracle数据库与文件系统的备份管理。数据就是企业的生命力,必须被妥善保管并保护,保持在服务器激活的状态过程中不被恶意访问。数据中心的安全策略是保护物理接入服务器、数据、公司网络的关键。当数据保存至磁带,Oracle Secure Backup 将与安全策略平行工作,它提供了安全的Inter-Domain数据交换与控制消息机制,限制授权用户对备份数据的管理并通过RMAN将数据加密写入至备份磁带。


借助于成熟的Secure Socket Layer (SSL) 技术,集成Oracle Advanced Security 选件,Oracle Secure Backup可以实现双向服务器授权、加密备份恢复控制消息、网络传输过程数据加密,从而保证数据备份传输过程中的安全。


数据库备份可以通过RMAN进行加密,然后由Oracle Secure Backup完成对磁带的加密格式写入。这种集成式的加密方案保证了数据在离开数据库之后的安全。RMAN加密备份的解密是在恢复操作或重载时自动完成的,任何需要的时候密钥都是可用的,用户可以任意选用密码或Oracle Encryption Wallet。


Oracle Secure Backup提供了基于密码和关联用户权限的用户层访问控制以避免备份数据被未授权用户所访问。每个配置用户必须被注册拥有指定的系列权限,可以在域内设置执行任何数据保护操作。通过组合内部预置的18种特殊的备份及恢复权力,备份者可以高度精确的控制用户对域内的访问行为。通过选择预定义的5类对用户接入域时对各角度“读”或“写”的安全控制类,同时用户也可以定义自己的安全控制类来满足用户环境内的安全需求。一个客户只能被赋予一种安全类但是一个安全类可以拥有多个用户。


4. 监控

4.1 精细审核

审核是普遍使用的最有效的安全性机制,它可以保证系统的合法用户能完成他们应该从事的工作,同时又可以限制他们对权限的滥用。通过审核,企业可以跟踪用户的活动,从而发现安全性的漏洞。而且,用户如果知道他们正受到跟踪,他们也不愿意滥用他们的权限。因为传统的审核将产生极大量的数据,因此也很难发现有用的信息,Oracle10g强化了精细审核(FGA)。利用精细的扩展审核,安全性漏洞将会非常容易被发现。例如,如果建立了一个审核策略用于重复性筛选信用卡号,就会自动生成一个警告,警告系统管理员可能的入侵。系统管理员可根据这些警告做出响应,如终止非法数据库的对话。


4.2 Audit Vault

如果说Oracle Database Vault是完善了对于数据库SYSDBA的权限控制,那么Oracle Audit Vault则是对于企业内部违规者的进一步预防和监控。


目前最新版本的OAV 10.2.3.1不但支持Oracle的数据库产品(Oracle Database 9i到10gR2的所有数据库版本),同时也提供了对于Microsoft SQL Server 2000/2005. IBM DB2 UDB 8.2/9.5, 以及 Sybase ASE 12.5/15.0等主流关系型数据库的支持,自动获捕获,存储,分析审计数据,用户已经能够通过单一的审计解决方案来完成对企业内各种数据库活动的审计工作。





对于Oracle数据库而言,OAV的元数据可以是数据库redo日志,数据库Audit日志以及操作系统Audit日志(其它类型的数据库会有所不同),这些元数据被Audit Agent通过预先制定的捕获规则抓取到Audit Server中,而Audit Server端则提供了多样的报表查询,并且根据预先定义的企业策略提供违规操作告警功能。


Oracle Audit Vault帮助机构实施“信任但是验证”原则。Oracle Audit Vault帮助企业简化合规报告,及早检测到威胁,同时以透明的方式收集并将审计数据整合到企业级的审计整合管理解决方案中;整合并保护从Oracle数据库采集的审计数据;通过简化IT安全官员和内部审计员的工作,大幅度降低企业合规性的成本;使用户拥有最大灵活性的同时,有效监控他们的活动以确保这些行为符合企业的制度。


结论


MSA架构体现了Oracle的深度防御理念(Defense-in-Depth),在网络层,存储层,人员层都提供了完善的防护,同时在监控方面又体现了Proactive的2.0理念,实时并且全方位的给予安全监控。


世上没有安全性的魔术子弹,但Oracle始终站在我们的产品安全性以及我们客户的系统安全性的背后。Oracle提出:


因为我们自己的企业也是运行在Oracle之上,我们也是提供坚不可摧软件的既得利益者。坚不可摧建立在我们具有20年为世界上最具安全性意识的客户建立系统的实力和经验的基础上,这些客户中包括情报部门和国防部,也建立在由十九个独立的安全性评估所提供的保障基础上。(我们最强劲的竞争对手只分别具有零个或一个评估。他们为什么不在安全产品的生命周期和可证明的安全软件上进行投资呢?)


坚不可摧的软件是一个长期的承诺,它已经在实施过程中,它将被扩展到对每个Oracle产品的相同的开发方法和保障评定中。今天,所有强烈关注安全性的客户都会考虑把他们的数据库运行在Oracle上。

posted @ 2009-03-25 10:33 巴西木 阅读(235) | 评论 (0)编辑 收藏

仅列出标题
共33页: First 2 3 4 5 6 7 8 9 10 Last