posts - 495,  comments - 11,  trackbacks - 0
  2007年9月28日

> 引言
  在Jorm中,主键的生成策略主要有AUTO、UUID、GUID、FOREIGN、SEQUENCE、INCREMENT、IDENTITY、ASSIGNED,下面分别来讲述这几种策略的应用场景

> GenerationType.AUTO
  Jorm的默认主键策略,自动增长型,自增步长为1,适用数据类型int,long,如:
  private int id // 默认策略就是AUTO,故可以不写主键策略
  或
  @Id(GenerationType.AUTO) // 默认策略可以省去不写的哦~
  private int id

> GenerationType.INCREMENT
  顾名思义,增长型,适用数据类型int,long。自增步长为1
  1> 使用默认自增步长1,如:
     @Id(GenerationType.INCREMENT)
     @Column("item_id")
     private long id;
  2> 使用自定义步长,如:
     @Id(value = GenerationType.INCREMENT, incrementBy=3) // 这里自增步长为3,注意写法
     private int id;

> GenerationType.IDENTITY
  对于那些实现了自动增长的数据库,可以使用IDENTITY,如MySQL,SQL Server,PostreSQL,前提是
  MySQL数据库中建表语句定义了主键为:id(你的主键列名) int NOT NULL AUTO_INCREMENT 或
                                                                      
id(你的主键列名) bigint NOT NULL AUTO_INCREMENT
  SQL Server数据库中建表语句定义了主键为:id int identity(xx, xx) 如此类似
  PostreSQL数据库中建表语句定义了主键为:id bigserial  或  id serial
  使用例子
  @Id(GenerationType.IDENTITY)
  @Column("id")
  private long sid;

> GenerationType.UUID
  与数据库无关的策略,适用数据类型:字符串类型,适用所有数据库,长度须大于或等于32
  @Id(GenerationType.UUID)
  private String id;

> GenerationType.GUID
  与UUID有点类似,不过这个id值是又数据库来生成的,适用于数据库MySQL、PostgreSQL、SQL Server、Oracle等
  @Id(GenerationType.GUID)
  private String id;

> GenerationType.FOREIGN
  适用于一对一关系中引用了另一个对象的主键作为自己的主键的情形,如:
  @Id(GenerationType.FOREIGN)
  @Column("identity_number")
  private String identity;

> GenerationType.SEQUENCE
  这个不用多说,应用于Oracle、H2、PostgreSQL等有sequence序列功能的数据库

> GenerationType.ASSIGNED
  用户自定义生成,需要由程序员手工给主键主动赋值

 

项目地址:http://javaclub.sourceforge.net/jorm.html
下载地址:http://sourceforge.net/projects/javaclub/files/jorm/


posted @ 2011-10-10 15:17 jadmin 阅读(1488) | 评论 (3)编辑 收藏

直接上代码吧:

> Demo one
public void batch_op_one() {

    session = Jorm.getSession();
    JdbcBatcher batcher = session.createBatcher();
    batcher.addBatch("delete from t_id_auto");
    batcher.addBatch("delete from t_incre");
    batcher.addBatch("delete from t_user");
    batcher.execute();
   
    session.beginTransaction();
    long start;
    try {
        start = System.currentTimeMillis();
        String sql = "INSERT INTO t_user(sex,age,career,name,id) VALUES(?,?,?,?,?)";
        for (int i = 0; i < 100000; i++) {
            batcher.addBatch(sql, new Object[] {"男", Numbers.random(98), Strings.random(10), Strings.fixed(6), (i+1) });}
            String sqlx = "INSERT INTO t_id_auto(name, id) VALUES(?, ?)";
            for (int i = 0; i < 100000; i++) {
                batcher.addBatch(sqlx, new Object[] {Strings.fixed(6), (i+1)});
                if(i > 200) {
                    //Integer.parseInt("kkk");
                }
            }
            batcher.execute();   
            System.out.println(System.currentTimeMillis() - start);
    } catch (Exception e) {
        session.rollback();
    } finally {
        session.endTransaction();
        session.close();
    }
}

> Demo two
public void batch_op_two() {

    session = Jorm.getSession();
    session.beginTransaction();
    session.clean(User.class);
    JdbcBatcher batcher = session.createBatcher();
    batcher.setBatchSize(500);// 指定每批处理的记录数
   
    User u;
    int times = 20 * 100;
    long start = System.currentTimeMillis();
    for(int i = 0; i < times; i++) {
     String sex = (i % 2 == 0 ? "男" : "女");
     u = new User(Strings.fixed(6), sex, Numbers.random(100), Strings.random(16));
     batcher.save(u);
    }
    batcher.execute();
    session.endTransaction();
    long cost = (System.currentTimeMillis() - start);
    System.out.println("Total:" + cost);
    System.out.println("Each:" + (float) cost / times);
    session.close();
}

项目地址:http://javaclub.sourceforge.net/jorm.html
下载地址: http://sourceforge.net/projects/javaclub/files/jorm/

posted @ 2011-10-09 20:09 jadmin 阅读(1287) | 评论 (0)编辑 收藏
关系数据库不支持继承,我们可以做如下的映射,这些映射都是牺牲关系模式的范式基础的
 
1,  用一个表包含所有继承层次的所有字段,然后标识列来标示是哪个类。这种映射方法最简单,但是是违反规范化的,而且有些字段要强制为NULL值,无法保证关系数据模型的数据完整性,这种映射方式性能最高,最简单。
 
2,  每个具体类一张表(意思就是父类不需要表),所有父属性在具体类表中重复,这种映射如果要查询父类要全部扫描子类表,而且一旦父类变化,这些字表要全部变化。
 
3,  每个类一张表,表里只包含所属类的属性,然后子类和父类共享外键,这种映射避免了第2种的可怕的修改,但是查询的时候要执行连接。
posted @ 2011-09-27 09:38 jadmin 阅读(195) | 评论 (0)编辑 收藏

      在一般情况下,在新增领域对象后,都需要获取对应的主键值。使用应用层来维护主键,在一定程度上有利于程序性能的优化和应用移植性的提高。在采用数据库自增主键的方案里,如果JDBC驱动不能绑定新增记录对应的主键,就需要手工执行查询语句以获取对应的主键值,对于高并发的系统,这很容易返回错误的主键。通过带缓存的DataFieldMaxValueIncrementer,可以一次获取批量的主键值,供多次插入领域对象时使用,它的执行性能是很高的。

      我们经常使用数据的自增字段作为表主键,也即主键值不在应用层产生,而是在新增记录时,由数据库产生。这样,应用层在保存对象前并不知道对象主键值,而必须在保存数据后才能从数据库中返回主键值。在很多情况下,我们需要获取新对象持久化后的主键值。在Hibernate等ORM框架,新对象持久化后,Hibernate会自动将主键值绑定到对象上,给程序的开发带来了很多方便。 

      在JDBC 3.0规范中,当新增记录时,允许将数据库自动产生的主键值绑定到Statement或PreparedStatement中。

      使用Statement时,可以通过以下方法绑定主键值: int executeUpdate(String sql, int autoGeneratedKeys) 

      也可以通过Connection创建绑定自增值的PreparedStatement: PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) 

      当autoGeneratedKeys参数设置为Statement.RETURN_GENERATED_KEYS值时即可绑定数据库产生的主键值,设置为Statement.NO_GENERATED_KEYS时,不绑定主键值。下面的代码演示了Statement绑定并获取数据库产生的主键值的过程:

      Statement stmt = conn.createStatement();
      String sql = "INSERT INTO t_topic(topic_title,user_id) VALUES(‘测试主题’,’123’)";
      stmt.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS); // ①指定绑定表自增主键值
      ResultSet rs = stmt.getGeneratedKeys();
      if( rs.next() ) {
           intkey = rs.getInt(); // ②获取对应的表自增主键值
      }

      Spring利用这一技术,提供了一个可以返回新增记录对应主键值的方法: int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) ,其中第二个参数类型org.springframework.jdbc.support.KeyHolder,它是一个回调接口,Spring使用它保存新增记录对应的主键,该接口的接口方法描述如下: 

      Number getKey() throws InvalidDataAccessApiUsageException;

      当仅插入一行数据,主键不是复合键且是数字类型时,通过该方法可以直接返回新的主键值。如果是复合主键,或者有多个主键返回时,该方法抛出 InvalidDataAccessApiUsageException。该方法是最常用的方法,因为一般情况下,我们一次仅插入一条数据并且主键字段类型为数字类型; 

      如果是复合主键,则列名和列值构成Map中的一个Entry。如果返回的是多个主键,则抛出InvalidDataAccessApiUsageException异常; 

      Map getKeys() throws InvalidDataAccessApiUsageException;

      如果返回多个主键,即PreparedStatement新增了多条记录,则每一个主键对应一个Map,多个Map构成一个List。

      List getKeyList(): 

      Spring为KeyHolder接口指代了一个通用的实现类GeneratedKeyHolder,该类返回新增记录时的自增长主键值。假设我们希望在新增论坛板块对象后,希望将主键值加载到对象中,则可以按以下代码进行调整:

      public voidaddForum(final Forum forum) {
            final String sql = "INSERT INTO t_forum(forum_name,forum_desc) VALUES(?,?)";
            KeyHolder keyHolder = newGeneratedKeyHolder(); // ①创建一个主键执有者
            getJdbcTemplate().update(newPreparedStatementCreator() {
                  public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                        PreparedStatement ps = conn.prepareStatement(sql);
                        ps.setString(1, forum.getForumName());
                        ps.setString(2, forum.getForumDesc());
                        returnps;
                  }
            }, keyHolder);
            forum.setForumId(keyHolder.getKey().intValue()); // ②从主键执有者中获取主键
      }

      这样,在调用addForum(Forum forum)新增forum领域对象后,forum将拥有对应的主键值,方便后继的使用。在JDBC 3.0之前的版本中,PreparedStatement不能绑定主键,如果采用表自增键(如MySQL的auto increment或SQLServer的identity)将给获取正确的主键值带来挑战——因为你必须在插入数据后,马上执行另一条获取新增主键的查询语句。下面给出了不同数据库获取最新自增主键值的查询语句: 

posted @ 2011-09-25 14:27 jadmin 阅读(986) | 评论 (0)编辑 收藏

1) Assigned
主键由外部程序负责生成,无需Hibernate参与。


2) hilo
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态。


3) seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。


4) increment
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。 这种方式可能产生的问题是:如果当前有多个实例访问同一个数据库,那么由于各个实例各自维护主键状态,不同实例可能生成同样的主键,从而造成主键重复异常。因此,如果同一数据库有多个实例访问,此方式必须避免使用。


5) identity
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL中的主键生成机制。


6) sequence
采用数据库提供的sequence 机制生成主键。如Oralce 中的Sequence。


7) native
由Hibernate根据底层数据库自行判断采用identity、hilo、sequence其中一种作为主键生成方式。


8) uuid.hex
由Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后以长度32 的字符串表示)作为主键。


9) uuid.string
与uuid.hex 类似,只是生成的主键未进行编码(长度16)。在某些数据库中可能出现问题(如PostgreSQL)。


10) foreign
使用外部表的字段作为主键。一般而言,利用uuid.hex方式生成主键将提供最好的性能和数据库平台适应性。
另外由于常用的数据库,如Oracle、DB2、SQLServer、MySql 等,都提供了易用的主键生成机制(Auto-Increase 字段或者Sequence)。我们可以在数据库提供的主键生成机制上,采用generator-class=native的主键生成方式。不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,


大量并发insert数据时可能会引起表之间的互锁。数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量), 之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。 因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成机制。


如果需要采用定制的主键生成算法,则在此处配置主键生成器,主键生成器须实现org.hibernate.id.IdentifierGenerator 接口

 

关键词: Hibernate  主键   主键生成方式  IdentifierGenerator

 

posted @ 2011-09-25 13:47 jadmin 阅读(998) | 评论 (0)编辑 收藏
     摘要:   阅读全文
posted @ 2011-09-23 16:17 jadmin 阅读(1268) | 评论 (1)编辑 收藏

http://www.oschina.net/news/21642/jdbc-orm-framework-1-0-6

主要更新:
----------------------------------------
 * [35] fix: oracle下一个分页取limit数错误的bug.
 * [34] fix: oracle下检测是否支持Savepoints时,一个未捕获的异常.
 * [33] add: 对bonecp的支持
 * [32] add: 对proxool的支持
 * [31] add: 对commons-dbcp的支持
 * [30] fix: classpath没有config.properties文件会报错


posted @ 2011-09-23 10:53 jadmin 阅读(191) | 评论 (0)编辑 收藏
> 引言
有时候我们有这样的需求,对象有一个属性可能有多个值,需要在数据库中作为一个字段存储

还是以User为例,career存储多个职业

> 建表
以MySQL为例,执行下面的sql建立数据表
CREATE TABLE `t_user` (                
        `id` int(11) NOT NULL,               
        `name` varchar(50) DEFAULT NULL,     
        `sex` char(4) DEFAULT NULL,          
        `age` int(11) DEFAULT NULL,          
        `career` varchar(100) DEFAULT NULL,  
        PRIMARY KEY (`id`)                   
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

> 代码
实体类 User.java
@Entity(table = "t_user")
@PK(value 
= "id")
public class User implements Serializable {

    
/** desc */
    
private static final long serialVersionUID = -4750351638245912867L;

    @Id
    
private int id;

    
private String name;

    
private String sex;

    
private Integer age;

    @Basic(processor
=DefinedFieldProcessor.class)
    
private String[] career;

    @NoColumn
    
private int kvalue;

    
public JawaUser() {
        
super();
    }

    
public JawaUser(String name, String sex, Integer age, String[] career) {
        
super();
        
this.name = name;
        
this.sex = sex;
        
this.age = age;
        
this.career = career;
    }

    
public int getId() {
        
return id;
    }

    
public void setId(int id) {
        
this.id = id;
    }

    
public String getName() {
        
return name;
    }

    
public void setName(String name) {
        
this.name = name;
    }

    
public String getSex() {
        
return sex;
    }

    
public void setSex(String sex) {
        
this.sex = sex;
    }

    
public Integer getAge() {
        
return age;
    }

    
public void setAge(Integer age) {
        
this.age = age;
    }

    
public String[] getCareer() {
        
return career;
    }

    
public void setCareer(String[] career) {
        
this.career = career;
    }

    
public int getKvalue() {
        
return kvalue;
    }

    
public void setKvalue(int kvalue) {
        
this.kvalue = kvalue;
    }

    
public String toString() {
        
return "User [age=" + age + ", career=" + Arrays.toString(career)
                
+ ", id=" + id + ", kvalue=" + kvalue + ", name=" + name
                
+ ", sex=" + sex + "]";
    }
}
属性字段处理类 DefinedFieldProcessor.java

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.javaclub.jorm.Session;
import org.javaclub.jorm.common.CommonUtil;
import org.javaclub.jorm.common.Reflections;
import org.javaclub.jorm.jdbc.process.FieldProcessor;

public
 class DefinedFieldProcessor implements FieldProcessor {

    
public Object insert(Session session, Object entity, Field field) {
        String[] crs 
= (String[]) Reflections.getFieldValue(entity, field);
        
if(!CommonUtil.isEmpty(crs)) {
            StringBuilder sbf 
= new StringBuilder();
            
for (int i = 0; i < crs.length; i++) {
                
if(i > 0) {
                    sbf.append(
",");
                }
                sbf.append(crs[i]);
            }
            
return sbf.toString();
        }
        
return "";
    }

    
public void load(Session session, Object entity, Field field, ResultSet rs,
            
int idx) throws SQLException {
        String str 
= rs.getString(idx);
        String[] crs 
= str.split(",");
        Reflections.setFieldValue(entity, field, crs);
    }

}

> 测试

import org.javaclub.jorm.Jorm;
import org.javaclub.jorm.Session;
import org.javaclub.jorm.common.Numbers;
import org.javaclub.jorm.common.Strings;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public
 class FieldProcessorTest {

    
static Session session;

    @BeforeClass
    
public static void setUpBeforeClass() {
        session 
= Jorm.getSession();
    }

    @AfterClass
    
public static void destroy() {
        Jorm.free();
    }

    @Test
    
public void test_save() {

        session.clean(User.
class);
        User u;
        
for (int i = 0; i < 100; i++) {
            String sex 
= (i % 2 == 0 ? "" : "");
            String[] cr 
= {};
            
if(i % 3 == 0) {
                cr 
= new String[] {Strings.fixed(2), Strings.random(5), Strings.fixed(6)};
            } 
else if(i % 3 == 1) {
                cr 
= new String[] {Strings.fixed(2), Strings.random(5)};
            } 
else {
                cr 
= new String[] {Strings.fixed(2)};
            }
            u 
= new User(Strings.fixed(6), sex, Numbers.random(100), cr);
            session.save(u);
        }

        
for (int i = 0; i < 10; i++) {
            u 
= session.read(User.class, i + 1);
            System.out.println(u);
        }
    }
}
posted @ 2011-09-22 20:16 jadmin 阅读(1213) | 评论 (0)编辑 收藏

> 准备
以MySQL为例,执行下面的sql建立数据表
CREATE TABLE `t_user` (                
        `id` int(11) NOT NULL,               
        `name` varchar(50) DEFAULT NULL,     
        `sex` char(4) DEFAULT NULL,          
        `age` int(11) DEFAULT NULL,          
        `career` varchar(100) DEFAULT NULL,  
        PRIMARY KEY (`id`)                   
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

> 引入jar或maven依赖,需要jar包
gerald-jorm-1.0.5.jar 最新版本下载:http://sourceforge.net/projects/javaclub/files
commons-logging-1.1.1.jar
log4j-1.2.14.jar
mysql-connector-java-5.1.6.jar
javassist-3.11.0.GA.jar 或 cglib-nodep-2.2.2.jar (根据实际情况选择性加入)


> 配置文件
在你的java工程的classpath下建立config.properties和jdbc.cfg.xml文件
config.properties内容:
# 下面路径可以根据实际情况指定,为相对classpath的路径地址
jdbc.config.path=jdbc.cfg.xml

jdbc.cfg.xml内容:
<?xml version='1.0' encoding="UTF-8"?>
<jdbc-configuration>

  <constant name="show_sql" value="true" />
  <constant name="jdbc.batch_size" value="600" />
  <constant name="bytecode.provider" value="cglib" />
 
  <connections default="simple">
 
    <connection name="simple">
      <property name="connection.implementation">org.javaclub.jorm.jdbc.connection.impl.SimpleConnection</property>
      <property name="connection.dialect">MySQLDialect</property>
      <property name="connection.driver">com.mysql.jdbc.Driver</property>
      <property name="connection.jdbcurl">jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=UTF-8</property>
      <property name="connection.database">test</property>
      <property name="connection.username">root</property>
      <property name="connection.password">root</property>
    </connection>

    <connection name="c3p0">
      <property name="connection.implementation">org.javaclub.jorm.jdbc.connection.impl.PooledConnection</property>
      <property name="connection.dialect">MySQLDialect</property>
      <property name="connection.driver">com.mysql.jdbc.Driver</property>
      <property name="connection.jdbcurl">jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=UTF-8</property>
      <property name="connection.database">test</property>
      <property name="connection.username">root</property>
      <property name="connection.password">root</property>
      <property name="connection.pool.min">1</property>
      <property name="connection.pool.max">8</property>
      <property name="connection.test.sql">select 1</property>
    </connection>
    
  </connections>

</jdbc-configuration>


> 实体类User.java
@PK(value = "id")
@Entity(table="t_user")
public class User {
    
    @Id
    private int id;

    private String name;

    private String sex;

    private Integer age;

    private String career;
    
    @NoColumn
    private int kvalue;
    
    public User() {
        super();
    }

    public User(String name, String sex, Integer age, String career) {
        super();
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.career = career;
    }

    public User(Integer id, String name, String sex, Integer age, String career) {
        super();
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.career = career;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getCareer() {
        return career;
    }

    public void setCareer(String career) {
        this.career = career;
    }

    public int getKvalue() {
        return kvalue;
    }

    public void setKvalue(int kvalue) {
        this.kvalue = kvalue;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[" + id + ", " + name + ", " + sex + ", " + age + ", " + career + "]");
        return sb.toString();
    }

}

这里数据库字段和java实体类User的属性在命名上是一致的,如果不一致,比如如果表创建sql为:
CREATE TABLE `t_user` (                
        `user_id` int(11) NOT NULL,               
        `user_name` varchar(50) DEFAULT NULL,     
        `sex` char(4) DEFAULT NULL,          
        `col_age` int(11) DEFAULT NULL,          
        `career_job` varchar(100) DEFAULT NULL,  
        PRIMARY KEY (`id`)                   
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

那么对应的实体User应该写成:
@PK(value = "id")
@Entity(table="t_user")
public class User {
    
    @Id
    @Column("user_id")
    private int id;

    @Column("user_name")
    private String name;
        
    // 与数据库字段命名一致,可以不指定@Column
    private String sex;

    @Column("col_age")
    private Integer age;

    @Column("career_job")
    private String career;
    
    @NoColumn
    private int kvalue;
    
    public User() {
        super();
    }

    public User(String name, String sex, Integer age, String career) {
        super();
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.career = career;
    }

    public User(Integer id, String name, String sex, Integer age, String career) {
        super();
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.career = career;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getCareer() {
        return career;
    }

    public void setCareer(String career) {
        this.career = career;
    }

    public int getKvalue() {
        return kvalue;
    }

    public void setKvalue(int kvalue) {
        this.kvalue = kvalue;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[" + id + ", " + name + ", " + sex + ", " + age + ", " + career + "]");
        return sb.toString();
    }

}


> 对User的增删查改,UserCrudTest.java,记得引入junit-4.8.2.jar
public class UserCrudTest {

    static Session session;
    
    @BeforeClass
    public static void before() {
        session = Jorm.getSession();
    }
    
    @AfterClass
    public static void after() {
        Jorm.free();
    }
    
    @Test
    public void save_user() {
        session.clean(User.class);
        User user = null;
        for (int i = 0; i < 600; i++) {
            String sex = (i % 2 == 0 ? "男" : "女");
            user = new User(Strings.fixed(5), sex, Numbers.random(98), Strings.random(8));
            session.save(user);
        }
    }
    
    @Test // 批量保存
    public void batch_save_user() {
        session.clean(User.class);
        JdbcBatcher batcher = session.createBatcher();
        User user = null;
        for (int i = 0; i < 600; i++) {
            String sex = (i % 2 == 0 ? "男" : "女");
            user = new User(Strings.fixed(5), sex, Numbers.random(98), Strings.random(8));
            batcher.save(user);
        }
        batcher.execute();
    }
    
    @Test
    public void loadUser() {
        User user = session.read(User.class, 1);
        // 这里user是一个代理对象,因为@Entity(table="t_user", lazy = true)
        System.out.println(user.getCareer());// 发出查询sql
    }
    
    @Test
    public void deletUser() {
        User user = session.read(User.class, 1);
        if(null != user) {
            session.delete(user);
        }
        user = session.read(User.class, 1);
        System.out.println(user);
    }
    
    @Test
    public void test_update_proxy() {
        
        User u;
        u = session.read(User.class, 2);
        Assert.assertNotNull(u);
        Assert.assertTrue(u instanceof JormProxy);
        
        u.setName("Gerald.Chen");
        session.update(u);
        System.out.println(u.getName());
        u = session.read(User.class, 2);
        Assert.assertTrue("Gerald.Chen".equals(u.getName()));
    }
    
    @Test
    public void queryUser() {
        SqlParams<User> params = new SqlParams<User>();
        params.setObjectClass(User.class);
        params.setFirstResult(8);
        params.setMaxResults(20);
        List<User> users = session.list(params);
        System.out.println(users.size());
        System.out.println(users);
    }
    
}

posted @ 2011-09-21 18:42 jadmin 阅读(1406) | 评论 (5)编辑 收藏

> 特点
  1.支持多数据源管理和配置
  2.自动封装Entity
  3.支持事务
  4.支持存储过程的方便调用
  5.支持lazy加载
  6.支持分页查询
  7.支持多种数据库H2,MySQL,Oracle,PostgrSQL,SQLServer

> 要求
  1.JDK 1.5 or later
  2.如需要lazy加载,需要引入cglib或javaassit,具体可配置

> 示例
  1.添加
  Session session = Jorm.getSession();
  User u = new User("Gerald.Chen", "男", 21, "job");;
  session.save(u);

  2.删除
  session.clean(User.class);// 清空表
  session.delete(User.class, "id > 100");// 指定条件删除

  session.delete(user);

  3.查询
  User user = session.read(User.class, 1);// 根据主键加载

  // 加载第一个
  User user = session.loadFirst(User.class, "(SELECT * FROM t_user WHERE id > ?)", 88);

  // 分页查询
  SqlParams<User> params = new SqlParams<User>("SELECT * FROM t_user WHERE id > ?", new Object[] { 6 });
  params.setObjectClass(User.class);
  params.setFirstResult(3);
  params.setMaxResults(10);
  List<User> users = session.list(params);

  // 查询单个属性
  String sql = "SELECT name FROM t_user WHERE id = 28";
  String name = session.queryUniqueObject(sql);

  // 查询属性列表
  List<String> names = session.list(String.class, "SELECT name FROM t_user WHERE id > ?", 200);
  List<Integer> ages = session.list(int.class, "SELECT age FROM t_user WHERE age > 18");

  4.存储过程
  final String pro = "{? = call hello_proc(?)}";
  String r = session.call(new ProcedureCaller() {
            
     public CallableStatement prepare() throws SQLException {
    CallableStatement cs = this.getSession().getConnection().prepareCall(pro);
    cs.setString(2, "World");
    cs.registerOutParameter(1, Types.CHAR);
    return cs;
     }
            
     public String callback(CallableStatement cs) throws SQLException {
    cs.execute();
    return cs.getString(1);
     }
  });

  5.事务
  session.clean(User.class);
  User u;
  session.beginTransaction();
  try {
    for(int i = 0; i < 1000; i++) {
        String sex = (i % 2 == 0 ? "男" : "女");
        u = new User(Strings.fixed(6), sex, Numbers.random(100), Strings.random(16));
        session.save(u);
        if(i == 886) {
            Integer.parseInt("kkk");
        }
    }
    session.commit();
  } catch (Exception e) {
    session.rollback();
  } finally {
    session.endTransaction();
  }

这是一个完全基于JDBC的轻量java orm framework, 目标定位于使用方便,简单,后续会增加许多新的特性


 

项目地址:http://javaclub.sourceforge.net/jorm.html

下载地址:http://sourceforge.net/projects/javaclub/files

 

posted @ 2011-09-20 18:52 jadmin 阅读(255) | 评论 (0)编辑 收藏

> 原理

其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已。
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:
假设服务器域名为 wwww.sjtu.edu.cn,文件名为 down.zip。
GET /down.zip HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-
excel, application/msword, application/vnd.ms-powerpoint, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Connection: Keep-Alive

服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

200
Content-Length=106786028
Accept-Ranges=bytes
Date=Mon, 30 Apr 2001 12:56:11 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web 服务器的时候要多加一条信息 -- 从哪里开始。
下面是用自己编的一个"浏览器"来传递请求信息给 Web 服务器,要求从 2000070 字节开始。
GET /down.zip HTTP/1.0
User-Agent: NetFox
RANGE: bytes=2000070-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

仔细看一下就会发现多了一行 RANGE: bytes=2000070-
这一行的意思就是告诉服务器 down.zip 这个文件从 2000070 字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
206
Content-Length=106786028
Content-Range=bytes 2000070-106786027/106786028
Date=Mon, 30 Apr 2001 12:55:20 GMT
ETag=W/"02ca57e173c11:95b"
Content-Type=application/octet-stream
Server=Microsoft-IIS/5.0
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

和前面服务器返回的信息比较一下,就会发现增加了一行:
Content-Range=bytes 2000070-106786027/106786028

返回的代码也改为 206 了,而不再是 200 了。

> 关键点

(1) 用什么方法实现提交 RANGE: bytes=2000070-。
当然用最原始的 Socket 是肯定能完成的,不过那样太费事了,其实 Java 的 net 包中提供了这种功能。代码如下:

URL url = new URL("http://www.sjtu.edu.cn/down.zip");
HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();

// 设置 User-Agent
httpConnection.setRequestProperty("User-Agent","NetFox");
// 设置断点续传的开始位置
httpConnection.setRequestProperty("RANGE","bytes=2000070");
// 获得输入流
InputStream input = httpConnection.getInputStream();

从输入流中取出的字节流就是 down.zip 文件从 2000070 开始的字节流。大家看,其实断点续传用 Java 实现起来还是很简单的吧。接下来要做的事就是怎么保存获得的流到文件中去了。

(2)保存文件采用的方法。
我采用的是 IO 包中的 RandAccessFile 类。
操作相当简单,假设从 2000070 处开始保存文件,代码如下:
RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");
long nPos = 2000070;
// 定位文件指针到 nPos 位置
oSavedFile.seek(nPos);
byte[] b = new byte[1024];
int nRead;
// 从输入流中读入字节流,然后写到文件中
while((nRead=input.read(b,0,1024)) > 0) {
     oSavedFile.write(b,0,nRead);

}

 

posted @ 2011-09-08 21:51 jadmin 阅读(105) | 评论 (0)编辑 收藏

SymmetricDS是一个平台独立的数据同步和复制的解决方案。

配置数据模型:

运行时数据模型:

posted @ 2011-09-02 09:15 jadmin 阅读(230) | 评论 (0)编辑 收藏

> 问题:给40亿个不重复的unsigned int的整数,没排过序的,然后再给几个数,如何快速判断这几个数是否在那40亿个数当中?

> 解决:unsigned int 的取值范围是0到2^32-1。我们可以申请连续的2^32/8=512M的内存,用每一个bit对应一个unsigned int数字。首先将512M内存都初始化为0,然后每处理一个数字就将其对应的bit设置为1。当需要查询时,直接找到对应bit,看其值是0还是1即可。

posted @ 2011-08-30 21:01 jadmin 阅读(137) | 评论 (0)编辑 收藏

lazy的属性有false、true、extra

false和true用得比较多,extra属性是不大容易重视的,其实它和true差不多

extra有个小的智能的地方是,即调用集合的size/contains等方法的时候,hibernate并不会去加载整个集合的数据,而是发出一条聪明的SQL语句,以便获得需要的值,只有在真正需要用到这些集合元素对象数据的时候,才去发出查询语句加载所有对象的数据


posted @ 2011-08-30 20:00 jadmin 阅读(107) | 评论 (0)编辑 收藏

本文将介绍在Linux(Red Hat 9)环境下搭建Hadoop集群,此Hadoop集群主要由三台机器组成,主机名分别为
linux      192.168.35.101
linux02  192.168.35.102
linux03  192.168.35.103

从map reduce计算的角度讲,linux作为master节点,linux02和linux03作为slave节点。
从hdfs数据存储角度讲,linux作为namenode节点,linux02和linux03作为datanode节点。


一台namenode机,主机名为linux,hosts文件内容如下:
127.0.0.1       linux          localhost.localdomain          localhost
192.168.35.101     linux          linux.localdomain              linux
192.168.35.102     linux02
192.168.35.103     linux03

两台datanode机,主机名为linux02和linux03
>linux02的hosts文件
127.0.0.1         linux02       localhost.localdomain       localhost
192.168.35.102     linux02       linux02.localdomain         linux02
192.168.35.101     linux
192.168.35.103     linux03
>inux03的hosts文件
127.0.0.1              linux03          localhost.localdomain          localhost
192.168.35.103          linux03            linux03.localdomain            linux03
192.168.35.101       linux
192.168.35.102       linux02

1.安装JDK
> 从java.cun.com下载jdk-6u7-linux-i586.bin

> ftp上传jdk到linux的root目录下

> 进入root目录,先后执行命令
chmod 755 jdk-6u18-linux-i586-rpm.bin
./jdk-6u18-linux-i586-rpm.bin

一路按提示下去就会安装成功

> 配置环境变量
cd进入/etc目录,vi编辑profile文件,将下面的内容追加到文件末尾
export JAVA_HOME=/usr/java/jdk1.6.0_18
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

注意:三台机器都要安装JDK~

2.设置Master/Slave机器之间可以通过SSH无密钥互相访问
最好三台机器的使用相同的账户名,我是直接使用的root账户

操作namenode机linux:
以用户root登录linux,在/root目录下执行下述命令:
ssh-keygen -t rsa
一路回车下去即可在目录/root/.ssh/下建立两个文件id_rsa.pub和id_rsa。

接下来,需要进入/root/.ssh目录,执行如下命令:
cd .ssh

再把is_rsa.pub文件复制到linux02和linux03机器上去。
scp -r id_rsa.pub root@192.168.35.102:/root/.ssh/authorized_keys_01
scp -r id_rsa.pub root@192.168.35.103:/root/.ssh/authorized_keys_01

操作datanode机linux02:
以用户root登录linux02,在目录下执行命令:
ssh-keygen -t rsa
一路回车下去即可在目录/root/.ssh/下建立两个文件 id_rsa.pub和id_rsa。

接下来,需要进入/root/.ssh目录,执行如下命令:
cd .ssh

再把is_rsa.pub文件复制到namenode机linux上去。
scp -r id_rsa.pub root@192.168.35.101:/root/.ssh/authorized_keys_02

操作datanode机linux03:
以用户root登录linux03,在目录下执行命令:
ssh-keygen -t rsa
一路回车下去即可在目录/root/.ssh/下建立两个文件 id_rsa.pub和id_rsa。

接下来,需要进入/root/.ssh目录,执行如下命令:
cd .ssh

再把is_rsa.pub文件复制到namenode机linux上去。
scp -r id_rsa.pub root@192.168.35.101:/root/.ssh/authorized_keys_03

*******************************************************************************

上述方式分别为linux\linux02\linux03机器生成了rsa密钥,并且把linux的id_rsa.pub复制到linux02\linux03上去了,而把linux02和linux03上的id_rsa.pub复制到linux上去了。

接下来还要完成如下步骤:

linux机:
以root用户登录linux,并且进入目录/root/.ssh下,执行如下命令:
cat id_rsa.pub >> authorized_keys
cat authorized_keys_02 >> authorized_keys
cat authorized_keys_03 >> authorized_keys
chmod 644 authorized_keys

linux02机:
以root用户登录linux02,并且进入目录/root/.ssh下,执行如下命令:
cat id_rsa.pub >> authorized_keys
cat authorized_keys_01 >> authorized_keys
chmod 644 authorized_keys

linux03机:
以root用户登录linux03,并且进入目录/root/.ssh下,执行如下命令:
cat id_rsa.pub >> authorized_keys
cat authorized_keys_01 >> authorized_keys
chmod 644 authorized_keys

通过上述配置,现在以用户root登录linux机,既可以无密钥认证方式访问linux02和linux03了,同样也可以在linux02和linux03上以ssh linux方式连接到linux上进行访问了。

3.安装和配置Hadoop
> 在namenode机器即linux机上安装hadoop
我下载的是hadoop-0.20.2.tar.gz,ftp上传到linux机的/root目录上,解压到安装目录/usr/hadoop,最终hadoop的根目录是/usr/hadoop/hadoop-0.20.2/

编辑/etc/profile文件,在文件尾部追加如下内容:
export HADOOP_HOME=/usr/hadoop/hadoop-0.20.2
export PATH=$HADOOP_HOME/bin:$PATH

> 配置Hadoop
core-site.xml:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<!-- Put site-specific property overrides in this file. -->
<configuration>
    <property>
                <name>fs.default.name</name>
                <value>hdfs://192.168.35.101:9000</value>
        </property>
        <property>
                <name>hadoop.tmp.dir</name>
                <value>/tmp/hadoop/hadoop-${user.name}</value>
        </property>
</configuration>

hdfs-site.xml:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<!-- Put site-specific property overrides in this file. -->
<configuration>
        <property>
                <name>dfs.name.dir</name>
                <value>/home/hadoop/name</value>
        </property>
        <property>
                <name>dfs.data.dir</name>
                <value>/home/hadoop/data</value>
        </property>
        <property>
                <name>dfs.replication</name>
                <value>2</value>
        </property>
</configuration>

mapred-site.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<!-- Put site-specific property overrides in this file. -->
<configuration>
        <property>
                <name>mapred.job.tracker</name>
                <value>192.168.35.101:9001</value>
        </property>
</configuration>

masters
192.168.35.101

slaves
192.168.35.102
192.168.35.103

至此,hadoop的简单配置已经完成

> 将在namenode机器上配置好的hadoop部署到datanode机器上
这里使用scp命令进行远程传输,先后执行命令
scp -r /usr/hadoop/hadoop-0.20.2 root@192.168.35.102:/usr/hadoop/
scp -r /usr/hadoop/hadoop-0.20.2 root@192.168.35.103:/usr/hadoop/

4.测试
以root用户登入namenode机linux,进入目录/usr/hadoop/hadoop-0.20.2/
cd /usr/hadoop/hadoop-0.20.2

> 执行格式化
[root@linux hadoop-0.20.2]# bin/hadoop namenode -format
11/07/26 21:16:03 INFO namenode.NameNode: STARTUP_MSG:
/************************************************************
STARTUP_MSG: Starting NameNode
STARTUP_MSG:   host = linux/127.0.0.1
STARTUP_MSG:   args = [-format]
STARTUP_MSG:   version = 0.20.2
STARTUP_MSG:   build = https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.20 -r 911707; compiled by 'chrisdo' on Fri Feb 19 08:07:34 UTC 2010
************************************************************/
Re-format filesystem in /home/hadoop/name ? (Y or N) Y
11/07/26 21:16:07 INFO namenode.FSNamesystem: fsOwner=root,root,bin,daemon,sys,adm,disk,wheel
11/07/26 21:16:07 INFO namenode.FSNamesystem: supergroup=supergroup
11/07/26 21:16:07 INFO namenode.FSNamesystem: isPermissionEnabled=true
11/07/26 21:16:07 INFO common.Storage: Image file of size 94 saved in 0 seconds.
11/07/26 21:16:07 INFO common.Storage: Storage directory /home/hadoop/name has been successfully formatted.
11/07/26 21:16:07 INFO namenode.NameNode: SHUTDOWN_MSG:
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at linux/127.0.0.1
************************************************************/

> 启动hadoop
[root@linux hadoop-0.20.2]# bin/start-all.sh
starting namenode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-namenode-linux.out
192.168.35.102: starting datanode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-datanode-linux02.out
192.168.35.103: starting datanode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-datanode-linux03.out
192.168.35.101: starting secondarynamenode, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-secondarynamenode-linux.out
starting jobtracker, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-jobtracker-linux.out
192.168.35.103: starting tasktracker, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-tasktracker-linux03.out
192.168.35.102: starting tasktracker, logging to /usr/hadoop/hadoop-0.20.2/bin/../logs/hadoop-root-tasktracker-linux02.out
[root@linux hadoop-0.20.2]#

> 用jps命令查看进程
[root@linux hadoop-0.20.2]# jps
7118 SecondaryNameNode
7343 Jps
6955 NameNode
7204 JobTracker
[root@linux hadoop-0.20.2]#

posted @ 2011-08-25 16:01 jadmin 阅读(125) | 评论 (0)编辑 收藏

引言

Hadoop分布式文件系统(HDFS)被设计成适 合运行在通用硬件(commodity hardware)上的分布式文件系统。它和现有的分布式文件系统有很多共同点。但同时,它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高 度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。HDFS放宽了一部分POSIX约束,来实 现流式读取文件系统数据的目的。HDFS在最开始是作为Apache Nutch搜索引擎项目的基础架构而开发的。HDFS是Apache Hadoop Core项目的一部分。这个项目的地址是http://hadoop.apache.org/core/

前提和设计目标

硬件错误

硬件错误是常态而不是异常。HDFS可能由成百上千的服务器所构成,每个服务器上存储着文件系统的部分数据。我们面对的现实是构成系统的组件数目是巨大 的,而且任一组件都有可能失效,这意味着总是有一部分HDFS的组件是不工作的。因此错误检测和快速、自动的恢复是HDFS最核心的架构目标。

流式数据访问

运行在HDFS上的应用和普通的应用不同,需要流式访问它们的数据集。HDFS的设计中更多的考虑到了数据批处理,而不是用户交互处理。比之数据访问的低 延迟问题,更关键的在于数据访问的高吞吐量。POSIX标准设置的很多硬性约束对HDFS应用系统不是必需的。为了提高数据的吞吐量,在一些关键方面对 POSIX的语义做了一些修改。

大规模数据集

运行在HDFS上的应用具有很大的数据集。HDFS上的一个典型文件大小一般都在G字节至T字节。因此,HDFS被调节以支持大文件存储。它应该能提供整 体上高的数据传输带宽,能在一个集群里扩展到数百个节点。一个单一的HDFS实例应该能支撑数以千万计的文件。

简单的一致性模型

HDFS应用需要一个“一次写入多次读取”的文件访问模型。一个文件经过创建、写入和关闭之后就不需要改变。这一假设简化了数据一致性问题,并且使高吞吐 量的数据访问成为可能。Map/Reduce应用或者网络爬虫应用都非常适合这个模型。目前还有计划在将来扩充这个模型,使之支持文件的附加写操作。

“移动计算比移动数据更划算”

一个应用请求的计算,离它操作的数据越近就越高效,在数据达到海量级别的时候更是如此。因为这样就能降低网络阻塞的影响,提高系统数据的吞吐量。将计算移 动到数据附近,比之将数据移动到应用所在显然更好。HDFS为应用提供了将它们自己移动到数据附近的接口。

异构软硬件平台间的可移植性

HDFS在设计的时候就考虑到平台的可移植性。这种特性方便了HDFS作为大规模数据应用平台的推广。

Namenode 和 Datanode

HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。Namenode是一个中心 服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上 的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组 Datanode上。Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的 映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

HDFS 架构

Namenode和Datanode被设计成可以在普通的商用机器上运行。这些机器一般运行着GNU/Linux操作系统(OS)。 HDFS采用Java语言开发,因此任何支持Java的机器都可以部署Namenode或Datanode。由于采用了可移植性极强的Java语言,使得 HDFS可以部署到多种类型的机器上。一个典型的部署场景是一台机器上只运行一个Namenode实例,而集群中的其它机器分别运行一个Datanode 实例。这种架构并不排斥在一台机器上运行多个Datanode,只不过这样的情况比较少见。

集群中单一Namenode的结构大大简化了系统的架构。Namenode是所有HDFS元数据的仲裁者和管理者,这样,用户数据永远不会流过Namenode。

文件系统的名字空间 (namespace)

HDFS支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统 类似:用户可以创建、删除、移动或重命名文件。当前,HDFS不支持用户磁盘配额和访问权限控制,也不支持硬链接和软链接。但是HDFS架构并不妨碍实现 这些特性。

Namenode负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被Namenode记录下来。应用程序可以设置HDFS保存的文件的副本数目。文件副本的数目称为文件的副本系数,这个信息也是由Namenode保存的。

数据复制

HDFS被设计成能够在一个大集群中跨机器可靠地存储超大文件。它将每个文件存储成一系列的数据块,除了最后一个,所有的数据块都是同样大小的。为了容 错,文件的所有数据块都会有副本。每个文件的数据块大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指 定,也可以在之后改变。HDFS中的文件都是一次性写入的,并且严格要求在任何时候只能有一个写入者。

Namenode全权管理数据块的复制,它周期性地从集群中的每个Datanode接收心跳信号和块状态报告(Blockreport)。接收到心跳信号意味着该Datanode节点工作正常。块状态报告包含了一个该Datanode上所有数据块的列表。

HDFS Datanodes

副本存放: 最最开始的一步

副本的存放是HDFS可靠性和性能的关键。优化的副本存放策略是HDFS区分于其他大部分分布式文件系统的重要特性。这种特性需要做大量的调优,并需要经 验的积累。HDFS采用一种称为机架感知(rack-aware)的策略来改进数据的可靠性、可用性和网络带宽的利用率。目前实现的副本存放策略只是在这 个方向上的第一步。实现这个策略的短期目标是验证它在生产环境下的有效性,观察它的行为,为实现更先进的策略打下测试和研究的基础。

大型HDFS实例一般运行在跨越多个机架的计算机组成的集群上,不同机架上的两台机器之间的通讯需要经过交换机。在大多数情况下,同一个机架内的两台机器间的带宽会比不同机架的两台机器间的带宽大。

通过一个机架感知的 过程,Namenode可以确定每个Datanode所属的机架id。一个简单但没有优化的策略就是将副本存放在不同的机架上。这样可以有效防止当整个机 架失效时数据的丢失,并且允许读数据的时候充分利用多个机架的带宽。这种策略设置可以将副本均匀分布在集群中,有利于当组件失效情况下的负载均衡。但是, 因为这种策略的一个写操作需要传输数据块到多个机架,这增加了写的代价。

在大多数情况下,副本系数是3,HDFS的存放策略是将一个副本存放在本地机架的节点上,一个副本放在同一机架的另一个节点上,最后一个副本放在不同机架 的节点上。这种策略减少了机架间的数据传输,这就提高了写操作的效率。机架的错误远远比节点的错误少,所以这个策略不会影响到数据的可靠性和可用性。于此 同时,因为数据块只放在两个(不是三个)不同的机架上,所以此策略减少了读取数据时需要的网络传输总带宽。在这种策略下,副本并不是均匀分布在不同的机架 上。三分之一的副本在一个节点上,三分之二的副本在一个机架上,其他副本均匀分布在剩下的机架中,这一策略在不损害数据可靠性和读取性能的情况下改进了写 的性能。

当前,这里介绍的默认副本存放策略正在开发的过程中。

副本选择

为了降低整体的带宽消耗和读取延时,HDFS会尽量让读取程序读取离它最近的副本。如果在读取程序的同一个机架上有一个副本,那么就读取该副本。如果一个HDFS集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。

安全模式

Namenode启动后会进入一个称为安全模式的特殊状态。处于安全模式的Namenode是不会进行数据块的复制的。Namenode从所有的 Datanode接收心跳信号和块状态报告。块状态报告包括了某个Datanode所有的数据块列表。每个数据块都有一个指定的最小副本数。当 Namenode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全(safely replicated)的;在一定百分比(这个参数可配置)的数据块被Namenode检测确认是安全之后(加上一个额外的30秒等待时 间),Namenode将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他Datanode上。

文件系统元数据的持久化

Namenode上保存着HDFS的名字空间。对于任何对文件系统元数据产生修改的操作,Namenode都会使用一种称为EditLog的事务日志记录 下来。例如,在HDFS中创建一个文件,Namenode就会在Editlog中插入一条记录来表示;同样地,修改文件的副本系数也将往Editlog插 入一条记录。Namenode在本地操作系统的文件系统中存储这个Editlog。整个文件系统的名字空间,包括数据块到文件的映射、文件的属性等,都存 储在一个称为FsImage的文件中,这个文件也是放在Namenode所在的本地文件系统上。

Namenode在内存中保存着整个文件系统的名字空间和文件数据块映射(Blockmap)的映像。这个关键的元数据结构设计得很紧凑,因而一个有4G 内存的Namenode足够支撑大量的文件和目录。当Namenode启动时,它从硬盘中读取Editlog和FsImage,将所有Editlog中的 事务作用在内存中的FsImage上,并将这个新版本的FsImage从内存中保存到本地磁盘上,然后删除旧的Editlog,因为这个旧的 Editlog的事务都已经作用在FsImage上了。这个过程称为一个检查点(checkpoint)。在当前实现中,检查点只发生在Namenode 启动时,在不久的将来将实现支持周期性的检查点。

Datanode将HDFS数据以文件的形式存储在本地的文件系统中,它并不知道有关HDFS文件的信息。它把每个HDFS数据块存储在本地文件系统的一 个单独的文件中。Datanode并不在同一个目录创建所有的文件,实际上,它用试探的方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。 在同一个目录中创建所有的本地文件并不是最优的选择,这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件。当一个Datanode启动时,它 会扫描本地文件系统,产生一个这些本地文件对应的所有HDFS数据块的列表,然后作为报告发送到Namenode,这个报告就是块状态报告。

通讯协议

所有的HDFS通讯协议都是建立在TCP/IP协议之上。客户端通过一个可配置的TCP端口连接到Namenode,通过ClientProtocol协议与Namenode交互。而Datanode使用DatanodeProtocol协议与Namenode交互。一个远程过程调用(RPC)模型被抽象出来封装ClientProtocol和Datanodeprotocol协议。在设计上,Namenode不会主动发起RPC,而是响应来自客户端或 Datanode 的RPC请求。

健壮性

HDFS的主要目标就是即使在出错的情况下也要保证数据存储的可靠性。常见的三种出错情况是:Namenode出错, Datanode出错和网络割裂(network partitions)。

磁盘数据错误,心跳检测和重新复制

每个Datanode节点周期性地向Namenode发送心跳信号。网络割裂可能导致一部分Datanode跟Namenode失去联系。 Namenode通过心跳信号的缺失来检测这一情况,并将这些近期不再发送心跳信号Datanode标记为宕机,不会再将新的IO请 求发给它们。任何存储在宕机Datanode上的数据将不再有效。Datanode的宕机可能会引起一些数据块的副本系数低于指定值,Namenode不 断地检测这些需要复制的数据块,一旦发现就启动复制操作。在下列情况下,可能需要重新复制:某个Datanode节点失效,某个副本遭到损 坏,Datanode上的硬盘错误,或者文件的副本系数增大。

集群均衡

HDFS的架构支持数据均衡策略。如果某个Datanode节点上的空闲空间低于特定的临界点,按照均衡策略系统就会自动地将数据从这个Datanode 移动到其他空闲的Datanode。当对某个文件的请求突然增加,那么也可能启动一个计划创建该文件新的副本,并且同时重新平衡集群中的其他数据。这些均 衡策略目前还没有实现。

数据完整性

从某个Datanode获取的数据块有可能是损坏的,损坏可能是由Datanode的存储设备错误、网络错误或者软件bug造成的。HDFS客户端软件实 现了对HDFS文件内容的校验和(checksum)检查。当客户端创建一个新的HDFS文件,会计算这个文件每个数据块的校验和,并将校验和作为一个单 独的隐藏文件保存在同一个HDFS名字空间下。当客户端获取文件内容后,它会检验从Datanode获取的数据跟相应的校验和文件中的校验和是否匹配,如 果不匹配,客户端可以选择从其他Datanode获取该数据块的副本。

元数据磁盘错误

FsImage和Editlog是HDFS的核心数据结构。如果这些文件损坏了,整个HDFS实例都将失效。因而,Namenode可以配置成支持维护多 个FsImage和Editlog的副本。任何对FsImage或者Editlog的修改,都将同步到它们的副本上。这种多副本的同步操作可能会降低 Namenode每秒处理的名字空间事务数量。然而这个代价是可以接受的,因为即使HDFS的应用是数据密集的,它们也非元数据密集的。当 Namenode重启的时候,它会选取最近的完整的FsImage和Editlog来使用。

Namenode是HDFS集群中的单点故障(single point of failure)所在。如果Namenode机器故障,是需要手工干预的。目前,自动重启或在另一台机器上做Namenode故障转移的功能还没实现。

快照

快照支持某一特定时刻的数据的复制备份。利用快照,可以让HDFS在数据损坏时恢复到过去一个已知正确的时间点。HDFS目前还不支持快照功能,但计划在将来的版本进行支持。

数据组织

数据块

HDFS被设计成支持大文件,适用HDFS的是那些需要处理大规模的数据集的应用。这些应用都是只写入数据一次,但却读取一次或多次,并且读取速度应能满 足流式读取的需要。HDFS支持文件的“一次写入多次读取”语义。一个典型的数据块大小是64MB。因而,HDFS中的文件总是按照64M被切分成不同的 块,每个块尽可能地存储于不同的Datanode中。

Staging

客户端创建文件的请求其实并没有立即发送给Namenode,事实上,在刚开始阶段HDFS客户端会先将文件数据缓存到本地的一个临时文件。应用程序的写 操作被透明地重定向到这个临时文件。当这个临时文件累积的数据量超过一个数据块的大小,客户端才会联系Namenode。Namenode将文件名插入文 件系统的层次结构中,并且分配一个数据块给它。然后返回Datanode的标识符和目标数据块给客户端。接着客户端将这块数据从本地临时文件上传到指定的 Datanode上。当文件关闭时,在临时文件中剩余的没有上传的数据也会传输到指定的Datanode上。然后客户端告诉Namenode文件已经关 闭。此时Namenode才将文件创建操作提交到日志里进行存储。如果Namenode在文件关闭前宕机了,则该文件将丢失。

上述方法是对在HDFS上运行的目标应用进行认真考虑后得到的结果。这些应用需要进行文件的流式写入。如果不采用客户端缓存,由于网络速度和网络堵塞会对吞估量造成比较大的影响。这种方法并不是没有先例的,早期的文件系统,比如AFS,就用客户端缓存来提高性能。为了达到更高的数据上传效率,已经放松了POSIX标准的要求。

流水线复制

当客户端向HDFS文件写入数据的时候,一开始是写到本地临时文件中。假设该文件的副本系数设置为3,当本地临时文件累积到一个数据块的大小时,客户端会 从Namenode获取一个Datanode列表用于存放副本。然后客户端开始向第一个Datanode传输数据,第一个Datanode一小部分一小部 分(4 KB)地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个Datanode节点。第二个Datanode也是这样,一小部分一小部分地 接收数据,写入本地仓库,并同时传给第三个Datanode。最后,第三个Datanode接收数据并存储在本地。因此,Datanode能流水线式地从 前一个节点接收数据,并在同时转发给下一个节点,数据以流水线的方式从前一个Datanode复制到下一个。

可访问性

HDFS给应用提供了多种访问方式。用户可以通过Java API接口访问,也可以通过C语言的封装API访问,还可以通过浏览器的方式访问HDFS中的文件。通过WebDAV协议访问的方式正在开发中。

DFSShell

HDFS以文件和目录的形式组织用户数据。它提供了一个命令行的接口(DFSShell)让用户与HDFS中的数据进行交互。命令的语法和用户熟悉的其他shell(例如 bash, csh)工具类似。下面是一些动作/命令的示例:

动作 命令
创建一个名为/foodir的目录 bin/hadoop dfs -mkdir /foodir
创建一个名为/foodir的目录 bin/hadoop dfs -mkdir /foodir
查看名为/foodir/myfile.txt的文件内容 bin/hadoop dfs -cat /foodir/myfile.txt

DFSShell 可以用在那些通过脚本语言和文件系统进行交互的应用程序上。

DFSAdmin

DFSAdmin 命令用来管理HDFS集群。这些命令只有HDSF的管理员才能使用。下面是一些动作/命令的示例:

动作 命令
将集群置于安全模式 bin/hadoop dfsadmin -safemode enter
显示Datanode列表 bin/hadoop dfsadmin -report
使Datanode节点datanodename退役 bin/hadoop dfsadmin -decommission datanodename

浏览器接口

一个典型的HDFS安装会在一个可配置的TCP端口开启一个Web服务器用于暴露HDFS的名字空间。用户可以用浏览器来浏览HDFS的名字空间和查看文件的内容。

存储空间回收

文件的删除和恢复

当用户或应用程序删除某个文件时,这个文件并没有立刻从HDFS中删除。实际上,HDFS会将这个文件重命名转移到/trash目录。只要文件还在/trash目录中,该文件就可以被迅速地恢复。文件在/trash中保存的时间是可配置的,当超过这个时间时,Namenode就会将该文件从名字空间中删除。删除文件会使得该文件相关的数据块被释放。注意,从用户删除文件到HDFS空闲空间的增加之间会有一定时间的延迟。

只要被删除的文件还在/trash目录中,用户就可以恢复这个文件。如果用户想恢复被删除的文件,他/她可以浏览/trash目录找回该文件。/trash目录仅仅保存被删除文件的最后副本。/trash目录与其他的目录没有什么区别,除了一点:在该目录上HDFS会应用一个特殊策略来自动删除文件。目前的默认策略是删除/trash中保留时间超过6小时的文件。将来,这个策略可以通过一个被良好定义的接口配置。

减少副本系数

当一个文件的副本系数被减小后,Namenode会选择过剩的副本删除。下次心跳检测时会将该信息传递给Datanode。Datanode遂即移除相应的数据块,集群中的空闲空间加大。同样,在调用setReplicationAPI结束和集群中空闲空间增加间会有一定的延迟。

参考资料

posted @ 2011-08-24 12:59 jadmin 阅读(127) | 评论 (0)编辑 收藏

function is_email($email) {
        $exp = "^[a-z'0-9]+([._-][a-z'0-9]+)*@([a-z0-9]+([._-][a-z0-9]+))+$";
        if(eregi($exp,$email)) {
            return true;
        }
        return false;
 }


posted @ 2011-08-22 19:37 jadmin 阅读(98) | 评论 (0)编辑 收藏

function remove_quote(&$str) {
        if (preg_match("/^\"/",$str)){
            $str = substr($str, 1, strlen($str) - 1);
        }
        //判断字符串是否以'"'结束
        if (preg_match("/\"$/",$str)){
            $str = substr($str, 0, strlen($str) - 1);;
        }
        return $str;
  }

posted @ 2011-08-22 19:36 jadmin 阅读(424) | 评论 (0)编辑 收藏

function is_chinese($s){
        $allen = preg_match("/^[^\x80-\xff]+$/", $s);   //判断是否是英文
        $allcn = preg_match("/^[".chr(0xa1)."-".chr(0xff)."]+$/",$s);  //判断是否是中文
        if($allen){  
              return 'allen';  
        }else{  
              if($allcn){  
                   return 'allcn';  
              }else{  
                   return 'encn';  
              }  
        } 
   }

posted @ 2011-08-22 10:14 jadmin 阅读(216) | 评论 (0)编辑 收藏

DML(data manipulation language):
       它们是SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这4条命令是用来对数据库里的数据进行操作的语言
DDL(data definition language):
       DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表时使用
DCL(Data Control Language):
       是数据库控制功能。是用来设置或更改数据库用户或角色权限的语句,包括(grant,deny,revoke等)语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行DCL

详细解释:
一、DDL is Data Definition Language statements. Some examples:数据定义语言,用于定义和管理 SQL 数据库中的所有对象的语言
      1.CREATE - to create objects in the database   创建
      2.ALTER - alters the structure of the database   修改
      3.DROP - delete objects from the database   删除
      4.TRUNCATE - remove all records from a table, including all spaces allocated for the records are removed
      TRUNCATE TABLE [Table Name]。
  下面是对Truncate语句在MSSQLServer2000中用法和原理的说明:
  Truncate table 表名 速度快,而且效率高,因为:
  TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。
  DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
  TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。
  对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
  TRUNCATE TABLE 不能用于参与了索引视图的表。
       5.COMMENT - add comments to the data dictionary 注释
       6.GRANT - gives user's access privileges to database 授权
       7.REVOKE - withdraw access privileges given with the GRANT command   收回已经授予的权限

二、DML is Data Manipulation Language statements. Some examples:数据操作语言,SQL中处理数据等操作统称为数据操纵语言
       1.SELECT - retrieve data from the a database           查询
       2.INSERT - insert data into a table                    添加
       3.UPDATE - updates existing data within a table    更新
       4.DELETE - deletes all records from a table, the space for the records remain   删除
       5.CALL - call a PL/SQL or Java subprogram
       6.EXPLAIN PLAN - explain access path to data
       Oracle RDBMS执行每一条SQL语句,都必须经过Oracle优化器的评估。所以,了解优化器是如何选择(搜索)路径以及索引是如何被使用的,对优化SQL语句有很大的帮助。Explain可以用来迅速方便地查出对于给定SQL语句中的查询数据是如何得到的即搜索路径(我们通常称为Access Path)。从而使我们选择最优的查询方式达到最大的优化效果。
       7.LOCK TABLE - control concurrency 锁,用于控制并发

三、DCL is Data Control Language statements. Some examples:数据控制语言,用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,对数据库实行监视等
       1.COMMIT - save work done 提交
       2.SAVEPOINT - identify a point in a transaction to which you can later roll back 保存点
       3.ROLLBACK - restore database to original since the last COMMIT   回滚
       4.SET TRANSACTION - Change transaction options like what rollback segment to use   设置当前事务的特性,它对后面的事务没有影响.


posted @ 2011-08-17 19:40 jadmin 阅读(106) | 评论 (0)编辑 收藏

今天启动Eclipse时,弹出错误提示:

解决办法:将Eclipse下的eclipse.ini文件做如下改动

=>

 

posted @ 2011-08-16 17:09 jadmin 阅读(105) | 评论 (0)编辑 收藏

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.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。


> sql优化方法

1、使用索引来更快地遍历表。

  缺省情况下建立的索引是非群集索引,但有时它并不是最佳的。在非群集索引下,数据在物理上随机存放在数据页上。合理的索引设计要建立在对各种查询的分析和预测上。一般来说:

  a.有大量重复值、且经常有范围查询( > ,< ,> =,< =)和order by、group by发生的列,可考虑建立群集索引;

  b.经常同时存取多列,且每列都含有重复值可考虑建立组合索引;

  c.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。索引虽有助于提高性能但不是索引越多越好,恰好相反过多的索引会导致系统低效。用户在表中每加进一个索引,维护索引集合就要做相应的更新工作。

2、在海量查询时尽量少用格式转换。

3、ORDER BY和GROPU BY:使用ORDER BY和GROUP BY短语,任何一种索引都有助于SELECT的性能提高。

4、任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

5、IN、OR子句常会使用工作表,使索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。

6、只要能满足你的需求,应尽可能使用更小的数据类型:例如使用MEDIUMINT代替INT

7、尽量把所有的列设置为NOT NULL,如果你要保存NULL,手动去设置它,而不是把它设为默认值。

8、尽量少用VARCHAR、TEXT、BLOB类型

9、如果你的数据只有你所知的少量的几个。最好使用ENUM类型

10、正如graymice所讲的那样,建立索引。

 

posted @ 2011-08-13 15:50 jadmin 阅读(114) | 评论 (0)编辑 收藏

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset

LIMIT子句可以被用于强制SELECT语句返回指定的记录数。LIMIT接受一个或两个数字参数,参数必须是一个整数常量。
如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。
初始记录行的偏移量是0(而不是1):为了与 PostgreSQL 兼容,MySQL 也支持句法: LIMIT # OFFSET #。

mysql> SELECT * FROM table LIMIT 5, 10;  // 检索记录行 6-15

//为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql> SELECT * FROM table LIMIT 95, -1; // 检索记录行 96-last.

//如果只给定一个参数,它表示返回最大的记录行数目:
mysql> SELECT * FROM table LIMIT 5;      //检索前 5 个记录行

//换句话说,LIMIT n 等价于 LIMIT 0,n。

sql-1.
SELECT * FROM table WHERE id >= (
    SELECT MAX(id) FROM (
       SELECT id FROM table ORDER BY id limit 90001
    ) AS tmp
) limit 100;

sql-2.
SELECT * FROM table WHERE id >= (
    SELECT MAX(id) FROM (
       SELECT id FROM table ORDER BY id limit 90000, 1
    ) AS tmp
) limit 100;

同样是取90000条后100条记录,第1句快还是第2句快?
第1句是先取了前90001条记录,取其中最大一个id值作为起始标识,然后利用它可以快速定位下100条记录
第2句择是仅仅取90000条记录后1条,然后取id值作起始标识定位下100条记录
第1句执行结果.100 rows in set (0.23) sec
第2句执行结果.100 rows in set (0.19) sec

很明显第2句胜出.看来limit好像并不完全像我之前想象的那样做全表扫描返回limit offset+length条记录,
这样看来limit比起MS-SQL的Top性能还是要提高不少的.

其实sql-2完全可以简化成:

SELECT * FROM table WHERE id >= (
    SELECT id FROM table limit 90000, 1
) limit 100;

直接利用第90000条记录的id,不用经过MAX函数运算,这样做理论上效率因该高一些,但在实际使用中几乎看不到效果,
因为本身定位id返回的就是1条记录,MAX几乎不用运作就能得到结果,但这样写更清淅明朗,省去了画蛇那一足.

可是,既然MySQL有limit可以直接控制取出记录的位置,为什么不干脆用SELECT id FROM table limit 90000, 1呢?岂不更简洁?

 

posted @ 2011-08-13 15:47 jadmin 阅读(116) | 评论 (0)编辑 收藏

tmp_table_size = 500mb //临时表大小设置

//指定用于索引的缓冲区大小,增加它可得到更好的索引处理性能。
//对于内存在4GB左右的服务器该参数可设置为256M或384M。
//注意:该参数值设置的过大反而会是服务器整体效率降低!
key_buffer_size = 384m

sort_buffer_size = 17mb //排序缓存

read_buffer_size=4m //读取缓存

table_cache=256 //表缓存

ft_min_word_len //全文搜索

query_cache_size 查询缓存

<?
#!/bin/sh
#######检查mysql状态
PORT=`netstat -na | grep "LISTEN" | grep "3306" | awk '{print $4}' | awk -F. '{print $2}'`
if [ "$PORT" -eq "3306" ]
        then
#######检查mysql占CPU负载
        mysql_cpu=`top -U root -b -n 1 | grep mysql | awk '{print $10}'|awk -F. '{print $1}'`
##如果mysql cpu负载大于80,则重启mysql
        if [ "$mysql_cpu" -ge "80" ]
                then
                ps xww |grep 'bin/mysqld_safe' |grep -v grep | awk '{print $1}' | xargs kill -9
                ps xww |grep 'libexec/mysqld' |grep -v grep | awk '{print $1}' | xargs kill -9
                sleep 5
                /usr/local/mysql/bin/mysqld_safe --user=root > /dev/null &
        else
                exit 0
        fi
else
         /usr/local/mysql/bin/mysqld_safe --user=root > /dev/null &
fi
?>

影响列数: 4999 (查询花费 0.1756 秒)
UPDATE `jobs_faces` SET postime = '1250784000' WHERE jid <505000 AND jid >500000

jobs_faces字段
字段 类型 整理 属性 Null 默认 额外 操作
jid int(10)   UNSIGNED 否  auto_increment              
oid int(10)   UNSIGNED 否 0               
cid mediumint(8)   UNSIGNED 否 0               
requests smallint(4)   UNSIGNED 否 0               
views mediumint(6)   UNSIGNED 是 0               
checked tinyint(1)   UNSIGNED 否 0               
istoped tinyint(1)   UNSIGNED 否 0               
postime int(10)   UNSIGNED 否 0               
losetime int(10)   UNSIGNED 否 0               
toped tinyint(1)   UNSIGNED 否 0               
toptime int(10)   UNSIGNED 否 0               
bold tinyint(1)   UNSIGNED 否 0               
highlight varchar(7) gbk_chinese_ci  否                
lightime int(10)   UNSIGNED 否 0               
people smallint(4)   UNSIGNED 否 0               
sex tinyint(1)   UNSIGNED 否 0               
djobskinds varchar(30) gbk_chinese_ci  否                
jname varchar(60) gbk_chinese_ci  否

影响列数: 4999 (查询花费 0.2393 秒)
UPDATE `jobs_faces` SET postime = '1250784000' WHERE jid <455000 AND jid >450000

posted @ 2011-08-13 15:45 jadmin 阅读(115) | 评论 (0)编辑 收藏

注意:要把php.ini中 extension=php_mbstring.dll 前的;号去掉,重启apache就可以了。
我创建三个文件:text1.txt   text2.txt   text3.txt
分别以ASCII  UTF-8  UNICODE 的编码方式保存

 

<?php
define ('UTF32_BIG_ENDIAN_BOM',  chr(0x00) . chr(0x00) . chr(0xFE) . chr(0xFF));
define ('UTF32_LITTLE_ENDIAN_BOM',  chr(0xFF) . chr(0xFE) . chr(0x00) . chr(0x00));
define ('UTF16_BIG_ENDIAN_BOM',  chr(0xFE) . chr(0xFF));
define ('UTF16_LITTLE_ENDIAN_BOM', chr(0xFF) . chr(0xFE));
define ('UTF8_BOM',  chr(0xEF) . chr(0xBB) . chr(0xBF));

function detect_utf_encoding($text) {
    $first2 = substr($text, 0, 2);
    $first3 = substr($text, 0, 3);
    $first4 = substr($text, 0, 3);
   
    if ($first3 == UTF8_BOM) return 'UTF-8';
    elseif ($first4 == UTF32_BIG_ENDIAN_BOM) return 'UTF-32BE';
    elseif ($first4 == UTF32_LITTLE_ENDIAN_BOM) return 'UTF-32LE';
    elseif ($first2 == UTF16_BIG_ENDIAN_BOM) return 'UTF-16BE';
    elseif ($first2 == UTF16_LITTLE_ENDIAN_BOM) return 'UTF-16LE';
}

function getFileEncoding($str){
    $encoding=mb_detect_encoding($str);
    if(empty($encoding)){
        $encoding=detect_utf_encoding($str);
    }
    return $encoding;
}

$file = 'text1.txt';
echo getFileEncoding(file_get_contents($file));  // 输出ASCII
echo '<br />';

$file = 'text2.txt';
echo getFileEncoding(file_get_contents($file));  // 输出UTF-8
echo '<br />';


$file = 'text3.txt';
echo getFileEncoding(file_get_contents($file));  // 输出UTF-16LE
echo '<br />';
?>


posted @ 2011-08-12 20:16 jadmin 阅读(160) | 评论 (0)编辑 收藏

1. 下载PostgreSQL数据库zip版本

2.  解压到D盘,例如:D:\database\postgresql

3.  cmd窗口进入D:\database\postgresq\bin,依次执行如下命令:
set PGHOME=D:\database\postgresq
set PGDATA=%PGHOME%\data
set PGLIB=%PGHOME%\lib
set PGHOST=localhost
set PATH=%PGHOME%\bin;%PATH%

4.  添加用户

> 添加windows用户,用于启动PostgreSQL的windows服务

D:\database\postgresql>net user postgres pgsqlpw /add /expires:never /passwordchg:no

> 为保证安全,此用户不允许本地登录
D:\database\postgresql>net localgroup users postgres /del

> 赋于windows用户postgres访问PostgreSQL安装目录的权限
D:\database\postgresql>cacls . /T /E /P postgres:R

5.  初始化数据库

> 切换到windows用户postgres的命令行环境
D:\database\postgresql>runas /noprofile /env /user:postgres "cmd"

> 初始化数据库,若不使用-U admin,则数据库里自动添加当前windows用户(即postgres)为数据库帐号
D:\database\postgresql>bin\initdb -D "D:\database\postgresql\data" -E UTF-8  --locale=chs -A md5 -U admin -W



6. 启动PostgreSQL服务:
pg_ctl -D D:\database\postgresql\data -l  D:\database\postgresql\pglog.txt start



7. 创建并连接数据库:
createdb test
psql -h localhost -w -d test



8. 关闭PostgreSQL服务:
pg_ctl -D  D:\database\postgresql\data stop

9. 注册为Windows服务:

> 注册为windows服务,当前windows用户(即postgres)将作为PostgreSQL服务的登录用户
D:\pgsql>bin\pg_ctl register -N PostgreSQL  -D “D:\database\postgresql\data

> 启动PostgreSQL服务
D:\pgsql> sc start PostgreSQL

 

 

posted @ 2011-08-11 20:46 jadmin 阅读(230) | 评论 (0)编辑 收藏

postgres=# select uuid_generate_v1();
uuid_generate_v1
--------------------------------------
86811bd4-22a5-11df-b00e-ebd863f5f8a7
(1 row)

postgres=# select uuid_generate_v4();
uuid_generate_v4
--------------------------------------
5edbfcbb-1df8-48fa-853f-7917e4e346db
(1 row)

主要就是uuid_generate_v1和uuid_generate_v4,当然还有uuid_generate_v3和uuid_generate_v5。

其他使用可以参见PostgreSQL官方文档 http://www.postgresql.org/docs/8.3/static/uuid-ossp.html


posted @ 2011-08-05 18:20 jadmin 阅读(586) | 评论 (0)编辑 收藏

> memcache介绍
Memcached是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。但是它并不提供冗余(例如,复制其hashmap条目);当某个服务器S停止运行或崩溃了,所有存放在S上的键/值对都将丢失。


Memcached官方:http://danga.com/memcached/

> memcache下载安装
下载Windows的Server端,下载地址:http://code.jellycan.com/memcached/

安装Memcache Server(也可以不安装直接启动)
1. 下载memcached的windows稳定版,解压放某个盘下面,比如在c:\memcached
2. 在CMD下输入 "c:\memcached\memcached.exe -d install" 安装.
3. 再输入:"c:\memcached\memcached.exe -d start" 启动。NOTE: 以后memcached将作为windows的一个服务每次开机时自动启动。这样服务器端已经安装完毕了。

如果下载的是二进制的版本,直接运行就可以了,可以加上参数来加以设置。

常用设置:
-p <num>          监听的端口
-l <ip_addr>      连接的IP地址, 默认是本机
-d start          启动memcached服务
-d restart        重起memcached服务
-d stop|shutdown  关闭正在运行的memcached服务
-d install        安装memcached服务
-d uninstall      卸载memcached服务
-u <username>     以<username>的身份运行 (仅在以root运行的时候有效)
-m <num>          最大内存使用,单位MB。默认64MB
-M                内存耗尽时返回错误,而不是删除项
-c <num>          最大同时连接数,默认是1024
-f <factor>       块大小增长因子,默认是1.25
-n <bytes>        最小分配空间,key+value+flags默认是48
-h                显示帮助

然后就可以用java的memcached客户端来试一下了。

posted @ 2011-08-01 10:41 jadmin 阅读(92) | 评论 (0)编辑 收藏

1 echo()
可以同时输出多个字符串,可以多个参数,并不需要圆括号,无返回值。

2 print()
只可以同时输出一个字符串,一个参数,需要圆括号,有返回值,当其执行失败时返flase .    print 的用法和C语言很像,所以会对输出内容里的%做特殊解释。
$a=print('hi');
echo $a;
//----------------------------
hi 1   //1是$a的值。
//-----------------------------

3 die();  // 和exit()区别。
有两个功能:先输出内容,然后退出程序。(常用在链接服务器,数据库)
mysql_connect("locahost","root","root") or die("链接服务器失败!");

4 printf();    //f指format格式化
printf("参数1",参数2):
参数1=按什么格式输出;参数2=输出的变量。
(%s:按字符串;%d:按整型;%b:按二进制;%x:按16进制;%X:按16进制大写输出;%o:按八进制; %f:按浮点型)

对于参数1,其格式如下:
%[ 'padding_character][-][width][.precision]type

说明:
所有转换都以%开头,如果想打印一个%,则必须用“%%”;
参数padding_character是可选的,用来填充变量直至指定的宽度,如:printf ("$%'a10.2f" , 43.2); //$aaaaa43.20,默认是填充一个空格,如果指定了一个空格或0就不需要使用“'”做为前缀。对于任何其它前缀则必须指定单引号。
【-】是可选的,添加它则表明数据应该左对齐。而不是默认的右对齐,如上例加一个-则为:printf ("$%'a-10.2f" , 43.2); //$43.20aaaaa
whidth 表示在这里为将被替换的变量留下多少空间(按字符计算)。如上例的10(包括小数点).
precision则必须是一个小数点开始,表示小数位后面要显示的位数。

函数,返回输出字符个数,把文字格式化以后输出,如:

printf ("$%01.2f" , 43.2); //$43.20

$表示填充的字符

0表示位数不够在不影响原值的情况下补0
1表示输出的总宽度
2表示小数位数,有四舍五入

%f 是表示显示为一个浮点数

格式化命令及说明:

%% 印出百分比符号,不转换。
%b 整数转成二进位。
%c 整数转成对应的 ASCII 字符。 如:printf ("$%c" , 65); // 输出:A
%d 整数转成十进位。 如:printf ("$%d" , 65.53); // 输出:65
%f 倍精确度数字转成浮点数。
%o 整数转成八进位。
%s 整数转成字符串。
%x 整数转成小写十六进位。
%X 整数转成大写十六进位

对于printf(),还可以使用带序号并以$符号结束的参数方式来指定参数转换的顺序。如:
printf ("the total is $%2$.2f and subtotal: %1$.2f" , 65.55,37.2); //the total is $37.20 and subtotal: 65.55
如上:%2$.2f指定了使用第二个参数65.55,%1$.2f则指定用第一个参数37.20。

   <?php
     $num=100.001;
     printf("%d",$num); //100
     printf("%s",$num); //100.001
     printf("%s---%d---%b---%x---%o---%f",$num,$num,$num,$num,$num,$num)
     //100.001---100---1100100---64---144---1001.00100
     printf("%.2f",$num); //100.00 (小数点保留2位)
     printf("%.1f",$num); //100.0 (小数点保留1位)
     printf("%`#10s",$num); // #10s
     printf("%#10s",$num); //10s
   ?>

5 sprintf();
此并不能直接输出,先赋给一个变量,然后再输出变量。
<?php
$num=100.001;
$a=sprintf("%d",$num);
echo $a; //100
?>

6 print_r();
功能:只用于输出数组。
$a = array (1, 2, array ("a", "b", "c"));
print_r ($a);

返回:
Array ( [0] => 1 [1] => 2 [2] => Array ( [0] => a [1] => b [2] => c ) )

7 var_dump();
功能: 输出变量的内容,类型或字符串的内容,类型,长度。常用来调试。
<?php
$a=100;
var_dump($a); //int(100)

$a=100.356;
var_dump($a); //float(100.356)
?>

8.var_export ();
返回关于传递给该函数的变量的结构信息,它和 var_dump() 类似,不同的是其返回的表示是合法的 PHP 代码。
您可以通过将函数的第二个参数设置为 TRUE,从而返回变量的值。
<?php
$a = array (1, 2, array ("a", "b", "c"));
var_export ($a);
/* 输出:
array (
0 => 1,
1 => 2,
2 =>
array (
   0 => 'a',
   1 => 'b',
   2 => 'c',
),
)
*/

$b = 3.1;
$v = var_export($b, TRUE);
echo $v;
/* 输出:
3.1
*/
?>

posted @ 2011-07-26 10:09 jadmin 阅读(78) | 评论 (0)编辑 收藏

mb_convert_encoding这个函数是用来转换编码的。原来一直对程序编码这一概念不理解,不过现在好像有点开窍了。
不过英文一般不会存在编码问题,只有中文数据才会有这个问题。比如你用Zend Studio或Editplus写程序时,用的是gbk编码,如果数据需要入数据库,而数据库的编码为utf8时,这时就要把数据进行编码转换,不然进到数据库就会变成乱码。

mb_convert_encoding的用法见官方:
http://cn.php.net/manual/zh/function.mb-convert-encoding.php

做一个GBK To UTF-8
< ?php
header("content-Type: text/html; charset=Utf-8");
echo mb_convert_encoding("妳係我的友仔", "UTF-8", "GBK");
?>

再来个GB2312 To Big5
< ?php
header("content-Type: text/html; charset=big5");
echo mb_convert_encoding("你是我的朋友", "big5", "GB2312");
?>
不过要使用上面的函数需要安装但是需要先enable mbstring 扩展库。

PHP中的另外一个函数iconv也是用来转换字符串编码的,与上函数功能相似。

下面还有一些详细的例子:
iconv — Convert string to requested character encoding
(PHP 4 >= 4.0.5, PHP 5)
mb_convert_encoding — Convert character encoding
(PHP 4 >= 4.0.6, PHP 5)

用法:
string mb_convert_encoding ( string str, string to_encoding [, mixed from_encoding] )
需要先enable mbstring 扩展库,在 php.ini里将; extension=php_mbstring.dll 前面的 ; 去掉
mb_convert_encoding 可以指定多种输入编码,它会根据内容自动识别,但是执行效率比iconv差太多;

string iconv ( string in_charset, string out_charset, string str )
注意:第二个参数,除了可以指定要转化到的编码以外,还可以增加两个后缀://TRANSLIT 和 //IGNORE,其中 //TRANSLIT 会自动将不能直接转化的字符变成一个或多个近似的字符,//IGNORE 会忽略掉不能转化的字符,而默认效果是从第一个非法字符截断。
Returns the converted string or FALSE on failure.

使用:
发现iconv在转换字符”—”到gb2312时会出错,如果没有ignore参数,所有该字符后面的字符串都无法被保存。不管怎么样,这个”—”都无法转换成功,无法输出。 另外mb_convert_encoding没有这个bug.

一般情况下用 iconv,只有当遇到无法确定原编码是何种编码,或者iconv转化后无法正常显示时才用mb_convert_encoding 函数.

from_encoding is specified by character code name before conversion. it can be array or string - comma separated enumerated list. If it is not specified, the internal encoding will be used.
/* Auto detect encoding from JIS, eucjp-win, sjis-win, then convert str to UCS-2LE */
$str = mb_convert_encoding($str, “UCS-2LE”, “JIS, eucjp-win, sjis-win”);
/* “auto” is expanded to “ASCII,JIS,UTF-8,EUC-JP,SJIS” */
$str = mb_convert_encoding($str, “EUC-JP”, “auto”);

例子:
$content = iconv(”GBK”, “UTF-8″, $content);
$content = mb_convert_encoding($content, “UTF-8″, “GBK”);

posted @ 2011-07-23 13:01 jadmin 阅读(95) | 评论 (0)编辑 收藏

选择【Window】菜单
Preferences ——>General——>Editors——>Text Editors——>Hyperlinking



posted @ 2011-07-22 10:17 jadmin 阅读(179) | 评论 (0)编辑 收藏

<?php
$photo  = 'http://www.xxx.com/uploads/5908618d80559a594164d984c5ca2b01_32.png';
if ($photo) {
        $http = new HttpRequest($photo, HTTP_METH_GET);
        try {
                $http->send();
        } catch(Exception $e) {
                try {
                        $http->send();
                } catch (Exception $e) {
                        try {
                                $http->send();
                        } catch (Exception $e) {
                                echo 'error occured while loading file.';
                                exit;
                        }
                }
        }
        if ($http->getResponseCode() == 200) {
                $header = $http->getResponseHeader();
                if (strstr($header['Content-Type'], 'image') !== FALSE) {
                     echo base64_encode($http->getResponseBody());
                }
        }
}
?>

posted @ 2011-07-22 09:45 jadmin 阅读(103) | 评论 (0)编辑 收藏

今天在MySQL中建立了一张表,其中一个字段是order,通过jdbc往里面插数据一直报错,好长时间找不到原因

结果把order字段的名称改成别的,居然成功插入数据,看来是MySQL字段列名不能使用insert、order等关键字


posted @ 2011-07-12 20:40 jadmin 阅读(101) | 评论 (0)编辑 收藏

> 添加curl扩展

1.在C\windows里的php.ini中我打开了extension=php_curl.dll的功能

2.把php目录中的libeay32.dll,ssleay32.dll拷到c:\windows\system32里

3.重新启动Apache

> 代码

<?php
   //初始化curl
   $ch = curl_init() or die (curl_error());
   echo "Test for searching 'php' in baidu.";
   //设置URL参数
   curl_setopt($ch,CURLOPT_URL,"http://www.baidu.com/s?wd=php");
   //要求CURL返回数据
   curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
   //执行请求
   $result = curl_exec($ch) or die (curl_error());
   //取得返回的结果,并显示
   echo $result;
   echo curl_error($ch);
   //关闭CURL
   curl_close($ch);
?>

> 效果

>CURL函数库(Client URL Library Function)

curl_close — 关闭一个curl会话
curl_copy_handle — 拷贝一个curl连接资源的所有内容和参数
curl_errno — 返回一个包含当前会话错误信息的数字编号
curl_error — 返回一个包含当前会话错误信息的字符串
curl_exec — 执行一个curl会话
curl_getinfo — 获取一个curl连接资源句柄的信息
curl_init — 初始化一个curl会话
curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄资源
curl_multi_close — 关闭一个批处理句柄资源
curl_multi_exec — 解析一个curl批处理句柄
curl_multi_getcontent — 返回获取的输出的文本流
curl_multi_info_read — 获取当前解析的curl的相关传输信息
curl_multi_init — 初始化一个curl批处理句柄资源
curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源
curl_multi_select — Get all the sockets associated with the cURL extension, which can then be "selected"
curl_setopt_array — 以数组的形式为一个curl设置会话参数
curl_setopt — 为一个curl设置会话参数
curl_version — 获取curl相关的版本信息


关键词:php抓取   php库函数   curl   php常用函数


posted @ 2011-07-08 18:19 jadmin 阅读(109) | 评论 (0)编辑 收藏

> 函数date(format,timestamp)
format         必需。规定时间戳的格式。
timestamp      可选。规定时间戳。默认是当前的日期和时间。
<?php
echo date("Y/m/d");
echo "<br />";
echo date("Y.m.d");
echo "<br />";
echo date("Y-m-d");
?>

> 格式化当前时间

<?php echo $showtime=date("Y-m-d H:i:s");?>
显示的格式: 年-月-日 小时:分钟:妙
相关时间参数:
a - "am" 或是 "pm"
A - "AM" 或是 "PM"
d - 几日,二位数字,若不足二位则前面补零; 如: "01" 至 "31"
D - 星期几,三个英文字母; 如: "Fri"
F - 月份,英文全名; 如: "January"
h - 12 小时制的小时; 如: "01" 至 "12"
H - 24 小时制的小时; 如: "00" 至 "23"
g - 12 小时制的小时,不足二位不补零; 如: "1" 至 12"
G - 24 小时制的小时,不足二位不补零; 如: "0" 至 "23"
i - 分钟; 如: "00" 至 "59"
j - 几日,二位数字,若不足二位不补零; 如: "1" 至 "31"
l - 星期几,英文全名; 如: "Friday"
m - 月份,二位数字,若不足二位则在前面补零; 如: "01" 至 "12"
n - 月份,二位数字,若不足二位则不补零; 如: "1" 至 "12"
M - 月份,三个英文字母; 如: "Jan"
s - 秒; 如: "00" 至 "59"
S - 字尾加英文序数,二个英文字母; 如: "th","nd"
t - 指定月份的天数; 如: "28" 至 "31"
U - 总秒数
w - 数字型的星期几,如: "0" (星期日) 至 "6" (星期六)
Y - 年,四位数字; 如: "1999"
y - 年,二位数字; 如: "99"
z - 一年中的第几天; 如: "0" 至 "365"

 

关键词: php学习   php教程  php格式化时间   php函数  date()

 

posted @ 2011-07-08 14:58 jadmin 阅读(106) | 评论 (0)编辑 收藏

C:\windows\php.ini

extension=php_xdebug.dll
xdebug.profiler_enable=on
xdebug.trace_output_dir="C:/www/test/xdebug"
xdebug.profiler_output_dir="C:/www/test/xdebug"

 

xdebug.default_enable = On
xdebug.show_exception_trace = On // 设置为On后,即使捕捉到异常,代码行仍将强制执行异常跟踪.
xdebug.show_local_vars = 1    // 将打印每个函数调用的最外围中的所有局部变量,包括尚未初始化的变量
xdebug.max_nesting_level = 50
xdebug.var_display_max_depth = 6 // 表示转储复杂变量的深度.

xdebug.dump_once = On
xdebug.dump_globals = On

// 如果进一步将 xdebug.dump_undefined 设为 On 并且不设定指定的超全局变量,则仍用值 undefined 打印变量.
xdebug.dump_undefined = On
xdebug.dump.REQUEST = *

// 将打印 PHP 超全局变量 $_SERVER['REQUEST_METHOD']、$_SERVER['REQUEST_URI'] 和 $_SERVER['HTTP_USER_AGENT'].
xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT

xdebug.trace_format  设为 0则输出将符合人类阅读习惯(将参数设为 1 则为机器可读格式).
xdebug.show_mem_delta = 1 则可以查看内存使用量是在增加还是在减少,    
xdebug.collect_params = 4 则可以查看传入参数的类型和值.要监视每个函数返回的值,请设定 xdebug.collect_return = 1.

PHP Warning:  Xdebug MUST be loaded as a Zend extension in Unknown on line 0 出错解决

;extension=php_xdebug.dll
zend_extension_ts="C:/php/ext/php_xdebug.dll"      //以zend方式加载
xdebug.profiler_enable=on
xdebug.trace_output_dir="C:/www/test/xdebug"
xdebug.profiler_output_dir="C:/www/test/xdebug"

 

posted @ 2011-07-07 14:31 jadmin 阅读(136) | 评论 (0)编辑 收藏

比如当前文件是放在(d:\www\)下,文件名是test.php。

<?php   echo __FILE__ ; // 取得当前文件的绝对地址,结果:D:\www\test.php   echo dirname(__FILE__); // 取得当前文件所在的绝对目录,结果:D:\www\   echo dirname(dirname(__FILE__)); //取得当前文件的上一层目录名,结果:D:\?>

使用方法提示,
dirname(__FILE__) 取到的是当前文件的绝对路径,也就是说,比起相对路径,查找速度是最快的。
如果重复一次可以把目录往上提升一个层次:
比如:$d = dirname(dirname(__FILE__));
其实就是把一个目录给dirname()做参数了.因为dirname()返回最后的目录不带\\或者是/
所以重复使用的时候可以认为 dirname() 把最下层的目录当成文件名来处理了.照常返回
当前目录的上级目录.这样重复就得到了它的上一级的目录.

包含得到上一级目录的文件
include(dirname(__FILE__).’/../filename.php’);

posted @ 2011-07-07 13:18 jadmin 阅读(94) | 评论 (0)编辑 收藏

本文主要介绍PHP5.2.11 + Apache2.2.19 + MySQL5.1.45的PHP集成运行环境的搭建(Windows XP SP3操作系统环境)

> 安装并配置APACHE(安装到C:\apache)

   1、安装时默认安装,Network Domain, Server Name 我填写我的计算机名,Administrator's Email Address区域填你的邮件地址
   2、安装完后在安装目录下有个conf文件夹,打开httpd.conf文件进行配置
        ·找到 DocumentRoot ,将其设置为你所要存放php, htm等网页文件的文件夹,如 "D:\phpapache\Apache2.2\htdocs";
        ·找到 DirectoryIndex ,在index.html后添加index.php, index.htm等,以单个空格将其分开;
        ·重启Apache,用http://localhosthttp://127.0.0.1http://yourcompanyname测试是否成功。成功的话屏幕会有个It works!

> 安装配置PHP(解压PHP压缩包到C:\php)

   1、将php.ini-recommended文件重命名为php.ini并将其剪到系统所在目录下(如放在2000/NT的WINNT, XP的Windows目录下),
   2、将extension_dir 改为php/ext所在目录,如 "C:\php\ext";
   3、将doc_root 改为第一步中的同样目录,如 "C:\apache\htdocs";
   4、找到 ;session.save_path = "/tmp" ,将';'去掉,设置你保存session的目录,如session.save_path = "C:/php/tmp";
   5、然后把下面几句前面的分号去掉,以更好支持Mysql and PHPmyadmin
        extension=php_mbstring.dll
        extension=php_gd2.dll
        extension=php_mysql.dll
        extension=php_pdo.dll
        extension=php_pdo_mysql.dll

> PHP+APACHE整合

   1、允许Apache将PHP程序作为模块来运行:
           打开httpd.conf,添加下面内容(位置任意):
             LoadModule php5_module "C:/php/php5apache2_2.dll"

             AddType application/x-httpd-php .php
             AddType application/x-httpd-php .htm
            (.htm, .php为可执行php语言的扩展名,也可加html, php3, php4,甚至txt)
           (以下两步可以不需要)
   2、如果你出于某种原因而需要在CGI模式中运行PHP程序(使用Php.exe),
          请将上面这一行变成注释(各行头加#即可),添加下面这些行:
           # ScriptAlias /php/ "C:/php/"
           # AddType application/x-httpd-php .php
           #Action application/x-httpd-php "/php/php-cgi.exe"
   3、现在apache 2 支持HTML而不支持PHP,先把下面几句加到C:\apache\conf\httpd.conf去:
           # ScriptAlias /php/ "C:/php/"
           # AddType application/x-httpd-php .php
           #Action application/x-httpd-php "/php/php-cgi.exe"

> 重启服务,测试环境

   1、在C:\php里找到php5ts.dll,libmysql.dll将其复制到C:\winnt\system32下(winNT/2000的机器),而winXP/2003是复制到C:\windows\system32下
   2、测试Apache与php是否连接成功:
          启动start apache服务或者正在运行的就重新启动restart apache
   3、在Web根目录下新建test.php(即C:\apache\htdocs目下)
           <html>
           <head><title>test</title></head>
           <body>
            <?php
              phpinfo();
            ?>
         </body>
          </html>

   4、运行http://localhost/test.php
         如果成功,则应该看到一个含有PHP徽标的网页,其中包含大量设置和其他信息
         那么恭喜你,环境已经搭建成功!

 

关键词:PHP   PHP5  Apache  MySQL  PHP运行环境 

posted @ 2011-07-07 12:05 jadmin 阅读(80) | 评论 (0)编辑 收藏

  Hibernate 团队对外宣布了一个新的家族成员,Hibernate OGM, OGM 是 Object Grid Mapping的缩写,它的目标是试图使用 JPA 来操作 NoSQL数据库,目前似乎局限于Infinispan 。

  目前支持的特性:

  • CRUD operations for entities
  • properties with simple (JDK) types
  • embeddable objects
  • entity hierarchy
  • identifier generators (TABLE and all in-memory based generators today)
  • optimistic locking
  • @ManyToOne,@OneToOne,@OneToManyand@ManyToManyassociations
  • bi-directional associations
  • Set,ListandMapsupport for collections
  • most Hibernate native APIs (likeSession) and JPA APIs (likeEntityManager)
  • same bootstrap model found in JPA or Hibernate Core: in JPA, set<provider>toorg.hibernate.ogm.jpa.HibernateOgmPersistenceand you're good to go

  下载:http://www.hibernate.org/subprojects/ogm/download

  参考手册:http://docs.jboss.org/hibernate/ogm/3.0/reference/en-US/html_single/

  PS:从目前情况看,不支持流行的 MongoDB 等等。与DataNucleus(http://www.datanucleus.org)在Backend的存储技术方面,还不能相提并论,DataNucleus支持JDO,JPA标准,支持目前几乎所有的流行的存储方式,Google的APPEngine也是基于DataNucleus的。


posted @ 2011-06-21 12:58 jadmin 阅读(143) | 评论 (0)编辑 收藏

  ”…在很多领域,专家的作用体现在他们的专业知识上而不是智力上。“

  --Don Reinertsen

  领域驱动设计(Domain Driven Design)是一种软件开发方法,目的是让软件系统在实现时准确的基于对真实业务过程的建模并根据真实业务过程的调整而调整。

  传统的开发工作趋向于一种以技术为先导的过程,需求从业务方传递到开发团队,开发人员依据需求上的描述创造出最有可能的假想。

  在瀑布开发过程中,这导致了大量的需要频繁校对,分析,复核和审批的需求文档。之后这些文档被交给开发团队去变成能够运行的软件。

  敏捷开发方法同样可以采纳瀑布模式过程中产生的需求文档,但敏捷方法在实际的处理过程中会把它们分成很小的任务和“故事”,之后的开发工作将依据这些任务的排序。

  领域驱动设计很大程度上使你从这两种截然不同的结果中抽身出来,让你能看到需求是如何在第一现场被收集到——如果你愿意看的话,它在动手先做的方式和在最后一分钟才做的方式之间做了弥补。

  领域驱动设计方式知道需求是永远不会“完成”的,需求就像一个活的文档。更重要的是,这些仍待讨论的活文档实际上就是软件自身——所有的文档都是程序代码的一种影像,一种演示品。

  随着软件系统的开发和发展,你对各种问题的理解也会更深——领域驱动设计就是要通过深入的理解问题来找到问题的解决方案。

  然而,领域驱动设计真正的不同之处却是,它把软件系统当作业务过程的一个影射,是使能动,而不是驱动。领域驱动设计是要你深入到业务过程中,了解业务术语和实践方法。技术方面的事被放在了第二位,只是最终的一种手段而已。

  Ubiquitous语言(UL)是领域驱动设计的中心——这是一种共有的不断成长的语言。它是一种来源于业务术语、经过开发团队的补充而产生 的协商后的语言。如果一个业务人员不懂得UL里的一个术语,有可能是UL需要改进发展。如果一个技术人员不懂得UL里的一个术语,有可能是他们需要跟领域 专家进行交流。

  领域专家是领域驱动设计里第二重要的组成部分——这些人能够对这个领域有深入的了解,包括这个业务本身。这些人构成了开发过程中必要的组成部 分。他们也许像一些敏捷开发方法里传统的产品拥有者那样不需要“全天候”的在职,但他们必须在开发过程中能被持续的接触到,而且随时准备好参与到开发过程 中。领域专家不能被当作门外人,而应被当作领域驱动设计过程中的核心——他们非常像是开发团队中的一部分,就像普通的开发者和测试者一样。

  领域驱动设计没有开始和结束——它是一个不断的再评估,再重构,再建模,再设计的持续过程——每一次的对话都会使你对问题有更进一步的理解。领 域驱动设计没有“完成”点——它永远都在进行;Ubiquitous语言会不断发展和成长,领域模型随着对业务理解的改变而改变,代码不断的再组织和重构 来更好的表现你的理解。

  各种模拟产物产生又抛弃,而唯一真正有意义的只有代码。它是解决方案的唯一表达,是一种不再抽象的表达。文档是用来解释和描述系统的,而只有代 码能不失分毫的做到这些。这就是说,在领域驱动设计里,代码必须保持高质量,要清晰,要有表达力,没有技术上省略和专门用语,尽可能的要让代码能够在被解 释时对领域专家有些意义。

  领域驱动设计里没有精巧的代码,也没有奇特的处理过程,或“你不需要知道”的模块。领域专家不需要成为开发人员来理解软件系统里用来做这些工作的关键部分是什么。他们同样也不需要考虑数据库或批处理任务或其他技术相关的方面。

  领域驱动设计是敏捷方法的终极表达——它是用来处理不断变化和发展的需求的——正如任何一个从未涉足软件项目的人都知道——一个项目的需求从开始到结束保持一成不变是极其罕见的,绝大多数情况是它会随着业务的增长和变化而变化。

  通过不断的交流,领域驱动设计会指导你用软件最精确的表达你的业务过程。

 

关键词:领域模型     设计     领域驱动设计

posted @ 2011-06-11 02:26 jadmin 阅读(92) | 评论 (0)编辑 收藏

这是一款开源PHP5写的MongoDB管理工具,项目地址:http://code.google.com/p/rock-php

 

具体安装使用可参考wiki --->http://code.google.com/p/rock-php/wiki/rock_mongo_zh

 

关键词:数据库   database  MongoDB   数据库连接   数据库管理工具  MongoDB管理工具

 

posted @ 2011-06-10 15:07 jadmin 阅读(117) | 评论 (0)编辑 收藏

TimeUnit是一个枚举类型,可以将时间方便快捷的转换为(天、时、分、秒、纳 秒)day,hour,minute,second,millli...
有了这个类我们可以方便将时间进行转换 


1、我们将1个小时转换为多少分钟、多少秒
  1小时转换分钟数为60分钟
  TimeUnit.HOURS.toMinutes(1) =>60
  1小时转换分钟数为3600秒
  TimeUnit.HOURS.toSeconds(1) =>3600
2、如果将秒转换为小时、分钟呢
  3600秒转换分钟数为60分钟
  TimeUnit.SECONDS.toMinutes(3600) =>60
  3600秒转换小时数为1小时
  TimeUnit.SECONDS.toHours(3600) =>1

posted @ 2011-06-10 09:23 jadmin 阅读(147) | 评论 (0)编辑 收藏

怎么有效的提高页面的打开速度,提高网站性能,发现查看网站页面源代码的时候,页面上充斥了无数的空格跟换行,

增加了页面的体积,这样会影响页面性能,为了有效的解决这个问题,现提供方法如下:

1、在工程的web.xml上加上如下配置

<web-app
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   metadata-complete="false"
   version="2.5">

<jsp-config>  
       <jsp-property-group>  
           <url-pattern>*.jsp</url-pattern>  
           <trim-directive-whitespaces>true</trim-directive-whitespaces>  
       </jsp-property-group>   
</jsp-config>

2、在每个JSP的头上加上一段代码 <%@ page trimDirectiveWhitespaces="true" %>

以上两种方法取其一即可,建议使用第一种。

PS:

第一种方式要求:web.xml 中的配置需在servlet2.5、tomcat6.0以上使用才会有效。
第二种方式要求:jsp版本需要在jsp2.1及以上版本使用才会有效。
低版本的环境下,使用仅无效果,不会对应用功能造成影响。

 

 JSP、SERVLET版本查看方式:

找到tomcat下的lib目录,查看jsp-api.jar和servlet-api.jar两个jar包,jar包里面的META-INF文件夹下的MANIFEST.MF文件,里面有相应的版本号
   如(jsp2.1):
   Name: javax/servlet/jsp/
   Specification-Title: Java API for JavaServer Pages
   Specification-Version: 2.1
   Specification-Vendor: Sun Microsystems, Inc.
   Implementation-Title: javax.servlet.jsp
   Implementation-Version: 2.1.FR
   Implementation-Vendor: Apache Software Foundation

原理: tomcat在将JSP解释成JAVA文件时,会根据trim-directive-whitespaces来判断,生成的代码在遇到jsp标签时,是否需要输出一段代码:

out.write("\r\n");

所以这种去空格的方式是在tomcat每次编译JSP时,就一次处理的,一旦jsp生成了对应的JAVA,后续的处理过程中,即不再去处理空格的问题,有效的节省资源。

 

posted @ 2011-06-09 18:29 jadmin 阅读(131) | 评论 (0)编辑 收藏

简介: Spring 的依赖配置方式与 Spring 框架的内核自身是松耦合设计的。然而,直到 Spring 3.0 以前,使用 XML 进行依赖配置几乎是唯一的选择。Spring 3.0 的出现改变了这一状况,它提供了一系列的针对依赖注入的注解,这使得 Spring IoC 在 XML 文件之外多了一种可行的选择。本文将详细介绍如何使用这些注解进行依赖配置的管理。

使用 @Repository、@Service、@Controller 和 @Component 将类标识为 Bean

Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发。@Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。具体只需将该注解标注在 DAO 类上即可。同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean 的自动扫描功能,这可以通过 <context:component-scan/> 实现。如下所示:

posted @ 2011-06-09 16:17 jadmin 阅读(109) | 评论 (0)编辑 收藏

Java很多ThreadDump中,都可以看到Thin Lock, Fat Lock, Spin Lock,这些Lock都与Java语言、OS有密切的关系。
回到一个简单的问题,在Java中,如何实现Synchronizd?
最简单的一种做法是,利用OS的mutex机制,把Java的同步(基于Object),翻译成OS相关的monitor_enter和monitor_exit原语。

回到Java锁本身,锁在不同的应用下有着不同的统计表现,而大部分的统计数据表明,其实线程抢锁,即锁竞争,都是短暂的,在大部分的情况下,几乎都不会发生锁竞争的现象。
也就是说,Java锁,从安全性的角度来看,是有点累赘。
因此,大量的专家都在锁上针对这样的统计特性对Java锁进行优化。
其中一种优化方案是,我们对所有的锁都需要monitor_enter和monitor_exit吗?事实上不需要。

如果我们把monitor_enter/monitor_exit看成是Fat Lock方式,则可以把Thin Lock看成是一种基于CAS(Compare and Swap)的简易实现。
这两种锁,简单一点理解,就是:

而基于CAS方式的实现,线程进入竞争状态的,获得锁的线程,会让其他线程处于自旋状态(也称之为Spin Mode,即自旋),这是一种while(Lock_release) doStuff()的Busy-Wait方式,是一种耗CPU的方式;而Fat Lock方式下,一个线程获得锁的时候,其他线程可以先sleep,等锁释放后,再唤醒(Notify)。
CAS的优点是快,如果没有线程竞争的情况下,因为CAS只需要一个指令便获得锁,所以称之为Thin Lock,缺点也是很明显的,即如果频繁发生线程竞争,CAS是低效,主要表现为,排斥在锁之外的线程是Busy Wait状态;而monitor_enter/monitor_exit/monitor_notify方式,则是重量级的,在线程产生竞争的时候,Fat Lock在OS mutex方式下,可以实现no busy-wait。

于是,JVM早期版本的做法是,如果T1, T2,T3,T4...产生线程竞争,则T1通过CAS获得锁(此时是Thin Lock方式),如果T1在CAS期间获得锁,则T2,T3进入SPIN状态直到T1释放锁;而第二个获得锁的线程,比如T2,会将锁升级(Inflation)为Fat Lock,于是,以后尝试获得锁的线程都使用Mutex方式获得锁。

这种设计为锁提供了两条路径:Thin Lock路径和Fat Lock路径,大部分情况下,可能都是走Thin Lock路径,而可能少部分情况,是走Fat Lock路径,这种方式提供了锁升级,但是避免不了Busy Wait,而且Thin-Lock升级Fat-Lock之后,没有办法回退到Thin-Lock(性能比Fat-Lock更好)。

Tasuki锁为这种方式做了2个优化:
1) 避免CAS导致Busy wait
2) Fat Lock可以deflate(与Inflate刚好相反)为Thin Lock(之前是Thin Lock变成Fat Lock之后便不能再回退)。

经过这样的改造后,锁性能提高了10%以上。

目前,Oracle的BEA JRockit与IBM的JVM都实现了Tasuki锁机制,唯一的不同是,在锁实现上都做了不同启发式的设计,即根据运行时采样的数据,动态调整一些权值数据,一边左右Lock Inflation/Lock Defaltion的过程(一颗树的两个分支),获取更好的锁性能。

 

关键词:JAVA   对象锁   JVM   锁机制   JVM锁   LOCK


posted @ 2011-06-09 01:06 jadmin 阅读(115) | 评论 (0)编辑 收藏

顾名思义:同步任务是指事情需要一件一件的做,做完当前的任务,才能开始做下一任务;异步任务是指做当前任务的同时,后台还可以在执行其他任务,可理解为可同时执行多任务,不必一件一件接着去做,下面开始上例子了

1.同步任务

/*
 * @(#)SyncTaskExecutorTest.java    2011-4-27
 *
 * Copyright (c) 2011. All Rights Reserved.
 *
 */

package org.jsoft.opensource.demos.spring.task;

import org.junit.Test;
import org.springframework.core.task.SyncTaskExecutor;

/**
 * Spring同步任务处理
 *
 * @author <a href="mailto:hongyuan.czq@taobao.com">Gerald Chen</a>
 * @version $Id: SyncTaskExecutorTest.java,v 1.1 2011/05/30 08:58:07 gerald.chen Exp $
 */
public class SyncTaskExecutorTest {

    @Test
    public void test() throws InterruptedException {
        SyncTaskExecutor executor = new SyncTaskExecutor();
        executor.execute(new OutThread());
        System.out.println("Hello, World!");
        Thread.sleep(10000 * 1000L);
    }
    
    static class OutThread implements Runnable {

        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println(i + " start ...");
                try {
                    Thread.sleep(2 * 1000L);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        
    }

}

必须在线程任务执行完毕之后,"Hello,World!"才会被打印出来

2.异步任务

 /*
 * @(#)AsyncTaskExecutorTest.java    2011-4-27
 *
 * Copyright (c) 2011. All Rights Reserved.
 *
 */

package org.jsoft.opensource.demos.spring.task;

import org.junit.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;

/**
 * Spring异步任务处理
 *
 * @author <a href="mailto:hongyuan.czq@taobao.com">Gerald Chen</a>
 * @version $Id: AsyncTaskExecutorTest.java,v 1.1 2011/05/30 08:58:07 gerald.chen Exp $
 */
public class AsyncTaskExecutorTest {

    @Test
    public void test() throws InterruptedException {
        AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("sys.out");
        executor.execute(new OutThread(), 50000L);
        System.out.println("Hello, World!");
        Thread.sleep(10000 * 1000L);
    }
    
    static class OutThread implements Runnable {

        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(i + " start ...");
                try {
                    Thread.sleep(2 * 1000L);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        
    }
}

"Hello,World!"被正常打印出来,线程任务在后台静静地执行.

 

关键词:JAVA   Spring   任务   同步   异步    软件工程师   程序员   编程

 

posted @ 2011-06-08 20:53 jadmin 阅读(761) | 评论 (0)编辑 收藏

一、DB Shell数据库操作

数据库

1、Help查看命令提示
help
db.help();
db.yourColl.help();
db.youColl.find().help();
rs.help();

2、切换/创建数据库
>use yourDB;
当创建一个集合(table)的时候会自动创建当前数据库

3、查询所有数据库
show dbs;

4、删除当前使用数据库
db.dropDatabase();

5、从指定主机上克隆数据库
db.cloneDatabase(“127.0.0.1”);
将指定机器上的数据库的数据克隆到当前数据库

6、从指定的机器上复制指定数据库数据到某个数据库
db.copyDatabase("mydb", "temp", "127.0.0.1");
将本机的mydb的数据复制到temp数据库中

7、修复当前数据库
db.repairDatabase();

8、查看当前使用的数据库
db.getName();
db;
db和getName方法是一样的效果,都可以查询当前使用的数据库

9、显示当前db状态
db.stats();

10、当前db版本
db.version();

11、查看当前db的链接机器地址

db.getMongo();

 

Collection聚集集合

1、创建一个聚集集合(table)
db.createCollection(“collName”, {size: 20, capped: 5, max: 100});

2、得到指定名称的聚集集合(table)
db.getCollection("account");

3、得到当前db的所有聚集集合
db.getCollectionNames();

4、显示当前db所有聚集索引的状态
db.printCollectionStats();

 

用户相关

1、添加一个用户
db.addUser("name");
db.addUser("userName", "pwd123", true);
添加用户、设置密码、是否只读

2、数据库认证、安全模式
db.auth("userName", "123123");

3、显示当前所有用户
show users;

4、删除用户
db.removeUser("userName");

 

其他

1、查询之前的错误信息
db.getPrevError();

2、清除错误记录
db.resetError();

 

二、Collection聚集集合操作

查看聚集集合基本信息

1、查看帮助
db.yourColl.help();


2、查询当前集合的数据条数
db.yourColl.count();

3、查看数据空间大小
db.userInfo.dataSize();

4、得到当前聚集集合所在的db
db.userInfo.getDB();

5、得到当前聚集的状态
db.userInfo.stats();

6、得到聚集集合总大小
db.userInfo.totalSize();

7、聚集集合储存空间大小
db.userInfo.storageSize();

8、Shard版本信息
db.userInfo.getShardVersion()

9、聚集集合重命名
db.userInfo.renameCollection("users");
将userInfo重命名为users

10、删除当前聚集集合
db.userInfo.drop();

 

聚集集合查询
1、查询所有记录
db.userInfo.find();
相当于:select * from userInfo;
默认每页显示20条记录,当显示不下的情况下,可以用it迭代命令查询下一页数据。注意:键入it命令不能带“;”
但是你可以设置每页显示数据的大小,用DBQuery.shellBatchSize = 50;这样每页就显示50条记录了。

2、查询去掉后的当前聚集集合中的某列的重复数据
db.userInfo.distinct("name");
会过滤掉name中的相同数据
相当于:select distict name from userInfo;

3、查询age = 22的记录
db.userInfo.find({"age": 22});
相当于: select * from userInfo where age = 22;

4、查询age > 22的记录
db.userInfo.find({age: {$gt: 22}});
相当于:select * from userInfo where age > 22;

5、查询age < 22的记录
db.userInfo.find({age: {$lt: 22}});
相当于:select * from userInfo where age < 22;

6、查询age >= 25的记录
db.userInfo.find({age: {$gte: 25}});
相当于:select * from userInfo where age >= 25;

7、查询age <= 25的记录
db.userInfo.find({age: {$lte: 25}});

8、查询age >= 23 并且 age <= 26
db.userInfo.find({age: {$gte: 23, $lte: 26}});

9、查询name中包含 mongo的数据
db.userInfo.find({name: /mongo/});
//相当于%%
select * from userInfo where name like ‘%mongo%’;

10、查询name中以mongo开头的
db.userInfo.find({name: /^mongo/});
select * from userInfo where name like ‘mongo%’;

11、查询指定列name、age数据
db.userInfo.find({}, {name: 1, age: 1});
相当于:select name, age from userInfo;
当然name也可以用true或false,当用ture的情况下河name:1效果一样,如果用false就是排除name,显示name以外的列信息。

12、查询指定列name、age数据, age > 25
db.userInfo.find({age: {$gt: 25}}, {name: 1, age: 1});
相当于:select name, age from userInfo where age > 25;

13、按照年龄排序
升序:db.userInfo.find().sort({age: 1});
降序:db.userInfo.find().sort({age: -1});

14、查询name = zhangsan, age = 22的数据
db.userInfo.find({name: 'zhangsan', age: 22});
相当于:select * from userInfo where name = ‘zhangsan’ and age = ‘22’;

15、查询前5条数据
db.userInfo.find().limit(5);
相当于:select top 5 * from userInfo;

16、查询10条以后的数据
db.userInfo.find().skip(10);
相当于:select * from userInfo where id not in (
select top 10 * from userInfo
);

17、查询在5-10之间的数据
db.userInfo.find().limit(10).skip(5);
可用于分页,limit是pageSize,skip是第几页*pageSize

18、or与 查询
db.userInfo.find({$or: [{age: 22}, {age: 25}]});
相当于:select * from userInfo where age = 22 or age = 25;

19、查询第一条数据
db.userInfo.findOne();
相当于:select top 1 * from userInfo;
db.userInfo.find().limit(1);

20、查询某个结果集的记录条数
db.userInfo.find({age: {$gte: 25}}).count();
相当于:select count(*) from userInfo where age >= 20;

21、按照某列进行排序
db.userInfo.find({sex: {$exists: true}}).count();
相当于:select count(sex) from userInfo;

索引

1、创建索引
db.userInfo.ensureIndex({name: 1});
db.userInfo.ensureIndex({name: 1, ts: -1});

2、查询当前聚集集合所有索引
db.userInfo.getIndexes();

3、查看总索引记录大小
db.userInfo.totalIndexSize();

4、读取当前集合的所有index信息
db.users.reIndex();

5、删除指定索引
db.users.dropIndex("name_1");

6、删除所有索引索引
db.users.dropIndexes();

修改、添加、删除集合数据
1、添加
db.users.save({name: ‘zhangsan’, age: 25, sex: true});
添加的数据的数据列,没有固定,根据添加的数据为准

2、修改
db.users.update({age: 25}, {$set: {name: 'changeName'}}, false, true);
相当于:update users set name = ‘changeName’ where age = 25;

db.users.update({name: 'Lisi'}, {$inc: {age: 50}}, false, true);
相当于:update users set age = age + 50 where name = ‘Lisi’;

db.users.update({name: 'Lisi'}, {$inc: {age: 50}, $set: {name: 'hoho'}}, false, true);
相当于:update users set age = age + 50, name = ‘hoho’ where name = ‘Lisi’;

3、删除
db.users.remove({age: 132});

4、查询修改删除
db.users.findAndModify({
    query: {age: {$gte: 25}},
    sort: {age: -1},
    update: {$set: {name: 'a2'}, $inc: {age: 2}},
    remove: true
});

db.runCommand({ findandmodify : "users",
    query: {age: {$gte: 25}},
    sort: {age: -1},
    update: {$set: {name: 'a2'}, $inc: {age: 2}},
    remove: true
});
update 或 remove 其中一个是必须的参数; 其他参数可选。

参数                                                                                              详解                                                                                        默认值
query                                                                                      查询过滤条件                                                                                       {}
sort         如果多个文档符合查询过滤条件,将以该参数指定的排列方式选择出排在首位的对象,该对象将被操作           {}
remove                                                             若为true,被选中对象将在返回前被删除                                                             N/A
update                                                                               一个 修改器对象                                                                                     N/A
new                                  若为true,将返回修改后的对象而不是原始对象。在删除操作中,该参数被忽略。                      false
fields                                                           参见Retrieving a Subset of Fields (1.5.0+)                                                            All fields
upsert                                                          创建新对象若查询结果为空。 示例 (1.5.4+)                                                             false

 

语句块操作
1、简单Hello World
print("Hello World!");
这种写法调用了print函数,和直接写入"Hello World!"的效果是一样的;

2、将一个对象转换成json
tojson(new Object());
tojson(new Object('a'));

3、循环添加数据
> for (var i = 0; i < 30; i++) {
    db.users.save({name: "u_" + i, age: 22 + i, sex: i % 2});
  };
这样就循环添加了30条数据,同样也可以省略括号的写法
> for (var i = 0; i < 30; i++) db.users.save({name: "u_" + i, age: 22 + i, sex: i % 2});
也是可以的,当你用db.users.find()查询的时候,显示多条数据而无法一页显示的情况下,可以用it查看下一页的信息;

4、find 游标查询
>var cursor = db.users.find();
> while (cursor.hasNext()) {
    printjson(cursor.next());
}
这样就查询所有的users信息,同样可以这样写
var cursor = db.users.find();
while (cursor.hasNext()) { printjson(cursor.next); }
同样可以省略{}号

5、forEach迭代循环
db.users.find().forEach(printjson);
forEach中必须传递一个函数来处理每条迭代的数据信息

6、将find游标当数组处理
var cursor = db.users.find();
cursor[4];
取得下标索引为4的那条数据
既然可以当做数组处理,那么就可以获得它的长度:cursor.length();或者cursor.count();
那样我们也可以用循环显示数据
for (var i = 0, len = c.length(); i < len; i++) printjson(c[i]);

7、将find游标转换成数组
> var arr = db.users.find().toArray();
> printjson(arr[2]);
用toArray方法将其转换为数组

8、定制我们自己的查询结果
只显示age <= 28的并且只显示age这列数据
db.users.find({age: {$lte: 28}}, {age: 1}).forEach(printjson);
db.users.find({age: {$lte: 28}}, {age: true}).forEach(printjson);
排除age的列
db.users.find({age: {$lte: 28}}, {age: false}).forEach(printjson);

9、forEach传递函数显示信息
db.things.find({x:4}).forEach(function(x) {print(tojson(x));});
上面介绍过forEach需要传递一个函数,函数会接受一个参数,就是当前循环的对象,然后在函数体重处理传入的参数信息。
 

posted @ 2011-06-08 20:13 jadmin 阅读(88) | 评论 (0)编辑 收藏

解决办法:

设置host,在文件 C:\Windows\System32\drivers\etc\hosts 中加入 66.249.89.99  code.google.com

 

posted @ 2011-06-08 15:45 jadmin 阅读(116) | 评论 (0)编辑 收藏

      SD Times高级编辑Alex Handy日前列出了当前使用Hadoop的项目中他认为最成功的五个。

posted @ 2011-06-06 23:10 jadmin 阅读(181) | 评论 (0)编辑 收藏

<script type="text/javascript">
function newGuid()
{
    var guid = "";
    for (var i = 1; i <= 32; i++) {
      var n = Math.floor(Math.random()*16.0).toString(16);
      guid +=   n;
      if((i==8)||(i==12)||(i==16)||(i==20))
        guid += "-";
    }
    return guid;   
}
</script>

 

posted @ 2011-06-01 21:06 jadmin 阅读(102) | 评论 (0)编辑 收藏

这两天在看《编程人生》,这本书确实非常不错。而且看得也特别的轻松。其中有几个人都谈到了如何学习新的语言,但是给我最深刻的是google的首席java架构师joshua bloch。正好最近我也在学习python,所以顺便总结一下如何学习一门新的语言。希望你能补充一些。

心态

这不但是学习一门新的语言最重要的,而是对任何的学习都是最重要的。下面是书中的描述,非常的精彩,特别是那个比喻:

“学习一门新的语言的时候,要利用以前所学的语言的功底,但是也要保持开放的心态。有些人执着于一种理念:“这就是写所有程序必须遵循的方法”。我不是说那种语言,但是某些语言,令人执着于这样的理念。当开始学习新语言的时候,他们会批评这种语言跟真正神的语言的所有的不同之处。当使用新语言时,他们极力使用神的语言的方法去写。这样,你就会错过这个新语言真正的独特之处。
这就像你本来只有一个榔头,有人给了你一个螺丝刀,你说“哎,这不是一把好榔头,但是我应该可以倒着拿螺丝刀,用螺丝刀来砸东西。”你得到了一个很烂的榔头,但事实上它确实一把很不错的螺丝刀。所以你应该对所有的事物保持开放和积极的心态。”

如果你的杯子满了,那他永远再也装不进水了。如果你认为你找到了银弹,那么你可能就要固步自封了。

对新的事物,方法保持一个开发而积极的心态,才能真正了解他,了解他的独特之处。

这一点相对来说比较难,程序员一般对他们的语言有一种近乎固执的偏爱。Paul Graham在《黑客与画家》中好像提到过,开发语言是程序员的宗教信仰,贬低一种语言对使用这种语言的程序员是一种侮辱。

了解他的历史,哲学观

选择一门语言,往往选择了一种思维方式和哲学观。所以,了解一门语言的历史和哲学观非常重要。你要知道这门语言是谁创建的,为什么创建,如何发展起来的,适合那些领域,以及解决问题的哲学是什么。

那python来说,他的设计哲学是“用一种方法,最好是只有一种方法来做一件事”,而perl的设计哲学是“总有多种方法来做同一件事”。所以,我选择的是python。

了解这方面的知识的一个非常好的来源是百科网站。

代码,代码,还是代码

代码是学习一门语言的必经之路,可能也是最快的一种方法。

你不但要找一些优秀的代码来阅读,还要亲自动手来写代码。这个过程对学习语言来说是非常快的。另外,你一定要用语言去解决实际的问题,而不仅仅是写代码来验证语法。在解决问题的过程中,你可以学习它是如何解决问题的,而且会积累语言的经验。

在工作中使用一门新的语言来开发新项目的风险相对较大,所以,如果再工作中尝试使用新的语言,可以选择一些小的项目来积累经验。如果工作中无法使用这个语言,那么就在业余使用这个语言解决问题吧。

社区

多去这个语言的社区逛逛吧,这里有很多人在讨论这种语言,和他们一起讨论你能够学到更多。


本文转自CSDN博客

posted @ 2011-06-01 12:48 jadmin 阅读(73) | 评论 (0)编辑 收藏

/**
* Java + MongoDB in Secure Mode
*
* @author <a href="mailto:gerald.chen@qq.com">Gerald Chen</a>
* @version $Id: AuthTest.java,v 1.1 2011/05/27 07:24:04 gerald.chen Exp $
*/

public class AuthTest {
 
/** 数据库连接IP */
public static final String DB_HOST ="192.168.35.101";
 
/** 数据库连接端口 */
public static final int DB_PORT =27017;
 
public static void main(String[] args) throws Exception, MongoException {
      Mongo mongo =new Mongo(DB_HOST, DB_PORT);
      DB db = mongo.getDB("test_db");
 
      boolean auth = db.authenticate("gerald", "123456".toCharArray());
      System.out.println(auth);
 
      DBCollection collection = db.getCollection("test_collection");
      System.out.println(collection.getFullName());
 
}
 
}

posted @ 2011-05-27 15:42 jadmin 阅读(96) | 评论 (0)编辑 收藏

1.进入mongodb命令行管理
C:\Documents and Settings\Administrator>mongo
MongoDB shell version: 1.8.1
connecting to: test

 

2.显示数据库
> show dbs
admin   (empty)
local   (empty)
test_db 0.03125GB

 

3.使用数据库
> use test_db
switched to db test_db

 

4.添加数据库用户
> db.users.save({username:"gerald"})

 

5.查找数据库用户
> db.users.find()
{ "_id" : ObjectId("4ddf396e641b4986d346fe89"), "username" : "gerald" }

 

6.添加隶属于某个数据库的用户
> use test_db
switched to db test_db
> db.addUser("gerald","123456")
{
        "user" : "gerald",
        "readOnly" : false,
        "pwd" : "f528f606b8635241c7f060408973b5b9"
}

 

7.以用户验证的身份登录数据库
> use test_db
switched to db test_db
> db.auth("gerald","123456")
1

PS: 1代表验证成功, 0代表验证失败

 

关键词:数据库  NoSQL  MongoDB  Database

 

posted @ 2011-05-27 15:38 jadmin 阅读(105) | 评论 (0)编辑 收藏

1.准备

     下载Mongo Java Driver,下载地址:https://github.com/downloads/mongodb/mongo-java-driver/mongo-2.5.3.jar

     如果是使用maven编译,可在pom.xml文件中加入如下依赖

     <dependency>
          <groupId>org.mongodb</groupId>
          <artifactId>mongo-java-driver</artifactId>
          <version>2.5.3</version>
     </dependency>

2.上程序

/**
 * MongoDB学习之HelloWorld
 *
 * @author <a href="mailto:gerald.chen@qq.com">GeraldChen</a>
 * @version $Id: HelloWorldTest.java,v 1.1 2011/05/26 12:42:45 gerald.chen Exp $
 */
public class HelloWorldTest {

      /** 数据库连接IP */
     public static final String DB_HOST = "192.168.35.101";

     /** 数据库连接端口 */
     public static final int DB_PORT = 27017;

     public static void main(String[] args) throws Exception {
         // connect to mongoDB, ip and port number
         Mongo mongo = new Mongo(DB_HOST, DB_PORT);

         // get database from MongoDB,
         // if database doesn't exists, mongoDB will create it automatically
         DB db = mongo.getDB("test_db");

         // Get collection from MongoDB, database named "yourDB"
         // if collection doesn't exists, mongoDB will create it automatically
         DBCollection collection = db.getCollection("test_collection");

         // create a document to store key and value
         BasicDBObject document = new BasicDBObject();
         document.put("id", 1001);
         document.put("message", "hello world mongoDB in Java");

         // save it into collection named "yourCollection"
         collection.insert(document);

         // search query
         BasicDBObject searchQuery = new BasicDBObject();
         searchQuery.put("id", 1001);

         // query it
         DBCursor cursor = collection.find(searchQuery);

         // loop over the cursor and display the retrieved result
         while (cursor.hasNext()) {
                  System.out.println(cursor.next());
         }
         System.out.println("Done");
   }

}

2.程序输出


关键词:HelloWorld   MongoDB   NoSQL   JAVA    程序   软件   数据库   程序员
 

posted @ 2011-05-26 20:47 jadmin 阅读(95) | 评论 (0)编辑 收藏

       继上一篇MongoDB学习——安装与配置 ,我们接着来看下如何将MongoDB安装为Windows的服务:

1.服务化

       在命令窗口运行如下命令即可:

       #>mongod --dbpath "G:\database\mongodb\data" --logpath "G:\database\mongodb\logs.txt" --install --serviceName "MongoDB"

       其中"G:\database\mongodb\data"为MongoDB的数据目录

2.卸载服务

       执行命令:

       #>mongod --remove --serviceName "MongoDB"

 

关键词:HelloWorld   MongoDB   NoSQL   JAVA    程序   软件   数据库   程序员

 

posted @ 2011-05-25 20:57 jadmin 阅读(112) | 评论 (0)编辑 收藏

1.MongoDB介绍

  MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
  它的特点是高性能、易部署、易使用,存储数据非常方便。主要功能特性有:
  *面向集合存储,易存储对象类型的数据。
  *模式自由。
  *支持动态查询。
  *支持完全索引,包含内部对象。
  *支持查询。
  *支持复制和故障恢复。
  *使用高效的二进制数据存储,包括大型对象(如视频等)。
  *自动处理碎片,以支持云计算层次的扩展性
  *支持RUBY,PYTHON,JAVA,C++,PHP等多种语言。
  *文件存储格式为BSON(一种JSON的扩展)
  *可通过网络访问

  所谓“面向集合”(Collenction-Oriented),意思是数据被分组存储在数据集中,被称为一个集合(Collenction)。每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定义任何模式(schema)。
  模式自由(schema-free),意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义。如果需要的话,你完全可以把不同结构的文件存储在同一个数据库里。
  存储在集合中的文档,被存储为键-值对的形式。键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized dOcument Format)。

2.下载MongoDB

  下载地址http://www.mongodb.org/downloads 至本文成文之时,版本号为:1.8.1

   http://downloads.mongodb.org/win32/mongodb-win32-i386-1.8.1.zip

3.安装

  将zip文件解压至某个磁盘目录,如:G:\database\mongodb

4.配置

  建立数据存储目录G:\database\mongodb\data

  启动命令窗口,进到目录G:\>cd database\mongodb\bin

  执行命令mongod --dbpath G:\database\mongodb\data,出现如下信息:

G:\database\mongodb\bin>mongod --dbpath G:\database\mongodb\data
Wed May 25 20:03:06 [initandlisten] MongoDB starting : pid=3244 port=27017 dbpath=G:\database\mongodb\data 32-bit

** NOTE: when using MongoDB 32 bit, you are limited to about 2 gigabytes of data

**       seehttp://blog.mongodb.org/post/137788967/32-bit-limitations
**       with --dur, the limit is lower

Wed May 25 20:03:06 [initandlisten] db version v1.8.1, pdfile version 4.5
Wed May 25 20:03:06 [initandlisten] git version: a429cd4f535b2499cc4130b06ff7c26f41c00f04
Wed May 25 20:03:06 [initandlisten] build sys info: windows (5, 1, 2600, 2, 'Service Pack 3') BOOST_LIB_VERSION=1_35
Wed May 25 20:03:06 [initandlisten] waiting for connections on port 27017
Wed May 25 20:03:06 [websvr] web admin interface listening on port 28017

5.查看管理后台

  打开浏览器,登入地址:http://localhost:28017
  出现如下内容:


关键词:HelloWorld   MongoDB   NoSQL   JAVA    程序   软件   数据库   程序员

 

posted @ 2011-05-25 20:20 jadmin 阅读(110) | 评论 (0)编辑 收藏

       由于MySQL目前字段的默认值不支持函数的形式设置默认值是不可能的。
       代替的方案是使用TIMESTAMP类型代替DATETIME类型。
       CURRENT_TIMESTAMP :当我更新这条记录的时候,这条记录的这个字段不会改变。
       CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP :当我更新这条记录的时候,这条记录的这个字段将会改变。即时间变为了更新时候的时间。(注意一个UPDATE设置一个列为它已经有的值,这将不引起TIMESTAMP列被更新,因为如果你设置一个列为它当前的值,MySQL为了效率而忽略更改。)如果有多个TIMESTAMP列,只有第一个自动更新。 
      TIMESTAMP列类型自动地用当前的日期和时间标记INSERT或UPDATE的操作。 
      如果有多个TIMESTAMP列,只有第一个自动更新。 
      自动更新第一个TIMESTAMP列在下列任何条件下发生: 
      列值没有明确地在一个INSERT或LOAD DATA INFILE语句中指定。 
      列值没有明确地在一个UPDATE语句中指定且另外一些的列改变值。(注意一个UPDATE设置一个列为它已经有的值,这将不引起TIMESTAMP列被更新,因为如果你设置一个列为它当前的值,MySQL为了效率而忽略更改。) 
      你明确地设定TIMESTAMP列为NULL. 
      除第一个以外的TIMESTAMP列也可以设置到当前的日期和时间,只要将列设为NULL,或NOW()。 
      另外在5.0以上版本中也可以使用trigger来实现此功能。

      create table test_time ( 
      id int(11), 
      create_time datetime 
      ); 
      delimiter | 
            create trigger default_datetime before insert on test_time 
      for each row 
            if new.create_time is null then 
            set new.create_time = now(); 
      end if;| 
      delimiter ;

posted @ 2011-05-23 11:57 jadmin 阅读(90) | 评论 (0)编辑 收藏

单库单表

         单库单表是最常见的数据库设计,例如,有一张用户(user)表放在数据库db中,所有的用户都可以在db库中的user表中查到。

 

单库多表

随着用户数量的增加,user表的数据量会越来越大,当数据量达到一定程度的时候对user表的查询会渐渐的变慢,从而影响整个DB的性能。如果使用mysql, 还有一个更严重的问题是,当需要添加一列的时候,mysql会锁表,期间所有的读写操作只能等待。

可以通过某种方式将user进行水平的切分,产生两个表结构完全一样的user_0000,user_0001等表,user_0000 + user_0001 + …的数据刚好是一份完整的数据。

 

多库多表

         随着数据量增加也许单台DB的存储空间不够,随着查询量的增加单台数据库服务器已经没办法支撑。这个时候可以再对数据库进行水平区分。

 

分库分表规则

         设计表的时候需要确定此表按照什么样的规则进行分库分表。例如,当有新用户时,程序得确定将此用户信息添加到哪个表中;同理,当登录的时候我们得通过用户的账号找到数据库中对应的记录,所有的这些都需要按照某一规则进行。

路由

         通过分库分表规则查找到对应的表和库的过程。如分库分表的规则是user_id mod 4的方式,当用户新注册了一个账号,账号id123,我们可以通过id mod 4的方式确定此账号应该保存到User_0003表中。当用户123登录的时候,我们通过123 mod 4后确定记录在User_0003中。

分库分表产生的问题,及注意事项

1.   分库分表维度的问题

假如用户购买了商品,需要将交易记录保存取来,如果按照用户的纬度分表,则每个用户的交易记录都保存在同一表中,所以很快很方便的查找到某用户的购买情况,但是某商品被购买的情况则很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,可以很方便的查找到此商品的购买情况,但要查找到买人的交易记录比较麻烦。

 

所以常见的解决方式有:

     a.通过扫表的方式解决,此方法基本不可能,效率太低了。

     b.记录两份数据,一份按照用户纬度分表,一份按照商品维度分表。

     c.通过搜索引擎解决,但如果实时性要求很高,又得关系到实时搜索。

 

2.   联合查询的问题

联合查询基本不可能,因为关联的表有可能不在同一数据库中。

 

3.   避免跨库事务

避免在一个事务中修改db0中的表的时候同时修改db1中的表,一个是操作起来更复杂,效率也会有一定影响。

 

4.   尽量把同一组数据放到同一DB服务器上

例如将卖家a的商品和交易信息都放到db0中,当db1挂了的时候,卖家a相关的东西可以正常使用。也就是说避免数据库中的数据依赖另一数据库中的数据。

 

 

一主多备

         在实际的应用中,绝大部分情况都是读远大于写。Mysql提供了读写分离的机制,所有的写操作都必须对应到Master,读操作可以在MasterSlave机器上进行,SlaveMaster的结构完全一样,一个Master可以有多个Slave,甚至Slave下还可以挂Slave,通过此方式可以有效的提高DB集群的QPS.                                                       

  所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

  此外,可以看出Master是集群的瓶颈,当写操作过多,会严重影响到Master的稳定性,如果Master挂掉,整个集群都将不能正常工作。

  所以,1. 当读压力很大的时候,可以考虑添加Slave机器的分式解决,但是当Slave机器达到一定的数量就得考虑分库了。 2. 当写压力很大的时候,就必须得进行分库操作。

 

         另外,可能会因为种种原因,集群中的数据库硬件配置等会不一样,某些性能高,某些性能低,这个时候可以通过程序控制每台机器读写的比重,达到负载均衡。

posted @ 2011-05-19 16:52 jadmin 阅读(122) | 评论 (0)编辑 收藏

1,保证线程安全的三种方法:
    a,不要跨线程访问共享变量

    b,使共享变量是
final类型的
    c,将共享变量的操作加上同步


2,一开始就将类设计成线程安全的
,比在后期重新修复它,更容易.

3,编写多线程程序
,首先保证它是正确的,其次再考虑性能
.

4,无状态或只读对象永远是线程安全的
.

5,不要将一个共享变量裸露在多线程环境下
(无同步或不可变性保护
)

6,多线程环境下的延迟加载需要同步的保护
,因为延迟加载会造成对象重复实例化


7,对于
volatile 声明的数值类型变量进行运算,往往是不安全的(volatile 只能保证可见性, 不能保证原子性).
详见
volatile 原理与技巧中,脏数据问题讨论
.

8,当一个线程请求获得它自己占有的锁时
( 同一把锁的嵌套使用),我们称该锁为可重入锁.
jdk1.5 并发包中,提供了可重入锁的java 实现
-ReentrantLock.

9,每个共享变量
, 都应该由一个唯一确定的锁保护.
创建与变量相同数目的
ReentrantLock,使他们负责每个变量的线程安全
.

10,虽然缩小同步块的范围
,可以提升系统性能.
但在保证原子性的情况下
,不可将原子操作分解成多个synchronized
.

11,在没有同步的情况下
,编译器与处理器运行时的指令执行顺序可能完全出乎意料
.
原因是
,编译器或处理器为了优化自身执行效率,而对指令进行了的重排序
(reordering).

12,当一个线程在没有同步的情况下读取变量
,它可能会得到一个过期值,但是至少它可以看到那个

线程在当时设定的一个真实数值
.而不是凭空而来的值.这种安全保证,称之为最低限的安全性(out-of-thin-air safety)

在开发并发应用程序时
,有时为了大幅度提高系统的吞吐量与性能,会采用这种无保障的做法
.
但是针对
,数值的运算,仍旧是被否决的
.

13,volatile 变量
, 只能保证可见性,无法保证原子性
.

14,某些耗时较长的网络操作或IO,确保执行时,不要占有锁.

15,发布(publish) 对象,指的是使它能够被当前范围之外的代码所使用.( 引用传递
)
对象逸出(escape),指的是一个对象在尚未准备好时将它发布.

原则:为防止逸出,对象必须要被完全构造完后,才可以被发布( 最好的解决方式是采用同步
)

this
关键字引用对象逸出

例子:在构造函数中,开启线程,并将自身对象this 传入线程,造成引用传递.
而此时,构造函数尚未执行完,就会发生对象逸出了
.

16,必要时,使用ThreadLocal变量确保线程封闭性(封闭线程往往是比较安全的,但一定程度上会造成性能损耗)
封闭对象的例子在实际使用过程中,比较常见,例如hibernate openSessionInView机制, jdbcconnection机制.

17,单一不可变对象往往是线程安全的(复杂不可变对象需要保证其内部成员变量也是不可变的)
良好的多线程编程习惯是:将所有的域都声明为final,除非它们是可变的

18,保证共享变量的发布是安全的

    a,
通过静态初始化器初始化对象(jls 12.4.2 叙述, jvm 会保证静态初始化变量是同步的)
    b,
将对象申明为volatile 或使用
AtomicReference
    c,
保证对象是不可变的

    d,
将引用或可变操作都由锁来保护

19,设计线程安全的类,应该包括的基本要素:
    a,确定哪些是可变共享变量
    b,确定哪些是不可变的变量
    c,指定一个管理并发访问对象状态的策略

20,将数据封装在对象内部,并保证对数据的访问是原子的
.
建议采用volatile javabean 模型或者构造同步的getter,setter.

21,线程限制性使构造线程安全的类变得更容易,因为类的状态被限制后,分析它的线程安全性时,就不必检查完整的程序.

22,
编写并发程序,需要更全的注释,更完整的文档说明.

23,在需要细分锁的分配时,使用java监视器模式好于使用自身对象的监视器锁.
前者的灵活性更好.

Object target = new Object();
//这里使用外部对象来作为监视器,而非this
synchronized(target) {
    // TODO
}

针对java monitor pattern,实际上ReentrantLock的实现更易于并发编程.
功能上,也更强大.

24,设计并发程序时,在保证伸缩性与性能折中的前提下,优先考虑将共享变量委托给线程安全的类
.
由它来控制全局的并发访问
.

25,使用普通同步容器(Vector, Hashtable) 的迭代器,需要外部锁来保证其原子性
.
原因是,普通同步容器产生的迭代器是非线程安全的
.

26,在并发编程中,需要容器支持的时候,优先考虑使用jdk 并发容器

(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).


27, ConcurrentHashMap, CopyOnWriteArrayList

并发容器的迭代器, 以及全范围的size(), isEmpty()都表现出弱一致性
.
他们只能标示容器当时的一个数据状态.无法完整响应容器之后的变化和修改
.

28,
使用有界队列,在队列充满或为空时,阻塞所有的读与写操作. ( 实现生产- 消费的良好方案)

BlockQueue
下的实现有LinkedBlockingQueue ArrayBlockingQueue,前者为链表,可变操作频繁优先考虑, 后者为数组,读取操作频繁优先考虑.
PriorityBlockingQueue
是一个按优先级顺序排列的阻塞队列,它可以对所有置入的元素进行排序( 实现Comparator 接口
)

29,
当一个方法,能抛出InterruptedException,则意味着,这个方法是一个可阻塞的方法,如果它被中断,将提前结束阻塞状态
.
当你调用一个阻塞方法,也就意味着,本身也称为了一个阻塞方法,因为你必须等待阻塞方法返回
.

如果阻塞方法抛出了中断异常,我们需要做的是,将其往上层抛,除非当前已经是需要捕获异常的层次
.
如果当前方法,不能抛出InterruptedException,可以使用Thread.currentThread.interrupt() 方法,手动进行中断.

 

posted @ 2011-05-18 22:53 jadmin 阅读(159) | 评论 (0)编辑 收藏

1. java是如何管理内存的

 Java的内存管理就是对象的分配和释放问题。(两部分)

分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。
释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。

2. 什么叫java的内存泄露

     在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连(也就是说仍存在该内存对象的引用);其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

3. JVM的内存区域组成

java把内存分两种:一种是栈内存,另一种是堆内存1。在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;2。堆内存用来存放由new创建的对象和数组以及对象的实例变量 在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理
堆和栈的优缺点   

 堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。

缺点就是要在运行时动态分配内存,存取速度较慢; 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。

另外,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

4. Java中数据在内存中是如何存储的

a) 基本数据类型

   Java的基本数据类型共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的。如int a = 3;这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。比如:我们同时定义:
int a=3;
int b =3;
    编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。    定义完a与b的值后,再令a = 4;那么,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

b)    对象

在Java中,创建一个对象包括对象的声明和实例化两步,下面用一个例题来说明对象的内存模型。  假设有类Rectangle定义如下:
public class Rectangle {
double width;
double height;
public Rectangle(double w,double h){
w = width;
h = height;
}
}
(1)声明对象时的内存模型
 用Rectangle rect;声明一个对象rect时,将在栈内存为对象的引用变量rect分配内存空间,但Rectangle的值为空,称rect是一个空对象。空对象不能使用,因为它还没有引用任何"实体"。
(2)对象实例化时的内存模型
 当执行rect=new Rectangle(3,5);时,会做两件事: 在堆内存中为类的成员变量width,height分配内存,并将其初始化为各数据类型的默认值;接着进行显式初始化(类定义时的初始化值);最后调用构造方法,为成员变量赋值。  返回堆内存中对象的引用(相当于首地址)给引用变量rect,以后就可以通过rect来引用堆内存中的对象了。

c)    创建多个不同的对象实例

        一个类通过使用new运算符可以创建多个不同的对象实例,这些对象实例将在堆中被分配不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。例如:
Rectangle r1= new Rectangle(3,5);
Rectangle r2= new Rectangle(4,6);
 此时,将在堆内存中分别为两个对象的成员变量width、height分配内存空间,两个对象在堆内存中占据的空间是互不相同的。如果有:
Rectangle r1= new Rectangle(3,5);
Rectangle r2=r1;
则在堆内存中只创建了一个对象实例,在栈内存中创建了两个对象引用,两个对象引用同时指向一个对象实例。
 

d)    包装类

         基本型别都有对应的包装类:如int对应Integer类,double对应Double类等,基本类型的定义都是直接在栈中,如果用包装类来创建对象,就和普通对象一样了。例如:int i=0;i直接存储在栈中。  Integer i(i此时是对象) = new Integer(5);这样,i对象数据存储在堆中,i的引用存储在栈中,通过栈中的引用来操作对象。
 

e)    String

  String是一个特殊的包装类数据。可以用用以下两种方式创建:String str = new String("abc");String str = "abc";
第一种创建方式,和普通对象的的创建过程一样;
第二种创建方式,Java内部将此语句转化为以下几个步骤:
(1) 先定义一个名为str的对String类的对象引用变量:String str;
(2) 在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"
地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈
这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并
回o的地址。
(3) 将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种
合下,其字符串值却是保存了一个指向存在栈中数据的引用。
为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
String str1="abc";
String str2="abc";
System.out.println(s1==s2);//true
注意,这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
我们再接着看以下的代码。
String str1= new String("abc");
String str2="abc";
System.out.println(str1==str2);//false
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。     以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

f)    数组

         当定义一个数组,int x[];或int []x;时,在栈内存中创建一个数组引用,通过该引用(即数组名)来引用数组。x=new int[3];将在堆内存中分配3个保存int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0。
 

g)    静态变量

         用static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的"固定位置"-static storage,可以理解为所有实例对象共有的内存空间。static变量有点类似于C中的全局变量的概念;静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址。把static拿来,就是告诉JVM它是静态的,它的引用(含间接引用)都是指向同一个位置,在那个地方,你把它改了,它就不会变成原样,你把它清理了,它就不会回来了。         那静态变量与方法是在什么时候初始化的呢?对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。         我们常可看到类似以下的例子来说明这个问题:
class Student{
static int numberOfStudents=0;
Student()
{
numberOfStudents++;
}
}
每一次创建一个新的Student实例时,成员numberOfStudents都会不断的递增,并且所有的Student实例都访问同一个numberOfStudents变量,实际上int numberOfStudents变量在内存中只存储在一个位置上。

5. Java的内存管理实例

  Java程序的多个部分(方法,变量,对象)驻留在内存中以下两个位置:即堆和栈,现在我们只关心3类事物:实例变量,局部变量和对象:
实例变量和对象驻留在堆上
局部变量驻留在栈上
       让我们查看一个java程序,看看他的各部分如何创建并且映射到栈和堆中:
public class Dog {
Collar c;
String name;
//1. main()方法位于栈上
public static void main(String[] args) {
//2. 在栈上创建引用变量d,但Dog对象尚未存在
Dog d;
//3. 创建新的Dog对象,并将其赋予d引用变量
d = new Dog();
//4. 将引用变量的一个副本传递给go()方法
d.go(d);
}
//5. 将go()方法置于栈上,并将dog参数作为局部变量
void go(Dog dog){
//6. 在堆上创建新的Collar对象,并将其赋予Dog的实例变量
c =new Collar();
}
//7.将setName()添加到栈上,并将dogName参数作为其局部变量
void setName(String dogName){
//8. name的实例对象也引用String对象
name=dogName;
}
//9. 程序执行完成后,setName()将会完成并从栈中清除,此时,局部变量dogName也会消失,尽管它所引用的String仍在堆上
}

6. 垃圾回收机制:

(问题一:什么叫垃圾回收机制?) 垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用,以免造成内存泄露。 (问题二:java的垃圾回收有什么特点?) JAVA语言不允许程序员直接控制内存空间的使用。内存空间的分配和回收都是由JRE负责在后台自动进行的,尤其是无用内存空间的回收操作(garbagecollection,也称垃圾回收),只能由运行环境提供的一个超级线程进行监测和控制。 (问题三:垃圾回收器什么时候会运行?) 一般是在CPU空闲或空间不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时机和顺序等。 (问题四:什么样的对象符合垃圾回收条件?) 当没有任何获得线程能访问一个对象时,该对象就符合垃圾回收条件。 (问题五:垃圾回收器是怎样工作的?) 垃圾回收器如发现一个对象不能被任何活线程访问时,他将认为该对象符合删除条件,就将其加入回收队列,但不是立即销毁对象,何时销毁并释放内存是无法预知的。垃圾回收不能强制执行,然而Java提供了一些方法(如:System.gc()方法),允许你请求JVM执行垃圾回收,而不是要求,虚拟机会尽其所能满足请求,但是不能保证JVM从内存中删除所有不用的对象。 (问题六:一个java程序能够耗尽内存吗?) 可以。垃圾收集系统尝试在对象不被使用时把他们从内存中删除。然而,如果保持太多活的对象,系统则可能会耗尽内存。垃圾回收器不能保证有足够的内存,只能保证可用内存尽可能的得到高效的管理。 (问题七:如何显示的使对象符合垃圾回收条件?) (1) 空引用 :当对象没有对他可到达引用时,他就符合垃圾回收的条件。也就是说如果没有对他的引用,删除对象的引用就可以达到目的,因此我们可以把引用变量设置为null,来符合垃圾回收的条件。
StringBuffer sb = new StringBuffer("hello");
System.out.println(sb);
sb=null;
(2) 重新为引用变量赋值:可以通过设置引用变量引用另一个对象来解除该引用变量与一个对象间的引用关系。
StringBuffer sb1 = new StringBuffer("hello");
StringBuffer sb2 = new StringBuffer("goodbye");
System.out.println(sb1);
sb1=sb2;//此时"hello"符合回收条件 
(3) 方法内创建的对象:所创建的局部变量仅在该方法的作用期间内存在。一旦该方法返回,在这个方法内创建的对象就符合垃圾收集条件。有一种明显的例外情况,就是方法的返回对象。
public static void main(String[] args) {
Date d = getDate();
System.out.println("d = " + d);
}
private static Date getDate() {
Date d2 = new Date();
StringBuffer now = new StringBuffer(d2.toString());
System.out.println(now);
return d2;
}
(4) 隔离引用:这种情况中,被回收的对象仍具有引用,这种情况称作隔离岛。若存在这两个实例,他们互相引用,并且这两个对象的所有其他引用都删除,其他任何线程无法访问这两个对象中的任意一个。也可以符合垃圾回收条件。
public class Island {
Island i;
public static void main(String[] args) {
Island i2 = new Island();
Island i3 = new Island();
Island i4 = new Island();
i2.i=i3;
i3.i=i4;
i4.i=i2;
i2=null;
i3=null;
i4=null;
}
}
(问题八:垃圾收集前进行清理------finalize()方法) java提供了一种机制,使你能够在对象刚要被垃圾回收之前运行一些代码。这段代码位于名为finalize()的方法内,所有类从Object类继承这个方法。由于不能保证垃圾回收器会删除某个对象。因此放在finalize()中的代码无法保证运行。因此建议不要重写finalize();
7.    final问题:
      final使得被修饰的变量"不变",但是由于对象型变量的本质是"引用",使得"不变"也有了两种含义:引用本身的不变?,和引用指向的对象不变。?         引用本身的不变:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//编译期错误
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//编译期错误
引用指向的对象不变:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //编译通过
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //编译通过
可见,final只对引用的"值"(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的"值"相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。在举一个例子:
public class Name {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
 
public class Name {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
 
         编写测试方法:
public static void main(String[] args) {
final Name name = new Name();
name.setFirstname("JIM");
name.setLastname("Green");
System.out.println(name.getFirstname()+" "+name.getLastname());
}
public static void main(String[] args) {
final Name name = new Name();
name.setFirstname("JIM");
name.setLastname("Green");
System.out.println(name.getFirstname()+" "+name.getLastname());
}
 
         理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它"永远不变"。其实那是徒劳的。         Final还有一个值得注意的地方:         先看以下示例程序:
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
         对于类变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。但是对于用final修饰的类变量,虚拟机不会为其赋予初值,必须在constructor (构造器)结束之前被赋予一个明确的值。可以修改为"final int i = 0;"。
 
8.    如何把程序写得更健壮:
     1、尽早释放无用对象的引用。 好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率;
     2、定义字符串应该尽量使用 String str="hello"; 的形式 ,避免使用String str = new String("hello"); 的形式。因为要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Demo {
private String s;
public Demo() {
s = "Initial Value";
}
}
 
public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}
 而非
s = new String("Initial Value"); 
s = new String("Initial Value");
     后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。   

3、我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer ,因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象,请看下列代码;
String s = "Hello";  
s = s + " world!"; 
String s = "Hello";
s = s + " world!";
       在这段代码中,s原先指向一个String对象,内容是 "Hello",然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。         通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为 String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
     4、尽量少用静态变量 ,因为静态变量是全局的,GC不会回收的;
     5、尽量避免在类的构造函数里创建、初始化大量的对象 ,防止在调用其自身类的构造器时造成不必要的内存资源浪费,尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。         以下是初始化不同类型的对象需要消耗的时间:

运算操作  
 示例   
 标准化时间
 
本地赋值   
 i = n
 1.0
 
实例赋值   
 this.i = n
 1.2
 
方法调用   
 Funct()
 5.9
 
新建对象   
 New Object()
 980
 
新建数组   
 New int[10]
 3100
 

       
从表1可以看出,新建一个对象需要980个单位的时间,是本地赋值时间的980倍,是方法调用时间的166倍,而新建一个数组所花费的时间就更多了。
     6、尽量在合适的场景下使用对象池技术 以提高系统性能,缩减缩减开销,但是要注意对象池的尺寸不宜过大,及时清除无效对象释放内存资源,综合考虑应用运行环境的内存资源限制,避免过高估计运行环境所提供内存资源的数量。
     7、大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理 ,然后解决一块释放一块的策略。
     8、不要在经常调用的方法中创建对象 ,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
     9、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。
     10、尽量少用finalize函数 ,因为finalize()会加大GC的工作量,而GC相当于耗费系统的计算能力。
    11、不要过滥使用哈希表 ,有一定开发经验的开发人员经常会使用hash表(hash表在JDK中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用HashMap缓存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。其实我们可以使用操作系统中的缓存的概念来解决这个问题,也就是给被缓存的分配一个一定大小的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因为进行了对象缓存而提高了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用。现在有很多开源的缓存实现项目,比如ehcache、oscache等,这些项目都实现了FIFO、MRU等常见的缓存算法

 

关键词:JAVA   内存   转帖

posted @ 2011-05-18 20:58 jadmin 阅读(104) | 评论 (0)编辑 收藏

        从开始编程到现在已经有10年的时间了,10年之间我做过很多的工作,当然都称不上卓越,我虚度光阴,过一天就算一天。在深圳呆了6个年头后,我才发现事情的严重性,作为一名软件开发工程师,我一事无成,我还没有一个身经百战的团队或team,没有一个能拿的出手,又有知名度的软件产品,经常与印度程序员交流,却连国门都没有迈出去过。

        于是在今天这个飘雨的周末的黄昏,内力莫名的涌出一阵阵地懊悔,世界不会因为我没有完成代码而停滞不前,地球依然在转动,我讨厌这种感觉,这让我觉得我的存在毫无意义。中国的人太多,程序员太多,如果你想变的卓越出众,那么你就必须相信你所做的事情正在推动整个世界的发展,同时你也必须付出比别人更多的努力。所以今天我在我的愚人笔记 博客里,新建了一个专栏,程序人生 ,开始回顾和记录我程序的生涯的点点滴滴,拿出来跟各位朋友分享,也希望各位程序员朋友们,不要再我摔过的地方倒下;不要再我迷茫的地方浪费时光;更不要在我们所热爱的这个行业里,也和我一样变的一事无成。我觉得每个人都有自己的卓越之处,只是在懵懂中没有找到打开心中那扇石门的钥匙,希望我的程序人生,能抛砖引玉帮大家找到那把钥匙。

 

        下海做程序员的第一步也是最重要的一部,如何订制自己的程序之路。
        很多人在一谈到自己的计划的时候,都会去看看别人是怎么做的,一味的跟随别人的规划,多少岁之前做coder,多少岁之前做manager.其实每个人都有自己的特点,你应该停下来好好的审视自己的职业,不要跟着别人的路去走了,你应该知道自己要何去何从.

        我们的职业之路要怎么制定呢?毕竟程序员是一门职业,作为软件的开发人员,我们就是一个从事某一个职业的工人,公司雇佣我们,绝对不是因为公司爱我们。虽然天天公司教导我们“公司我是家”但是深圳的住房公积金,公司的交的那部分,还是转嫁给我们自己承担。事实上,公司以前从没有爱过我吗,将来也绝对不会。公司是你自己的那就另当别论了,否则,程序员就不是一个职业了,职业不就每天要我们去一个地方,呆上8个或者更多的小时,牺牲大量的脑细胞或者汗水,然后领取报酬吗?职业就是生意,把我们做的生意说的惨淡点,就是出卖自己i的劳动力,换钱,再高级的白领也是如此。当然做生意有赚有亏得,想要在这个行业里面成为佼佼者,那就是必须要知道自己应该如何去做这门生意,如何为自己创造利润?

        如果把你的职业人生想象成为一个你正在开发的软件产品的生命周期,现在你的所有需求都已经明确(有车,有房,有钱,有公司等等),接下来我们就要开始职业人生的设计了,在制定这个规划的时候,我们要重要的注意以下4个方面的内容,这个四个方面运用到整个人职业的生命周期中。

一、选择市场

        一定要谨慎的挑选你要关注的技术和商业领域。如何权衡风险和收益?
都是做软件开发,你究竟要做与硬件相关的还是与网络相关的?与手机相关的还是与汽车相关的?每一个分支都有专家和权威,你要确认自己想站在哪一个分支的顶点。在深圳很多程序员,为了生存,先入行再转行。程序员需要积累,面试官不喜欢一张白纸上满是编程理念的空头支票。

二、投资

        做生意哪有不投资就赚钱的好事,你的知识和技术就是你这件生意的基础。所以你要在这两个方面合理的投资,时间,金钱。只知道在理论上使用VB或者Java已经远远不够了,那么在新的环境下,新的平台下,又有哪些新的技术你应该具备的呢?

三、执行力

        用我老板的话来说单纯有技术出色的员工,并不能给公司带来利益。员工必须要有产出才行。有的时候一名优秀的员工产出远远不及一名普通的员工,反而有时候会让简单的事情变的一团糟糕,2分钟一个简单的算法,被花上2天时间提高0.001%的效率这种事情也是经常发生的。所以我们应该考虑的是能否创造最有利的价值而不是完美,

四、团队

        程序员孤军奋战成不了大事。一个再优秀的程序员也完成不了整个windows操作系统的工作,虽然我见过一个人是可以独立完成破解windows的工作的。所以如果不想过于孤单和山寨,请找到一支正规军加入他们。

五、又是市场

        你们肯定会说,你开始写循环了是吧?怎么又是市场?
        一个人选对了市场,投资技术,有了回报,有了产出,有了自己的团队,恭喜你,你离出产品的日子不远了。但是你有没有考虑一下你的产品的市场,若是无人知晓,毫无用途,又怎么会有利润呢?你的成绩又怎么会被老板和同行认可呢?请记住:一个团队奋斗了1个月写出来一个:Hello world!是赚不了钱的。

到这一节结尾的时候了,写几句鼓励的话,鼓励一下自己和大家:如果你要做一名优秀的软件工程师,请绝对不要萎靡不振,也不要毫无成果的去寻找工作,因为有很多跟你一样的人,因为努力成功了,所以你也不要担心,请相信自己一定会成功,没事多写写代码,或者来我的博客 逛逛,这样就不会感到恐惧了。

 

关键词:程序员   职业规划   软件工程师   转帖

posted @ 2011-05-18 12:58 jadmin 阅读(92) | 评论 (0)编辑 收藏
1、悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。


2、乐观锁( Optimistic Locking )
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

 

posted @ 2011-05-18 12:51 jadmin 阅读(108) | 评论 (0)编辑 收藏

  生活并没有拖欠我们任何东西,所以没有必要总苦着脸。应对生活充满感激,至少,它给了我们生命,给了我们生存的空间。

  微笑是对生活的一种态度,跟贫富,地位,处境没有必然的联系。一个富翁可能整天忧心忡忡,而一个穷人可能心情舒畅:一位残疾人可能坦然乐观;一位处境顺利的人可能会愁眉不展,一位身处逆境的人可能会面带微笑……

  一个人的情绪受环境的影响,这是很正常的,但你苦着脸,一副苦大仇深的样子,对处境并不会有任何的改变,相反,如果微笑着去生活,那会增加亲和力,别人更乐于跟你交往,得到的机会也会更多。

  只有心里有阳光的人,才能感受到现实的阳光,如果连自己都常苦着脸,那生活如何美好?生活始终是一面镜子,照到的是我们的影像,当我们哭泣时,生活在哭泣,当我们微笑时,生活也在微笑。

  微笑发自内心,不卑不亢,既不是对弱者的愚弄,也不是对强者的奉承。奉承时的笑容,是一种假笑,而面具是不会长久的,一旦有机会,他们便会除下面具,露出本来的面目。

  微笑没有目的,无论是对上司,还是对门卫,那笑容都是一样,微笑是对他人的尊重,同时是对生活的尊重。微笑是有"回报"的,人际关系就像物理学上所说的力的平衡,你怎样对别人,别人就会怎样对你,你对别人的微笑越多,别人对你的微笑也会越多。

  在受到别人的曲解后,可以选择暴怒,也可以选择微笑,通常微笑的力量会更大,因为微笑会震撼对方的心灵,显露出来的豁达气度让对方觉得自己渺小,丑陋。

  清者自清,浊者自浊。有时候过多的解释、争执是没有必要的。对于那些无理取闹、蓄意诋毁的人,给他一个微笑,剩下的事就让时间去证明好了。

  当年,有人处处说爱因斯坦的理论错了,并且说有一百位科学家联合作证,爱因斯坦知道了这件事,只是淡淡的笑了笑,说,一百位?要这么多人?只要证明我真的错了,一个人出面便行了。

  爱因斯坦的理论经历了时间的考验,而那些人却让一个微笑打败了。

  微笑发自内心,无法伪装。保持“微笑”的心态,人生会更加美好。人生中有挫折有失败,有误解,那是很正常的,要想生活中一片坦途,那么首先就应清除心中的障碍。微笑的实质便是爱,懂得爱的人,一定不会是平庸的。

  微笑是人生最好的名片,谁不希望跟一个乐观向上的人交朋友呢?微笑能给自己一种信心,也能给别人一种信心,从而更好地激发潜能。

  微笑是朋友间最好的语言,一个自然流露的微笑,胜过千言万语,无论是初次谋面也好,相识已久也好,微笑能拉近人与人之间的距离,另彼此之间倍感温暖。

  微笑是一种修养,并且是一种很重要的修养,微笑的实质是亲切,是鼓励,是温馨。真正懂得微笑的人,总是容易获得比别人更多的机会,总是容易取得成功。

posted @ 2010-09-10 13:01 jadmin 阅读(97) | 评论 (0)编辑 收藏

一、介绍:
简单日记门面(simple logging Facade for java)SLF4J是为各种loging APIs提供一个简单统一的
接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。 Logging API实现既可以
选择直接实现SLF4J接的loging APIs如: NLOG4J、SimpleLogger。也可以通过SLF4J提供的API实现
来开发相应的适配器如Log4jLoggerAdapter、JDK14LoggerAdapter。在SLF4J发行版本中包含了几个
jar包,如slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-log4j13.jar,
slf4j-jdk14.jar and slf4j-jcl.jar通过这些jar文件可以使编译期与具体的实现脱离。或者说可以
灵活的切换

二、官方站点
官方的网站:
http://www.slf4j.org/manual.html
三、为何使用slf4j?
我们在开发过程中可能使用各种log,每个Log有不同的风格、布局,如果想灵活的切换那么slf4j是比较好的
选择。

四、如何使用slf4j
下边一段程序是经典的使用slf4j的方法.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Wombat {
    final Logger logger = LoggerFactory.getLogger(Wombat.class);
    Integer t;
    Integer oldT;
    public void setTemperature(Integer temperature) {
        oldT = t;
        t = temperature;
        logger.error("Temperature set to {}. Old temperature was {}.", t, oldT);
        if (temperature.intValue() > 50) {
            logger.info("Temperature has risen above 50 degrees.");
        }
    }
    public static void main(String[] args) {
        Wombat wombat = new Wombat();
        wombat.setTemperature(1);
        wombat.setTemperature(55);
    }
}

下边介绍一下运行上边程序的过程。
1,编译上边的程序,需要classpath中加入slf4j-api-1.4.1.jar文件
2,运行时,需要classpath中加上slf4j-simple-1.4.1.jar
运行得到结果:
----------------------------
0 [main] ERROR Wombat - Temperature set to 1. Old temperature was null.
0 [main] ERROR Wombat - Temperature set to 55. Old temperature was 1.
0 [main] INFO Wombat - Temperature has risen above 50 degrees.
这个是simple log风格,

3,切换:如果想切换到jdk14的log的风格,只需要把slf4j-simple-1.4.1.jar
从classpath中移除,同时classpath中加入slj4j-jdk14-1.4.1.jar
这时的运行结果:
---------------------------------------------------
2007-7-9 10:40:15 Wombat setTemperature
严重: Temperature set to 1. Old temperature was null.
2007-7-9 10:40:16 Wombat setTemperature
严重: Temperature set to 55. Old temperature was 1.
2007-7-9 10:40:16 Wombat setTemperature
信息: Temperature has risen above 50 degrees.
已经变成jdk14的log风格了。
4,再次切换到log4j
同样移除slj4j-jdk14-1.4.1.jar,加入slf4j-log4j12-1.4.1.jar,同时加入log4j-1.2.x.jar
加入log4j.properties。得到显示结果:
---------------------------------------
10:42:27,328 ERROR Wombat: Temperature set to 1. Old temperature was null.
10:42:27,328 ERROR Wombat: Temperature set to 55. Old temperature was 1.
10:42:27,328 INFO Wombat: Temperature has risen above 50 degrees.
在不同的风格中切换只需要在部署期切换类库就可以了,和开发时无关。


posted @ 2010-08-17 23:52 jadmin 阅读(94) | 评论 (0)编辑 收藏

在Stack Overflow上有这样的一个贴子《What’s your most controversial programming opinion?》,翻译成中文就是“你认为最有争议的编程观点是什么?”,不过,在400多个主回贴,以及千把个子回贴中,好像并不是很有争议,而是令人相当的茅塞顿开,下面罗列一些,并通过我自己的经历和理解发挥了一些,希望对你有帮助。

1) The only “best practice” you should be using all the time is “Use Your Brain”.

唯一的“Best Practice”并不是使用各种各样被前人总结过的各种设计方法、模式,框架,那些著名的方法、模式、框架只代码赞同他们的人多,并不代表他们适合你,你应该更多的去使用你的大脑,独立地思考那些方法、模式、框架出现的原因和其背后的想法和思想,那才是“best practice”。事实上来说,那些所谓的“Best Practice”只不过是限制那些糟糕的程序员们的破坏力。

2)Programmers who don’t code in their spare time for fun will never become as good as those that do.

如果你对编程没有感到一种快乐,没有在你空闲的时候去以一种的轻松的方式去生活,无论是编程,还是运动,还是去旅游,只要你在没有从中感到轻松和愉快,那么你只不过是在应付它们。而你无时无刻不扎在程序堆中,这样下来,就算是你是一个非常聪明,非常有才华的人,你也不会成为一个优秀的编程员,要么只会平平凡凡,要么只会整天扎在技术中成为书呆子。当然,这个观点是有争议,热情和能力的差距也是很大的。不过我们可以从中汲取其正面的观点。

3)Most comments in code are in fact a pernicious form of code duplication.

注释应该是注释Why,而不是How和What,参看《惹恼程序员的十件事》,代码告诉你How,而注释应该告诉你Why。但大多数的程序并不知道什么是好的注释,那些注释其实和code是重复的,毫无意义。

4)XML is highly overrated

XML可能被高估了。XML对于Web上的应用是不错的,但是我们把其用到了各种地方,好像没有XML,我们都不会编程了。

5)Not all programmers are created equal

这是那些junior经理或是流程爱犯的错,他们总是认为,DeveloperA == DeveloperB,只要他们的title一样,他们以为他们的能力、工作速度、解决问题的方法,掌握的技能等等都是一样的。呵呵。更扯的是,在某些时候,就算是最差的程序员,因为Title,他们也会认为其比别人强十倍,这就是很表面的愚蠢的管理。

6)”Googling it” is okay!

不可否认,查找知识是一种能力。但Google只会给你知识,并不会教给你技能。那里只有“鱼”,没有“渔”,过度的使用Google,只会让你越来越离不开他,你越来越需要要它立马告诉你答案,而你越来越不会自己去思考,自己去探索,去专研。如果KFC快餐是垃圾食品对我们的身体没有好处,那么使用Google也一种快餐文化对我们的智力发展大大的没有好处。因为我们过度地关注了答案,而不是寻找答案的技术和过程。

7)If you only know one language, no matter how well you know it, you’re not a great programmer.

如果你只懂一种语言,准确的说,如果你只懂一类语类,如:Java和C#,PHP和Perl,那么,你将会被局限起来,只有了解了各种各样的语言,了解了不同语言的不同方法 ,你才会有比较,只有了比较,你才会明白各种语言的长处和短处,才会让你有更为成熟的观点,而且不整天和别的程序在网上斗嘴争论是Windows好还是Unix好,是C好还是C++好,有这点工夫能干好多事了。世界因为不同而精彩,只知道事物的一面是有害的。

8)Your job is to put yourself out of work.

你的工作不是保守,那种教会徒弟,饿死师父的想法,不但是相当短浅的,而且还是相当脑残的。因为,在计算机世界里,你掌握的老技术越多,你就越没用,因为技术更新的太快。你对工作越保守,这个工作就越来越离不开你,你就越不越不能抽身去学新的东西,你也就越来越OUT了。记住:If you can’t be replaced then you can’t be promoted!

9)Design patterns are hurting good design more than they’re helping it.

很多程序员把设计模式奉为天神,他们过度的追求设计模式以至都都忘了需求是什么,结果整个系统设计被设计模式搞得乱七八糟,我们叫这种编程为“设计模式驱动编程”,正如第一点所说,如果你不懂得用自己的大脑思考的话,知其然,不知所以然的话,那么你不但得不到其好处,反而受其所累。

10)Unit Testing won’t help you write good code

其实,unit test 的主要目的是,为了防止你不会因为一个改动而引入Bug,但这并不会让你能写出更好的代码。这只会让你写出不会出错的代码。同第一点,这样的方法,只不过是防止糟糕的程序员,而并不是让程序员或代码质量更有长进。反而,程序员通常会借用“通过Unit Test”来为自己代码做辩解,而此时,Unit Test Report成了一种托辞。

最后,顺便说一下,以前去那个敏捷的公司面试,发现那个公司的某些技术人员中毒不浅,具体表现在上述的1)9)10)观点上,过份地迷信了best practice,Design Patterns和Unit Testing。

Requirements

JUEL requires Java 5 or later.

加入juel.jar

-------------------------
I think I get same problem when trying to integrate JBPM4 into my app. And I find out why.
Because you're using Tomcat 6.0... The lib el-api.jar in %tomcat_home%/lib conflicts with juel.jar, which exists in %jbpm4_home%/lib.

juel: <http://juel.sourceforge.net/> You will find the 2 jars define the same api for javax/el/ExressionFactory.

The solution is that you use Tomcat 5.5 instead of Tomcat 6.0. Because tomcat 5.5 uses commons-el.jar (Tomcat5.5/common/lib)

Or you can still use Tomcat 6.0, but you must replace el-api.jar with juel.jar. And don't forget to remove juel.jar from your app lib(A duplicate import, if you don't remove).

Try it!

---------------------------------------------------------
解决:删掉tomcat6的el.jar,加入juel.jar,juel-impl.jar,juel-engine.jar

posted @ 2010-07-21 17:40 jadmin 阅读(1453) | 评论 (0)编辑 收藏


  僧人竺法深在东晋简文帝处作客,刘尹问:「法师是学道之人,为什么要来官宦之门中走动?」竺法深回答说:「你自见这是朱门高第,在贫道眼里,同走在茅屋草舍间并无任何差别。」
  法师的境界,是繁华阅尽后的云淡风清,是滚滚红尘里的淡定从容。
  人生的浮浮沉沉,欲望乃是最大的滥殇。它可以是推动你向上的一股力量,也可以是主宰你堕落的源头。生命如此短暂,有所营谋,必有所烦恼;有所执着,必有所束缚;有所得,必有所失。生前的显赫富贵,终究是些过眼烟云。如果为此穷尽一生,岂非本末倒置?
  人生在世,要活得很自在,才会幸福。不能控制欲望的人,当然就得不到安详。我们如果能在每一刹那,自我观照,自我控制,长养智慧与安详。没有忧虑、没有恐惧、没有攀缘,离开一切执着,则能拥有统一和谐的心灵,幸福也就掌握在你的手中。

posted @ 2010-07-11 12:22 jadmin 阅读(104) | 评论 (0)编辑 收藏

  人的一生是要经历许多阶段的,比如说纯真无邪的少年时代,激情如火的青春岁月,厚重沉稳的中年时期,从容淡定的人生暮年。每个时候都有独特的风景,每段岁月都会给人不同的感受。可进入中年的她,突然间感觉自己,就一下从躁动中宁静下来了,不经意间就有了种坐看云起云舒,我自心境如水的超然。

  她感到在无意中,一切都漫漫地淡下来了,常常会挂着淡淡的微笑,给人一种和谐温馨之感;常常看淡名利和物质,却看重人与人之间的感情,常常不会冲动行事,也不会轻易后悔,她会为自己的决定负责。可当她一旦爱上一个人,一定会坚守自己的那份爱,爱情的保质期是“永远”。

  她还会在秋阳明丽的早晨或午后为自己沏一壶香茗,手捧一本书细细品位,慢慢欣赏。她懂得什么是智性美,她更愿意在闲暇的时候去学习书法音乐美术,或者去充电接受最新的科技知识,来提高自己的修养和品位,她不会把时间浪费在世俗的纷争和无聊的麻将中,更不会和别人去攀比高档名牌的服饰和虚荣的炫耀,她知道真正的美丽一定是由内而外散发出来的。

  可是她也记得不久前还在为工作上的事烦恼不已,什么上司不赏识呀,工作业绩不突出啦,还有同事之间不服气了,等等,等等,整个身心陷进了争强好胜的泥沼里,苦苦挣扎,不能释怀,可是到了中年一切就都云开日出了,不是不努力工作,只是觉得自己尽力就问心无愧了,至于结果就不会去过多考虑了,这样反而同事之间的关系和谐了,人的精神就愉快了,心胸也宽广了。

  她也有曾经陷入爱恋中不能自拔的时候。那时,在热恋中痛苦,因为怕失去,所以猜忌怀疑,无事生非,互相折磨;在失恋中更痛苦,因为无所依傍,所以孤独寂寞,痛不欲生,自我戕害。可是到了人生的这个时期,不管是热恋也好,失恋也罢,都能平静地对待,诗意的化解。不是说心如止水,情如枯井,而是能理智地看待,睿智地经营,这样使情爱更彰显出深沉含蓄之美,情深意切之境。让相爱的双方没有压力,更能享受爱本身给人带来的快乐。

  她想每个人的一生中的某个阶段是需要某种热闹的,那时侯饱涨的生命力需要向外奔突,就象急湍的河流一样。但一个人不能永远停留在这个阶段。经过了激烈的撞击之后,生命就来到了一块开阔的谷地,汇蓄成了一片浩瀚的的湖泊。这时就会变得异常的平和宁静,这种脱离了世俗的宁静,是以丰富的精神内涵为依傍的。它是一种超脱,一种繁华落尽见真情的纯粹,一种精神的升华。托尔斯泰曾经说过:“随着年岁增长,我的生命越来越精神化了”。说的就是这样的感触。

  人淡如菊,就是一种丰富的精神安静。具有这种品格的人,能够浸润在风晨雨夕,面对着阶柳庭花,听得到自然的呼吸,感受得到自然的脉搏。这时,斗室便是八极,内心顿成宇宙;这时,精神就会富有,心胸就会博大;这时,便拥有了一份澄明清澈,一份从容淡定。人生就从此不寂寞了。

posted @ 2010-06-29 23:37 jadmin 阅读(124) | 评论 (0)编辑 收藏

这种情况通常是发生在换了另一份 Eclipse 拷贝之后。之前一般都能正常在 Eclipse 中执行 Ant 脚本,删除了原来的 Eclipse 之后换了另一个拷贝或者是新版本,在其中执行 Ant 脚本时弹出窗口出现以下错误:

---------------------------
Java Virtual Machine Launcher
---------------------------
Could not find the main class. Program will exit.
---------------------------
确定  
---------------------------

这种错误对用过 Java 的人来说还是很明白,找不到 Ant 的主类吗。不过说实话也困扰过我一段时间,也没去深究。说开了,症结就是 Eclipse 中的 ANT_HOME 指向不对。

解决办法:Eclipse 中进入 Window->Preferences->Ant->Runtime,在Classpath 标签页,看到 Ant Home Entries 指向的目录不对了(它仍然指向你上回的目录,而这个目录应该不存在了),你要做的就是改变 Ant Home 指向正确的目录,点 Ant Home 按钮,选择 Ant Home 目录,比如我用的 Eclipse 是 3.3.1 的,Ant Home 是 Eclipse 的插件目录下的 org.apache.ant_1.7.0.v200706080842。

现在可以再次在 Eclipse 中执行你的 Ant 脚本,是不是能正常运行了啊!

posted @ 2010-05-17 00:09 jadmin 阅读(195) | 评论 (0)编辑 收藏

        很多朋友都很喜欢在DOS命令行下来操作计算机,我也是。 如何打开DOS并定位到指定的路径呢?很多优化软件都提供了往右键菜单中加入“当前目录打开DOS”的功能,当右键点击文件夹时,就打开DOS并定位到该文件夹。网上也有修改注册表来实现的,其实还有更简单的方法来实现!

        打开“我的电脑”,点击菜单中的“工具”-“文件夹选项”,选择“文件类型”,找到“(无)资料夹”,点“高级”,“新建”,在“操作”中填入 “DOS快速通道”(这里可以随便填),“用于执行操作的应用程序”中填入“CMD.exe /k cd %1”(这个是关键),确定即可。

        右键打开任何一个文件夹、分区,点击“DOS快速通道”,就可以打开DOS并定位到你所点击的目录下!这里要告诉大家,其实许多问题都有更简单的方法,只要大家细心一点。都可以大大提高我们的办事效率!


posted @ 2010-05-15 23:52 jadmin 阅读(95) | 评论 (0)编辑 收藏

1、放下压力
  累与不累,取决于自己的心态
  心灵的房间,不打扫就会落满灰尘。蒙尘的心,会变得灰色和迷茫。我们每天都要经历很多事情,开心的,不开心的,都在心里安家落户。心里的事情一多,就会变得杂乱无序,然后心也跟着乱起来。有些痛苦的情绪和不愉快的记忆,如果充斥在心里,就会使人委靡不振。所以,扫地除尘,能够使黯然的心变得亮堂;把事情理清楚,才能告别烦乱;把一些无谓的痛苦扔掉,快乐就有了更多更大的空间。
  紧紧抓住不快乐的理由,无视快乐的理由,就是你总是觉得难受的原因了。

2、放下烦恼
  快乐其实很简单
  所谓练习微笑,不是机械地挪动你的面部表情,而是努力地改变你的心态,调节你的心情。学会平静地接受现实,学会对自己说声顺其自然,学会坦然地面对厄运,学会积极地看待人生,学会凡事都往好处想。这样,阳光就会流进心里来,驱走恐惧,驱走黑暗,驱走所有的阴霾。
  快乐其实很简单,不要自己不快乐就可以了。


3、放下自卑
  把自卑从你的字典里删去
  不是每个人都可以成为伟人,但每个人都可以成为内心强大的人。内心的强大,能够稀释一切痛苦和哀愁;内心的强大,能够有效弥补你外在的不足;内心的强大,能够让你无所畏惧地走在大路上,感到自己的思想,高过所有的建筑和山峰!
  相信自己,找准自己的位置,你同样可以拥有一个有价值的人生。

4、放下懒惰
  奋斗改变命运
  不要一味地羡慕人家的绝活与绝招,通过恒久的努力,你也完全可以拥有。因为,把一个简单的动作练到出神入化,就是绝招;把一件平凡的小事做到炉火纯青,就是绝活。
  提醒自己,记住自己的提醒,上进的你,快乐的你,健康的你,善良的你,一定会有一个灿烂的人生。


5、放下消极
  绝望向左,希望向右
  如果你想成为一个成功的人,那么,请为"最好的自己"加油吧,让积极打败消极,让高尚打败鄙陋,让真诚打败虚伪,让宽容打败褊狭,让快乐打败忧郁,让勤奋打败懒惰,让坚强打败脆弱,让伟大打败猥琐……只要你愿意,你完全可以一辈子都做最好的自己。
  没有谁能够左右胜负,除了你。自己的战争,你就是运筹帷幄的将军!
  不是所有的梦想都能成为美好的现实,但美丽的梦想同样可以装点出生活的美丽。

6、放下抱怨
  与其抱怨,不如努力
  所有的失败都是为成功做准备。抱怨和泄气,只能阻碍成功向自己走来的步伐。放下抱怨,心平气和地接受失败,无疑是智者的姿态。
  抱怨无法改变现状,拼搏才能带来希望。真的金子,只要自己不把自己埋没,只要一心想着闪光,就总有闪光的那一天。
  纵观古今中外,很多人生的奇迹,都是那些最初拿了一手坏牌的人创造的。
  不要总是烦恼生活。不要总以为生活辜负了你什么,其实,你跟别人拥有的一样多。


7、放下犹豫
  立即行动,成功无限
  认准了的事情,不要优柔寡断;选准了一个方向,就只管上路,不要回头。机遇就像闪电,只有快速果断才能将它捕获。
  立即行动是所有成功人士共同的特质。如果你有什么好的想法,那就立即行动吧;如果你遇到了一个好的机遇,那就立即抓住吧。立即行动,成功无限!
  有些人是必须忘记的,有些事是用来反省的,有些东西是不能不清理的。该放手时就放手,你才可以腾出手来,抓住原本属于你的快乐和幸福!
  有些事情是不能等待的,一时的犹豫,留下的将是永远的遗憾!


8、放下狭隘
  心宽,天地就宽
  宽容是一种美德。宽容别人,其实也是给自己的心灵让路。只有在宽容的世界里,人,才能奏出和谐的生命之歌!
  要想没有偏见,就要创造一个宽容的社会。要想根除偏见,就要首先根除狭隘的思想。只有远离偏见,才有人与内心的和谐,人与人的和谐,人与社会的和谐。
  我们不但要自己快乐,还要把自己的快乐分享给朋友、家人甚至素不相识的陌生人。因为分享快乐本身就是一种快乐,一种更高境界的快乐。
  宽容是一种美德。宽容别人,其实也是给自己的心灵让路。只有在宽容的世界里,人,才能奏出和谐的生命之歌!

posted @ 2010-05-08 23:37 jadmin 阅读(83) | 评论 (0)编辑 收藏

SQL Server2000和2005的连接代码区别,写连接代码时需要注意2000和2005的不同:  
1、连接SqlServer2000  
      Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();  
      URL   =   "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=test";  
2、连接SqlServer2005  
      Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver").newInstance();  
      URL   =   "jdbc:sqlserver://localhost:1433;DatabaseName=test";

posted @ 2010-04-26 17:28 jadmin 阅读(89) | 评论 (0)编辑 收藏

今天突然接到淘宝的电话面试,问了一些问题,其中一个是关于Java序列化的

问题大概就是serialVersionUID的作用之类的吧,当时回答是凭感觉和经验回答的,后来通过测试,我的回答是正确的,这里再总结下序列化的问题

1.Java的序列化机制只序列化对象的属性值,而不会去序列化什么所谓的方法,列化机制只保存对象的类型信息,属性的类型信息和属性值,和方法没有什么关系,你就是给这个类增加10000个方法,序列化内容也不会增加任何东西。

2.对于需要序列化和反序列化的的实体,最好加上serialVersionUID,并不要随便更改其值。

       对于一个实体User好啦,实现了java.io.Serializable接口,但没有加上serialVersionUID,先实例化一个User对象并将它序列化到磁盘上,然后再反序列化,OK,这是没有问题的!看下面的:

       在User类里再增加一个属性,接着再将之前序列化的文件反序列化成User对象,报错!!!为何?就是因为没有加上serialVersionUID!

       如果User加上了serialVersionUID,给User增加属性后,再将之前序列化的文件反序列化回User对象,是没有问题的!!

3.总结

       serialVersionUID 用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会出错。为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入private static final long serialVersionUID这个属性,具体数值自己定义。

posted @ 2010-04-24 23:08 jadmin 阅读(105) | 评论 (0)编辑 收藏

态度篇

1. 做实事

不要抱怨,发牢骚,指责他人,找出问题所在,想办法解决。对问题和错误,要勇于承担。

2. 欲速则不达

用小聪明、权宜之计解决问题,求快而不顾代码质量,会给项目留下要命的死角。

3. 对事不对人

就事论事,明智、真诚、虚心地讨论问题,提出创新方案。

4. 排除万难,奋勇前进

勇气往往是克服困难的唯一方法。

学习篇

5. 跟踪变化

新技术层出不穷并不可怕。坚持学习新技术,读书,读技术杂志,参加技术活动,与人交流。要多理解新词背后的所以然,把握技术大趋势,将新技术用于产品开发要谨慎。

6. 对团队投资

打造学习型团队,不断提高兄弟们的平均水平。

7. 懂得丢弃

老的套路和技术,该丢,就得丢。不要固步自封。

8. 打破砂锅问到底

不断追问,真正搞懂问题的本质。为什么?应该成为你的口头禅。

9. 把握开发节奏

控制好时间,养成好习惯,不要加班。

开发流程篇

10. 让客户做决定

让用户在现场,倾听他们的声音,对业务最重要的决策应该让他们说了算。

11. 让设计指导而不是操纵开发

设计是前进的地图,它指引的是方向,而不是目的本身。设计的详略程度应该适当。

12. 合理地使用技术

根据需要而不是其他因素选择技术。对各种技术方案进行严格地追问,真诚面对各种问题。

13. 让应用随时都可以发布

通过善用持续集成和版本管理,你应该随时都能够编译、运行甚至部署应用。

14. 提早集成,频繁集成

集成有风险,要尽早尽量多地集成。

15. 提早实现自动化部署

16. 使用演示获得频繁反馈

17. 使用短迭代,增量发布

18. 固定价格就意味着背叛承诺

估算应该基于实际的工作不断变化。

用户篇

19. 守护天使

自动化单元测试是你的守护天使。

20. 先用它再实现它

测试驱动开发其实是一种设计工具。

21. 不同环境,就有不同问题

要重视多平台问题。

22. 自动验收测试

23. 度量真实的进度

在工作量估算上,不要自欺欺人。

24. 倾听用户的声音

每一声抱怨都隐藏着宝贵的真理。

编程篇

25. 代码要清晰地表达意图

代码是给人读的,不要耍小聪明。

26. 用代码沟通

注释的艺术。

27. 动态地进行取舍

记住,没有最佳解决方案。各种目标不可能面面俱到,关注对用户重要的需求。

28. 增量式编程

写一点代码就构建、测试、重构、休息。让代码干净利落。

29. 尽量简单

宁简勿繁。如果没有充足的理由,就不要使用什么模式、原则和特别的技术。

30. 编写内聚的代码

类和组件应该足够小,任务单一。

31. 告知,不要询问

多用消息传递,少用函数调用。

32. 根据契约进行替换

委托往往优于继承。

调试篇

33. 记录问题解决日志

不要在同一地方摔倒两次。错误是最宝贵的财富。

34. 警告就是错误

忽视编译器的警告可能铸成大错。

35. 对问题各个击破

分而治之是计算机科学中最重要的思想之一。但是,要从设计和原型阶段就考虑各部分应该能够很好地分离。

36. 报告所有的异常

37. 提供有用的错误信息

稍微多花一点心思,出错的时候,将给你带来极大便利。

团队协作篇

38. 定期安排会面时间

常开会,开短会。

39. 架构师必须写代码

不写代码的架构师不是好架构师。好的设计都来自实际编程。编程可以带来深入的理解。

40. 实行代码集体所有制

让开发人员在系统不同区域中不同的模块和任务之间轮岗。

41. 成为指导者

教学相长。分享能提高团队的总体能力。

42. 让大家自己想办法

指引方向,而不是直接提供解决方案。让每个人都有机会在干中学习。

43. 准备好后再共享代码

不要提交无法编译或者没有通过单元测试的代码!

44. 做代码复查

复查对提高代码质量、减少错误极为重要。

45. 及时通报进展与问题

主动通报,不要让别人来问你。

posted @ 2010-04-24 15:23 jadmin 阅读(69) | 评论 (0)编辑 收藏

  有一次,一位老师问他的学生:「你们说说人的生命究竟有多长久?」「人的生命平均起来有几十年的长度。」一个学生回答。老师摇了摇头。另一个弟子见状,肃穆地说道:「人的生命如花草,春天萌芽发枝,灿烂似锦;冬天枯萎凋零,化为尘土。」老师露出了赞许的微笑:「你能够体察到生命的短暂,但仍限于表面。」又听得一个无限悲怆的声音说道:「我觉得生命就像浮游虫一样,早上才出生,晚上就可能死去,充其量不过是一昼夜的时间!」「喔!你观察到生命朝生暮死的现象,有了更为深入的认识。」这时,一个学生站起身来说道:「老师,依学生看来,人命只在一呼一吸之间。」话语一出,四座愕然。老师微笑着点了点头。
  人生的下一秒难以预料,一息不来便是隔世了。唯有珍惜当下,才不会留下遗憾啊。让我们把每一个平凡的日子都当成人生的最后一天来珍视吧。

posted @ 2010-04-20 16:25 jadmin 阅读(104) | 评论 (0)编辑 收藏

/*
* @(#)GtalkTest.java 2010-4-17
*
* Copyright (c) 2010 by gerald. All Rights Reserved.
*/

package org.jsoft.opensource.demos.smack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;

/**
* 利用Smack,通过XMPP协议与Gtalk通信.
*
* @author <a href="
mailto:gerald.chen@qq.com">GeraldChen</a>
* @version $Id: GtalkTest.java 2010-4-17 上午12:30:23$
*/
public class GtalkTest {
/** Logger for this class */
protected static final Log LOG = LogFactory.getLog(GtalkTest.class);

public static void main(String[] args) {
   test();
}

/**
* 与Gtalk进行通讯
*
*/
public static void test() {
   ConnectionConfiguration connectionConfig = new ConnectionConfiguration(
     "talk.google.com", 5222, "gmail.com");
   connectionConfig.setSASLAuthenticationEnabled(false);
   XMPPConnection connection = new XMPPConnection(connectionConfig);
   try {
    connection.connect();
    connection.login("logs.chen", "***********");
    ChatManager chatmanager = connection.getChatManager();

    Chat newChat = chatmanager.createChat("edotxp@gmail.com",
      new MessageListener() {
       public void processMessage(Chat chat, Message message) {
        if(LOG.isInfoEnabled()) {
         LOG.info("Received message: \r\n" + message.toXML());
         LOG.info("收到相应消息");
        }
       }
      });
    newChat.sendMessage("陈先生,您好!");
    Thread.sleep(10000);
   } catch (Exception e) {
    LOG.error(e);
   }
}

}

posted @ 2010-04-17 02:11 jadmin 阅读(344) | 评论 (0)编辑 收藏

  一位青年满怀烦恼去找一位智者,他大学毕业后,曾豪情万丈地为自己树立了许多目标,可是几年下来,依然一事无成。

  他找到智者时,智者正在河边小屋里读书。智者微笑着听完青年的倾诉,对他说:“来,你先帮我烧壶开水!”

  青年看见墙角放着一把极大的水壶,旁边是一个小火灶,可是没发现柴火,于是便出去找。

  他在外面拾了一些枯枝回来,装满一壶水,放在灶台上,在灶内放了一些柴便烧了起来,可是由于壶太大,那捆柴烧尽了,水也没开。于是他跑出去继续找柴,回来的时候那壶水已经凉得差不多了。这回他学聪明了,没有急于点火,而是再次出去找了些柴,由于柴准备充足,水不一会就烧开了。

  智者忽然问他:“如果没有足够的柴,你该怎样把水烧开?”

  青年想了一会,摇了摇头。

  智者说:“如果那样,就把水壶里的水倒掉一些!”

  青年若有所思地点了点头。

  智者接着说:“你一开始踌躇满志,树立了太多的目标,就像这个大水壶装了太多水一样,而你又没有足够的柴,所以不能把水烧开,要想把水烧开,你或者倒出一些水,或者先去准备柴!”

  青年恍然大悟。回去后,他把计划中所列的目标去掉了许多,只留下最近的几个,同时利用业余时间学习各种专业知识。几年后,他的目标基本上都实现了。

  只有删繁就简,从最近的目标开始,才会一步步走向成功。万事挂怀,只会半途而废。另外,我们只有不断地捡拾“柴”,才能使人生不断加温,最终让生命沸腾起来。

posted @ 2010-04-15 22:05 jadmin 阅读(93) | 评论 (0)编辑 收藏

  01.慢慢的才知道,太在乎别人了往往会伤害自己­

  02.慢慢的才知道,对自己好的人会随着时间的流逝越来越少,­

  03.慢慢的才知道,一个人要自己对自己好,因为真正关心你的人很少,有了事他们也不一定会在你身边。所以要自己照顾自己­

  04.慢慢的才知道,真心对一个人好不一定有回报,而你忽略的人往往有可能是最重视你的,­

  05.慢慢的才知道,很多东西是可遇而不可求的,很多东西只能拥有一次,­

  06.慢慢的才知道,恋爱不一定是真心的,有可能是利益关系,有可能是攀比心理,­

  07.慢慢的才知道,不要和别人争论什么,因为那是没有结果的,无论谁对谁错,­

  08.慢慢的才知道,很多时候自己遇到不开心事,千万不要渴望别人同情,大多数人会采取冷漠回敬的。那样会更让人家看不起,­

  09.慢慢的才知道,有很多东西是不属于你的,你使劲强求会遭天遣的,­

  10.慢慢的才知道,未必做每件事情都有意义,可是做的每件事情都觉得是一件回忆!­

  11.慢慢的才知道,人的性格可以差异到如此之大,­

  12.慢慢的才知道,许多曾经的人会变的让你认不出,但请留住回忆。­

  13.慢慢的才知道,从现在开始应该把握每一个你能把握的人,放弃你留不住的人,不要因为想留住个别人而失去一群人。­

  14.慢慢的才知道,自己一定在乎自己的自尊,但你的自尊在别人眼里根本不算什么,­

  15.慢慢的才知道,不要心情不好的时候对周围人发脾气,渴望他们谅解你,人家不是你的父母,现在你可以明白父母对自己多么重要,­

  16.慢慢的才知道,即便有人对情感看的无所谓,你一定要坚信,人之间的感情,有可能会令所有东西都无法超越的,但记住,只是有可能,­

  17.慢慢的才知道,原来现实如此的无奈。­

  18.慢慢的才知道,会遇到许多自己看不惯的人或事,但那与你无关,别人爱咋整随他便,别生不该生的气,不值,­

  20.慢慢的才知道,两个天天在一起的人不一定是朋友,有可能什么都不是,­

  21.慢慢的才知道,会遇到很多诱惑,无论别人怎么样,你是你,你有你的原则和底限,­

  22.慢慢的才知道,会有人很讨厌你或者和你过不去,但是他爱怎么样就怎么样,我们要大度,不和小人计较,但前提是你正确,­

  23.慢慢的才知道,很多人无法理解男女之间的朋友关系,在一起就一定是恋人,不是恋人就一定不能在一起,­

  24.慢慢的才知道,学习要刻苦,因为凭聪明就能应付考试科目的人是凤毛翎角,­

  25.慢慢的才知道,原来时间一空闲下来是那么无聊,丝毫没有中学的充实的感觉,­

  26.慢慢的才知道,手机是别人有事找你的时候用的,并不是为了交流感情的­

  27.慢慢的才知道,可以不把所有人当朋友,但千万不能把一个人当敌人,至少可以当同学,­

  28.慢慢的才知道,玩你能玩的起的,玩不起的千万别玩,不然会输了什么都没有的,­

  29.慢慢的才知道,快乐常常来自回忆,而痛苦常常来自于回忆与现实的差距,­

  30.慢慢的才知道,那些嘻哈打闹只是消遣而已,而过往的抽烟打架更是无知.­

  31.慢慢的才知道,有很多人的想法与做法你无法理解,或是根本不知道他在想什么,千万别在那揣摩或者瞎猜,那样会让自己累,既然人家要保持神秘感那就让人家保持去啊,自己又不是占卜师,­

  32.慢慢的才知道,不要把自己想的有多高,没有绝对性的胜利,也没有绝对性的失败­

  33,慢慢的才知道,生活是有很多不公平的,你一定要正视,相信实力和群众的眼睛,­

  34.慢慢的才知道,兄弟情义有时候未必是想像的那么美好,只有自己真心付出,才有可能得到别人的真心对待.­

  35.慢慢的才知道,有的人不断的算计,到头还来是会输的很惨,所以应当保持一个平和的心!­

  36.慢慢的才知道,有的事情不是自己所愿意的,但是有的事情必须得去完成,那也是对自己的一段特训.­

  37,慢慢的才知道,原来两个人在一起或真或假,相处的时间还是占据着重要成份.­

  38.慢慢的才知道,现实根想法的差距,必须要随机应变,跟上生活的步伐!­

  39.慢慢的才知道,自己也在慢慢长大,不在是小孩子了,适应着每一件事的成长.­

  40.慢慢的才知道,给人留一线日后好相见的真正意义,没有永远的敌人只有永远的朋友,凡事不要做的太绝,事情的结局都是用嘴巴说出来的.­

  41.慢慢的才知道,不管玩的多好的朋友都有可能失去,但是我们还是要乐观面对,若是真的把他(她)当作自己的朋友就应该为他(她)祝福.遥望!只是做自己所做的.­

  42.慢慢的才知道,自己在慢慢接受社会了,所以也要慢慢学会适应­


posted @ 2010-04-14 19:41 jadmin 阅读(84) | 评论 (0)编辑 收藏

        把自己当自己。此语最为重要。人生最大的敌人,不是别人,而是自己,战胜了自己,便攻无不克、战无不胜。把自己当自己,就是要求自己不要和自己过不去,别为一个小小的职位、一份微薄的报酬,甚至是他人一些闲言碎语,一个不屑的眼神而怒发冲冠,要以平静淡泊的心态去面对种种荣辱得失和情仇恩怨。

        把自己当别人。人生在世注定要历经诸多喜怒哀乐之事,只有把自己当成了别人,才不会在喜事面前若狂,在困苦面前痛悲。人生在世还会涉及功名利禄之事,也只有把自己当成了别人,才不会为名所累、为利所动、为官所困、为情所恼。

        把别人当自己。人的一生不可能总是一帆风顺、花好月圆的,总会遇到这样或那样的艰难困苦。面对别人的不幸,只有调换位置,把别人当成自己,才会真情实意地同情别人的不幸,理解别人的苦衷,并且在别人需要帮助的时候主动地伸出援助之手。

        信任,是人生一笔弥足珍贵的储蓄。这储蓄,是流言袭来时投向你的善意的目光,是前进道路上给你的坚定的陪伴,是遇到困难时的全力以赴的支持,是遭受诬蔑时驱赶痛苦一盏心灯。

        友情,是人生一笔受益匪浅的储蓄。这储蓄,是患难中的倾囊相助,是错误道路上的逆耳忠言,是跌倒时的一把真诚搀扶,是痛苦时抹去泪水的一缕春风。

        当狂风在你耳边呼啸时,你只当它微风拂面;当暴雨在你眼前倾泻时,你只当它屋檐滴水;当闪电在你头顶肆虐时,你只当它萤火流逝。人,决不能在逆境面前屈服。

        人生犹如一首歌,音调高低起伏,旋律抑扬顿挫;人生仿佛一本书,写满了酸甜苦辣,记录着喜怒哀乐;人生就像一局棋,布满了危险,也撒遍了机遇;人生恰似一条路,有山重水复的坎坷,也有柳暗花明的坦途;人生如同一条河,有时九曲回肠,有时一泻千里。

        亲情友情是我的财富。我是一朵白云,亲情是包容我的蓝天;我是一棵绿树,亲情便是滋养我的土地;我是一只飞鸟,亲情便是庇护我的森林;我是一泓清泉,亲情便是拥抱我的山峦。

        青春,是人生的花朵;青春,是人生的春天;青春,是不耐久藏的珍宝;青春,是创造一切的希望。青春是珍贵的,它是人生最美的花朵,是不耐久藏的珍宝,是转瞬即逝的春光;青春是饱满的,她代表着时代的精神,展示着时代的性格,孕育着时代的希望。

        经历就是人生的硎石,生命的锋芒在磨砺中闪光;经历就是人生的矿石,生命的活力在提炼中释放。经历就是体验,经历就是积淀。没有体验就没有生存的质量;没有积淀,就没有生存的智慧。人生的真谛在经历中探寻,人生的价值在经历中实现。

        真诚是一盏夜幕下的路灯,让行人因它照亮夜色而增添一份夜行的信心。生活中每一回真诚的履践,都会令我们不由自主地萌发对自己心灵的感动。

        一棵小草,也许永远不能成为参天大树,但它可能做最绿最坚强的小草;一滴水,也许永远不能像长江大河一样奔腾,但它可以成为所有水中的最纯的那一滴

        每一个善良的人都是勤劳的农夫,在或肥沃或贫瘠的土地上播种着爱心,他们付出的心血虽不尽相同,但目的都只有一个:收获爱心。

        如果我们能够勇敢地去爱,坚强地去宽容,大度地去为别人的快乐而高兴,明智地理解身边充满爱意,那么我们就能够取得别的生物所不能取得的成就。

        当无事时,应该像有事那样谨慎,当有事时,应像无事时那样镇静。因为漫长的旅途中,实在难以完全避免崎岖和坎坷。

        人生是愈取愈少,愈舍愈多,该当如何?少年时取其丰,壮年时取其实,老年时取其精。少年时舍其不能有,壮年时舍其不当有,老年时舍其不必有。

        人生犹如一首歌,音调高低起伏,旋律抑扬顿挫;人生仿佛一本书,写满了酸甜苦辣,记录着喜怒哀乐;人生就像一局棋,布满了危险,也撒遍了机遇;人生恰似一条路,有山重水复的坎坷,也有柳暗花明的坦途;人生如同一条河,有时九曲回肠,有时一泻千里。

        用不着把年轻的心灵装点得沉重。表面上的沧桑,外在的严肃,并不能让你上升为哲人;离开所有的朋友,你有的只能是孤单的背影。既然现在的我还不能变得深刻,那么,我就让自己变得轻松。哭丧着脸的人,怎能听清花开的响声;伪装自己的人,又怎能听懂蛙鸣一片里的激动

        人生就像一条河,经历丰富,才能远源流长。伟大的一生,像黄河一样跌宕起伏,像长江一样神奇壮美。人生就像一座山,经历奇特才能蔚为大观。伟大的一生,像黄山一样奇峰迭起,像泰山一样大气磅礴。

        失败,是把有价值的东西毁灭给人看;成功,是把有价值的东西包装给人看。成功的秘诀是不怕失败和不忘失败。成功者都是从失败的炼狱中走出来的。成功与失败循环往复,构成精彩的人生。成功与失败的裁决,不是在起点,而是在终点。

        我们的生命之所以贫瘠,原因往往不是放弃了工作,便是因工作放弃了沉思:要不断地工作,也要不断地沉思。生命原是一个不知来自何处去至何方的奇迹,存在也是一个时空的偶然,我们需要不停的奋斗,来印证我们生命的真正存在。这样我们便须活跃我们的思维,点燃灵台的明灯,照亮我们该走的路,以便我们继续跋涉。生命也是需要不断跋涉的,不管昨日你有多少功绩,不管昨日你园圃里有多少花朵,那是属于昨日;若你一心沉湎于昨日的喜悦,就难享今日更清醇的欢欣。今日,一个新的开始,更需要我们前进,更需要我们去孕育。人生是一条永远走不完的旅程,需要生命的火把,直至成灰而泪.

        经历就是人生的硎石,生命的锋芒在磨砺中闪光;经历就是人生的矿石,生命的活力在提炼中释放。经历就是体验,经历就是积淀。没有体验就没有生存的质量;没有积淀,就没有生存的智慧。人生的真谛在经历中探寻,人生的价值在经历中实现。

posted @ 2010-04-13 20:20 jadmin 阅读(84) | 评论 (0)编辑 收藏

  感谢伤害你的人,因为他磨炼了你的心志!
  感谢绊倒你的人,因为他强化了你的双腿!
  感谢欺骗你的人,因为他增进了你的智慧!
  感谢藐视你的人,因为他觉醒了你的自尊!
  感谢遗弃你的人,因为他教会了你该独立!

  在人生的旅途中,最糟糕的境遇往往不是贫困,不是厄运,而是精神和心境处于一种无知无觉的疲惫状态:感动过你的一切不能再感动你,吸引过你的一切不能再吸引你,甚至激怒过你的一切不能再激怒你。这时,人需要寻找另一片风景。

  【1】,“要想改变我们的人生,第一步就是要改变我们的心态。只要心态是正确的,我们的世界就会的光明的。”

  其实人与人之间本身并无太大的区别,真正的区别在于心态,“要么你去驾驭生命,要么生命驾驭你。你的心态决定谁是坐骑,谁是骑师。”在面对心理低谷之时,有的人向现实妥协,放弃了自己的理想和追求;有的人没有低头认输,他们不停审视自己的人生,分析自己的错误,勇于面对,从而走出困境,继续追求自己的梦想。

  我们不能控制自己的遭遇,但我们可以控制自己的心态;我们改变不了别人,我们却可以改变自己;我们改变不了已经发生的事情,但是我们可以调节自己的心态。

  有心无难事,有诚路定通,正确的心态能让你的人生更坦然舒心。当然,心态是依靠你自己调整的,只要你愿意,你就可以给自己的一个正确的心态。

  心态是人真正的主人。改变心态,就是改变人生。有什么样的心态,就会有什么样的人生。要想改变我们的人生,其第一步就是要改变我们的心态。只要心态是正确的,我们的世界也会是光明的。

  【2】,“人活着就是为了解决困难。这才是生命的意义,也是生命的内容。逃避不是办法,知难而上往往是解决问题的最好手段。”

  人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境。其实,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机。换句话说,想要成功的人,都必须懂得知道如何将别人对自己的折磨,转化成一种让自己克服挫折的磨练,这样的磨练让未成功的人成长、茁壮。所以,当你遭遇厄运的时候,坚强与懦弱是成败的分水岭。一个生命能否战胜厄运,创造奇迹,取决于你是否赋于它一种信念的力量。一个在信念力量驱动下的生命即可创造人间的奇迹。

  在困难面前,如果你能在众人都放弃时再多坚持一秒,那么,最后的胜利一定是属于你的。坚定的信念是获取成功的动力。很多的时候,成功都是在最后一刻才蹒跚到来。因此,做任何事情,我们都不应该半途而废,哪怕前行的道路再苦再难,也要坚持下去,这样才不会在自己的人生里留下太多的遗憾。

  精彩的人生是在挫折中造就的,挫折是一个人的炼金石,许多挫折往往是好的开始。你只要按照自己的禀赋发展自我,不断地超越心灵的绊马索,你就不会发现自己生命中的太阳熠熠闪耀着光彩!

  【3】,“要想赢,就一定不能怕输。不怕输,结果未必能赢。但是怕输,结果则一定是输。”

  人生的道路上,我们每个人都不可避免地面对各种风险与挑战,结果有成功,也有失败。不过,人生的胜利不在于一时的得失,而是在于谁是最后的胜利者。没有走到生命的尽头,我们谁也无法说我们到底是成功了还是失败了。所以我们在生命的任何阶段都不能泄气,都要充满希望!

  不要因为痛苦而放弃你的选择。所谓的成功人士,无非是比别人多付出,多经历了磨难的人罢了。不因痛苦而放弃你的选择,你才能成功。

  凤凰涅羽化成蝶,正是因为经历了强烈的痛苦,然后才有着震撼人心的美丽。一个人的成功并不是偶然的,他是踩着无数的失败和痛苦走过来的,别人看到的只是他今天的光辉和荣耀。只有他自己知道,在他通往成功的路人,有着被荆棘扎破的斑斑血迹。

  【4】“人生目标确定容易实现难,但如果不去行动,那么连实现的可能也不会有。”

  千里之行,始于足下;不积跬步,无以至千里;不积小流,无以成江海。凡事要想做大,都得从小处做起,从眼前最基本的事物做起。如果一个人心里有远大的理想,却不愿意一步一步去努力,那他永远也不会有美梦成真的那一天。

  有个故事告诉我们行动的重要性,有一个穷和尚和一个富和尚都住在一个偏远的地方,有一天,穷和尚对富和尚说:“我想到南海去,您看怎么样?”富和尚说你凭什么去呢?穷和尚说:“一个水瓶,一个饭钵就足够了。”富和尚说:“我多年来就想租船沿长江南下,现在还没做到呢。你凭什么走?”第二年,穷和尚从南海归来,把去南海的事告诉了富和尚,富和尚深感惭愧。人生目标确定容易实现难,但如果不去行动。那么连实现的可能也不会有。没有行动的人只是在做白日梦,所以心动不如行动,勇于迈出行动的第一步,你成功的机会就会提高,而光想不做,那你将永远没有实现计划的可能。

  【5】,“人生就有许多这样的奇迹,看似比登天还难的事,有时轻而易举就可以做到,其中的差别就在于非凡的信念。”

  多年前,有一位穷苦的牧羊人领着两个年幼的儿子以替别人放羊来维持生活。一天他们赶着羊来到一个山坡,这时,一群大雁鸣叫着从他们头顶飞过,并很快消失在远处。牧羊人的小儿子问他的父亲:“爸爸,爸爸,大雁要往哪里飞?”“他们要去一个温暖的地方,在那里安家,度过寒冷的冬天。”牧羊人说。他的大儿子眨着眼睛羡慕得说:“要是我们也能像大雁那样飞起来就好了,那我就要飞得比大雁还要高,去天堂,看妈妈是不是在那里。”小儿子也对父亲说:“做个会飞的大雁多好啊,那样就不用放羊了,可以飞到自己想去的地方。”

  牧羊人沉默了一下,然后对两个儿子说:“只要你们想,你们也能飞起来。”两个儿子试了试,并没有飞起来。他们用怀疑的眼神瞅着父亲。牧羊人说,让我飞给你们看,于是他飞了两下,也没飞起来。牧羊人肯定地说:“我是因为年纪大了才飞不起来,你们还小,只要不断的努力,就一定能飞起来,去想去的地方。”儿子们牢牢地记住了父亲的话,并一直不断的努力,等到他们长大以后果然飞起来了,他们发明了飞机,他们就是美国的莱特兄弟。

  这使我坚信:一个人的内心中如果蕴涵着一个信念,并坚持不懈地为之努力,那么,他一定会是一位成功的人。

  【6】,“即使遭遇了人间最大的不幸,能够解决一切困难的前提是——活着。只有活着,才有希望。无论多么痛苦、多么悲伤,只要能够努力地活下去,一切都会好起来。”

  如果说,人生是一本书,那遗憾就是一串串省略号,空白之处,蕴含着深刻的哲理!生命赋予我们每一个人都是单程车票,我们活着就有自己的高尚和卑劣,就要享受生命的欢乐和烦恼。

  生命是上天赐予我们的特别礼物,即使陷入了绝望的泥沼中,也应该握住生命中哪怕一点点儿值得赞美的亮色,从而鼓励自己要挺住,别倒下。只要有一线希望,我们就要坚强的活下去,因为活着就会有希望。

  活着就是希望,活着就有希望。其实,世上没有绝望的处境,只有对处境绝望的人。

  告诉自己还有希望,因为自己还活着。只要活着,就有实现希望与梦想的机会……

  【7】,“真正成功的人生,不在于成就的大小,而在于你是否努力地去实现自我,喊出自己的声音,走出属于自己的道路。”

  平庸的人总是有一种幸灾乐祸的心理,因为成功者总是给他们强有力的刺激。他们总是希望成功者能够功败垂成,沦为和他们一样的平庸。人生如戏,即使今天你是炙手可热的主角,明天你可能就是一个跑龙套的。可谓是:“平步青云会有时,误杀落地未尝知。”聪明人总是用平常心应对人生中的起起伏伏,这就是一种大智慧,台上台下都能自在坚韧、淡然洒脱。

  而且聪明人都会对自己的生活设定一个标准,它不是人云亦云的标准,而是自己真正想要的标准。只要我们自己认为有意义,就一定要坚持下去。在不伤害他人和社会的情况下,当你想当的人,做你想做的事,说你想说的话……这,就是我们每个人成功的实质。

  【8】,“低头是一种能力,它不是自卑,也不是怯弱,它是清醒中的嬗变。有时,稍微低一下头,或者我们的人生路会更精彩。”

  人,心至善,情至诚,志必坚。

  人,得意不可忘形,失意不可失志。

  人,没有自尊心就不可能有耻辱感,没有耻辱感就不可能有自尊心。

  人,每说一句话,每做一件事,都有要考虑会否影响别人,会否损害别人,这是一个人最起码的素质。

  有人问过苏格拉底:“你是天下最有学问的人,那么你说天与地之间的高度是多少?”苏格拉底毫不迟疑地说:“三尺!”那人不以为然:“我们每个人都五尺高,天与地之间只有三尺,那不是戳破苍穹?”苏格拉底笑着说:“所以,凡是高度超过三尺的人,要长立于天地之间,就要懂得低头。”其实,我们的生活又何尝不是如此。自认怀才不遇的人,往往看不到别人的优秀;愤世嫉俗的人,往往看不到世界的美好;只有敢于低头并不断否定自己的人,才能够不断吸取教训,才会为别人的成功而欣喜,为自己的善解人意而自得,才会在挫折面前心安理得。

  【9】,“很多时候,不快乐并不是因为快乐的条件没有齐备,而是因为活得还不够简单,”

  幸福是什么?

  幸福不一定是腰缠万贯、高官显禄、呼风唤雨。平凡人自有平凡人的幸福,只要你懂得怎样生活,只要你不放弃对美好生活的追求,你就不会被幸福抛弃。

  快乐是什么?

  其实快乐是一件非常简单的事,快乐就在每个人的身边,可并不是每一个人都清楚这一点。“只有简单着,才能快乐着。”不奢求华屋美厦,不垂涎山珍海味,不追名逐利,不扮贵人相,过一种简朴素净的生活,才能感受到生活的快乐,一种外在的财富也许不如人、但内心充实富有的生活,这才是自然的生活。有劳有逸,有工作的乐趣,也有与家人共享天伦的温馨、自由活动的闲暇,还用去忙里偷闲吗?“世味淡,不偷闲而闲自来。”

  “简朴生活”并不是要你放弃所有的一切。实行它,必须从你的实际出发。简单生活不是自甘贫贱。你可以开一部昂贵的车子,但仍然可以使生活简化。一个基本的概念就在于你想要改进你的生活品质而已,关键是诚实地面对自己,想想生命中对自己真正重要的是什么。

  【10】,“懂得感恩,是收获幸福的源泉。懂得感恩,你会发现原来自己周围的一切都是那样的美好。”

  落叶在空中盘旋,谱写着一曲感恩的乐章,那是树对大地的感恩;白云在蔚蓝的天空飘荡,描绘着一幅幅感恩的画面,那是白云对蓝天的感恩。

  人生更是处处要感恩。

  一个人,如果常怀一颗感恩的心,那么他就会感觉到什么叫幸福,并且随时能品尝到幸福的滋味,就会更加珍惜生活中的一切,就会觉得人生是十分美好的。

  懂得感恩是获得幸福的源泉,在生活中,如果我们每个人都不忘感恩,人与人之间的关系会变得更加和谐、更加亲切。我们自身也会因为这种感恩心理的存在而变得更加健康、快乐!

  世界上只有一件事比遭人折磨还要糟糕,那就是从来不曾被人折磨过。因为,当一个人受尽折磨时,他的潜能才会被激发出来,而且,他才能越挫越勇,逼得自己去突破现状……

posted @ 2010-04-11 19:19 jadmin 阅读(78) | 评论 (0)编辑 收藏

1.活着一天,就是有福气,就该珍惜。当我哭泣我没有鞋子穿的时候,我发现有人却没有脚。

2.宁可自己去原谅别人,莫让别人来原谅你。

3.世界原本就不是属于你,因此你用不着抛弃,要抛弃的是一切的执著。万物皆为我所用,但非我所属。

4.别人可以违背因果,别人可以害我们,打我们,毁谤我们。可是我们不能因此而憎恨别人,为什么?我们一定要保有一颗完整的本性和一颗清净的心。

5.你有你的生命观,我有我的生命观,我不干涉你。只要我能,我就感化你。如果不能,那我就认命。

6.如果你准备结婚的话,告诉你一句非常重要的哲学名言「你一定要忍耐包容对方的缺点,世界上没有绝对幸福圆满的婚姻,幸福只是来自于无限的容忍与互相尊重。

7.我的财富并不是因为我拥有很多,而是我要求的很少。

8.不是某人使我烦恼,而是我拿某人的言行来烦恼自己。

9.活在别人的掌声中,是禁不起考验的人。

10.如果你能每天呐喊二十一遍「我用不着为这一点小事而烦恼」,你会发现,你心里有一种不可思议的力量,试试看,很管用的。

11.若能一切随他去,便是世间自在人。

12.感谢上苍我所拥有的,感谢上苍我所没有的。

13.来是偶然的,走是必然的。所以你必须,随缘不变,不变随缘。

14.别人永远对,我永远错,这样子比较没烦恼。

15.愚痴的人,一直想要别人了解他。有智慧的人,却努力的了解自己。

16.对于不可改变的事实,除了认命以外,没有更好的办法了。

17.其实爱美的人,只是与自己谈恋爱罢了。

18.说一句谎话,要编造十句谎话来弥补,何苦呢?

19.当你用烦恼心来面对事物时,你会觉得一切都是业障,世界也会变得丑陋可恨。

20.根本不必回头去看咒骂你的人是谁?如果有一条疯狗咬你一口,难道你也要趴下去反咬他一口吗?

21.狂妄的人有救,自卑的人没有救。

22.你什么时候放下,什么时候就没有烦恼。

23.人之所以痛苦,在于追求错误的东西。

24.与其说是别人让你痛苦,不如说是自己的修养不够。

25.命运负责洗牌,但是玩牌的是我们自己!

26.过错是暂时的遗憾,而错过则是永远的遗憾!

posted @ 2010-04-11 18:52 jadmin 阅读(89) | 评论 (0)编辑 收藏

Tomcat中,为了保证get数据采用UTF8编码,在server.xml中进行了如下设置:

<Connector port="8080" maxThreads="150" minSpareThreads="25"
maxSpareThreads="75" enableLookups="false" redirectPort="8443"
acceptCount="100" debug="99" connectionTimeout="20000"
disableUploadTimeout="true" URIEncoding="UTF-8"/>

这里指定了get时候的数据编码。但是,当使用IIS作为webserver转发servlet/jsp请求给Tomcat时候,这个设置却失效了。其实原因很简单:IIS是通过AJP协议,把请求转发到Tomcat监听的8009端口上的,所以这里针对8080的设置自然就无效了。正确的方法是进行下面的设置:

<Connector port="8009" enableLookups="false" redirectPort="8443"
debug="0" protocol="AJP/1.3" URIEncoding="UTF-8"/>

posted @ 2010-04-10 15:45 jadmin 阅读(109) | 评论 (0)编辑 收藏

  一、成熟的人不问过去;聪明的人不问现在;豁达的人不问未来。

  二、在人之上,要把人当人;在人之下,要把自己当人。

  三、知道看人背后的是君子;知道背后看人的是小人。

  四、你犯错误时,等别人都来了再骂你的是敌人,等别人都走了骂你的是朋友

  五、人只要能掌握自己,便什么也不会失去

  六、变老并不等于成熟,真正的成熟在于看透。

  七、简单的生活之所以很不容易,是因为要活的简单,一定不能想的太多。

  八、人们常犯最大的错误,是对陌生人太客气,而对亲密的人太苛刻,把这个坏习惯改过来,天下太平。

  九、我们在梦里走了许多路,醒来后发现自己还在床上。

  十、你的丑和你的脸没有关系。

  十一、航海者虽比观望者要冒更大的风险,但却有希望到达彼岸。

  十二、穷人的苦恼在于没有选择,富人的苦恼在于有太多选择。

  十三、不要总觉得被轻视,先问问自己有没有分量。

  十四、一个人的价值,不体现在与别人相同的东西上,而体现在与别人不同的东西上。

  十五、静坐常思己过,闲谈莫论人非。

  十六、发展是硬道理,但硬发展是没道理。

  十七、人们是看你做什么,不是听你说什么。

  十八、要求别人是很痛苦的,要求自己是很快乐的。

  十九、不敢生气的是懦夫,不去生气的才是智者。

  二十、对于人来说,问心无愧是最舒服的枕头。

  二十一、嫉妒他人,表明他人的成功,被人嫉妒,表明自己成功。

  二十二、有些事情,不谈是个结,谈开了是个疤。

  二十三、一口吃不成胖子,但胖子却是一口一口吃来的。

  二十四、喜欢花的人是会去摘花的,然而爱花的人则会去浇水。


  1、不要自视清高

  天外有天,人上有人,淡泊明志,宁静致远。当别人把你当领导时,自己不要把自己当领导,当别人不把你当领导时,自己一定要把自己当领导,权力是一时的,金钱是身外的,身体是自己的,做人是长久的。

  2、不要盲目承诺

  言而有信。种下行动就会收获习惯;种下习惯便会收获性格;种下性格便会收获命运——习惯造就一个人。

  3、不要轻易求人

  把自己当别人——减少痛苦、平淡狂喜,把别人当自己——同情不幸,理解需要,把别人当别人——尊重独立性,不侵犯他人,把自己当自己——珍惜自己,快乐生活。能够认识别人是一种智慧,能够被别人认识是一种幸福,能够自己认识自己是圣者贤人。

  4、不要强加于人

  人本是人,不必刻意去做人;世本是世,无须精心去处世。人生三种境界:看山是山,看水是水——人之初;看山不是山,看水不是水——人到中年;看山还是山,看水还是水——回归自然。

  5、不要取笑别人

  损害他人人格,快乐一时,伤害一生。生命的整体是相互依存的,世界上每一样东西都依赖其它另一样东西,学会感恩。感恩大自然的福佑,感恩父母的养育,感恩社会的安定,感恩食之香甜,感恩衣之温暖,感恩花草鱼虫,感恩苦难逆境。

  6、不要乱发脾气

  一伤身体,二伤感情,人与人在出生和去世中都是平等的——哭声中来,哭声中去。千万注意,自己恋恋不舍,而别人早就去意已决,人生应看三座山:井冈山普陀山八宝山,退一步海阔天空,忍一事风平浪静;牢骚太多防肠断,风物长宜放眼量。

  7、不要信口开河

  言多必失,沉默是金,倾听一种智慧,一种修养、一种尊重、一种心灵的沟通,平静是一种心态,一种成熟。

  8、不要小看仪表

  撒播美丽,收获幸福,仪表是一种心情,仪表是一种力量,在自己审视美的同时,让别人欣赏美,心灵瑜伽——调适、修炼、超越。

  9、不要封闭自己

  帮助人是一种崇高,理解人是一种豁达,原谅人是一种美德,服务人是一种快乐,月圆是诗,月缺是花,仰首是春,俯首是秋。

  10、不要欺负老实人

  同情弱者是一种品德、一种境界、一种和谐,心理健康,才能身体健康,人有一分器量,便多一分气质,人有一分气质,便多一分人缘,人有一分人缘,便多一分事业,积善成德、修身养性。

posted @ 2010-04-08 21:14 jadmin 阅读(84) | 评论 (0)编辑 收藏

      多线程的同步依靠的是对象锁机制,synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问。

      下面以一个简单的实例来进行对比分析。实例要完成的工作非常简单,就是创建10个线程,每个线程都打印从099100个数字,我们希望线程之间不会出现交叉乱序打印,而是顺序地打印。

      先来看第一段代码,这里我们在run()方法中加入了synchronized关键字,希望能对run方法进行互斥访问,但结果并不如我们希望那样,这是因为这里synchronized锁住的是this对象,即当前运行线程对象本身。代码中创建了10个线程,而每个线程都持有this对象的对象锁,这不能实现线程的同步。

class MyThread implements java.lang.Runnable

{

   private int threadId;

   public MyThread(int id)

   {

       this.threadId = id;

   }

   

   public synchronized void run()

   {

       for (int i = 0; i < 100; ++i)

       {

           System.out.println("Thread ID: " + this.threadId + " : " + i);

       }

   }

}

public class ThreadDemo

{

   public static void main(String[] args) throws InterruptedException

   {

       for (int i = 0; i < 10; ++i)

       {

           new Thread(new MyThread(i)).start();

           Thread.sleep(1);

       }

   }

}

      从上述代码段可以得知,要想实现线程的同步,则这些线程必须去竞争一个唯一的共享的对象锁。

      基于这种思想,我们将第一段代码修改如下所示,在创建启动线程之前,先创建一个线程之间竞争使用的Object对象,然后将这个Object对象的引用传递给每一个线程对象的lock成员变量。这样一来,每个线程的lock成员都指向同一个Object对象。我们在run方法中,对lock对象使用synchronzied块进行局部封锁,这样就可以让线程去竞争这个唯一的共享的对象锁,从而实现同步。

class MyThread implements java.lang.Runnable

{

   private int threadId;

   private Object lock;

   public MyThread(int id, Object obj)

   {

       this.threadId = id;

       this.lock = obj;

   }

   

   publicvoid run()

   {

       synchronized(lock)

       {

           for (int i = 0; i < 100; ++i)

           {

               System.out.println("Thread ID: " + this.threadId + " : " + i);

           }

       }

   }

}

public class ThreadDemo

{

   public static void main(String[] args) throws InterruptedException

   {

       Object obj = new Object();

       for (int i = 0; i < 10; ++i)

       {

           new Thread(new MyThread(i, obj)).start();

           Thread.sleep(1);

       }

   }

}

      从第二段代码可知,同步的关键是多个线程对象竞争同一个共享资源即可,上面的代码中是通过外部创建共享资源,然后传递到线程中来实现。我们也可以利用类成员变量被所有类的实例所共享这一特性,因此可以将lock用静态成员对象来实现,代码如下所示:

class MyThread implements java.lang.Runnable

{

   private int threadId;

   private static Object lock = new Object();

   public MyThread(int id)

   {

       this.threadId = id;

  }

   publicvoid run()

   {

       synchronized(lock)

       {

           for (int i = 0; i < 100; ++i)

           {

               System.out.println("Thread ID: " + this.threadId + " : " + i);

           }

       }

   }

}

public class ThreadDemo

{

   public static void main(String[] args) throws InterruptedException

   {

       for (int i = 0; i < 10; ++i)

       {

           new Thread(new MyThread(i)).start();

           Thread.sleep(1);

       }

   }

}

      再来看第一段代码,实例方法中加入sychronized关键字封锁的是this对象本身,而在静态方法中加入sychronized关键字封锁的就是类本身。静态方法是所有类实例对象所共享的,因此线程对象在访问此静态方法时是互斥访问的,从而可以实现线程的同步,代码如下所示:

class MyThread implements java.lang.Runnable

{

   private int threadId;

   

   public MyThread(int id)

   {

       this.threadId = id;

   }

   

   publicvoid run()

   {

       taskHandler(this.threadId);

   }

   private static synchronized void taskHandler(int threadId)

   {

       for (int i = 0; i < 100; ++i)

       {

           System.out.println("Thread ID: " + threadId + " : " + i);

       }

   }

}

public class ThreadDemo

{

   public static void main(String[] args) throws InterruptedException

   {

       for (int i = 0; i < 10; ++i)

       {

           new Thread(new MyThread(i)).start();

           Thread.sleep(1);

       }

   }

}

Java线程同步总结:

1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

posted @ 2010-03-18 01:43 jadmin 阅读(359) | 评论 (1)编辑 收藏

你站在桥上看风景
看风景的人在楼上看你

明月装饰了你的窗子
你装饰了别人的梦

posted @ 2010-03-16 22:56 jadmin 阅读(95) | 评论 (0)编辑 收藏

有时候将自己的程序打包成jar文件作为类库调用,出错时,遇到自己的jar包里的类文件报异常信息时,无法打印出行号,而是(Unknown Source)

解决办法:在编译任务中加上如下参数debug="true" debuglevel="lines,vars,source"

例如:

<target name="compile" depends="prepare" description="编绎源码">
   <javac encoding="utf-8" destdir="${build.dir}" source="1.5" target="1.5"
        deprecation="false" optimize="false" failonerror="true"
        debug="true" debuglevel="lines,vars,source">
     <src refid="src-paths" />
     <classpath refid="lib-paths" />
   </javac>

   <copy todir="${build.dir}" preservelastmodified="true">
     <fileset dir="${src.dir}">
       <include name="**/*.txt"/>
       <include name="**/*.xml"/>
       <include name="**/*.dtd"/>
       <include name="**/*.properties"/>
     </fileset>
   </copy>
</target>

posted @ 2010-03-14 20:26 jadmin 阅读(139) | 评论 (0)编辑 收藏

CVS介绍

CVS是Concurrent Versions System(并发版本系统)的缩写,基于Unix体系中成熟的SCCS(Source Code Control System)和RCS(Revision Control System)开发,是一个开放源码的项目,目前已是版本控制系统的主流软件。一个很常见的使用CVS的场合,就是开放源码项目。由于开放源码项目的开发者的分布性,对于版本管理的要求更加严格,而目前大部分的开放源码项目几乎都是采用CVS来管理源代码,CVS的标准性和强大可见一斑。CVS跟微软的VSS一样,是源代码版本控制工具之一,所不同的是CVS属于开源项目,并且CVS最初是为Linux/Unix设计的,现在已经有Windows下版本,它实现了跨平台。

CVS采用客户机/服务器体系,代码以及各种版本存储在中心服务器内,每一个个体开发者开发时都首先从服务器上获得一份自己的拷贝,在此基础上进行开发,以避免直接影响服务器上的数据。开发者可以随时把自己的新代码提交给服务器,并通过更新获得代码的最新状态,保持与其他开发者的一致。

CVS对于网络是透明的,开发者可以使用客户端软件(几乎所有的平台上都有相应的客户端软件)在任何时候,任何地点通过网络来获取最新的代码。有关Linux下CVS的搭建的资料很多而且很全,在这里就不再介绍。下面主要介绍一下个人在Windows下面搭建CVS的一个过程,或许对你有一定的帮助。

一、需要的软件
1、CVSNT
CVSNT是目前在Windows平台上构建CVS服务器最方便的工具,操作简单,以Windows服务程序的形式运行。目前CVSNT已被移植到其它平台如Linux上。
选用版本:2.0.58d。
在这里我选用这个版本而不采用最新的2.5.x的原因主要是因为2.5.x版本的CVS改变了History文件的格式,导致无法使用CVSTracNT一起工作。但是如果你不使用CVSTracNT,选用2.5.x也一样,其操作与2.0.58d基本相同,也是十分的简单。
CVSNT可以直接到官方站点下载:http://www.cvsnt.org/

2、WinCVS
WinCVS是CVS的一个客户端程序,当你创建了一个CVS服务器以后,你可以通过WinCVS来访问CVS服务器,添加数据以及获取CVS服务器上的文件等。WinCVS同样拥有Windows版本。
选用版本:2.0.2 (Build2)
客户端程序版本无所谓,只要与服务器端兼容即可。CVS访问协议有多种,我们经常用的是pserver,格式是::pserver;username=youname;password=youpass;hostname=192.168.1.22:/cvsroot注意,这里的用户名以及密码是你访问服务器的用户名以及密码,对于Windows服务器来讲,可以在计算机管理中添加用户,最后面的cvsroot是CVS服务器数据仓库的根路径,与服务器设置要保持一致。
官方下载地址:http://www.wincvs.org

3、Python
要想让WinCVS运行起来并能够支持命令行操作,必不可少的就是Python(包含TCL)。
选用版本:2.4.1
在运行CVS客户端程序的时候,需要Python支持,可以运行WinCVS在命令行中进行操作版本选择原则是与WinCVS兼容。
官方下载地址:http://www.python.org/

4、CVSTracNT
除了安装CVS的服务器端以及客户端程序外,在项目管理中我们还一般会配套安装CVSTracNT,它是一个基于Web的CVS源代码跟踪工具。使用CVSTracNT,我们可以跟踪源代码的变更,可以查看提交的记录,可以对比不同版本之间的异同,允许提交任务单、创建里程碑,并通过Web的方式查看、比较源代码文件。对于CVS系统本身来说,是一个非常有益的补充,对开发起到很大的帮助。
选用版本:1.1.5 Build20050703多国语言版。
这个版本由cnpack项目组的成员进行了汉化,并添加了许多实用的工具。具体的内容可以参考其中文网站。
官方下载地址:http://www.cnpack.org/

二、安装过程
1、安装CVSNT
直接运行CVSNT安装包。安装结束后,打开Service Control Panel,配置你的CVS仓库。点击Repositories选项页,添加你的仓库地址就可以了,不需要重新启动CVS服务器。这里注意的是,你选择的数据仓库路径是你CVS服务器存储数据的路径,并不是你需要用来管理的文件目录。我们可以指定一个目录用于存储。

2、安装WinCVS
直接运行安装文件,按照提示Next下去即可。安装完成以后使用分配的帐号登陆CVS服务器进行相应的操作。详细使用可以参考WinCVS帮助文档。

3、安装Python
直接运行安装文件。注意要选择以前装TCL(使用命令行操作WinCVS时需要TCL的支持),其他一路Next。记得最后在PATH中添加一个Python的安装目录。

4、安装CVSTracNT
直接运行CVSTracNT安装包。安装结束后运行CVSTrac配置程序,程序启动后会自动搜索CVS仓库,将其添加到CVSTrac数据库。选中数据库,点击浏览按钮,你的浏览器应该打开并显示CVSTrac的页面了。
CVSTracNT的使用及配置参考 http://www.cnpack.org/ 上的介绍。

三、总结
以上的搭配过程是本人自己第一次搭建的写照,很多地方可能还不是十分的完整,以后会不断补充。但是基本的都做完了,而且测试通过。在安装过程中刚开始的时候可能对CVS帐号的分配会比较迷惑。本人也是通过本次安装才知道账号添加试通过计算机管理来实现的,就是添加Windows用户 :-) 到此为止,一个简单的CVS环境就培植成功了,接下来的事情是如何添加文件到CVS数据库中以及如何使用WinCVS进行获取文件了。

posted @ 2010-03-14 02:26 jadmin 阅读(104) | 评论 (0)编辑 收藏

[1]单击 工具 菜单中的 选项 菜单项,在弹出的 选择 对话框中选择 拼写和语法 选项卡

[2]在 拼写 选项组中取消 键入时检查拼写 复选框,在 语法 选项组中取消 键入时检查语法。

[3]选中 拼写 选项组中的 隐藏文档中的拼写错误 复选框和 语法 选项组中的 隐藏文档中的语 法错误 复选框。 (此步骤实际上在[2]完成后就已经完成了)

[4]确定

以上方法在Word 2003中测试通过

posted @ 2010-03-12 01:36 jadmin 阅读(91) | 评论 (0)编辑 收藏

  化冰为水

  春已至,冰成水。

  寒冬,将水凝。一道冰墙,阻挡在心与心之间。

  冰“水为之而寒于水”,连同杂质都冻成心墙。冰在零下便不可一世,低温是他滋生的温床,而人与人之间的淡漠使寒气更重。

  一堵心的冰墙,看似无坚不摧无法跨过,但,不要忽视了一样非常极其有用的东西——热。当冰度不断加热,直至温度不断升高,它就会屈服,融化,化成一滩水,有温度的柔水。杂质便会显露,沉入水底。但,水亦可灭火,冰化成水时会吸取大量的热,会浇灭火。因而,包容的烧杯便会出现,将冰化成的水积入杯中,一点一滴。

  人与人之间的种种不愉快便是杂质,其种类繁多,仇怨恨等等,而主体冰则是相互之间的不沟通和不包容。一旦冷漠加剧,心温下降,便会结成冰墙,立在两人心与心之间不远的地方,只要稍微给一点热情,便会化成水,失去靠山的杂质便会随水流走,洗净心灵。倘若,心墙很厚,杂质很多,冷漠很多,心就会很冷,人与人阻隔在冰川之中,犹如南极大陆,感觉到的只是冷,看到的除了冰还是冰。

  心被冰封在冰层之中,渐渐死去。没有了知觉的心,没有心的人能够称为人吗所以,心必须吸收温暖,必须从阴暗中走出来,去晒一晒太阳。

  阳春,冰墙在阳光之下还能放肆吗只能逐渐消亡,化成一股暖流,在心中汇聚成河,各种各样的鱼虾于其中欢快,这就是真正的人生。

  冬天,寒冷是不可避免的,但只要心中有春,最寒冷的南极也不会冻心。

posted @ 2010-03-06 21:46 jadmin 阅读(68) | 评论 (0)编辑 收藏

java异常[java.util.regex.patternsyntaxexception dangling meta character '+' near index]解决

String s="/babalaautomgr.ejs?method=constr";
int i=s.split("?").length;
System.out.println(i);

java.util.regex.PatternSyntaxException: Dangling meta character '?' near index 0

问题出现在加号附近,查询相关的资料显示,+、*、|、\等符号在正则表达示中有相应的不同意义。
一般来讲只需要加[]、或是\\即可

int i=s.split("[?]").length;或者int i=s.split("\\?").length;

posted @ 2009-11-02 20:57 jadmin 阅读(1297) | 评论 (0)编辑 收藏


首先给出一段XML格式文本信息:
<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
    <book>
        <title lang="eng">Harry Potter</title>
<price>29.99</price>
    </book>
    <book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
    </book>
</bookstore>


表达式     描述
节点名     选择所有该名称的节点集
/              选择根节点
//             选择当前节点下的所有节点
.              选择当前节点
..             选择父节点
@            选择属性

示例
表达式                  描述
bookstore             选择所有bookstore子节点
/bookstore            选择根节点bookstore
bookstore/book    在bookstore的子节点中选择所有名为book的节点
//book                   选择xml文档中所有名为book的节点
bookstore//book   选择节点bookstore下的所有名为book为节点
//@lang                 选择所有名为lang的属性

断言
在方括号中[],用来更进一步定位选择的元素
表达式                                             描述
/bookstore/book[1]                          选择根元素bookstore的book子元素中的第一个(注意: IE5以上浏览器中第一个元素是0)
/bookstore/book[last()]                    选择根元素bookstore的book子元素中的最后一个
/bookstore/book[last()-1]                 选择根元素bookstore的book子元素中的最后第二个
/bookstore/book[position()<3]         选择根元素bookstore的book子元素中的前两个
//title[@lang]                                    选择所有拥有属性lang的titile元素
//title[@lang='eng']                          选择所有属性值lang为eng的title元素
/bookstore/book[price>35.00]         选择根元素bookstore的book子元素中那些拥有price子元素且值大于35的
/bookstore/book[price>35.00]/title   选择根元素bookstore的book子元素中那些拥有price子元素且值大于35的title子元素

选择位置的节点
通配符      描述
*                匹配所有元素
@*             匹配所有属性节点
node()       匹配任何类型的节点
示例
表达式                    描述
/bookstore/*            选择根元素bookstore的下的所有子元素
//*                            选择文档中所有元素
//title[@*]                 选择所有拥有属性的title元素

使用操作符“|”组合选择符合多个path的表达式

posted @ 2009-11-02 20:55 jadmin 阅读(115) | 评论 (0)编辑 收藏
一个后台应用程序,使用了Spring+iBatis框架。
有这样的需求,要求程序启动后,要一直驻留内存,而不能因为出现数据库连接失效、“闪动”、或者网线断了而挂起,因为没有人值守程序,并且当网络故障、数据库故障、配置参数等故障排除后,程序能根据修复的新状态继续执行。
实现方式:以前使用Linux操作系统的shell脚本定时检测,但是俺不会写shell脚本。
于是有了下面的实现方式:
        public static voidmain(String[] args) {
                while(true) {
                        try{
                                 ctx = ApplicationContextUtil.getApplicationContext();
                                 IssuePlan issuePlan = (IssuePlan) ctx.getBean("issuePlan");
                                 issuePlan.execute();
                         }catch(Throwable e) {
                                 log.error("网络视频节目分发程序启动发生了严重错误!", e);
                                try{
                                         Thread.sleep(pause_timespan * 1000L);
                                 }catch(InterruptedException e1) {
                                 }
                         }
                 }
         }
这种方式运行良好,每次因为严重错误都会重新初始化Spring的ApplicationContext。这样,整个程序的运行就是:一直执行任务,有任务就执行,没任务休息一段时间,有错误等待一段时间重试,没错误继续。
issuePlan.execute(); 是核心的后台任务执行者,这个方法在正常情况下是不会退出的,写法是while(true)逻辑,只有当发生一些严重错误会导致此方法发生异常退出。
 

posted @ 2009-10-01 16:05 jadmin 阅读(144) | 评论 (0)编辑 收藏

Ehcache缓存回收策略

posted @ 2009-09-05 00:33 jadmin 阅读(135) | 评论 (0)编辑 收藏

1.Cache Hit and Cache Miss

当使用者第一次向数据库发出查询数据的请求的时候,数据库会先在缓冲区中查找该数据,如果要访问的数据恰好已经在缓冲区中(我们称之为Cache Hit)那么就直接用缓冲区中读取该数据.

反之如果缓冲区中没有使用者要查询的数据那么这种情况称之为Cache Miss,在这种情况下数据库就会先从磁盘上读取使用者要的数据放入缓冲区,使用者再从缓冲区读取该数据.

很显然从感觉上来说Cache Hit会比Cache Miss时存取速度快.

2. LRU(最近最少使用算法) and MRU(最近最常使用算法)

所谓的LRU(Least recently used)算法的基本概念是:当内存的剩余的可用空间不够时,缓冲区尽可能的先保留使用者最常使用的数据,换句话说就是优先清除较不常使用的数据”,并释放其空间.之所以较不常使用的数据要用引号是因为这里判断所谓的较不常使用的标准是人为的、不严格的.所谓的MRU(Most recently used)算法的意义正好和LRU算法相反.

下面我们通过Oracle 9i Cache中对LRUMRU的使用来看一下两者在缓冲区工作机制中的作用和区别:

     Oracle 9i中有LRU List的概念: 我们可以把LRU List想象成是一连串的缓冲区集合,两端分别是LRUMRU, 当数据库从磁盘上读取数据放入缓冲区时,系统必须先确定缓冲区中有free buffers,这个时候Oracle 9i会扫描LRU List,扫描的基本原则是:

1.     LRUMRU;

2.     当扫描到free buffer或已扫描的缓冲区数目超过临界值时,就会停止扫描动作;

      如果在扫描过程顺利的在LRU List中找到了free buffer,那么Oracle 9i就把从磁盘读出的数据写到free buffer中然后把free buffer加到LRU ListMRU.

      那如果扫描过程没有在LRU List中找到free buffer怎么办?当然是从LRU ListLRU开始清除缓冲区,如此一来就可以腾出新的空间了.

      下图就是一个例子:

          使用者查询数据A,初始的时候LRU List中没有数据A,于是Oracle 9i到磁盘读取A,然后放到LRU ListMRU,使用者再从LRU List中读取数据A,同理对于B,C…LRU List满了以后,如果使用者查询N,此时N不在LRU List中而且LRU List中已经没有free buffer,此时Oracle 9i就开始从LRU淘汰A以腾出空间存放N.

                      1

我们再来看另外一种情况:

    State 3之后,恰好使用者持续的查询A—这将会导致A一直被放置在靠近MRU的缓冲区,结果将如图State m’所示,你会发现图2State m’与图1State m缓冲区存放的数据完全一样但是存放位置不一样.此时LRU List满了,如果再放N的时候LRU List`淘汰的是B,因为A的查询率高于B,所以LRU ListA在缓冲区中呆上较长的时间而先淘汰掉较不常用的B.

                             2

posted @ 2009-09-05 00:27 jadmin 阅读(156) | 评论 (0)编辑 收藏

[A] 每次读入的字节数不同
[B] 前者带有缓冲,后者没有
[C] 前者是块读写,后者是字节读写
[D] 二者没有区别,可以互换使用

结果:[C]

posted @ 2009-09-03 21:29 jadmin 阅读(806) | 评论 (0)编辑 收藏

比如现在有一人员表(表名:peosons) 若想将姓名、身份证号、住址这三个字段完全相同的记录查询出来
select p1.* from persons p1,persons p2
where p1.idp2.id and p1.cardid = p2.cardid and p1.pname = p2.pname and p1.address = p2.address 可以实现上述效果.

几个删除重复记录的SQL语句

1.用rowid方法

2.用group by方法

3.用distinct方法

1.用rowid方法

据据oracle带的rowid属性,进行判断,是否存在重复,语句如下:
查数据:
     select * from table1 a where rowid !=(select   max(rowid)
     from table1 b where a.name1=b.name1 and a.name2=b.name2......)
删数据:
    delete   from table1 a where rowid !=(select   max(rowid)
     from table1 b where a.name1=b.name1 and a.name2=b.name2......)

2.group by方法

查数据:
  select count(num), max(name) from student --列出重复的记录数,并列出他的name属性
  group by num
  having count(num) >1 --按num分组后找出表中num列重复,即出现次数大于一次
删数据:
  delete from student
  group by num
  having count(num) >1
  这样的话就把所有重复的都删除了。

3.用distinct方法 -对于小的表比较有用

create table table_new as   select distinct *   from table1 minux
truncate table table1;
insert into table1 select * from table_new;


查询及删除重复记录的方法大全

1、查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断
select * from people
where peopleId in (select peopleId from people group by peopleId having count(peopleId) > 1)


2、删除表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断,只留有rowid最小的记录
delete from people
where peopleId in (select peopleId from people group by peopleId   having count(peopleId) > 1)
and rowid not in (select min(rowid) from people group by peopleId having count(peopleId )>1)


3、查找表中多余的重复记录(多个字段)
select * from vitae a
where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)


4、删除表中多余的重复记录(多个字段),只留有rowid最小的记录
delete from vitae a
where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)
and rowid not in (select min(rowid) from vitae group by peopleId,seq having count(*)>1)


5、查找表中多余的重复记录(多个字段),不包含rowid最小的记录
select * from vitae a
where (a.peopleId,a.seq) in (select peopleId,seq from vitae group by peopleId,seq having count(*) > 1)
and rowid not in (select min(rowid) from vitae group by peopleId,seq having count(*)>1)


(二)
比方说
在A表中存在一个字段“name”,
而且不同记录之间的“name”值有可能会相同,
现在就是需要查询出在该表中的各记录之间,“name”值存在重复的项;
Select Name,Count(*) From A Group By Name Having Count(*) > 1
如果还查性别也相同大则如下:
Select Name,sex,Count(*) From A Group By Name,sex Having Count(*) > 1


(三)
方法一
declare @max integer,@id integer
declare cur_rows cursor local for select 主字段,count(*) from 表名 group by 主字段 having count(*) >; 1
open cur_rows
fetch cur_rows into @id,@max
while @@fetch_status=0
begin
select @max = @max -1
set rowcount @max
delete from 表名 where 主字段 = @id
fetch cur_rows into @id,@max
end
close cur_rows
set rowcount 0


方法二
"重复记录"有两个意义上的重复记录,一是完全重复的记录,也即所有字段均重复的记录,二是部分关键字段重复的记录,比如Name字段重复,而其他字段不一定

重复或都重复可以忽略。


  1、对于第一种重复,比较容易解决,使用
select distinct * from tableName
  就可以得到无重复记录的结果集。
  如果该表需要删除重复的记录(重复记录保留1条),可以按以下方法删除
select distinct * into #Tmp from tableName
drop table tableName
select * into tableName from #Tmp
drop table #Tmp
  发生这种重复的原因是表设计不周产生的,增加唯一索引列即可解决。


  2、这类重复问题通常要求保留重复记录中的第一条记录,操作方法如下
  假设有重复的字段为Name,Address,要求得到这两个字段唯一的结果集
select identity(int,1,1) as autoID, * into #Tmp from tableName
select min(autoID) as autoID into #Tmp2 from #Tmp group by Name,autoID
select * from #Tmp where autoID in(select autoID from #tmp2)
  最后一个select即得到了Name,Address不重复的结果集(但多了一个autoID字段,实际写时可以写在select子句中省去此列)


(四)
查询重复
select * from tablename where id in (
select id from tablename
group by id
having count(id) > 1
)

posted @ 2009-09-03 16:50 jadmin 阅读(156) | 评论 (0)编辑 收藏

原题如下:用1、2、2、3、4、5这六个数字,用java写一个程序,打印出所有不同的排列,如:512234、412345等,要求:"4"不能在第三位,"3"与"5"不能相连。

解题思路:

很明显,这是一个递归算法。我们可以排列将这6个数按从小到大的顺序排一下,如果是1,2,3,4,5,6,那么会有1*2*3*4*5*6=6!=720个递增的数。但如果是1,2,2,3,4,5,那么在这720个数中一定会有相同的数对出现(由于在这6个数中只有两个数两同,也就是说,如果有重复的数,那么一定是一对数,如122345会出现两次)。

排列的基本规则是分步进行。也就是说,要排列上面6个数,首先应该选择第一个数,这第一个数可以选择这6个数中的任意一个,如选择1.第二步是选择第二个数,这第二个数不能再选择已经选过的数,如1.因此,它只能从后面5个数中选择。如选择2。以此类推。

我们也可以在程序中模拟这一过程。源程序如下:

public class test1 {
     private int[] numbers = new int[] { 1, 2, 3, 3, 4, 5 };
     public int n;
     private String lastResult = "";
     private boolean validate(String s) {
          if (s.compareTo(lastResult) <= 0)
               return false;
          if (s.charAt(2) == '4')
               return false;
          if (s.indexOf("35") >= 0 || s.indexOf("53") >= 0)
               return false;
          return true;
     }

     public void list(String index, String result) {
          for (int i = 0; i < numbers.length; i++) {
               if (index.indexOf(i + 48) < 0) {
                      String s = result + String.valueOf(numbers[i]);
                      if (s.length() == numbers.length) {
                           if (validate(s)) {
                                System.out.println(s);
                                lastResult = s;
                                n++;
                           }
                          break;
                      }
                      list(index + String.valueOf(i), s);
               }
          }
     }

     public static void main(String[] args) {
          test1 t = new test1();
          t.list("", "");
          System.out.println("总数:" + t.n);
     }
}

其中list函数是这个算法的核心函数。index参数表示已经选择过的数,用numbers数组的索引表示。如index="012",表示numbers的前三个数已经被选择,也表示应该选择第四个数了,而这第四个数应该从后三个数中选择。result参数表示临时的数字组合(这个数字组合最多是5个数字,因为,如果到了6个数字,就表示已经有一个结果产生了)。在默认情况下index和result的值都是""。

在validate中使用了 if (s.compareTo(lastResult) <= 0)进行判断,由于按这种方法进行排列,如果这6个数是递增给出的,那么排列的结果一定是递增的,但上述的6个数其中第2和第3个位置上都是2,因此,如果出现了上一个结果不小于当前结果的情况,一定是有重复了,因此,要将这部分数过滤出去。

使用1, 2, 2, 3, 4, 5的测试结果
122345
122543
123245
123254
123425
123452
125234
125243
125423
125432
132245
132254
132425
132452
132524
132542
142325
... ...
... ...
542313
542331
543123
543132
543213
543231
543312
543321
总数:118

使用1, 3, 3, 3, 4, 5的测试结果

133345
313345
315433
331345
331543
333145
333154
333415
333451
343315
345133
433315
451333
513334
513343
513433
541333
543133
543313
543331
总数:20

posted @ 2009-09-03 01:39 jadmin 阅读(93) | 评论 (0)编辑 收藏

作用域将对Bean的生命周期和创建方式产生影响.

singleton            在spring IOC容器中仅存在一个Bean实例,Bean以单实例的方式存在.


prototype            每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean()的操作.


request               每次HTTP请求都会创建一个新的Bean,该作用域仅适用于webApplicationContext环境.


session               同一个HTTP session共享一个Bean,不同HTTP session使用不同的Bean,该作用域仅适用于webApplicationContext环境.


globalSession   同一个全局session共享一个Bean,一般用于portlet应用环境,该作用域仅适用于webApplicationContext环境.

在低版本的spring中,由于只有两个Bean作用域,所以采用singleton="true|false"的配置方式,spring2.0为了向后兼容,依旧支持这种配置方式.不过,spring2.0推荐采用新的配置方式:scope="<作用域类型>"

-------------------------------------------------

singleton作用域


spring以容器的方式提供天然的单实例模式功能,任何POJO无须编写特殊的代码仅通过配置就可以了.

注意:spring将Bean的默认作用域定为singleton.

singleton例:

<bean id="car" class="com.baobaotao.scope.Car" scope="singleton"/>

<bean id="boss1" class="com.baobaotao.scope.Boss">

<property name="car" ref="car"/>

</bean>

Car Bean声明为singleton(因为默认是singleton,所以可以不显式指定).

在默认情况下,spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中.

虽然启动时会花费一些时间,但带来两个好处:首先对Bean提前的实例化操作会及早发现一些潜在的配置问题.

其次Bean以缓存的方式保存,当运行时使用到该Bean时就无须再实例化了,加快了运行效率.如果用户不希望在容

器启动时提前实例化singleton的Bean,可以通过lazy-init属性进行控制:

<bean id="boos1" class="com.baobaotao.scope.Boss" lazy-init="true">

<property name="car" ref="car"/>

</bean>

lazy-init="true"的Bean在某些情况下依旧会提前实例化:如果该Bean被其它需要提前实例化的Bean引用到,

spring也将忽略延迟实例化的设置.

-------------------------------------------------

prototype作用域

采用scope="prototype"指定非单实例作用域Bean,请看:

<bean id="car" class="com.baobaotao.scope.Car" scope="prototype"/>

<bean id="boss1" class="com.baobaotao.scope.Boss">

<property name="car" ref="car"/>

</bean>

<bean id="boss2" class="com.baobaotao.scope.Boss">

<property name="car" ref="car"/>

</bean>

boss1,boss2所引用的都是一个独立的Car实例.

在默认情况下,spring容器在启动时不实例化prototype的Bean.此外,spring容器将prototype的Bean交给调用

者后,就不再管理它的生命周期.

-------------------------------------------------

web应用环境相关的Bean作用域

如果用户使用spring的webApplicationContext,则可以使用另外3种Bean的作用域:request,session和globalSession.不过

在使用这些作用域之前,首先必须在web容器中进行一些额外的配置,在高版本的web容器中,则可以利用HTTP请求监听器进行配置:

<web-app>

...

<listener>

<listener-class>

org.springframework.web.context.request.RequestContextListener

</listener-class>

</listener>

...

</web-app>

细心的朋友可能有一个疑问:在介绍webApplicationContext初始化时,我们已经通过ContextLoaderListener将web容器与

spring容器整合,为什么这里又要引入一个额外的RequestContextListener以支持Bean的另外3个作用域呢?

在整合spring容器时使用ContextLoaderListener,它实现了ServletContextListener监听器接口,ServletContextListener

只负责监听web容器启动和关闭的事件.而RequestContextListener实现ServletRequestListener监听器接口,该监听器监听

HTTP请求事件,web服务器接收的每一次请求都会通知该监听器.

spring容器启动和关闭操作由web容器的启动和关闭事件触发,但如果spring容器中的Bean需要request,session,globalsession

作用域的支持,spring容器本身就必须获得web容器的HTTP请求事件,以HTTP请求的事件"驱动"Bean作用域的控制逻辑.

request作用域

顾名思义,request作用域的Bean对应一个HTTP请求和生命周期,考虑下面的配置:

<bean name="car" class="com.baobaotao.scope.Car" scope="request"/>

这样,每次HTTP请求调用到car Bean时,spring容器创建一个新的Car Bean,请求处理完毕后,销毁这个Bean.

session作用域

假设将以上car的作用域调整为session类型:

<bean name="car" class="com.baobaotao.scope.Car" scope="session"/>

这样配置后,car Bean的作用域横跨整个HTTP session,session中所有HTTP请求都共享同一个Car Bean,当HTTP Session结束后,实例

才被销毁.

globalSession作用域

下面的配置片断将car的作用域设置为了globalSession:

<bean name="loginController" class="com.baobaotao.scope.Car" scope="globalSession"/>

globalSession作用域类似于session作用域,不过仅在portlet的web应用中使用.Portlet规范定义了全局Session概念,它被组成portlet

web应用的所有子portlet共享.如果不在Portlet web应用环境下,globalSession自然等价于session作有域了.

posted @ 2009-08-31 14:48 jadmin 阅读(138) | 评论 (0)编辑 收藏


15 = 15
15 = 7 + 8
15 = 4 + 5 + 6
15 = 1 + 2 + 3 + 4 + 5

首先考虑一般的形式,设n为被划分的正整数,x为划分后最小的整数,如果n有一种划分,那么


结果就是x,如果有两种划分,就是x和x x + 1, 如果有m种划分,就是 x 、x x + 1 、 x x + 1 x + 2 、... 、x x + 1 x + 2 ... x + m - 1


将每一个结果相加得到一个公式(i * x + i * (i - 1) / 2) = n,i为当前划分后相加的正整数个数。


满足条件的划分就是使x为正整数的所有情况。


如上例,当i = 1时,即划分成一个正整数时,x = 15, 当i = 2时, x = 7。


当x = 3时,x = 4, 当x = 4时,4/9,不是正整数,因此,15不可能划分成4个正整数相加。


当x = 5时,x = 1。

Java代码

public static int split(int n) {
   int m = 0, x, t1, t2;
   for (int i = 1; (t1 = i * (i - 1) / 2) < n; i++) {
    t2 = (n - t1);
    x = t2 / i;
    if (x <= 0)
     break;
    if ((n - t1) % i == 0) {
     System.out.print(x + " ");
     for (int j = 1; j < i; j++) {
      System.out.print(x + j + " ");
     }
     System.out.println();
     m++;
    }
   }
   return m;
}

posted @ 2009-08-29 02:28 jadmin 阅读(103) | 评论 (0)编辑 收藏

private static int numOfZero(int n) {
   int count = 0;
   int data = 1;

   for (int i = 1; i <= n; i++) {
    data = data * i;
    while (data % 10 == 0) {
     count++;
     data = data / 10;
    }
    data = data % 10;// 只保留个位数字,因其它位数字对0的个数无影响
   }
   return count;
}

posted @ 2009-08-21 00:45 jadmin 阅读(89) | 评论 (0)编辑 收藏

Java代码实现

/**
* 查找两个串的最大公共子串
*
* @param s1
* @param s2
* @return
*/
public static String commonMaxSubString(String s1, String s2) {
   String maxstr = "";
   String substring = "";
   if (s1.length() > s2.length()) { // s1为两个串中的短串;s2为长串
    String temp = s1;
    s1 = s2;
    s2 = temp;
   }
   int len = s1.length();
   ok: for (int i = len; i > 0; i--) {
    for (int j = 0; j < len - i + 1; j++) {
     substring = s1.substring(j, j + i);
     if (s2.indexOf(substring) != -1) {
      maxstr = substring;
      break ok; // 只要一找到最大子串,就退出这个for循环
     }
    }
   }
   return maxstr;
}

posted @ 2009-08-20 23:42 jadmin 阅读(76) | 评论 (0)编辑 收藏

通常交换两个整型变量,经常会如下做:

public void swap(int a, int b) {
    int t = a;// 使用第三个变量t

    a = b;

    b = t;
}

下面介绍两种不借助变量,让两个整型变量交换的方法

方法一:借助代数运算

public void swap(int a, int b) {
     a = a + b;

     b = a - b; // 这个时候a=a+b,b=a

     a = a - b; // a = b
}

方法二:借助^异或运算

public void swap(int a, int b) {
     a = a^b;

     b = a^b;

     a = a^b;
}

posted @ 2009-08-20 23:36 jadmin 阅读(85) | 评论 (0)编辑 收藏

OGNL中的#%$符号

#%$符号在OGNL表达式中经常出现,而这三种符号也是开发者不容易掌握和理解的部分。在这里笔者简单介绍它们的相应用途。


1
#符号

#符号的用途一般有三种。
>>>
访问非根对象属性,例如示例中的#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀。实际上,#相当于ActionContext. getContext()#session.msg表达式相当于ActionContext.getContext().getSession(). getAttribute("msg")
>>>

用于过滤和投影(projecting)集合,如示例中的persons.{?#this.age>20}
>>>

用来构造Map,例如示例中的#{'foo1':'bar1', 'foo2':'bar2'}

2%符号

%符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值。如下面的代码所示:

构造Map

The value of key "foo1" is 
不使用%:
使用%:

运行界面如图8.4所示。

8.4 “%”OGNL表达式用法

3$符号

$符号主要有两个方面的用途。
>>>
在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}${max}之间。
>>>
Struts 2框架的配置文件中引用OGNL表达式,例如下面的代码片断所示:

 
10
100
BAction-test
校验:数字必须为${min}${max}之间!


posted @ 2009-08-15 17:34 jadmin 阅读(93) | 评论 (0)编辑 收藏

<?xml:namespace prefix = st1 />1.1.1 业务控制器

为本示例建立一个业务控制器,该控制器用到了代码8.1中定义的Person人员信息类。该业务控制器如代码8.4所示。

代码8.4 Struts 2OGNL示例业务控制器

package ch8;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class OgnlAction extends ActionSupport {
//List
类型属性
private List persons;
//execute
方法
public String execute() throws Exception {
//
获得ActionContext实例,以便访问Servlet API
ActionContext ctx = ActionContext.getContext();
//
存入application
ctx.getApplication().put("msg", "application
信息");
//
保存session
ctx.getSession().put("msg", "seesion
信息");
//
保存request信息
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute("msg", "request
信息");
//
persons赋值
persons = new LinkedList();
Person person1=new Person();
person1.setName("pla1");
person1.setAge(26);
person1.setBirthday(new Date());
persons.add(person1);

Person person2=new Person();
person2.setName("pla2");
person2.setAge(36);
person2.setBirthday(new Date());
persons.add(person2);
Person person3=new Person();
person3.setName("pla3");
person3.setAge(16);
person3.setBirthday(new Date());
persons.add(person3);
return SUCCESS;
}
public List getPersons() {
return persons;
}
public void setPersons(List persons) {
this.persons = persons;
}
}

该业务控制器分别在applicationsessionrequest中存入名为“msg”的字符串信息,另外定义了一个List类型属性,同时添加了两个Person类型元素。在配置文件中增加了相应的配置,代码如下:


/ch8/showognl.jsp

1.1.2 JSP视图

showognl.jsp是使用了OGNL表达式的JSP视图,视图用来显示Action中处理的各种信息,读者可以看到,使用OGNL表达式,代码更加简洁和直观,如代码8.5所示。

代码8.5使用OGNL表达式的JSP视图


http://www.w3.org/TR/ xhtml1/DTD/xhtml1-transitional.dtd">
http://www.w3.org/1999/xhtml">

访问OGNL上下文和Action上下文

parameters: 
request.msg: 
session.msg: 
application.msg: 
attr.msg: 


用于过滤和投影(projecting)集合
年龄大于20
1.                          - 年龄:
姓名为pla1的年龄: 


构造Map
The value of key "foo1" is 

1.1.3 运行示例

在浏览器中输入http://localhost:8080/bookcode/ch8/OgnlAction.action?msg=hello,运行结果如图8.3所示。

8.3 Struts 2中使用OGNL表达式

说明

本示例演示了如何使用OGNL表达式来访问OGNL上下文和值栈,同时演示了如何使用OGNL表达式进行集合操作。对读者深入理解Struts 2OGNL表达式的使用有所帮助。

posted @ 2009-08-15 17:29 jadmin 阅读(84) | 评论 (0)编辑 收藏

OGNL的集合操作

如果需要一个集合元素的时候(例如List对象或者Map对象),可以使用OGNL中同集合相关的表达式。

可以使用如下代码直接生成一个List对象:

{e1,e2,e3…}

OGNL表达式中,直接生成了一个List对象,该List对象中包含3个元素:e1e2e3。如果需要更多的元素,可以按照这样的格式定义多个元素,多个元素之间使用逗号隔开。

如下代码可以直接生成一个Map对象:

#{key1:value1,key2:value2,…}


Map
类型的集合对象,使用key-value格式定义,每个key-value元素使用冒号标识,多个元素之间使用逗号隔开。

对于集合类型,OGNL表达式可以使用innot in两个元素符号。其中,in表达式用来判断某个元素是否在指定的集合对象中;not in判断某个元素是否不在指定的集合对象中,如代码8.3所示。

代码8.3使用OGNL集合操作符


muhahaha


boo


muhahaha


boo

除了innot in之外,OGNL还允许使用某个规则获得集合对象的子集,常用的有以下3个相关操作符。
>>>
?
:获得所有符合逻辑的元素。
>>>
^
:获得符合逻辑的第一个元素。
>>>
$
:获得符合逻辑的最后一个元素。

例如代码:

person.relatives.{? #this.gender == 'male'}

该代码可以获得person的所有性别为malerelatievs集合。

posted @ 2009-08-15 17:25 jadmin 阅读(155) | 评论 (0)编辑 收藏

注意

使用索引,并不是直接获得指定的元素,而是从指定的索引位置搜索。

Struts 2中的OGNL ContextActionContext,如图8.2所示。

  

8.2 Struts 2OGNL Context结构示意图

说明

8.2只是说明Struts 2OGNL Context结构,实际上Context还包含其他对象。

由于值栈是Struts 2OGNL的根对象,如果用户需要访问值栈中的对象,则可以直接通过下面的代码访问值栈中的属性:

//获得值栈中的foo属性
${foo}

如果访问其他Context中的对象,由于不是根对象,在访问时,需要加#前缀。
>>
application
对象:用于访问ServletContext,例如#application.userName或者#application['userName'],相当于调用ServletgetAttribute("username")
>>

session
对象:用来访问HttpSession,例如#session.userName或者#session['userName'],相当于调用session.getAttribute("userName")
>>

request
对象:用来访问HttpServletRequest属性(attribute)的Map,例如#request.userName或者#request['userName'],相当于调用request.getAttribute ("userName")
>>

parameters
对象:用于访问HTTP的请求参数,例如#parameters.userName或者#parameters['userName'],相当于调用request.getParameter("username")
>>

attr
对象:用于按page-request-session-application顺序访问其属性。

posted @ 2009-08-15 17:18 jadmin 阅读(93) | 评论 (0)编辑 收藏

OGNLStruts 2框架的默认表达式语言,增强了Struts 2的数据访问能力,同时简化了代码。

Struts 2OGNL表达式

标准的OGNL会设定一个根对象(root对象)。假设使用标准OGNL表达式来求值(不使用Struts 2OGNL表达式),如果OGNL上下文(OgnlContext Map类型)有两个对象:foo对象,在OgnlContext中名称为foobar对象,在OgnlContext中名称为bar。同时foo对象被设置为根对象(root)。则利用下面的OGNL表达式求值:

// 返回foo.getBlah()
#foo.blah
//
返回bar.getBlah()
#bar.blah
//
返回foo.getBlah() ,因为foo为根对象
blah

说明

使用OGNL是非常简单的,如果要访问的对象不是根对象,如示例中的bar对象,则需要使用命名空间,用“#”来标识,如“#bar”;如果访问一个根对象,则不用指定命名空间,可以直接访问根对象的属性。

Struts 2框架中,值栈(Value Stack)就是OGNL的根对象,假设值栈中存在两个对对象实例:ManAnimal,这两个对象实例都有一个name属性,Animal有一个species属性,Man有一个salary属性,假设Animal在值栈的顶部,ManAnimal后面,下面的代码片断会帮助读者更好地理解OGNL表达式:

// 调用animal.getSpecies()
species
//
调用man.getSalary()
salary
//
调用animal.getName(),因为Animal位于值栈的顶部
name


最后一行示例代码,返回的是animal.getName()返回值,即返回了Animalname属性,因为Animal是值栈的顶部元素,OGNL将从顶部元素搜索,所以会返回Animalname属性值。如果要获得Manname值,则需要如下代码:

man.name

Struts 2允许在值栈中使用索引,示例代码如下所示:

[0].name   // 调用animal.getName()
[1].name   //
调用man.getName()

posted @ 2009-08-15 17:18 jadmin 阅读(86) | 评论 (0)编辑 收藏

    XFire、Axis

    XFire、Axis是Webservice的实现框架,WebService可算是一个完整的SOA架构实现标准了,因此采用XFire、Axis这些也就意味着是采用webservice方式了。

  1、是基于什么协议实现的?

  基于SOAP协议。

  2、怎么发起请求?

  获取到远端service的proxy后直接调用。

  3、怎么将请求转化为符合协议的格式的?

  将请求信息转化为遵循SOAP协议的XML格式,由框架转化为流进行传输。

  4、使用什么传输协议传输?

  Http协议。

  5、响应端基于什么机制来接收请求?

  监听Http请求。

  6、怎么将流还原为传输格式的?

  根据SOAP协议进行还原。

  7、处理完毕后怎么回应?

  返回结果写入XML中,由框架返回至调用端。

  ------------------------------------------------------------------------------------------------------------------------------

  ActiveMQ

  ActiveMQ是JMS的实现,基于JMS这类消息机制实现远程通讯是一种不错的选择,毕竟消息机制本身的功能使得基于它可以很容易的去实现同步/异步/单向调用等,而且消息机制从容错角度上来说也是个不错的选择,这是Erlang能够做到容错的重要基础

  1、是基于什么协议实现的?

  基于JMS协议。

  2、怎么发起请求?

  遵循JMS API发起请求。

  3、怎么将请求转化为符合协议的格式的?

  不太清楚,猜想应该是二进制流。

  4、使用什么传输协议传输?

  支持多种传输协议,例如tcp/ip、udp、http等等。

  5、响应端基于什么机制来接收请求?

  监听符合协议的端口。

  6、怎么将流还原为传输格式的?

  同问题3。

  7、处理完毕后怎么回应?

  遵循JMS API生成消息,并写入JMS Queue中。

  基于JMS此类机制实现远程通讯的例子有Spring-Intergration、Mule、Lingo等等。

 -----------------------------------------------------------------------------------------------------------------------------

    Mina

    Mina是Apache提供的通讯框架,在之前一直没有提到网络IO这块,之前提及的框架或library基本都是基于BIO的,而Mina是采用NIO的,NIO在并发量增长时对比BIO而言会有明显的性能提升,而java性能的提升,与其NIO这块与OS的紧密结合是有不小的关系的。

    1、是基于什么协议实现的?

    可选的传输协议+NIO.

    2、怎么发起请求?

    通过Mina提供的Client API.

    3、怎么将请求转化为符合协议的格式的?

    Mina遵循java串行化机制对请求对象进行序列化。

    4、使用什么传输协议传输?

    支持多种传输协议,例如tcp/ip、http等等。

    5、响应端基于什么机制来接收请求?

    以NIO的方式监听协议端口。

    6、怎么将流还原为传输格式的?

    遵循java串行化机制对请求对象进行反序列化。

    7、处理完毕后怎么回应?

    遵循Mina API进行返回。

    MINA是NIO方式的,因此支持异步调用是毫无悬念的。

 --------------------------------------------------------------------------------------------------------------------------------

    EJB

    EJB最突出的在于其分布式,EJB采用的是ORMI协议,和RMI协议是差不多的,但EJB在分布式通讯的安全控制、transport pool、smart proxy等方面的突出使得其在分布式领域是不可忽视的力量。

    1、是基于什么协议实现的?

    基于ORMI协议。

    2、怎么发起请求?

    EJB调用。

    3、怎么将请求转化为符合协议的格式的?

    遵循java串行化机制对请求对象进行序列化。

    4、使用什么传输协议传输?

    tcp/ip.

    5、响应端基于什么机制来接收请求?

    监听协议端口。

    6、怎么将流还原为传输格式的?

    遵循java串行化机制对请求对象进行反序列化。

    7、处理完毕后怎么回应?

    直接返回处理对象即可。

    在之前的分布式服务框架系列的文章中对于jndi有误导的嫌疑,在这篇blog中也顺带的提下jndi的机制,由于JNDI取决于具体的实现,在这里只能是讲解下jboss的jndi的实现了。

    在将对象实例绑定到jboss jnp server后,当远程端采用context.lookup()方式获取远程对象实例并开始调用时,jboss jndi的实现方法是从jnp server上获取对象实例,将其序列化回本地,然后在本地进行反序列化,之后在本地进行类调用。

    通过这个机制,就可以知道了,本地其实是必须有绑定到jboss上的对象实例的class的,否则反序列化的时候肯定就失败了,而远程通讯需要做到的是在远程执行某动作,并获取到相应的结果,可见纯粹基于JNDI是无法实现远程通讯的。

    但JNDI也是实现分布式服务框架一个很关键的技术点,因为可以通过它来实现透明化的远端和本地调用,就像ejb,另外它也是个很好的隐藏实际部署机制(就像datasource)等的方案。

    总结

    由上一系列的分析可知,在远程通讯领域中,涉及的知识点还是相当的多的,例如有:通信协议或远程调用协议(tcp/http/udp/rmi/xml-rpc etc.)、消息机制、网络IO(BIO/NIO/AIO)、MultiThread、本地调用与远程调用的透明化方案(涉及java classloader、Dynamic Proxy、Unit Test etc.)、异步与同步调用、网络通信处理机制(自动重连、广播、异常、池处理等等)、Java Serialization (各种协议的私有序列化机制等)、各种框架的实现原理(传输格式、如何将传输格式转化为流的、如何将请求信息转化为传输格式的、如何接收流的、如何将流还原为传输格式的等等),要精通其中的哪些东西,得根据实际需求来决定了,只有在了解了原理的情况下才能很容易的做出选择,甚至可以根据需求做私有的远程通讯协议,对于从事分布式服务平台或开发较大型的分布式应用的人而言,我觉得至少上面提及的知识点是需要比较了解的。

posted @ 2009-08-15 15:10 jadmin 阅读(237) | 评论 (0)编辑 收藏

    可选实现技术

    当然,在上面的原理中并没有介绍到所有的java领域可选的远程通信协议了,例如还有EJB采用的ORMI、Spring自己定义的一个简单的Http Invoker等等。

    看完原理后我们再来看看目前java领域可用于实现远程通讯的框架或library,知名的有:JBoss-Remoting、Spring-Remoting、Hessian、Burlap、XFire(Axis)、ActiveMQ、Mina、Mule、EJB3等等,来对每种做个简单的介绍和评价,其实呢,要做分布式服务框架,这些东西都是要有非常深刻的了解的,因为分布式服务框架其实是包含了解决分布式领域以及应用层面领域两方面问题的。

    当然,你也可以自己根据远程网络通信原理(transport protocol+Net IO)去实现自己的通讯框架或library.

    那么在了解这些远程通讯的框架或library时,会带着什么问题去学习呢?

    1、是基于什么协议实现的?

    2、怎么发起请求?

    3、怎么将请求转化为符合协议的格式的?

    4、使用什么传输协议传输?

    5、响应端基于什么机制来接收请求?

    6、怎么将流还原为传输格式的?

    7、处理完毕后怎么回应?

    JBoss-Remoting

    Jboss-remoting是由jboss编写的一个java领域的远程通讯框架,基于此框架,可以很简单的实现基于多种传输协议的java对象的RPC.

    直接来回答问题:

    1、是基于什么协议实现的?

    JBoss-Remoting是个通讯框架,因此它支持多种协议方式的通信,例如tcp/ip+io方式、rmi方式、http+io方式等。

    2、怎么发起请求?

    在JBoss-Remoting中,只需将需要发起的请求参数对象传入jboss-remoting的InvocationRequest对象即可,也可根据协议基于InvocationRequest封装符合需求的InvocationRequest对象。

    3、怎么将请求转化为符合协议的格式的?

    JBoss-Remoting基于Java串行化机制或JBoss自己的串行化实现来将请求转化为对象字节流。

    4、使用什么传输协议传输?

    支持多种传输协议,例如tcp/ip、http等。

    5、响应端基于什么机制来接收请求?

    响应端只需将自己的处理对象注册到JBoss-Remoting提供的server端的Connector对象中即可。

    6、怎么将流还原为传输格式的?

    JBoss-Remoting基于java串行化机制或jboss自己的串行化实现来将请求信息还原为java对象。

    7、处理完毕后怎么回应?

    处理完毕后将结果对象直接返回即可,jboss-remoting会将此对象按照协议进行序列化,返回至调用端。

    另外,jboss-remoting支持多种通信方式,例如同步/异步/单向通信等。

 ---------------------------------------------------------------------------------------------------------------------------

    Spring-Remoting

    Spring-remoting是Spring提供java领域的远程通讯框架,基于此框架,同样也可以很简单的将普通的spring bean以某种远程协议的方式来发布,同样也可以配置spring bean为远程调用的bean.

  1、是基于什么协议实现的?

  和JBoss-Remoting一样,作为一个远程通讯的框架,Spring通过集成多种远程通讯的library,从而实现了对多种协议的支持,例如rmi、http+io、xml-rpc、binary-rpc等。

  2、怎么发起请求?

  在Spring中,由于其对于远程调用的bean采用的是proxy实现,发起请求完全是通过服务接口调用的方式。

  3、怎么将请求转化为符合协议的格式的?

  Spring按照协议方式将请求的对象信息转化为流,例如Spring Http Invoker是基于Spring自己定义的一个协议来实现的,传输协议上采用的为http,请求信息是基于java串行化机制转化为流进行传输。

  4、使用什么传输协议传输?

  支持多种传输协议,例如rmi、http等等。

  5、响应端基于什么机制来接收请求?

  响应端遵循协议方式来接收请求,对于使用者而言,则只需通过spring的配置方式将普通的spring bean配置为响应端或者说提供服务端。

  6、怎么将流还原为传输格式的?

  按照协议方式来进行还原。

  7、处理完毕后怎么回应?

  处理完毕后直接返回即可,spring-remoting将根据协议方式来做相应的序列化。

  -----------------------------------------------------------------------------------------------------------------------------

  Hessian

  Hessian是由caucho提供的一个基于binary-RPC实现的远程通讯library。

  1、是基于什么协议实现的?

  基于Binary-RPC协议实现。

  2、怎么发起请求?

  需通过Hessian本身提供的API来发起请求。

  3、怎么将请求转化为符合协议的格式的?

  Hessian通过其自定义的串行化机制将请求信息进行序列化,产生二进制流。

    Hessian基于Http协议进行传输。

    5、响应端基于什么机制来接收请求?

    响应端根据Hessian提供的API来接收请求。

    6、怎么将流还原为传输格式的?

    Hessian根据其私有的串行化机制来将请求信息进行反序列化,传递给使用者时已是相应的请求信息对象了。

    7、处理完毕后怎么回应?

    处理完毕后直接返回,hessian将结果对象进行序列化,传输至调用端。

 -------------------------------------------------------------------------------------------------------------------------------

    Burlap

    Burlap也是有caucho提供,它和hessian的不同在于,它是基于XML-RPC协议的。

    1、是基于什么协议实现的?

    基于XML-RPC协议实现。

    2、怎么发起请求?

    根据Burlap提供的API.

    3、怎么将请求转化为符合协议的格式的?

    将请求信息转化为符合协议的XML格式,转化为流进行传输。

    4、使用什么传输协议传输?

    Http协议。

    5、响应端基于什么机制来接收请求?

    监听Http请求。

    6、怎么将流还原为传输格式的?

    根据XML-RPC协议进行还原。

    7、处理完毕后怎么回应?

    返回结果写入XML中,由Burlap返回至调用端。

 -----------------------------------------------------------------------------------------------------------------------------

posted @ 2009-08-15 15:08 jadmin 阅读(84) | 评论 (0)编辑 收藏

  在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS 等,这些名词之间到底是些什么关系呢,它们背后到底是基于什么原理实现的呢,了解这些是实现分布式服务框架的基础知识,而如果在性能上有高的要求的话,那深入了解这些技术背后的机制就是必须的了,在这篇blog中我们将来一探究竟,抛砖引玉,欢迎大家提供更多的实现远程通讯的技术和原理的介绍。

  基本原理

  要实现网络机器间的通讯,首先得来看看计算机系统网络通信的基本原理,在底层层面去看,网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络IO来实现,其中传输协议比较出名的有 http、tcp、udp等等,http、tcp、udp都是在基于Socket概念上为某类应用场景而扩展出的传输协议,网络IO,主要有bio、 nio、aio三种方式,所有的分布式应用通讯都基于这个原理而实现,只是为了应用的易用,各种语言通常都会提供一些更为贴近应用易用的应用层协议。

  应用级协议

  远程服务通讯,需要达到的目标是在一台计算机发起请求,另外一台机器在接收到请求后进行相应的处理并将结果返回给请求端,这其中又会有诸如one way request、同步请求、异步请求等等请求方式,按照网络通信原理,需要实现这个需要做的就是将请求转换成流,通过传输协议传输至远端,远端计算机在接收到请求的流后进行处理,处理完毕后将结果转化为流,并通过传输协议返回给调用端。

  原理是这样的,但为了应用的方便,业界推出了很多基于此原理之上的应用级的协议,使得大家可以不用去直接操作这么底层的东西,通常应用级的远程通信协议会提供:

  1. 为了避免直接做流操作这么麻烦,提供一种更加易用或贴合语言的标准传输格式;

  2. 网络通信机制的实现,就是替你完成了将传输格式转化为流,通过某种传输协议传输至远端计算机,远端计算机在接收到流后转化为传输格式,并进行存储或以某种方式通知远端计算机。

  所以在学习应用级的远程通信协议时,我们可以带着这几个问题进行学习:

  1. 传输的标准格式是什么?

  2. 怎么样将请求转化为传输的流?

  3. 怎么接收和处理流?

  4. 传输协议是?

  不过应用级的远程通信协议并不会在传输协议上做什么多大的改进,主要是在流操作方面,让应用层生成流和处理流的这个过程更加的贴合所使用的语言或标准,至于传输协议则通常都是可选的,在java领域中知名的有:RMI、XML-RPC、Binary-RPC、SOAP、CORBA、JMS,来具体的看看这些远程通信的应用级协议:

  RMI

  RMI是个典型的为java定制的远程通信协议,我们都知道,在single vm中,我们可以通过直接调用java object instance来实现通信,那么在远程通信时,如果也能按照这种方式当然是最好了,这种远程通信的机制成为RPC(Remote Procedure Call),RMI正是朝着这个目标而诞生的。

  来看下基于RMI的一次完整的远程通信过程的原理:

  1. 客户端发起请求,请求转交至RMI客户端的stub类;

  2. stub类将请求的接口、方法、参数等信息进行序列化;

  3. 基于socket将序列化后的流传输至服务器端;

  4. 服务器端接收到流后转发至相应的skelton类;

  5. skelton类将请求的信息反序列化后调用实际的处理类;

  6. 处理类处理完毕后将结果返回给skelton类;

  7. Skelton类将结果序列化,通过socket将流传送给客户端的stub;

  8. stub在接收到流后反序列化,将反序列化后的Java Object返回给调用者。

  来看jboss-remoting对于此过程的一个更好的图示:

Java远程通讯可选技术及原理

  根据原理来回答下之前学习应用级协议带着的几个问题:

  1. 传输的标准格式是什么?

  是Java ObjectStream。

  2. 怎么样将请求转化为传输的流?

  基于Java串行化机制将请求的java object信息转化为流。

  3. 怎么接收和处理流?

  根据采用的协议启动相应的监听端口,当有流进入后基于Java串行化机制将流进行反序列化,并根据RMI协议获取到相应的处理对象信息,进行调用并处理,处理完毕后的结果同样基于java串行化机制进行返回。

  4. 传输协议是?

  Socket。

  XML-RPC

  XML-RPC也是一种和RMI类似的远程调用的协议,它和RMI的不同之处在于它以标准的xml格式来定义请求的信息(请求的对象、方法、参数等),这样的好处是什么呢,就是在跨语言通讯的时候也可以使用。

  来看下XML-RPC协议的一次远程通信过程:

  1. 客户端发起请求,按照XML-RPC协议将请求信息进行填充;

  2. 填充完毕后将xml转化为流,通过传输协议进行传输;

  3. 接收到在接收到流后转换为xml,按照XML-RPC协议获取请求的信息并进行处理;

  4. 处理完毕后将结果按照XML-RPC协议写入xml中并返回。

  图示以上过程:

Java远程通讯可选技术及原理

  同样来回答问题:

  1. 传输的标准格式是?

  标准格式的XML。

  2. 怎么样将请求转化为传输的流?

  将XML转化为流。

  3. 怎么接收和处理流?

  通过监听的端口获取到请求的流,转化为XML,并根据协议获取请求的信息,进行处理并将结果写入XML中返回。

  4. 传输协议是?

  Http。

  Binary-RPC

  Binary-RPC看名字就知道和XML-RPC是差不多的了,不同之处仅在于传输的标准格式由XML转为了二进制的格式。

  同样来回答问题:

  1. 传输的标准格式是?

  标准格式的二进制文件。

  2. 怎么样将请求转化为传输的流?

  将二进制格式文件转化为流。

  3. 怎么接收和处理流?

  通过监听的端口获取到请求的流,转化为二进制文件,根据协议获取请求的信息,进行处理并将结果写入XML中返回。

  4. 传输协议是?

  Http。

  SOAP

  SOAP原意为Simple Object Access Protocol,是一个用于分布式环境的、轻量级的、基于XML进行信息交换的通信协议,可以认为SOAP是XML RPC的高级版,两者的原理完全相同,都是http+XML,不同的仅在于两者定义的XML规范不同,SOAP也是Webservice采用的服务调用协议标准,因此在此就不多加阐述了。

  CORBA

  Common Object Request Broker Architecture(公用对象请求代理[调度]程序体系结构),是一组用来定义“分布式对象系统”的标准,由OMG(Object Menagement Group)作为发起和标准制定单位。CORBA的目的是定义一套协议,符合这个协议的对象可以互相交互,不论它们是用什么样的语言写的,不论它们运行于什么样的机器和操作系统。

  CORBA在我看来是个类似于SOA的体系架构,涵盖可选的远程通信协议,但其本身不能列入通信协议这里来讲,而且CORBA基本淘汰,再加上对CORBA也不怎么懂,在此就不进行阐述了。

  JMS

  JMS呢,是实现java领域远程通信的一种手段和方法,基于JMS实现远程通信时和RPC是不同的,虽然可以做到RPC的效果,但因为不是从协议级别定义的,因此我们不认为JMS是个RPC协议,但它确实是个远程通信协议,在其他的语言体系中也存在着类似JMS的东西,可以统一的将这类机制称为消息机制,而消息机制呢,通常是高并发、分布式领域推荐的一种通信机制,这里的主要一个问题是容错(详细见ErLang论文)。

  来看JMS中的一次远程通信的过程:

  1. 客户端将请求转化为符合JMS规定的Message;

  2. 通过JMS API将Message放入JMS Queue或Topic中;

  3. 如为JMS Queue,则发送中相应的目标Queue中,如为Topic,则发送给订阅了此Topic的JMS Queue。

  4. 处理端则通过轮训JMS Queue,来获取消息,接收到消息后根据JMS协议来解析Message并处理。

  回答问题:

  1. 传输的标准格式是?

  JMS规定的Message。

  2. 怎么样将请求转化为传输的流?

  将参数信息放入Message中即可。

  3. 怎么接收和处理流?

  轮训JMS Queue来接收Message,接收到后进行处理,处理完毕后仍然是以Message的方式放入Queue中发送或Multicast。

  4. 传输协议是?

  不限。

  基于JMS也是常用的实现远程异步调用的方法之一。

posted @ 2009-08-15 14:59 jadmin 阅读(96) | 评论 (0)编辑 收藏

下面以一个简单的示例来帮助读者理解OGNL表达式。使用OGNL表达式,需要在www.ognl.org网站下载一个ognl.jar插件包,将该文件复制到classpath路径下即可。建立一个复合类型,如代码8.1所示。

代码8.1定义复合类型

package ch8;
import java.util.Date;
//
团队类
public class Team {
//
团队名称
private String teamname;
//
定义团队人员属性
private Person person;
//
团队人数
private int personnum;
//
属性的gettersetter方法
public String getTeamname() {
return teamname;
}
public void setTeamname(String teamname) {
this.teamname = teamname;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public int getPersonnum() {
return personnum;
}
public void setPersonnum(int personnum) {
this.personnum = personnum;
}
}
//
定义人员类
class Person {
//
姓名
private String name;
//
年龄
private int age;
//
人员出生日期
private Date birthday;
//
属性的gettersetter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}

代码8.1所示内容定义了两个复合类型:团队(team)和人员(person)类型。使用OGNL表达式示例,如代码8.2所示。

代码8.2使用OGNL表达式示例

package ch8;
import java.util.HashMap;
import java.util.Map;
import ognl.Ognl;
import ognl.OgnlException;
public class TestOGNL {
public static void main(String[] args) {
//
定义一个Map对象
Map m = new HashMap();
//
定义一个Team对象
Team team1 = new Team();
team1.setTeamname("
团队1");
//
定义一个Person对象
Person person1 = new Person();
person1.setName("pla1");
//
添加team元素
team1.setPerson(person1);
//
定义一个Team对象
Team team2 = new Team();
team2.setTeamname("
团队2");
//
定义一个Person对象
Person person2 = new Person();
person2.setName("pla2");
//
添加team元素
team2.setPerson(person2);

//
添加Map元素
m.put("team1", team1);
m.put("team2", team2);
try {
System.out.println(Ognl.getValue("team1.teamname", m));
System.out.println(Ognl.getValue("team2.person.name", m));
System.out.println(Ognl.getValue("teamname", team2));
System.out.println(Ognl.getValue("person.name", team2));
} catch (OgnlException e) {
}
}
}


代码8.2所示内容定义了一个Map类型的嵌套属性,如图8.1所示。

  

8.1嵌套属性示意图

运行该示例,控制器显示如下信息:

团队1
pla2
团队2
pla2

说明

OGNL可以使用非常简单的表达式来访问多层嵌套属性,为开发者提供了一个有力的工具。

posted @ 2009-08-13 14:25 jadmin 阅读(130) | 评论 (0)编辑 收藏

基本的OGNL语法是十分简单的,当然OGNL支持丰富的表达式,一般情况下,不用担心OGNL的复杂性。例如有一个man对象,该对象有一个name属性,那么使用OGNL来获得该name属性可以使用如下表达式:

man.name

OGNL表达式的基础单元称为导航链,简称为链。一个最简单的链由如下部分组成。
>
属性名称:如上述示例中的name
>
方法调用:hashCode()返回当前对象的hash code
>
数组元素:listeners[0]返回当前对象的监听器列表中的第一个元素。

说明

OGNL表达式基于OGNL上下文中的当前对象,一个将使用上一个的处理结果,开发者可以任意扩展该链的长度,OGNL没有限制。

例如,一个OGNL表达式如下:

name.toCharArray()[0].numericValue.toString()


该表达式将按照如下步骤求值。

1)获得OGNL Context中初始对象或者是根对象(root对象)的name对象。

2)调用toCharArray()方法,返回一个String类型对象。

3)获得该String对象的第一个字符。

4)获得该字符的numericValue属性(该字符为一个Character对象,该对象有一个getNumericValue()方法,该方法返回一个Integer类型值)。

5)将获得的Integer对象转换为一个String类型值(使用toString()方法)。

posted @ 2009-08-13 13:55 jadmin 阅读(78) | 评论 (0)编辑 收藏

OGNL是Object Graph Navigation Language的缩写,与JSP,JSF相比,OGNL是一种功能非常强大的针对Java的表达式语言(EL),它可用来读取和更新Java对象的属性。

OGNL可以用在以下方面:
- 用做数据绑定语言用来绑定GUI元素(textfield, combobox等)到模型对象
- 用做数据源语言用来映射数据库表到表模型对象
- 用做数据绑定语言用来绑定web组件到数据模型(WebOGNLTapestryWebWork等)
- 提供类似Jakarta Commons BeanUtils所提供的功能(读取Java对象的属性)

OGNL表达式语法:
Java标准类型:
bool类型:true,false
int类型:10, 0xABCD等
long类型:100L
float类型:1.0, 0.5F等
double类型:0.01D
char类型:'A', '\uFFFF'等
字符串类型:"Hello World!"
null

OGNL独自类型:
例:10.01B,相当于java.math.BigDecimal
例:100000H,相当于java.math.BigInteger

OGNL表达式中能使用的操作符号:
OGNL表达式中能使用的操作符基本跟Java里的操作符一样,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等

变量的引用:
使用方法:#变量名
例:#this, #user.name

对静态方法或变量的访问:
@mypkg.MyClass@myVar
@mypkg.MyClass@myMethod()

读取变量值:
例:user.address.countryName

方法调用:
例:user.getName()

对象的创建:
new java.net.URL("http://localhost/")

List表达式例:
{"green", "red", "blue"}


Map表达式例:
#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}
对map引用,例:map.key1

等等。

OGNL官方首页:
http://www.ognl.org/

OGNL官方文档 (2.6.9)
OGNL Language Guide (2.6.9)

附:
OGNL使用例:
  1. package com.test.ognl;   
  2. import java.util.HashMap;   
  3. import java.util.List;   
  4. import java.util.Map;   
  5.   
  6. import junit.framework.TestCase;   
  7. import ognl.Ognl;   
  8. import ognl.OgnlContext;   
  9.   
  10. public class OgnlTest extends TestCase {   
  11.     public void testGetValue() throws Exception {   
  12.          OgnlContext context = new OgnlContext();   
  13.          Book book = new Book("book1");   
  14.          context.put("book", book);   
  15.   
  16.         final String expression = "book.name";   
  17.          Object parseExpression = Ognl.parseExpression(expression);   
  18.          assertEquals("book1", Ognl.getValue(parseExpression, context));   
  19.            
  20.          book.setName("book2");   
  21.          assertEquals("book2", Ognl.getValue(parseExpression, context));   
  22.      }   
  23.        
  24.     public void testSetValue() throws Exception {   
  25.          OgnlContext context = new OgnlContext();   
  26.          Book book = new Book("book1");   
  27.          context.put("book", book);   
  28.   
  29.         final String expression = "book.name";   
  30.          Object parseExpression = Ognl.parseExpression(expression);   
  31.          Ognl.setValue(parseExpression, context, "book2");   
  32.          assertEquals("book2", book.getName());   
  33.      }   
  34.        
  35.     public void testCallStaticMethod() throws Exception {   
  36.          OgnlContext context = new OgnlContext();   
  37.   
  38.         final String expression = "@com.test.ognl.Book@test()";   
  39.          Object parseExpression = Ognl.parseExpression(expression);   
  40.          assertEquals("Hello World", Ognl.getValue(parseExpression, context));   
  41.      }   
  42.        
  43.     public void testArray() throws Exception {   
  44.          OgnlContext context = new OgnlContext();   
  45.   
  46.         final String expression = "new int[]{1, 2, 3}";   
  47.          Object parseExpression = Ognl.parseExpression(expression);   
  48.         int[] ret = (int[]) Ognl.getValue(parseExpression, context);   
  49.   
  50.          assertEquals(1, ret[0]);   
  51.          assertEquals(2, ret[1]);   
  52.          assertEquals(3, ret[2]);   
  53.      }   
  54.   
  55.     public void testList() throws Exception {   
  56.          OgnlContext context = new OgnlContext();   
  57.   
  58.         final String expression = "{1, 2, 3}";   
  59.          Object parseExpression = Ognl.parseExpression(expression);   
  60.          List ret = (List) Ognl.getValue(parseExpression, context);   
  61.   
  62.          assertEquals(new Integer(1), ret.get(0));   
  63.          assertEquals(new Integer(2), ret.get(1));   
  64.          assertEquals(new Integer(3), ret.get(2));   
  65.      }   
  66.        
  67.     public void testMap() throws Exception {   
  68.          OgnlContext context = new OgnlContext();   
  69.   
  70.         final String expression = "#{\"name\" : \"book1\", \"price\" : 10.2}";   
  71.          Object parseExpression = Ognl.parseExpression(expression);   
  72.          Map value = (Map) Ognl.getValue(parseExpression, context);   
  73.          assertEquals("book1", value.get("name"));   
  74.          assertEquals(new Integer(10.2), value.get("price"));   
  75.      }   
  76. }   
  77.   
  78. class Book {   
  79.     private int name;   
  80.   
  81.     public Book(String bookName) {   
  82.         this.name = bookName;   
  83.      }   
  84.     public int getName() {   
  85.         return name;   
  86.      }   
  87.   
  88.     public void setName(int Name) {   
  89.         this.name = name;   
  90.      }   
  91.   
  92.     // test static method   
  93.     public static String hello() {   
  94.         return "Hello World";   
  95.      }  

posted @ 2009-08-12 18:19 jadmin 阅读(104) | 评论 (0)编辑 收藏

cellspacing ---> 单元格的元素与边界的距离

cellpadding ---> 单元格与单元格之间的距离

posted @ 2009-08-09 19:34 jadmin 阅读(131) | 评论 (0)编辑 收藏


    缺省构造函数的问题:base类是父类,derived类是子类,首先要说明的是由于先有父类后有子类,所以生成子类之前要首先有父类。class是由class的构造函数constructor产生的,每一个class都有构造函数,如果你在编写自己的class时没有编写任何构造函数,那么编译器为你自动产生一个缺省default构造函数。这个default构造函数实质是空的,其中不包含任何代码。但是一牵扯到继承,它的问题就出现了。

    如果父类base class只有缺省构造函数,也就是编译器自动为你产生的。而子类中也只有缺省构造函数,那么不会产生任何问题,因为当你试图产生一个子类的实例时,首先要执行子类的构造函数,但是由于子类继承父类,所以子类的缺省构造函数自动调用父类的缺省构造函数。先产生父类的实例,然后再产生子类的实例。如下:

class base{
}
class derived extends base{
public static void main(String[] args){
    derived d=new derived();
}
}

下面我自己显式地加上了缺省构造函数:
class base{
base(){
    System.out.println("base constructor");
}
}
class derived extends base{
derived(){
    System.out.println("derived constructor");
}
public static void main(String[] args){
    derived d=new derived();
}
}

执行结果如下:说明了先产生base class然后是derived class。
base constructor
derived constructor

我要说明的问题出在如果base class有多个constructor而derived class也有多个constructor,这时子类中的构造函数缺省调用那个父类的构造函数呢?答案是调用父类的缺省构造函数。但是不是编译器自动为你生成的那个缺省构造函数而是你自己显式地写出来的缺省构造函数。

class base{
base(){
    System.out.println("base constructor");
}
base(int i){
    System.out.println("base constructor int i");
}
}
class derived extends base{
derived(){
    System.out.println("derived constructor");
}
derived(int i){
    System.out.println("derived constructor int i");
}
public static void main(String[] args){
    derived d=new derived();
    derived t=new derived(9);
}
}

D:\java\thinking\think6>java derived
base constructor
derived constructor
base constructor
derived constructor int i

如果将base 类的构造函数注释掉,则出错。

class base{
// base(){
//    System.out.println("base constructor");
// }
base(int i){
    System.out.println("base constructor int i");
}
}
class derived extends base{
derived(){
    System.out.println("derived constructor");
}
derived(int i){
    System.out.println("derived constructor int i");
}
public static void main(String[] args){
    derived d=new derived();
    derived t=new derived(9);
}
}


D:\java\thinking\think6>javac derived.java
derived.java:10: cannot resolve symbol
symbol : constructor base ()
location: class base
derived(){
           ^
derived.java:13: cannot resolve symbol
symbol : constructor base ()
location: class base
derived(int i){
2 errors

说明子类中的构造函数找不到显式写出的父类中的缺省构造函数,所以出错。

那么如果你不想子类的构造函数调用你显式写出的父类中的缺省构造函数怎么办呢?如下例:

class base{
// base(){
//    System.out.println("base constructor");
// }
base(int i){
    System.out.println("base constructor int i");
}
}
class derived extends base{
derived(){
    super(8);
    System.out.println("derived constructor");
}
derived(int i){
    super(i);
    System.out.println("derived constructor int i");
}
public static void main(String[] args){
    derived d=new derived();
    derived t=new derived(9);
}
}


D:\java\thinking\think6>java derived
base constructor int i
derived constructor
base constructor int i
derived constructor int i

super(i)表示父类的构造函数base(i)请大家注意:一个是super(i)一个是super(8)。大家想想是为什么??

结论:
子类如果有多个构造函数的时候,父类要么没有构造函数,让编译器自动产生,那么在执行子类构造函数之前先执行编译器自动产生的父类的缺省构造函数;要么至少要有一个显式的缺省构造函数可以让子类的构造函数调用。

posted @ 2009-08-09 13:18 jadmin 阅读(110) | 评论 (0)编辑 收藏

Win+R

cmd

sc delete 服务名

posted @ 2009-08-09 12:29 jadmin 阅读(92) | 评论 (0)编辑 收藏

      java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize()。

      (1).对象不一定会被回收。
      (2).垃圾回收不是析构函数。
      (3).垃圾回收只与内存有关。
      (4).垃圾回收和finalize()都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。

     垃圾收集器在进行垃圾收集的时候会自动呼叫对象的finalize方法,用来进行一些用户自定义的非内存清理工作,因为垃圾收集器不会处理内存以外的东西。所以,有的时候用户需要定义一些清理的方法,比如说处理文件和端口之类的非内存资源。

      finalize的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.

      finalize()在什么时候被调用?

      有三种情况
      1.所有对象被Garbage Collection时自动调用,比如运行System.gc()的时候.
      2.程序退出时为每个对象调用一次finalize方法。
      3.显式的调用finalize方法

      除此以外,正常情况下,当某个对象被系统收集为无用信息的时候,finalize()将被自动调用,但是jvm不保证finalize()一定被调用,也就是说,finalize()的调用是不确定的,这也就是为什么sun不提倡使用finalize()的原因。

      理解finalize( ) 正好在垃圾回收以前被调用非常重要。例如当一个对象超出了它的作用域时,finalize( ) 并不被调用。这意味着你不可能知道何时——甚至是否——finalize( ) 被调用。因此,你的程序应该提供其他的方法来释放由对象使用的系统资源,而不能依靠finalize( ) 来完成程序的正常操作。

posted @ 2009-08-08 23:17 jadmin 阅读(125) | 评论 (0)编辑 收藏

Java版二分查找算法

二分查找算法的目标查找集合应该为有序序列

/*
* @(#)BinarySearch.java 2009-8-8
*
* Copyright (c) 2009 by jadmin. All Rights Reserved.
*/

package algorithm.search;


/**
* 二分查找算法
*
* @author <a href="mailto:jadmin@126.com">jadmin</a>
* @version $Id: BinarySearch.java 2009-8-8 上午05:07:05$
* @see <a href="http://hi.baidu.com/jadmin">myblog</a>
*/
public final class BinarySearch {

public static int find(int[] a, int key) {
   return find(a, 0, a.length - 1, key);
}

// 非递归实现
public static int find(int[] a, int fromIndex, int toIndex, int key) {
   int low = fromIndex;
   int high = toIndex;

   while (low <= high) {
    // 无符号右移位逻辑运算
    int mid = (low + high) >>> 1;
    int midVal = a[mid];

    if (midVal < key)
     low = mid + 1;
    else if (midVal > key)
     high = mid - 1;
    else
     return mid; // key found
   }
   return -(low + 1); // key not found.
}

// 递归实现
public static int search(int[] a, int fromIndex, int toIndex, int key) {
   if(fromIndex > toIndex) {
    return -1;
   }
   int mid = (fromIndex + toIndex) >>> 1;
   if(a[mid] < key) {
    return search(a, mid + 1, toIndex, key);
   } else if(a[mid] > key) {
    return search(a, fromIndex, mid - 1, key);
   } else {
    return mid;
   }
}

posted @ 2009-08-08 05:10 jadmin 阅读(94) | 评论 (0)编辑 收藏

题目:1 ~ 1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来,不用辅助存储空间,能否设计一个算法实现?

姑且令该数组为int[] a

解法1:数组累和 - (1+2+3+...+.. + 999 + 1000)= 所求结果

public int find(int[] a) {

    int t = 1000 * (1000 + 1) / 2; // 1 ~ 1000的累和
    int sum = 0;
    for(int i = 0;i < a.length;i++) {
        sum += a[i];
    }
    return (sum - t);
}

解法2:异或


将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。

但是这个算法虽然很简单,但证明起来并不是一件容易的事情。这与异或运算的几个特性有关系。
首先是异或运算满足交换律、结合律。
所以,1^2^...^n^...^n^...^1000,无论这两个n出现在什么位置,都可以转换成为1^2^...^1000^(n^n)的形式。

其次,对于任何数x,都有x^x=0,x^0=x。
所以1^2^...^n^...^n^...^1000 = 1^2^...^1000^(n^n)= 1^2^...^1000^0 = 1^2^...^1000(即序列中除了n的所有数的异或)。

令,1^2^...^1000(序列中不包含n)的结果为T
则1^2^...^1000(序列中包含n)的结果就是T^n。
T^(T^n)=n。
所以,将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。

public int find(int[] a) {
    int t1 = 0;
    int t2 = 0;
    for(int i = 0;i < a.length;i++) {
        t1 ^= a[i];
    }

    for(int i = 1;i <= 1000;i++) {
        t2 ^= i;
    }
    return (t1 ^ t2);
}

遗留问题:如果放入数组a中的数为:1000个不连续且互不相同的数(设其组成的数组为n) + 重复数(取自数组n),又如何求取这个重复数呢,要保证算法的效率哦

参考:

http://www.cnblogs.com/myqiao/archive/2009/07/21/1528156.html

http://www.cnblogs.com/myqiao/archive/2009/07/22/1528271.html

posted @ 2009-08-08 03:57 jadmin 阅读(102) | 评论 (0)编辑 收藏

/*
* @(#)RandNumberUtil.java 2009-8-8
*
* Copyright (c) 2009 by jadmin. All Rights Reserved.
*/

package com.jsoft.util.random;

/**
* 随机数辅助类
*
* @author <a href="mailto:jadmin@126.com">jadmin</a>
* @version $Id: RandNumberUtil.java 2009-8-8 上午03:22:37$
* @see <a href="http://hi.baidu.com/jadmin">myblog</a>
*/
public class RandNumberUtil {

/**
* 随机生成count个不重复的并且介于min和max间的整数
*
* @param min
* @param max
* @param count
* @return
*/
public static int[] generate(int min, int max, int count) {
   if(min > max) {
    throw new IllegalArgumentException("参数min必须小于max...");
   }
   int n = max - min + 1;
   if(count > n) {
    throw new IllegalArgumentException("参数count超出范围...");
   }
   int[] span = new int[n];
   for (int i = 0, j = min; i < n; i++, j++) {
    span[i] = j;
   }
  
   // 存储要生成的随机数
   int[] target = new int[count];
   for (int i = 0; i < target.length; i++) {
    int r = (int)(Math.random() * n);
    target[i] = span[r];
    span[r] = span[n - 1];
    n--;
   }
   return target;
}

public static void main(String[] args) {
   int[] a = generate(12, 68, 9);
   for(int i : a) {
    System.out.print(i + " ");
   }
}
}

posted @ 2009-08-08 03:29 jadmin 阅读(120) | 评论 (0)编辑 收藏

jsp文件

1.index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title>Index Page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">   
</head>

<body>
    <jsp:forward page="login.jsp"></jsp:forward>
</body>
</html>

2.login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title>Index Page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">   
</head>

<body>
<s:fielderror></s:fielderror>

<s:form action="login">
    <s:textfield name="user.username" label="用 户"></s:textfield>
    <s:password name="user.password" label="密 码"></s:password>
    <s:submit value="登录"></s:submit>
</s:form>

</body>
</html>

3.login_success.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="c" uri="
http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title>Index Page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">   
</head>

<body>
<c:choose>
   <c:when test="${empty user}">您还没有登录哦。。。</c:when>
   <c:otherwise>${user.username},欢迎登录</c:otherwise>
</c:choose>

</body>
</html>

附图

注意:运行本项目需要jstl标签库的支持,如果你使用的是java ee 5的话就不需要另外引入了

posted @ 2009-07-29 16:33 jadmin 阅读(97) | 评论 (0)编辑 收藏

1.准备工作

建好一个Web项目,加入必要的jar包(见本文末的附图),本文将演示一个用户登录的例子,使用的是struts2.1.6,java ee 5

2.代码

User.java

/*
* @(#)User.java 2009-7-29
*
* Copyright (c) 2009 by jadmin. All Rights Reserved.
*/

package com.jsoft.domain;

/**
* 用户实体类
*
* @author <a href="
mailto:jadmin@126.com">jadmin</a>
* @version $Id: User.java 2009-7-29 下午12:47:16$
* @see <a href="
http://hi.baidu.com/jadmin">myblog</a>
*/
public class User {

private String username;

private String password;

public String getUsername() {
   return username;
}

public void setUsername(String username) {
   this.username = username;
}

public String getPassword() {
   return password;
}

public void setPassword(String password) {
   this.password = password;
}

@Override
public String toString() {
   return super.toString() + "[" + username + ", " + password + "]";
}
}

UserAction.java

/*
* @(#)UserAction.java 2009-7-29
*
* Copyright (c) 2009 by jadmin. All Rights Reserved.
*/

package com.jsoft.web.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;

import com.jsoft.domain.User;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;

/**
* Action
*
* @author <a href="
mailto:jadmin@126.com">jadmin</a>
* @version $Id: UserAction.java 2009-7-29 下午12:24:33$
* @see <a href="
http://hi.baidu.com/jadmin">myblog</a>
*/
public class UserAction extends ActionSupport {

private static final long serialVersionUID = 6488865641880260892L;
private User user;

@Action(value = "login",
    results = {
     @Result(name = "success", location = "/login_success.jsp", type = "redirect"),
     @Result(name = "input", location = "/login.jsp", type = "dispatcher")
    })
public String execute() throws Exception {
   System.out.println(user);
   ActionContext.getContext().getSession().put("user", user);
   return SUCCESS;
}

@Override
public void validate() {
   if (!"admin".equals(user.getUsername())) {
    addFieldError("user.username", "登录名不正确!");
   } else if (!"admin".equals(user.getPassword())) {
    addFieldError("user.password", "密码不正确!");
   }
}

public User getUser() {
   return user;
}

public void setUser(User user) {
   this.user = user;
}
}

web.xml文件

<filter>
   <filter-name>struts2</filter-name>
   <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
   <filter-name>struts2</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<welcome-file-list>
   <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

posted @ 2009-07-29 16:25 jadmin 阅读(78) | 评论 (0)编辑 收藏

验证Email地址是否合法

<script language="javascript">
function isEmail(email) {
  
if (email.search(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/) != -1) {
return true;
}
   
else {
   return false;
}

}
</script>

验证IP地址

IPV4_PATTERN = "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])";
IPV6_PATTERN = "([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}";

待续。。。

posted @ 2009-07-29 00:51 jadmin 阅读(70) | 评论 (0)编辑 收藏

1.准备工作

除了必要spring的支持外,还需要引入两个jar包,分别是activation.jar和mail.jar

2.代码

SimpleHtmlMailSender.java

/*
* @(#)SimpleHtmlMailSender.java 2009-7-28
*
* Copyright (c) 2009 by jadmin. All Rights Reserved.
*/

package com.jsoft.s2sh.util.mail;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

/**
* 用于发送简单的HTML文本邮件
*
* @author <a href="mailto:jadmin ON 126.com">jadmin</a>
* @version $Id: SimpleHtmlMailSender.java 2009-7-28 上午01:15:35$
* @see <a href="http://hi.baidu.com/jadmin">myblog</a>
*/
public class SimpleHtmlMailSender {

protected JavaMailSender sender;

public void setSender(JavaMailSender sender) {
   this.sender = sender;
}

public void sendMessage(String message,String to, String from, String subject, String encoding) throws MessagingException {
   MimeMessage msg = sender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(msg, true, encoding);
   helper.setTo(to);
   helper.setFrom(from);
   helper.setSubject(subject);
   helper.setText(message, true);

   sender.send(msg);
}

public static void main(String[] args) throws MessagingException {
   ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-mail.xml");
   String to = "etxp on   qq.com";
   String from = "etxp on 163.com";
   String subject = "感谢您对本站的关注,请激活您的帐号";
  
   String message = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=gb2312\"></head><body><h1><a href='#'>哈哈!"
    + "</a></h1></body></html>";

   SimpleHtmlMailSender sender = (SimpleHtmlMailSender) ctx.getBean("mailSender");
   sender.sendMessage(message, to, from, subject, "GB2312");

}

}

3.配置

applicationContext.xml(将此文件之余classpath下)

<?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: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.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">


<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
   <property name="host">
    <value>smtp.163.com</value>
   </property>
   <property name="username">
    <value>etxp</value>
   </property>
   <property name="password">
    <value>**********</value>
   </property>
   <property name="javaMailProperties">
    <props>
     <prop key="mail.smtp.auth">true</prop>
    </props>
   </property>
</bean>

<bean id="mailSender" class="com.jsoft.s2sh.util.mail.SimpleHtmlMailSender">
   <property name="sender">
    <ref bean="javaMailSender" />
   </property>
</bean>

</beans>

4.运行

posted @ 2009-07-28 04:29 jadmin 阅读(86) | 评论 (0)编辑 收藏

Spring在TransactionDefinition接口中7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:

1.PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

2.PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。

3.PROPAGATION_MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常。

4.PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。

5.PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。


6.PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。


7.PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

posted @ 2009-07-27 18:33 jadmin 阅读(70) | 评论 (0)编辑 收藏
在web应用开发中,为了使视图与数据逻辑分离,需要使用标签,jstl就是其中之一。
一、用法配置

JSTL的版本和servlet规范的版本不同时,配置方式是不同的,以下以servlet2.4和jstl1.1为例。

核心标签c:
1、web.xml中的配置如下(其实ide已经帮你配置好了)
</web-app>
2、在jsp页面导入声明
<%@ taglib prefix="c" uri=http://java.sun.com/jsp/jstl/core%>
二、常用标签
1、<c:out/>
用于输出内容
例子:<c:out value="abc"/><c:out value="${va}" ></c:out>
2、<c:set></c:set>
用于设置作用域变量
例子:<c:set value="Hello" var="sessionVar" scope="session"></c:set>
3、<c:remove />
用于清除作用域变量
<c:remove var="maxUser" scope="application"/>
4、<c:forEach/>
用于循环输出变量
属性:vars:循环体中可以引用的变量;begin:循环开始的下标;end:循环结束的下标;items:集合的名称;
例如:<c:forEach begin="0" end="5" items="array" var="s" />
${s}<br>
</c:forEach>
将输出array集合中的6个元素,array是作用域变量,可以是request,session,application作用域内属性变量(调用setAttribute方法设置的)。如果array中存放的是对象如User(包含name,id属性),如果想输出name属性的话
可以用${s.name},其等价于调用s.getName()方法。
5、<c:if/>
用于执行条件判断
例如:<c:if test="${empty sessionScope.name }">
<c:redirect url="testJSTLlogin.jsp" />
</c:if>
个人觉得此功能不够强大,还是脚本灵活
6、<c:choose><c:when></c:when>......<c:other></c:other>
用于执行条件判断相当于if,else if,else if... else
用法:c:choose标签用来选择执行语句
当c:when的test中判断结果为true时执行该标签中的内容;
如果所有c:when的test中判断结果都为false,则执行c:otherwise中的内容;
例子:

<c:choose>
<c:whentest="testCondition">
Content1
</c:when>
<c:whentest="testCondition">
Content2
</c:when>
<c:otherwise>
Content3
</c:otherwise>
</c:choose>

posted @ 2009-07-27 18:28 jadmin 阅读(103) | 评论 (0)编辑 收藏

3声明式管理Hibernate分布式事务

通过Spring,还可以很方便地切换至另一种事务管理策略。比如需要提供分布式事务管理策略时,只要替换一下配置即可,如代码10.29所示。

代码10.29 appContext-jta.xml

<beans>

<bean id="transactionManager"

class="org.springframework.transaction.jta.JtaTransactionManager">

    <property name="sessionFactory" >

      <ref bean="sessionFactory" />

    </property>

</bean>

<bean id="myDataSource1"

class="org.springframework.jndi.JndiObjectFactoryBean">

      <property name="jndiName">

          <value>java:comp/env/jdbc/myds1</value>

      </property>

</bean>

<bean id="myDataSource2"

class="org.springframework.jndi.JndiObjectFactoryBean">

      <property name="jndiName">

          <value>java:comp/env/jdbc/myds2</value>

      </property>

</bean>

<bean id="sessionFactory1"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="myDataSource1"/>

    <property name="configLocations">

      <value>hibernate.cfg1.xml</value>

    </property>

</bean>

<bean id="sessionFactory2"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="myDataSource2"/>

    <property name="configLocations">

      <value>hibernate.cfg2.xml</value>

    </property>

</bean>

<bean id="dao1"

class="daopackage1.DaoImpl">

    <property name="sessionFactory" ref="sessionFactory"/>

</bean>

<bean id="dao2"

class="daopackage2.DaoImp2">

    <property name="sessionFactory" ref="sessionFactory"/>

</bean>

</beans>

<bean id="business" class="businesspackage.BusinessFacadeImpl">

    <property name="dao1">

      <ref bean="dao1"/>

    </property>

    <property name="dao2">

      <ref bean="dao2"/>

    </property>

</bean>

<bean id="businessProxy"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <property name="transactionManager">

      <ref bean="transactionManager" />

    </property>

    <property name="target">

      <ref bean="business" />

    </property>

    <property name="transactionAttributes">

      <props>

        <prop key="business*">PROPAGATION_REQUIRED</prop>

        <prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>

      </props>

    </property>

</bean>

</beans>

可以看到,对于横跨多个Hibernate SessionFacotry的分布式事务,只需简单地将JtaTransactionManagerLocalSessionFactoryBean的定义结合起来就可以了,其中每个DAO通过bean属性得到各自的SessionFactory引用。

说明:如果所有底层数据源都是支持事务的容器,那么只需要对一个业务对象应用JtaTransactionManager策略,该对象就可以横跨多个DAO和多个Session Factory来划分事务了。使用Spring的最大好处就是,可通过配置来声明式地管理事务,无需对应用代码作任何改动。

posted @ 2009-07-27 01:52 jadmin 阅读(102) | 评论 (0)编辑 收藏

2声明式管理Hibernate本地事务

Spring提供了一种统一的IoC方式来管理Hibernate事务(本地或者分布式事务)。从Spring接手hibernate.cfg.xmlHibernate的基本配置文件)起,Hibernate事务便轻易交由Spring拖管了。

说明:在上一章介绍IBatisDAO的时候,曾经针对事务和DAO的关系简单的进行了探讨。通常DAO的粒度应该都是比较细的,即它们只是一些单步的CRUD操作,所以就需要引入一个业务对象来包裹DAO,这样,就可以在业务对象的基础上,提供更粗粒度的事务划分了(比如跨越多个DAO的方法调用进行事务管理)。

为了能对DAO进行更粗粒度的事务控制,需要为其增加一个业务对象。下面给出了该业务对象的接口和实现,如代码10.25~10.26所示。

代码10.25 StockFacade.java

package chapter10.spring.hibernate;

import chapter10.hibernate.domain.Category;

public interface StockFacade {

public void business1(Category category);

public void someOtherBusiness();

}

代码10.26 BusinessFacadeImpl.java

public class BusinessFacadeImpl implements StockFacade {

private StockDao stockDao;

public void setStockDao(StockDao stockDao) {

    this.stockDao = stockDao;

}

public void business1(Category category) {

    stockDao.createCategoryCascade(category);

    stockDao.retrieveProductBy(category);

    stockDao.deleteCategoryCascade(category);

}

public void someOtherBusiness() {

    //other implemention

}

}

接着给出关于事务策略的配置,其中使用了Spring针对Hibernate3给出的HibernateTransactionManager,它提供了Hibernate的本地事务管理策略,如代码10.27所示。

代码10.27 transaction-context.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="transactionManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager">

    <property name="sessionFactory" >

      <ref bean="sessionFactory" />

    </property>

</bean>

<bean id="business"

class="chapter10.spring.hibernate.BusinessFacadeImpl">

    <property name="stockDao">

      <ref bean="stockDao"/>

    </property>

</bean>

<bean id="businessProxy"

    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <property name="transactionManager">

      <ref bean="transactionManager" />

    </property>

    <property name="target">

       <ref bean="business" />

    </property>

    <property name="transactionAttributes">

      <props>

        <!--运行在当前事务范围内,如果当前没有启动事务,那么创建一个新的事务-->

        <prop key="business*">PROPAGATION_REQUIRED</prop>

        <!--运行在当前事务范围内,如果当前没有启动事务,那么抛出异常-->

         <prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>

      </props>

    </property>

</bean>

</beans>

代码10.28 HibernateTransactionUsageTest.java

package chapter10.spring.hibernate;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import chapter10.hibernate.domain.Category;

import junit.framework.TestCase;

public class HibernateTransactionUsageTest extends TestCase {

private StockFacade stockBusiness;

protected void setUp() throws Exception {

    String path = "ch10/spring/hibernate/";

    ApplicationContext ctx = new ClassPathXmlApplicationContext(

         new String[]{path+"dataAccessContext-support-local.xml",

             path+"transaction-context.xml"});

    stockBusiness = (StockFacade)ctx.getBean("businessProxy");

}

public void testTransctionUsage() {

    Category category = new Category("RABBIT");

    category.setName("Rabbit");

    category.setDescn("Desciption of Rabbit");

    stockBusiness.business1(category);

}

}

posted @ 2009-07-27 01:51 jadmin 阅读(70) | 评论 (0)编辑 收藏

1Spring上下文中配置SessionFactory

通过上文的描述,可以知道,Spring使用JdbcTemplate时必须和特定的数据源进行绑定。而在Hibernate中,数据源是对用户屏蔽的,它使用一个称为“Session”的强劲武器。

Session具有建立或取消对象的持久关联、同步对象状态和数据库以及事务管理等等复杂功能。SessionHibernate的核心概念,就如同SqlMap与之IBatis一样。Session的创建依赖于HibernateSessionFactorySessionFactorySession一样,也是Hibernate的核心组件。

IBatis的整合方式一样,Spring会将Hibernate基本配置文件中的数据源属性抽离到Spring自身的配置文件中,以提供统一的数据源管理和注射。

首先,给出Hibernate的配置文件,如代码10.19所示。

代码10.19 hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<!-- Database connection settings -->

<!--    <property name="connection.driver_class">-->

<!--      org.postgresql.Driver-->

<!--    </property>-->

<!--    <property name="connection.url">-->

<!--      jdbc:postgresql://1210.0.0.1:5432/hibernate-->

<!--    </property>-->

<!--    <property name="connection.username">postgres</property>-->

<!--    <property name="connection.password">1111</property>-->

    <!-- SQL dialect -->

    <property name="dialect">

      org.hibernate.dialect.PostgreSQLDialect

   </property>

    <!-- Echo all executed SQL to stdout -->

    <property name="show_sql">true</property>

    <!-- Drop and re-create the database schema on startup -->

    <property name="hbm2ddl.auto">create</property>

    <mapping resource="chapter7/hibernate/domain/Category.hbm.xml" />

    <mapping resource="chapter7/hibernate/domain/Product.hbm.xml" />

</session-factory>

</hibernate-configuration>

注意,代码10.19中被注释掉的部分,它将被抽离到了Spring的上下文配置文件,而其余部分则暂时保持不变。

下面给出Spring的配置文件,如代码10.20所示。

代码10.20 dataAccessContext-local.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="propertyConfigurer"

class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <property name="locations">

      <list>

        <value>classpath:jdbc.properties</value>

      </list>

    </property>

</bean>

<bean id="dataSource"

class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

    <property name="driverClassName" value="${jdbc.driverClassName}"/>

    <property name="url"

    value="jdbc:postgresql://127.0.0.1:5432/hibernate"/>

    <property name="username" value="${jdbc.username}"/>

    <property name="password" value="${jdbc.password}"/>

</bean>

<bean id="sessionFactory"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="dataSource"/>

    <property name="configLocations">

      <value>chapter7/spring/hibernate/hibernate.cfg.xml</value>

    </property>

<!--    <property name="mappingResources">-->

<!--      <list>-->

<!--        <value>chapter7/hibernate/domain/Category.hbm.xml</value>-->

<!--        <value>chapter7/hibernate/domain/Product.hbm.xml</value>-->

<!--      </list>-->

<!--    </property>-->

<!--    <property name="hibernateProperties">-->

<!--      <props>-->

<!--        <prop key="hibernate.dialect">

                 org.hibernate.dialect.PostgreSQLDialect

               </prop>-->

<!--        <prop key="show_sql">true</prop>-->

<!--        <prop key="hbm2ddl.auto">create</prop>-->

<!--      </props>-->

<!--    </property>-->

</bean>

</beans>

可以发现,从代码10.19抽离出的数据源,现在换成了ApacheDBCP连接池。当然,也可以换作其他实现,比如从一个JNDI上获取的数据源:

<bean id="dataSource"

class="org.springframework.jndi.JndiObjectFactoryBean">

    <property name="jndiName" value="java:comp/env/jdbc/myds"/>

</bean>

Spring提供了LocalSessionFactoryBean来创建本地的Hibernate SessionFactory,这就类似于上节中介绍的SqlMapClientFactoryBean(它用以创建IBatisSqlMap)。当然也可以创建绑定在JNDI上的SessionFactory,不过这通常只在EJB环境下使用。

注意:代码10.20中被注释掉的部分,如果不使用LocalSessionFactoryBeanconfigLocations属性读取Hibernate的原生配置文件,可由SpringLocalSessionFactoryBean负责配置Hibernate,它和hibernate.cfg.xml的作用完全一致,这时候就不需要Hibernate的原生配置了。

posted @ 2009-07-27 01:50 jadmin 阅读(85) | 评论 (0)编辑 收藏

Spring提供了一个很实用的工具,可以让Web应用灵活配置log4j,这个工具类是:

org.springframework.web.util.Log4jConfigListener

org.springframework.web.util.Log4jConfigServlet

由于:

Note that this class has been deprecated for containers implementing
Servlet API 2.4 or higher, in favor of
{@linkLog4jConfigListener}.</i><br>
According to Servlet 2.4, listeners must be initialized before load-on-startup
servlets. Many Servlet 2.3 containers already enforce this behavior
(see ContextLoaderServlet javadocs for details). If you use such a container,
this servlet can be replaced with Log4jConfigListener.

建议使用org.springframework.web.util.Log4jConfigListener,而非org.springframework.web.util.Log4jConfigServlet,下面来说下Log4jConfigListener的用法:

其实很简单,只要在web.xml文件中配置相关参数和注册此监听器即可,下面是相应的配置片段:

<!-- 配置log4j配置文件的路径,可以是xml或 properties文件(此参数必须配)-->
<context-param>
   <param-name>log4jConfigLocation</param-name>
   <param-value>WEB-INF/log4j.properties</param-value>
</context-param>

<!-- 每隔多少毫秒扫描一下配置文件的变化(此参数可选配) -->
<context-param>
   <param-name>log4jRefreshInterval</param-name>
   <param-value>60000</param-value>
</context-param>

<!-- spring框架默认定义webAppRootKey的值为webapp.root,若不配此参数默认值就是webapp.root(因此,此参数可选配) -->
<context-param>
   <param-name>webAppRootKey</param-name>
   <param-value>home</param-value>
</context-param>

<listener>
   <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

这样就可以在log4j的配置中如下进行了:

log4j.appender.DailyLog=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DailyLog.File=${home}/WEB-INF/logs/log4j.log
log4j.appender.DailyLog.Append=false
log4j.appender.DailyLog.DatePattern='.'yyyy-MM-dd
log4j.appender.DailyLog.layout=org.apache.log4j.PatternLayout
log4j.appender.DailyLog.layout.ConversionPattern=%p %d [%l]%n    - %m%n

posted @ 2009-07-25 03:34 jadmin 阅读(161) | 评论 (0)编辑 收藏
然后我们要改一下代理对象DynaProxyHello中的代码.如下:
1packagesinosoft.dj.aop.proxyaop;
2
3importjava.lang.reflect.InvocationHandler;
4importjava.lang.reflect.Method;
5importjava.lang.reflect.Proxy;
6
7publicclassDynaProxyHelloimplementsInvocationHandler{
8  /**
9      * 操作者
10     */

11    privateObject proxy;
12  /**
13      * 要处理的对象(也就是我们要在方法的前后加上业务逻辑的对象,如例子中的Hello)
14     */

15    privateObject delegate;
16
17/**
18      * 动态生成方法被处理过后的对象 (写法固定)
19      *
20      *@paramdelegate
21      *@paramproxy
22      *@return
23     */

24    publicObject bind(Object delegate,Object proxy){
25        
26        this.proxy=proxy;
27        this.delegate=delegate;
28        returnProxy.newProxyInstance(
29                this.delegate.getClass().getClassLoader(),this.delegate
30                         .getClass().getInterfaces(),this);
31     }

32  /**
33      * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说,要处理的对象的方法只能通过此方法调用
34      * 此方法是动态的,不是手动调用的
35     */

36    publicObject invoke(Object proxy, Method method, Object[] args)
37            throwsThrowable{
38         Object result=null;
39        try{
40            //反射得到操作者的实例
41             Class clazz=this.proxy.getClass();
42            //反射得到操作者的Start方法
43             Method start=clazz.getDeclaredMethod("start",
44                    newClass[]{ Method.class});
45            //反射执行start方法
46             start.invoke(this.proxy,newObject[]{ method });
47            //执行要处理对象的原本方法
48             result=method.invoke(this.delegate, args);
49//             反射得到操作者的end方法
50             Method end=clazz.getDeclaredMethod("end",
51                    newClass[]{ Method.class});
52//             反射执行end方法
53             end.invoke(this.proxy,newObject[]{ method });
54
55         }
catch(Exception e){
56             e.printStackTrace();
57         }

58        returnresult;
59     }

60
61}

62

然后我们把Test.java中的代码改一下.测试一下:
packagesinosoft.dj.aop.proxyaop;

publicclassTest{
    
publicstaticvoidmain(String[] args){
         IHello hello
=(IHello)newDynaProxyHello().bind(newHello(),newLoggerOperation());
         hello.sayGoogBye(
"Double J");
         hello.sayHello(
"Double J");
        
     }

}

结果还是一样的吧.

如果你想在每个方法之前加上日志记录,而不在方法后加上日志记录.你就把LoggerOperation类改成如下:
1packagesinosoft.dj.aop.proxyaop;
2
3importjava.lang.reflect.Method;
4
5publicclassLoggerOperationimplementsIOperation{
6
7    publicvoidend(Method method){
8        //Logger.logging(Level.DEBUGE, method.getName() + " Method end.");
9     }

10
11    publicvoidstart(Method method){
12         Logger.logging(Level.INFO, method.getName()+"Method Start!");
13     }

14
15}

16

运行一下.你就会发现,每个方法之后没有记录日志了. 这样,我们就把代理者和操作者解藕了!

下面留一个问题给大家,如果我们不想让所有方法都被日志记录,我们应该怎么去解藕呢.?
我的想法是在代理对象的public Object invoke(Object proxy, Method method, Object[] args)方法里面加上个if(),对传进来的method的名字进行判断,判断的条件存在XML里面.这样我们就可以配置文件时行解藕了.如果有兴趣的朋友可以把操作者,被代理者,都通过配置文件进行配置 ,那么就可以写一个简单的SpringAOP框架了.
posted @ 2009-07-24 20:43 jadmin 阅读(93) | 评论 (0)编辑 收藏
从上面的例子我们看出.只要你是采用面向接口编程,那么,你的任何对象的方法执行之前要加上记录日志的操作都是可以的.他(DynaPoxyHello)自动去代理执行被代理对象(Hello)中的每一个方法,一个java.lang.reflect.InvocationHandler接口就把我们的代理对象和被代理对象解藕了.但是,我们又发现还有一个问题,这个DynaPoxyHello对象只能跟我们去在方法前后加上日志记录的操作.我们能不能把DynaPoxyHello对象和日志操作对象(Logger)解藕呢?
结果是肯定的.让我们来分析一下我们的需求.
我们要在被代理对象的方法前面或者后面去加上日志操作代码(或者是其它操作的代码),
那么,我们可以抽象出一个接口,这个接口里就只有两个方法,一个是在被代理对象要执行方法之前执行的方法,我们取名为start,第二个方法就是在被代理对象执行方法之后执行的方法,我们取名为end .接口定义如下 :
1packagesinosoft.dj.aop.proxyaop;
2
3importjava.lang.reflect.Method;
4
5publicinterfaceIOperation{
6  /**
7      * 方法执行之前的操作
8      *@parammethod
9     */

10    voidstart(Method method);
11  /**
12      * 方法执行之后的操作
13      *@parammethod
14     */

15    voidend(Method method);
16}

17

我们去写一个实现上面接口的类.我们把作他真正的操作者,如下面是日志操作者的一个类:
LoggerOperation.java
packagesinosoft.dj.aop.proxyaop;

importjava.lang.reflect.Method;

publicclassLoggerOperationimplementsIOperation{

    
publicvoidend(Method method){
         Logger.logging(Level.DEBUGE, method.getName()
+"Method end.");
     }


    
publicvoidstart(Method method){
         Logger.logging(Level.INFO, method.getName()
+"Method Start!");
     }


}


posted @ 2009-07-24 20:42 jadmin 阅读(67) | 评论 (0)编辑 收藏

但是我们会发现一个问题,如果我们像Hello这样的类很多,那么,我们是不是要去写很多个HelloProxy这样的类呢.没错,是的.其实也是一种很麻烦的事.在jdk1.3以后.jdk跟我们提供了一个API   java.lang.reflect.InvocationHandler的类. 这个类可以让我们在JVM调用某个类的方法时动态的为些方法做些什么事.让我们把以上的代码改一下来看看效果.
同样,我们写一个IHello的接口和一个Hello的实现类.在接口中.我们定义两个方法;代码如下 :

IHello.java

1package sinosoft.dj.aop.proxyaop;
2
3public interface IHello {
4  /**
5      * 业务处理A方法
6      * @param name
7     */

8    void sayHello(String name);
9  /**
10      * 业务处理B方法
11      * @param name
12     */

13    void sayGoogBye(String name);
14}

15



Hello.java

1package sinosoft.dj.aop.proxyaop;
2
3public class Hello implements IHello {
4
5    public void sayHello(String name) {
6         System.out.println("Hello " + name);
7     }

8    public void sayGoogBye(String name) {
9         System.out.println(name+" GoodBye!");
10     }

11}

12


我们一样的去写一个代理类.只不过.让这个类去实现java.lang.reflect.InvocationHandler接口,代码如下:

1package sinosoft.dj.aop.proxyaop;
2
3import java.lang.reflect.InvocationHandler;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6
7public class DynaProxyHello implements InvocationHandler {
8
9   /**
10      * 要处理的对象(也就是我们要在方法的前后加上业务逻辑的对象,如例子中的Hello)
11     */

12    private Object delegate;
13
14/**
15      * 动态生成方法被处理过后的对象 (写法固定)
16      *
17      * @param delegate
18      * @param proxy
19      * @return
20     */

21    public Object bind(Object delegate) {
22        this.delegate = delegate;
23        return Proxy.newProxyInstance(
24                this.delegate.getClass().getClassLoader(), this.delegate
25                         .getClass().getInterfaces(), this);
26     }

27  /**
28      * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说,要处理的对象的方法只能通过此方法调用
29      * 此方法是动态的,不是手动调用的
30     */

31    public Object invoke(Object proxy, Method method, Object[] args)
32            throws Throwable {
33         Object result = null;
34        try {
35            //执行原来的方法之前记录日志
36             Logger.logging(Level.DEBUGE, method.getName() + " Method end .");
37            
38            //JVM通过这条语句执行原来的方法(反射机制)
39             result = method.invoke(this.delegate, args);
40            //执行原来的方法之后记录日志
41             Logger.logging(Level.INFO, method.getName() + " Method Start!");
42         }
catch (Exception e) {
43             e.printStackTrace();
44         }

45        //返回方法返回值给调用者
46        return result;
47     }

48
49}

50


上面类中出现的Logger类和Level枚举还是和上一上例子的实现是一样的.这里就不贴出代码了.

让我们写一个Test类去测试一下.代码如下:
Test.java

1package sinosoft.dj.aop.proxyaop;
2
3public class Test {
4    public static void main(String[] args) {
5         IHello hello = (IHello)new DynaProxyHello().bind(new Hello());
6         hello.sayGoogBye("Double J");
7         hello.sayHello("Double J");
8        
9     }

10}

11


运行输出的结果如下:

Tue Mar 04 21:24:03 CST 2008 sayGoogBye Method end .
Double J GoodBye!
2008-3-4 21:24:03 sayGoogBye Method Start!
Tue Mar
04 21:24:03 CST 2008 sayHello Method end .
Hello Double J
2008-3-4 21:24:03 sayHello Method Start!


由于线程的关系,第二个方法的开始出现在第一个方法的结束之前.这不是我们所关注的!

posted @ 2009-07-24 20:40 jadmin 阅读(82) | 评论 (0)编辑 收藏
好长时间没有用过Spring了. 突然拿起书.我都发现自己对AOP都不熟悉了.
其实AOP的意思就是面向切面编程.
OO注重的是我们解决问题的方法(封装成Method),而AOP注重的是许多解决解决问题的方法中的共同点,是对OO思想的一种补充!
还是拿人家经常举的一个例子讲解一下吧:
比如说,我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,或部分监控.也许我们就会在要一些方法前去加上一条日志记录,
我们写个例子看看我们最简单的解决方案
我们先写一个接口IHello.java代码如下:
1packagesinosoft.dj.aop.staticaop;
2
3publicinterfaceIHello{
4  /**
5      * 假设这是一个业务方法
6      *@paramname
7     */

8    voidsayHello(String name);
9}

10

里面有个方法,用于输入"Hello" 加传进来的姓名;我们去写个类实现IHello接口
packagesinosoft.dj.aop.staticaop;

publicclassHelloimplementsIHello{

    
publicvoidsayHello(String name){
         System.out.println(
"Hello"+name);
     }


}


现在我们要为这个业务方法加上日志记录的业务,我们在不改变原代码的情况下,我们会去怎么做呢?也许,你会去写一个类去实现IHello接口,并依赖Hello这个类.代码如下:
1packagesinosoft.dj.aop.staticaop;
2
3publicclassHelloProxyimplementsIHello{
4    privateIHello hello;
5
6    publicHelloProxy(IHello hello){
7        this.hello=hello;
8     }

9
10    publicvoidsayHello(String name){
11         Logger.logging(Level.DEBUGE,"sayHello method start.");
12         hello.sayHello(name);
13         Logger.logging(Level.INFO,"sayHello method end!");
14
15     }

16
17}

18

其中.Logger类和Level枚举代码如下:
Logger.java
1packagesinosoft.dj.aop.staticaop;
2
3importjava.util.Date;
4
5publicclassLogger{
6  /**
7      * 根据等级记录日志
8      *@paramlevel
9      *@paramcontext
10     */

11    publicstaticvoidlogging(Level level, String context){
12        if(level.equals(Level.INFO)){
13             System.out.println(newDate().toLocaleString()+""+context);
14         }

15        if(level.equals(Level.DEBUGE)){
16             System.err.println(newDate()+""+context);
17         }

18     }

19
20}

21
Level.java

1packagesinosoft.dj.aop.staticaop;
2
3publicenumLevel{
4     INFO,DEBUGE;
5}

6
那我们去写个测试类看看,代码如下:
Test.java
1packagesinosoft.dj.aop.staticaop;
2
3publicclassTest{
4    publicstaticvoidmain(String[] args){
5         IHello hello=newHelloProxy(newHello());
6         hello.sayHello("Doublej");
7     }

8}

9
运行以上代码我们可以得到下面结果:

Tue Mar0420:57:12CST2008sayHello method start.
Hello Doublej
2008-3-420:57:12sayHello method end!

从上面的代码我们可以看出,hello对象是被HelloProxy这个所谓的代理态所创建的.这样,如果我们以后要把日志记录的功能去掉.那我们只要把得到hello对象的代码改成以下:
1packagesinosoft.dj.aop.staticaop;
2
3publicclassTest{
4    publicstaticvoidmain(String[] args){
5         IHello hello=newHello();
6         hello.sayHello("Doublej");
7     }

8}

9

上面代码,可以说是AOP最简单的实现!
posted @ 2009-07-24 20:37 jadmin 阅读(70) | 评论 (0)编辑 收藏

<SCRIPT LANGUAGE="JavaScript">

       function selectInst(){

              var checkbox = document.getElementsByName("inst");

              for(i=0;i<checkbox.length;i++){

                     if(checkbox[i].checked){

                            var temp = checkbox[i].value;

                            alert(temp);

                     }

              }

       }

</SCRIPT>

<form>

       选择爱好:<br>

       <input type="checkbox" name="inst" value="足球">足球

       <input type="checkbox" name="inst" value="篮球">篮球

       <input type="checkbox" name="inst" value="音乐">音乐

       <input type="checkbox" name="inst" value="上网">上网

       <input type="checkbox" name="inst" value="跳舞">跳舞

       <input type="button" value="提交" onclick="selectInst()">

</form>
posted @ 2009-07-20 22:30 jadmin 阅读(78) | 评论 (0)编辑 收藏

<SCRIPT LANGUAGE="JavaScript">

       var checkFlag = "false";

       function selectAll(field){

              if(checkFlag=="false"){

                     for(i=0;i<field.length;i++){

                            field[i].checked=true;

                     }

                     checkFlag = "true";

              }else{

                     for(i=0;i<field.length;i++){

                            field[i].checked=false;

                     }

                     checkFlag = "false";

              }}

</SCRIPT>

<form>

       选择爱好: <br>

       <input type="checkbox" name="inst" value="足球">足球

       <input type="checkbox" name="inst" value="篮球">篮球

       <input type="checkbox" name="inst" value="音乐">音乐

       <input type="checkbox" name="inst" value="上网">上网

       <input type="checkbox" name="inst" value="跳舞">跳舞

       <input type="checkbox" name="chkAll" value="instAll" onclick="selectAll(this.form.inst)">全选<br>

</form>
posted @ 2009-07-20 22:28 jadmin 阅读(121) | 评论 (0)编辑 收藏
一、AOP 概念
Joinpoint:它定义在哪里加入你的逻辑功能,对于Spring AOP,Jointpoint指的就是Method。

Advice:特定的Jointpoint处运行的代码,对于Spring AOP 来讲,有Before advice、AfterreturningAdvice、ThrowAdvice、AroundAdvice(MethodInteceptor)等。

Pointcut:一组Joinpoint,就是说一个Advice可能在多个地方织入,

Aspect:这个我一直迷惑,它实际是Advice和Pointcut的组合,但是Spring AOP 中的Advisor也是这样一个东西,但是Spring中为什么叫Advisor而不叫做Aspect。

Weaving:将Aspect加入到程序代码的过程,对于Spring AOP,由ProxyFactory或者ProxyFactoryBean负责织入动作。

Target:这个很容易理解,就是需要Aspect功能的对象。

Introduction:引入,就是向对象中加入新的属性或方法,一般是一个实例一个引用对象。当然如果不引入属性或者引入的属性做了线程安全性处理或者只读属性,则一个Class一个引用也是可以的(自己理解)。Per-class lifecycle or per-instance life cycle

二、AOP 种类
1、静态织入:指在编译时期就织入Aspect代码,AspectJ好像是这样做的。

2、动态织入:在运行时期织入,Spring AOP属于动态织入,动态织入又分静动两种,静则指织入过程只在第一次调用时执行;动则指根据代码动态运行的中间状态来决定如何操作,每次调用Target的时候都执行(性能较差)。

三、Spring AOP 代理原理
Spring AOP 是使用代理来完成的,Spring 会使用下面两种方式的其中一种来创建代理:
1、JDK动态代理,特点只能代理接口,性能相对较差,需要设定一组代理接口。
2、CGLIB 代理,可代理接口和类(final method除外),性能较高(生成字节码)。


四、Spring AOP 通知类型
1、BeforeAdvice:前置通知需实现MethodBeforeAdvice,但是该接口的Parent是BeforeAdvice,致于什么用处我想可能是扩展性需求的设计吧。或者Spring未来也并不局限于Method的JoinPoint(胡乱猜测)。BeforeAdvice可以修改目标的参数,也可以通过抛出异常来阻止目标运行。

2、AfterreturningAdvice:实现AfterreturningAdvice,我们无法修改方法的返回值,但是可以通过抛出异常阻止方法运行。

3、AroundAdvice:Spring 通过实现MethodInterceptor(aopalliance)来实现包围通知,最大特点是可以修改返回值,当然它在方法前后都加入了自己的逻辑代码,因此功能异常强大。通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用)。

4、ThrowsAdvice:通过实现若干afterThrowing()来实现。

5、IntroductionInterceptor:Spring 的默认实现为DelegatingIntroductionInterceptor

五、Spring AOP Pointcut
以上只是Advice,如果不指定切入点,Spring 则使用所有可能的Jointpoint进行织入(当然如果你在Advice中进行方法检查除外)。因此切入点在AOP中扮演一个十分重要的角色。Spring 2.0 推荐使用AspectJ的Annocation的切入点表达式来定义切入点,或者使用<aop:xxx/>来定义AOP,这方面本篇不做考虑。

1、Pointcut:它是Spring AOP Pointcut的核心,定义了getClassFilter()和getMethodMatcher()两个方法。

2、ClassFilter:定义了matches(Class cls)一个方法。

3、MethodMatcher() 定义了matches(Method,Class),isRuntime(),matches(Mathod,Class,Object[])三个方法,如果isRuntime()返回true则表示为动态代理(实际是动态代理的动态代理),则调用第三个方法(每访问一次调用一次),否则调用第一个方法(并且只调用一次)

4、Spring AOP 静态切入点的几个实现。
ComposablePointcut 太复杂一个切入点无法表达就用这个,union MethodMatcher和ClassFilter或者intersection MethodMatcher、ClassFilter和Pointcut。为什么不实现union Pointcut? 而只能通过Pointcuts类对Pointcut进行union操作。

ControlFlowPointcut 想对程序的运行过程进行追踪就用这个

DynamicMatchMatcherPointcut 想用动态AOP 就用这个

JdkRegexpMethodPointcut 想使用正则表达式就用这个

Perl5RegexpMethodPointcut

NameMatchMethodPointcut 想用方法名字来匹配就用这个

StaticMethodMatcherPointcut 静态切入点就用这个
没有人反对你直接实现Pointcut:)。

六、Spring AOP 中的Advisor其实就是Aspect
1、 PointcutAdvisor
其实一般使用DefaultPointcutAdvisor就足够了,给它Advice和Pointcut。
当然如果想少写那么几行代码也可以使用NameMatchMethodPointcutAdvisor,RegexpMethodPointcutAdvisor等。
更多Advisor可以查看API文档。


2、 IntroductionAdvisor
默认实现为DefaultIntroductionAdvisor。

七、AOP ProxyFactory
使用代码实现AOP 可使用ProxyFactory
声明式AOP 可使用ProxyFactoryBean
ProxyFactoryBean 需要设定 target,interceptorNames(可以是Advice或者Advisor,注意顺序)
对接口代理需设置proxyInterfaces

八、自动代理
BeanNameAutoProxyCreator
Java代码
  1. <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">   
  2.      <property name="beanNames"><value>jdk*,onlyJdk</value></property>   
  3.      <property name="interceptorNames">   
  4.          <list>   
  5.              <value>myInterceptor</value>   
  6.          </list>   
  7.      </property>   
  8. </bean>  

DefaultAdvisorAutoProxyCreator
Java代码
  1. <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>   
  2. <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">   
  3.      <property name="transactionInterceptor" ref="transactionInterceptor"/>   
  4. </bean>   
  5. <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>   
  6. <bean id="businessObject1" class="com.mycompany.BusinessObject1">   
  7.      <!-- Properties omitted -->   
  8. </bean>   
  9. <bean id="businessObject2" class="com.mycompany.BusinessObject2"/>  

posted @ 2009-07-20 21:44 jadmin 阅读(146) | 评论 (0)编辑 收藏

需明确的几个概念:

l         通知(Advice):用于告知系统将有哪些新的行为。

l         切入点(Pointcut):定义了通知应该在应用到那些连接点。

l         目标对象(Target):被通知的对象。

l         代理(Proxy):将通知应用到目标对象后创建的对象。

Spring有两种代理创建方式:

1.   如果目标对象实现了一个或多个接口暴露的方法,Spring将使用JDKjava.lang.reflect.Proxy创建代理。这个类让Spring动态产生一个新的类,它实现了所需的接口,织入了通知,并且代理目标的所有请求。(这篇主要介绍这个方式)

2.   如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个子类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。

下面以一个实例说明Spring AOP的基本开发方法:

一.创建通知

Spring连接点模型是建立在方法拦截上,这意味着你编写的

Spring通知会在方法调用周围的各个地方织入系统中。

TestAopServiceAdvice实现了接口MethodBeforeAdvice(前置通知),并实现它的惟一的方法before,这个类就可以在调用目标对象前被调用。同样的还有AfterReturningAdvice(后置通知)MethodInterceptor(环绕通知),异常通知(ThrowsAdvice),引入通知等。

在这个方法中我们输出了一个字符串TestAopServiceAdvice,用于验证这个方法是否在目标对象前调用了。

注意:我们无法改变before方法中的参数argstarget中的值,args中存的是原来要传入目标对象的变量,target即指目标对象。

二.配置Spring XML配置文件

要在Spring中实现AOP,一般情况下需要配置4bean

1.   目标对象(target

2.   通知(advice)

3.   切入点(pointcut)

4.   代理(proxy)

切入点又分为静态切入点和动态切入点

l         静态切入点的意思是通知总是被执行,也是最常用的一种切入点。

l         动态切入点根据运行时方法的参数值决定通知是否被执行。

在图2中,定义了使用了一个Spring提供的静态切入点

NameMatchMethodPointAdvisor,它保证了当被调用的方法的名字与给出的映射名字相匹配的时候,这个切入点才匹配。

Spring提供的另一个静态切入点为RegexpMethodPointcutAdvisor,让你可以利用正则表达式来定义切入点,正则表达式需要jakarta-oro.jar包的支持。

使用ProxyBeanFactory可以创建一个被通知的类,即代理对象。它的最常用的三个控制行为的属性是:

l         proxyInterfaces:代理应该实现的接口列表。

l         interceptorNames:需要应用到目标对象上的通知Bean的名字。可以是拦截器、Advisor或其他通知类的名字。

注:在用容器的getBean方法时,应该是getBean(代理类的名字),而不是getBean(目标对象的名字),否则AOP无法工作。

posted @ 2009-07-20 21:43 jadmin 阅读(81) | 评论 (0)编辑 收藏
Spring2.5是Spring2.1各个里程碑版本的终结。

Spring2.5是对Spring2.0的增强,增加了一些新的特性:

  1. 全面支持java6和javaEE5(JDBC 4.0, JTA 1.1, JavaMail 1.4, JAX-WS 2.0等)
  2. 全特性的注释驱动依赖注入,包括对限定词的支持
  3. 支持基于classpath的组件扫描,自动侦测有注释的类
  4. 支持AspectJ切点表达式中包含bean name切点元素
  5. 内置AspectJ加载时编织,基于LoadTimeWeaver 提取
  6. 更多的XML配置文件的名字空间支持,比如context和jms等,最大程度提高编写的方便性
  7. 全面修订集成测试框架,支持JUnit4和TestNG
  8. 新的基于注释的Servlet MVC和Portlet MVC控制器风格配置
  9. 扩展SimpleJdbcTemplate功能,支持命名的参数等
  10. 官方认可的Websphere支持,支持WebSphere 6 UOWManager 机制
  11. Spring框架的jar文件,兼容OSGi绑定,并能直接使用
  12. Spring ApplicationContext可被部署为JCA RAR文件,用于非主导应用模块
  13. JCA 1.5消息终端管理,用于Spring管理的JMS和CCI消息监听器

另外,分发包有三种形式,增加了最小标准zip包和包含文档的zip包。
官方推荐升级所有2.0.x版本到2.5版本,因为可以从新特性中获益和显著提升性能。
Spring2.0可以简单的升级到2.5版本,只需替换相关jar文件。
Spring2.5仍然兼容JDK1.4.2+和J2EE1.3+。
posted @ 2009-07-20 01:45 jadmin 阅读(73) | 评论 (0)编辑 收藏

一、事务管理
事务传播
1、required:方法在一个事务中执行,如果调用的方法在一个事务中,则使用该事务,否则将创建一个新的事务。(必须有,有就用,没有就建)
2、mandatory:如果运行于事务中的客户调用了该方法,方法在客户的事务中执行。如果客户没有关联到事务中,容器就会抛出TransactionRequiredException.(必须有,有就用,没有报错)
3、requiresnew:方法将在一个新的事务中执行,如果调用的方法已经在一个事务中,则暂停旧的事务。在调用结束后恢复旧的事务。(必须有,有没有都要建)
4、supports:如果方法在一个事务中被调用,则使用该事务,否则不使用事务。(有没有都中,有就用,没有不用)
5、not_supported:如果方法在一个事务中被调用,容器会在调用之前终止该事务。在调用结束后,容器会恢复客户事务。如果客户没有关联到一个事务中,容器不会入运行在该方法启动一个新的事务。用notsupported属性标识不需要事务的方法。因为事务会带来更高的性能支出,所以这个属性可以提高性能。(不需要,有就挂起事务,没有直接运行)
6、Never:如果在一个事务中调用该方法,容器会抛出RemoteException。如果客户没有关联到一个事务中,容器不会在运行入该方法前启动一个新的事务。(必须没有,有就报错,没有就直接运行)
事务隔离
为什么要使用事物隔离,是因为事物并发引起的一些错误现象
并发问题:
脏读:一个事务读取了未提交的事务
不可重复读:同一个事务中多次读取同一个数据返回的结果不同
幻读:一个事务读取到了另一个事务已提交的insert数据。

事务并发处理:
共享锁:共享锁用于读取数据操作,它允许其他事务同时读取某锁定的资源,但不允许其他事务更新它。
排他锁:排它锁用于修改数据的场合。它锁定的资源,其他事务不能读取也不能修改。
更新锁:更新锁在更新操作的初始化阶段用来锁定可能要被修改的资源,从而避免使用共享锁造成的死锁现象。

事务隔离级别:
ReadUncommitted:读未提交数据,该选项指示数据库读取数据时不使用任何锁。在这种情况下,事务可以读取到未提交的数据,会出现脏读,不可重复读和幻读现象。

ReadCommited:
该选项只会返回"读取时间点"之前已提交的数据。因此可以避免脏读,但是会出现不可重复读,另外还会出现幻读现象。

RepeatableRead:该选项能够保证可重复读,可以避免脏读和不可重复读。

Serializable:该选项能够避免脏读、不可重复读和幻读现象,是最严格的隔离级别。



二、spring集成struts
1、应用服务器没有直接调用启动Spring的方法,但是应用服务器编译运行servlet,filter,listener,所以spring提供一个listener类,在服务器初始化的时候调用该类中的方法,所以在容器中配置如下:
<!-- 指定spring的配置文件,多个文件之间用逗号分隔 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:beans.xml</param-value>
</context-param>
<!-- 启动Spring容器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2、我们把我们需要交给spring管理的类在beans.xml中配置:
如<bean name="/user/regist"
class="cn.sun.ssh.web.action.UserManagerAction">
<property name="dao" ref="userDAO"></property>
</bean>
但是action是被引擎调用的,我们如何把需要的action交给引擎呢,通过重写struts中的requestprocessor类中的processactioncreate方法,在spring中获得action后交给引擎管理,这也是struts的一个扩展机制。
所以我们要在struts-config.xml中配置controller
<controller>
<set-property property="processorClass" value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>


三、spring集成hibernate
1、spring集成hibernate时把dao和sessionfactory交给spring管理

posted @ 2009-07-20 00:48 jadmin 阅读(59) | 评论 (0)编辑 收藏
        切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现。

        连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点 总是 代表一个方法的执行。 通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。

        通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。

        切入点(Pointcut): 匹配连接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

        引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

        目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

        AOP代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 注意:Spring 2.0最新引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。

        织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯JavaAOP框架一样,在运行时完成织入。

        通知的类型:

        前置通知(Before advice): 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

        返回后通知(After returning advice): 在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

        抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。

        后通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

        环绕通知(Around Advice): 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

===============================================

1.切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。


2.连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。


3.通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。


4.切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。


5.引入:为类添加新方法和属性。


6.目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。


7.代理:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。


8.织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:

编译期:切面在目标对象编译时织入.这需要一个特殊的编译器.
类装载期:切面在目标对象被载入JVM时织入.这需要一个特殊的类载入器.
运行期:切面在应用系统运行时织入.

posted @ 2009-07-20 00:41 jadmin 阅读(78) | 评论 (0)编辑 收藏

       数据库查询性能的提升也是涉及到开发中的各个阶段,在开发中选用正确的查询方法无疑是最基础也最简单的。

SQL语句的优化

       使用正确的SQL语句可以在很大程度上提高系统的查询性能。获得同样数据而采用不同方式的SQL语句在性能上的差距可能是十分巨大的。

       由于Hibernate是对JDBC的封装,SQL语句的产生都是动态由Hibernate自动完成的。Hibernate产生SQL语句的方式有两种:一种是通过开发人员编写的HQL语句来生成,另一种是依据开发人员对关联对象的访问来自动生成相应的SQL语句。

       至于使用什么样的SQL语句可以获得更好的性能要依据数据库的结构以及所要获取数据的具体情况来进行处理。在确定了所要执行的SQL语句后,可以通过以下三个方面来影响Hibernate所生成的SQL语句:

●   HQL语句的书写方法。

●   查询时所使用的查询方法。

●   对象关联时所使用的抓取策略。

使用正确的查询方法

       在前面已经介绍过,执行数据查询功能的基本方法有两种:一种是得到单个持久化对象的get()方法和load()方法,另一种是Query对象的list()方法和iterator()方法。在开发中应该依据不同的情况选用正确的方法。

       get()方法和load()方法的区别在于对二级缓存的使用上。load()方法会使用二级缓存,而get()方法在一级缓存没有找到的情况下会直接查询数据库,不会去二级缓存中查找。在使用中,对使用了二级缓存的对象进行查询时最好使用load()方法,以充分利用二级缓存来提高检索的效率。

       list()方法和iterator()方法之间的区别可以从以下几个方面来进行比较。

●   执行的查询不同

       list()方法在执行时,是直接运行查询结果所需要的查询语句,而iterator()方法则是先执行得到对象ID的查询,然后再根据每个ID值去取得所要查询的对象。因此,对于list()方式的查询通常只会执行一个SQL语句,而对于iterator()方法的查询则可能需要执行N+1条SQL语句(N为结果集中的记录数)。

       iterator()方法只是可能执行N+1条数据,具体执行SQL语句的数量取决于缓存的情况以及对结果集的访问情况。

●   缓存的使用

       list()方法只能使用二级缓存中的查询缓存,而无法使用二级缓存对单个对象的缓存(但是会把查询出的对象放入二级缓存中)。所以,除非重复执行相同的查询操作,否则无法利用缓存的机制来提高查询的效率。

       iterator()方法则可以充分利用二级缓存,在根据ID检索对象的时候会首先到缓存中查找,只有在找不到的情况下才会执行相应的查询语句。所以,缓存中对象的存在与否会影响到SQL语句的执行数量。

●   对于结果集的处理方法不同

       list()方法会一次获得所有的结果集对象,而且它会依据查询的结果初始化所有的结果集对象。这在结果集非常大的时候必然会占据非常多的内存,甚至会造成内存溢出情况的发生。

       iterator()方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。因此在访问中可以控制缓存中对象的数量,以避免占用过多缓存,导致内存溢出情况的发生。使用iterator()方法的另外一个好处是,如果只需要结果集中的部分记录,那么没有被用到的结果对象根本不会被初始化。所以,对结果集的访问情况也是调用iterator()方法时执行数据库SQL语句多少的一个因素。

       所以,在使用Query对象执行数据查询时应该从以上几个方面去考虑使用何种方法来执行数据库的查询操作。

使用正确的抓取策略

       所谓抓取策略(fetching strategy)是指当应用程序需要利用关联关系进行对象获取的时候,Hibernate获取关联对象的策略。抓取策略可以在O/R映射的元数据中声明,也可以在特定的HQL或条件查询中声明。

       Hibernate 3定义了以下几种抓取策略。

●   连接抓取(Join fetching)

       连接抓取是指Hibernate在获得关联对象时会在SELECT语句中使用外连接的方式来获得关联对象。

●   查询抓取(Select fetching)

       查询抓取是指Hibernate通过另外一条SELECT语句来抓取当前对象的关联对象的方式。这也是通过外键的方式来执行数据库的查询。与连接抓取的区别在于,通常情况下这个SELECT语句不是立即执行的,而是在访问到关联对象的时候才会执行。

●   子查询抓取(Subselect fetching)

       子查询抓取也是指Hibernate通过另外一条SELECT语句来抓取当前对象的关联对象的方式。与查询抓取的区别在于它所采用的SELECT语句的方式为子查询,而不是通过外连接。

●   批量抓取(Batch fetching)

       批量抓取是对查询抓取的优化,它会依据主键或者外键的列表来通过单条SELECT语句实现管理对象的批量抓取。

以上介绍的是Hibernate 3所提供的抓取策略,也就是抓取关联对象的手段。为了提升系统的性能,在抓取关联对象的时机上,还有以下一些选择。

●   立即抓取(Immediate fetching)

       立即抓取是指宿主对象被加载时,它所关联的对象也会被立即加载。

●   延迟集合抓取(Lazy collection fetching)

       延迟集合抓取是指在加载宿主对象时,并不立即加载它所关联的对象,而是到应用程序访问关联对象的时候才抓取关联对象。这是集合关联对象的默认行为。

●   延迟代理抓取(Lazy proxy fetching)

       延迟代理抓取是指在返回单值关联对象的情况下,并不在对其进行get操作时抓取,而是直到调用其某个方法的时候才会抓取这个对象。

●   延迟属性加载(Lazy attribute fetching)

       延迟属性加载是指在关联对象被访问的时候才进行关联对象的抓取。

       介绍了Hibernate所提供的关联对象的抓取方法和抓取时机,这两个方面的因素都会影响Hibernate的抓取行为,最重要的是要清楚这两方面的影响是不同的,不要将这两个因素混淆,在开发中要结合实际情况选用正确的抓取策略和合适的抓取时机。

       抓取时机的选择

       在Hibernate 3中,对于集合类型的关联在默认情况下会使用延迟集合加载的抓取时机,而对于返回单值类型的关联在默认情况下会使用延迟代理抓取的抓取时机。

       对于立即抓取在开发中很少被用到,因为这很可能会造成不必要的数据库操作,从而影响系统的性能。当宿主对象和关联对象总是被同时访问的时候才有可能会用到这种抓取时机。另外,使用立即连接抓取可以通过外连接来减少查询SQL语句的数量,所以,也会在某些特殊的情况下使用。

       然而,延迟加载又会面临另外一个问题,如果在Session关闭前关联对象没有被实例化,那么在访问关联对象的时候就会抛出异常。处理的方法就是在事务提交之前就完成对关联对象的访问。

       所以,在通常情况下都会使用延迟的方式来抓取关联的对象。因为每个立即抓取都会导致关联对象的立即实例化,太多的立即抓取关联会导致大量的对象被实例化,从而占用过多的内存资源。

       抓取策略的选取

       对于抓取策略的选取将影响到抓取关联对象的方式,也就是抓取关联对象时所执行的SQL语句。这就要根据实际的业务需求、数据的数量以及数据库的结构来进行选择了。

在这里需要注意的是,通常情况下都会在执行查询的时候针对每个查询来指定对其合适的抓取策略。指定抓取策略的方法如下所示:

       User user = (User) session.createCriteria(User.class)

                   .setFetchMode("permissions", FetchMode.JOIN)

                   .add( Restrictions.idEq(userId) )

                   .uniqueResult();

       本文介绍了查询性能提升的方法,关键是如何通过优化SQL语句来提升系统的查询性能。查询方法和抓取策略的影响也是通过执行查询方式和SQL语句的多少来改变系统的性能的。这些都属于开发人员所应该掌握的基本技能,避免由于开发不当而导致系统性能的低下。

       在性能调整中,除了前面介绍的执行SQL语句的因素外,对于缓存的使用也会影响系统的性能。通常来说,缓存的使用会增加系统查询的性能,而降低系统增加、修改和删除操作的性能(因为要进行缓存的同步处理)。所以,开发人员应该能够正确地使用有效的缓存来提高数据查询的性能,而要避免滥用缓存而导致的系统性能变低。在采用缓存的时候也应该注意调整自己的检索策略和查询方法,这三者配合起来才可以达到最优的性能。

       另外,事务的使用策略也会影响到系统的性能。选取正确的事务隔离级别以及使用正确的锁机制来控制数据的并发访问都会影响到系统的性能。

posted @ 2009-07-19 21:36 jadmin 阅读(73) | 评论 (0)编辑 收藏

       Hibernate是对JDBC的轻量级封装,因此在很多情况下Hibernate的性能比直接使用JDBC存取数据库要低。然而,通过正确的方法和策略,在使用Hibernate的时候还是可以非常接近直接使用JDBC时的效率的,并且,在有些情况下还有可能高于使用JDBC时的执行效率。

       在进行Hibernate性能优化时,需要从以下几个方面进行考虑:

●   数据库设计调整。

●   HQL优化。

●   API的正确使用(如根据不同的业务类型选用不同的集合及查询API)。

●   主配置参数(日志、查询缓存、fetch_size、batch_size等)。

●   映射文件优化(ID生成策略、二级缓存、延迟加载、关联优化)。

●   一级缓存的管理。

●   针对二级缓存,还有许多特有的策略。

●   事务控制策略。

       数据的查询性能往往是影响一个应用系统性能的主要因素。对查询性能的影响会涉及到系统软件开发的各个阶段,例如,良好的设计、正确的查询方法、适当的缓存都有利于系统性能的提升。

       系统性能的提升设计到系统中的各个方面,是一个相互平衡的过程,需要在应用的各个阶段都要考虑。并且在开发、运行的过程中要不断地调整和优化才能逐步提升系统的性能。

posted @ 2009-07-19 21:30 jadmin 阅读(45) | 评论 (0)编辑 收藏

       在前面介绍了Hibernate的缓存技术以及基本的用法,在这里就具体的Hibernate所提供的查询方法与Hibernate缓存之间的关系做一个简单的总结。

       在开发中,通常是通过两种方式来执行对数据库的查询操作的。一种方式是通过ID来获得单独的Java对象,另一种方式是通过HQL语句来执行对数据库的查询操作。下面就分别结合这两种查询方式来说明一下缓存的作用。

       通过ID来获得Java对象可以直接使用Session对象的load()或者get()方法,这两种方式的区别就在于对缓存的使用上。

●   load()方法

       在使用了二级缓存的情况下,使用load()方法会在二级缓存中查找指定的对象是否存在。

在执行load()方法时,Hibernate首先从当前Session的一级缓存中获取ID对应的值,在获取不到的情况下,将根据该对象是否配置了二级缓存来做相应的处理。

       如配置了二级缓存,则从二级缓存中获取ID对应的值,如仍然获取不到则还需要根据是否配置了延迟加载来决定如何执行,如未配置延迟加载则从数据库中直接获取。在从数据库获取到数据的情况下,Hibernate会相应地填充一级缓存和二级缓存,如配置了延迟加载则直接返回一个代理类,只有在触发代理类的调用时才进行数据库的查询操作。

       在Session一直打开的情况下,并在该对象具有单向关联维护的时候,需要使用类似Session.clear(),Session.evict()的方法来强制刷新一级缓存。

●   get()方法

       get()方法与load()方法的区别就在于不会查找二级缓存。在当前Session的一级缓存中获取不到指定的对象时,会直接执行查询语句从数据库中获得所需要的数据。

       在Hibernate中,可以通过HQL来执行对数据库的查询操作。具体的查询是由Query对象的list()和iterator()方法来执行的。这两个方法在执行查询时的处理方法存在着一定的差别,在开发中应该依据具体的情况来选择合适的方法。

●   list()方法

       在执行Query的list()方法时,Hibernate的做法是首先检查是否配置了查询缓存,如配置了则从查询缓存中寻找是否已经对该查询进行了缓存,如获取不到则从数据库中进行获取。从数据库中获取到后,Hibernate将会相应地填充一级、二级和查询缓存。如获取到的为直接的结果集,则直接返回,如获取到的为一些ID的值,则再根据ID获取相应的值(Session.load()),最后形成结果集返回。可以看到,在这样的情况下,list()方法也是有可能造成N次查询的。

       查询缓存在数据发生任何变化的情况下都会被自动清空。

●   iterator()方法

       Query的iterator()方法处理查询的方式与list()方法是不同的,它首先会使用查询语句得到ID值的列表,然后再使用Session的load()方法得到所需要的对象的值。

       在获取数据的时候,应该依据这4种获取数据方式的特点来选择合适的方法。在开发中可以通过设置show_sql选项来输出Hibernate所执行的SQL语句,以此来了解Hibernate是如何操作数据库的。

posted @ 2009-07-19 21:29 jadmin 阅读(52) | 评论 (0)编辑 收藏

查询缓存

       查询缓存是专门针对各种查询操作进行缓存。查询缓存会在整个SessionFactory的生命周期中起作用,存储的方式也是采用key-value的形式来进行存储的。

       查询缓存中的key是根据查询的语句、查询的条件、查询的参数和查询的页数等信息组成的。而数据的存储则会使用两种方式,使用SELECT语句只查询实体对象的某些列或者某些实体对象列的组合时,会直接缓存整个结果集。而对于查询结果为某个实体对象集合的情况则只会缓存实体对象的ID值,以达到缓存空间可以共用,节省空间的目的。

       在使用查询缓存时,除了需要设置hibernate.cache.provider_class参数来启动二级缓存外,还需要通过hibernate.cache.use_query_cache参数来启动对查询缓存的支持。

       另外需要注意的是,查询缓存是在执行查询语句的时候指定缓存的方式以及是否需要对查询的结果进行缓存。

       下面就来了解一下查询缓存的使用方法及作用。

       修改Hibernate配置文件

       首先需要修改Hibernate的配置文件,增加hibernate.cache.use_query_cache参数的配置。配置方法如下:

       <property name="hibernate.cache.use_query_cache">true</property>

       Hibernate配置文件的详细内容请参考配套光盘中的hibernate\src\cn\hxex\ hibernate\cache\hibernate.cfg.xml文件。

       编写主测试程序

       由于这是在前面二级缓存例子的基础上来开发的,所以,对于EHCache的配置以及视图对象的开发和映射文件的配置工作就都不需要再重新进行了。下面就来看一下主测试程序的实现方法,如清单14.11所示。

       清单14.11    主程序的实现

……

    public void run() {

           SessionFactory sf = QueryCacheMain.getSessionFactory();

           Session session = sf.getCurrentSession();

           session.beginTransaction();

           Query query = session.createQuery( "from User" );

           Iterator it = query.setCacheable( true ).list().iterator();

           while( it.hasNext() ) {

                  System.out.println( it.next() );

           }

           User user = (User)session.get( User.class, "1" );

           System.out.println( user );

           session.getTransaction().commit();

    }

       public static void main(String[] args) {

              QueryCacheMain main1 = new QueryCacheMain();

              main1.start();

              try {

                     Thread.sleep( 2000 );

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

              QueryCacheMain main2 = new QueryCacheMain();

              main2.start();

       }

}

       主程序在实现的时候采用了多线程的方式来运行。首先将“from User”查询结果进行缓存,然后再通过ID取得对象来检查是否对对象进行了缓存。另外,多个线程的执行可以看出对于进行了缓存的查询是不会执行第二次的。

       运行测试主程序

       接着就来运行测试主程序,其输出结果应该如下所示:

Hibernate: select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_ from USERINFO user0_

ID: 1

Namge:   galaxy

Age:       32

ID: 1

Namge:   galaxy

Age:       32

ID: 1

Namge:   galaxy

Age:       32

ID: 1

Namge:   galaxy

Age:       32

       通过上面的执行结果可以看到,在两个线程执行中,只执行了一个SQL查询语句。这是因为根据ID所要获取的对象在前面的查询中已经得到了,并进行了缓存,所以没有再次执行查询语句。

posted @ 2009-07-19 21:25 jadmin 阅读(62) | 评论 (0)编辑 收藏

二级缓存

       与Session相对的是,SessionFactory也提供了相应的缓存机制。SessionFactory缓存可以依据功能和目的的不同而划分为内置缓存和外置缓存。

       SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的副本,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来的。SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。

       SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的副本,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的二级缓存。

       Hibernate的二级缓存的实现原理与一级缓存是一样的,也是通过以ID为key的Map来实现对对象的缓存。

       由于Hibernate的二级缓存是作用在SessionFactory范围内的,因而它比一级缓存的范围更广,可以被所有的Session对象所共享。

二级缓存的工作内容

       Hibernate的二级缓存同一级缓存一样,也是针对对象ID来进行缓存。所以说,二级缓存的作用范围是针对根据ID获得对象的查询。

       二级缓存的工作可以概括为以下几个部分:

●   在执行各种条件查询时,如果所获得的结果集为实体对象的集合,那么就会把所有的数据对象根据ID放入到二级缓存中。

●   当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,如果查不到并且配置了二级缓存,那么会从二级缓存中查找,如果还查不到,就会查询数据库,把结果按照ID放入到缓存中。

●   删除、更新、增加数据的时候,同时更新缓存。

二级缓存的适用范围

       Hibernate的二级缓存作为一个可插入的组件在使用的时候也是可以进行配置的,但并不是所有的对象都适合放在二级缓存中。

       在通常情况下会将具有以下特征的数据放入到二级缓存中:

●   很少被修改的数据。

●   不是很重要的数据,允许出现偶尔并发的数据。

●   不会被并发访问的数据。

●   参考数据。

       而对于具有以下特征的数据则不适合放在二级缓存中:

●   经常被修改的数据。

●   财务数据,绝对不允许出现并发。

●   与其他应用共享的数据。

       在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,JDBC),因为那样Hibernate将不会知道数据已经被修改,也就无法保证缓存中的数据与数据库中数据的一致性。

二级缓存组件

       在默认情况下,Hibernate会使用EHCache作为二级缓存组件。但是,可以通过设置hibernate.cache.provider_class属性,指定其他的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。

       通过实现org.hibernate.cache.CacheProvider接口可以提供对不同二级缓存组件的支持。

       Hibernate内置支持的二级缓存组件如表14.1所示。

表14.1    Hibernate所支持的二级缓存组件

posted @ 2009-07-19 21:23 jadmin 阅读(45) | 评论 (0)编辑 收藏

       大家都知道,Hibernate是以JDBC为基础实现的持久层组件,因而其性能肯定会低于直接使用JDBC来访问数据库。因此,为了提高Hibernate的性能,在Hibernate组件中提供了完善的缓存机制来提高数据库访问的性能。

       什么是缓存

       缓存是介于应用程序和物理数据之间的,其作用是为了降低应用程序对物理数据访问的频次从而提高应用系统的性能。缓存思想的提出主要是因为对物理数据的访问效率要远远低于对内存的访问速度,因而采用了将部分物理数据存放于内存当中,这样可以有效地减少对物理数据的访问次数,从而提高系统的性能。

       缓存广泛地存在于我们所接触的各种应用系统中,例如数据库系统、Windows操作系统等,在进行物理数据的访问时无一例外地都使用了缓存机制来提高操作的性能。

       缓存内的数据是对物理数据的复制,因此一个缓存系统所应该包括的最基本的功能是数据的缓存和读取,同时在使用缓存的时候还要考虑缓存中的数据与物理数据的同步,也就是要保持两者是一致的。

       缓存要求对数据的读写速度很高,因此,一般情况下会选用内存作为存储的介质。但如果内存有限,并且缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。

       为了提高系统的性能,Hibernate也使用了缓存的机制。在Hibernate框架中,主要包括以下两个方面的缓存:一级缓存和二级缓存(包含查询缓存)。Hibernate中缓存的作用主要表现在以下两个方面:

●   通过主键(ID)加载数据的时候

●   延迟加载中

一级缓存

       Hibernate的一级缓存是由Session提供的,因此它只存在于Session的生命周期中,也就是当Session关闭的时候该Session所管理的一级缓存也会立即被清除。

       Hibernate的一级缓存是Session所内置的,不能被卸载,也不能进行任何配置。

       一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。所以说,一级缓存是以实体对象为单位进行存储的,在访问的时候使用的是主关键字ID。

       虽然,Hibernate对一级缓存使用的是自动维护的功能,没有提供任何配置功能,但是可以通过Session中所提供的方法来对一级缓存的管理进行手工干预。Session中所提供的干预方法包括以下两种。

●   evict() :用于将某个对象从Session的一级缓存中清除。

●   clear() :用于将一级缓存中的对象全部清除。

       在进行大批量数据一次性更新的时候,会占用非常多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。具体的实现方法如清单14.8所示。

       清单14.8    大批量更新时缓存的处理方法

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

  

for ( int i=0; i<100000; i++ ) {

    Customer customer = new Customer(……);

    session.save(customer);

    if ( i % 20 == 0 ) {

        //将本批插入的对象立即写入数据库并释放内存

        session.flush();

        session.clear();

    }

}

  

tx.commit();

session.close();

posted @ 2009-07-19 21:18 jadmin 阅读(87) | 评论 (0)编辑 收藏

并发控制

       当数据库系统采用Read Committed隔离级别时,会导致不可重复读取和两次更新丢失的并发问题,可以在应用程序中采用锁机制来避免这类问题的产生。

       从应用程序的角度上看,锁可以分为乐观锁和悲观锁两大类。

悲观锁

       在多个客户端可能读取同一笔数据或同时更新一笔数据的情况下,必须要有访问控制的手段,防止同一个数据被修改而造成混乱,最简单的手段就是对数据进行锁定。在自己进行数据读取或更新等动作时,锁定其他客户端不能对同一笔数据进行任何的动作。

       悲观锁(Pessimistic Locking),如其名称所示,悲观地认定每次资料存取时,其他的客户端也会存取同一笔数据,因此将会锁住该笔数据,直到自己操作完成后再解除锁。

       悲观锁假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,因而对数据采取了数据库层次的锁定状态,在锁定的时间内其他的客户不能对数据进行存取。对于单机或小系统而言,这并不成问题,然而如果是在网络上的系统,同时间会有许多访问的机器,如果每一次读取数据都造成锁定,其后继的存取就必须等待,这将造成效能上的问题,造成后继使用者的长时间等待。

       悲观锁通常透过系统或数据库本身的功能来实现,依赖系统或数据库本身提供的锁机制。Hibernate即是如此,可以利用Query或Criteria的setLockMode()方法来设定要锁定的表或列及其锁模式,可设定的锁模式有以下几个。

       LockMode.UPGRADE:利用数据库的for update子句进行锁定。

       LockMode.UPGRADE_NOWAIT:使用for update nowait子句进行锁定,在Oracle数据库中使用。

       下面来实现一个简单的例子,测试一下采用悲观锁时数据库是如何进行操作的。

       首先来完成一个实体对象——User,该对象包含了id,name和age三个属性,实现的方法如清单14.1所示。

       清单14.1    User对象的实现

package cn.hxex.hibernate.lock;

public class User {

       private String id;

       private String name;

       private Integer age;

      

       // 省略了getter和setter方法

       ……

}

       接下来就是映射文件的配置,由于该映射文件没有涉及到任何与其他对象的关联配置,所以实现的方法也非常简单,代码如清单14.2所示。

       清单14.2    User映射文件的实现

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

       "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

       "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.hxex.hibernate.lock">

       <class name="User" table="USERINFO">

              <id name="id" column="userId">

                 <generator class="uuid.hex"/>

           </id>

             

              <property name="name" column="name" type="java.lang.String"/>

              <property name="age" column="age" type="java.lang.Integer"/>

       </class>

</hibernate-mapping>

       另外一件重要的工作就是Hibernate的配置文件了,在这个配置文件中包含了连接数据库的参数以及其他一些重要的参数,实现的方法如清单14.3所示。

       清单14.3    Hibernate配置文件的实现

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

       <session-factory>

              <!-- 数据库的URL -->

              <!-- property name="hibernate.connection.url">

              jdbc:oracle:thin:@192.168.10.121:1521:HiFinance</property-->

              <property name="hibernate.connection.url">

       jdbc:mysql://localhost:3306/lockdb?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;autoReconnectForPools=true

        </property>

             

              <!-- 数据库的驱动程序 -->

              <!-- property name="hibernate.connection.driver_class">

              oracle.jdbc.driver.OracleDriver</property-->

              <property name="hibernate.connection.driver_class">

           com.mysql.jdbc.Driver

        </property>

              <!-- 数据库的用户名 -->

              <property name="hibernate.connection.username">lockdb</property>

              <!-- 数据库的密码 -->

              <property name="hibernate.connection.password">lockdb</property>

              <!-- 数据库的Dialect -->

              <!-- property name="hibernate.dialect">

              org.hibernate.dialect.Oracle9Dialect</property -->

              <property name="hibernate.dialect">

              org.hibernate.dialect.MySQLDialect</property>

              <!-- 输出执行的SQL语句 -->

              <property name="hibernate.show_sql">true</property>

             

              <property name="hibernate.current_session_context_class">thread</property>

             

              <property name="hibernate.hbm2ddl.auto">update</property>

              <!-- HBM文件列表 -->

              <mapping resource="cn/hxex/hibernate/lock/User.hbm.xml" />

       </session-factory>

</hibernate-configuration>

       最后要实现的就是测试主程序了,在测试主程序中包含了Hibernate的初始化代码以及悲观锁的测试方法。测试主程序的实现方法如清单14.4所示。

       清单14.4    测试主程序的实现

package cn.hxex.hibernate.lock;

import java.net.URL;

import java.util.List;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.hibernate.LockMode;

import org.hibernate.Query;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.cfg.Configuration;

public class LockMain {

    private static Log log = LogFactory.getLog( LockMain.class );

    // 静态Configuration和SessionFactory对象的实例(全局唯一的)

    private static Configuration configuration;

    private static SessionFactory sessionFactory;

    static

    {

        // 从默认的配置文件创建SessionFactory

        try

        {

                          URL configURL = ClassLoader.getSystemResource(

                               "cn/hxex/hibernate/lock/hibernate.cfg.xml" );

                          // 创建默认的Configuration对象的实例

            configuration = new Configuration();

            // 读取hibernate.properties或者hibernate.cfg.xml文件

            configuration.configure( configURL );

            // 使用静态变量来保持SessioFactory对象的实例

            sessionFactory = configuration.buildSessionFactory();

        }

        catch (Throwable ex)

        {

            // 输出异常信息

            log.error("Building SessionFactory failed.", ex);

            ex.printStackTrace();

            throw new ExceptionInInitializerError(ex);

        }

    }

      

    public static SessionFactory getSessionFactory() {

                  return sessionFactory;

    }

   

    public void testPessimisticLock() {

              SessionFactory sf = LockMain.getSessionFactory();

              Session session = sf.getCurrentSession();

              session.beginTransaction();

             

              Query query = session.createQuery("from User user");

              query.setLockMode("user", LockMode.UPGRADE);

              List users = query.list();

              for( int i=0; i<users.size(); i++ ) {

                     System.out.println( users.get( i ) );

              }

              session.getTransaction().commit();

    }

   

       public static void main(String[] args) {

             

              LockMain main = new LockMain();

              main.testPessimisticLock();

       }

}

       在上面的清单中,testPessimisticLock()方法就是测试悲观锁的方法,该方法在执行查询之前通过Query对象的setLockMode()方法设置了访问User对象的模式,这样,这个程序在执行的时候就会使用以下的SQL语句:

       select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_

       from USERINFO user0_ for update

       除了Query对象外,也可以在使用Session的load()或是lock()时指定锁模式。

       除了前面所提及的两种锁模式外,还有三种Hibernate内部自动对数据进行加锁的模式,但它的处理是与数据库无关的。

       LockMode.WRITE:在insert或update时进行锁定,Hibernate会在调用save()方法时自动获得锁。

       LockMode.READ:在读取记录时Hibernate会自动获得锁。

       LockMode.NONE:没有锁。

       如果数据库不支持所指定的锁模式,Hibernate会选择一个合适的锁替换,而不是抛出一个异常。

乐观锁

       乐观锁(Optimistic Locking)认为资料的存取很少发生同时存取的问题,因而不做数据库层次上的锁定。为了维护正确的数据,乐观锁是使用应用程序上的逻辑来实现版本控制的。

在使用乐观锁策略的情况下,数据不一致的情况一旦发生,有几个解决方法,一种是先更新为主,一种是后更新为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁。

       Hibernate中通过检查版本号来判断数据是否已经被其他人所改动,这也是Hibernate所推荐的方式。在数据库中加入一个version字段记录,在读取数据时连同版本号一同读取,并在更新数据时比较版本号与数据库中的版本号,如果等于数据库中的版本号则予以更新,并递增版本号,如果小于数据库中的版本号就抛出异常。

       下面就来在前面例子的基础上进行Hibernate乐观锁的测试。

       首先需要修改前面所实现的业务对象,在其中增加一个version属性,用来记录该对象所包含数据的版本信息,修改后的User对象如清单14.5所示。

       清单14.5    修改后的User对象

package cn.hxex.hibernate.lock;

public class User {

       private String id;

       private Integer version; // 增加版本属性

       private String name;

       private Integer age;

      

       // 省略了getter和setter方法

       ……

}

       然后是修改映射文件,增加version属性的配置。在这里需要注意的是,这里的version属性应该使用专门的<version>元素来进行配置,这样才能使其发挥乐观锁的作用。如果还使用<property>元素来进行配置,那么Hibernate只会将其作为一个普通的属性来进行处理。

修改后的映射文件如清单14.6所示。

       清单14.6    修改后的映射文件

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

       "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

       "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.hxex.hibernate.lock">

       <class name="User" table="USERINFO" optimistic-lock="version">

              <id name="id" column="userId">

                 <generator class="uuid.hex"/>

           </id>

             

              <version name="version" column="version" type="java.lang.Integer"/>

             

              <property name="name" column="name" type="java.lang.String"/>

              <property name="age" column="age" type="java.lang.Integer"/>

       </class>

</hibernate-mapping>

       接下来还要进行测试主程序的修改。由于需要模拟两个人同时修改同一个记录的情况,所以在这里需要将主程序修改为是可以多线程执行的,然后在run()方法中,调用对User对象的修改程序。

       实现后的主测试程序如清单14.7所示。

       清单14.7    修改后的测试主程序

package cn.hxex.hibernate.lock;

import java.net.URL;

import java.util.List;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.hibernate.LockMode;

import org.hibernate.Query;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.Transaction;

import org.hibernate.cfg.Configuration;

public class LockMain extends Thread{

……

    public void testOptimisticLock() {

           SessionFactory sf = LockMain.getSessionFactory();

           Session session = sf.openSession();

           Transaction tx = session.beginTransaction();

          

           User userV1 = (User)session.load( User.class, "1" );

          

           // 等第二个进程执行

           try {

                     sleep( 3000 );

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

          

              userV1.setAge(new Integer(32));

              tx.commit();

              session.close();

    }

   

    public void run() {

                  testOptimisticLock();

    }

   

       public static void main(String[] args) {

             

              // LockMain main = new LockMain();

              // main.testPessimisticLock();

             

              LockMain main1 = new LockMain();

              main1.start();

              LockMain main2 = new LockMain();

              main2.start();

       }

}

       最后,执行测试主程序,在控制台中应该看到类似下面的输出:

Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?

Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?

Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?

Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?

2006-10-3 21:27:20 org.hibernate.event.def.AbstractFlushingEventListener performExecutions

严重: Could not synchronize database state with session

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [cn.hxex.hibernate.lock.User#1]

……

       在Hibernate所执行的UPDATE语句中可以看到,version字段是作为更新的条件来执行的。对于第二个进程来说,由于数据库中的记录已经被第一个进程更新(更新的同时会导致version自动增加),就必然会导致第二个进程操作的失败。Hibernate正是利用这种机制来避免两次更新问题的出现。

posted @ 2009-07-19 21:11 jadmin 阅读(158) | 评论 (0)编辑 收藏

       在现在的B/S体系结构的软件开发中,对于数据库事务处理中最常使用的方式是每个用户请求一个事务。也就是说,当服务器端接收到一个用户请求后,会开始一个新的事务,直到对用户请求的所有处理都进行完毕并且完成了响应用户请求的所有输出之后才会关闭这个事务。

       对于使用Hibernate实现持久化功能的系统来说,事务的处理是这样的:服务器端在接收到用户的请求后,会创建一个新的Hibernate Session对象,然后通过该Session对象开始一个新的事务并且之后所有对数据库的操作都通过该Session对象来进行。最后,完成将响应页面发送到客户端的工作后再提交事务并且关闭Session。

       Session的对象是轻型的,非线程安全的,所以在每次用户请求时创建,请求处理完毕后丢弃。

       那么,该如何实现这种方式的事务处理呢?处理的难点在于如何在业务处理之前创建Session并开始事务以及在业务处理之后提交事务并关闭Session。对于现在的Web应用来说,通常情况下是通过ServletFilter来完成事务处理的操作。这样,就可以轻松地实现在用户请求到达服务器端的时候创建Session并开始事务,而服务器端响应处理结束之前提交事务并关闭Session。

       另外一个问题是,在ServletFilter中创建的Session是如何传递给业务处理方法中的呢?处理的方法是通过一个ThreadLocal变量来把创建的Session对象绑定到处理用户请求的线程上去,这样就可以使任何的业务处理方法可以轻松得到Session对象。

       Hibernate中事务处理的具体方法可以参照前面的网络博客的实例。

       但是这种事务处理的方式还是会遇到一些问题,其中最突出的就是更新冲突的问题。例如,某个操作人员进入了用户信息的修改页面,在经过一段时间的对用户信息的修改后,进行提交操作,而与此同时可能会有另外一个操作人员也进行了相同的操作,这样在处理提交的时候就会产生冲突。

       产生这个冲突的原因在于在开发中需要使用多个数据库事务来实现一个应用事务。也就是说,在应用程序层,应该将读取用户信息、显示修改页面以及用户提交工作来作为一个事务进行处理,在处理的过程中应该避免其他操作人员进行类似的操作。

       回想前面的介绍,我们对于数据库事务所采取的策略是每个用户请求一个事务,而上面的业务处理则至少需要两个请求才能完成。这样,两者之间就存在着一定的矛盾,这也就导致了不可重复读取和两次更新问题的发生。

       为了解决并发中数据访问的问题,通常会采用锁的机制来实现数据访问的排他性,从而避免两次更新问题的发生。

posted @ 2009-07-19 21:07 jadmin 阅读(68) | 评论 (0)编辑 收藏

       为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。

●   未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

●   授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

●   可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

●   序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

       隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

       通过前面的介绍已经知道,通过选用不同的隔离等级就可以在不同程度上避免前面所提及的在事务处理中所面临的各种问题。所以,数据库隔离级别的选取就显得尤为重要,在选取数据库的隔离级别时,应该注意以下几个处理的原则:

       首先,必须排除“未授权读取”,因为在多个事务之间使用它将会是非常危险的。事务的回滚操作或失败将会影响到其他并发事务。第一个事务的回滚将会完全将其他事务的操作清除,甚至使数据库处在一个不一致的状态。很可能一个已回滚为结束的事务对数据的修改最后却修改提交了,因为“未授权读取”允许其他事务读取数据,最后整个错误状态在其他事务之间传播开来。

       其次,绝大部分应用都无须使用“序列化”隔离(一般来说,读取幻影数据并不是一个问题),此隔离级别也难以测量。目前使用序列化隔离的应用中,一般都使用悲观锁,这样强行使所有事务都序列化执行。

       剩下的也就是在“授权读取”和“可重复读取”之间选择了。我们先考虑可重复读取。如果所有的数据访问都是在统一的原子数据库事务中,此隔离级别将消除一个事务在另外一个并发事务过程中覆盖数据的可能性(第二个事务更新丢失问题)。这是一个非常重要的问题,但是使用可重复读取并不是解决问题的唯一途径。

       假设使用了“版本数据”,Hibernate会自动使用版本数据。Hibernate的一级Session缓存和版本数据已经为你提供了“可重复读取隔离”绝大部分的特性。特别是,版本数据可以防止二次更新丢失的问题,一级Session缓存可以保证持久载入数据的状态与其他事务对数据的修改隔离开来,因此如果使用对所有的数据库事务采用授权读取隔离和版本数据是行得通的。

       “可重复读取”为数据库查询提供了更好的效率(仅对那些长时间的数据库事务),但是由于幻影读取依然存在,因此没必要使用它(对于Web应用来说,一般也很少在一个数据库事务中对同一个表查询两次)。

       也可以同时考虑选择使用Hibernate的二级缓存,它可以如同底层的数据库事务一样提供相同的事务隔离,但是它可能弱化隔离。假如在二级缓存大量使用缓存并发策略,它并不提供重复读取语义(例如,后面章节中将要讨论的读写,特别是非严格读写),很容易可以选择默认的隔离级别:因为无论如何都无法实现“可重复读取”,因此就更没有必要拖慢数据库了。另一方面,可能对关键类不采用二级缓存,或者采用一个完全的事务缓存,提供“可重复读取隔离”。那么在业务中需要使用到“可重复读取”吗?如果你喜欢,当然可以那样做,但更多的时候并没有必要花费这个代价。

posted @ 2009-07-19 21:04 jadmin 阅读(63) | 评论 (0)编辑 收藏

       数据库的事务处理是在进行数据库应用开发中必须进行处理的一个问题。那么对于选择Hibernate作为持久层组件,了解Hibernate的事务处理机制就显得尤为重要了。

事务的基本概念

       事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。所以,应该把它们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。

       针对上面的描述可以看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。

       事务具有以下4个基本特征。

●   Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。

●   Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。

●   Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。

●   Durability(持久性):事务结束后,事务处理的结果必须能够得到固化。

       数据库肯定是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。

●   更新丢失(Lost update):两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。

●   脏读取(Dirty Reads):一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚。

●   不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如,在两次读取的中途,有另外一个事务对该行数据进行了修改,并提交。

●   两次更新问题(Second lost updates problem):无法重复读取的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。

●   虚读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

posted @ 2009-07-19 21:04 jadmin 阅读(149) | 评论 (0)编辑 收藏

6.5.4 使用HibernateCallBack

HibernateTemplate还提供了一种更加灵活的方式来操作数据库,通过这种方式可以完全使用Hibernate的操作方式。HibernateTemplate的灵活访问方式可通过如下两个方法完成:

   ● Object execute(HibernateCallback action)。

   ● List execute(HibernateCallback action)。

这两个方法都需要一个HibernateCallback的实例,HibernateCallback实例可在任何有效的Hibernate数据访问中使用。程序开发者通过HibernateCallback,可以完全使用Hibernate灵活的方式来访问数据库,解决Spring封装Hibernate后灵活性不足的缺陷。

HibernateCallback是一个接口,该接口包含一个方法doInHibernate(org.hibernate. Session session),该方法只有一个参数Session。在开发中提供HibernateCallback实现类时,必须实现接口里包含的doInHibernate方法,在该方法体内即可获得Hibernate Session的引用,一旦获得了Hibernate Session的引用,就可以完全以Hibernate的方式进行数据库访问。

注意:doInHibernate方法内可以访问Session,该Session对象是绑定在该线程的Session实例。该方法内的持久层操作,与不使用Spring时的持久层操作完全相同。这保证了对于复杂的持久层访问,依然可以使用Hibernate的访问方式。

下面的代码对HibernateDaoSupport类进行扩展(虽然Spring 2.0的HibernateTemplate提供了一个分页方法setMaxResults,但仅此一个方法依然不能实现分页查询),这种扩展主要是为该类增加了3个分页查询的方法,分页查询时必须直接调用Hibernate的Session完成,因此,必须借助于HibernateCallBack的帮助。

public class YeekuHibernateDaoSupport extends HibernateDaoSupport

{

    /**

    * 使用hql 语句进行分页查询操作

    * @param hql 需要查询的hql语句

    * @param offset 第一条记录索引

    * @param pageSize 每页需要显示的记录数

    * @return 当前页的所有记录

    */

    public List findByPage(final String hql,

        final int offset, final int pageSize)

    {

        //HibernateDaoSupport已经包含了getHibernateTemplate()方法

        List list = getHibernateTemplate().executeFind(new
        HibernateCallback()

            {

                public Object doInHibernate(Session session)

                    throws HibernateException, SQLException

                //该方法体内以Hibernate方法进行持久层访问

                {

                    List result = session.createQuery(hql)

                                        .setFirstResult(offset)

                                         .setMaxResults(pageSize)

                                        .list();

                    return result;

                }

            });

        return list;

    }

    /**

    * 使用hql 语句进行分页查询操作

    * @param hql 需要查询的hql语句

    * @param value 如果hql有一个参数需要传入,value就是传入的参数

    * @param offset 第一条记录索引

    * @param pageSize 每页需要显示的记录数

    * @return 当前页的所有记录

    */

    public List findByPage(final String hql , final Object value ,

        final int offset, final int pageSize)

    {

        List list = getHibernateTemplate().executeFind(new
        HibernateCallback()

            {

                public Object doInHibernate(Session session)

                    throws HibernateException, SQLException

                {

                    //下面查询的是最简单的Hiberante HQL查询

                    List result = session.createQuery(hql)

                                         .setParameter(0, value)

                                        .setFirstResult(offset)

                                         .setMaxResults(pageSize)

                                        .list();

                    return result;

                }

            });

        return list;

    }

    /**

    * 使用hql 语句进行分页查询操作

    * @param hql 需要查询的hql语句

    * @param values 如果hql有多个参数需要传入,values就是传入的参数数组

    * @param offset 第一条记录索引

    * @param pageSize 每页需要显示的记录数

    * @return 当前页的所有记录

    */

    public List findByPage(final String hql, final Object[] values,

        final int offset, final int pageSize)

    {

        List list = getHibernateTemplate().executeFind(new
        HibernateCallback()

            {

                public Object doInHibernate(Session session)

                    throws HibernateException, SQLException

                {

                    Query query = session.createQuery(hql);

                    for (int i = 0 ; i < values.length ; i++)

                    {

                        query.setParameter( i, values[i]);

                    }

                    List result = query.setFirstResult(offset)

                                       .setMaxResults(pageSize)

                                       .list();

                    return result;

                }

            });

        return list;

    }

}

在上面的代码实现中,直接使用了getHibernateTemplate()方法,这个方法由Hibernate- DaoSupport提供。而YeekuHibernateDaoSupport是HibernateDaoSupport的子类,因此,可以直接使用该方法。

当实现doInHibernate(Session session)方法时,完全以Hibernate的方式进行数据库访问,这样保证了Hibernate进行数据库访问的灵活性。

注意:Spring提供的XxxTemplate和XxxCallBack互为补充,二者体现了Spring框架设计的用心良苦:XxxTemplate对通用操作进行封装,而XxxCallBack解决了封装后灵活性不足的缺陷。

6.5.5 实现DAO组件

为了实现DAO组件,Spring提供了大量的XxxDaoSupport类,这些DAO支持类对于实现DAO组件大有帮助,因为这些DAO支持类已经完成了大量基础性工作。

Spring为Hibernate的DAO提供了工具类HibernateDaoSupport。该类主要提供如下两个方法以方便DAO的实现:

   ● public final HibernateTemplate getHibernateTemplate()。

   ● public final void setSessionFactory(SessionFactory sessionFactory)。

其中,setSessionFactory方法可用于接收Spring的ApplicationContext的依赖注入,可接收配置在Spring的SessionFactory实例,getHibernateTemplate方法用于返回通过SessionFactory产生的HibernateTemplate实例,持久层访问依然通过HibernateTemplate实例完成。

下面实现的DAO组件继承了Spring提供的HibernateDaoSupport类,依然实现了PersonDao接口,其功能与前面提供的PersonDao实现类完全相同。其代码如下:

public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao

{

    /**

     * 加载人实例

     * @param id 需要加载的Person实例的主键值

     * @return 返回加载的Person实例

     */

    public Person get(int id)

    {

        return (Person)getHibernateTemplate().get(Person.class, new
        Integer(id));

    }

    /**

     * 保存人实例

     * @param person 需要保存的Person实例

     */   

    public void save(Person person)

    {

        getHibernateTemplate().save(person);

    }

    /**

     * 修改Person实例

     * @param person 需要修改的Person实例

     */

    public void update(Person person)

    {

        getHibernateTemplate().update(person);

    }

    /**

     * 删除Person实例

     * @param id 需要删除的Person的id

     */

    public void delete(int id)

    {

        getHibernateTemplate().delete(getHibernateTemplate().
        get(Person.class, new Integer(id)));

    }

    /**

     * 删除Person实例

     * @param person 需要删除的Person实例

     */

    public void delete(Person person)

    {

        getHibernateTemplate().delete(person);

    }

    /**

     * 根据用户名查找Person

     * @param name 用户名

     * @return 用户名对应的全部用户

     */

    public List findByPerson(String name)

    {

        return getHibernateTemplate().find("from Person p where p.name
        like ?" , name);       

    }

    /**

    * 返回全部的Person实例

    * @return 全部的Person实例

    */

    public List findAllPerson()

    {

        return getHibernateTemplate().find("from Person ");

    }

}

上面的代码与前面的PersonDAOImpl对比会发现,代码量大大减少。事实上,DAO的实现依然借助于HibernateTemplate的模板访问方式,只是HibernateDaoSupport将依赖注入SessionFactory的工作已经完成,获取HibernateTemplate的工作也已完成。该DAO的配置必须依赖于SessionFactory,配置文件与前面部署DAO组件的方式完全相同,此处不再赘述。

在继承HibernateDaoSupport的DAO实现里,Hibernate Session的管理完全不需要打开代码,而由Spring来管理。Spring会根据实际的操作,采用“每次事务打开一次session”的策略,自动提高数据库访问的性能。

6.5.6 使用IoC容器组装各种组件

至此为止,J2EE应用所需要的各种组件都已经出现了,从MVC层的控制器组件,到业务逻辑组件,以及持久层的DAO组件,已经全部成功实现。应用程序代码并未将这些组件耦合在一起,代码中都是面向接口编程,因此必须利用Spring的IoC容器将他们组合在一起。

从用户角度来看,用户发出HTTP请求,当MVC框架的控制器组件拦截到用户请求时,将调用系统的业务逻辑组件,而业务逻辑组件则调用系统的DAO组件,而DAO组件则依赖于SessionFactory和DataSource等底层组件实现数据库访问。

从系统实现角度来看,IoC容器先创建SessionFactory和DataSource等底层组件,然后将这些底层组件注入给DAO组件,提供一个完整的DAO组件,并将此DAO组件注入给业务逻辑组件,从而提供一个完整的业务逻辑组件,而业务逻辑组件又被注入给控制器组件,控制器组件负责拦截用户请求,并将处理结果呈现给用户——这一系列的衔接都由Spring的IoC容器提供实现。

下面给出关于如何在容器中配置J2EE组件的大致模板,其模板代码如下:

<?xml version="1.0" encoding="GBK"?>

<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定义Hibernate的SessionFactory Bean -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依赖注入数据源,注入的正是上文中定义的dataSource -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources属性用来列出全部映射文件 -->

        <property name="mappingResources">

            <list>

                <!-- 以下用来列出所有的PO映射文件 -->

                <value>lee/Person.hbm.xml</value>

                <!-- 此处还可列出更多的PO映射文件 -->

            </list>

        </property>

          <!-- 定义Hibernate的SessionFactory属性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的连接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 指定启动应用时,是否根据Hibernate映射文件创建数据表 -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

        </property>

    </bean>

    <!-- 配置Person持久化类的DAO Bean -->

    <bean id="personDao" class="lee.PersonDaoImpl">

        <!-- 采用依赖注入来传入SessionFactory的引用 -->

        <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

    <!-- 下面能以相同的方式配置更多的持久化Bean -->

    ...

    <bean id="myService" class="lee.MyServiceImp">

        <!-- 注入业务逻辑组件所必需的DAO组件 -->

        <property name="peronDdao" ref=" personDao "/>

        <!-- 此处可采用依赖注入更多的DAO组件 -->

        ...

    </bean>

    <!-- 配置控制器Bean,设置起作用域为Request -->

    <bean name="/login" class="lee.LoginAction" scope="request">

        <!-- 依赖注入控制器所必需的业务逻辑组件 -->

        <property name="myService" ref=" myService "/>

    </bean>

</beans>

在上面的配置文件中,同时配置了控制器Bean、业务逻辑组件Bean、DAO组件Bean以及一些基础资源Bean。各组件的组织被解耦到配置文件中,而不是在代码层次的低级耦合。

当客户端的HTTP请求向/login.do发送请求时,将被容器中的lee.LoginAction拦截,LoginAction调用myService Bean,myService Bean则调用personDao等系列DAO组件,整个流程将系统中的各组件有机地组织在一起。

注意:在实际应用中,很少会将DAO组件、业务逻辑组件以及控制组件都配置在同一个文件中。而是在不同配置文件中,配置相同一组J2EE应用组件。

6.5.7 使用声明式事务

在上面的配置文件中,部署了控制器组件、业务逻辑组件、DAO组件,几乎可以形成一个完整的J2EE应用。但有一个小小的问题:事务控制。系统没有任何事务逻辑,没有事务逻辑的应用是不可想象的。

Spring提供了非常简洁的声明式事务控制,只需要在配置文件中增加事务控制片段,业务逻辑代码无须任何改变。Spring的声明式事务逻辑,甚至支持在不同事务策略之间切换。

配置Spring声明式事务时,通常推荐使用BeanNameAutoProxyCreator自动创建事务代理。通过这种自动事务代理的配置策略,增加业务逻辑组件,只需要在BeanNameAutoProxyCreator Bean配置中增加一行即可,从而避免了增量式配置。

在上面的配置模板文件中增加如下配置片段,系统的myService业务逻辑组件将变成事务代理Bean,从而为业务逻辑方法增加事务逻辑。

    <!-- 配置Hibernate的局部事务管理器 -->

    <!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager
    接口,针对采用Hibernate持久化连接的特定实现 -->

    <bean id="transactionManager"

          class="org.springframework.orm.hibernate3.
         HibernateTransactionManager">

        <!-- HibernateTransactionManager Bean需要依赖注入一个SessionFactory
        bean的引用 -->

         <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

    <!-- 配置事务拦截器Bean -->

    <bean id="transactionInterceptor"

        class="org.springframework.transaction.interceptor.
        TransactionInterceptor">

        <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->

        <property name="transactionManager" ref="transactionManager"/>

        <property name="transactionAttributes">

            <!-- 下面定义事务传播属性 -->

            <props>

                <prop key="insert*">PROPAGATION_REQUIRED</prop>

                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

        </property>

    </bean>

    <!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->

    <bean class="org.springframework.aop.framework.autoproxy.
    BeanNameAutoProxyCreator">

    <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->

        <property name="beanNames">

            <!-- 下面是所有需要自动创建事务代理的Bean -->

            <list>

                <value>myService</value>

                <!-- 下面还可增加需要增加事务逻辑的业务逻辑Bean -->

                ...

            </list>

            <!-- 此处可增加其他需要自动创建事务代理的Bean -->

        </property>

        <!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->

        <property name="interceptorNames">

            <list>

                <value>transactionInterceptor</value>

                <!-- 此处可增加其他新的Interceptor -->

            </list>

        </property>

    </bean>

一旦增加了如上的配置片段,系统中的业务逻辑方法就有了事务逻辑。这种声明式事务配置方式可以在不同的事务策略之间自由切换。

提示:尽量使用声明式事务配置方式,而不要在代码中完成事务逻辑。

posted @ 2009-07-19 10:24 jadmin 阅读(374) | 评论 (0)编辑 收藏

6.5 Spring整合Hibernate

时至今日,可能极少有J2EE应用会直接以JDBC方式进行持久层访问。毕竟,用面向对象的程序设计语言来访问关系型数据库,是一件让人沮丧的事情。大部分时候,J2EE应用都会以ORM框架来进行持久层访问,在所有的ORM框架中,Hibernate以其灵巧、轻便的封装赢得了众多开发者的青睐。

Spring具有良好的开放性,能与大部分ORM框架良好整合。下面将详细介绍Spring与Hibernate的整合。

6.5.1 Spring提供的DAO支持

DAO模式是一种标准的J2EE设计模式,DAO模式的核心思想是,所有的数据库访 问,都通过DAO组件完成,DAO组件封装了数据库的增、删、改等原子操作。而业务逻辑组件则依赖于DAO组件提供的数据库原子操作,完成系统业务逻辑的实现。

对于J2EE应用的架构,有非常多的选择,但不管细节如何变换,J2EE应用都大致可分为如下3层:

   ● 表现层。

   ● 业务逻辑层。

   ● 数据持久层。

轻量级J2EE架构以Spring IoC容器为核心,承上启下。其向上管理来自表现层的Action,向下管理业务逻辑层组件,同时负责管理业务逻辑层所需的DAO对象。各层之间负责传值的是值对象,也就是JavaBean实例。

图6.5精确地描绘了轻量级J2EE架构的大致情形。

DAO组件是整个J2EE应用的持久层访问的重要组件,每个J2EE应用的底层实现都难以离开DAO组件的支持。Spring对实现DAO组件提供了许多工具类,系统的DAO组件可通过继承这些工具类完成,从而可以更加简便地实现DAO组件。

Spring的DAO支持,允许使用相同的方式、不同的数据访问技术,如JDBC、Hibernate或JDO。Spring的DAO在不同的持久层访问技术上提供抽象,应用的持久层访问基于Spring的DAO抽象。因此,应用程序可以在不同的持久层技术之间切换。

Spring提供了一系列的抽象类,这些抽象将被作为应用中DAO实现类的父类。通过继承这些抽象类,Spring简化了DAO的开发步骤,能以一致的方式使用数据库访问技术。不管底层采用JDBC、JDO或Hibernate,应用中都可采用一致的编程模型。

图6.5 轻量级J2EE应用架构

应用的DAO类继承这些抽象类,会大大简化应用的开发。最大的好处是,继承这些抽象类的DAO能以一致的方式访问数据库,意味着应用程序可以在不同的持久层访问技术中切换。

除此之外,Spring提供了一致的异常抽象,将原有的Checked异常转换包装成Runtime异常,因而,编码时无须捕获各种技术中特定的异常。Spring DAO体系中的异常,都继承DataAccessException,而DataAccessException异常是Runtime的,无须显式捕捉。通过DataAccessException的子类包装原始异常信息,从而保证应用程序依然可以捕捉到原始异常信息。

Spring提供了多种数据库访问技术的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring可以使用相同的访问模式、不同的数据库访问技术。就Hibernate的持久层访问技术而言,Spring提供了如下3个工具类(或接口)来支持DAO组件的实现:

   ● HibernateDaoSupport。

   ● HibernateTemplate。

   ● HibernateCallBack。

6.5.2 管理Hibernate的SessionFactory

前面介绍Hibernate时已经知道,在通过Hibernate进行持久层访问时,Hibernate的SessionFactory是一个非常重要的对象,它是单个数据库映射关系编译后的内存镜像。大部分情况下,一个J2EE应用对应一个数据库,也即对应一个SessionFactory对象。

在纯粹的Hibernate访问中,应用程序需要手动创建SessionFactory实例,可想而知,这不是一个优秀的策略。在实际开发中,希望以一种声明式的方式管理SessionFactory实例,直接以配置文件来管理SessionFactory实例,在示范Struts的PlugIn扩展点时,大致示范了这种方式(请参阅2.12.1节的内容)。

Spring的IoC容器则提供了更好的管理方式,它不仅能以声明式的方式配置Session- Factory实例,也可充分利用IoC容器的作用,为SessionFactory注入数据源引用。

下面是Spring配置文件中配置Hibernate SessionFactory的示范代码:

<?xml version="1.0" encoding="GBK"?>

<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
    destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定义Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依赖注入数据源,正是上文定义的dataSource -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources属性用来列出全部映射文件 -->

        <property name="mappingResources">

            <list>

                <!-- 以下用来列出所有的PO映射文件 -->

                <value>lee/MyTest.hbm.xml</value>

            </list>

        </property>

          <!-- 定义Hibernate的SessionFactory属性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的连接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 配置启动应用时,是否根据Hibernate映射自动创建数据表 -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

           </props>

        </property>

    </bean>

</beans>

一旦在Spring的IoC容器中配置了SessionFactory Bean,它将随应用的启动而加载,并可以充分利用IoC容器的功能,将SessionFactory Bean注入任何Bean,比如DAO组件。一旦DAO组件获得了SessionFactory Bean的引用,就可以完成实际的数据库访问。

当然,Spring也支持访问容器数据源。如果需要使用容器数据源,可将数据源Bean修改成如下配置:

<!-- 此处配置JNDI数据源 -->

<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

    <property name="jndiName">

        <!-- 指定数据源的JNDI -->

        <value>java:comp/env/jdbc/myds</value>

    </property>

</bean>

可见,以声明式的方式管理SessionFactory实例,可以让应用在不同数据源之间切换。如果应用更换数据库等持久层资源,只需对配置文件进行简单修改即可。

提示:以声明式的方式管理SessionFactory,非常类似于早期将数据库服务的相关信息放在web.xml文件中进行配置。这种方式是为了提供更好的适应性,当持久层服务需要更改时,应用代码无须任何改变。

6.5.3 使用HibernateTemplate

HibernateTemplate提供持久层访问模板,使用HibernateTemplate无须实现特定接口,它只需要提供一个SessionFactory的引用就可执行持久化操作。SessionFactory对象既可通过构造参数传入,也可通过设值方式传入。HibernateTemplate提供如下3个构造函数:

   ● HibernateTemplate()。

   ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。

   ● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。

第一个构造函数,构造一个默认的HibernateTemplate实例。因此,使用Hibernate- Template实例之前,还必须使用方法setSessionFactory(SessionFactory sessionFactory)来为HibernateTemplate传入SessionFactory的引用。

第二个构造函数,在构造时已经传入SessionFactory引用。

第三个构造函数,其boolean型参数表明,如果当前线程已经存在一个非事务性的Session,是否直接返回此非事务性的Session。

在Web应用中,通常启动时自动加载ApplicationContext,SessionFactory和DAO对象都处在Spring上下文管理下,因此无须在代码中显式设置,可采用依赖注入完成Session- Factory和DAO的解耦,依赖关系通过配置文件来设置,如下所示:

<?xml version="1.0" encoding="GBK"?>

<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定义Hibernate的SessionFactory Bean -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依赖注入数据源,注入的正是上文中定义的dataSource -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources属性用来列出全部映射文件 -->

        <property name="mappingResources">

            <list>

                <!-- 以下用来列出所有的PO映射文件 -->

                <value>lee/Person.hbm.xml</value>

            </list>

        </property>

          <!-- 定义Hibernate的SessionFactory属性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的连接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 指定启动应用时,是否根据Hibernate映射文件创建数据表 -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

        </property>

    </bean>

    <!-- 配置Person持久化类的DAO bean -->

    <bean id="personDao" class="lee.PersonDaoImpl">

        <!-- 采用依赖注入来传入SessionFactory的引用 -->

        <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

</beans>

在PersonDao组件中,所有的持久化操作都通过HibernateTemplate实例完成,而HibernateTemplate操作数据库非常简洁,大部分CRUD操作都可通过一行代码解决问题。下面介绍如何通过HibernateTemplate进行持久层访问。

HibernateTemplate提供了非常多的常用方法来完成基本的操作,比如通常的增加、删除、修改、查询等操作,Spring 2.0更增加了对命名SQL查询的支持,也增加了对分页的支持。大部分情况下,使用Hibernate的常规用法,就可完成大多数DAO对象的CRUD操作。下面是HibernateTemplate的常用方法简介:

   ● void delete(Object entity),删除指定持久化实例。

   ● deleteAll(Collection entities),删除集合内全部持久化类实例。

   ● find(String queryString),根据HQL查询字符串来返回实例集合。

   ● findByNamedQuery(String queryName),根据命名查询返回实例集合。

   ● get(Class entityClass, Serializable id),根据主键加载特定持久化类的实例。

   ● save(Object entity),保存新的实例。

   ● saveOrUpdate(Object entity),根据实例状态,选择保存或者更新。

   ● update(Object entity),更新实例的状态,要求entity是持久状态。

   ● setMaxResults(int maxResults),设置分页的大小。

下面是一个完整DAO类的源代码:

public class PersonDaoImpl implements PersonDao

{

    //执行持久化操作的HibernateTemplate实例

    private HibernateTemplate ht = null;

    private SessionFactory sessionFactory;

    //该DAO组件持久化操作所需的SessionFactory对象

    public void setSessionFactory(SessionFactory sessionFactory)

    {

        this.sessionFactory = sessionFactory;

    }

    //用于根据SessionFactory实例返回HibernateTemplate实例的方法

    private HibernateTemplate getHibernateTemplate()

    {

        if (ht == null)

        {

            ht = new HibernateTemplate(sessionFactory);

        }

        return ht;

    }

    /**

     * 加载人实例

     * @param id 需要加载的Person实例的主键值

     * @return 返回加载的Person实例

     */

    public Person get(int id)

    {

        return (Person)getHibernateTemplate().get(Person.class, new
        Integer(id));

    }

    /**

     * 保存人实例

     * @param person 需要保存的Person实例

     */   

    public void save(Person person)

    {

        getHibernateTemplate().save(person);

    }

    /**

     * 修改Person实例

     * @param person 需要修改的Person实例

     */

    public void update(Person person)

    {

        getHibernateTemplate().update(person);

    }

    /**

     * 删除Person实例

     * @param id 需要删除的Person的id

     */

    public void delete(int id)

    {

        getHibernateTemplate().delete(getHibernateTemplate().get(Person.
        class,new Integer(id)));

    }

    /**

     * 删除Person实例

     * @param person 需要删除的Person实例

     */

   public void delete(Person person)

    {

        getHibernateTemplate().delete(person);

    }

    /**

     * 根据用户名查找Person

     * @param name 用户名

     * @return 用户名对应的全部用户

     */

    public List findByName(String name)

    {

        return getHibernateTemplate().find("from Person p where p.name
        like ?" , name);

    }

    /**

    * 返回全部的Person实例

    * @return 全部的Person实例

    */

    public List findAllPerson()

    {

        return getHibernateTemplate().find("from Person ");

    }

}

通过上面实现DAO组件的代码可以看出,通过HibernateTemplate进行持久层访问的代码如此清晰,大部分CRUD操作一行代码即可完成,完全无须Hibernate访问那些繁琐的步骤。而且,一旦DAO组件获得了SessionFactory的引用,即可很轻易地创建HibernateTemplate实例。

提示:HibernateTemplate是Spring众多模板工具类之一,Spring正是通过这种简便地封装,完成了开发中大量需要重复执行的工作。

posted @ 2009-07-19 10:24 jadmin 阅读(671) | 评论 (0)编辑 收藏

Struts的plug-in配置部分明确指出,Spring的配置文件有两个:applicationContext.xml和action-servlet.xml。其实,完全可以使用一个配置文件。通常,习惯将Action Bean配置在控制器的context内。action-servlet.xml用于配置表现层上下文,其详细配置信息如下:

<?xml version="1.0" encoding="gb2312"?>

<!-- 指定Spring配置文件的根元素,以及对应的Schame信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 每个request请求产生一个新实例,将所有该请求的作用域配置成request -->

    <bean name="/login" class="lee.LoginAction" scope="request">

        <property name="vb" ref="vb"/>

    </bean>

</beans>

因为每次请求都应该启动新的Action处理用户请求,因此,应将Action的作用域配置成Request。

注意:ActionServlet转发请求时,是根据Bean的name属性,而不是id属性。因此,此处确定的name属性与Struts的action属性相同。

applicationContext.xml只有一个bean配置,即vb bean。其详细配置如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring 配置文件的根元素,以及对应的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置ValidBean实例 -->

    <bean id="vb" class="lee.ValidBeanImpl"/>

</beans>

ValidBeanImpl是一个业务逻辑bean,本示例程序中仅作简单的判断,ValidBeanImpl的源代码如下:

//面向接口编程,实现ValidBean接口

public class ValidBeanImpl implements ValidBean

{

    //根据输入的用户名和密码判断是否有效

    public boolean valid(String username,String pass)

    {

        //有效,返回true

        if (username.equals("scott") && pass.equals("tiger"))

        {

            return true;

        }

        return false;

    }

}

注意:上面的业务逻辑组件非常简单,它只是一个示意。如果是真实的应用,业务逻辑组件应该通过DAO组件来实现业务逻辑方法。

应用的业务逻辑控制器,Action则负责调用业务逻辑组件的方法,并根据业务逻辑组件方法的返回值,确定如何响应用户请求。下面是该示例应用控制器的代码:

//业务控制器继承Action

public class LoginAction extends Action

{

    //action控制器将调用的业务逻辑组件

    private ValidBean vb;

    //依赖注入业务逻辑组件的setter方法

    public void setVb(ValidBean vb)

    {

        this.vb = vb;

    }

    //必须重写该核心方法,该方法actionForm将表单的请求参数封装成值对象

    public ActionForward execute(ActionMapping mapping, ActionForm form,

       HttpServletRequest request, HttpServletResponse response)throws
       Exception

    {

        //form由ActionServlet转发请求时创建,封装了所有的请求参数

        LoginForm loginForm = (LoginForm)form;

        //获取username请求参数

        String username = loginForm.getUsername();

        //获取pass请求参数

        String pass = loginForm.getPass();

        //下面为服务器端的数据校验

        String errMsg = "";

        //判断用户名不能为空

        if (username == null || username.equals(""))

        {

            errMsg += "您的用户名丢失或没有输入,请重新输入";

        }

        //判断密码不能为空

        else if(pass == null || pass.equals(""))

        {

            errMsg += "您的密码丢失或没有输入,请重新输入";

        }

        //如果用户名和密码不为空,才调用业务逻辑组件

        else

        {

            //vb是业务逻辑组件,由容器注入

            if (vb.valid(username,pass))

            {

                return mapping.findForward("welcome");

            }

            else

            {

                errMsg = "您的用户名和密码不匹配";

            }

        }

        //判断是否生成了错误信息

        if (errMsg != null && !errMsg.equals(""))

        {

            //如果有错误信息,将错误信息保存在request里,并跳转到input对应的
            forward对象

            request.setAttribute("err" , errMsg);

            return mapping.findForward("input");

        }

        else

        {

            //如果没有错误信息,跳转到welcome对应的forward对象

            return mapping.findForward("welcome");

        }

    }

}

在本应用中,使用了Struts的客户端数据校验,让Action继承ValidatorActionForm即可。ActionForm的代码非常简单,此处不再赘述。

为了完成数据校验,还应该编写数据校验规则文件。在struts-config.xml文件的尾部,另有一个plug-in用来加载校验文件,其中validator-rules.xml文件位于struts压缩包的lib下,直接复制过来即可使用,而validator.xml必须自己编写,validator.xml文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 验证规则文件的文件头,包括DTD等信息 -->

<!DOCTYPE form-validation PUBLIC

          "-//Apache Software Foundation//DTD Commons Validator Rules
          Configuration 1.1.3//EN"

          "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">

<!-- 验证文件的根元素 -->

<form-validation>

    <!-- 所有需要验证的form都放在formset里 -->

<formset>

    <!-- 需要验证的form名,该名与struts里配置的名相同 -->

    <form name="loginForm">

        <!-- 指定该form的username域必须满足的规则:必填、模式匹配 -->

        <field property="username" depends="required,mask">

            <arg key="loginForm.username" position="0"/>

            <var>

                <!-- 确定匹配模式的正则表达式 -->

            <var-name>mask</var-name>

            <var-value>^[a-zA-Z]+$</var-value>

            </var>

        </field>

        <!-- 指定该form的pass域必须满足的规则:必填 -->

        <field property="pass" depends="required">

            <msg name="required" key="pass.required"/>

            <arg key="loginForm.pass" position="0"/>

        </field>

    </form>

</formset>

</form-validation>

上面示例程序的结构非常清晰:表现层组件(Action)配置在action-servlet.xml文件中,而业务逻辑层组件(vb)配置在applicationContext.xml文件中,如果应用中有DAO组件,将DAO组件配置在dao-context.xml文件中。将3个文件放在plug-in元素里一起加载。

文本框:图6.3  DelegatingRequestProcessor整合策略的登录失败效果DelegatingRequestProcessor会将请求转发到Action,该Action已经处于IoC容器管理之下,因此,可以方便地访问容器中的其他Bean。

通过配置文件可以看出,Action根本无须type属性,即struts-config.xml中Action根本没有实例化过,DelegatingRequestProcessor将请求转发给Spring容器中的同名Bean。这种转发的时机非常早,避免了创建struts-config.xml配置文件中的Action,因而性能非常好。

图6.3是采用这种整合策略的执行效果。

6.4.4 使用DelegatingActionProxy

使用DelegatingRequestProcessor简单方便,但有一个缺点,RequestProcessor是Struts的一个扩展点,也许应用程序本身就需要扩展RequestProcessor,而DelegatingRequest- Processor已经使用了这个扩展点。

为了重新利用Struts的RequestProcessor这个扩展点,有两个做法:

   ● 应用程序的RequestProcessor不再继承Struts的RequestProcessor,改为继承DelegatingRequestProcessor。

   ● 使用DelegatingActionProxy。

前者常常有一些未知的风险,而后者是Spring推荐的整合策略。使用Delegating- ActionProxy与DelegatingRequestProcessor的目的都只有一个,将请求转发给Spring管理的Bean。

DelegatingRequestProcessor直接替换了原有的RequestProcessor,在请求转发给action之前,转发给Spring管理的Bean;而DelegatingActionProxy则被配置成Struts的Action,即所有的请求先被ActionServlet截获,请求被转发到对应的Action,而action的实现类全都是DelegatingActionProxy,DelegatingActionProxy再将请求转发给Spring容器的Action。

可以看出,使用DelegatingActionProxy比使用DelegatingRequestProcessor要晚一步转发到Spring的context。但通过这种方式可以避免占用扩展点。

与使用DelegatingRequestProcessor相比,使用DelegatingActionProxy仅需要去掉controller配置元素,并将所有的action实现类改为DelegatingActionProxy即可。详细的配置文件如下:

<!-- XML文件的版本和编码集 -->

<?xml version="1.0" encoding="gb2312"?>

<!-- struts配置文件的文件头,包括DTD等信息 -->

<!DOCTYPE struts-config PUBLIC

          "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

          "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<!-- struts配置文件的根元素 -->

<struts-config>

    <!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->

    <form-beans>

        <!-- 定义了一个formbean,确定formbean名和实现类 -->

        <form-bean name="loginForm" type="lee.LoginForm"/>

    </form-beans>

    <!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->

    <action-mappings>

        <!-- 这里只定义了一个action。必须配置action的type元素为
        DelegatingActionProxy -->

        <action path="/login" type="org.springframework.web.struts.
        DelegatingActionProxy"

            name="loginForm" scope="request" validate="true" input=
            "/login.jsp" >

            <!-- 定义action内的两个局部forward元素 -->

            <forward name="input" path="/login.jsp"/>

            <forward name="welcome" path="/welcome.html"/>

        </action>

    </action-mappings>

    <!-- 加载国际化的资源包 -->

    <message-resources parameter="mess"/>

    <!-- 装载验证的资源文件 -->

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

        <set-property property="pathnames" value="/WEB-INF/validator-
        rules.xml,/WEB-INF/validation.xml" />

        <set-property property="stopOnFirstError" value="true"/>

    </plug-in>

    <!-- 装载Spring配置文件,随应用启动创建ApplicationContext实例 -->

    <plug-in className="org.springframework.web.struts. ContextLoaderPlugIn">
        <set-property property="contextConfigLocation"

            value="/WEB-INF/applicationContext.xml,

                   /WEB-INF/action-servlet.xml"/>

    </plug-in>

</struts-config>

DelegatingActionProxy接收ActionServlet转发过来的请求,然后转发给Application- Context管理的Bean,这是典型的链式处理。

通过配置文件可以看出,struts-config.xml文件中配置了大量DelegatingActionProxy实例,Spring容器中也配置了同名的Action。即Struts的业务控制器分成了两个部分:第一个部分是Spring的DelegatingActionProxy,这个部分没有实际意义,仅仅完成转发;第二个部分是用户的Action实现类,该实现类负责真实的处理。

这种策略的性能比前一种策略的效果要差一些,因为需要多创建一个Delegating- ActionProxy实例。而且,J2EE应用中Action非常多,这将导致大量创建DelegatingActionProxy实例,使用一次之后,等待垃圾回收机制回收——这对性能的影响不可避免。

图6.4是DelegatingActionProxy的执行效果。

文本框:图6.4  DelegatingActionProxy整合策略的登录成功效果注意:使用DelegatingActionProxy的整合策略,可避免占用Struts的RequestProcessor扩展点,但降低了整合性能。

6.4.5 使用ActionSupport代替Action

前面已经介绍了,Spring与Struts的整合还有一种策略,让Struts的Action显式获取Spring容器中的Bean。在这种策略下,Struts的Action不接受IoC容器管理,Action的代码与Spring API部分耦合,造成代码污染。这种策略也有其好处:代码的可读性非常强,Action的代码中显式调用业务逻辑组件,而无须等待容器注入。

Action中访问ApplicationContext有两种方法:

   ● 利用WebApplicationContextUtils工具类。

   ● 利用ActionSupport支持类。

通过WebApplicationContextUtils,可以显式获得Spring容器的引用(请参阅6.4.1节的内容),而ActionSupport类则提供了一个更简单的方法getWebApplicationContext(),该方法可直接获取Spring容器的引用。

所谓ActionSupport类,是指Spring提供了系列扩展。Spring扩展了Struts的Action,在Struts的Action后加上Support后缀,Spring扩展的Action有如下几个:

   ● ActionSupport。

   ● DispatchActionSupport。

   ● LookupDispatchActionSupport。

   ● MappingDispatchActionSupport。

下面的示例将展示这种整合策略,在这种整合策略下,Struts的Action改为继承Spring扩展后的Action,下面是应用的Action代码:

//新的业务控制器,继承Spring的ActionSupport类

public class LoginAction extends ActionSupport

{

    //依然将ValidBean作为成员变量

    private ValidBean vb;

    //构造器,注意:不可在构造器中调用getWebApplicationContext()方法

    public LoginAction()

    {

    }

    //完成ValidBean的初始化

    public ValidBean getVb()

    {

        return(ValidBean)getWebApplicationContext().getBean("vb");

    }

    //必须重写该核心方法,该方法actionForm将表单的请求参数封装成值对象

    public ActionForward execute(ActionMapping mapping, ActionForm form,

       HttpServletRequest request, HttpServletResponse response)throws
       Exception

    {

        //form由ActionServlet转发请求时创建,封装了所有的请求参数

        LoginForm loginForm = (LoginForm)form;

        //获取username请求参数

        String username = loginForm.getUsername();

        //获取pass请求参数

        String pass = loginForm.getPass();

        //下面为服务器端的数据校验

        String errMsg = "";

        //判断用户名不能为空

        if (username == null || username.equals(""))

        {

            errMsg += "您的用户名丢失或没有输入,请重新输入";

        }

        //判断密码不能为空

        else if(pass == null || pass.equals(""))

        {

            errMsg += "您的密码丢失或没有输入,请重新输入";

        }

        //如果用户名和密码不为空,才调用业务逻辑组件

        else

        {

            //vb是业务逻辑组件,通过上面的初始化方法获得

            if (getVb().valid(username,pass))

            {

                return mapping.findForward("welcome");

            }

            else

            {

                errMsg = "您的用户名和密码不匹配";

            }

        }

        //判断是否生成了错误信息

        if (errMsg != null && !errMsg.equals(""))

        {

            //如果有错误信息,将错误信息保存在request里,并跳转到input对应的
            //forward对象

            request.setAttribute("err" , errMsg);

            return mapping.findForward("input");

        }

        else

        {

            //如果没有错误信息,跳转到welcome对应的forward对象

            return mapping.findForward("welcome");

        }

    }

}

在上面的Action代码中,Action显式获取容器中的业务逻辑组件,而不是依靠Spring容器的依赖注入。在这种整合策略下,表现层的控制器组件不再接受IoC容器管理。因此,没有控制器上下文,应将原有的action-servlet.xml文件删除,并修改plug-in元素,不要加载该文件。还要修改Action配置,将Action配置的type元素修改成实际的处理类。这      种整合策略也有一个好处:代码的可读性更强,对传统Struts应用开发的改变很小,容易使用。

将该Action部署在struts-config.xml中,Struts将负责创建该Action。struts-config.xml文件的源代码如下:

<!-- XML文件的版本和编码集 -->

<?xml version="1.0" encoding="gb2312"?>

<!-- Struts配置文件的文件头,包括DTD等信息 -->

<!DOCTYPE struts-config PUBLIC

          "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

          "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<!-- struts配置文件的根元素 -->

<struts-config>

    <!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->

    <form-beans>

        <!-- 定义了一个formbean,确定formbean名和实现类 -->

        <form-bean name="loginForm" type="lee.LoginForm"/>

    </form-beans>

    <!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->

    <action-mappings>

        <!-- 这里只定义了一个action。action的类型为ActionSupport的子类 -->

        <action path="/login" type="type="lee.LoginAction"

            name="loginForm" scope="request" validate="true" input=
            "/login.jsp" >

            <!-- 定义action内的两个局部forward元素 -->

            <forward name="input" path="/login.jsp"/>

            <forward name="welcome" path="/welcome.html"/>

        </action>

    </action-mappings>

    <!-- 加载国际化的资源包 -->

    <message-resources parameter="mess"/>

    <!-- 装载验证的资源文件 -->

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

        <set-property property="pathnames" value="/WEB-INF/validator-
        rules.xml,/WEB-INF/validation.xml" />

        <set-property property="stopOnFirstError" value="true"/>

    </plug-in>

</struts-config>

此时,Spring无须使用配置Action的配置文件,这种配置方式非常简单。只需要业务逻辑组件的配置文件,业务逻辑组件的配置文件与前面的示例没有任何改变。

该配置文件中的业务逻辑组件由Spring容器负责实现,而ActionSupport能够先定位Spring容器,然后获得容器的业务逻辑组件。

这种整合策略的执行效果与前面两种整合策略的执行效果完全相同。从代码中分析可见,在这种整合策略下,业务控制器再次退回到Struts起初的设计。仅由strutsconfig.xml中Action充当,从而避免了像DelegatingActionProxy整合策略的性能低下,因为可以只需要创建实际的Action实例。

注意:在这种整合策略下,Struts开发者的改变最小,最接近传统Struts应用开发者的习惯。但这种整合策略会造成代码污染,因为Action类必须继承Spring的ActionSupport类。

posted @ 2009-07-19 10:23 jadmin 阅读(72) | 评论 (0)编辑 收藏

6.4 Spring整合Struts

虽然Spring也提供了自己的MVC组件,但一来Spring的MVC组件过于繁琐,二     来Struts的拥护者实在太多。因此,很多项目都会选择使用Spring整合Struts框架。而且Spring确实可以无缝整合Struts框架,二者结合成一个更实际的J2EE开发平台。

6.4.1 利用Struts的PlugIn来启动Spring容器

使用Spring的Web应用时,不用手动创建Spring容器,而是通过配置文件声明式地创建Spring容器。因此,在Web应用中创建Spring容器有如下两个方式:

   ● 直接在web.xml文件中配置创建Spring容器。

   ● 利用第三方MVC框架的扩展点,创建Spring容器。

其实第一种创建Spring容器的方式更加常见。为了让Spring容器随Web应用的启动而自动启动,有如下两个方法:

   ● 利用ServletContextListener实现。

   ● 采用load-on-startup Servlet实现。

Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类可以作为Listener使用,会在创建时自动查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,只需在web.xml文件中增加如下配置片段即可:

<listener>

   <listener-class>org.springframework.web.context.
    ContextLoaderListener</listener-class>

</listener>

如果有多个配置文件需要载入,则考虑使用<context-param>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contextConfigLocation的参数。因此,配置context-param时,参数名字应该是contextConfigLocation。

带多个配置文件的web.xml文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Web配置文件的根元素,以及相应的Schema信息 -->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    version="2.4">

    <!-- 确定多个配置文件 -->

    <context-param>

        <!-- 参数名为contextConfigLocation -->

        <param-name>contextConfigLocation</param-name>

        <!-- 多个配置文件之间以“,”隔开 -->

        <param-value>/WEB-INF/daoContext.xml,/WEB-INF/
        applicationContext.xml</param-value>

    </context-param>

    <!-- 采用listener创建ApplicationContext实例 -->

    <listener>

        <listener-class>org.springframework.web.context.
        ContextLoaderListener</listener-class>

    </listener>

</web-app>

如果没有通过contextConfigLocation指定配置文件,Spring会自动查找application- Context.xml配置文件;如果有contextConfigLocation,则利用该参数确定的配置文件。如果无法找到合适的配置文件,Spring将无法正常初始化。

Spring根据bean定义创建WebApplicationContext对象,并将其保存在web应用的ServletContext中。大部分情况下,应用中的Bean无须感受到ApplicationContext的存在,只要利用ApplicationContext的IoC即可。

如果需要在应用中获取ApplicationContext实例,可以通过如下代码获取:

//获取当前Web应用的Spring容器

WebApplicationContext ctx =

    WebApplicationContextUtils.getWebApplicationContext(servletContext);

除此之外,Spring提供了一个特殊的Servlet类ContextLoaderServlet。该Servlet在启动时,会自动查找WEB-INF/下的applicationContext.xml文件。

当然,为了让ContextLoaderServlet随应用的启动而启动,应将此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一点比较合适,这样可以保证Application- Context更快的初始化。

如果只有一个配置文件,并且文件名为applicationContext.xml,在web.xml文件中增加如下一段即可:

<servlet>

    <servlet-name>context</servlet-name>

    <servlet-class>org.springframework.web.context.ContextLoaderServlet
    </servlet-class>

    <load-on-startup>1</load-on-startup>

</servlet>

该Servlet用于提供“后台”服务,主要用于创建Spring容器,无须响应客户请求,因此无须配置servlet-mapping。

如果有多个配置文件,一样使用<context-param>元素来确定多个配置文件。

事实上,不管是ContextLoaderServlet,还是ContextLoaderListener,都依赖于ContextLoader创建ApplicationContext实例。

在ContextLoader代码的第240行,有如下代码:

String configLocation = servletContext.getInitParameter
(CONFIG_LOCATION_PARAM);

if (configLocation != null) {

    wac.setConfigLocations(StringUtils.tokenizeToStringArray
    (configLocation,

    ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));

}

其中,CONFIG_LOCATION_PARAM是该类的常量,其值为contextConfigLocation。可以看出,ContextLoader首先检查servletContext中是否有contextConfigLocation的参数,如果有该参数,则加载该参数指定的配置文件。

ContextLoaderServlet与ContextLoaderListener底层都依赖于ContextLoader。因此,二者的效果几乎没有区别。之间的区别不是它们本身引起的,而是由于Servlet规范,Listener比Servlet优先加载。因此,采用ContextLoaderListener创建ApplicationContext的时机更早。

当然,也可以通过ServletContext的getAttribute方法获取ApplicationContext。但使用WebApplicationContextUtils类更便捷,因为无须记住ApplicationContext的属性名。即使ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE属性没有对应对象,WebApplicationContextUtils的getWebApplicationContext()方法将会返回空,而不会引起异常。

到底需要使用Listener,还是使用load-on-startup Servlet来创建Spring容器呢?通常推荐使用Listener来创建Spring容器。但Listerner是Servlet 2.3以上才支持的标准,因此,必须Web容器支持Listener才可使用Listerner。

注意:使用Listener创建Spring容器之前,应先评估Web容器是否支持Listener标准。

还有一种情况,利用第三方MVC框架的扩展点来创建Spring容器,比如Struts。在第2章介绍Strust框架时,知道Struts有一个扩展点PlugIn。

实际上,Spring正是利用了PlugIn这个扩展点,从而提供与Struts的整合。Spring提供了PlugIn接口的实现类org.springframework.web.struts.ContextLoaderPlugIn。这个实现类可作为Struts的PlugIn配置,Struts框架启动时,将自动创建Spring容器。

为了利用Struts的PlugIn创建Spring容器,只需在Struts配置文件中增加如下片段   即可:

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">

<set-property property="contextConfigLocation"

      value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.
      xml"/>

</plug-in>

其中,指定contextConfigLocation属性值时,即可以指定一个Spring配置文件的位置,可以指定多个Spring配置文件的位置。

6.4.2 MVC框架与Spring整合的思考

对于一个基于B/S架构的J2EE应用而言,用户请求总是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。此时有一个问题,控制器应该如何获得业务逻辑组件?

最容易想到的策略是,直接通过new关键字创建业务逻辑组件,然后调用业务逻辑组件的方法,根据业务逻辑方法的返回值确定结果。

实际的应用中,很少见到采用上面的访问策略,因为这是一种非常差的策略。不这样做至少有如下3个原因:

   ● 控制器直接创建业务逻辑组件,导致控制器和业务逻辑组件的耦合降低到代码层次,不利于高层次解耦。

   ● 控制器不应该负责业务逻辑组件的创建,控制器只是业务逻辑组件的使用者。无须关心业务逻辑组件的实现。

   ● 每次创建新的业务逻辑组件将导致性能下降。

答案是采用工厂模式或服务定位器。采用服务定位器的模式,是远程访问的场景。在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无须理会该业务逻辑组件的创建,直接调用即可,但在调用之前,必须先找到该服务——这就是服务定位器的概念。经典J2EE应用就是这种结构的应用。

对于轻量级的J2EE应用,工厂模式则是更实际的策略。因为轻量级的J2EE应用里,业务逻辑组件不是EJB,通常就是一个POJO,业务逻辑组件的生成通常由工厂负责,而且工厂可以保证该组件的实例只需一个就够了,可以避免重复实例化造成的系统开销。

如图6.2就是采用工厂模式的顺序图。

图6.2 工厂模式顺序图

采用工厂模式,将控制器与业务逻辑组件的实现分离,从而提供更好的解耦。

在采用工厂模式的访问策略中,所有的业务逻辑组件的创建由工厂负责,业务逻辑组件的运行也由工厂负责。而控制器只需定位工厂实例即可。

如果系统采用Spring框架,则Spring成为最大的工厂。Spring负责业务逻辑组件的创建和生成,并可管理业务逻辑组件的生命周期。可以如此理解,Spring是一个性能非常优秀的工厂,可以生产出所有的实例,从业务逻辑组件,到持久层组件,甚至控制器。

现在的问题是,控制器如何访问到Spring容器中的业务逻辑组件?为了让Action访 问Spring的业务逻辑组件,有两种策略:

   ● Spring管理控制器,并利用依赖注入为控制器注入业务逻辑组件。

   ● 控制器显式定位Spring工厂,也就是Spring的容器ApplicationContext实例,并从工厂中获取业务逻辑组件实例的引用。

第一种策略,充分利用Spring的IoC特性,是最优秀的解耦策略。但不可避免带来一些不足之处,归纳起来主要有如下不足之处:

   ● Spring管理Action,必须将所有的Action配置在Spring容器中,而struts-config.xml文件中的配置也不会减少,导致配置文件大量增加。

   ● Action的业务逻辑组件接收容器注入,将导致代码的可读性降低。

总体而言,这种整合策略是利大于弊。

第二种策略,与前面介绍的工厂模式并没有太大的不同。区别是Spring容器充当了业务逻辑组件的工厂。控制器负责定位Spring容器,通常Spring容器访问容器中的业务逻辑组件。这种策略是一种折衷,降低了解耦,但提高了程序的可读性。

Spring完全支持这两种策略,既可以让Spring容器管理控制器,也可以让控制器显式定位Spring容器中的业务逻辑组件。

6.4.3 使用DelegatingRequestProcessor

这里介绍的是第一种整合策略:让Spring管理Struts的Action。那么同样有一个问题,让Spring管理Struts的Action时,客户端的HTTP 请求如何转向Spring容器中的Action?

当使用Struts作为MVC框架时,客户端的HTTP请求都是直接向ActionServlet请求的,因此关键就是让ActionServlet将请求转发给Spring容器中的Action。这很明显可以利用Spring的另一个扩展点:通过扩展RequestProcessor完成,使用扩展的RequestProcessor替换Struts的RequestProcessor。

Spring完成了这种扩展,Spring提供的DelegatingRequestProcessor继承Request- Processor。为了让Struts使用DelegatingRequestProcessor,还需要在struts-config.xml文件中增加如下一行:

//使用spring的RequestProcessor替换struts原有的RequestProcessor

<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>

完成这个设置后,Struts会将截获到的用户请求转发到Spring context下的bean,根据bean的name属性来匹配。而Struts中的action配置则无须配置class属性,即使配置了class属性也没有任何用处,即下面两行配置是完全一样的:

//配置struts action时,指定了实现类

<action path="/user" type="lee.UserAction"/>

//配置struts action时,没有指定实现类

<action path="/user"/>

下面的示例程序在上一个示例程序的基础上稍作修改,增加了客户端验证和程序国际化部分。也调用了Spring的业务bean来验证登录。先看修改后的struts-config.xml文件:

<!-- XML文件版本,编码集 -->

<?xml version="1.0" encoding="gb2312"?>

<!-- Struts配置文件的文件头,包括DTD等信息 -->

<!DOCTYPE struts-config PUBLIC

          "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

          "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<!-- struts配置文件的根元素 -->

<struts-config>

    <!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->

    <form-beans>

        <!-- 定义了一个formbean,确定formbean名和实现类 -->

        <form-bean name="loginForm" type="lee.LoginForm"/>

    </form-beans>

    <!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->

    <action-mappings>

        <!-- 这里只定义了一个action。而且没有指定该action的type元素 -->

        <action path="/login" name="loginForm"

            scope="request" validate="true" input="/login.jsp" >

            <!-- 定义action内的两个局部forward元素 -->

            <forward name="input" path="/login.jsp"/>

            <forward name="welcome" path="/welcome.html"/>

        </action>

    </action-mappings>

    <!-- 使用DelegatingRequestProcessor替换RequestProcessor -->

    <controller processorClass="org.springframework.web.struts.
    DelegatingRequestProcessor"/>

    <!-- 加载国际化的资源包 -->

    <message-resources parameter="mess"/>

    <!-- 装载验证的资源文件 -->

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

        <set-property property="pathnames" value="/WEB-INF/validator-
        rules.xml,/WEB-INF/validation.xml" />

        <set-property property="stopOnFirstError" value="true"/>

    </plug-in>

    <!-- 装载Spring配置文件,随应用的启动创建ApplicationContext实例 -->

    <plug-in className="org.springframework.web.struts.
    ContextLoaderPlugIn">

        <set-property property="contextConfigLocation"

            value="/WEB-INF/applicationContext.xml,

                   /WEB-INF/action-servlet.xml"/>

    </plug-in>

</struts-config>

修改后的struts-config.xml文件,增加加载国际化资源文件。配置Struts的action不需要class属性,完成了ApplicationContext的创建。

然后考虑web.xml文件的配置,在web.xml文件中必须配置Struts框架的加载。除此之外,因为使用了Spring管理Struts的Action,而Action是随HTTP请求启动的,因此,应将Action的作用域配置成Request,为了使用Request作用域,必须在web.xml文件中增加适当的配置。

下面是web.xml文件的代码:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Web配置文件的根元素,以及对应的Schema信息 -->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    version="2.4">

    <!-- 定义一个Filter,该Filter是使用Request作用域的基础 -->

    <filter>

        <filter-name>requestContextFilter</filter-name>

        <filter-class>org.springframework.web.filter.
        RequestContextFilter </filter-class>

    </filter>

    <!-- 定义filter-mapping,让上面的Filter过滤所有的用户请求 -->

    <filter-mapping>

        <filter-name>requestContextFilter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <!-- 定义Struts的核心Servlet -->

    <servlet>

        <servlet-name>action</servlet-name>

        <servlet-class>org.apache.struts.action.ActionServlet
        </servlet-class>

        <load-on-startup>2</load-on-startup>

    </servlet>

    <!-- 定义Struts的核心Servlet拦截所有*.do请求 -->

    <servlet-mapping>

        <servlet-name>action</servlet-name>

        <url-pattern>*.do</url-pattern>

    </servlet-mapping>

    <!-- 关于Struts标签库的配置 -->

    <jsp-config>

        <!-- 配置bean标签 -->

        <taglib>

            <taglib-uri>/tags/struts-bean</taglib-uri>

            <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>

        </taglib>

        <!-- 配置html标签 -->

        <taglib>

            <taglib-uri>/tags/struts-html</taglib-uri>

            <taglib-location>/WEB-INF/struts-html.tld</taglib-location>

        </taglib>

        <!-- 配置logic标签 -->

        <taglib>

            <taglib-uri>/tags/struts-logic</taglib-uri>

            <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>

        </taglib>

    </jsp-config>

</web-app>

posted @ 2009-07-19 10:22 jadmin 阅读(64) | 评论 (0)编辑 收藏

6.3.2 Spring事务策略的优势

虽然在上面的配置片段中,仅仅配置了JDBC局部事务管理器、Hibernate局部事务管理器、JDBC全局事务管理器等。但Spring支持大部分持久化策略的事务管理器。

不论采用何种持久化策略,Spring都提供了一致的事务抽象,因此,应用开发者能在任何环境下,使用一致的编程模型。无须更改代码,应用就可在不同的事务管理策略中切换。Spring同时支持声明式事务管理和编程式事务管理。

使用编程式事务管理,开发者使用的是Spring事务抽象,而无须使用任何具体的底层事务API。Spring的事务管理将代码从底层具体的事务API中抽象出来,该抽象可以使用在任何底层事务基础之上。

使用声明式策略,开发者通常书写很少的事务管理代码,因此,不依赖Spring或任何其他事务API。Spring的声明式事务无须任何额外的容器支持,Spring容器本身管理声明式事务。使用声明事务策略,无须在业务代码中书写任何事务代码,可以让开发者更好地专注于业务逻辑的实现。Spring管理的事务支持多个事务资源的跨越,但无法支持跨越远程调用的事务上下文传播。

6.3.3 使用TransactionProxyFactoryBean创建事务代理

Spring同时支持编程式事务策略和声明式事务策略,大部分时候,都推荐采用声明式事务策略,使用声明事务策略的优势十分明显:

   ● 声明式事务能大大降低开发者的代码书写量。而且声明式事务几乎不需要影响应用的代码。因此,无论底层事务策略如何变化,应用程序无须任何改变。

   ● 应用程序代码无须任何事务处理代码,可以更专注于业务逻辑的实现。

   ● Spring则可对任何POJO的方法提供事务管理,而且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。

   ● EJB的CMT无法提供声明式回滚规则。而通过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不仅可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。

   ● 由于Spring采用AOP的方式管理事务,因此,可以在事务回滚动作中插入用户自己的动作,而不仅仅是执行系统默认的回滚。

提示:本节不打算全面介绍Spring的各种事务策略,因此本节不会介绍编程式事务。如果读者需要更全面了解Spring事务的相关方面,请参阅笔者所著的《Spring2.0宝典》     一书。

对于采用声明式事务策略,可以使用TransactionProxyFactoryBean来配置事务代理Bean。正如它的类名所暗示的,它是一个工厂Bean,工厂Bean用于生成一系列的Bean实例,这一系列的Bean实例都是Proxy。

可能读者已经想到了,既然TransactionProxyFactoryBean产生的是代理Bean,可见这种事务代理正是基于Spring AOP组件的。配置TransactionProxyFactoryBean时,一样需要指定目标Bean。

每个TransactionProxyFactoryBean为一个目标Bean生成事务代理,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行之前加入开始事务,在目标Bean的方法正常结束之前提交事务,如果遇到特定异常则回滚事务。

TransactionProxyFactoryBean创建事务代理时,需要了解当前事务所处的环境,该环境属性通过PlatformTransactionManager实例传入,而相关事务传入规则在TransactionProxy- FactoryBean的定义中给出。

下面给出声明式事务配置文件的完整代码:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定义Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->

        <property name="dataSource" <ref="dataSource"/>

        <!-- mappingResources属性用来列出全部映射文件 -->

        <property name="mappingResources">

            <list>

                <!-- 以下用来列出所有的PO映射文件 -->

                <value>lee/Person.hbm.xml</value>

            </list>

        </property>

          <!-- 定义Hibernate的SessionFactory属性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的连接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 是否根据Hiberante映射创建数据表时,选择create、update、
                create-drop -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

        </property>

    </bean>

    <!-- 配置DAO Bean,该Bean将作为目标Bean使用 -->

    <bean id="personDAOTarget" class="lee.PersonDaoImpl">

        <!-- 采用依赖注入来传入SessionFactory的引用 -->

        <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

    <!-- 配置Hibernate的事务管理器 -->

    <!-- 使用HibernateTransactionManager类,该类实现PlatformTransactionManager
    接口,针对采用Hibernate持久化连接的特定实现 -->

    <bean id="transactionManager"

        class="org.springframework.orm.hibernate3.
        HibernateTransactionManager">

        <!-- HibernateTransactionManager Bean,它需要依赖注入一个SessionFactory
        Bean的引用 -->

        <property name="sessionFactory" ref="sessionFactory"/>

    </bean>

    <!-- 配置personDAOTarget Bean的事务代理 -->

    <bean id="personDAO"

        class="org.springframework.transaction.interceptor.
        TransactionProxyFactoryBean">

        <!-- 依赖注入PlatformTransactionManager的bean引用,此处使用
        Hibernate的bean -->

        <!-- 局部事务器,因此transactionManager 传入Hibernate事务管理器的
        引用 -->

           <property name="transactionManager" ref="transactionManager"/>

        <!-- 需要生成代理的目标bean -->

           <property name="target" ref="personDAOTarget"/>

        <!-- 指定事务属性 -->

           <property name="transactionAttributes">

               <props>

                <!-- 以下部分为定义事务回滚规则 -->

                   <prop key="insert*">PROPAGATION_REQUIRED,
                -MyCheckedException</prop>

                   <prop key="update*">PROPAGATION_REQUIRED</prop>

                   <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

            </props>

        </property>

    </bean>

</beans>

在上面的定义文件中,没有对DAO对象采用Service层包装。通常情况下,DAO层上应有一层Service层。事务代理则以Service层Bean为目标Bean。此处为了简化配置,TransactionProxyFactoryBean直接以DAO bean作为目标bean,这一点不会影响事务代理的生成。

事务回滚规则部分定义了三个回滚规则:

第一个回滚规则表示所有以insert开始的方法,都应该满足该事务规则。PROPAGATION_REQUIRED事务传播规则指明,该方法必须处于事务环境中,如果当前执行线程已处于事务环境下,则直接执行;否则,启动新的事务然后执行该方法。该规则还指定,如果方法抛出MyCheckedException的实例及其子类的实例,则强制回滚。MyCheckedException前的“-”表示强制回滚;“+”则表示强制提交,即某些情况下,即使抛出异常也强制提交;

第二个回滚规则表示所有以update开头的方法,都遵守PROPAGATION_REQUIRED的事务传播规则;

第三个回滚规则表示除前面规定的方法外,其他所有方法都采用PROPAGATION_ REQUIRED事务传播规则,而且只读。

常见的事务传播规则有如下几个:

   ● PROPAGATION_MANDATORY,要求调用该方法的线程必须处于事务环境中,否则抛出异常。

   ● PROPAGATION_NESTED,如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果执行该方法的线程序并未处于事务中,也启动新的事务,然后执行该方法,此时与PROPAGATION_REQUIRED相同。

   ● PROPAGATION_NEVER,不允许调用该方法的线程处于事务环境下,如果调用该方法的线程处于事务环境下,则抛出异常。

   ● PROPAGATION_NOT_SUPPORTED,如果调用该方法的线程处在事务中,则先暂停当前事务,然后执行该方法。

   ● PROPAGATION_REQUIRED,要求在事务环境中执行该方法,如果当前执行线程已处于事务中,则直接调用;如果当前执行线程不处于事务中,则启动新的事务后执行该方法。

   ● PROPAGATION_REQUIRES_NEW,该方法要求有一个线程在新的事务环境中执行,如果当前执行线程已处于事务中,先暂停当前事务,启动新的事务后执行该方法;如果当前调用线程不处于事务中,则启动新的事务后执行该方法。

   ● PROPAGATION_SUPPORTS,如果当前执行线程处于事务中,则使用当前事务,否则不使用事务。

程序里原来使用personDAO的地方,无须变化。因为,配置文件里将personDAO目标Bean的id改成personDAOTarget,为TransactionProxyFactoryBean工厂Bean所产生的代理Bean命名为personDAO。该代理Bean会包含原有personDAO的所有方法,而且为这些方法增加了不同的事务处理规则。

程序面向PersonDaoImpl类所实现的接口编程,TransactionProxyFactoryBean生成的代理Bean也会实现TransactionProxyFactoryBean接口。因此,原有的程序中调用DAO组件的代码无须任何改变。程序运行时,由事务代理完成原来目标Bean完成的工作。

事实上,Spring不仅支持对接口的代理,整合CGLIB后,Spring甚至可对具体类生成代理。只要设置proxyTargetClass属性为true就可以。如果目标Bean没有实现任何接口,proxyTargetClass属性默认被设为true,此时Spring会对具体类生成代理。当然,通常建议面向接口编程,而不要面向具体的实现类编程。

6.3.4 使用继承简化事务配置

仔细观察配置文件中两个事务代理Bean的配置时,发现两个事务代理Bean的大部分配置完全相同,如果配置文件中包含大量这样的事务代理Bean配置,配置文件将非常臃肿。考虑到大部分事务代理Bean的配置都大同小异,可以使用Bean继承来简化事务代理的配置。

正如前面部分介绍的,Bean继承是将所有子Bean中相同的配置定义成一个模板,并将此模板Bean定义成一个抽象Bean。考虑所有事务代理Bean中,有如下部分是大致相   同的:

   ● 事务代理Bean所使用的事务管理器。

   ● 事务传播规则。

因此,现在配置文件中定义如下的事务代理模板Bean,其配置代码如下:

<!-- 定义所有事务代理Bean的模板 -->

<bean id="txProxyTemplate" abstract="true"

        class="org.springframework.transaction.interceptor.
        TransactionProxyFactoryBean">

    <!-- 为事务代理Bean注入生成代理所需的PlatformTransactionManager实例 -->

    <property name="transactionManager" ref="transactionManager"/>

        <!-- 定义生成事务代理通用的事务属性 -->

        <property name="transactionAttributes">

            <props>

                <!-- 所有的方法都应用PROPAGATION_REQUIRED的事务传播规则 -->

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

    </property>

</bean>

而真正的事务代理Bean,则改为继承上面的事务模板Bean。考虑到将目标Bean定义在Spring容器中可能增加未知的风险,因此将目标Bean定义成嵌套Bean。

<!-- 让事务代理Bean继承模板Bean -->

<bean id="personDAO" parent="txProxyTemplate">

    <!-- 这里采用嵌套Bean的方式来定义目标Bean,当然也可以引用已存在的Bean -->

    <property name="target">

        <bean class="lee.personDAO"/>

    </property>

</bean>

此时的personDAO Bean无须具体地定义事务属性,它将在其父Bean txProxyTemplate中获取事务定义属性。此处采用嵌套Bean来定义目标Bean,因此,并未将目标Bean直接暴露在Spring的上下文中让其他模块调用。当然,也可采用一个已经存在的Bean作为目标Bean;子Bean的事务属性定义,完全可覆盖事务代理模板里的事务属性定义。如下例所示:

<!-- 让事务代理bean继承模板Bean -->

<bean id="personDAO" parent="txProxyTemplate">

    <!-- 这里,采用引用已存在的bean的方式来定义目标Bean -->

    <property name="target" ref ="personDAOTarget"/>

    <!-- 覆盖事务代理模板bean中的事务属性定义 -->

    <property name="transactionAttributes">

        <props>

            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>

            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>

         </props>

</property>

</bean>

可见,采用Bean继承方式定义事务代理的方式,可以很好地简化事务代理的配置,可以避免配置事务代理Bean时的冗余配置。

提示:使用Bean继承可以很好地简化事务代理Bean的配置,通过将各事务代理Bean共同的配置信息提取成事务模板Bean,可以让实际的事务代理Bean的配置更加简洁;而且,配置方式相当直观。尽量将目标Bean配置成嵌套Bean,这样的方式可以保证更好的内聚性。

如果读者还记得前面介绍的AOP知识,应该知道还有一种更加简洁的配置,就是利用Bean后处理器,让Bean后处理器为容器中其他Bean自动创建事务代理。

6.3.5 使用自动创建代理简化事务配置

回顾6.2.6节和6.2.7节,读者可能已经想到如何自动创建代理。是的,正是通过6.2.6节和6.2.7节所给出的两个自动代理创建类来生成事务代理。

正如前文已经提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator来创建代理时,并不一定是创建事务代理,关键在于传入的拦截器,如果传入事务拦截器,将可自动生成事务代理。

下面是使用BeanNameAutoProxyCreator自动生成事务代理的配置文件:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及相应的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 使用JDBC的局部事务策略 -->

    <bean id="transactionManager"

        class="org.springframework.jdbc.datasource.DataSource-
        TransactionManager">

        <!-- 为事务管理器注入所需的数据源Bean -->

        <property name="dataSource" ref="dataSource"/>

    </bean>

    <!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->

    <bean id="test1" class="lee.TransactionTestImpl">

        <!-- 依赖注入目标Bean所必需的数据源Bean -->

        <property name="ds" ref="dataSource"/>

    </bean>

    <!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->

    <bean id="test2" class="lee.TestImpl">

        <!-- 依赖注入目标Bean所必需的数据源Bean -->

        <property name="ds" ref="dataSource"/>

    </bean>

    <!-- 配置事务拦截器Bean -->

    <bean id="transactionInterceptor"

        class="org.springframework.transaction.interceptor.
        TransactionInterceptor">

        <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->

        <property name="transactionManager" ref="transactionManager"/>

        <property name="transactionAttributes">

            <!-- 下面定义事务传播属性 -->

            <props>

                <prop key="insert*">PROPAGATION_REQUIRED</prop>

                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

                <prop key="*">PROPAGATION_REQUIRED</prop>

            </props>

        </property>

    </bean>

    <!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->

    <bean class="org.springframework.aop.framework.autoproxy.
    BeanNameAutoProxyCreator">

    <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->

        <property name="beanNames">

            <!-- 下面是所有需要自动创建事务代理的Bean -->

            <list>

                <value>test1</value>

                <value>test2</value>

            </list>

            <!-- 此处可增加其他需要自动创建事务代理的Bean -->

        </property>

        <!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->

        <property name="interceptorNames">

            <list>

                <value>transactionInterceptor</value>

                <!-- 此处可增加其他新的Interceptor -->

            </list>

        </property>

    </bean>

</beans>

如果配置文件中仅有两个目标Bean,可能不能很清楚地看出这种自动创建代理配置方式的优势,但如果有更多目标Bean需要自动创建事务代理,则可以很好地体会到这种配置方式的优势:配置文件只需要简单地配置目标Bean,然后在BeanNameAutoProxyCreator配置中增加一行即可。

提示:使用BeanNameAutoProxyCreator可以自动创建事务代理,使用DefaultAdvisor- AutoProxyCreator也可自动创建事务代理。关于后一个Bean后处理器的配置方式,请参看前面6.2.7节的内容。

posted @ 2009-07-19 10:18 jadmin 阅读(74) | 评论 (0)编辑 收藏

6.3 Spring的事务

Spring的事务管理不需与任何特定的事务API耦合。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性地管理事务。声明式事务基于Spring AOP实现,却并不需要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。

6.3.1 Spring支持的事务策略

Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。该接口的源代码如下:

public interface PlatformTransactionManager

{

    //平台无关的获得事务的方法

    TransactionStatus getTransaction(TransactionDefinition definition)

        throws TransactionException;

    //平台无关的事务提交方法

    void commit(TransactionStatus status) throws TransactionException;

    //平台无关的事务回滚方法

    void rollback(TransactionStatus status) throws TransactionException;

}

PlatformTransactionManager是一个与任何事务策略分离的接口,随着底层不同事务策略切换,应用必须采用不同的实现类。PlatformTransactionManager接口没有与任何事务资源捆绑在一起,它可以适应于任何的事务策略,结合Spring的IoC容器,可以向PlatformTransactionManager注入相关的平台特性。

PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,对不同平台的底层支持,由PlatformTransactionManager接口的实现类完成。从而,应用程序无须与具体的事务API耦合。因此,使用PlatformTransactionManager接口,可将代码从具体的事务API中解耦出来。

即使使用特定容器管理的JTA,代码依然无须执行JNDI查找,无须与特定的JTA资源耦合在一起。通过配置文件,JTA资源传给PlatformTransactionManager的实现类。因此,程序的代码可在JTA事务管理和非JTA事务管理之间轻松切换。

在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition definition)方法,该方法根据一个TransactionDefinition参数,返回一个TransactionStatus对象。TransactionStatus对象表示一个事务。TransactionStatus被关联在当前执行的线程。

getTransaction(TransactionDefinition definition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处于事务管理下,返回当前线程的事务对象,否则,返回当前线程的调用堆栈已有的事务对象。

TransactionDefinition接口定义了一个事务规则,该接口必须指定如下几个属性值:

   ● 事务隔离,当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。

   ● 事务传播,通常,在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数情况下,简单地在现有的事务上下文中运行;或者挂起现有事务,创建一个新的事务。Spring提供EJB CMT(Contain Manager Transaction,容器管理事务)中所有的事务传播选项。

   ● 事务超时,事务在超时前能运行多久。事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。

   ● 只读状态,只读事务不修改任何数据。在某些情况下(例如使用Hibernate时),只读事务是非常有用的优化。

TransactionStatus代表事务本身,它提供了简单的控制事务执行和查询事务状态的方法。这些方法在所有的事务API中都是相同的。TransactionStatus接口的源代码如下:

public interface TransactionStatus

{

    //判断事务是否是新建的事务

    boolean isNewTransaction();

    //设置事务回滚

    void setRollbackOnly();

    //查询事务是否已有回滚标志

    boolean isRollbackOnly();

}

Spring的事务管理由PlatformTransactionManager的不同实现类完成。在Spring上下文中配置PlatformTransactionManager Bean时,必须针对不同环境提供不同的实现类。

下面提供不同的持久层访问环境,及其对应的PlatformTransactionManager实现类的 配置。

JDBC数据源的局部事务策略:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 定义数据源Bean,使用C3P0数据源实现 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

    <!-- 指定连接数据库的驱动 -->

    <property name="driverClass" value="com.mysql.jdbc.Driver"/>

    <!-- 指定连接数据库的URL -->

    <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

    <!-- 指定连接数据库的用户名 -->

    <property name="user" value="root"/>

    <!-- 指定连接数据库的密码 -->

    <property name="password" value="32147"/>

    <!-- 指定连接数据库连接池的最大连接数 -->

    <property name="maxPoolSize" value="40"/>

    <!-- 指定连接数据库连接池的最小连接数 -->

    <property name="minPoolSize" value="1"/>

    <!-- 指定连接数据库连接池的初始化连接数 -->

    <property name="initialPoolSize" value="1"/>

    <!-- 指定连接数据库连接池的连接最大空闲时间 -->

    <property name="maxIdleTime" value="20"/>

</bean>

<!-- 配置JDBC数据源的局部事务管理器 -->

<!-- 使用DataSourceTransactionManager 类,该类实现PlatformTransactionManager接口 -->

<!-- 针对采用数据源连接的特定实现 -->

<bean id="transactionManager"

        class="org.springframework.jdbc.datasource.
        DataSourceTransactionManager">

        <!-- DataSourceTransactionManager bean需要依赖注入一个DataSource
        bean的引用 -->

         <property name="dataSource" ref="dataSource"/>

    </bean>

</beans>

对于容器管理JTA数据源,全局事务策略的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置JNDI数据源Bean -->

    <bean id="dataSource" class="org.springframework.jndi.
    JndiObjectFactoryBean">

    <!-- 容器管理数据源的JNDI -->

         <property name="jndiName" value="jdbc/jpetstore"/>

    </bean>

    <!-- 使用JtaTransactionManager类,该类实现PlatformTransactionManager接
    口 -->

    <!-- 针对采用全局事务管理的特定实现 -->

    <!-- JtaTransactionManager不需要知道数据源,或任何其他特定资源 -->

    <!-- 因为它使用容器的全局事务管理 -->

    <bean id="transactionManager"

        class="org.springframework.transaction.jta.
        JtaTransactionManager" />

</beans>

对于采用Hibernate持久层访问策略时,局部事务策略的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="root"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="32147"/>

        <!-- 指定连接数据库连接池的最大连接数 -->

        <property name="maxPoolSize" value="40"/>

        <!-- 指定连接数据库连接池的最小连接数 -->

        <property name="minPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的初始化连接数 -->

        <property name="initialPoolSize" value="1"/>

        <!-- 指定连接数据库连接池的连接最大空闲时间 -->

        <property name="maxIdleTime" value="20"/>

    </bean>

    <!-- 定义Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources属性用来列出全部映射文件 -->

        <property name="mappingResources">

            <list>

                <!-- 以下用来列出所有的PO映射文件 -->

                <value>lee/MyTest.hbm.xml</value>

            </list>

        </property>

          <!-- 定义Hibernate的SessionFactory的属性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的连接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 是否根据Hibernate映射创建数据表时,选择create、update、
                create-drop -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

        </property>

    </bean>

    <!-- 配置Hibernate的局部事务管理器 -->

    <!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager
    接口,针对采用Hibernate持久化连接的特定实现 -->

    <bean id="transactionManager"

    class="org.springframework.orm.hibernate3.
    HibernateTransactionManager">

            <!-- HibernateTransactionManager Bean需要依赖注入一个
            SessionFactorybean的引用 -->

         <property name="sessionFactory" ref="sessionFactory"/>

     </bean>

</beans>

对于采用Hibernate持久层访问策略时,全局事务策略的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置JNDI数据源Bean -->

    <bean id="dataSource" class="org.springframework.jndi.
    JndiObjectFactoryBean">

        <!-- 容器管理数据源的JNDI -->

         <property name="jndiName" value="jdbc/jpetstore"/>

    </bean>

    <!--定义Hibernate的SessionFactory -->

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.
    LocalSessionFactoryBean">

        <!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource Bean -->

        <property name="dataSource" ref="dataSource"/>

        <!-- mappingResources属性用来列出全部映射文件 -->

        <property name="mappingResources">

            <list>

                  <!-- 以下用来列出所有的PO映射文件 -->

                <value>lee/MyTest.hbm.xml</value>

            </list>

        </property>

          <!-- 定义Hibernate的SessionFactory的属性 -->

        <property name="hibernateProperties">

             <props>

                <!-- 指定Hibernate的连接方言 -->

                <prop key="hibernate.dialect">org.hibernate.dialect.
                MySQLDialect</prop>

                <!-- 是否根据Hiberante映射创建数据表时,选择create、update、
                create-drop -->

                  <prop key="hibernate.hbm2ddl.auto">update</prop>

             </props>

          </property>

    </bean>

    <!-- 使用JtaTransactionManager类,该类是PlatformTransactionManager接口,
            针对采用数据源连接的特定实现 -->

    <!-- JtaTransactionManager不需要知道数据源,或任何其他特定资源,

            因为使用容器的全局事务管理 -->

    <bean id="transactionManager"

           class="org.springframework.transaction.jta.
           JtaTransactionManager" />

</beans>

不论采用哪种持久层访问技术,只要使用JTA数据源,Spring事务管理器的配置都是一样的,因为它们都采用的是全局事务管理。

可以看到,仅仅通过配置文件的修改,就可以在不同的事务管理策略间切换,即使从局部事务到全局事务的切换。

提示:Spring所支持的事务策略非常灵活,Spring的事务策略允许应用程序在不同事务策略之间自由切换,即使需要在局部事务策略和全局事务策略之间切换,只需要修改配置文件,而应用程序的代码无须任何改变。这种灵活的设计,又何尝不是因为面向接口编程带来的优势,可见面向接口编程给应用程序更好的适应性。

posted @ 2009-07-19 10:18 jadmin 阅读(72) | 评论 (0)编辑 收藏

6.2.4 代理接口

当目标Bean的实现类实现了接口后,Spring AOP可以为其创建JDK动态代理,而无须使用CGLIB创建的代理,这种代理称为代理接口。

创建AOP代理必须指定两个属性:目标Bean和处理。实际上,很多AOP框架都以拦截器作为处理。因为Spring AOP与IoC容器的良好整合,因此配置代理Bean时,完全可以利用依赖注入来管理目标Bean和拦截器Bean。

下面的示例演示了基于AOP的权限认证,它是简单的TestService接口,该接口模拟Service组件,该组件内包含两个方法:

   ● 查看数据。

   ● 修改数据。

接口的源代码如下:

//Service组件接口

public interface TestService

{

    //查看数据

    void view();

    //修改数据

    void modify();

}

该接口的实现类实现两个方法。因为篇幅限制,本示例并未显示出完整的查看数据和修改数据的持久层操作,仅仅在控制台打印两行信息。实际的项目实现中,两个方法的实现则改成对持久层组件的调用,这不会影响示例程序的效果。实现类的源代码如下:

TestService接口的实现类

public class TestServiceImpl implements TestService

{

    //实现接口必须实现的方法

    public void view()

    {

        System.out.println("用户查看数据");

    }

    //实现接口必须实现的方法

    public void modify()

    {

        System.out.println("用户修改数据");

    }

}

示例程序采用Around 处理作为拦截器,拦截器中使用依赖注入获得当前用户名。实际Web应用中,用户应该从session中读取。这不会影响示例代码的效果。拦截器源代码如下:

public class AuthorityInterceptor implements MethodInterceptor

{

    //当前用户名

    private String user;

    //依赖注入所必需的setter方法

    public void setUser(String user)

    {

        this.user = user;

    }

    public Object invoke(MethodInvocation invocation) throws Throwable

    {

        //获取当前拦截的方法名

        String methodName = invocation.getMethod().getName();

        //下面执行权限检查

        //对既不是管理员,也不是注册用户的情况

        if (!user.equals("admin") && !user.equals("registedUser"))

        {

            System.out.println("您无权执行该方法");

            return null;

        }

        //对仅仅是注册用户,调用修改数据的情况

        else if (user.equals("registedUser") && methodName.equals
        ("modify"))

        {

            System.out.println("您不是管理员,无法修改数据");

            return null;

        }

        //对管理员或注册用户,查看数据的情况

        else

        {

            return invocation.proceed();

        }

    }

}

TestAction类依赖TestService。因篇幅关系,此处不给出TestAction的接口的源代码,TestActionImpl的源代码如下:

public class TestActionImpl

{

    //将TestService作为成员变量,面向接口编程

    private TestService ts;

    //依赖注入的setter方法

    public void setTs(TestService ts)

    {

        this.ts = ts;

    }

    //修改数据

    public void modify()

    {

        ts.modify();

    }

    //查看数据

    public void view()

    {

        ts.view();

    }

}

配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置目标Bean -->

    <bean id="serviceTarget" class="lee.TestServiceImpl"/>

    <!-- 配置拦截器,拦截器作为处理使用 -->

    <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">

        <property name="user" value="admin"/>

    </bean>

    <!-- 配置代理工厂Bean,负责生成AOP代理 -->

    <bean id="service" class="org.springframework.aop.framework.
    ProxyFactoryBean">

        <!-- 指定AOP代理所实现的接口 -->

        <property name="proxyInterfaces" value="lee.TestService"/>

        <!-- 指定AOP代理所代理的目标Bean -->

        <property name="target" ref="serviceTarget"/>

        <!-- AOP代理所需要的拦截器列表 -->

        <property name="interceptorNames">

            <list>

                <value>authorityInterceptor</value>

            </list>

        </property>

    </bean>

    <!-- 配置Action Bean,该Action依赖TestService Bean -->

    <bean id="testAction" class="lee.TestActionImpl">

        <!-- 此处注入的是依赖代理Bean -->

        <property name="ts" ref="service"/>

    </bean>

</beans>

主程序请求testAction Bean,然后调用该Bean的两个方法,主程序如下:

public class BeanTest

{

    public static void main(String[] args)throws Exception

    {

        //创建Spring容器实例

        ApplicationContext ctx = new FileSystemXmlApplicationContext
        ("bean.xml");

        //获得TestAction bean

        TestAction ta = (TestAction)ctx.getBean("testAction");

        //调用bean的两个测试方法

        ta.view();

        ta.modify();

    }

}

程序执行结果如下:

[java] 用户查看数据

[java] 用户修改数据

代理似乎没有发挥任何作用。因为配置文件中的当前用户是admin,admin用户具备访问和修改数据的权限,因此代理并未阻止访问。将配置文件中的admin修改成registed- User,再次执行程序,得到如下结果:

[java] 用户查看数据

[java] 您不是管理员,无法修改数据

代理阻止了registedUser修改数据,查看数据可以执行。将registedUser修改成其他用户,执行程序,看到如下结果:

[java] 您无权执行该方法

[java] 您无权执行该方法

代理阻止用户对两个方法的执行。基于AOP的权限检查,可以降低程序的代码量,因为无须每次调用方法之前,手动编写权限检查代码;同时,权限检查与业务逻辑分离,提高了程序的解耦。

示例中的目标Bean被暴露在容器中,可以被客户端代码直接访问。为了避免客户端代码直接访问目标Bean,可以将目标Bean定义成代理工厂的嵌套Bean,修改后的配置文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置拦截器,拦截器作为处理使用 -->

    <bean id="authorityInterceptor" class="lee.AuthorityInterceptor">

        <property name="user" value="admin"/>

    </bean>

    <!-- 配置代理工厂Bean,该工厂Bean将负责创建目标Bean的代理 -->

    <bean id="service" class="org.springframework.aop.framework.
    ProxyFactoryBean">

        <!-- 指定AOP代理所实现的接口 -->

        <property name="proxyInterfaces" value="lee.TestService"/>

        <property name="target">

            <!-- 以嵌套Bean的形式定义目标Bean,避免客户端直接访问目标Bean -->

            <bean class="lee.TestServiceImpl"/>

        </property>

        <!-- AOP代理所需要的拦截器列表 -->

        <property name="interceptorNames">

            <list>

                <value>authorityInterceptor</value>

            </list>

        </property>

    </bean>

    <!-- 配置Action Bean,该Action依赖TestService Bean -->

    <bean id="testAction" class="lee.TestActionImpl">

        <!-- 此处注入的是依赖代理Bean -->

        <property name="ts" ref="service"/>

    </bean>

</beans>

由上面介绍的内容可见,Spring的AOP是对JDK动态代理模式的深化。通过Spring AOP组件,允许通过配置文件管理目标Bean和AOP所需的处理。

下面将继续介绍如何为没有实现接口的目标Bean创建CGLIB代理。

6.2.5 代理类

如果目标类没有实现接口,则无法创建JDK动态代理,只能创建CGLIB代理。如果需要没有实现接口的Bean实例生成代理,配置文件中应该修改如下两项:

   ● 去掉<property name="proxyInterfaces"/>声明。因为不再代理接口,因此,此处的配置没有意义。

   ● 增加<property name="proxyTargetClass">子元素,并设其值为true,通过该元素强制使用CGLIB代理,而不是JDK动态代理。

注意:最好面向接口编程,不要面向类编程。同时,即使在实现接口的情况下,也可强制使用CGLIB代理。

CGLIB代理在运行期间产生目标对象的子类,该子类通过装饰器设计模式加入到Advice中。因为CGLIB代理是目标对象的子类,则必须考虑保证如下两点:

   ● 目标类不能声明成final,因为final类不能被继承,无法生成代理。

   ● 目标方法也不能声明成final,final方法不能被重写,无法得到处理。

当然,为了需要使用CGLIB代理,应用中应添加CGLIB二进制Jar文件。

6.2.6 使用BeanNameAutoProxyCreator自动创建代理

这是一种自动创建事务代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator实例,该实例将会对指定名字的Bean实例自动创建代理。实际上,BeanNameAutoProxyCreator是一个Bean后处理器,理论上它会对容器中所有的Bean进行处理,实际上它只对指定名字的Bean实例创建代理。

BeanNameAutoProxyCreator根据名字自动生成事务代理,名字匹配支持通配符。

与ProxyFactoryBean一样,BeanNameAutoProxyCreator需要一个interceptorNames属性,该属性名虽然是“拦截器”,但并不需要指定拦截器列表,它可以是Advisor或任何处理类型。

下面是使用BeanNameAutoProxyCreator的配置片段:

<!-- 定义事务拦截器bean -->

<bean id="transactionInterceptor"

    class="org.springframework.transaction.interceptor.
    TransactionInterceptor">

    <!-- 事务拦截器bean需要依赖注入一个事务管理器 -->

    <property name="transactionManager" ref="transactionManager"/>

    <property name="transactionAttributes">

        <!-- 下面定义事务传播属性 -->

        <props>

            <prop key="insert*">PROPAGATION_REQUIRED </prop>

            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

            <prop key="*">PROPAGATION_REQUIRED</prop>

        </props>

    </property>

    </bean>

<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,

        负责为容器中特定的Bean创建AOP代理 -->

<bean class="org.springframework.aop.framework.autoproxy.
    BeanNameAutoProxyCreator">

    <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->

    <property name="beanNames">

        <list>

            <!-- 下面是所有需要自动创建事务代理的Bean -->

            <value>core-services-applicationControllerSevice</value>

            <value>core-services-deviceService</value>

            <value>core-services-authenticationService</value>

            <value>core-services-packagingMessageHandler</value>

            <value>core-services-sendEmail</value>

            <value>core-services-userService</value>

            <!-- 此处可增加其他需要自动创建事务代理的Bean -->

        </list>

    </property>

    <!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->

    <property name="interceptorNames">

        <list>

            <value>transactionInterceptor</value>

            <!-- 此处可增加其他新的Interceptor -->

        </list>

    </property>

</bean>

上面的片段是使用BeanNameAutoProxyCreator自动创建事务代理的片段。Transaction- Interceptor用来定义事务拦截器,定义事务拦截器时传入事务管理器Bean,也指定事务传播属性。

通过BeanNameAutoProxyCreator定义Bean后处理器,定义该Bean后处理器时,通过beanNames属性指定有哪些目标Bean生成事务代理;还需要指定“拦截器链”,该拦截器链可以由任何处理组成。

定义目标Bean还可使用通配符,使用通配符的配置片段如下所示:

<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,

        负责为容器中特定的Bean创建AOP代理 -->

<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">

    <!-- 指定对满足哪些bean name的bean自动生成业务代理 -->

    <property name="beanNames">

        <!-- 此处使用通配符确定目标bean -->

        <value>*DAO,*Service,*Manager</value>

    </property>

    <!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器 -->

    <property name="interceptorNames">

        <list>

            <value>transactionInterceptor</value>

            <!-- 此处可增加其他新的Interceptor -->

        </list>

    </property>

</bean>

上面的配置片段中,所有名字以DAO、Service、Manager结尾的bean,将由该“bean后处理器”为其创建事务代理。目标bean不再存在,取而代之的是目标bean的事务代理。通过这种方式,不仅可以极大地降低配置文件的繁琐,而且可以避免客户端代码直接调用目标bean。

注意:虽然上面的配置片段是为目标对象自动生成事务代理。但这不是唯一的,如果有需要,可以为目标对象生成任何的代理。BeanNameAutoProxyCreator为目标对象生成怎样的代理,取决于传入怎样的处理Bean,如果传入事务拦截器,则生成事务代理Bean;否则将生成其他代理,在后面的实例部分,读者将可以看到大量使用BeanNameAutoProxy- Creator创建的权限检查代理。

6.2.7 使用DefaultAdvisorAutoProxyCreator自动创建代理

Spring还提供了另一个Bean后处理器,它也可为容器中的Bean自动创建代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更强大的自动代理生成器。它将自动应用于当前容器中的advisor,不需要在DefaultAdvisorAutoProxyCreator定义中指定目标Bean的名字字符串。

这种定义方式有助于配置的一致性,避免在自动代理创建器中重复配置目标Bean 名。

使用该机制包括:

   ● 配置DefaultAdvisorAutoProxyCreator bean定义。

   ● 配置任何数目的Advisor,必须是Advisor,不仅仅是拦截器或其他处理。因为,必须使用切入点检查处理是否符合候选Bean定义。

DefaultAdvisorAutoProxyCreator计算Advisor包含的切入点,检查处理是否应该被应用到业务对象,这意味着任何数目的Advisor都可自动应用到业务对象。如果Advisor中没有切入点符合业务对象的方法,这个对象就不会被代理。如果增加了新的业务对象,只要它们符合切入点定义,DefaultAdvisorAutoProxyCreator将自动为其生成代理,无须额外   配置。

当有大量的业务对象需要采用相同处理,DefaultAdvisorAutoProxyCreator是非常有用的。一旦定义恰当,直接增加业务对象,而不需要额外的代理配置,系统自动为其增加     代理。

<beans>

    <!-- 定义Hibernate局部事务管理器,可切换到JTA全局事务管理器 -->

    <bean id="transactionManager"

         class="org.springframework.orm.hibernate3.
        HibernateTransactionManager">

        <!-- 定义事务管理器时,依赖注入SessionFactory -->

         <property name="sessionFactory" ref bean="sessionFactory"/>

    </bean>

    <!-- 定义事务拦截器 -->

    <bean id="transactionInterceptor"

        class="org.springframework.transaction.interceptor.
        TransactionInterceptor">

         <property name="transactionManager" ref="transactionManager"/>

         <property name="transactionAttributeSource">

            <props>

                <prop key="*">PROPAGATION_REQUIRED</prop>

                  <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>

            </props>

        </property>

    </bean>

    <!-- 定义DefaultAdvisorAutoProxyCreator Bean,这是一个Bean后处理器 -->

    <bean class="org.springframework.aop.framework.autoproxy.
    DefaultAdvisorAutoProxyCreator"/>

    <!-- 定义事务Advisor -->

    <bean class="org.springframework.transaction.interceptor.
    TransactionAttributeSourceAdvisor">

         <property name="transactionInterceptor" ref=
        "transactionInterceptor"/>

    </bean>

    <!-- 定义额外的Advisor>

    <bean id="customAdvisor" class="lee.MyAdvisor"/>

</beans>

DefaultAdvisorAutoProxyCreator支持过滤和排序。如果需要排序,可让Advisor实现org.springframework.core.Ordered接口来确定顺序。TransactionAttributeSourceAdvisor已经实现Ordered接口,因此可配置其顺序,默认是不排序。

采用这样的方式,一样可以避免客户端代码直接访问目标Bean,而且配置更加简洁。只要目标Bean符合切入点检查,Bean后处理器自动为目标Bean创建代理,无须依次指定目标Bean名。

注意:Spring也支持以编程方式创建AOP代理,但这种方式将AOP代理所需要的目标对象和处理Bean等对象的耦合降低到代码层次,因此不推荐使用。如果读者需要深入了解如何通过编程方式创建AOP代理,请参阅笔者所著的《Spring2.0宝典》。

posted @ 2009-07-19 10:16 jadmin 阅读(92) | 评论 (0)编辑 收藏

6.2 Spring的AOP

AOP(Aspect Orient Programming),也就是面向切面编程,作为面向对象编程的一种补充。问世的时间并不太长,甚至在国内的翻译还不太统一(有些书翻译成面向方面编程),但它确实极好地补充了面向对象编程的方式。面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。

可以这样理解,面向对象编程是从静态角度考虑程序结构,面向切面编程是从动态角度考虑程序运行过程。

Spring AOP是Spring框架的一个重要组件,极好地补充了Spring IoC容器的功能。Spring AOP将Spring IoC容器与AOP组件紧密结合,丰富了IoC容器的功能。当然,即使不使用AOP组件,依然可以使用Spring的IoC容器。

6.2.1 AOP的基本概念

AOP从程序运行角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。

AOP框架并不与特定的代码耦合,AOP框架能处理程序执行中的特定点,而不是某个具体的程序。AOP框架具有如下两个特征:

   ● 各步骤之间的良好隔离性。

   ● 源代码无关性。

下面是关于面向切面编程的一些术语:

   ● 切面,业务流程运行的某个特定步骤,就是运行过程的关注点,关注点可能横切多个对象。

   ● 连接点,程序执行过程中明确的点,如方法的调用或异常的抛出。Spring AOP中,连接点总是方法的调用,Spring并没有显式地使用连接点。

   ● 处理(Advice),AOP框架在特定的连接点执行的动作。处理有around、before和throws等类型。大部分框架都以拦截器作为处理模型。

   ● 切入点,系列连接点的集合,它确定处理触发的时机。AOP框架允许开发者自己定义切入点,如使用正则表达式。

   ● 引入,添加方法或字段到被处理的类。Spring允许引入新的接口到任何被处理的对象。例如,可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。

   ● 目标对象,包含连接点的对象。也称为被处理对象或被代理对象。

   ● AOP代理,AOP框架创建的对象,包含处理。简单地说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。

注意:面向切面编程是比较前沿的知识,而国内大部分翻译人士翻译计算机文献时,总是一边开着各种词典和翻译软件,一边逐词去看文献,不是先从总体上把握知识的架构。因此,难免导致一些术语的翻译词不达意,例如,Socket被翻译成“套接字”等。在面向切面编程的各术语翻译上,也存在较大的差异。对于Advice一词,有翻译为“通知”的,有翻译为“建议”的,如此种种,不一而足。实际上,Advice指AOP框架在特定切面所做的事情,故而笔者翻译为“处理”,希望可以表达Advice的真正含义。

6.2.2 AOP的代理

所谓AOP代理,就是AOP框架动态创建的对象,这个对象通常可以作为目标对象的替代品,而AOP代理提供比目标对象更加强大的功能。真实的情形是,当应用调用AOP代理的方法时,AOP代理会在自己的方法中回调目标对象的方法,从而完成应用的调用。

关于AOP代理的典型例子就是Spring中的事务代理Bean。通常,目标Bean的方法不是事务性的,而AOP代理包含目标Bean的全部方法,而且这些方法经过加强变成了事务性方法。简单地说,目标对象是蓝本,AOP代理是目标对象的加强,在目标对象的基础上,增加属性和方法,提供更强大的功能。

目标对象包含一系列切入点。切入点可以触发处理连接点集合。用户可以自己定义切入点,如使用正则表达式。AOP代理包装目标对象,在切入点处加入处理。在切入点加入的处理,使得目标对象的方法功能更强。

Spring默认使用JDK动态代理实现AOP代理,主要用于代理接口。也可以使用CGLIB代理。实现类的代理,而不是接口。如果业务对象没有实现接口,默认使用CGLIB代理。但面向接口编程是良好的习惯,尽量不要面向具体类编程。因此,业务对象通常应实现一个或多个接口。

下面是一个简单动态代理模式的示例,首先有一个Dog的接口,接口如下:

public interface Dog

{

    //info方法声明

    public void info();

    //run方法声明

    public void run();

}

然后,给出该接口的实现类,实现类必须实现两个方法,源代码如下:

public class DogImpl implements Dog

{

    //info方法实现,仅仅打印一个字符串

    public void info()

    {

        System.out.println("我是一只猎狗");

    }

    //run方法实现,仅仅打印一个字符串

    public void run()

    {

        System.out.println("我奔跑迅速");

    }

}

上面的代码没有丝毫独特之处,是典型的面向接口编程的模型,为了有更好的解耦,采用工厂来创建Dog实例。工厂源代码如下:

public class DogFactory

{

    //工厂本身是单态模式,因此,将DogFactory作为静态成员变量保存

    private static DogFactory df;

    //将Dog实例缓存

    private Dog gundog;

    //默认的构造器,单态模式需要的构造器是private

    private DogFactory()

    {

    }

    //单态模式所需的静态方法,该方法是创建本类实例的唯一方法点

    public static DogFactory instance()

    {

        if (df == null)

        {

            df = new DogFactory();

        }

        return df;

    }

    //获得Dog实例

    public Dog getDog(String dogName)

    {

        //根据字符串参数决定返回的实例

        if (dogName.equals("gundog"))

        {

            //返回Dog实例之前,先判断缓存的Dog是否存在,如果不存在才创建,

            否则直接返回缓存的Dog实例

            if (gundog == null )

            {

                gundog = new DogImpl();

            }

            return gundog;

         }

        return null;

    }

}

下面是一个通用的处理类,该处理类没有与任何特定的类耦合,它可以处理所有的目标对象。从JDK 1.3起,Java的import java.lang.reflect下增加InvocationHandler接口,该接口是所有处理类的根接口。

该类处理类的源代码如下:

public class ProxyHandler implements InvocationHandler

{

    //需被代理的目标对象

    private Object target;

    //执行代理的目标方法时,该invoke方法会被自动调用

    public Object invoke(Object proxy, Method method, Object[] args)throws
    Exception

    {

        Object result = null;

        if (method.getName().equals("info"))

        {

            System.out.println("======开始事务...");

            result =method.invoke(target, args);

            System.out.println("======提交事务...");

        }

        else

        {

            result =method.invoke(target, args);

        }

        return result;

    }

    //通过该方法,设置目标对象

    public void setTarget(Object o)

    {

        this.target = o;

    }

}

该处理类实现InvocationHandler接口,实现该接口必须实现invoke(Object proxy, Method method, Object[] args)方法,程序调用代理的目标方法时,自动变成调用invoke方法。

该处理类并未与任何接口或类耦合,它完全是通用的,它的目标实例是Object类型,可以是任何的类型。

在invoke方法内,对目标对象的info方法进行简单加强,在开始执行目标对象的方法之前,先打印开始事务,执行目标对象的方法之后,打印提交事务。

通过method对象的invoke方法,可以完成目标对象的方法调用,执行代码如下:

result =method.invoke(target, args);

下面是代理工厂:

public class MyProxyFactory

{

    /**

      * 实例Service对象

     * @param serviceName String

     * @return Object

     */

    public static Object getProxy(Object object)

    {

       //代理的处理类

        ProxyHandler handler = new ProxyHandler();

       //把该dog实例托付给代理操作

        handler.setTarget(object);

        //第一个参数是用来创建动态代理的ClassLoader对象,只要该对象能访问Dog接口
        即可

        //第二个参数是接口数组,正是代理该接口数组

        //第三个参数是代理包含的处理实例

        return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),

            object.getClass().getInterfaces(),handler);

    }

}

代理工厂里有一行代码:

Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);

Proxy.newProxyInstance()方法根据接口数组动态创建代理类实例,接口数组通过object.getClass().getInterfaces()方法获得,创建的代理类是JVM在内存中动态创建的,该类实现传入接口数组的全部接口。

因此,Dynamic Proxy要求被代理的必须是接口的实现类,否则无法为其构造相应的动态类。因此,Spring对接口实现类采用Dynamic Proxy实现AOP,而对没有实现任何接口的类,则通过CGLIB实现AOP代理。

下面是主程序:

public class TestDog

{

    public static void main(String[] args)

    {

        Dog dog = null;

        //创建Dog实例,该实例将作为被代理对象

        Dog targetObject = DogFactory.instance().getDog("gundog");

        //以目标对象创建代理

        Object proxy = MyProxyFactory.getProxy(targetObject);

        if (proxy instanceof Dog)

        {

            dog = (Dog)proxy;

        }

        //测试代理的方法

        dog.info();

        dog.run();

    }

}

代理实例会实现目标对象实现的全部接口。因此,代理实例也实现了Dog接口,程序运行结果如下:

[java] ======开始事务...

[java] 我是一只猎狗

[java] ======提交事务...

[java] 我奔跑迅速

代理实例加强了目标对象的方法——仅仅打印了两行字符串。当然,此种加强没有实际意义。试想一下,若程序中打印字符串的地方,换成真实的事务开始和事务提交,则代理实例的方法为目标对象的方法增加了事务性。

6.2.3 创建AOP代理

通过前面的介绍,AOP代理就是由AOP框架动态生成的一个对象,该对象可作为目标对象使用,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP方法在特定切面插入处理,在处理之间回调目标对象的方法。

AOP代理所包含的方法与目标对象所包含的方法的示意图,如图6.1所示。

文本框:图6.1  AOP代理的方法与目标对象的方法Spring中AOP代理由Spring的IoC容器负责生成和管理,其依赖关系也由IoC容器负责管理。因此,AOP代理能够引用容器中的其他Bean实例,这种引用由IoC容器的依赖注入提供。

Spring的AOP代理大都由ProxyFactoryBean工厂类产生,如图6.1所示,产生一个AOP代理至少有两个部分:目标对象和AOP框架所加入的处理。因此,配置ProxyFactoryBean时需要确定如下两个属性:

   ● 代理的目标对象。

   ● 处理(Advice)。

注意:关于Advice的更多知识,此处由于篇幅原因,无法深入讨论。实际上,Spring也提供了很多种Advice的实现,如需要更深入了解Spring AOP,请读者参考笔者所著的《Spring2.0宝典》。

所有代理工厂类的父类是org.springframework.aop.framework.ProxyConfig。因此,该类的属性是所有代理工厂的共同属性,这些属性也是非常关键的,包括:

   ● proxyTargetClass,确定是否代理目标类,如果需要代理目标是类,该属性设为true,此时需要使用CGLIB生成代理;如果代理目标是接口,该属性设为false,默认是false。

   ● optimize,确定是否使用强优化来创建代理。该属性仅对CGLIB代理有效;对JDK 动态代理无效。

   ● frozen,确定是否禁止改变处理,默认是false。

   ● exposeProxy,代理是否可以通过ThreadLocal访问,如果exposeProxy属性为true,则可通过AopContext.currentProxy()方法获得代理。

   ● aopProxyFactory,所使用的AopProxyFactory具体实现。该参数用来指定使用动态代理、CGLIB或其他代理策略。默认选择动态代理或CGLIB。一般不需要指定该属性,除非需要使用新的代理类型,才指定该属性。

配置ProxyFactoryBean工厂bean时,还需要指定它的特定属性,ProxyFactoryBean的特定属性如下所示:

   ● proxyInterfaces,接口名的字符串数组。如果没有确定该参数,默认使用CGLIB代理。

   ● interceptorNames,处理名的字符串数组。此处的次序很重要,排在前面的处理,优先被调用。此处的处理名,只能是当前工厂中处理的名称,而不能使用bean引用。处理名字支持使用通配符(*)。

   ● singleton,工厂是否返回单态代理。默认是true,无论 getObject()被调用多少次,将返回相同的代理实例。如果需要使用有状态的处理——例如,有状态的mixin,可改变默认设置,prototype处理。

posted @ 2009-07-19 10:15 jadmin 阅读(64) | 评论 (0)编辑 收藏

6.1 两种后处理器

Spring 框架提供了很好的扩展性,除了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展。这种扩展并不是通过实现BeanFactory或ApplicationContext的子类,而是通过两个后处理器对IoC容器进行扩展。Spring提供了两种常用的后处理器:

   ● Bean后处理器,这种后处理器会对容器中特定的Bean进行定制,例如功能的    加强。

   ● 容器后处理器,这种后处理器对IoC容器进行特定的后处理。

下面将介绍这两种常用的后处理器以及两种后处理器相关知识。

6.1.1 Bean后处理器

Bean后处理器是一种特殊的Bean,这种特殊的Bean并不对外提供服务,它无须id属性,但它负责对容器中的其他Bean执行后处理,例如为容器中的目标Bean生成代理。这种Bean可称为Bean后处理器,它在Bean实例创建成功后,对其进行进一步的加强     处理。

Bean后处理器必须实现BeanPostProcessor接口。

BeanPostProcessor接口包含两个方法:

   ● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,该方法的第一个参数是系统即将初始化的Bean实例,第二个参数是Bean实例的名字。

   ● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,该方法的第一个参数是系统刚完成初始化的Bean实例,第二个参数是Bean实例的名字。

实现该接口的Bean必须实现这两个方法,这两个方法会对容器的Bean进行后处理。两个方法会在目标Bean初始化之前和初始化之后分别调用。这两个方法用于对系统完成的默认初始化进行加强。

注意:Bean后处理器是对IoC容器一种极好的扩展,Bean后处理器可以对容器中的Bean进行后处理,这种后处理完全由开发者决定。

下面将定义一个简单的Bean后处理器,该Bean后处理器将对容器中其他Bean进行后处理。Bean后处理器的代码如下:

//自定义Bean后处理器,负责后处理容器中所有的Bean

public class MyBeanPostProcessor implements BeanPostProcessor

{

    //在初始化bean之前,调用该方法

    public Object postProcessBeforeInitialization(Object bean , String
    beanName)throws BeansException

    {

        //仅仅打印一行字符串

        System.out.println("系统正在准备对" + beanName + "进行初始化...");

        return bean;

    }

    //在初始化bean之后,调用该方法

    public Object postProcessAfterInitialization(Object bean , String
    beanName)throws BeansException

    {

        System.out.println("系统已经完成对" + beanName + "的初始化");

        //如果系统刚完成初始化的bean是Chinese

        if (bean instanceof Chinese)

        {

            //为Chinese实例设置name属性

            Chinese c = (Chinese)bean;

            c.setName("wawa");

         }

        return bean;

    }

}

下面是Chinese的源代码,该类实现了InitializingBean接口,还额外提供了一个初始化方法,这两个方法都由Spring容器控制回调。

public class Chinese implements Person,InitializingBean

{

    private Axe axe;

    private String name;

    public Chinese()

    {

        System.out.println("Spring实例化主调bean:Chinese实例...");

    }

    public void setAxe(Axe axe)

    {

        System.out.println("Spring执行依赖关系注入...");

        this.axe = axe;

    }

    public void setName(String name)

    {

        this.name = name;

    }

    public void useAxe()

    {

        System.out.println(name + axe.chop());

    }

    public void init()

    {

        System.out.println("正在执行初始化方法   init...");

    }

   public void afterPropertiesSet() throws Exception

    {

       System.out.println("正在执行初始化方法 afterPropertiesSet...");

    }

}

配置文件如下:

<?xml version="1.0" encoding="gb2312"?>

<!-- 指定Spring 配置文件的dtd>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

    "http://www.springframework.org/dtd/spring-beans.dtd">

<!-- Spring配置文件的根元素 -->

<beans>

    <!-- 配置bean后处理器,可以没有id属性,此处id属性为了后面引用 -->

    <bean id="beanPostProcessor" class="lee.MyBeanPostProcessor"/>

    <bean id="steelAxe" class="lee.SteelAxe"/>

    <bean id="chinese" class="lee.Chinese" init-method="init">

        <property name="axe" ref="steelAxe"/>

    </bean>

</beans>

本应用的chinese具有两个初始化方法:

   ● init-method指定初始化方法。

   ● 实现InitializingBean接口,提供了afterPropertiesSet初始化方法。

MyBeanPostProcessor类实现了BeanPostProcessor接口,并实现了该接口的两个方法,这两个方法分别在初始化方法调用之前和之后得到回调。

注意:上面的配置文件配置Bean后处理器时,依然为Bean处理器指定了id属性,指定id属性是为了方便程序通过该id属性访问Bean后处理器。大部分时候,程序无须手动访问该Bean后处理器,因此无须为其指定id属性。

主程序如下:

public class BeanTest

{

    public static void main(String[] args)throws Exception

    {

        //CLASSPATH路径下的bean.xml文件创建Resource对象

        ClassPathResource isr = new ClassPathResource("bean.xml");

        //以Resource对象作为参数,创建BeanFactory的实例

        XmlBeanFactory factory = new XmlBeanFactory(isr);

        //获取Bean后处理器实例

        MyBeanPostProcessor beanProcessor =

            (MyBeanPostProcessor)factory.getBean("beanPostProcessor");

        //注册BeanPostProcessor实例

        factory.addBeanPostProcessor(beanProcessor);

        System.out.println("程序已经实例化BeanFactory...");

        Person p = (Person)factory.getBean("chinese");

        System.out.println("程序中已经完成了chinese bean的实例化...");

        p.useAxe();

    }

}

如果使用BeanFactory作为Spring容器,必须手动注册Bean后处理器,因此在程序中先获取Bean后处理器实例,然后手动注册——这就是在配置文件中指定Bean后处理器id属性的原因。通过BeanFactory的addBeanPostProcessor可以注册BeanPostProcessor实例。程序执行结果如下:

[java] 程序已经实例化BeanFactory...

[java] Spring实例化主调bean:Chinese实例...

[java] Spring实例化依赖bean:SteelAxe实例...

[java] 系统正在准备对steelAxe进行初始化...

[java] 系统已经完成对steelAxe的初始化

[java] Spring执行依赖关系注入...

[java] 系统正在准备对chinese进行初始化...

[java] 正在执行初始化方法 afterPropertiesSet...

[java] 正在执行初始化方法   init...

[java] 系统已经完成对chinese的初始化

[java] 程序中已经完成了chinese bean的实例化...

[java] wawa钢斧砍柴真快

在配置文件中配置chinese实例时,并未指定name属性值。但程序执行时,name属性有了值,这就是Bean后处理器完成的,在Bean后处理器中判断Bean是否是Chinese实例,然后设置它的name属性。

容器中一旦注册了Bean后处理器,Bean后处理器会自动启动,在容器中每个Bean创建时自动工作,完成加入Bean后处理器需要完成的工作。

实现BeanPostProcessor接口的Bean后处理器可对Bean进行任何操作,包括完全忽略这个回调。BeanPostProcessor通常用来检查标记接口或将Bean包装成一个Proxy的事情。Spring的很多工具类,就是通过Bean后处理器完成的。

从主程序中看到,采用BeanFactory作为Spring容器时,必须手动注册BeanPost- Processor。而对于ApplicationContext,则无须手动注册。ApplicationContext可自动检测到容器中的Bean后处理器,自动注册。Bean后处理器会在Bean实例创建时,自动启动。即主程序采用如下代码,效果完全一样:

public class BeanTest

{

    public static void main(String[] args)throws Exception

    {

        ApplicationContext ctx = new ClassPathXmlApplicationContext
        ("bean.xml");

        Person p = (Person)factory.getBean("chinese");

        System.out.println("程序中已经完成了chinese bean的实例化...");

        p.useAxe();

    }

}

使用ApplicationContext作为容器,无须手动注册BeanPostProcessor。因此,如果需要使用Bean后处理器,Spring容器建议使用ApplicationContext,而不是BeanFactory。

6.1.2 Bean后处理器的用处

上一节介绍了一个简单的Bean后处理器,上面的Bean后处理器负责对容器中的Chinese Bean进行后处理,不管Chinese Bean如何初始化,总是将Chinese Bean的name属性设置为wawa。这种后处理看起来作用并不是特别大。

实际上,Bean后处理器完成的工作更加实际,例如生成Proxy。Spring框架本身提供了大量的Bean后处理器,这些后处理器负责对容器中的Bean进行后处理。

下面是Spring提供的两个常用的后处理器:

   ● BeanNameAutoProxyCreator,根据Bean实例的name属性,创建Bean实例的代理。

   ● DefaultAdvisorAutoProxyCreator,根据提供的Advisor,对容器中所有的Bean实例创建代理。

上面提供的两个Bean后处理器,都用于根据容器中配置的拦截器创建目标Bean代理,目标代理就在目标Bean的基础上修改得到。

注意:如果需要对容器中某一批Bean进行特定的处理,可以考虑使用Bean后处理器。

6.1.3 容器后处理器

除了上面提供的Bean后处理器外,Spring还提供了一种容器后处理器。Bean后处理器负责后处理容器生成的所有Bean,而容器后处理器则负责后处理容器本身。

容器后处理器必须实现BeanFactoryPostProcessor接口。实现该接口必须实现如下一个方法:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

实现该方法的方法体就是对Spring容器进行的处理,这种处理可以对Spring容器进行任意的扩展,当然也可以对Spring容器不进行任何处理。

类似于BeanPostProcessor,ApplicationContext可自动检测到容器中的容器后处理器,并且自动注册容器后处理器。但若使用BeanFactory作为Spring容器,则必须手动注册后处理器。

下面定义了一个容器后处理器,这个容器后处理器实现BeanFactoryPostProcessor接口,但并未对Spring容器进行任何处理,只是打印出一行简单的信息。该容器后处理器的代码如下:

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor

{

    //容器后处理器对容器进行的处理在该方法中实现

    public void postProcessBeanFactory(ConfigurableListableBeanFactory
    beanFactory)

        throws BeansException

    {

        System.out.println("程序对Spring所做的BeanFactory的初始化没有意
        见...");

    }

}

将该Bean作为普通Bean部署在容器中,然后使用ApplicationContext作为容器,容器会自动调用BeanFactoryPostProcessor处理Spring容器。程序执行效果如下:

[java] 程序对Spring所做的BeanFactory的初始化没有意见...

实现BeanFactoryPostProcessor接口的Bean后处理器不仅可对BeanFactory执行后处理,也可以对ApplicationContext容器执行后处理。容器后处理器还可用来注册额外的属性编辑器。

注意:Spring没有提供ApplicationContextPostProcessor。也就是说,对于Application- Context容器,一样使用BeanFactoryPostProcessor作为容器后处理器。

Spring已提供如下两个常用的容器后处理器,包括:

   ● PropertyResourceConfigurer,属性占位符配置器。

   ● PropertyPlaceHolderConfigurer,另一种属性占位符配置器。

下面将详细介绍这两种常用的容器后处理器。

6.1.4 属性占位符配置器

Spring提供了PropertyPlaceholderConfigurer,它是一个容器后处理器,负责读取Java属性文件里的属性值,并将这些属性值设置到Spring容器定义中。

通过使用PropertyPlaceholderConfigurer后处理器,可以将Spring配置文件中的部分设置放在属性文件中设置。这种配置方式当然有其优势:可以将部分相似的配置(如数据库的urls、用户名和密码)放在特定的属性文件中,如果只需要修改这部分配置,则无须修改Spring配置文件,修改属性文件即可。

下面的配置文件配置了PropertyPlaceholderConfigurer后处理器,在配置数据源Bean时,使用了属性文件中的属性值。配置文件的代码如下:

<?xml version="1.0" encoding="GBK"?>

<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置一个容器后处理器Bean -->

    <bean id="propertyConfigurer"

        class="org.springframework.beans.factory.config.
        PropertyPlaceholderConfigurer">

        <!-- locations属性指定属性文件的位置 -->

        <property name="locations">

            <list>

                <value>dbconn.properties</value>

                <!-- 如果有多个属性文件,依次在下面列出来 -->

            </list>

        </property>

    </bean>

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="${jdbc.driverClassName}"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="${jdbc.url}"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="${jdbc.username}"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="${jdbc.password}"/>

    </bean>

</beans>

在上面的配置文件中,配置driverClass和jdbcUrl等信息时,并未直接设置这些属性的属性值,而是设置了${jdbc.driverClassName}和${jdbc.url}属性值。这表明Spring容器将从propertyConfigurer指定属性文件中搜索这些key对应的value,并为该Bean的属性值设置这些value值。

如前所述,ApplicationContext会自动检测部署在容器的容器后处理器,无须额外的注册,容器自动注册。因此,只需提供如下Java Properties文件:

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/j2ee

jdbc.username=root

jdbc.password=32147

通过这种方法,可从主XML配置文件中分离出部分配置信息。如果仅需要修改数据库连接属性,则无须修改主XML配置文件,只需要修改属性文件即可。采用属性占位符的配置方式,可以支持使用多个属性文件。通过这种方式,可将配置文件分割成多个属性文件,从而降低修改配置的风险。

注意:对于数据库连接等信息集中的配置,可以将其配置在Java属性文件中,但不要过多地将Spring配置信息抽离到Java属性文件中,否则可能会降低Spring配置文件的可读性。

6.1.5 另一种属性占位符配置器(PropertyOverrideConfigurer)

PropertyOverrideConfigurer是Spring提供的另一个容器后处理器,这个后处理器的额作用与上面介绍的容器后处理器作用大致相同。但也存在些许差别:PropertyOverride- Configurer使用的属性文件用于覆盖XML配置文件中的定义。即PropertyOverride- Configurer允许XML配置文件中有默认的配置信息。

如果PropertyOverrideConfigurer的属性文件有对应配置信息,XML文件中的配置信息被覆盖;否则,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的属性文件,应是如下的格式:

beanName.property=value

beanName是属性占位符试图覆盖的Bean名,property是试图覆盖的属性名。看如下配置文件:

<?xml version="1.0" encoding="GBK"?>

<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置一个属性占位符Bean。ApplictionContext能自动识别
    PropertyPlaceholderConfigurer Bean -->

    <bean id="propertyOverrider"

        class="org.springframework.beans.factory.config.
        PropertyOverrideConfigurer">

        <property name="locations">

            <list>

                <value>dbconn.properties</value>

                <!-- 如果有多个属性文件,依次在下面列出来 -->

            </list>

        </property>

    </bean>

    <!-- 定义数据源Bean,使用C3P0数据源实现 -->

    <bean id="dataSource" class="com.mchange.v2.c3p0.
    ComboPooledDataSource" destroy-method="close">

        <!-- 指定连接数据库的驱动 -->

        <property name="driverClass" value="dd"/>

        <!-- 指定连接数据库的URL -->

        <property name="jdbcUrl" value="xx"/>

        <!-- 指定连接数据库的用户名 -->

        <property name="user" value="dd"/>

        <!-- 指定连接数据库的密码 -->

        <property name="password" value="xx"/>

    </bean>

</beans>

上面的配置文件中,指定数据源Bean的各种属性值时,只是随意指定了几个属性值,很明显通过这几个属性值无法连接到数据库服务。

但因为Spring容器中部署了一个PropertyOverrideConfigurer的容器后处理器,而且Spring容器使用ApplicationContext作为容器,它会自动检测容器中的容器后处理器,无须额外的注册,容器自动注册该后处理器。

PropertyOverrideConfigurer后处理器读取dbconn.properties文件中的属性,用于覆盖目标Bean的属性。因此,如果属性文件中有dataSource Bean属性的设置,则配置文件中指定的属性值将没有任何作用。

dbconn.properties属性文件如下:

dataSource.driverClassName=com.mysql.jdbc.Driver

dataSource.url=jdbc:mysql://wonder:3306/j2ee

dataSource.username=root

dataSource.password=32147

注意属性文件的格式必须是:

beanName.property=value

也就是说,dataSource必须是容器中真实存在的bean名,否则程序将出错。

注意:程序无法知道BeanFactory定义是否被覆盖。仅仅通过察看XML配置文件,无法知道配置文件的配置信息是否被覆盖。如有多个PorpertyOverrideConfigurer对同一Bean属性定义了覆盖,最后一个覆盖获胜。

posted @ 2009-07-19 10:12 jadmin 阅读(117) | 评论 (0)编辑 收藏

4.9 Struts与Hibernate的整合策略

前面介绍了Hibernate的一些相关知识点,距离Hibernate进入实际开发还有一段路要走。Hibernate作为持久层解决方案,必须与其他表现层技术组合在一起才可形成一个J2EE开发框架。经常看到网上一些朋友给出的Hibernate入门示例,居然在JSP页面中访问Hibernate Configuratioin对象。甚至看到某些所谓的精通J2EE书籍,也居然在JSP页面中访问Hibernate的Configuration对象——这种现状非常让人担忧,Hibernate并不是万金油,并不是说项目中使用Hibernate就怎么了不起了,而是通过使用Hibernate,可以让J2EE应用架构更科学,可以让开发者以更好的面向对象的方式进行项目开发。

反过来说,即使不使用Hibernate,而使用普通的JDBC持久化解决方案,也不应该在JSP(表现层)访问到JDBC API(持久层API)。下面介绍如何让Hibernate和Struts进行整合,整合Spring部分将在后面章节介绍。

4.9.1 工厂模式介绍

工厂模式是指当应用程序中A组件需要B组件协助时,并不是直接创建B组件的实例,而是通过B组件的工厂——该工厂可以生成某一个类型组件的实例。在这种模式下,A组件无须与B组件以硬编码方式耦合在一起,而只需要与B组件的工厂耦合。

对于A组件而言,它只关心工厂生产的实例是否满足某种规范,即实现了某个接口(满足接口规范,即可供自己正常调用)。这种模式提供了对象之间清晰的角色划分,降低了程序的耦合。

接口产生的全部实例通常实现相同接口,接口里定义全部实例共同拥有的方法,这些方法在不同的实现类中实现方式不同。程序调用者无须关心方法的具体实现,从而降低了系统异构的代价。

下面是工厂模式的示例代码:

//Person接口定义

public interface Person

{  

    /**

    * @param name 对name打招呼

    * @return 打招呼的字符串

    */

    public String sayHello(String name);

    /**

    * @param name 对name告别

    * @return 告别的字符串

    */

    public String sayGoodBye(String name);

}

该接口定义Person的规范,该接口必须拥有两个方法:能打招呼、能告别。规范要求实现该接口的类必须具有这两个方法:

//American类实现Person接口

public class American implements Person

{

    /**

    * @param name 对name打招呼

    * @return 打招呼的字符串

    */

    public String sayHello(String name)

    {

        return name + ",Hello";

    }

    /**

    * @param name 对name告别

    * @return 告别的字符串

    */

    public String sayGoodBye(String name)

    {

        return name + ",Good Bye";

    }

}

下面是实现Person接口的另一个实现类Chinese

public class Chinese implements Person

{

    /**

    * @param name 对name打招呼

    * @return 打招呼的字符串

    */

    public String sayHello(String name)

    {

        return name + ",您好";

    }

    /**

    * @param name 对name告别

    * @return 告别的字符串

    */

    public String sayGoodBye(String name)

    {

        return name + ",下次再见";

    }

}

然后看Person工厂的代码:

public class PersonFactory

{

    /**

    * 获得Person实例的工厂方法

    * @ param ethnic 调用该实例工厂方法传入的参数

    * @ return返回Person实例

    */

    public Person getPerson(String ethnic)

    {

        //根据参数返回Person接口的实例

        if (ethnic.equalsIgnoreCase("chin"))

        {

            return new Chinese();

        }

        else

        {

            return new American();

        }

    }

}

最简单的工厂模式的框架基本如上所示。

主程序部分仅仅需要与工厂耦合,而无须与具体的实现类耦合在一起。下面是主程序部分:

public class FactroyTest

{

    public static void main(String[] args)

    {

        //创建PersonFactory的实例,获得工厂实例

        PersonFactory pf = new PersonFactory();

        //定义接口Person的实例,面向接口编程

        Person p = null;

        //使用工厂获得Person的实例

        p = pf.getPerson("chin");

        //下面调用Person接口的方法

        System.out.println(p.sayHello("wawa"));

        System.out.println(p.sayGoodBye("wawa"));

        //使用工厂获得Person的另一个实例

        p = pf.getPerson("ame");

        //再次调用Person接口的方法

        System.out.println(p.sayHello("wawa"));

        System.out.println(p.sayGoodBye("wawa"));

    }

}

主程序从Person接口的具体类中解耦出来,而且程序调用者无须关心Person的实例化过程,角色划分清晰。主程序仅仅与工厂服务定位结合在一起,获得工厂的引用,程序将可获得所有工厂产生的实例。具体类的变化,重要接口不发生任何改变,调用者程序代码部分几乎无须发生任何改动。

4.9.2 使用DAO模式

第1章介绍了J2EE应用的架构,最上面的表现层,表现层与MVC框架的控制器交互,控制器负责调用业务逻辑组件的业务逻辑方法来处理用户请求,而业务逻辑组件则依赖于DAO组件提供的数据库原子操作,这种模式也被称为DAO模式。

由上面关于J2EE应用架构的介绍可见,控制器总是依赖于业务逻辑组件,而业务逻辑组件总是依赖于DAO组件。也就是说,控制器需要调用业务逻辑组件的方法,而业务逻辑组件需要调用DAO组件的方法。

DAO模式的分层非常清晰,持久层访问被封装在DAO层下,而决不会扩散到业务逻辑层,更不会在JSP页面(表现层)中进行持久层访问。

注意:即使在早期的Model 1(使用JSP + JavaBean创建应用的模式,没有使用MVC设计模式)模式下,持久层访问也被封装在JavaBean中完成,而不是直接在JSP页面中进行数据库访问。对于直接在JSP中访问持久层API的做法,可以说根本不了解J2EE开发。

那么控制器采用怎样的方式访问业务逻辑组件呢?应该采用工厂模式,让控制器与业务逻辑组件的实现类分离,仅与业务逻辑工厂耦合;同样,业务逻辑组件也应该采用工厂模式访问DAO模式,而不是直接与DAO实现类耦合。

后面的案例部分会介绍更实际的整合策略,此处仅仅介绍DAO模式下两个工厂模式策略。

4.9.3 DAO组件的工厂模式

在J2EE应用开发中,可扩展性是一个随时需要关注的问题。而DAO组件是经常需要增加的项目组件,如果每次需要增加一个DAO组件都需要修改代码是相当让人沮丧的事情。为了避免这种情况,采用XML配置文件来管理所有的DAO组件,这种DAO组件配置文件的代码如下:

<?xml version="1.0" encoding="GBK"?>

<daoContext>

    <!-- 配置应用需要的sonDao组件 -->

    <dao id="sonDao" class="org.yeeku.dao.impl.SonDaoImpl"/>

    <!-- 配置应用需要的personDao组件 -->

    <dao id="personDao" class="org.yeeku.dao.impl.PersonDaoImpl"/>

</daoContext>

查看上面的配置文件可以看出,应用中有配置了两个DAO组件,因为每个DAO组件在J2EE应用中仅需要一个实例就足够了,因此DAO工厂类提供了一个缓存池来缓存每个DAO实例,并负责在应用启动时创建所有的DAO。

下面是DAO工厂类的代码:

public class DaoFactory

{

    //用于缓存DAO实例的Map对象

    private Map<String, Dao> daoMap = new HashMap<String , Dao>();

    //将DAO工厂写成单态模式

    private static DaoFactory df;

    //DAO工厂的构造器

    private DaoFactory()throws Exception

    {

        //使用SAXReader来负责解析daoContext.xml配置文档

        Document doc = new SAXReader().read(new File(ConstantsUtil.realPath

        + "\\daoContext.xml"));

        //获取文档的根文档

        Element root = doc.getRootElement();

        //获取daoContext根元素的所有子元素

        List el = root.elements();

        for (Iterator it = el.iterator();it.hasNext() ; )

        {

            //每个子元素对应一个DAO组件

            Element em = (Element)it.next();

            String id = em.attributeValue("id");

            //获取实现类

            String impl = em.attributeValue("class");

            //通过反射,根据类名创建DAO组件的实例

            Class implClazz = Class.forName(impl);

            Dao d = (Dao)implClazz.newInstance();

            //将创建的DAO组件放入缓存池中

            daoMap.put(id, d);

        }

    }

    //单态模式必须提供一个入口方法来创建DAO工厂的方法

    public static DaoFactory instance()throws Exception

    {

        //如果DAO工厂还未创建

        if (df == null)

        {

            df = new DaoFactory();

        }

        return df;

    }

    //下面的方法用于根据DAO组件ID获取DAO组件

    public Dao getDao(String id)

    {

        return daoMap.get(id);

    }

}

通过上面的工厂类代码可以看出,DAO工厂负责初始化所有的DAO组件。系统每增加一个DAO组件,无须再修改任何代码,仅仅需要在daoContext.xml配置文件中增加配置即可。

注意:这种整合策略非常优秀。可扩展性很好,如果应用需要增加一个DAO组件,只需要修改配置文件,并提供相应的DAO组件实现即可。而且,如果有一天需要重构DAO组件,只须提供修改过的DAO组件实现类,而业务逻辑组件无须任何改变。

业务逻辑组件代码无须与DAO实现类耦合,业务逻辑组件的代码面向DAO组件的接口编程,将业务逻辑组件和DAO组件的耦合降低到接口层次。

4.9.4 业务逻辑组件的工厂模式

与此类似的是,业务逻辑组件完全可以采用这种编程模式,业务逻辑组件的配置文件代码如下:

<?xml version="1.0" encoding="GBK"?>

<appContext>

    <!-- 配置应用需要的业务逻辑组件,每个业务逻辑组件对应一个app元素 -->

    <app id="wawa" class="org.yeeku.service.impl.WawaServiceImpl"/>

</appContext>

业务逻辑组件工厂同样可根据该配置文件来初始化所有业务逻辑组件,并将业务逻辑组件放入缓存池中,让控制器与业务逻辑组件的耦合降低到接口层次。业务逻辑组件的工厂类代码如下:

public class AppFactory

{

    private Map<String , Object> appMap = new HashMap<String , Object>();

    //业务逻辑组件工厂采用单态模式

    private static AppFactory df;

    //业务逻辑组件工厂的私有构造器

    private AppFactory()throws Exception

    {

        //使用SAXReader来负责解析appContext.xml配置文档

        Document doc = new SAXReader().read(new File(ConstantsUtil.realPath

        + "\\appContext.xml"));

        //获取文档的根文档

        Element root = doc.getRootElement();

        //获取appContext根元素的所有子元素

        List el = root.elements();

        for (Iterator it = el.iterator();it.hasNext() ; )

        {

            //每个app元素对应一个业务逻辑组件

            Element em = (Element)it.next();

            String id = em.attributeValue("id");

            //根据配置文件指定的业务逻辑组件实现类来创建业务逻辑组件实例

            String impl = em.attributeValue("class");

            Class implClazz = Class.forName(impl);

            Object d = implClazz.newInstance();

            //将业务逻辑组件放入缓存池中

            appMap.put(id , d);

        }

    }

    //单态模式必须提供入口方法,用于创建业务逻辑组件工厂

    public static AppFactory instance()throws Exception

    {

        //如果业务逻辑组件工厂为空

        if (df == null)

        {

            df = new AppFactory();

        }

        return df;

    }

    //根据业务逻辑组件的id属性获取业务逻辑组件

    public Object getApp(String id)

    {

        //直接从缓存池中取出业务逻辑组件,并返回

        return appMap.get(id);

    }

}

从某种程度上来讲,这种方式与后来Spring的控制反转(Inversion of Control,IoC)容器有异曲同工之妙,但Spring的IoC容器则提供了更多的功能。

上面的两个类中都用到了一个ConstantsUtil,它仅用于保存一个全局变量,有一个public static的realPath属性,该属性用于保存应用在服务器中的路径。

posted @ 2009-07-19 10:08 jadmin 阅读(64) | 评论 (0)编辑 收藏

4.8 事 件 机 制

通常,Hibernate执行持久化过程中,应用程序无法参与其中。所有的数据持久化操作,对用户都是透明的,用户无法插入自己的动作。

通过事件框架,Hibernate允许应用程序能响应特定的内部事件,从而允许实现某些通用的功能,或对Hibernate功能进行扩展。

Hibernate的事件框架由两个部分组成:

   ● 拦截器机制,对于特定动作拦截,回调应用中的特定动作。

   ● 事件系统,重写Hibernate的事件监听器。

4.8.1 拦截器

通过Interceptor接口,可以从Session中回调应用程序的特定方法,这种回调机制可让应用程序在持久化对象被保存、更新、删除或加载之前,检查并修改其属性。

通过Interceptor接口,可以在数据进入数据库之间,对数据进行最后的检查,如果数据不符合要求,可以修改数据,从而避免非法数据进入数据库。当然,通常无须这样做,只是在某些特殊的场合下,才考虑使用拦截器完成检查功能。

使用拦截器可按如下步骤进行:

(1)定义实现Interceptor接口的拦截器类;

(2)通过Session启用拦截器,或者通过Configuration启用全局拦截器。

下面是一个拦截器的示例代码,该拦截器没有进行任何实际的操作,仅仅打印出标志代码:

public class MyInterceptor extends EmptyInterceptor

{

    //更新的次数

    private int updates;

    //插入的次数

    private int creates;

    //删除数据时,将调用onDelete方法

    public void onDelete(Object entity,Serializable id,Object[]

    state,String[] propertyNames, Type[] types)

    {

        //do nothing

    }

    //同步Session和数据库中的数据

    public boolean onFlushDirty(Object entity, Serializable id, Object[]

    currentState, Object[] previousState, String[] propertyNames, Type[]

                            types)

    {

        //每同步一次,修改的累加器加1

        updates++;

        for ( int i=0; i < propertyNames.length; i++ )

        {

            if ( "lastUpdateTimestamp".equals( propertyNames[i] ) )

            {

                currentState[i] = new Date();

                return true;

            }

        }

        return false;

        }

    //加载持久化实例时,调用该方法

    public boolean onLoad(Object entity,Serializable id,Object[]

    state,String[] propertyNames,Type[] types)

    {

        System.out.println("========================");

        for ( int i=0; i < propertyNames.length; i++ )

        {

            if ( "name".equals( propertyNames[i] ) )

            {

                System.out.println(state[i]);

                state[i] = "aaa";

                return true;

            }

        }

        return false;

    }

    //保存持久化实例时,调用该方法

    public boolean onSave(Object entity,Serializable id,Object[]

    state,String[] propertyNames,Type[] types)

    {

        creates++;

        for ( int i=0; i<propertyNames.length; i++ )

        {

            if ( "createTimestamp".equals( propertyNames[i] ) )

            {

                state[i] = new Date();

                return true;

            }

        }

        return false;

    }

    //提交刷新

    public void postFlush(Iterator entities)

    {

        System.out.println("创建的次数: " + creates + ", 更新的次数: " +

    updates);

    }

    public void preFlush(Iterator entities)

    {

        updates=0;

        creates=0;

    }

    //事务提交前,触发该方法

    public void beforeTransactionCompletion(Transaction tx)

    {

        System.out.println("事务即将结束");

    }

    //事务提交后,触发该方法

    public void afterTransactionCompletion(Transaction tx)

    {

        System.out.println("事务已经结束");

    }

}

在上面的拦截器实现类中,实现了很多方法,这些方法都是在Hibernate执行特定动作时自动调用。

完成了拦截器的定义,下面是关于拦截器的使用。拦截器的使用有两种方法:

   ● 通过SessionFactory的openSession(Interceptor in)方法打开一个带局部拦截器的Session。

   ● 通过Configuration的setInterceptor(Interceptor in)方法设置全局拦截器。

下面是使用局部拦截器的示例代码:

public class HibernateUtil

{

    //静态类属性 SessionFactory

    public static final SessionFactory sessionFactory;

    //静态初始化块,完成静态属性的初始化

    static

    {

        try

        {

            //采用默认的hibernate.cfg.xml来启动一个Configuration的实例

            Configuration configuration=new Configuration().configure();

            //由Configuration的实例来创建一个SessionFactory实例

            sessionFactory = configuration.buildSessionFactory();

        }

        catch (Throwable ex)

        {

            System.err.println("初始化sessionFactory失败." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要

    对线程同步   

    public static final ThreadLocal session = new ThreadLocal();

    //不加拦截器的打开Session方法

    public static Session currentSession() throws HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            s = sessionFactory.openSession();

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

        }

        return s;

    }

    //加拦截器的打开Session方法

    public static Session currentSession(Interceptor it) throws

    HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            //以拦截器创建Session对象

            s = sessionFactory.openSession(it);

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

            }

        return s;

    }

    //关闭Session对象

    public static void closeSession() throws HibernateException

    {

        Session s = (Session) session.get();

        if (s != null)

            s.close();

        session.set(null);

    }

}

上面的Hibernate工具类提供了两个currentSession方法,分别用于不使用拦截器获取Session对象和使用拦截器获取Session对象。

下面是主程序使用拦截器的代码片段:

private void testUser()

{

    //以拦截器开始Session

    Session session = HibernateUtil.currentSession(new MyInterceptor());

    //开始事务

    Transaction tx = session.beginTransaction();

    //执行下面的代码时,可以看到系统回调onSave等方法

    /*

    User u = new User();

    u.setName("Yeeku Lee");

    u.setAge(28);

    u.setNationality("中国");

    session.persist(u);

    u.setAge(29);

    u.setAge(30);

    session.persist(u);

    */

    //执行下面的代码时,可以看到系统回调onLoad等方法

    Object o = session.load(User.class , new Integer(1));

    System.out.println(o);

    User u = (User)o;

    System.out.println(u.getName());

    //提交事务时,可以看到系统回调事务相关方法

    tx.commit();

    HibernateUtil.closeSession();

}

4.8.2 事件系统

Hibernate 3的事件系统是功能更强大的事件框架,事件系统可以替代拦截器,也可以作为拦截器的补充来使用。

基本上,Session接口的每个方法都有对应的事件。如LoadEvent和FlushEvent等。当Session调用某个方法时,Hibernate Session会生成对应的事件,并激活对应的事件监听器。

系统默认监听器实现的处理过程,完成了所有的数据持久化操作,包括插入和修改等操作。如果用户定义了自己的监听器,则意味着用户必须完成对象的持久化操作。

例如,可以在系统中实现并注册LoadEventListener监听器,该监听器负责处理所有调用Session的load()方法的请求。

监听器是单态模式对象,即所有同类型的事件处理共享同一个监听器实例,因此监听器不应该保存任何状态,即不应该使用成员变量。

使用事件系统可按如下步骤进行:

(1)实现自己的事件监听器类;

(2)注册自定义事件监听器,代替系统默认的事件监听器。

实现用户的自定义监听器有如下3个方法:

   ● 实现对应的监听器接口,这是不可思议的,实现接口必须实现接口内的所有方法,关键是必须实现Hibernate对应的持久化操作,即数据库访问,这意味着程序员完全取代了Hibernate的底层操作。

   ● 继承事件适配器,可以选择性地实现需要关注的方法,但依然试图取代Hibernate完成数据库的访问,这也不太现实。

   ● 继承系统默认的事件监听器,扩展特定方法。

实际上,前两种方法很少使用。因为Hibernate的持久化操作也是通过这些监听器实现的,如果用户取代了这些监听器,则应该自己实现所有的持久化操作,这意味着用户放弃了Hibernate的持久化操作,而改为自己完成Hibernate的核心操作。

通常推荐采用第三种方法实现自己的事件监听器。Hibernate默认的事件监听器都被声明成non-final,从而方便用户继承。

下面是用户自定义监听器的示例:

//自定义LoadListener,继承默认的DefaultLoadEventListener实现类

public class MyLoadListener extends DefaultLoadEventListener

{

    //在LoadEventListener接口仅仅定义了这个方法

    public Object onLoad(LoadEvent event, LoadEventListener.LoadType

    loadType)throws HibernateException

    {

        //先调用父类的onLoad方法,从而完成默认的持久化操作

        Object o = super.onLoad(event, loadType);

        //加入用户的自定义处理

        System.out.println("自定义的load事件");

        System.out.println(event.getEntityClassName() + "==========" +

        event.getEntityId());

        return o;

    }

}

下面还有一个MySaveListener,用于监听SaveEvent事件:

//自定义SavaListener,继承默认的DefaultSaveEventListener实现类

public class MySaveListener extends DefaultSaveEventListener

{

    //该方法完成实际的数据插入动作

    protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event)

    {

        //先执行用户自定义的操作

        System.out.println(event.getObject());

        //调用父类的默认持久化操作

        return super.performSaveOrUpdate(event);

    }

}

注意:扩展用户自定义监听器时,别忘了在方法中调用父类的对应方法。

注册用户自定义监听器也有两种方法:

   ● 编程式,通过使用Configuration对象编程注册。

   ● 声明式,在Hibernate的XML格式配置文件中进行声明,使用Properties格式的配置文件将无法配置自定义监听器。

下面的示例代码,通过编程方式使用自定义监听器:

public class HibernateUtil2

{

    //静态类属性 SessionFactory

    public static final SessionFactory sessionFactory;

    //静态初始化块,完成静态属性的初始化

    static

    {

        try

        {

            Configuration cfg = new Configuration();

            //注册loadEventListener监听器

            cfg.getSessionEventListenerConfig().setLoadEventListener

            ( new MyLoadListener() );

            //注册saveListener监听器

            cfg.getSessionEventListenerConfig().setSaveEventListener

            (new MySaveListener() );

            //由Configuration实例来创建一个SessionFactory实例

            sessionFactory = cfg.configure().buildSessionFactory();

        }

        catch (Throwable ex)

        {

            System.err.println("初始化sessionFactory失败." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要

    对线程同步

    public static final ThreadLocal session = new ThreadLocal();

    //不加拦截器的打开Session方法

    public static Session currentSession() throws HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            s = sessionFactory.openSession();

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

        }

        return s;

    }

    //关闭Session对象

    public static void closeSession() throws HibernateException

    {

        Session s = (Session) session.get();

        if (s != null)

            s.close();

        session.set(null);

    }

}

如果不想修改代码,也可以在配置文件中使用事件监听器,注册事件监听器的Hibernate配置文件代码如下:

<?xml version='1.0' encoding='GBK'?>

<!-- Hibernate配置文件的文件头,包含DTD等信息 -->

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.

        dtd">

<!-- Hibernate配置文件的根元素 -->

<hibernate-configuration>

    <session-factory>

        <!—设置数据库驱动 -->

        <property name="connection.driver_class">com.mysql.jdbc.Driver

        </property>

        <!-- 数据库服务的url -->

        <property name="connection.url">jdbc:mysql://localhost/hibernate

        </property>

        <!-- 数据库服务的用户名 -->

        <property name="connection.username">root</property>

        <!-- 数据库服务的密码 -->

        <property name="connection.password">32147</property>

        <!-- JDBC connection pool (use the built-in) -->

        <property name="connection.pool_size">5</property>

        <!-- 设置数据库方言 -->

        <property name="dialect">org.hibernate.dialect.MySQLDialect

        </property>

        <!-- 显示Hibernate生成的SQL语句 -->

        <property name="show_sql">true</property>

        <!-- 配置应用启动时,是否启动自动建表 -->

        <property name="hbm2ddl.auto">update</property>

        <!-- 列出所有的持久化映射文件 -->

        <mapping resource="User.hbm.xml"/>

        <!-- 注册事件监听器 -->

        <listener type="load" class="lee.MyLoadListener"/>

        <listener type="save" class="lee.MySaveListener"/>

    </session-factory>

</hibernate-configuration>

使用配置文件注册事件监听器虽然方便,但也有不利之处,通过配置文件注册的监听器不能共享实例。如果多个<listener/>元素中使用了相同的类,则每一个引用都将产生一个新的拦截器实例。如果需要在多个事件之间共享监听器的实例,则必须使用编程方式注册事件监听器。

注意:虽然监听器类实现了特定监听器的接口,在注册的时候还要明确指出注册的事件。这是因为一个类可能实现多个监听器的接口,注册时明确指定要监听的事件,可以使得启用或者禁用某个事件监听的配置工作更简单。

posted @ 2009-07-19 09:42 jadmin 阅读(83) | 评论 (0)编辑 收藏

4.7 事 务控 制

每个业务逻辑方法都是由一系列的数据库访问完成,这一系列的数据访问可能会修改多条数据记录,这系列的修改应该是一个整体,绝不能仅修改其中的几条。也就是说,多个数据库原子访问应该绑定成一个整体——这就是事务。事务是一个最小的逻辑执行单元,整个事务不能分开执行,要么同时执行,要么同时放弃执行。

4.7.1 事务的概念

事务是一步或几步基本操作组成的逻辑执行单元,这些基本操作作为一个整体执行单元,它们要么全部执行,要么全部取消,绝不能仅仅执行部分。一般而言,每次用户请求,对应一个业务逻辑方法,一个业务逻辑方法往往具有逻辑上的原子性,应该使用事务。例如,一个转账操作,对应修改两个账户的余额,这两个账户的修改要么同时生效,要么同时取消——同时生效是转账成功,同时取消是转账失败;但不可只修改其中一个账户,那将破坏数据库的完整性。

通常来讲,事务具备如下4个特性:原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持续性(durability)。这4个特性也简称为ACID性。

   ● 原子性:事务是应用中最小执行单位,就如原子是自然界最小颗粒,具有不可再分的特征一样。事务是应用中不可再分的最小逻辑执行体。

   ● 一致性:事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时,数据库就处于一种不正确的状态。比如银行在两个账户之间转账,从A账户向B账户转入1000元。系统先减少A账户的1000元,然后再为B账户增加1000元。如果全部执行成功,数据库处于一致性状态。如果仅执行完A账户金额的修改,而没有增加B账户的金额,则数据库就处于不一致性状态。因此,一致性是通过原子性来保证的。

   ● 隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都具有隔离性。也即并发执行的事务之间不能互相影响。

   ● 持续性:持续性也称为持久性(persistence),指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常保存进物理数据库。

4.7.2 Hibernate的事务

Hibernate直接使用JDBC连接和JTA资源,不添加任何附加锁定行为。Hibernate只添加自动版本管理,而不会锁定内存中的对象,也不会改变数据库事务的隔离级别。基本上,使用 Hibernate就好像直接使用JDBC(或者JTA/CMT)进行数据库访问。

Hibernate中SessionFactory对象的创建代价很高,它是线程安全的对象,被设计成可以为所有的应用程序线程所共享。通常,SessionFactory会在应用程序启动时创建,一旦创建了SessionFactory将不会轻易关闭,只有当应用关闭时,SessionFactory才会关闭。

而Session的对象是轻量级的,它也是线程不安全的。对于单个业务进程单个工作单元而言,Session只被使用一次。创建Session时,并不会立即打开与数据库之间的连接,Session只在需要进行数据库操作时,才会获取JDBC连接。因此,打开和关闭Session,并不会对性能造成很大的影响。甚至即使无法确定一个请求是否需要数据访问,也可以打开Session对象,因为如果不进行数据库访问,Session不会获取JDBC连接。

相反,数据库事务应该尽可能的短。从而,降低数据库锁定造成的资源争用。数据库长事务会导致应用程序无法承载高并发的负荷。

由上面的介绍可知,Hiberante的Session和事务是紧密相关的,因为事务是通过Session来打开的。那么事务的范围是多大?单个Session可以跨越多个数据库事务吗?事务和Session的对应关系又如何呢?下面将介绍Hibernate Session和事务的关系。

4.7.3 事务和Session

数据库操作必须在Hibernate的Session管理下进行,但不推荐因为一次简单的数据库原子调用,就打开和关闭一次Session,数据库事务也是如此。因为,对于一次原子操作打开的事务没有任何意义——事务应该是将多个操作步骤组合成一个逻辑整体。

事务是按顺序发送并组成一个逻辑整体的原子操作单元。

注意:也就是说单个的SQL语句发送之后,自动事务提交模式失效了。这种自动提交模式仅为SQL控制台设计,在实际项目没有太大的实用价值。Hibernate禁止事务立即自动提交模式,或者让应用服务器禁止事务自动提交。

通常,建议每个请求对应一个Session。在这种模式下,来自客户端的请求被发送到服务器端,此处可能对应一个业务逻辑方法。在这个业务逻辑方法内,一个新的Hibernate Session被打开,然后开始事务,在事务内执行这个操作单元中所有的数据库操作。一旦操作完成,需要发送给客户端的响应也准备就绪。此时,提交事务,然后关闭Session。在这种模式下,Session和用户请求是一对一的关系,这是一种理想的Session管理模式。

为了达到这种效果,推荐使用一个ThreadLocal变量,把Session绑定到处理客户端请求的线程上去。这种方式可以让运行在该线程上的所有程序代码轻松地访问Session。也可以在一个ThreadLocal变量中保持事务上下文环境,不过这依赖于所选择的数据库事务划分机制。这种实现模式被称之为ThreadLocal Session和Open Session in View。

下面是一个HibernateUtil类,该类将Hibernate Session存放在一个ThreadLocal变量中,对于同一个线程的请求,将可以轻松访问该Session。

public class HibernateUtil

{

    public static final SessionFactory sessionFactory;

    //静态初始化块,使用该类时使用该代码块

    static

    {

        try

        {

            //采用默认的hibernate.cfg.xml来启动一个Configuration的实例

            Configuration configuration=new Configuration().configure();

            //由Configuration的实例来创建一个SessionFactory实例

            sessionFactory = configuration.buildSessionFactory();

        }

        catch (Throwable ex)

        {

            System.err.println("初始化sessionFactory失败." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要

    对线程同步

    public static final ThreadLocal session = new ThreadLocal();

    //该方法用于获取当前线程的Session对象

    public static Session currentSession() throws HibernateException

    {

        Session s = (Session) session.get();

        //如果该线程还没有Session,则创建一个新的Session

        if (s == null)

        {

            s = sessionFactory.openSession();

            //将获得的Session变量存储在ThreadLocal变量的Session里

            session.set(s);

        }

        return s;

    }

    //该方法用于关闭当前线程里的Session

    public static void closeSession() throws HibernateException

    {

        Session s = (Session) session.get();

        if (s != null)

            s.close();

        session.set(null);

    }

}

在上面的代码中,Hibernate Session被绑定到当前线程。当调用currentSession方法时,如果当前线程中的Session已经创建出来,那么将返回这个已经存在的Session实例。

每次请求对应一个Session的模式不仅可以用于设计操作单元,甚至很多业务处理流程都需要组合一系列的用户操作,即用户对数据库的交叉访问。

但是,对于企业应用,跨用户交互的数据库事务是无法接受的。例如,在第一个页面,用户打开对话框,打开一个特定Session装入的数据,可以随意修改对话框中的数据,修改完成后,将修改结果存入数据库。

从用户的角度来看,这个操作单元被称为应用程序长事务。在一个J2EE应用实现中,可以有很多方法来实现这种应用程序长事务。

一个比较差的做法是,当用户思考时,应用程序保持Session和数据库事务是打开的,并保持数据库锁定,以阻止并发修改,从而保证数据库事务隔离级别和原子操作。这种数据库锁定会导致应用程序无法扩展并发用户的数目。

因此,不要使用每个应用对应一次Hibernate Session的模式,也不要使用每次Http Session对应一次Hibernate Session的模式。

注意:几乎所有情况下,都不要使用每个应用对应一次Hibernate Session的模式,也不要使用每次Http Session对应一次Hibernate Session的模式。

对于这种情况,Hibernate主要有如下两种模式来解决这个问题:

   ● 脱管对象,如果采用每次用户请求对应一次Session的模式。那么,前面载入的实例在用户思考的过程中,始终与Session脱离,处于脱管状态。都处于与Session脱离的状态。Hibernate允许把脱管对象重新关联到Session上,并且对修改进行持久化。在这种模式下,自动版本化被用来隔离并发修改。这种模式也被称为使用脱管对象的每个请求对应一个Hibernate Session。

   ● 长生命周期Session,Session可以在数据库事务提交之后,断开和底层的JDBC连接。当新的客户端请求到来时,它又重新连接上底层的JDBC连接。这种模式被称为每个应用程序事务对应一个Session,因为应用程序事务相当长(跨越多个用户请求),所以也被称为每次应用事务对应一个Hibernate Session。

posted @ 2009-07-19 09:11 jadmin 阅读(82) | 评论 (0)编辑 收藏

数据过滤并不是一种常规的数据查询方法,而是一种整体的筛选方法。数据过滤也可对数据进行筛选,因此,将其放在Hibernate的数据查询框架中介绍。

如果一旦启用了数据过滤器,则不管数据查询,还是数据加载,该过滤器将自动作用于所有数据,只有满足过滤条件的记录才会被选出来。

过滤器与定义在类和集合映射文件上的“where”属性非常相似。它们的区别是过滤器可以带参数,应用程序可以在运行时决定是否启用指定的过滤器,以及使用什么样的参数值。而映射文件上的“where”属性将一直生效,且无法动态传入参数。

过滤器的用法很像数据库视图,区别是视图在数据库中已经定义完成,而过滤器则还需在应用程序中确定参数值。

过滤器的使用分成三步:

(1)定义过滤器。使用filter-def元素定义过滤器;

(2)使用过滤器。使用filter元素使用过滤器;

(3)在代码中启用过滤器。

前两个步骤都是在Hibernate的映射文件中完成的,其中filter-def是hibernate-mapping元素的子元素,而filter元素是class集合元素的子元素。

filter-def元素用于定义一个过滤器,filter则将指定的过滤器应用到指定的持久化类。

一个持久化类或集合可以使用多个过滤器,而一个过滤器也可以作用于多个持久化类或集合。

看下面的映射文件示例:

<?xml version="1.0"?>

<!-- Hibernate配置文件的文件头,包含DTD等信息 -->

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<!-- Hibernate 配置文件的根元素 -->

<hibernate-mapping >

    <!-- 每个class元素定义一个持久化类 -->

    <class name="Category" table="category">

        <!-- 定义标识属性 -->

        <id name="id" column="category_id" >

            <!-- 指定主键生成器策略 -->

            <generator class="native"/>

        </id>

        <!-- 映射name属性 -->

        <property name="name" type="string"/>

        <!-- 映射effectiveStartDate属性 -->

        <property name="effectiveStartDate" column="eff_start_date"

        type="java.util.Date"/>

        <!-- 映射effectiveEndDate属性 -->

        <property name="effectiveEndDate" column="eff_end_date"

        type="java.util.Date"/>

        <!-- 映射N-N关联属性 -->

        <set cascade="none" inverse="true" name="products"

        table="product_category">

            <!-- 定义关联属性的key,对应连接表中的外键列 -->

            <key column="category_id"/>

            <!-- 定义关联属性 -->

            <many-to-many column="product_id" class="Product"/>

        </set>

        <!-- 使用过滤器,并设置过滤器条件 -->

        <filter name="effectiveDate" condition=":asOfDate BETWEEN

        eff_start_date and eff_end_date"/>

    </class>

    <!-- 定义第二个持久化类 -->

    <class name="Product" table="product">

        <!-- 定义标识属性 -->

        <id name="id" column="product_id" >

            <!-- 指定主键生成器策略 -->

            <generator class="native"/>

        </id>

        <!-- 映射name属性 -->

        <property name="name" type="string"/>

        <!-- 映射stockNumber属性 -->

        <property name="stockNumber" column="stock_number" type="int"/>

        <!-- 映射effectiveStartDate属性 -->

        <property name="effectiveStartDate" column="eff_start_date"

        type="java.util.Date"/>

        <!-- 映射effectiveEndDate属性 -->

        <property name="effectiveEndDate" column="eff_end_date"

        type="java.util.Date"/>

        <!-- 映射N-N关联属性 -->

        <set cascade="all" name="categories" fetch="join"

        table="product_category" >

            <!-- 定义关联属性的key,对应连接表中的外键列 -->

            <key column="product_id"/>

            <!-- 定义关联属性 -->

            <many-to-many column="category_id"

                        class="Category" fetch="join">

                <!-- 对关联属性使用第一个过滤器 -->

                <filter name="effectiveDate"

                    condition=":asOfDate BETWEEN eff_start_date and

                    eff_end_date"/>

                <!-- 对关联属性使用第二个过滤器 -->

                <filter name="category" condition="category_id = :catId"/>

            </many-to-many>

        </set>

        <filter name="effectiveDate" condition=":asOfDate BETWEEN

        eff_start_date AND eff_end_date"/>

    </class>

    <!-- 定义第一个过滤器,该过滤器包含一个date类型的参数 -->

    <filter-def name="effectiveDate">

        <filter-param name="asOfDate" type="date"/>

    </filter-def>

    <!-- 定义第二个过滤器,该过滤器包含一个long类型的参数 -->

    <filter-def name="category">

        <filter-param name="catId" type="long"/>

    </filter-def>

</hibernate-mapping>

在上面的配置文件中,定义了两个过滤器,过滤器的定义通过filter-def元素完成。定义过滤器时,只需要指定过滤器的名字,以及过滤器的参数即可。如Java里的一个方法声明,只有方法名和参数列表,具体的方法实现是没有的。

过滤器的过滤条件是使用过滤器时才确定的,使用过滤器通过filter元素确定,filter的condition属性用于确定过滤条件,满足该条件的记录才会被抓取到。

系统默认不启用过滤器,必须显式通过enableFilter(String filterName)才可以启用过滤器,该方法返回一个Filter实例,Filter包含setParameter方法用于为过滤器参数赋值。

一旦启用了过滤器,过滤器在整个Session内有效,所有的数据加载将自动应用该过滤条件,直到调用disableFilter方法。

看下面的使用过滤器的示例代码:

private void test() throws Exception

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //启用第一个过滤器

    session.enableFilter("effectiveDate")

            //为过滤器设置参数

            .setParameter("asOfDate", new Date());

    //启动第二个过滤器

    session.enableFilter("category")

            //为过滤器设置参数

            .setParameter("catId", new Long(2));

    //执行查询,该查询没有任何的查询条件

    Iterator results = session.createQuery("from Product as p")

                          .iterate();

    //遍历结果集

    while (results.hasNext())

    {

        Product p = (Product)results.next();

        System.out.println(p.getName());

        //此处获取Product关联的种类,过滤器也将自动应用过滤

        Iterator it = p.getCategories().iterator();

        System.out.println(p.getCategories().size());

        while (it.hasNext())

        {

            Category c = (Category)it.next();

            System.out.println(c.getName());

        }

    }

    tx.commit();

    HibernateUtil.closeSession();

}

通过使用过滤器定义常用的数据筛选规则,如果是临时的数据筛选,还是使用常规查询比较好。对于从前使用行列表达式视图的地方,此处可以考虑使用过滤器。

posted @ 2009-07-19 09:08 jadmin 阅读(116) | 评论 (0)编辑 收藏

4.5 SQL查询

Hibernate还支持使用SQL查询,使用SQL查询可以利用某些数据库的特性,或者用于将原有的JDBC应用迁移到Hibernate应用上。使用命名的SQL查询还可以将SQL语句放在配置文件中配置,从而提高程序的解耦,命名SQL查询还可以用于调用存储过程。

如果是一个新的应用,通常不要使用SQL查询。

SQL查询是通过SQLQuery接口来表示的,SQLQuery接口是Query接口的子接口,因此完全可以调用Query接口的方法:

   ● setFirstResult(),设置返回结果集的起始点。

   ● setMaxResults(),设置查询获取的最大记录数。

   ● list(),返回查询到的结果集。

但SQLQuery比Query多了两个重载的方法:

   ● addEntity,将查询到的记录与特定的实体关联。

   ● addScalar,将查询的记录关联成标量值。

执行SQL查询的步骤如下:

(1)获取Hibernate Session对象;

(2)编写SQL语句;

(3)以SQL语句作为参数,调用Session的createSQLQuery方法创建查询对象;

(4)如果SQL语句包含参数,调用Query的setXxx方法为参数赋值;

(5)调用SQLQuery对象的addEntity或addScalar方法将选出的结果与实体或标量值关联;

(6)调用Query的list方法返回查询的结果集。

看下面的SQL查询示例:

private void test()

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //编写SQL语句

    String sqlString = "select {s.*} from student s where s.name like '马军'";

    //以SQL语句创建SQLQuery对象

    List l = session.createSQLQuery(sqlString)

                    //将查询到的记录与特定实体关联起来

                    .addEntity("s",Student.class)

                    //返回全部的记录集

                    .list();

    //遍历结果集

    Iterator it = l.iterator();

    while (it.hasNext())

    {

        //因为将查询结果与Student类关联,因此返回的是Student集合

        Student s = (Student)it.next();

        Set enrolments = s.getEnrolments();

        Iterator iter = enrolments.iterator();

        while(iter.hasNext())

        {

            Enrolment e = (Enrolment)iter.next();

            System.out.println(e.getCourse().getName());

        }

    }

    //提交事务

    tx.commit();

    //关闭Session

    HibernateUtil.closeSession();

}

上面的示例显示了将查询记录关联成一个实体的示例。事实上,SQL查询也支持将查询结果转换成标量值,转换成标量值可以使用addScalar方法,如:

Double max = (Double) session.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")

        .addScalar("maxWeight", Hibernate.DOUBLE);

        .uniqueResult();

使用SQL查询,如果需要将查询到的结果转换成特定实体,就要求为选出的字段命名别名。这别名不是随意命名的,而是以“/”实例名.属性名“/”的格式命名,例如:

//依次将多个选出的字段命名别名,命名别名时都以ss作为前缀,ss是关联实体的别名

String sqlStr = "select stu.studentId as {ss.studentNumber},"

        + "stu.name as {ss.name} from "

        + "student as stu where stu.name like '杨海华'";

List l = session.createSQLQuery(sqlStr)

            //将查询出的ss实例,关联到Student类

            .addEntity("ss",Student.class)

            .list();

在第一个示例中,以{s.*}代表该表的全部字段,且关联实例的别名也被指定为s。

注意:如果不使用{s.*}的形式,就可让实体别名和表别名互不相同。关联实体的类型时,被关联的类必须有对应的setter方法。

4.5.1 命名SQL查询

可以将SQL语句不放在程序中,而放在配置文件中,这种方式以松耦合的方式配置SQL语句,可以提高程序解耦。

在Hibernate的映射文件中定义查询名,然后确定查询所用的SQL语句,然后就可以直接调用该命名SQL查询。在这种情况下,不需要调用addEntity()方法,因为在配置命名SQL查询时,已经完成了查询结果与实体的关联。

下面是命名SQL查询的配置片段:

<!-- 每个sql-query元素定义一个命名SQL查询 -->

<sql-query name="mySqlQuery">

    <!-- 关联返回的结果与实体类 -->

    <return alias="s" class="Student"/>

        <!-- 定义命名SQL查询的SQL语句 -->

         SELECT {s.*}

        from student s WHERE s.name like'杨海华'

</sql-query>

sql-query元素是hibernate-mapping元素的子元素。因此,sql-query定义的名可以直接通过Session访问,上面定义的mySqlQuery查询可以直接访问,下面是使用该命名SQL查询的示例代码:

private void testNamedSQl()

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //调用命名查询,直接返回结果

    List l = session.getNamedQuery("mySqlQuery")

                         .list();

    //遍历结果集

    Iterator it = l.iterator();

    while (it.hasNext())

    {

        //在定义SQL查询时,已经将结果集与Student类关联起来

        //因此,集合里的每个元素都是Student实例

        Student s = (Student)it.next();

        Set enrolments = s.getEnrolments();

        Iterator iter = enrolments.iterator();

        while(iter.hasNext())

        {

            Enrolment e = (Enrolment)iter.next();

            System.out.println("=====================================");

            System.out.println(e.getCourse().getName());

            System.out.println("=====================================");

        }

    }

    tx.commit();

    HibernateUtil.closeSession();

}

4.5.2 调用存储过程

Hibernate 3增加了存储过程的支持,该存储过程只能返回一个结果集。

下面是Oracle 9i的存储过程示例:

CREATE OR REPLACE FUNCTION selectAllEmployments

    RETURN SYS_REFCURSOR

AS

    st_cursor SYS_REFCURSOR;

BEGIN

    OPEN st_cursor FOR

SELECT EMPLOYEE, EMPLOYER,

STARTDATE, ENDDATE,

REGIONCODE, EID, VALUE, CURRENCY

FROM EMPLOYMENT;

      RETURN st_cursor;

END;

如果需要使用该存储过程,可以先将其定义成命名SQL查询,例如:

<!-- 定义命名SQL查询,name属性指定命名SQL查询名 -->

<sql-query name="selectAllEmployees_SP" callable="true">

    <!-- 定义返回列与关联实体类属性之间的映射 -->

    <return alias="emp" class="Employment">

        <!-- 依次定义每列与实体类属性的对应 -->

        <return-property name="employee" column="EMPLOYEE"/>

        <return-property name="employer" column="EMPLOYER"/>

        <return-property name="startDate" column="STARTDATE"/>

        <return-property name="endDate" column="ENDDATE"/>

        <return-property name="regionCode" column="REGIONCODE"/>

        <return-property name="id" column="EID"/>

        <!-- 将两列值映射到一个关联类的组件属性 -->

        <return-property name="salary">

            <!-- 映射列与组件属性之间的关联 -->

            <return-column name="VALUE"/>

            <return-column name="CURRENCY"/>

        </return-property>

    </return>

    { ? = call selectAllEmployments() }

</sql-query>

调用存储过程还有如下需要注意的地方:

   ● 因为存储过程本身完成了查询的全部操作,所以调用存储过程进行的查询无法使用setFirstResult()/setMaxResults()进行分页。

   ● 存储过程只能返回一个结果集,如果存储过程返回多个结果集,Hibernate将仅处理第一个结果集,其他将被丢弃。

   ● 如果在存储过程里设定SET NOCOUNT ON,将有更好的性能表现。当然也可以没有该设定。

posted @ 2009-07-19 09:02 jadmin 阅读(1155) | 评论 (0)编辑 收藏

4.4 条 件 查 询

条件查询是更具面向对象特色的数据查询方式。条件查询可通过如下3个类完成:

   ● Criteria,代表一次查询。

   ● Criterion,代表一个查询条件。

   ● Restrictions,产生查询条件的工具类。

执行条件查询的步骤如下:

(1)获得Hibernate的Session对象。

(2)以Session对象创建Criteria对象。

(3)增加Criterion查询条件。

(4)执行Criteria的list等方法返回结果集。

看下面的条件查询示例:

private void test()

{

    //获取Hibernate Session对象

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //创建Criteria和添加查询条件同步完成

    //最后调用list方法,返回查询到的结果集

    List l = session.createCriteria(Student.class)

        //此处增加限制条件必须是Student已经存在的属性

        .add( Restrictions.gt("studentNumber" , new Long(20050231) ) )

        //如果要增加对Student的关联类的属性的限制则必须重新createCriteria()

        /如果此关联属性是集合,则只要集合里任意一个对象的属性满足下面条件

        .createCriteria("enrolments")即可

        .add( Restrictions.gt("semester" , new Short("2") ) )

        .list();

        Iterator it = l.iterator();

    //遍历查询到的记录

    while (it.hasNext())

    {

        Student s = (Student)it.next();

        System.out.println(s.getName());

        Set enrolments = s.getEnrolments();

        Iterator iter = enrolments.iterator();

        while(iter.hasNext())

        {

            Enrolment e = (Enrolment)iter.next();

            System.out.println(e.getCourse().getName());

       }

    }

    tx.commit();

    ibernateUtil.closeSession();

}

在条件查询中,Criteria接口代表一次查询,该查询本身不具备任何的数据筛选功能,Session调用createCriteria(Class clazz)方法对某个持久化类创建条件查询实例。

Criteria包含如下两个方法:

   ● Criteria setFirstResult(int firstResult),设置查询返回的第一行记录。

   ● Criteria setMaxResults(int maxResults),设置查询返回的记录数。

这两个方法与Query的这两个方法用法相似,都用于完成查询分页。

而Criteria还包含如下常用方法:

   ● Criteria add(Criterion criterion),增加查询条件。

   ● Criteria addOrder(Order order),增加排序规则。

   ● List list(),返回结果集。

Criterion接口代表一个查询条件,该查询条件由Restrictions负责产生,Restrictions是专门用于产生查询条件的工具类,它的方法大部分都是静态方法,常用的方法如下:

   ● static Criterion allEq(Map propertyNameValues),判断指定属性(由Map参数的key指定)和指定值(由Map参数的value指定)是否完全相等。

   ● static Criterion between(String propertyName,Object lo, Object hi),判断属性值在某个值范围之内。

   ● static Criterion ilike(String propertyName, Object value),判断属性值匹配某个字符串。

   ● static Criterion ilike(String propertyName, String value,MatchMode matchMode),判断属性值匹配某个字符串,并确定匹配模式。

   ● static Criterion in(String propertyName,Collection values),判断属性值在某个集合内。

   ● static Criterion in(String propertyName,Object[] values),判断属性值是数组元素的其中之一。

   ● static Criterion isEmpty(String propertyName),判断属性值是否为空。

   ● static Criterion isNotEmpty(String propertyName),判断属性值是否不为空。

   ● static Criterion isNotNull(String propertyName),判断属性值是否为空。

   ● static Criterion isNull(String propertyName),判断属性值是否不为空。

   ● static Criterion not(Criterion expression),对Criterion求否。

   ● static Criterion sizeEq(String propertyName, int size),判断某个属性的元素个数是否与size相等。

   ● static Criterion sqlRestriction(String sql),直接使用SQL语句作为筛选条件。

   ● static Criterion sqlRestriction(String sql, Object[] values, Type[] types),直接使用带参数占位符的SQL语句作为条件,并指定多个参数值。

   ● static Criterion sqlRestriction(String sql, Object value, Type type),直接使用带参数占位符的SQL语句作为条件,并指定参数值。

Order实例代表一个排序标准,Order有如下构造器:

Order(String propertyName, boolean ascending),根据propertyName排序,是否采用升序,如果后一个参数为true,采用升序排序,否则采用降序排序。

如果需要使用关联类的属性来增加查询条件,则应该对属性再次使用createCriteria方法。看如下示例:

session.createCriteria(Person.class)

    .add(Restrictions.like("name" , "dd%"))

    .createCriteria("addresses")

    .add(Restrictions.like("addressdetail" , "上海%"))

    .list();

上面的代码表示建立Person类的条件查询,第一个查询条件是直接过滤Person的属性,即选出name属性以dd开始的Person实例,第二个查询条件则过滤Person关联实例的属性,其中addresses是Person类的关联持久化类Address,而addressdetail则是Address类的属性。值得注意的是,查询并不是查询Address持久化类,而是查询Person持久化类。

注意:使用关联类的条件查询,依然是查询原有持久化类的实例,而不是查询被关联类的实例。

posted @ 2009-07-19 08:59 jadmin 阅读(142) | 评论 (0)编辑 收藏

4.3 使用HQL查询

Hibernate提供了异常强大的查询体系,使用Hibernate有多种查询方式。可以选择使用Hibernate的HQL查询,或者使用条件查询,甚至可以使用原生的SQL查询语句,此外还提供了一种数据过滤功能,这些都可用于筛选目标数据。

下面分别介绍Hibernate的4种数据筛选方法:

4.3.1 HQL查询

HQL是Hibernate Query Language的缩写,HQL的语法很像SQL的语法,但HQL是一种面向对象的查询语言。因此,SQL的操作对象是数据表和列等数据对象,而HQL的操作对象是类、实例、属性等。

HQL是完全面向对象的查询语言,因此可以支持继承和多态等特征。

HQL查询依赖于Query类,每个Query实例对应一个查询对象。使用HQL查询可按如下步骤进行:

(1)获取Hibernate Session对象;

(2)编写HQL语句;

(3)以HQL语句作为参数,调用Session的createQuery方法创建查询对象;

(4)如果HQL语句包含参数,调用Query的setXxx方法为参数赋值;

(5)调用Query对象的list等方法遍历查询结果。

看下面的查询示例:

public class HqlQuery

{

    public static void main(String[] args)throws Exception

    {

        HqlQuery mgr = new HqlQuery();

        //调用查询方法

        mgr.findPersons();

        //调用第二个查询方法

        mgr.findPersonsByHappenDate();

        HibernateUtil.sessionFactory.close();

    }

    //第一个查询方法

    private void findPersons()

    {

        //获得Hibernate Session

        Session sess = HibernateUtil.currentSession();

        //开始事务

        Transaction tx = sess.beginTransaction();

        //以HQL语句创建Query对象.

        //执行setString方法为HQL语句的参数赋值

        //Query调用list方法访问查询的全部实例

        List pl = sess.createQuery("from Person p where p.myEvents.title

        = :eventTitle")

                        .setString("eventTitle","很普通事情")

                        .list();

        //遍历查询的全部结果

        for (Iterator pit = pl.iterator() ; pit.hasNext(); )

        {

            Person p = ( Person )pit.next();

            System.out.println(p.getName());

        }

        //提交事务

        tx.commit();

        HibernateUtil.closeSession();

    }

    //第二个查询方法

    private void findPersonsByHappenDate()throws Exception

    {

        //获得Hibernate Session对象

        Session sess = HibernateUtil.currentSession();

        Transaction tx = sess.beginTransaction();

        //解析出Date对象

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Date start = sdf.parse("2005-01-01");

        System.out.println("系统开始通过日期查找人" + start);

        //通过Session的createQuery方法创建Query对象

        //设置参数

        //返回结果集

        List pl = sess.createQuery(

            "from Person p where p.myEvents.happenDate between :firstDate

            and :endDate")

                        .setDate("firstDate",start)

                        .setDate("endDate",new Date())

                        .list();

        //遍历结果集

        for (Iterator pit = pl.iterator() ; pit.hasNext(); )

        {

            Person p = ( Person )pit.next();

            System.out.println(p.getName());

        }

        tx.commit();

        HibernateUtil.closeSession();

    }

}

通过上面的示例程序,可看出查询步骤基本相似。Query对象可以连续多次设置参数,这得益于Hibernate Query的设计。

通常,setXxx方法的返回值都是void,但Hibernate Query的setXxx方法返回值是Query本身。因此,程序通过Session创建Query后,直接多次调用setXxx方法为HQL语句的参数赋值,再直接调用list方法返回查询到的全部结果即可。

Query还包含两个方法:

   ● setFirstResult(int firstResult),设置返回的结果集从第几条记录开始。

   ● setMaxResults(int maxResults),设置本次查询返回的结果数。

这两个方法用于实现Hibernate分页。

下面简单介绍HQL语句的语法。

HQL语句本身是不区分大小写的。也就是说,HQL语句的关键字和函数都是不区分大小写的。但HQL语句中所使用的包名、类名、实例名和属性名都区分大小写。

4.3.2 HQL查询的from子句

from子句是最简单的HQL语句,也是最基本的HQL语句。from关键字后紧跟持久化类的类名。例如:

from Person

表明从Person持久化类中选出全部的实例。

大部分时候,推荐为该Person的每个实例起别名。例如:

from Person as p

在上面的HQL语句中,Person持久化类中的实例的别名为p,既然 p是实例名,因此也应该遵守Java的命名规则:第一个单词的首字母小写,后面每个单词的首字母大写。

命名别名时,as关键字是可选的,但为了增加可读性,建议保留。

from后还可同时出现多个持久化类,此时将产生一个笛卡儿积或跨表的连接。

4.3.3 HQL查询的select子句

select子句用于确定选择出的属性,当然select选择的属性必须是from后持久化类包含的属性。例如:

select p.name from Person as p

select可以选择任意属性,不仅可以选择持久化类的直接属性,还可以选择组件属性包含的属性,例如:

select p.name.firstName from Person as p

select也支持将选择出的属性存入一个List对象中,例如:

select new list(p.name , p.address) from Person as p

甚至可以将选择出的属性直接封装成对象,例如:

select new ClassTest(p.name , p.address) from Person as p

前提是ClassTest支持p.name和p.address的构造器,假如p.name的数据类型是           String,p.address的数据类型是String,则ClassTest必须有如下的构造器:

ClassTest(String s1, String s2)

select还支持给选中的表达式命名别名,例如:

select p.name as personName from Person as p

这种用法与new map结合使用更普遍。如:

select new map(p.name as personName) from Person as p

在这种情形下,选择出的是Map结构,以personName为key,实际选出的值作为value。

4.3.4 HQL查询的聚集函数

HQL也支持在选出的属性上,使用聚集函数。HQL支持的聚集函数与SQL完全相同,有如下5个:

   ● avg,计算属性平均值。

   ● count,统计选择对象的数量。

   ● max,统计属性值的最大值

   ● min,统计属性值的最小值。

   ● sum,计算属性值的总和。

例如:

select count(*) from Person

select max(p.age) from Person as p

select子句还支持字符串连接符、算术运算符以及SQL函数。如:

select p.name || "" || p.address from Person as p

select子句也支持使用distinct和all关键字,此时的效果与SQL中的效果完全相同。

4.3.5 多态查询

HQL语句被设计成能理解多态查询,from后跟的持久化类名,不仅会查询出该持久化类的全部实例,还会查询出该类的子类的全部实例。

如下面的查询语句:

from Person as p

该查询语句不仅会查询出Person的全部实例,还会查询出Person的子类,如Teacher的全部实例,前提是Person和Teacher完成了正确的继承映射。

HQL支持在from子句中指定任何Java类或接口,查询会返回继承了该类的持久化子类的实例或返回实现该接口的持久化类的实例。下面的查询语句返回所有被持久化的对象:

from java.lang.Object o

如果Named接口有多个持久化类,下面的语句将返回这些持久化类的全部实例:

from Named as n

注意:后面的两个查询将需要多个SQL SELECT语句,因此无法使用order by子句对结果集进行排序,从而,不允许对这些查询结果使用Query.scroll()方法。

4.3.6 HQL查询的where子句

where子句用于筛选选中的结果,缩小选择的范围。如果没有为持久化实例命名别名,可以直接使用属性名引用属性。

如下面的HQL语句:

from Person where name like 'tom%'

上面HQL语句与下面的语句效果相同:

from Person as p where p.name like "tom%"

在后面的HQL语句中,如果为持久化实例命名了别名,则应该使用完整的属性名。两个HQL语句都可返回name属性以tom开头的实例。

复合属性表达式加强了where子句的功能,例如如下HQL语句:

from Cat cat where cat.mate.name like "kit%"

该查询将被翻译成为一个含有内连接的SQL查询,翻译后的SQL语句如下:

select * from cat_table as table1 cat_table as table2 where table1.mate =

table2.id and table1.name like "kit%"

再看下面的HQL查询语句:

from Foo foo where foo.bar.baz.customer.address.city like"guangzhou%"

翻译成SQL查询语句,将变成一个四表连接的查询。

=运算符不仅可以被用来比较属性的值,也可以用来比较实例:

from Cat cat, Cat rival where cat.mate = rival.mate

select cat, mate

from Cat cat, Cat mate

where cat.mate = mate

特殊属性(小写)id可以用来表示一个对象的标识符。(也可以使用该对象的属性名。)

from Cat as cat where cat.id = 123

from Cat as cat where cat.mate.id = 69

第二个查询是一个内连接查询,但在HQL查询语句下,无须体会多表连接,而完全使用面向对象方式的查询。

id也可代表引用标识符。例如,Person类有一个引用标识符,它由country属性 与medicareNumber两个属性组成。

下面的HQL语句有效:

from Person as person

where person.id.country = 'AU'

    and person.id.medicareNumber = 123456

from Account as account

where account.owner.id.country = 'AU'

    and account.owner.id.medicareNumber = 123456

第二个查询跨越两个表Person和Account。是一个多表连接查询,但此处感受不到多表连接查询的效果。

在进行多态持久化的情况下,class关键字用来存取一个实例的鉴别值(discriminator value)。嵌入where子句中的Java类名,将被作为该类的鉴别值。例如:

from Cat cat where cat.class = DomesticCat

where子句中的属性表达式必须以基本类型或java.lang.String结尾,不要使用组件类型属性结尾,例如Account有Person属性,而Person有Name属性,Name有firstName属性。

看下面的情形:

from Account as a where a.person.name.firstName like "dd%" //正确

from Account as a where a.person.name like "dd%" //错误

4.3.7 表达式

HQL的功能非常丰富,where子句后支持的运算符异常丰富,不仅包括SQL的运算符,还包括EJB-QL的运算符等。

where子句中允许使用大部分SQL支持的表达式:

   ● 数学运算符+、–、*、/ 等。

   ● 二进制比较运算符=、>=、<=、<>、!=、like等。

   ● 逻辑运算符and、or、not等。

   ● in、not in、between、is null、is not null、is empty、is not empty、member of和not member of等。

   ● 简单的case、case ... when ... then ... else ... end和case、case when ... then ... else ...       end等。

   ● 字符串连接符value1 || value2或使用字符串连接函数concat(value1 , value2)。

   ● 时间操作函数current_date()、current_time()、current_timestamp()、second()、minute()、hour()、day()、month()、year()等。

   ● HQL还支持EJB-QL 3.0所支持的函数或操作substring()、trim()、lower()、upper()、length()、locate()、abs()、sqrt()、bit_length()、coalesce()和nullif()等。

   ● 还支持数据库的类型转换函数,如cast(... as ...),第二个参数是Hibernate的类型名,或者extract(... from ...),前提是底层数据库支持ANSI cast() 和extract()。

   ● 如果底层数据库支持如下单行函数sign()、trunc()、rtrim()、sin()。则HQL语句也完全可以支持。

   ● HQL语句支持使用?作为参数占位符,这与JDBC的参数占位符一致,也可使用命名参数占位符号,方法是在参数名前加冒号 :,例如 :start_date和:x1等。

   ● 当然,也可在where子句中使用SQL常量,例如'foo'、69、'1970-01-01 10:00:         01.0'等。

   ● 还可以在HQL语句中使用Java public static final 类型的常量,例如eg.Color.TABBY。

除此之外,where子句还支持如下的特殊关键字用法。

   ● in与between...and可按如下方法使用:

from DomesticCat cat where cat.name between 'A' and 'B'

from DomesticCat cat where cat.name in ( 'Foo','Bar','Baz')

   ● 当然,也支持not in和not between...and的使用,例如:

from DomesticCat cat where cat.name not between 'A' and 'B'

from DomesticCat cat where cat.name not in ( 'Foo','Bar','Baz' )

   ● 子句is null与is not null可以被用来测试空值,例如:

from DomesticCat cat where cat.name is null;

from Person as p where p.address is not null;

如果在Hibernate配置文件中进行如下声明:

<property name="hibernate.query.substitutions">true 1, false 0</property>

上面的声明表明,HQL转换SQL语句时,将使用字符1和0来取代关键字true和false。然后将可以在表达式中使用布尔表达式,例如:

from Cat cat where cat.alive = true

   ● size关键字用于返回一个集合的大小,例如:

from Cat cat where cat.kittens.size > 0

from Cat cat where size(cat.kittens) > 0

   ● 对于有序集合,还可使用minindex与maxindex函数代表最小与最大的索引序数。同理,可以使用minelement与maxelement函数代表集合中最小与最大的元素。         例如:

from Calendar cal where maxelement(cal.holidays) > current date

from Order order where maxindex(order.items) > 100

from Order order where minelement(order.items) > 10000

   ● 可以使用SQL函数any、some、all、exists、in操作集合里的元素,例如:

//操作集合元素

select mother from Cat as mother, Cat as kit

where kit in elements(foo.kittens)

//p的name属性等于集合中某个元素的name属性

select p from NameList list, Person p

where p.name = some elements(list.names)

//操作集合元素

from Cat cat where exists elements(cat.kittens)

from Player p where 3 > all elements(p.scores)

from Show show where 'fizard' in indices(show.acts)

注意这些结构变量size、elements、indices、minindex、maxindex、minelement、maxelement 等,只能在where子句中使用。

   ● where子句中,有序集合的元素(arrays, lists, maps)可以通过[ ]运算符访问。例如:

//items是有序集合属性,items[0]代表第一个元素

from Order order where order.items[0].id = 1234

//holidays是map集合属性,holidays[national day]代表其中一个元素

select person from Person person, Calendar calendar

where calendar.holidays['national day'] = person.birthDay

and person.nationality.calendar = calendar

//下面同时使用list 集合和map集合属性

select item from Item item, Order order

where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11

select item from Item item, Order order

where order.items[ maxindex(order.items) ] = item and order.id = 11

在[]中的表达式甚至可以是一个算术表达式,例如:

select item from Item item, Order order

where order.items[ size(order.items) - 1 ] = item

借助于HQL,可以大大简化选择语句的书写,提高查询语句的可读性,看下面的HQL语句:

select cust

from Product prod,

    Store store

    inner join store.customers cust

where prod.name = 'widget'

    and store.location.name in ( 'Melbourne', 'Sydney' )

    and prod = all elements(cust.currentOrder.lineItems)

如果翻译成SQL语句,将变成如下形式:

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order

FROM customers cust,

    stores store,

    locations loc,

    store_customers sc,

    product prod

WHERE prod.name = 'widget'

    AND store.loc_id = loc.id

    AND loc.name IN ( 'Melbourne', 'Sydney' )

    AND sc.store_id = store.id

    AND sc.cust_id = cust.id

    AND prod.id = ALL(

        SELECT item.prod_id

        FROM line_items item, orders o

        WHERE item.order_id = o.id

            AND cust.current_order = o.id

    )

4.3.8 order by子句

查询返回的列表(list)可以根据类或组件属性的任何属性进行排序,例如:

from Person as p

order by p.name, p.age

还可使用asc或desc关键字指定升序或降序的排序规则,例如:

from Person as p

order by p.name asc , p.age desc

如果没有指定排序规则,默认采用升序规则。即是否使用asc关键字是没有区别的,加asc是升序排序,不加asc也是升序排序。

4.3.9 group by子句

返回聚集值的查询可以对持久化类或组件属性的属性进行分组,分组所使用的group by子句。看下面的HQL查询语句:

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

类似于SQL的规则,出现在select后的属性,要么出现在聚集函数中,要么出现在group by的属性列表中。看下面示例:

//select后出现的id出现在group by之后,而name属性则出现在聚集函数中

select foo.id, avg(name), max(name)

from Foo foo join foo.names name

group by foo.id

having子句用于对分组进行过滤,如下:

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

注意:having子句用于对分组进行过滤,因此having子句只能在有group by子句时才可以使用,没有group by子句,不能使用having子句。

Hibernate的HQL语句会直接翻译成数据库SQL语句。因此,如果底层数据库支持的having子句和group by子句中出现一般函数或聚集函数,HQL语句的having子句和order by 子句中也可以出现一般函数和聚集函数。

例如:

select cat

from Cat cat

join cat.kittens kitten

group by cat

having avg(kitten.weight) > 100

order by count(kitten) asc, sum(kitten.weight) desc

注意:group by子句与 order by子句中都不能包含算术表达式。

4.3.10 子查询

如果底层数据库支持子查询,则可以在HQL语句中使用子查询。与SQL中子查询相似的是,HQL中的子查询也需要使用()括起来。如:

from Cat as fatcat

where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )

如果select中包含多个属性,则应该使用元组构造符:

from Cat as cat

where not ( cat.name, cat.color ) in (

    select cat.name, cat.color from DomesticCat cat

)

4.3.11 fetch关键字

对于集合属性,Hibernate默认采用延迟加载策略。例如,对于持久化类Person,有集合属性scores。加载Person实例时,默认不加载scores属性。如果Session被关闭,Person实例将无法访问关联的scores属性。

为了解决该问题,可以在Hibernate映射文件中取消延迟加载或使用fetch join,例如:

from Person as p join p.scores

上面的fetch语句将会初始化person的scores集合属性。

如果使用了属性级别的延迟获取,可以使用fetch all properties来强制Hibernate立即抓取那些原本需要延迟加载的属性,例如:

from Document fetch all properties order by name

from Document doc fetch all properties where lower(doc.name) like '%cats%'

4.3.12 命名查询

HQL查询还支持将查询所用的HQL语句放入配置文件中,而不是代码中。通过这种方式,可以大大提供程序的解耦。

使用query元素定义命名查询,下面是定义命名查询的配置文件片段:

<!-- 定义命名查询 -->

<query name="myNamedQuery">

    <!-- 此处确定命名查询的HQL语句 -->

    from Person as p where p.age > ?

</query>

该命名的HQL查询可以直接通过Session访问,调用命名查询的示例代码如下:

private void findByNamedQuery()throws Exception

{

    //获得Hibernate Session对象

    Session sess = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = sess.beginTransaction();

    System.out.println("执行命名查询");

    //调用命名查询

    List pl = sess.getNamedQuery("myNamedQuery")

                        //为参数赋值

                       .setInteger(0 , 20)

                        //返回全部结果

                       .list();

    //遍历结果集

    for (Iterator pit = pl.iterator() ; pit.hasNext(); )

    {

        Person p = ( Person )pit.next();

        System.out.println(p.getName());

    }

    //提交事务

    tx.commit();

    HibernateUtil.closeSession();

}

posted @ 2009-07-19 08:48 jadmin 阅读(1599) | 评论 (0)编辑 收藏

Hibernate的批量处理

Hibernate完全以面向对象的方式来操作数据库,当程序里以面向对象的方式操作持久化对象时,将被自动转换为对数据库的操作。例如调用Session的delete()方法来删除持久化对象,Hibernate将负责删除对应的数据记录;当执行持久化对象的set方法时,Hibernate将自动转换为对应的update方法,修改数据库的对应记录。

问题是如果需要同时更新100 000条记录,是不是要逐一加载100 000条记录,然后依次调用set方法——这样不仅繁琐,数据访问的性能也十分糟糕。对这种批量处理的场景,Hibernate提供了批量处理的解决方案,下面分别从批量插入、批量更新和批量删除3个方面介绍如何面对这种批量处理的情形。

1) 批量插入

如果需要将100 000条记录插入数据库,通常Hibernate可能会采用如下做法:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {

    User u = new User (.....);

    session.save(customer);

}

tx.commit();

session.close();

但随着这个程序的运行,总会在某个时候运行失败,并且抛出OutOfMemoryException(内存溢出异常)。这是因为Hibernate的Session持有一个必选的一级缓存,所有的User实例都将在Session级别的缓存区进行了缓存的缘故。

为了解决这个问题,有个非常简单的思路:定时将Session缓存的数据刷新入数据库,而不是一直在Session级别缓存。可以考虑设计一个累加器,每保存一个User实例,累加器增加1。根据累加器的值决定是否需要将Session缓存中的数据刷入数据库。

下面是增加100 000个User实例的代码片段:

private void testUser()throws Exception

{

    //打开Session

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //循环100 000次,插入100 000条记录

    for (int i = 0 ; i < 1000000 ; i++ )

    {

        //创建User实例

        User u1 = new User();

        u1.setName("xxxxx" + i);

        u1.setAge(i);

        u1.setNationality("china");

        //在Session级别缓存User实例

        session.save(u1);

        //每当累加器是20的倍数时,将Session中的数据刷入数据库,并清空Session缓存

        if (i % 20 == 0)

        {

            session.flush();

            session.clear();

            tx.commit();

            tx = session.beginTransaction();

        }

    }

    //提交事务

    tx.commit();

    //关闭事务

    HibernateUtil.closeSession();

}

上面代码中,当i%20 == 0时,手动将Session处的缓存数据写入数据库,并手动提交事务。如果不提交事务,数据将依然缓存在事务处——未进入数据库,也将引起内存溢出的异常。

这是对Session级别缓存的处理,还应该通过如下配置来关闭SessionFactory的二级      缓存。

hibernate.cache.use_second_level_cache false

注意:除了要手动清空Session级别的缓存外,最好关闭SessionFactory级别的二级缓存。否则,即使手动清空Session级别的缓存,但因为在SessionFactory级别还有缓存,也可能引发异常。

2) 批量更新

上面介绍的方法同样适用于批量更新数据,如果需要返回多行数据,可以使用scroll()方法,从而可充分利用服务器端游标所带来的性能优势。下面是进行批量更新的代码片段:

private void testUser()throws Exception

{

    //打开Session

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //查询出User表中的所有记录

    ScrollableResults users = session.createQuery("from User")

        .setCacheMode(CacheMode.IGNORE)

        .scroll(ScrollMode.FORWARD_ONLY);

    int count=0;

    //遍历User表中的全部记录

    while ( users.next() )

    {

        User u = (User) users.get(0);

        u.setName("新用户名" + count);

        //当count为20的倍数时,将更新的结果从Session中flush到数据库

        if ( ++count % 20 == 0 )

        {

            session.flush();

            session.clear();

        }

    }

    tx.commit();

    HibernateUtil.closeSession();

}

通过这种方式,虽然可以执行批量更新,但效果非常不好。执行效率不高,而且需要先执行数据查询,然后再执行数据更新,并且这种更新将是逐行更新,即每更新一行记录,都需要执行一条update语句,性能非常低下。

为了避免这种情况,Hibernate提供了一种类似于SQL的批量更新和批量删除的HQL语法。

3) SQL风格的批量更新/删除

Hibernate提供的HQL语句也支持批量的UPDATE和DELETE语法。

批量UPDATE和DELETE语句的语法格式如下:

UPDATE | DELETE FROM? ClassName [WHERE WHERE_CONDITIONS]

关于上面的语法格式有以下四点值得注意:

   ● 在FROM子句中,FROM关键字是可选的。即完全可以不写FROM关键字。

   ● 在FROM子句中只能有一个类名,该类名不能有别名。

   ● 不能在批量HQL语句中使用连接,显式的或隐式的都不行。但可以在WHERE子句中使用子查询。

   ● 整个WHERE子句是可选的。

假设,需要批量更改User类实例的name属性,可以采用如下代码片段完成:

private void testUser()throws Exception

{

    //打开Session

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //定义批量更新的HQL语句

    String hqlUpdate = "update User set name = :newName";

    //执行更新

    int updatedEntities = session.createQuery( hqlUpdate )

                           .setString( "newName", "新名字" )

                           .executeUpdate();

    //提交事务

    tx.commit();

    HibernateUtil.closeSession();

}

从上面代码中可以看出,这种语法非常类似于PreparedStatement的executeUpdate语法。实际上,HQL的这种批量更新就是直接借鉴了SQL语法的UPDATE语句。

注意:使用这种批量更新语法时,通常只需要执行一次SQL的UPDATE语句,就可以完成所有满足条件记录的更新。但也可能需要执行多条UPDATE语句,这是因为有继承映射等特殊情况,例如有一个Person实例,它有Customer的子类实例。当批量更新Person实例时,也需要更新Customer实例。如果采用joined-subclass或union-subclass映射策略,Person和Customer实例保存在不同的表中,因此可能需要多条UPDATE语句。

执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法,下面是一次删除上面全部记录的代码片段:

private void testUser()throws Exception

{

    //打开Session实例

    Session session = HibernateUtil.currentSession();

    //开始事务

    Transaction tx = session.beginTransaction();

    //定义批量删除的HQL语句

    String hqlUpdate = "delete User";

    //执行批量删除

    int updatedEntities = session.createQuery( hqlUpdate )

                           .executeUpdate();

    //提交事务

    tx.commit();

    //关闭Session

    HibernateUtil.closeSession();

}

由Query.executeUpdate()方法返回一个整型值,该值是受此操作影响的记录数量。实际上,Hibernate的底层操作是通过JDBC完成的。因此,如果有批量的UPDATE或DELETE操作被转换成多条UPDATE或DELETE语句,该方法返回的是最后一条SQL语句影响的记录行数。

posted @ 2009-07-19 08:42 jadmin 阅读(74) | 评论 (0)编辑 收藏

  作为昔日开发语言的王者,Java已经有14岁了,它创新变革的脚步一直没有停下来。现在Java已经不单单是一种计算机语言,Java更是一个平台,一个社区,以及一个生态系统。在2009年,Oracle收购了SUN,Java留下的,只是一个王者的背影。

  JavaSE

  目前JDK的正式版本是JDK 6 Update 12。JDK 6 Update 10以来的版本,关键功能包括:

  Java内核大大缩小了,由原来的大约十几兆缩小到4兆,这样提高了启动Java程序的速度。而其它的Java库在需要的时候可以后台下载,这样也缩短了等待和安装的时间。

  下一代的Plug-in架构。Applet运行在自己的进程中,而不再依赖浏览器,提高了性能和可伸缩性。在2008 JavaOne有一段很酷的演示,就是将Applet直接从浏览器中拖拽到桌面上,或者从桌面上拖到浏览器中。这个功能的实现有赖于JDK 6中重写了连接Java运行环境和浏览器的代码。这个新的Plug-in架构还提供一个有意思的功能,可以在Web页面通过JavaScript调用Web页面上任何的Applet,不管这个Applet是用哪种语言写的,比如JavaFX Script, JRuby, Jython。

  这里要提到JDK 6中Java虚拟机(JVM)对动态语言的支持(JSR 223)。这个框架可以使Java应用程序中支持脚本引擎,这样各种脚本语言就可以运行在JVM上。JVM发展的一个重要方向就是去掉“J”,让JVM成为能支持各种语言的,全能的“VM(虚拟机)”。目前JVM支持的脚本语言包括:JavaFX Script, Groovy, JRuby, Jython, JavaScript, Scala, Clojure。可以预计到的是,将来会有更多的脚本语言运行在Java虚拟机上。

  还在草案阶段的JDK7,一些新特性也值得期待:

  一个是实现JDK 7模块化(JSR 294, JSR 277),将与OSGi联盟更紧密的配合,以便JSR 294模块可以被OSGi所使用。

  另外一个是并行包。这个包致力于通过充分利用底层硬件来达到真正的并发。随着硬件多核系统的广泛应用,并行计算的需求对Java的性能提出了更高要求。

  Java EE

  Java EE 6 (JSR 316)的公众审议将在2009年2月23号结束。Java EE 6继承了Java EE 5改进的目标,就是简化开发,另外,还增加了一个目标,更好地满足开发人员的需求。关于Java EE 6的讨论持续了很长时间,而争议最大的就是Java EE 6引入的Profile。

  Java EE十年来的发展结果,是这个平台变得越来越庞大,但对很多用户和开发者来说,也许他只需要使用众多功能中的很小一部分,却不得不安装整个平台。Profile就是为解决这个问题而定义的。Profile实际上是Java EE API的子集。讨论最热烈的Web Profile就集中在,哪些API应该被放在标准Profile中?

  Jave EE 6包括了一系列的新技术和升级,篇幅所限制,仅罗列一些名词:WebBean 1.0, JSF 2.0, EJB 3.1, JPA 2.0, Servlet 3.0, JAX-RS 1.1。

  Java ME

  Mobile Service Architecture 2 (MSA 2)目前已经到了公众审议的尾声(JSR 249)。预计2009年,MSA 2将进入实用阶段。MSA 2是下一代Java ME平台技术,提供了更多移动开发的新特性,比如可以访问手持设备上的各种传感器,如加速计传感器,电池容量(JSR256);可以在手机上看电视(JSR 272);如同信用卡支付功能的手机钱包(JSR 257);使用XML,脚本,与Java一同构造GUI (JSR 290);通过手机使用VOIP服务(JSR 281)。

  Java FX

  Java在企业应用程序的开发中一直占主导地位,但现在面向消费者的富互联网应用(Rich Internet Application, RIA)软件数量在急剧增加,这种情形下,JavaFX应运而生。和Java语言相比,JavaFX Script更适合开发高效,快速地开发集合各种媒体,交互性强,界面吸引用户的RIA应用程序。2009年2月,JavaFX SDK 1.1与JavaFX Mobile都正式发布了,下一个要期待的是JavaFX TV。借助Java这个强大的平台,JavaFX目标是提供给开发者更好的RIA平台与技术,除了继续要在传统的PC桌面保持优势外,更是面向未来的终端设备,手机和电视。

  JAVA是有SUN公司开发的新一代编程语言,它可以用在各种不同的机器、操作系统的网络环境中进行开发。不论你使用哪种浏览器或者使用哪种操作系统(Windows、Unix等等),只要浏览器支持JAVA,你就可以看到生动的主页。JAVA正在逐步成为Internet应用的主要开发语言,它彻底改变了应用软件的开发模式,为迅速发展的信息世界增添了新的活力。所以作为Internet应用的开发技术人员不可不看JAVA,而JAVA程序不可不先从基础学起。

  希望Java能够继续发展下去,作为一个影响着整个业界和无数技术人员的开发语言,Java不应该仅仅是给我们留下一个背影而已。

posted @ 2009-07-07 15:30 jadmin 阅读(84) | 评论 (0)编辑 收藏

struts.action.extension
          The URL extension to use to determine if the request is meant for a Struts action
           用URL扩展名来确定是否这个请求是被用作Struts action,其实也就是设置 action的后缀,例如login.do的'do'字。

struts.configuration
          The org.apache.struts2.config.Configuration implementation class
            org.apache.struts2.config.Configuration接口名

struts.configuration.files
          A list of configuration files automatically loaded by Struts
           struts自动加载的一个配置文件列表

struts.configuration.xml.reload
          Whether to reload the XML configuration or not
           是否加载xml配置(true,false)

struts.continuations.package
           The package containing actions that use Rife continuations
           含有actions的完整连续的package名称

struts.custom.i18n.resources
          Location of additional localization properties files to load
           加载附加的国际化属性文件(不包含.properties后缀)

struts.custom.properties
          Location of additional configuration properties files to load
           加载附加的配置文件的位置


struts.devMode
          Whether Struts is in development mode or not
           是否为struts开发模式

struts.dispatcher.parametersWorkaround
          Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic
            (某些版本的weblogic专用)是否使用一个servlet请求参数工作区(PARAMETERSWORKAROUND)

struts.enable.DynamicMethodInvocation
          Allows one to disable dynamic method invocation from the URL
            允许动态方法调用

struts.freemarker.manager.classname
          The org.apache.struts2.views.freemarker.FreemarkerManager implementation class
           org.apache.struts2.views.freemarker.FreemarkerManager接口名

struts.i18n.encoding
          The encoding to use for localization messages
           国际化信息内码

struts.i18n.reload
          Whether the localization messages should automatically be reloaded
           是否国际化信息自动加载

struts.locale
          The default locale for the Struts application
           默认的国际化地区信息

struts.mapper.class
          The org.apache.struts2.dispatcher.mapper.ActionMapper implementation class
            org.apache.struts2.dispatcher.mapper.ActionMapper接口

struts.multipart.maxSize
          The maximize size of a multipart request (file upload)
           multipart请求信息的最大尺寸(文件上传用)

struts.multipart.parser
          The org.apache.struts2.dispatcher.multipart.
          MultiPartRequest parser implementation for a multipart request (file upload)
          专为multipart请求信息使用的org.apache.struts2.dispatcher.multipart.MultiPartRequest解析器接口(文件上传用)


struts.multipart.saveDir
          The directory to use for storing uploaded files
           设置存储上传文件的目录夹

struts.objectFactory
          The com.opensymphony.xwork2.ObjectFactory implementation class
           com.opensymphony.xwork2.ObjectFactory接口(spring)

struts.objectFactory.spring.autoWire
          Whether Spring should autoWire or not
           是否自动绑定Spring

struts.objectFactory.spring.useClassCache
          Whether Spring should use its class cache or not
           是否spring应该使用自身的cache

struts.objectTypeDeterminer
          The com.opensymphony.xwork2.util.ObjectTypeDeterminer implementation class
            com.opensymphony.xwork2.util.ObjectTypeDeterminer接口

struts.serve.static.browserCache
If static content served by the Struts filter should set browser caching header properties or not
           是否struts过滤器中提供的静态内容应该被浏览器缓存在头部属性中

struts.serve.static
          Whether the Struts filter should serve static content or not
           是否struts过滤器应该提供静态内容

struts.tag.altSyntax
          Whether to use the alterative syntax for the tags or not
           是否可以用替代的语法替代tags

struts.ui.templateDir
          The directory containing UI templates
           UI templates的目录夹

struts.ui.theme
          The default UI template theme
           默认的UI template主题

struts.url.http.port
          The HTTP port used by Struts URLs
           设置http端口

struts.url.https.port
          The HTTPS port used by Struts URLs
           设置https端口

struts.url.includeParams
          The default includeParams method to generate Struts URLs
          在url中产生 默认的includeParams


struts.velocity.configfile
          The Velocity configuration file path
           velocity配置文件路径

struts.velocity.contexts
          List of Velocity context names
           velocity的context列表


struts.velocity.manager.classname
          org.apache.struts2.views.velocity.VelocityManager implementation class
           org.apache.struts2.views.velocity.VelocityManager接口名

struts.velocity.toolboxlocation
          The location of the Velocity toolbox
           velocity工具盒的位置


struts.xslt.nocache
          Whether or not XSLT templates should not be cached
           是否XSLT模版应该被缓存

摘自:http://li445970924.javaeye.com/blog/420056

posted @ 2009-07-04 15:33 jadmin 阅读(106) | 评论 (0)编辑 收藏

在*.hbm.xml必须声明的< generator>子元素是一个Java类的名字,用来为该持久化类的实例生成唯一的标识。

< generator class="sequence"/>

这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,Hibernate提供了很多内置的实现。下面是Generator子元素的一些内置生成器的快捷名字:

increment(递增)

用于为long, short或者int类型生成唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。

identity

对DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的内置标识字段提供支持。返回的标识符是long, short 或者int类型的。

sequence (序列)

在DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence),而在Interbase中使用生成器(generator)。返回的标识符是long, short或者 int类型的。

hilo (高低位)

使用一个高/低位算法来高效的生成long, short或者 int类型的标识符。给定一个表和字段(默认分别是是hibernate_unique_key 和next_hi)作为高位值得来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。在使用JTA获得的连接或者用户自行提供的连接中,不要使用这种生成器。

seqhilo(使用序列的高低位)

使用一个高/低位算法来高效的生成long, short或者 int类型的标识符,给定一个数据库序列(sequence)的名字。

uuid.hex

用一个128-bit的UUID算法生成字符串类型的标识符。在一个网络中唯一(使用了IP地址)。UUID被编码为一个32位16进制数字的字符串。

uuid.string

使用同样的UUID算法。UUID被编码为一个16个字符长的任意ASCII字符组成的字符串。不能使用在PostgreSQL数据库中

native(本地)

根据底层数据库的能力选择identity, sequence 或者hilo中的一个。

assigned(程序设置)

让应用程序在save()之前为对象分配一个标示符。

foreign(外部引用)

使用另外一个相关联的对象的标识符。和< one-to-one>联合一起使用。

Generator子元素的用法:

  1. <  class name="onlyfun.caterpillar.User" table="USER">
  2.            <  id name="id" type="string" unsaved-value="null">
  3.                <  column name="USER_ID"/>
  4.                <  generator class="uuid.hex"/>
  5.            <   /id>
posted @ 2009-07-03 14:04 jadmin 阅读(50) | 评论 (0)编辑 收藏

在Hibernate中有三种状态,对它的深入理解,才能更好的理解hibernate的运行机理,刚开始不太注意这些概念,后来发现它是重要的。对于理解hibernate,JVM和sql的关系有更好的理解。对于需要持久化的JAVA对象,在它的生命周期中有三种状态,而且互相转化。

Hibernate三种状态之一:临时状态(Transient):用new创建的对象,它没有持久化,没有处于Session中,处于此状态的对象叫临时对象;

Hibernate三种状态之二:持久化状态(Persistent):已经持久化,加入到了Session缓存中。如通过hibernate语句保存的对象。处于此状态的对象叫持久对象;

Hibernate三种状态之三:游离状态(Detached):持久化对象脱离了Session的对象。如Session缓存被清空的对象。特点:已经持久化,但不在Session缓存中。处于此状态的对象叫游离对象;

Hibernate三种状态中游离对象和临时对象异同:

两者都不会被Session关联,对象属性和数据库可能不一致;

游离对象由持久化对象关闭Session而转化而来,在内存中还有对象所以此时就变成游离状态了;

Hibernate和SQL的关系:

在操作了hibernate的方法如save()等后,并没有直接生成sql语句,去操作数据库,而是把这些更新存入Session中,只有Session缓存要被更新时,底层的sql语句才能执行,数据存入数据库;

下面举例说明:

一,Session.save(user)运行机理。
1,把User对象加入缓存中,使它变成持久化对象;
2,选用映射文件指定的标识生成ID;
3,在Session清理缓存时候执行:在底层生成一个insert sql语句,把对象存入数据库;

注意:在你执行Session.save(user)后,在Session清理缓存前,如果你修改user对象属性值,那么最终存入数据库的值将是最后修改的值;此过程中ID不能被修改;

二,Session.delete(user)运行过程。
如果user是持久化对象,则执行删除操作,同样底层数据库的执行条件是:在Session清理缓存时候;
如果user是游离对象:
1,将user对象和Session关联,使之成为持久化对象;
2,然后按照user 是持久化对象的过程执行;

posted @ 2009-07-03 14:00 jadmin 阅读(48) | 评论 (0)编辑 收藏

Hibernate访问多个数据库的设计思路:利用 Hibernate中config = new Configuration().configure(configFile);可以加载不同数据库配置信息的原理,编写一个数据库操作类,再编写一个数据库管理程序[map],将加载的数据库连接实例put早数据库管理程序中,具体实现见下面:

Hibernate访问多个数据库步骤一:hibernate配置文件

localhost.cfg.xml

  1. < ?xml version="1.0" encoding="utf-8"?>
  2. < !DOCTYPE hibernate-configuration
  3.      PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
  4.      "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  5. < hibernate-configuration>
  6.     < session-factory >
  7.   < !-- local connection properties -->
  8.   < property name="hibernate.connection.url">jdbc:mysql://localhost:3306/bookshop?zeroDateTimeBehavior=convertToNull< /property>
  9.   < property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver< /property>
  10.   < property name="hibernate.connection.username">root< /property>
  11.   < property name="hibernate.connection.password">12345678< /property>
  12.   < !-- property name="hibernate.connection.pool_size">< /property -->
  13.   < !-- dialect for MySQL -->
  14.         < property name="dialect">org.hibernate.dialect.MySQLDialect< /property>
  15.         < property name="hibernate.show_sql">true< /property>
  16.         < property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory< /property>        
  17.         < property name="hbm2ddl.auto">update< /property>
  18.      < mapping resource="org/jskyme/data/local/po/Shop.hbm.xml"/>
  19.     < /session-factory>
  20. < /hibernate-configuration>

data_server.cfg.xml

  1. < ?xml version="1.0" encoding="utf-8"?>
  2. < !DOCTYPE hibernate-configuration
  3.      PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
  4.      "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  5. < hibernate-configuration>
  6.     < session-factory >
  7.   < !-- local connection properties -->
  8.   < property name="hibernate.connection.url">jdbc:mysql://192.168.0.10:3306/bookshop?zeroDateTimeBehavior=convertToNull< /property>
  9.   < property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver< /property>
  10.   < property name="hibernate.connection.username">root< /property>
  11.   < property name="hibernate.connection.password">12345678< /property>
  12.   < !-- property name="hibernate.connection.pool_size">< /property -->
  13.   < !-- dialect for MySQL -->
  14.         < property name="dialect">org.hibernate.dialect.MySQLDialect< /property>
  15.         < property name="hibernate.show_sql">true< /property>
  16.         < property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory< /property>        
  17.         < property name="hbm2ddl.auto">update< /property>
  18.      < mapping resource="org/jskyme/data/local/po/Shop.hbm.xml"/>
  19.     < /session-factory>
  20. < /hibernate-configuration>

Hibernate访问多个数据库步骤二:数据库访问类:

数据库管理类:DataBaseManager

  1. package org.jskyme.hibernate.util;
  2. import java.util.HashMap;
  3. public class DataBaseManager extends HashMap {
  4. private static final long serialVersionUID = 6491666983237498097L;
  5. private static DataBaseManager inst = new DataBaseManager();
  6. public static DataBaseManager getInst() {
  7.   return inst;
  8. }
  9. public SessionManager get(Object key) {
  10.   return (SessionManager) super.get(key);
  11. }
  12. @Override
  13. public Object put(Object key, Object value) {
  14.   return super.put(key, value);
  15. }
  16. public static void setInst(DataBaseManager inst) {
  17.    DataBaseManager.inst = inst;
  18. }
  19. }

Hibernate连接数据库操作类:

  1. package org.jskyme.hibernate.util;
  2. import java.util.List;
  3. import org.hibernate.Criteria;
  4. import org.hibernate.Query;
  5. import org.hibernate.SQLQuery;
  6. import org.hibernate.Session;
  7. import org.hibernate.SessionFactory;
  8. import org.hibernate.Transaction;
  9. import org.hibernate.cfg.Configuration;
  10. public final class SessionManager {
  11. private Configuration config;
  12. private SessionFactory sessionFactory;
  13. private Session session;
  14. public Criteria createCriteria(Class persistentClass) {
  15.   return session.createCriteria(persistentClass);
  16. }
  17. private void buildSession() {
  18.    sessionFactory = config.buildSessionFactory();
  19.    session = sessionFactory.openSession();
  20. }
  21. public SessionManager(String configFile) {
  22.    config = new Configuration().configure(configFile);
  23.    buildSession();
  24. }
  25. public Session getSession() {
  26.   return session;
  27. }
  28. public void save(Object obj) {
  29.    Transaction tx = session.beginTransaction();
  30.    session.save(obj);
  31.    tx.commit();
  32. }
  33. public Object load(Class clas, Integer priId) {
  34.   return session.get(clas, priId);
  35. }
  36. public Query findbyhql(String hql) {
  37.   return session.createQuery(hql);
  38. }
  39. public List pageSizeByhql(String hql) {
  40.   return findbyhql(hql).list();
  41. }
  42. public SQLQuery findbysql(String sql) {
  43.   return session.createSQLQuery(sql);
  44. }
  45. public void update(Object obj) {
  46.    Transaction tx = session.beginTransaction();
  47.    session.saveOrUpdate(obj);
  48.    tx.commit();
  49. }
  50. public void delete(Class clas, Integer inte) {
  51.    session.delete(load(clas, inte));
  52. }
  53. public void delete(Object obj) {
  54.    session.delete(obj);
  55. }
  56. public void deletebyhql(String hql) {
  57.    Query query = session.createQuery(hql);
  58.    query.executeUpdate();
  59. }
  60. public Query createQuery(String hql) {
  61.   return session.createQuery(hql);
  62. }
  63. }

Hibernate访问多个数据库步骤三:测试类

  1. package org.jskyme.data.test;
  2. import junit.framework.TestCase;
  3. import org.hibernate.Query;
  4. import org.jskyme.hibernate.util.DataBaseManager;
  5. import org.jskyme.hibernate.util.SessionManager;
  6. public class DataBaseManagerTest extends TestCase {
  7. DataBaseManager dbm = DataBaseManager.getInst();
  8. public void testDatabase() {
  9.    setDatabase();
  10.    SessionManager tempSess = dbm.get("dataLocal");
  11.    Query query = tempSess.createQuery("from   Shop");
  12.    query.list();
  13.   
  14.    SessionManager tempSess27 = dbm.get("dateManage");
  15.    Query query27 = tempSess27.createQuery("from Shop");
  16.    query27.list();
  17. }
  18. private void setDatabase() {
  19.    SessionManager dateManageLocal = new SessionManager("localhost.cfg.xml");
  20.    SessionManager dateManage27 = new SessionManager("data_server.cfg.xml");
  21.    dbm.put("dateManage", dateManage27);
  22.    dbm.put("dataLocal", dateManageLocal);
  23. }
  24. }
posted @ 2009-07-03 13:51 jadmin 阅读(69) | 评论 (0)编辑 收藏

Hibernate延时加载,其实这个异常写的非常之清楚,就是会话关闭,无法对Hibernate实体进行操作。造成这样的情况有很多,什么书写错误啊,逻辑错误啊。

但就此说一下关于lazy机制:

Hibernate延时加载包括延迟初始化错误,这是运用Hibernate开发项目时最常见的错误。如果对一个类或者集合配置了延迟检索策略,那么必须当代理类实例或代理集合处于持久化状态(即处于Session范围内)时,才能初始化它。如果在游离状态时才初始化它,就会产生延迟初始化错误。

下面把Customer.hbm.xml文件的< class>元素的lazy属性设为true,表示使用延迟检索策略:

  1. < class name="mypack.Customer" table="CUSTOMERS" lazy="true">  

当执行Session的load()方法时,Hibernate不会立即执行查询CUSTOMERS表的select语句,仅仅返回Customer类的代理类的实例,这个代理类具由以下特征:

(1) 由Hibernate在运行时动态生成,它扩展了Customer类,因此它继承了Customer类的所有属性和方法,但它的实现对于应用程序是透明的。

(2) 当Hibernate创建Customer代理类实例时,仅仅初始化了它的OID属性,其他属性都为null,因此这个代理类实例占用的内存很少。

(3)当应用程序第一次访问Customer代理类实例时(例如调用customer.getXXX()或customer.setXXX ()方法), Hibernate会初始化代理类实例,在初始化过程中执行select语句,真正从数据库中加载Customer对象的所有数据。但有个例外,那就是当 应用程序访问Customer代理类实例的getId()方法时,Hibernate不会初始化代理类实例,因为在创建代理类实例时OID就存在了,不必 到数据库中去查询。

提示:Hibernate采用CGLIB工具来生成持久化类的代理类。CGLIB是一个功能强大的Java字节码生成工具,它能够在程序运行时动态生成扩 展 Java类或者实现Java接口的代理类。

以下代码先通过Session的load()方法加载Customer对象,然后访问它的name属性:

  1. tx = session.beginTransaction();
  2. Customer customer=(Customer)session.load(Customer.class,new Long(1));
  3. customer.getName();
  4. tx.commit();  

在运行session.load ()方 法时Hibernate不执行任何select语句,仅仅返回Customer类的代理类的实例,它的OID为1,这是由load()方法的第二个 参数指定的。当应用程序调用customer.getName()方法时,Hibernate会初始化Customer代理类实例,从数据库中加载 Customer对象的数据,执行以下select语句:

  1. select * from CUSTOMERS where ID=1;
  2. select * from ORDERS where CUSTOMER_ID=1;  

当< class>元素的lazy属性为true,会影响Session的load()方法的各种运行时行为,下面举例说明。

1.如果加载的Customer对象在数据库中不存在,Session的load()方法不会抛出异常,只有当运行customer.getName()方法时才会抛出以下异常:

  1. ERROR LazyInitializer:63 - Exception initializing proxy
  2. net.sf.hibernate.ObjectNotFoundException: No row with the given identifier exists: 1, of class:
  3. mypack.Customer  

2.如果在整个Session范围内,应用程序没有访问过Customer对象,那么Customer代理类的实例一直不会被初始化,Hibernate不会执行任何select语句。以下代码试图在关闭Session后访问Customer游离对象:

  1. tx = session.beginTransaction();
  2. Customer customer=(Customer)session.load(Customer.class,new Long(1));
  3. tx.commit();
  4. session.close();
  5. customer.getName();  

由于引用变量customer引用的Customer代理类的实例在Session范围内始终没有被初始化,因此在执行customer.getName()方法时,Hibernate会抛出以下异常(Hibernate延时加载的问题之一):

  1. ERROR LazyInitializer:63 - Exception initializing proxy
  2. net.sf.hibernate.HibernateException: Couldnotinitializeproxy-theowningSessionwasclosed  

由此可见,Customer代理类的实例只有在当前Session范围内才能被初始化。

3.net.sf.hibernate.Hibernate类的initialize()静态方法用于在Session范围内显式初始化代理类实例,isInitialized()方法用于判断代理类实例是否已经被初始化。例如:

  1. tx = session.beginTransaction();
  2. Customer customer=(Customer)session.load(Customer.class,new Long(1));
  3. if(!Hibernate.isInitialized(customer))
  4. Hibernate.initialize(customer);
  5. tx.commit();
  6. session.close();
  7. customer.getName();  

以上代码在Session范围内通过Hibernate类的initialize()方法显式初始化了Customer代理类实例,因此当Session关闭后,可以正常访问Customer游离对象。

4.当应用程序访问代理类实例的getId()方法时,不会触发Hibernate初始化代理类实例的行为,例如:

  1. tx = session.beginTransaction();
  2. Customer customer=(Customer)session.load(Customer.class,new Long(1));
  3. customer.getId();
  4. tx.commit();
  5. session.close();
  6. customer.getName();  

当应用程序访问customer.getId()方法时,该方法直接返回Customer代理类实例的OID值,无需查询数据库。由于引用变量 customer始终引用的是没有被初始化的Customer代理类实例,因此当Session关闭后再执行customer.getName()方法, Hibernate会抛出以下异常(Hibernate延时加载的问题之一):

  1. ERROR LazyInitializer:63 - Exception initializing proxy
  2. net.sf.hibernate.HibernateException: Couldnotinitializeproxy-theowningSessionwasclosed  

解决方法:

由于hibernate采用了lazy=true,这样当你用hibernate查询时,返回实际为利用cglib增强的代理类,但其并没有实际填 充;当你在前端,利用它来取值(getXXX)时,这时Hibernate才会到数据库执行查询,并填充对象,但此时如果和这个代理类相关的session已关闭掉,就会产生种错误.

在做一对多时,有时会出现"could not initialize proxy - clothe owning Session was sed,这个好像是hibernate的缓存问题.问题解决:需要在< many-to-one>里设置lazy="false". 但有可能会引发另一个异常叫

  1. failed to lazily initialize a collection of role: XXXXXXXX, no session or session was closed  

解决方法:在web.xml中加入

  1. < filter>
  2.     < filter-name>hibernateFilter< /filter-name>
  3.     < filter-class>
  4.       org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
  5.     < /filter-class>
  6. < /filter>
  7. < filter-mapping>
  8.     < filter-name>hibernateFilter< /filter-name>
  9.     < url-pattern>*.do< /url-pattern>
  10. < /filter-mapping>

就可以了。

以上文章转自:http://developer.51cto.com/art/200907/133249.htm

关键字:Hibernate,延时加载,lazy

posted @ 2009-07-03 13:39 jadmin 阅读(62) | 评论 (0)编辑 收藏
众所周知,设计模式描述的就是针对软件设计中的常见问题做出的可重复使用的解决方案。而了解及使用这些模式则是SOA取得成功的根本。
企业软件热点文章
Windows 7与Vista价格对比 微软大幅延长Windows 7 降级XP期限
Windows 7完整公开发售路线图 微软推Windows 7选择升级计划 VAIO率先响应
微软称Windows 7限期打折 预定只须50美元 Windows 7免费升级计划截至2010年1月31日
欧洲用户不能升级Windows 7 只能全新安装 消息称Windows 7中国零售价最低399元起

众所周知,设计模式描述的就是针对软件设计中的常见问题做出的可重复使用的解决方案。而了解及使用这些模式则是SOA取得成功的根本。下面是Gartner公司的分析师们通过分析得出的五种新兴SOA设计模式:

1. 多通道应用

2. 复合应用

3. 业务流程编排

4. 面向服务的企业

5. 联邦SOA

多通道应用

用SOA实现多通道应用真是再合适不过。这种模式能将后端业务逻辑与前端逻辑分离,通过各个通道在最短的时间内将全部的应用功能提交到最大数量的用户手上,并能重复使用同一服务。

战略远景:2008年,将有超过66%的新开发的中到大型交互式应用软件是支持多通道访问的,而2007年这一数据尚不及33%。

复合应用

在复合应用中使用的服务可能是新部署的服务、经过调整和封装的旧应用组件、或者是以上两者的组合。在组合SOA环境中,有两种集成技术是使系统有效运行的关键:1)帮助用户封装并接受各种初始SOA应用的服务接口底层的集成技术;和2)帮助用户组装并监控服务操作的集成技术。

战略远景:到2012年,大部分SOA应用软件将是交互式的复合应用。

业务流程编排

业务流程管理(BPM)软件包是用来实现基于SOA的多步处理过程的工具。BPEL标准经常被用来描述所设计的元数据流模型。元数据库(meta-database)是用来在运行时管理这些业务过程模型的行为的。这些过程中的部分步骤是通过调用SOA服务实现的。其它的步骤则需要人为的干预。

战略远景:到2009年,有超过75%的SOA应用将通过外部BPM技术实现一部分与服务部署无关的顺序控制。

面向服务的企业

基于SOA的企业模型离复合应用只有一步之遥。在这里,所有的应用程序都被看作是整体的一个组成部分。没有任何新应用程序是独立创建的。所有的应用程序都是以可重用的组件为基础构建的,它们不但可以实现预期的功能,也可以在其它环境下被不同的客户端使用。从本质上说,综合式的复合企业所拥有的已不是应用程序,而是业务组件——每一个组件都是企业的资产。

战略远景:到2010年,超过85%的企业会把应用集成视为与SOA管理工具和组织同样的企业组件。

联邦SOA

联邦SOA的基本概念就是采用合理的程序将企业分解为半独立的SOA领域(比如,以子公司、业务单元或部门来表示企业组织),每个领域都有其独自特有的SOA基础设施、治理过程和SOA卓越中心。然后各领域通过合适的互用性基础设施、治理过程和组织方式形成联邦(即以联合的方式实现领域内的服务共享,这是通常的方式,但不是必须的方式)。“SOA联邦”即是通过适当的技术、治理和组织方式形成联邦式SOA的过程。

战略远景:很少有大型组织有能力独自做出整个IT的宏伟蓝图。最好的实践还是支持领域的独立性并允许使用不同的技术与架构以换取互操作性协议与传输的同步。合并与收购很显然就是联邦SOA的一种方式。

posted @ 2009-06-28 13:25 jadmin 阅读(62) | 评论 (0)编辑 收藏
  许多种类的错误将触发异常,这些问题从像硬盘(crash)坠毁这样的严重硬件错误,到尝试访问越界数组元素这样的简单程序错误,像这样的错误如果在java函数中发生,函数将创建一个异常对象并把他抛出到运行时系统(runtimesystem)。

  许多种类的错误将触发异常,这些问题从像硬盘(crash)坠毁这样的严重硬件错误,到尝试访问越界数组元素这样的简单程序错误,像这样的错误如果在java函数中发生,函数将创建一个异常对象并把他抛出到运行时系统(runtimesystem)。异常对象包含异常的信息,包括异常的类型,异常发生时程序的状态。运行时系统则有责任找到一些代码处理这个错误。在java技术词典中,创建一个异常对象并把它抛给运行时系统叫做:抛出异常(throwinganexception)。

  当某个函数抛出一个异常后,运行时系统跳入了这样一个动作,就是找到一些人(译者注:其实是代码)来处理这个异常。要找的处理异常的可能的人(代码)的集合(set)是:在发生异常的方法的调用堆栈(callstack)中的方法的集合(set)。运行时系统向后搜寻调用堆栈,从错误发生的函数,一直到找到一个包括合适的异常处理器(exceptionhandler)的函数。一个异常处理器是否合适取决于抛出的异常是否和异常处理器处理的异常是同一种类型。因而异常向后寻找整个调用堆栈,直到一个合格的异常处理器被找到,调用函数处理这个异常。异常处理器的选择被叫做:捕获异常(catchtheexception)。

  如果运行时系统搜寻整个调用堆栈都没有找到合适的异常处理器,运行时系统将结束,随之java程序也将结束。

  使用异常来管理错误,比传统的错误管理技术有如下优势:

  1. 将错误处理代码于正常的代码分开。

  2. 沿着调用堆栈向上传递错误。

  3. 将错误分作,并区分错误类型。

  1. 将错误处理代码于正常的代码分开。

  在传统的程序种,错误侦测,报告,和处理,经常导致令人迷惑的意大利面条式(spaghetti)的代码。例如,假设你要写一个将这个文件读到内存种的函数,用伪代码描述,你的函数应该是这个样子的:

  readFile

  open the file; //打开文件

  determine its size; //取得文件的大小

  allocate that much memory; //分配内存

  read the file into memory; //读文件内容到内存中

  close the file; //关闭文件

  匆匆一看,这个版本是足够的简单,但是它忽略了所有潜在的问题:

  n 文件不能打开将发生什么?

  n 文件大小不能取得将发生什么?

  n 没有足够的内存分配将发生什么?

  n 读取失败将发生什么?

  n 文件不能关闭将发生什么?

  为了在read_file函数中回答这些错误,你不得不加大量的代码进行错误侦测,报告和处理,你的函数最后将看起来像这个样子:

  errorCodeType readFile

  initialize errorCode = 0;

  open the file;

  if (theFileIsOpen)

  determine the length of the file;

  if (gotTheFileLength)

  allocate that much memory;

  if (gotEnoughMemory)

  read the file into memory;

  if (readFailed)

  errorCode = -1;

  else

  errorCode = -2;

  else

  errorCode = -3;

  close the file;

  if (theFileDidntClose && errorCode 0)

  errorCode = -4;

  else

  errorCode = errorCode and -4;

  else

  errorCode = -5;

  return errorCode;

  随着错误侦测的建立,你的最初的7行代码(粗体)已经迅速的膨胀到了29行-几乎400%的膨胀率。更糟糕的是有这样的错误侦测,报告和错误返回值,使得最初有意义的7行代码淹没在混乱之中,代码的逻辑流程也被淹没。很难回答代码是否做的正确的事情:如果函数分配内容失败,文件真的将被关闭吗?更难确定当你在三个月后再次修改代码,它是否还能够正确的执行。许多程序员“解决”这个问题的方法是简单的忽略它,那样错误将以死机来报告自己。

  对于错误管理,Java提供一种优雅的解决方案:异常。异常可以使你代码中的主流程和处理异常情况的代码分开。如果你用异常代替传统的错误管理技术,readFile函数将像这个样子:

  readFile

  try

  open the file;

  determine its size;

  allocate that much memory;

  read the file into memory;

  close the file;

  catch (fileOpenFailed)

  doSomething;

  catch (sizeDeterminationFailed)

  doSomething;

  catch (memoryAllocationFailed)

  doSomething;

  catch (readFailed)

  doSomething;

  catch (fileCloseFailed)

  doSomething;

  注意:异常并不能节省你侦测,报告和处理错误的努力。异常提供给你的是:当一些不正常的事情发生时,将所有蹩脚(grungy)的细节,从你的程序主逻辑流程中分开。

  另外,异常错误管理的膨胀系数大概是250%,比传统的错误处理技术的400%少的多。

tags:异常

posted @ 2009-06-25 23:45 jadmin 阅读(73) | 评论 (0)编辑 收藏

       MD5的全称是Message-Digest Algorithm 5,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经MD2、MD3和MD4发展而来。

       Message-Digest泛指字节串(Message)的Hash变换,就是把一个任意长度的字节串变换成一定长的大整数。请注意我使用了“字节串”而不是“字符串”这个词,是因为这种变换只与字节的值有关,与字符集或编码方式无关。

       MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。

       MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。

       MD5还广泛用于加密和解密技术上,在很多操作系统中,用户的密码是以MD5值(或类似的其它算法)的方式保存的, 用户Login的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的MD5值进行比较,而系统并不“知道”用户的密码是什么。

       关键词:MD5,MD5算法,文件的MD5值,文件 | 曦勤,[风故故,也依依], 博客,百度,IT

posted @ 2009-05-28 19:45 jadmin 阅读(105) | 评论 (0)编辑 收藏

代码如下:

/*
* @(#)DatabaseBackup.java Apr 23, 2009
*
* Copyright (c) 2009 by jadmin. All Rights Reserved.
*/

package util.dbak;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

/**
* MySQL数据库的备份与恢复
* 缺陷:可能会被杀毒软件拦截
*
* @author <a href="mailto:jadmin@163.com">jadmin</a>
* @version $Revision: 1.0 Apr 23, 2009 11:44:00 PM $
*/
public class DatabaseBackup {

/** MySQL安装目录的Bin目录的绝对路径 */
private String mysqlBinPath;

/** 访问MySQL数据库的用户名 */
private String username;

/** 访问MySQL数据库的密码 */
private String password;

public String getMysqlBinPath() {
   return mysqlBinPath;
}

public void setMysqlBinPath(String mysqlBinPath) {
   this.mysqlBinPath = mysqlBinPath;
}

public String getUsername() {
   return username;
}

public void setUsername(String username) {
   this.username = username;
}

public String getPassword() {
   return password;
}

public void setPassword(String password) {
   this.password = password;
}

public DatabaseBackup() {
   super();
}

public DatabaseBackup(String mysqlBinPath, String username, String password) {
   super();
   if (!mysqlBinPath.endsWith(File.separator)) {
    mysqlBinPath = mysqlBinPath + File.separator;
   }
   this.mysqlBinPath = mysqlBinPath;
   this.username = username;
   this.password = password;
}

/**
* 备份数据库
*
* @param output 输出流
* @param dbname 要备份的数据库名
*/
public void backup(OutputStream output, String dbname) {
   String command = "cmd /c " + mysqlBinPath + "mysqldump -u" + username + " -p" + password + " --set-charset=utf8 "
     + dbname;
   System.out.println(command);
   PrintWriter p = null;
   BufferedReader reader = null;
   try {
    p = new PrintWriter(new OutputStreamWriter(output, "utf8"));
    Process process = Runtime.getRuntime().exec(command);
    InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream(), "utf8");
    reader = new BufferedReader(inputStreamReader);
    String line = null;
    while ((line = reader.readLine()) != null) {
     p.println(line);
    }
    p.flush();
   } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   } finally {
    try {
     if (reader != null) {
      reader.close();
     }
     if (p != null) {
      p.close();
     }
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
}

/**
* 备份数据库,如果指定路径的文件不存在会自动生成
*
* @param dest 备份文件的路径
* @param dbname 要备份的数据库
*/
public void backup(String dest, String dbname) {
   try {
    OutputStream out = new FileOutputStream(dest);
    backup(out, dbname);
   } catch (FileNotFoundException e) {
    e.printStackTrace();
   }
}

/**
* 恢复数据库
*
* @param input 输入流
* @param dbname 数据库名
*/
public void restore(InputStream input, String dbname) {
   String command = "cmd /c " + mysqlBinPath + "mysql -u" + username + " -p" + password + " " + dbname;
   try {
    Process process = Runtime.getRuntime().exec(command);
    OutputStream out = process.getOutputStream();
    String line = null;
    String outStr = null;
    StringBuffer sb = new StringBuffer("");
    BufferedReader br = new BufferedReader(new InputStreamReader(input, "utf8"));
    while ((line = br.readLine()) != null) {
     sb.append(line + "\r\n");
    }
    outStr = sb.toString();

    OutputStreamWriter writer = new OutputStreamWriter(out, "utf8");
    writer.write(outStr);
    writer.flush();
    out.close();
    br.close();
    writer.close();
   } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   }

}

/**
* 恢复数据库
*
* @param dest 备份文件的路径
* @param dbname 数据库名
*/
public void restore(String dest, String dbname) {
   try {
    InputStream input = new FileInputStream(dest);
    restore(input, dbname);
   } catch (FileNotFoundException e) {
    e.printStackTrace();
   }
}

public static void main(String[] args) {
   DatabaseBackup bak = new DatabaseBackup("C:/MySQL/UTF8/bin", "root", "root");
   bak.restore("c:/t.sql", "ttk");
}
}

tags:java,mysql,database,数据库,备份,恢复,mysql命令

posted @ 2009-04-24 22:55 jadmin 阅读(82) | 评论 (0)编辑 收藏

在实际项目中,经常会遇到这样的问题:想得到某个目录下的所有具有特定扩展名文件的文件名集合

解决方法:

1.定义自己的文件名过滤器类,这个类必须实现java.io.FilenameFilter接口

2.调用


下面是我的一个例子,目标:得到目录Constans.SCRIPT_DIR下所有扩展名为".sql"的文件的文件名集合

1.实现自己的文件名过滤类

/**
* 脚本文件过滤器
*
* @author <a href="mailto:jadmin@126.com">jadmin</a>
*/
public class ScriptFilenameFilter implements FilenameFilter {

private String suffix;

public ScriptFilenameFilter(String suffix) {
   this.suffix = suffix;
}

public boolean accept(File dir, String name) {
   if(name.endsWith(suffix)) {
    return true;
   }
   return false;
}

}


2.调用

String[] names = new java.io.File(Constans.SCRIPT_DIR).list(new ScriptFilenameFilter(".sql"));

这样就得到了一个文件名数组,注:Constans.SCRIPT_DIR是【目录】常量串

posted @ 2009-01-16 21:56 jadmin 阅读(89) | 评论 (0)编辑 收藏

使用笔记本,避免不了经常变换IP,如果是手动去每次都设置,太烦人了,下面推荐一款实用的绿色IP切换工具IPWhiz,使用起来非常方便,下面是截图:

PS:之前一直使用的是ASUS的公用程序Net4Switch,其实这一款也是很好用的IP切换工具,只不过我的瑞星更新到2009版后Net4Switch就出问题了,可能是软件之间冲突了吧...

posted @ 2008-12-30 20:38 jadmin 阅读(58) | 评论 (0)编辑 收藏

kk.jar的目录结构如下

+kk.jar
    +META-INF
        MANIFEST.MF
    +config
        database.properties

目标:读取database.properties中的参数信息

types=mysql
mysql.url=jdbc:mysql://127.0.0.1:3306/tjtz
mysql.user=root
mysql.password=root
mysql.driver=com.mysql.jdbc.Driver
mysql.maxCounts=5

代码如下:

/*
* @(#)JarFileReader.java Oct 9, 2008
*
* Copyright (c) 2008 by jadmin. All Rights Reserved.
*/

package file.jar;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* 从jar包中读取配置文件信息
*
* @author <a href="mailto:jadmin@yeah.net">jadmin</a>
* @version 1.00 Oct 9, 2008 1:10:44 AM
*/
public class JarReader {

public static void main(String[] args) {
   String jarPath = "C:\\Documents and Settings\\Administrator\\桌面\\新建文件夹\\kk.jar";
   String entryFile = "config/database.properties";
   doRead(jarPath,entryFile);
}

public static void doRead(String jarPath, String entryFile) {
   try {
    JarFile jarFile = new JarFile(jarPath);
    JarEntry entry = jarFile.getJarEntry(entryFile);
    InputStream input = jarFile.getInputStream(entry);
    process(input);
    jarFile.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
}

private static void process(InputStream input) {
   Properties p = new Properties();
   try {
    p.load(input);
    Set<?> set = p.keySet();
    for (Object name : set)
     System.out.println(name + "=" + p.getProperty((String) name));
   } catch (IOException e) {
    e.printStackTrace();
   }
}
}

运行结果如下:

posted @ 2008-10-09 01:49 jadmin 阅读(1735) | 评论 (0)编辑 收藏

  1.清浊并吞:水在流动时不管清水、浊水皆能加以并合,由此松下幸之助领悟出人或企业在成长、学习的过程中应去面对各种可能的挑战,而非去等待好的时机。因为,如果 任何事情皆要挑好天气、好机会、好环境的话,那终将只能留在原地,毫无机会。所以, “清浊并吞”即是告诉我们在任何环境下,皆需让自己有勇气下决心开始去学习,开始去做,就如同经济有景气、有不景气,人生亦本就有起有伏,重要的是当下的即知即行,而非等待和观望。


  2.随方亦圆:水在任何容器中皆可依容器之形状而呈现出不同形状的风貌,不管我们如何的摆弄它,它永远能展现出其最好的一面,人在学习、成长的环境中亦同,处逆境时应下更大的决心,处顺境时应更谦虚的学习,就像水的这种精神,在不同的环境中要能去适应环境、调适自己,去学习不同的东西和感受,给自己的人生或企业的未来下一个目标,然后去做它,不能自我设限,自我定型,而是不管在什幺情形下,皆能去适应它、去调适、去面对它、去克服这些环境,那终将有一番不同的成就。


  3.上波下静:水在表面上虽是波浪起伏,但在水面下却是平静无波的。松下幸之助由此领悟出,事业就是要做扎根的工作,根扎得稳,事业才会稳固,事业若建构于沙滩之上,则波浪一来必毁于瞬间。因此,人或企业应学习水的这种精神,努力的去做扎根的工作,怀着单一的思想,专注、持续的去学习、去做,一定会有所体会和成就,否则,随波逐流,没有定位,在三心二意与三分钟热度的情况下,终将一事无成。


  4.变化无穷:水存在着各种不同的型态,例如以水、水蒸气、雨水,冰块等不同的方式存在着,它在不同情况下会有不同的转化。松下幸之助又领悟出,做事业亦同,市场上的 变化无穷尽,遇到的人亦是形形色色的。但我们想想,为什幺有些事业做得那幺成功,有些人亦是那么成功?!成功的抉择到底何在呢?关键在于自己。因为,市场和人生乃是变化无穷的,需要有弹性去适应,就如同人在雨天时就应撑伞 ,企业在低潮、不景气时,就应调整策略,而非一成不变,僵化无以应变。事实上,人总是喜欢趋吉避凶,但奇怪的是却又喜欢听消极的事,因此,人的思想,企业文化乃是在面对变化无穷的环境时,能否成功的关键,挑战一定是有的,因难也一定是有的,但是成功的定律却是不变的,如何识人,如何保持弹性便是在面对各种状况时所应具备的。


  5.渗透扩散:水具有渗透、扩散的能力,这是许多东西所无法办到的能力。人生的成长、学习过程亦应像水一样,目标要不断的提升和提高,才能让自己有更多的收获;就如同水一样,泼到地上便马上让干的地面逐渐地变成湿的地面,不断的渗透、扩散。而设定目标的式是目标须较自己的能力高一些,方能不断自我挑战和成长。因为如果你有登峰攀顶的能力,为何要把目光停留在半山腰呢?


  6.久储必臭:水静置放着,一段时日后自然会发臭。松下幸之助体会到他经营这幺大的事业,如果没有让自己再去学习、成长,那幺一定会落伍,所以须学习水一样不断的去流动,而非静止在那边。故学习是无止境的,惟有不断的去学习、去努力,才有未来可言;对任一个想要成功的人或企业皆是如此,如果连松下幸之助这样的经营神都有这样的体会,那我们更应该努力的去学习。毕竟,人生或事业乃是不进则退的,因此须让自己像水一样,不断的去流动才会有量,才不致“久储臭”。


  7.急流澄清:水流很快的地方,水一定特别干净;就如同瀑布之头,因水流湍急,所以水一定是澄清的。松下幸之助亦因此体会到人生的成长不能像小的水流一样慢慢地流,他说慢慢的流虽不致发臭,但却不会澄澈,须像急流一样,水的力量大,又会清淯。因此在人生、事业的成长上要给自己压力,就如同急流而下的高速水流产生强大的力量一般,惟有透过自我要求,方能产生完成目标的力量。


  8.生存至宝:人若缺水则必无法生存,所以水是生存至宝。同样地,我们在自己的人生中,亦是扮演着很重要的角色;我们每一个人都是造物主最大的奇迹,因为在世界上,我们每一个人都是独一无二的,是没有任何人可以取代的。因此,我们自己须下最大的决心,对于自己的人生和目标有时间、计划性的努力,则必会完成自己的人生,成就一生的事业

posted @ 2008-09-19 22:09 jadmin 阅读(56) | 评论 (0)编辑 收藏

JTable是Swing编程中很常用的控件,这里总结了一些常用方法以备查阅.

一.创建表格控件的各种方式:
1) 调用无参构造函数.
JTable table = new JTable();
2) 以表头和表数据创建表格.
Object[][] cellData = {{"row1-col1", "row1-col2"},{"row2-col1", "row2-col2"}};
String[] columnNames = {"col1", "col2"};
   
JTable table = new JTable(cellData, columnNames);
3) 以表头和表数据创建表格,并且让表单元格不可改.
String[] headers = { "表头一", "表头二", "表头三" };
Object[][] cellData = null;

DefaultTableModel model = new DefaultTableModel(cellData, headers) {

public boolean isCellEditable(int row, int column) {
    return false;
}
};

table = new JTable(model);
二.对表格列的控制
1) 设置列不可随容器组件大小变化自动调整宽度.
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
2) 限制某列的宽度.
TableColumn firsetColumn = table.getColumnModel().getColumn(0);
firsetColumn.setPreferredWidth(30);
firsetColumn.setMaxWidth(30);
firsetColumn.setMinWidth(30);
3) 设置当前列数.
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int count=5;
tableModel.setColumnCount(count);
4) 取得表格列数
int cols = table.getColumnCount();
5) 添加列
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.addColumn("新列名");
6) 删除列
table.removeColumn(table.getColumnModel().getColumn(columnIndex));// columnIndex是要删除的列序号
三.对表格行的控制
1) 设置行高
table.setRowHeight(20);
2) 设置当前航数
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
int n=5;
tableModel.setRowCount(n);
3) 取得表格行数
int rows = table.getRowCount();

4) 添加表格行
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
tableModel.addRow(new Object[]{"sitinspring", "35", "Boss"});
5) 删除表格行
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
model.removeRow(rowIndex);// rowIndex是要删除的行序号
四.存取表格单元格的数据
1) 取单元格数据
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
String cellValue=(String) tableModel.getValueAt(row, column);// 取单元格数据,row是行号,column是列号
2) 填充数据到表格.
注:数据是Member类型的链表,Member类如下:
public class Member{
    // 名称
    private String name;
   
    // 年龄
    private String age;
   
    // 职务
    private String title;
}
填充数据的代码:
public void fillTable(List<Member> members){
DefaultTableModel tableModel = (DefaultTableModel) table
.getModel();
tableModel.setRowCount(0);// 清除原有行

// 填充数据
for(Member member:members){
    String[] arr=new String[3];
    arr[0]=member.getName();
    arr[1]=member.getAge();
    arr[2]=member.getTitle();
   
    // 添加数据到表格
    tableModel.addRow(arr);
}

// 更新表格
table.invalidate();
}
2) 取得表格中的数据
public List<Member> getShowMembers(){
List<Member> members=new ArrayList<Member>();

DefaultTableModel tableModel = (DefaultTableModel) table
.getModel();

int rowCount=tableModel.getRowCount();

for(int i=0;i<rowCount;i++){
    Member member=new Member();
   
    member.setName((String)tableModel.getValueAt(i, 0));// 取得第i行第一列的数据
    member.setAge((String)tableModel.getValueAt(i, 1));// 取得第i行第二列的数据
    member.setTitle((String)tableModel.getValueAt(i, 2));// 取得第i行第三列的数据
   
    members.add(member);
}

return members;
}
五.取得用户所选的行
1) 取得用户所选的单行
int selectRows=table.getSelectedRows().length;// 取得用户所选行的行数
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();

if(selectRows==1){
int selectedRowIndex = table.getSelectedRow(); // 取得用户所选单行

.// 进行相关处理
}
2) 取得用户所选的多行
int selectRows=table.getSelectedRows().length;// 取得用户所选行的行数
DefaultTableModel tableModel = (DefaultTableModel) table.getModel();

if(selectRows>1)
int[] selRowIndexs=table.getSelectedRows();// 用户所选行的序列

for(int i=0;i<selRowIndexs.length;i++){
    // 用tableModel.getValueAt(row, column)取单元格数据
    String cellValue=(String) tableModel.getValueAt(i, 1);
}
}
六.添加表格的事件处理
view.getTable().addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
    // 鼠标按下时的处理
}

public void mouseReleased(MouseEvent e) {
    // 鼠标松开时的处理
}

public void mouseEntered(MouseEvent e) {
    // 鼠标进入表格时的处理
}

public void mouseExited(MouseEvent e) {
    // 鼠标退出表格时的处理
}

public void mouseClicked(MouseEvent e) {
    // 鼠标点击时的处理
}
});

posted @ 2008-07-29 17:54 jadmin 阅读(69) | 评论 (0)编辑 收藏

MAGIC #1:
         一个印度人发现的,没有人可以在电脑的任何地方建立一个名为"CON"的文件夹 。微软公司的全体职员没有一个人能解释这是为什么;
这个的解释是在Windows里不能以设备名来命名文件或文件夹,aux、com1、prn、con、nul等,这些都是系统保留的名称

MAGIC #2:
         在电脑上照着这个做:1.新建一个空的文本文档       2.在里面输入"Bush hid the facts"(不要引号,可复制进去)   3.关闭文档,再打开文档。 发生了什么?
         这个我在做的时候试过把Bush改成其他英文名字,结果就会有点点不一样,大家们探索一下吧!
这个就不知道了

MAGIC #3:
         这是最酷也是最不可思议的一个。。。打开一个新的Word,在里面输入=rand (200, 99) ,之后按Enter。。。。自己看吧@ 这个据说连比尔盖茨也解释不清楚@

posted @ 2008-06-26 19:17 jadmin 阅读(61) | 评论 (0)编辑 收藏

第一步: 准备工作,建立个tabedit.html
里面的内容很简单, 建立个5X5的表格, 代码如下:

posted @ 2008-06-25 23:05 jadmin 阅读(70) | 评论 (0)编辑 收藏
本程序有两文件test.asp 和tree.asp 还有一些图标文件。

1. test.asp 调用类生成树代码如下

<%@ Language=VBScript %>
<html>
<head>
<link rel="stylesheet" href="tree.css">
<title>tree</title>
</head>
<!-- #include file="tree.asp" -->
<%

'========================================
' BUILDING A TREE PROGRAMATICALLY
'========================================
' This approach would be best suited for building
' dynamic trees using For..Next loops and such.

Set MyTree2 = New Tree
MyTree2.Top = 10
MyTree2.Left = 10
MyTree2.ExpandImage = "plus.gif"
MyTree2.CollapseImage = "minus.gif"
MyTree2.LeafImage = "webpage.gif"

' Notice the indentation used to reprensent the hierarchy
Set Node1 = MyTree2.CreateChild("script")
Set SubNode1 = Node1.CreateChild("server")
Set secSubNode1 = SubNode1.CreateChild("html")
secSubNode1.CreateChild "<A HREF=""http://127.0.0.1/"">asp</A>"
secSubNode1.CreateChild "<A HREF=""http://127.0.0.1/"">php</A>"
secSubNode1.CreateChild "<A HREF=""http://127.0.0.1/"">jsp</A>"

Set SubNode2 = Node1.CreateChild("os")
SubNode2.CreateChild "<A HREF=""#"">winnt</A>"
SubNode2.CreateChild "<A HREF=""#"">win2000</A>"

Set Node2 = MyTree2.CreateChild("Desktop")
Node2.CreateChild "<A HREF=""#"">Area Code Lookup</A>"
Node2.CreateChild "<A HREF=""#"">Arin Based Whois Search</A>"
Node2.CreateChild "<A HREF=""#"">World Time Zone Map</A>"

MyTree2.Draw()

Set MyTree2 = Nothing

%>

</BODY>
</HTML>


2. tree.asp 类的定义 代码如下

<%

Dim gblTreeNodeCount:gblTreeNodeCount = 1

Class TreeNode

Public Value
Public ExpandImage
Public CollapseImage
Public LeafImage
Public Expanded
Private mszName
Private mcolChildren
Private mbChildrenInitialized

Public Property Get ChildCount()
ChildCount = mcolChildren.Count
End Property

Private Sub Class_Initialize()
mszName = "node" & CStr(gblTreeNodeCount)
gblTreeNodeCount = gblTreeNodeCount + 1

mbChildrenInitialized = False
Expanded = False
End Sub

Private Sub Class_Terminate()
If mbChildrenInitialized And IsObject(mcolChildren) Then
mcolChildren.RemoveAll()
Set mcolChildren = Nothing
End If
End Sub

Private Sub InitChildList()
Set mcolChildren = Server.CreateObject("Scripting.Dictionary")
mbChildrenInitialized = True
End Sub

Private Sub LoadState()
If Request(mszName) = "1" Or Request("togglenode") = mszName Then
Expanded = True
End If
End Sub

Public Function CreateChild(szValue)

If Not mbChildrenInitialized Then InitChildList()

Set CreateChild = New TreeNode
CreateChild.Value = szValue
CreateChild.ExpandImage = ExpandImage
CreateChild.CollapseImage = CollapseImage
CreateChild.LeafImage = LeafImage

mcolChildren.Add mcolChildren.Count + 1, CreateChild

End Function

Public Sub Draw()

LoadState()

Response.Write "<table border=""0"">" & vbCrLf
Response.Write "<tr><td>" & vbCrLf

If Expanded Then
Response.Write "<a href=""javascript:collapseNode('" & mszName & "')""><img src=""" & CollapseImage & """ border=""0""></a>" & vbCrLf
ElseIf Not mbChildrenInitialized Then
Response.Write "<img src=""" & LeafImage & """ border=0>" & vbCrLf
Else
Response.Write "<a href=""javascript:expandNode('" & mszName & "')""><img src=""" & ExpandImage & """ border=""0""></a>" & vbCrLf
End If

Response.Write "</td>" & vbCrLf
Response.Write "<td>" & Value & "</td></tr>" & vbCrLf

If Expanded Then
Response.Write "<input type=""hidden"" name=""" & mszName & """ value=""1"">" & vbCrLf

If mbChildrenInitialized Then
Response.Write "<tr><td> </td>" & vbCrLf
Response.Write "<td>" & vbCrLf

For Each ChildNode In mcolChildren.Items
ChildNode.Draw()
Next

Response.Write "</td>" & vbCrLf
Response.Write "</tr>" & vbCrLf
End If
End If

Response.Write "</table>" & vbCrLf

End Sub

End Class


Class Tree

Public Top
Public Left
Public ExpandImage
Public CollapseImage
Public LeafImage
Private mszPosition
Private mcolChildren

Public Property Let Absolute(bData)
If bData Then mszPosition = "absolute" Else mszPosition = "relative"
End Property

Public Property Get Absolute()
Absolute = CBool(mszPosition = "absolute")
End Property

Private Sub Class_Initialize()
Set mcolChildren = Server.CreateObject("Scripting.Dictionary")
mnTop = 0
mnLeft = 0
mszPosition = "absolute"
End Sub

Private Sub Class_Terminate()
mcolChildren.RemoveAll()
Set mcolChildren = Nothing
End Sub

Public Function CreateChild(szValue)

Set CreateChild = New TreeNode

CreateChild.Value = szValue
CreateChild.ExpandImage = ExpandImage
CreateChild.CollapseImage = CollapseImage
CreateChild.LeafImage = LeafImage

mcolChildren.Add mcolChildren.Count + 1, CreateChild

End Function

Public Sub LoadTemplate(szFileName)
Dim objWorkingNode
Dim colNodeStack
Dim fsObj, tsObj
Dim szLine
Dim nCurrDepth, nNextDepth

Set colNodeStack = Server.CreateObject("Scripting.Dictionary")
Set fsObj = CreateObject("Scripting.FileSystemObject")
Set tsObj = fsObj.OpenTextFile(szFileName, 1)

nCurrDepth = 0
While Not tsObj.AtEndOfLine
nNextDepth = 1
szLine = tsObj.ReadLine()

If nCurrDepth = 0 Then
Set objWorkingNode = CreateChild(Trim(szLine))
nCurrDepth = 1
Else
While Mid(szLine,nNextDepth,1) = vbTab Or Mid(szLine,nNextDepth,1) = " "
nNextDepth = nNextDepth + 1
WEnd

If nNextDepth > 1 Then szLine = Trim(Mid(szLine,nNextDepth))

If szLine <> "" Then
If nNextDepth > nCurrDepth Then
If colNodeStack.Exists(nCurrDepth) Then
Set colNodeStack.Item(nCurrDepth) = objWorkingNode
Else
colNodeStack.Add nCurrDepth, objWorkingNode
End If

Set objWorkingNode = objWorkingNode.CreateChild(szLine)

nCurrDepth = nCurrDepth + 1
ElseIf nNextDepth <= nCurrDepth Then

If nNextDepth > 1 Then

nNextDepth = nNextDepth - 1
While Not colNodeStack.Exists(nNextDepth) And nNextDepth > 1
nNextDepth = nNextDepth - 1
WEnd

Set objWorkingNode = colNodeStack.Item(nNextDepth)
Set objWorkingNode = objWorkingNode.CreateChild(szLine)

nNextDepth = nNextDepth + 1
Else
Set objWorkingNode = CreateChild(szLine)
End If

nCurrDepth = nNextDepth
End If
End If

End If
WEnd

tsObj.Close()
Set tsObj = Nothing
Set fsObj = Nothing

colNodeStack.RemoveAll()
Set colNodeStack = Nothing

End Sub


Public Sub Draw()

AddClientScript()

Response.Write "<div id=""treectrl"" style=""left: " & Left & "px; top: " & Top & "px; position: " & mszPosition & ";"">" & vbCrLf
Response.Write "<form name=""treectrlfrm"" action=""" & Request.ServerVariables("SCRIPT_NAME") & """ method=""get"">" & vbCrLf
Response.Write "<table border=""0"">" & vbCrLf
Response.Write "<tr><td>" & vbCrLf

For Each ChildNode In mcolChildren.Items
ChildNode.Draw()
Next

Response.Write "</td></tr>" & vbCrLf
Response.Write "</table>" & vbCrLf

Response.Write "<input type=""hidden"" name=""togglenode"" value="""">" & vbCrLf
Response.Write "</form>" & vbCrLf
Response.Write "</div>" & vbCrLf

End Sub

Private Sub AddClientScript()
%>
<script language="JavaScript">

function expandNode(szNodeName)
{
if(document.layers != null) {
document.treectrl.document.treectrlfrm.togglenode.value = szNodeName;
document.treectrl.document.treectrlfrm.submit();
}
else {
document.all["treectrlfrm"].togglenode.value = szNodeName;
document.all["treectrlfrm"].submit();
}
}

function collapseNode(szNodeName)
{
if(document.layers != null) {
document.treectrl.document.treectrlfrm.elements[szNodeName].value = -1;
document.treectrl.document.treectrlfrm.submit();
}
else {
document.treectrlfrm.elements[szNodeName].value = -1;
document.treectrlfrm.submit();
}
}

</script>
<%
End Sub

End Class

%>
posted @ 2008-06-23 15:04 jadmin 阅读(100) | 评论 (0)编辑 收藏
  尽管对于现如今的带宽来说,网页文件那仅以K来算的大小实在是微不足道,但如何将这以K来计算的网页文件精简到最小还是网页设计师们所应该考虑的问题之一。

  众所周之,在不影响整个网页构架与功能的情况下,网页文件越小越好,因为更小的网页文件有利于浏览器对网页的解释时间缩到更短,自然访客也就不用面临等待网页缓慢呈现的烦躁了,这一点对于那些带宽少网速慢的用户犹为明显。试想一下,你会是希望打开一个网站的时候整个站点马上呈现在你面前呢?还是喜欢花上十几秒甚至是几分钏来看整个站点一点一点的被浏览器解释出来呢?

  在Table布局的时代,代码无数次的随着表格在页面里重复,致使整个网页文件变得臃肿无比,代码的可读性也降到最低,浏览器的解释时间自然也增加了不少。而自从DIV+CSS的布局替代Table布局之后,这一切都得到了极大的改善,让Table回归到它原本用于显示数据的位置上去,而布局就交给DIV+CSS来做,这样代码的可读性与复用性都得到了提高,而DIV +CSS更为重要的一点就是将网页文件的表现与结构区分开来,再也不用为了表现而去改动整个网页文件的结构了。

  即使DIV+CSS的布局方式将以前Table布局时代码的臃肿降到了最低,但对于网页设计师来说,如何将网页文件的大小控制到最小是永远值得探索和追求的一个问题。

  看如下一段代码:

  #header {
   margin-top:10px;
   margin-right:15px;
   margin-bottom:10px;
   margin-left:15px;
   backgroung-color:#333333;
   background-images:url(Images/header.jpg);
   }


  这样的一段CSS代码,在条理上很清晰,结构也很明了,可读性很强,可是这样的一段代码却没有做精简,也就是说它是最原始的CSS代码,下面看精简后的代码:

  #header {
   margin:10px 15px;
   backgroung:#333 url(Images/header.jpg);
   }


  在CSS中有复合属性这一说法,也就是说可以将很多属性参数整合在一起的,比如说上面的“margin-top; margin-right; margin-bottom; margin-left;”可以整合成一个“margin”属性,然后为其配上参数。
  

  通过这一点,我们就可以在原始CSS代码的基本上将代码进一步的精简。而且这样写的结构也合理,可读性也同样强。可是对于要精简到彻底来说,这还不够。为了让这段CSS代码的结构明了,我们用上了空格换行等占用空间的东西,如果将这些占用空间的去掉呢?

  #header{margin:10px 15px;background:#333 url(Images/header.jpg);}


  只这一句就替代了上面的一段代码,这样代码就已经精简到了最大化,当然,并不推荐所有人都这样写,这样写的CSS代码在可读性上要远远差于段落式的写法,除非你对自己写的代码有完全掌握的信心。

  在同一个站点的CSS文件中,不可避免的会出现不同的ID或Class却有一部分相同的属性,如果将这些ID或Class逐个分开来写的话,在CSS文件里无疑会生成重复代码,而我们要尽量的精简CSS文件的大小,那么“消灭”这部分重复的代码就变得势在必行。
  

  看下面一段CSS代码:

  #header{margin:10px 15px;background:#333 url(Images/header.jpg);}
  #content{margin:10px 15px;padding:10px;background:#999;}
  #copyright{margin:10px 15px;border:1px solid #f00;}


  在上面的三个ID中都有一个相同的属性“margin:10px 15px;”,如果就这样分开来写的话,这三个ID之间将保持各自独立的关系,但却生成了重复的代码,而我们可以将其写成如下格式:

  #header,#content,#copyright{margin:10px 15px;}
  #header{background:#333 url(Images/header.jpg);}
  #content{padding:10px;background:#999;}
  #copyright{border:1px solid #f00;}



  将上面的ID换成Class也是一样的。这样写我们就成功的将重复代码“消灭”掉了。但是如果这里具有相同的属性的ID或Class过多的话,难免会造成代码可读性降到很低很低,所以除此之外当具有相同属性的都是Class时还有另外的一种写法:


  .main{margin:10px 15px;}
  .header{background:#333 url(Images/header.jpg);}
  .content{padding:10px;background:#999;}
  .copyright{border:1px solid #f00;}


  当然这种写法时,调用时的写法也与平常不一样。



  这样的写法同样可以达到效果,并且也不会再怕具有相同属性的Class多而造成代码可读性差的问题,但值得注意的一点就是,这种写法对于ID是无效的,不管其中是存在一个ID或者全部都是ID,都将造成这段代码的无效。
posted @ 2008-06-23 15:01 jadmin 阅读(72) | 评论 (0)编辑 收藏
css对文字的布局上没有靠容器底部对齐的参数,目前使用的一个不错的方法也比较好.就是用position属性来解决,看下面的代码,用position的相对和绝对定位功能也轻松的实现了,文字靠近div低部对齐,并且靠近的距离还可以精确到像素,自己可以调节,是不是很不错呢?

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>无标题文档</title>
<style type="text/css">
#txt{

height:300px;
width:300px;
border:1px solid #333333;
text-align:center;
position:relative

}
#txt p{
position:absolute;
bottom:0px;
padding:0px;
margin:0px
}
</style>
</head>

<body>
<div id=txt>
<p>aadsad</p>
</div>
</body>
</html>

posted @ 2008-06-23 15:00 jadmin 阅读(95) | 评论 (0)编辑 收藏

apache的commons项目下有个email子项目,它对JavaMail API进行了封装,用起来特变方便。在开始之前,需要做以下准备:

1、JavaMail API

添加JavaMail API里的mail包到CLASSPATH里,JavaMail API下载地址:http://java.sun.com/products/javamail/downloads/index.html

2、commons email

下载地址:http://www.apache.org/dist/commons/email/

工程目录结构如下:

以下给出两个简单示例程序

// SendMail.java 使用SimpleEmail发邮件

package com.apache.commons.email.demo;

import org.apache.commons.mail.SimpleEmail;

public class SendMail
{
    public static void main ( String[] arg ) throws Exception
    {
    // 使用SimpleEmail对于中文内容,可能会产生乱码
        SimpleEmail email = new SimpleEmail ( );

        // SMTP服务器名
        email.setHostName ( "smtp.163.com" );
        // 登陆邮件服务器的用户名和密码
        email.setAuthentication ( "peki", "123456" );
        // 接收人
        email.addTo ( "jstio@qq.com", "曦勤" );
        // 发送人
        email.setFrom ( "peki@163.com", "小陈" );
        // 标题
        email.setSubject ( "Test message" );
        // 邮件内容
        email.setMsg ( "This is a simple test of commons-email<br>我是小陈" );
        // 发送
        email.send ( );
       
        System.out.println ( "Send email successful!" );

    }
}

收信结果如下:

This is a simple test of commons-email<br>????

有乱码产生,并且html内容没有正常显示

// MailSender.java   使用HtmlEmail发邮件

package com.apache.commons.email.demo;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;

public class MailSender {


public static void main(String[] args) {
   // 不要使用SimpleEmail,会出现乱码问题
   HtmlEmail email = new HtmlEmail();
   try {
    // 这里是SMTP发送服务器的名字:,163的如下:
    email.setHostName("smtp.163.com");
    // 字符编码集的设置
    email.setCharset("gbk");
    // 收件人的邮箱
    email.addTo("jstio@qq.com");
    // 发送人的邮箱
    email.setFrom("peki@163.com", "小陈");
    // 如果需要认证信息的话,设置认证:用户名-密码。分别为发件人在邮件服务器上的注册名称和密码
    email.setAuthentication("peki", "123456");
    email.setSubject("下午3:00会议室讨论,请准时参加");
    // 要发送的信息,由于使用了HtmlEmail,可以在邮件内容中使用HTML标签
    email.setMsg("下午3:00会议室讨论,请准时参加<BR>呵呵~!");
    // 发送
    email.send();
   
    System.out.println ( "邮件发送成功!" );
   } catch (EmailException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    System.out.println ( "邮件发送失败!" );
   }

}

}

收信结果如下:

下午3:00会议室讨论,请准时参加
呵呵~!
没有产生乱码,html内容正常显示
以上程序尽供参考,如需要在开发中使用,还得深入研究
posted @ 2008-06-21 22:12 jadmin 阅读(3462) | 评论 (0)编辑 收藏

1、我们在做验证码的时候往往由于要反作弊,验证有时故意加入多的干扰因素,这时验证码显示不很清楚,用户经常输入错误。这样不但要重新刷新页面,导致用户没有看清楚验证码而重填而不是修改,而且如果没有用session保存下用户输入的其它数据的话(如姓名),用户刚刚输入的内容也不存在了,这样给用户造成不好的体验。

2、本例在原有验证方式基础之上增加一段js,通过xmlhttp来获取返回值,以此来验证是否有效,这样即使用户浏览器不支持js,也不会影响他的正常使用了。

3、为了防止作弊,当用户连接3次输入错误时则重载一下图片,这样也利于用户因为图片上的验证码辨认不清而使其终无法输入正确。

4、本例还特别适合检验用户名是否有效,只要从后台做个sql查询,返回一个值或是xml即可。(这种例子太多 ,就在此不赘述了)。

5、本例的优点在于非常方便用户输入,而且减少对服务器端的请求,可以说既改善用户体验而且略会节省带宽成本,但相应地要在页面上增加一段JavaScript代码,在目前网速越来越快人们要求便捷舒适的今天,似乎我们更应注意提供给用户良好的使用感受。

代码如下:

1、img.jsp,输入主页面

posted @ 2008-06-18 21:29 jadmin 阅读(180) | 评论 (1)编辑 收藏

JSP分页共设计了三个类:SplitPageVo 、PageVo、SqlVo

以上三个类请参考本博客的

JSP分页类一:SplitPageVo       JSP分页类二:SqlVo          JSP分页类三:PageVo

三篇文章

核心为一条SQL语句:

SELECT * FROM              

             (SELECT TOP 每页显示条数 * FROM

                     (SELECT TOP 每页显示数量x当前页 * FROM 表名)    表变量1

                             ORDER BY 排序字段 DESC)   表变量2

                                         ORDER BY   排序字段

如:

SELECT * FROM

              (SELECT TOP 10 * FROM

                           (SELECT TOP 3x10 * FROM shop) x

                                     ORDER BY id DESC) y

                                                   ORDER BY id

使用简要说明:

//********************************************使~~~~用~~~~说~~~~明*****************************************
/**
...............................................................
在SERVELET里:
第一步:获取当前页码
if(request.getParameter("page")!=null){
    page=Integer.parseInt(request.getParameter("page"));
    }else{
    page=1 ;
   }
第二步:创建跳转对象
    SplitPageVo vo=new SplitPageVo()
第三步:设置SQL语句SqlVo,URL,当前页面page
    vo.setPageVo(page,10);
    vo.setSqlVo("shop", "1=1", "id", "asc")
    vo.setUrl("/servlet/Shop.do?action=0")
第四步:传入BIZ
    TransOrderBiz biz=new TransOrderBiz()
    vo=biz.getAllOrders(vo)
第五步:页面转向
request.setAttribute("OrderList",vo);
url="/tpl/tplproject/OrderList.jsp";
request.getRequestDispatcher(url).forward(request,response);
..................................................................
在BIZ里:
public class TransOrderBiz {
public SplitPageVo getAllOrders(SplitPageVo vo){
DBConnection dbc = new DBConnection();
if(dbc.getConnect()){
   Connection conn = dbc.getConn();
   try{
   BU_TRANS_ORDERDAO dao=new BU_TRANS_ORDERDAO();
   vo=dao.findByAll(conn);
   }catch(Exception e){
    e.printStackTrace();
   }finally{
    try{
     conn.close();
    }catch(Exception ex){
     ex.printStackTrace();
    }
   }
}else{
   System.out.println("数据库连接失败!");
}
return vo;
}
}

..................................................................
在DAO里:
public SplitPageVo findByAll(Connection conn ,SplitPageVo vo){
    Vector v=new Vector();
    StringBuffer sqlStr = null;
    PreparedStatement ps=null;
    Connection _conn=null;
    ResultSet rs=null;
    try{
     _conn=conn;
     $$$$$$$$$$$$$$$$$$$$$$$$$~~核~~心~~部~~分~~$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
      vo.getCount(_conn)//获取条数
     vo.setPageVo(vo.getPageVo().getNowPage(),vo.getPageVo().getCount(),vo.getPageVo().getPreList())//设置PageVo
     int top1 = vo.getTop1();
     int top2 = vo.getTop2();
     String strSql=vo.getSqlVo().toAllSql(top1,top2)//取出SQL语句
     $$$$$$$$$$$$$$$$$$$$$$$$$$~~核~~心~~部~~分~~$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
     ps=_conn.prepareStatement(strSql);
     rs=ps.executeQuery();
     while(rs.next()){
      BU_TRANS_ORDERVO vo=new BU_TRANS_ORDERVO();
      vo.setTO_ID(rs.getLong("TO_ID"));
                   。。。。。。。。。。。。。。。。。
                   。。。。。。。。。。。。。。。。。
                   。。。。。。。。。。。。。。。。。
      vo.setTO_PRICE(rs.getDouble("TO_PRICE"));
      v.addElement(vo);
     }
     vo.setData(v)//放入数据
    }
    catch(Exception e){
     e.printStackTrace();
    }
    finally{
     try{
      if (ps!=null){
       ps.close();
       ps=null;
      }
     
     }catch(Exception e){
      e.printStackTrace();
     }
    }
    return vo;//返回
   }

   ................................................................................
   在JSP里:
   SplitPageVo splitPageVo;
   splitPageVo=(SplitPageVo)request.getAttribute("OrderList");
   List l=splitPageVo.getData();
   if(l!=null && l.size()>0 ){
   for(int i=0;i<l.size() && i<l.size() ;i++){
   ComplOrderVo vo=(ComplOrderVo)l.get(i);
            。。。。。。。。。。。。。。。。。。。
            。。。。。。。。。。。。。。。。。。。
            。。。。。。。。。。。。。。。。。。。
   }
            。。。。。。。。。。。。。。。。。。。
            。。。。。。。。。。。。。。。。。。。
            。。。。。。。。。。。。。。。。。。。
    <td >
<%=splitPageVo.splitPage(splitPageVo)%> //分页部分
</td>
         。。。。。。。。。。。。。。。。。。。
            。。。。。。。。。。。。。。。。。。。
            。。。。。。。。。。。。。。。。。。。
   ................................................................................
*/

posted @ 2008-06-18 15:48 jadmin 阅读(56) | 评论 (0)编辑 收藏
很多时候我们需要提供这样的功能给访问者:当访问者点击页面中的缩略图时,其对应的全尺寸图片将显示在一个新的弹出窗口中供访问者查看。

实现此功能的最简单作法是用以下HTML代码创建一个图像链接:
<a href="fullsize.jpg" target="_blank"><img src="small.jpg"></a>
其中<a>标记的href属性指定全尺寸图片的URL,target属性设置为_blank指定在新窗口中显示该图片;<img>标记的src属性指定缩略图的URL。

如果我们想对显示全尺寸图片的窗口的外观进行某些控制(比如希望弹出窗口的高度、宽度能与全尺寸图片的大小匹配时),则可调用 window.open 方法,该方法接收三个参数,分别指定要打开文件的URL,窗口名及窗口特性,在窗口特性参数中可指定窗口的高度、宽度,是否显示菜单栏、工具栏等。以下代码将显示全尺寸图片在一个没有工具栏、地址栏、状态栏、菜单栏,宽、高分别为400、350的窗口中:
<a href="fullsize.jpg"
onClick="window.open(this.href,'', 'height=350,width=400,toolbar=no,location=no,
status=no,menubar=no');return false"><img src="small.jpg"></a>

这里就提出了个问题,如果所有全尺寸图片都具有统一的大小(比如都是400x350),那么以上代码适用于所有的缩略图片链接(只是href属性指向的全尺寸图片文件不同)。但如果全尺寸图片的大小并不统一,还用以上代码则我们需要先取得每幅全尺寸图片的大小,然后在window.open方法的窗口特性参数中一一设置height和width为正确的值,在图片数量较多的情况下,这显然效率太低了。那么是否有一劳永逸的方法,即让弹出窗口能自动适应要显示图片的大小?通过研究,发现可以使用 DHTML 中的 Image 对象来达到我们的目的,Image 对象可动态装载指定的图片,通过读取其 width 和 height 属性即能获得装入图片的大小,以此来设置弹出窗口的大小,即可实现自适应图片大小的弹出窗口了。下面即是实现代码:

<script language="JavaScript" type="text/JavaScript">
<!--
var imgObj;
function checkImg(theURL,winName){
  
// 对象是否已创建
  if (typeof(imgObj) == "object"){
    
// 是否已取得了图像的高度和宽度
    if ((imgObj.width != 0) && (imgObj.height != 0))
      
// 根据取得的图像高度和宽度设置弹出窗口的高度与宽度,并打开该窗口
      // 其中的增量 20 和 30 是设置的窗口边框与图片间的间隔量
       OpenFullSizeWindow(theURL,winName, ",width=" + (imgObj.width+20) + ",height=" + (imgObj.height+30));
    
else
      
// 因为通过 Image 对象动态装载图片,不可能立即得到图片的宽度和高度,所以每隔100毫秒重复调用检查
       setTimeout("checkImg('" + theURL + "','" + winName + "')", 100)
   }
}

function OpenFullSizeWindow(theURL,winName,features) {
  
var aNewWin, sBaseCmd;
  
// 弹出窗口外观参数
   sBaseCmd = "toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,";
  
// 调用是否来自 checkImg
  if (features == null || features == ""){
    
// 创建图像对象
     imgObj = new Image();
    
// 设置图像源
     imgObj.src = theURL;
    
// 开始获取图像大小
     checkImg(theURL, winName)
   }
  
else{
    
// 打开窗口
     aNewWin = window.open(theURL,winName, sBaseCmd + features);
    
// 聚焦窗口
     aNewWin.focus();
   }
}
//-->
</script>


使用时将上面的代码放在网页文档的<head></head>标记对中,然后在链接的点击事件中调用 OpenFullSizeWindow函数,如<a href="fullsize.jpg" onClick= "OpenFullSizeWindow(this.href,'','');return false"><img src="small.jpg"> </a>即可。
以上代码在IE 5.x-6.0中测试通过。
posted @ 2008-06-07 02:12 jadmin 阅读(85) | 评论 (0)编辑 收藏

把文字替换成图片
首先把图片复制到 剪贴板中,然后打开替换对话框,在“查找内容”框中输入将被替换的文字,接着在 “替换为”框中输入“^c”(注意:输入的一定要是半角字符,c要小写),单击替换 即可。说明:“^c”的意思就是指令Word XP以剪贴板中的内容替换“查找内容”框中的内 容。按此原理,“^c”还可替换包括回车符在内的任何可以复制到剪贴板的可视内容,甚至Excel表格。

三招去掉页眉那条横线
1、在页眉中,在“格式”-“边框和底纹”中设置表格和边框为“无”,应用于“段落”
2、同上,只是把边框的颜色设置为白色(其实并没有删的,只是看起来没有了,呵呵)
3、在“样式”栏里把“页眉”换成“正文”就行了——强烈推荐!
会多出--(两个横杠) 这是用户不愿看到的,又要多出一步作删除--
解决方法:替换时在前引号前加上一个空格 问题就解决了

插入日期和时间的快捷键
Alt+Shift+D:当前日期
Alt+Shift+T:当前时间

批量转换全角字符为半角字符
首先全选。然后“格式”→“更改大小写”,在对话框中先选中“半角”,确定即可


Word启动参数简介
单击“开始→运行”命令,然后输入Word所在路径及参数确定即可运行,如“C:\ PROGRAM FILES \MICROSOFT Office \Office 10\ WINWord.EXE /n”,这些常用的参数及功能如下:
/n:启动Word后不创建新的文件。
/a:禁止插件和通用模板自动启动。
/m:禁止自动执行的宏。
/w:启动一个新Word进程,独立与正在运行的Word进程。
/c:启动Word,然后调用Netmeeting。
/q:不显示启动画面。
另外对于常需用到的参数,我们可以在Word的快捷图标上单击鼠标右键,然后在“目标”项的路径后加上该参数即可。

快速打开最后编辑的文档
如果你希望Word在启动时能自动打开你上次编辑的文档,可以用简单的宏命令来完成:
(1)选择“工具”菜单中的“宏”菜单项,单击“录制新宏”命令打开“录制宏”对话框;
(2)在“录制宏”对话框中,在“宏名”输入框中输入“autoexec”,点击“确定”;
(3)从菜单中选择“文件”,点击最近打开文件列表中显示的第一个文件名;并“停止录制”。保存退出。下次再启动Word时,它会自动加载你工作的最后一个文档。

格式刷的使用
1、设定好文本1的格式。
2、将光标放在文本1处。
3、单击格式刷按钮。
4、选定其它文字(文本2),则文本2的格式与文本1 一样。
若在第3步中单击改为双击,则格式刷可无限次使用,直到再次单击格式刷(或按Esc键)为止。

删除网上下载资料的换行符(象这种“↓”)
在查找框内输入半角^l(是英文状态下的小写L不是数字1),在替换框内不输任何内容,单击全部替换,就把大量换行符删掉啦。

选择性删除文件菜单下的最近使用的文件快捷方式。
工具→选项→常规把“列出最近使用文件数改为0”可以全部删除,若要选择性删除,可以按ctrl+Alt+ -三个键,光标变为一个粗减号后,单击文件,再单击要删除的快捷方式就行了。

建立一个矩形选区:
一般的选区建立可用鼠标左键,或用shift键配合pgup、pgdn、home、end、箭头等功能键,当复制一个规则的矩形区域时,可先按住Alt键,然后用鼠标左键来选。我一般用此来删除段首多余的成块的空格。大家试一试*^_^*

将字体快速改为上标或下标的方法:
本人在一次无意间发现了这个方法,选定你要下标的字,然后在英文状态下按住Ctrl,再按一下BASKSPACE旁的+/=的键,就可以了。上标只要在按Ctrl的同时也按住Shift,大家可以试试。

让Word表格快速一分为二
将光标定位在分开的表格某个位置上,按下“Ctrl+Shift+Enter”组合键。这时你就会发现表格中间自动插入一个空行,这样就达到了将一个表格一分为二的目的。

用Word来拆字
首先点击“工具/自定义/命令/分解图片”,按住鼠标左键把它拖放到工具栏任意位置即可;然后点击“插入/图片/艺术字”,例如输入空心字“心”,选择该“心”字剪切,在选择性粘贴中选图片(Windows图元文件),选中该字,点击工具栏中的“分解图片”按钮,这样可以选择“心”中的任意笔画进行一笔一画的拆分了。

快速删除段前段后的任意多个空格
选定这些段段落,单击居中按钮,然后再单击原来的那种对齐方式按钮(如果原来是居中对齐的,先单击其它对齐方式按钮,再单击居中按钮就行了),是不是这些空格全不见了?


只要打开WORD新建一个空文档的时候,出现的不是空的文档,而是我以前打的一份文档
首先:将资源管理器设置为显示所有文件和文件夹;
然后:
C:\Documents and Settings\Administrator\Application Data\Microsoft\Templates文件夹下将所有Normal.doc文件删掉;
然后:OK(XP系统)

快速输入平方的方法
先输入2,然后选重后,按ctrl加shift加+就可以了.

WORD中表格的选择性录入
1.设置好表格,选定表格-视图-工具-窗体-插入下拉型窗体域
2.输入数据,完成
3.点击锁按钮,保护,输入完后再点击进行其它的输入.

标点符号的全角/半的转换用:Ctrl+.
数字字母的全角/半的转换用:Shift+空格

轻松了解工具栏按钮的作用
按下“shift+F1”键,鼠标指针旁多了一个“?”号,想知道哪个按钮
的作用,就用鼠标单击哪个。

要经常在文档中插入自己公司的信息
公司名称
公司住址
联系电话
联系人姓名
QQ号码
可以先选定这些内容,再单击工具→自动更正→在替换框中输入标记名称(如“公司信息”)→添加→确定,以后凡是在文档中要用到这个信息的地方键入“公司信息”(不要引号)这几个字后就自动替换成:
公司名称
公司住址
联系电话
联系人姓名
QQ号码
说明:有些输入法不支持这个功能,键入标记名称后要按一下空格才行。

快速换页的方法
双击某页的右下脚,光标即可定位在那里,然后按回车直到换页。ctrl+回车点插入按纽,分隔符,选中分页符,然后确认就OK了 !!!

表格的简单调整宽度
鼠标放在表格的右边框上带鼠标变成可以调整大小的时候
双击
根据表格内的内容调节表格大小

代替金山词霸
点工具——语言——翻译,在右边出现的搜索框中输入要查的单词,回车就可以翻译了。可以选择英语翻成中文或中文翻成英语。
第一次使用可能要安装。

[Alt]键实现标尺的精确定位
如果你经常使用水平标尺来精确定位标签、页边框、首字缩进及页面对象的位置,那么你点击标尺设置页边框或标签时,您只可以将其设置为1字符或2字符,但不能设为1.5字符!要想设置更为精确的度量单位(例如百分之几字符),在按住[Alt]键的同时,点击并移动标尺或边框,此时标尺将用数字精确显示出当前的位置为百分之几字符位置。

用“记事本”去除格式
网页上COPY下来的东西往往都是有网格的,如果直接粘贴在WORD中会杂乱无章。先粘贴到记事本当中,再粘贴到WORD中,就可以去除网格等格式,再全选选择清除格式,居中再取消居中即可取消所有格式。可以直接在WORD中进行:(菜单)编辑/选择性粘贴……/无格式文本/确定。这样省事多了。

快速将文档转换成图片
先把欲想转换的文档保存退出.如:保存在桌面
然后新建一个文件.把想转换的文档(鼠标左建按住该文档不放)直接施放在页面上

恢复office的默认设置
比如不小心把word设置乱了(如删了菜单栏等等).
查找normal.dot直接删除.
下一次启动word会恢复默认值.

让Word只粘贴网页中的文字而自动去除图形和版式
方法一、选中需要的网页内容并按“Ctrl+C”键复制,打开Word,选择菜单“编辑”→“选择性粘贴”,在出现的对话框中选择“无格式文本”。
方法二、选中需要的网页内容并按“Ctrl+C” 键复制,打开记事本等纯文本编辑工具,按“Ctrl+V”键将内容粘贴到这些文本编辑器中,然后再复制并粘贴到Word中。

ctrl+alt+f可以输入脚注
这个对于经常写论文的朋友应该有点帮助。

将阿拉伯数字转换成中文数字或序号
1、先输入阿拉伯数字(如1234),全选中,单击“插入/数字/数字类型(壹、贰……)/确定”,即变为大写数字(如壹仟贰佰叁拾肆),会计朋友非常适用。
2、其他像一千二百三十四,甲、乙……,子、丑……,罗马数字等的转换,可参考上法。

Word中的常用快捷键吧
“字体”对话框     Ctrl+D
选择框式工具栏中的“字体”框     Ctrl+Shift+F
加粗 Ctrl+B
倾斜 Ctrl+I
下划线Ctrl+U
“上标”效果     Ctrl+Shift+=
“下标”效果     Ctrl+=
“关闭”命令     Ctrl+W
Word快捷键一览表
序号       快捷键CTRL+       代表意义
1…………Z…………撤消
2…………A…………全选
3…………X…………剪切
4…………C…………复制
5…………V…………粘贴
6…………S…………保存
7…………B…………加粗
8………… Q…………左对齐
9…………E…………据中
10…………R…………右对齐
11…………]…………放大
22…………[…………缩小
12…………N…………新建文档
13…………I…………字体倾斜
14…………W…………退出
15…………P…………打印
16…………U…………下划线
17…………O…………打开
18…………k…………插入超级连接
19…………F…………查找
20…………H…………替换
21…………G…………定位
23…Ctrl+Alt+L……带括号的编号
24…Ctrl+Alt+.________…
25…Alt+数字………区位码输入
26…Ctrl+Alt+Del………关机
27…Ctrl+Alt+Shift+?……¿
28…Ctrl+Alt+Shift+!……¡
29…Alt+Ctrl+E……………?
30…Alt+Ctrl+R……………®
31…Alt+Ctrl+T……………™
32…Alt+Ctrl+Ctrl…………©
33……Ctrl+D……………格式字体
34……Ctrl+Shift+= ………上标
35……Ctrl+=………………下标
36……Ctrl+Shift+>……放大字体
37……Ctrl+Shift+< ……缩小字体
38……Alt+Ctrl+I………打印预览
39……Alt+Ctrl+O………大刚示图
40……Alt+Ctrl+P………普通示图
41……Alt+Ctrl+M………插入批注
42……Alt+菜单上字母………打开该菜单

无级微调
打开“绘图”工具栏-点开下拉菜单-绘图网格...-将水平间距和垂直间距调到最小0.01-确定,这样你就可以无级微调

把work设置成在线打开,但不能修改‘只读’怎搞啊?
文件夹共享为只读

在WORD中输入三个等号然后回车。。。出来的是双横线哦。。。
同样的方法也可以做出波浪线单横线哦!~~~~~ ,
###为中间粗上下细的三线, ***为点线, ~~~为波浪线, ---为单线

输入拼音字母的音调怎么输入
用智能ABC,键入v9,然后自己挑选吧!

页码设置
1、打开页眉/页脚视图,点击插入页码按钮,将页码插入(此时所有的页码是连续编号的) 2、切换到页面视图,在需要从1计数的页面上插入连续分节符(插入--分隔符--分节符--连续) 3、再次换到页眉/页脚视图,点击设置页码格式按钮,将页码编排-起始页码设置为1

把Excel中的表格以图片形式复制到Word中
除了用抓图软件和全屏拷贝法外还有更简单的呢
先选定区域,按住Shift健点击"编辑"会出现"复制图片""粘贴图片",复制了后,在Word中选"粘贴图片"就可像处理图片一样处理Excel表格了!

Ctrl+鼠标滑轮(左右键中间的那个轮子)可以迅速调节显示比例的大小(100%)。向上滑扩大,向下滑缩小。


快速调整页眉横线长度
在word插入页眉后,会自动在此位置添加一条长横线。如果需要调整此线的长度及其水平位置,可以首先激活页眉,选择格式下的段落命令,调整一下左右缩进的字符值,确定可以看到最终效果了!

快速浏览图片
在WORD2003中,如果插入的图片过多,会影响打开和翻滚的速度。其实,我们可以通过改变图片的显示方式改变浏览速度。
工具--选项--视图--图片框
这样,先显示的是图片框,需要看的时候,停留,即可显示!

WORD 中如何输入分数
1、打开word,点击工具菜单栏的“插入”,在下拉菜单中点“域”。
2、在打开的复选框中的类别栏中“选等式公式”,域名中“EQ”。然后点击“选项”,在出现的菜单选项中选“F(,)”,接着点击“添加到域”并“确定”。
3、然后在输入F(,)数字,如要输入23 只需在F(,)输入F(2,3)就能得到2/3

怎样使WORD 文档只有第一页没有页眉,页脚
答:页面设置-页眉和页脚,选首页不同,然后选中首页页眉中的小箭头,格式-边框和底纹,选择无,这个只要在“视图”——“页眉页脚”,其中的页面设置里,不要整个文档,就可以看到一个“同前”的标志,不选,前后的设置情况就不同了

Word中双击鼠标的妙用
在Word的程序窗口中不同位置上双击,可以快速实现一些常用功能,我们归纳如下:
在标题栏或垂直滚动条下端空白区域双击,则窗口在最大化和原来状态之间切换;
将鼠标在标题栏最左边WORD文档标记符号处双击,则直接退出WORD(如果没有保存,会弹出提示保存对话框);
将鼠标移到垂直滚动条的上端成双向拖拉箭头时双击,则快速将文档窗口一分为二;
将鼠标移到两个窗口的分界线处成双向拖拉箭头时双击,则取消对窗口的拆分;
在状态栏上的“修订”上双击,则启动“修订”功能,并打开“审阅”工具栏。再次双击,则关闭该功能,但“审阅”工具栏不会被关闭;
在状态栏上的“改写”上双击,则转换为“改写”形式(再次“双击”,转换为“插入”形式);
如果文档添加了页眉(页脚),将鼠标移到页眉(页脚)处双击,则激活页眉(页脚)进入编辑状态,对其进行编辑;在空白文档处双击,则启动“即点即输”功能;
在标尺前端空白处双击,则启动“页面设置”对话框。


在word编辑中经常要调整字休大小来满足编辑要求
选中要修改的文字,按ctrl+]或ctrl+[来改变字体的大小!
这个方法可以微量改字体大小~



文本框的线条
1. 制作好文档后,通过“视图→页眉页脚”命令,调出“页眉页脚”工具栏,单击其中的“显示→隐藏文档正文文字”按钮,隐藏正文部分的文字内容。
2. 选择“插入”菜单中的“文本框”命令,在页眉的下方插入一个空文本框。
3. 在文本框内加入作为水印的文字、图形等内容,右击图片,选择快捷菜单中的“设置图片格式”命令,在对话框中“图片”选项卡下,通过“图像控制”改变图像的颜色,对比度和亮度,并手动调整图片的大小。   
4. 通过“设置文本框格式”命令,把文本框的线条色改为无线条色。
5. 单击“页眉页脚”工具栏的“关闭”按钮,退出“页眉页脚”编辑。

每页添加水印的操作
1. 制作好文档后,通过“视图→页眉页脚”命令,调出“页眉页脚”工具栏,单击其中的“显示→隐藏文档正文文字”按钮,隐藏正文部分的文字内容。
2. 选择“插入”菜单中的“文本框”命令,在页眉的下方插入一个空文本框。
3. 在文本框内加入作为水印的文字、图形等内容,右击图片,选择快捷菜单中的“设置图片格式”命令,在对话框中“图片”选项卡下,通过“图像控制”改变图像的颜色,对比度和亮度,并手动调整图片的大小。   
4. 通过“设置文本框格式”命令,把文本框的线条色改为无线条色。
5. 单击“页眉页脚”工具栏的“关闭”按钮,退出“页眉页脚”编辑。
6. 完成上述步骤的操作,水印制作得以完成,这样就为每一页都添加了相同的水印。

让Word页面快速一分为二
将光标定位在想分开的位置上,按下“Ctrl+Shift+Enter”组合键。


使Word中的字体变清晰
Word文档中使用 “仿宋” 字体很淡,可按以下方法使字体更清晰:
右击桌面,点 “属性”,点 “外观”,点 “效果”,选中“使用下列方式使屏幕字体的边缘平滑”选“清晰”,确定。

Word双面打印技巧
   我们平时用电脑的时候可能都少不了打印材料,Word是我们平常用的最多的Office软件之一。有时我们要用Word打印许多页的文档,出于格式要求或为了节省纸张,会进行双面打印

  我们一般常用的操作方法是:选择“打印”对话框底部的“打印”下拉列表框中的“打印奇数页”或“打印偶数页”,来实现双面打印。我们设定为先打印奇数页。等奇数页打印结束后,将原先已打印好的纸反过来重新放到打印机上,选择该设置的“打印偶数页”,单击“确定”按钮。这样通过两次打印命令就可以实现双面打印。

  我们也可以利用另一种更灵活的双面打印方式:打开“打印”对话框,选中“人工双面打印”,确定后就会出现一个“请将出纸器中已打印好的一面的纸取出并将其放回到送纸器中,然后‘确定’按键,继续打印”的对话框并开始打印奇数页,打完后将原先已打印好的纸反过来重新放到打印机上,然后按下该对话框的“确定”按键,Word就会自动再打印偶数页,这样只用一次打印命令就可以了。

  两种方法对比,后者较前者更为方便。

posted @ 2008-06-06 12:16 jadmin 阅读(84) | 评论 (0)编辑 收藏
添加、删除、修改使用db.Execute(Sql)命令执行操作
╔----------------╗
☆ 数据记录筛选 ☆
╚----------------╝
注意:单双引号的用法可能有误(没有测式)

Sql = "Select Distinct 字段名 From 数据表"
Distinct函数,查询数据库存表内不重复的记录

Sql = "Select Count(*) From 数据表 where 字段名1>#18:0:0# and 字段名1< #19:00# "
count函数,查询数库表内有多少条记录,“字段名1”是指同一字段
例:
set rs=conn.execute("select count(id) as idnum from news")
response.write rs("idnum")

sql="select * from 数据表 where 字段名 between 值1 and 值2"
Sql="select * from 数据表 where 字段名 between #2003-8-10# and #2003-8-12#"
在日期类数值为2003-8-10 19:55:08 的字段里查找2003-8-10至2003-8-12的所有记录,而不管是几点几分。

select * from tb_name where datetime between #2003-8-10# and #2003-8-12#
字段里面的数据格式为:2003-8-10 19:55:08,通过sql查出2003-8-10至2003-8-12的所有纪录,而不管是几点几分。


Sql="select * from 数据表 where 字段名=字段值 order by 字段名 [desc]"

Sql="select * from 数据表 where 字段名 like '%字段值%' order by 字段名 [desc]"
模糊查询

Sql="select top 10 * from 数据表 where 字段名 order by 字段名 [desc]"
查找数据库中前10记录

Sql="select top n * form 数据表 order by newid()"
随机取出数据库中的若干条记录的方法
top n,n就是要取出的记录数

Sql="select * from 数据表 where 字段名 in ('值1','值2','值3')"

╔----------------╗
☆ 添加数据记录 ☆
╚----------------╝
sql="insert into 数据表 (字段1,字段2,字段3 …) valuess (值1,值2,值3 …)"

sql="insert into 数据表 valuess (值1,值2,值3 …)"
不指定具体字段名表示将按照数据表中字段的顺序,依次添加

sql="insert into 目标数据表 select * from 源数据表"
把源数据表的记录添加到目标数据表

╔----------------╗
☆ 更新数据记录 ☆
╚----------------╝
Sql="update 数据表 set 字段名=字段值 where 条件表达式"

Sql="update 数据表 set 字段1=值1,字段2=值2 …… 字段n=值n where 条件表达式"

Sql="update 数据表 set 字段1=值1,字段2=值2 …… 字段n=值n "
没有条件则更新整个数据表中的指定字段值

╔----------------╗
☆ 删除数据记录 ☆
╚----------------╝
Sql="delete from 数据表 where 条件表达式"

Sql="delete from 数据表"
没有条件将删除数据表中所有记录)

╔--------------------╗
☆ 数据记录统计函数 ☆
╚--------------------╝
AVG(字段名) 得出一个表格栏平均值
COUNT(*|字段名) 对数据行数的统计或对某一栏有值的数据行数统计
MAX(字段名) 取得一个表格栏最大的值
MIN(字段名) 取得一个表格栏最小的值
SUM(字段名) 把数据栏的值相加

引用以上函数的方法:
sql="select sum(字段名) as 别名 from 数据表 where 条件表达式"
set rs=conn.excute(sql)
用 rs("别名") 获取统的计值,其它函数运用同上。

╔----------------------╗
☆ 数据表的建立和删除 ☆
╚----------------------╝
Create TABLE 数据表名称(字段1 类型1(长度),字段2 类型2(长度) …… )
例:Create TABLE tab01(name varchar(50),datetime default now())
Drop TABLE 数据表名称 (永久性删除一个数据表)

╔--------------------╗
☆ 记录集对象的方法 ☆
╚--------------------╝
rs.movenext 将记录指针从当前的位置向下移一行
rs.moveprevious 将记录指针从当前的位置向上移一行
rs.movefirst 将记录指针移到数据表第一行
rs.movelast 将记录指针移到数据表最后一行
rs.absoluteposition=N 将记录指针移到数据表第N行
rs.absolutepage=N 将记录指针移到第N页的第一行
rs.pagesize=N 设置每页为N条记录
rs.pagecount 根据 pagesize 的设置返回总页数
rs.recordcount 返回记录总数
rs.bof 返回记录指针是否超出数据表首端,true表示是,false为否
rs.eof 返回记录指针是否超出数据表末端,true表示是,false为否
rs.delete 删除当前记录,但记录指针不会向下移动
rs.addnew 添加记录到数据表末端
rs.update 更新数据表记录
posted @ 2008-06-05 16:09 jadmin 阅读(73) | 评论 (0)编辑 收藏

你正在学习CSS布局吗?是不是还不能完全掌握纯CSS布局?通常有两种情况阻碍你的学习:

第一种可能是你还没有理解CSS处理页面的原理。在你考虑你的页面整体表现效果前,你应当先考虑内容的语义和结构,然后再针对语义、结构添加CSS。这篇文章将告诉你应该怎样把HTML结构化。

另一种原因是你对那些非常熟悉的表现层属性(例如:cellpadding,、hspace、align="left"等等)束手无策,不知道该转换成对 应的什么CSS语句。 当你解决了第一种问题,知道了如何结构化你的HTML,我再给出一个列表,详细列出原来的表现属性用什么CSS来代替。

结构化HTML

我们在刚学习网页制作时,总是先考虑怎么设计,考虑那些图片、字体、颜色、以及布局方案。然后我们用Photoshop或者Fireworks画出来、切割成小图。最后再通过编辑HTML将所有设计还原表现在页面上。

如果你希望你的HTML页面用CSS布局(是CSS-friendly的),你需要回头重来,先不考虑“外观”,要先思考你的页面内容的语义和结构。

外观并不是最重要的。一个结构良好的HTML页面可以以任何外观表现出来,CSS Zen Garden是一个典型的例子。CSS Zen Garden帮助我们最终认识到CSS的强大力量。

HTML不仅仅只在电脑屏幕上阅读。你用photoshop精心设计的画面可能不能显示在PDA、移动电话和屏幕阅读机上。但是一个结构良好的HTML页面可以通过CSS的不同定义,显示在任何地方,任何网络设备上。

开始思考

首先要学习什么是"结构",一些作家也称之为"语义"。这个术语的意思是你需要分析你的内容块,以及每块内容服务的目的,然后再根据这些内容目的建立起相应的HTML结构。

如果你坐下来仔细分析和规划你的页面结构,你可能得到类似这样的几块:

标志和站点名称

主页面内容

站点导航(主菜单)

子菜单

搜索框

功能区(例如购物车、收银台)

页脚(版权和有关法律声明)

我们通常采用DIV元素来将这些结构定义出来,类似这样:

<div id="header"></div>

<div id="content"></div>

<div id="globalnav"></div>

<div id="subnav"></div>

<div id="search"></div>

<div id="shop"></div>

<div id="footer"></div>

这不是布局,是结构。这是一个对内容块的语义说明。当你理解了你的结构,就可以加对应的ID在DIV上。DIV容器中可以包含任何内容块,也可以嵌套另一个DIV。内容块可以包含任意的HTML元素---标题、段落、图片、表格、列表等等。

根据上面讲述的,你已经知道如何结构化HTML,现在你可以进行布局和样式定义了。每一个内容块都可以放在页面上任何地方,再指定这个块的颜色、字体、边框、背景以及对齐属性等等。

使用选择器是件美妙的事

id的名称是控制某一内容块的手段,通过给这个内容块套上DIV并加上唯一的id,你就可以用CSS选择器来精确定义每一个页面元素的外观表现,包括标 题、列表、图片、链接或者段落等等。例如你为#header写一个CSS规则,就可以完全不同于#content里的图片规则。

另外一个例子是:你可以通过不同规则来定义不同内容块里的链接样式。类似这样:#globalnav a:link或者 #subnav a:link或者#content a:link。你也可以定义不同内容块中相同元素的样式不一样。例如,通过#content p和#footer p分别定义#content和#footer中p的样式。从结构上讲,你的页面是由图片、链接、列表、段落等组成的,这些元素本身并不会对显示在什么网络 设备中(PDA还是手机或者网络电视)有影响,它们可以被定义为任何的表现外观。

一个仔细结构化的HTML页面非常简单,每一个元素都被用于结构目的。当你想缩进一个段落,不需要使用blockquote标签,只要使用p标签,并对p 加一个CSS的margin规则就可以实现缩进目的。p是结构化标签,margin是表现属性,前者属于HTML,后者属于CSS。(这就是结构于表现的 相分离.)

良好结构的HTML页面内几乎没有表现属性的标签。代码非常干净简洁。例如,原先的代码<table width="80%" cellpadding="3" border="2" align="left">,现在可以只在HTML中写<table>,所有控制表现的东西都写到CSS中去,在结构化的HTML中, table就是表格,而不是其他什么(比如被用来布局和定位)。

亲自实践一下结构化

上面说的只是最基本的结构,实际应用中,你可以根据需要来调整内容块。常常会出现DIV嵌套的情况,你会看到"container"层中又有其它层,结构类似这样:

<div id="navcontainer">

<div id="globalnav">

<ul>a list</ul>

</div>

<div id="subnav">

<ul>another list</ul>

</div>

</div>

嵌套的div元素允许你定义更多的CSS规则来控制表现,例如:你可以给#navcontainer一个规则让列表居右,再给#globalnav一个规则让列表居左,而给#subnav的list另一个完全不同的表现。

用CSS替换传统方法

下面的列表将帮助你用CSS替换传统方法:

HTML属性以及相对应的CSS方法

HTML属性

CSS方法说明

align="left"

align="right" float: left;

float: right; 使用CSS可以浮动 任何元素:图片、段落、div、标题、表格、列表等等

当你使用float属性,必须给这个浮动元素定义一个宽度。

marginwidth="0" leftmargin="0" marginheight="0" topmargin="0" margin: 0; 使用CSS, margin可以设置在任何元素上, 不仅仅是body元素.更重要的,你可以分别指定元素的top, right, bottom和left的margin值。

vlink="#333399" alink="#000000" link="#3333FF" a:link #3ff;

a:visited: #339;

a:hover: #999;

a:active: #00f;

在HTML中,链接的颜色作为body的一个属性值定义。整个页面的链接风格都一样。使用CSS的选择器,页面不同部分的链接样式可以不一样。

bgcolor="#FFFFFF" background-color: #fff; 在CSS中,任何元素都可以定义背景颜色,不仅仅局限于body和table元素。

bordercolor="#FFFFFF" border-color: #fff; 任何元素都可以设置边框(boeder),你可以分别定义top, right, bottom和left

border="3"cellspacing="3" border-width: 3px; 用CSS,你可以定义table的边框为统一样式,也可以分别定义top, right, bottom and left边框的颜色、尺寸和样式。

你可以使用 table, td or th 这些选择器.

如果你需要设置无边框效果,可以使用CSS定义: border-collapse: collapse;

<br clear="left">

<br clear="right">

<br clear="all">

clear: left;

clear: right;

clear: both;

许多2列或者3列布局都使用 float属性来定位。如果你在浮动层中定义了背景颜色或者背景图片,你可以使用clear属性.

cellpadding="3"

vspace="3"

hspace="3" padding: 3px; 用CSS,任何元素都可以设定padding属性,同样,padding可以分别设置top, right, bottom and left。padding是透明的。

align="center" text-align: center;

margin-right: auto; margin-left: auto;

Text-align 只适用于文本.

象div,p这样的块级可以通过margin-right: auto; 和margin-left: auto;来水平居中

一些令人遗憾的技巧和工作环境

由于浏览器对CSS支持的不完善,我们有时候不得不采取一些技巧(hacks)或建立一种环境(Workarounds)来让CSS实现传统方法同样的效 果。例如块级元素有时侯需要使用水平居中的技巧,盒模型bug的技巧等等。所有这些技巧都在Molly Holzschlag的文章《Integrated Web Design: Strategies for Long-Term CSS Hack Management》中有详细说明。

另外一个关于CSS技巧的资源站点是Big John和Holly Bergevin的“Position is Everything”。

理解浮动行为

Eric Meyer的《Containing Floats》将帮助你掌握如何使用float属性布局。float元素有时候需要清除(clear),阅读《How To Clear Floats Without Structural Markup》将非常有帮助。

更多帮助

已有的《CSS Discussion》列表是很好的资源,它收集了一个WiKiA讨论组的信息,其中包括CSS布局总结(css- discuss.incutio.com/?page=CssLayouts),CSS 技巧总结 (css-discuss.incutio.com/?page=CssHack) 以及更多。

posted @ 2008-06-03 17:29 jadmin 阅读(65) | 评论 (0)编辑 收藏

在 MySQL下,在进行中文模糊检索时,经常会返回一些与之不相关的记录,如查找 "%a%" 时,返

回的可能有中文字符,却没有a字符存在。本人以前也曾遇到过类似问题,经详细阅读MySQL的

Manual,发现可以有一种方法很方便的解决并得到满意的结果。

  希望通过“标题”对新闻库进行检索,关键字可能包含是中英文,如下SQL语句:

  以下为引用的内容:


  
Code:
select id,title,name from achech_com.news where title like '%a%'


  返回的结果,某些title字段确定带了“a”关键字,而有些则只有中文,但也随之返回在检

索结果中。

  解决方法,使用 BINARY 属性进行检索,如:

  以下为引用的内容:


  
Code:
select id,title,name from achech_com.news where binary title like '%a%'


  返回的结果较之前正确,但英文字母区分大小写,故有时在检索如“Achech”及“achech”

的结果是不一样的。知道了使用 BINARY 属性可以解决前面这个问题,再看看 MySQL 支持的

UCASE 及 CONCAT 函数,其中 UCASE 是将英文全部转成大写,而CONCAT函数的作用是对字符进行

连接,以下是我们完全解决后的SQL 语句:


  
Code:
select id,title,name from achech_com.news

  where binary ucase(title) like concat('%',ucase('a'),'%')

  检索的步骤是先将属性指定为 BINARY ,以精确检索结果,而被 like 的 title内容存在大

小写字母的可能,故先使用 ucase 函数将字段内容全部转换成大写字母,然后再进行 like 操作

,而 like 的操作使用模糊方法,使用 concat的好处是传进来的可以是直接的关键字,不需要带

“%”万用符,将“'a'”直接换成你的变量,在任何语言下都万事无忧了。 当然你也可以这么写


  
Code:
select id,title,name from achech_com.news where binary ucase(title) like ucase('%a%')


  检索的结果还算满意吧,不过速度可能会因此而慢N毫秒。

posted @ 2008-06-01 21:36 jadmin 阅读(80) | 评论 (0)编辑 收藏

在试过Oracle安装目录下C:\oracle\product\10.2.0\db_1\jdbc\lib自带的驱动classes12.jar和ojdbc14.jar之后,都产生下面的错误

产生错误的原因是:数据库版本是10.2.0.1的驱动有问题,

解决办法:去下载最新的ojdbc14.jar 驱动,反正大于10.2.0.2版本应该就可以解决.

Oracle网站下载地址 :http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html

posted @ 2008-05-25 01:40 jadmin 阅读(343) | 评论 (0)编辑 收藏

转载自:http://java.ccidnet.com/art/3539/20080508/1443759_1.html

最近对程序占用内存方面做了一些优化,取得了不错的效果,总结了一些经验。

简要说一下,相信会对大家写出优质的程序有所帮助。

下面的论述针对32位系统,对64位系统不适用,后叙经常你写了一个程序,一测试,功能没问题,一看内存占用也不多,就不去考虑其它的东西了。但可能程序使用了一个什么数据结构,会当数据规模变大时,内存占用激增。

基本&&关键的问题是,Java里各种东东占多少内存?????????

对于primitive类型,有8个

byte short int long float double char boolean 它们的长度分别是

1 2 4 8 4 8 2 1

这个不罗嗦了,举例来说

long[] data=new long[1000];

占用内存 8*1000 bytes

此外,data本身是一个Object,也占用内存若干,后叙,当然它针对 8*1000来说,忽略不计

再说Object的占用,在说这个之前,先说说引用,一惯的说法是

Java里没有指针了,只有引用,引用是安全的

这个说法没错,但是从机理上来说,引用就是指针,只是jvm对指针的使用检查和限制很多,这个引用/指针变得很安全

直接来结论:一个引用占4byte ,在32位系统上

Object obj=null; //4byte

Object[] objs=new Object[1000]; //至少4*1000byte

你看我定义了一个 obj,还是null,就占4byte

定义了一个 objs,1000个元素,但都是null啊,就都每个占4byte

是的!!!!!

虽然obj==null,但它已经是 一个引用,或者说一个指针了

指针也要占地方啊!!!!啊!!!!啊!!!!

接下来,直接给另一个结论: Object占8byte,注意,纯Object

Object obj=new Object(); //多少????

8byte?? 错!! 12byte,忘了还有一个引用,8byte是Object的内容

记住 Object obj=new Object(); 占12byte

Object[] objs=new Object[1000];

for(int i=0;i<1000;i++) {

objs[i]=new Object();

}

至少占用 12*1000 bytes

推论: Object占12bytes,似乎和上面的结论矛盾??!!

没有!! 不管Object,没有被垃圾回收之前,总得被别人引用吧?

总的有指针指它吧? 既然指,那个引用or指针就要占地方啊 4byte

加起来是12byte,反正一个Object至少 12bytes

还是直接给结论,推导的过程我就都包办了,咱不是脏活累活抢着干么!!

一个Integer占 16 bytes

这时您可能会有疑问,Integer=Object+int,就是:

public class Integer {

public int value;

}

Integer应该占 8+4=12 bytes啊

你说的有道理,但是jvm对所有的Object有限制!!

这个限制被我发现了,就是不管什么Object占的空间,要是8的倍数

12不是8的倍数,只能是16了!!!

推论:Byte也占16bytes!!!!!!!!!!!

问:

Byte[] bytes=new Byte[1000];

占用空间多少?

答: 约为(至少为) (16+4)*1000 bytes

好家伙!!!!!!!!

论题:数组空间占用怎么算?

我这里直接给结论了,推导这个花了更长的时间:

对于数组来说,数组这个Object有一个length属性,数组的元素相当于其成员

public class Array {

public int length;

//... 其它成员

}

对于数组,我们不是直接可以取length属性么,源于此

public byte[] bytes=new byte[1000];

System.out.println(bytes.length); // 看,有length属性

上面的bytes换算过来是:

public class Array {

public int length;

public byte byte0;

public byte byte1;

...

public byte byte999;

}

上面的bytes占用的内存是:

4+[8+4 + 1*1000] = 4+ [1012]=4+1016=1020

4是 bytes这个引用,8是Object基占的,4是length属性占的

1000是1000个成员占的,本来是 1012,但要求是8的倍数,变成 1016了

总共是 1020

再如:

byte[] bytes=new byte[4];

的内存占用是:

4+[8+4+4*1]=4+[16]=20;

byte[] bytes=new byte[3]; 也是 20

对于元素是Object的数组,Object也是当作其成员,(注意只有引用这个数组的空间,这个可以推到普通Class上)

Byte[] bytes=new Byte[1000];

这个 bytes的定义相当于:

public class Array {

public int length;

public Byte byte0;

.....

public Byte byte999;

}

占用空间是:

4+[8+4+4*1000]+16*1000= 4+ 4016 + 16000 = 你自己算吧

推论:千万不要用 Byte[] 有20倍的差距!!!!!!!

你可能一下子没明白过来,没关系多琢磨一下,对于普通的class来说

,内容占用就是基加成员的占用,Object成员只记引用

public class Abc {

public int n;

public byte b;

public Object obj;

}

它的内容占用是: [8+4+1+4]=24

所以 Abc one=new Abc()的占用是 4+24=28

提醒:对于 Abc的成员 obj没有计,如果要计入的话,循环这个过程就可以了。(琢磨一下)

举例:

public class Abc {

public byte b;

public Object obj=null;

}

public class Def {

public int n;

public byte b;

public Abc obj=new Abc();

}

问:

Def one=new Def(); //占多少?

答:

4+[8+4+1+4]+[8+1+4]=4+24+16=44

public class Abc {

public byte b;

public Object obj=null;

}

public class Def {

public int n;

public byte b;

public Abc[] objs=new Abc[100];

{

for(int i=0;i<10;i++) {

objs[i]=new Abc();

}

}

}

问:

Def one=new Def(); //占多少?

答:

kao,一下我也算不出来,不过我写了程序,可以算出来,你给它一个Object,它就能递归的算出总共占了多少内存,这个程序不复杂,你也可以写出来。我等机会合适了再放出。

单独说一下String,String的结构是:

public class String {

private final char value[];

private final int offset;

private final int count;

private int hash; // Default to 0

}

所以,不考虑那个char[]的占用,一个String最少占用 [8+4+4+4+4]=24bytes

加上引用,共28bytes

所以

String s="";

占用28bytes!!!!! 尽管它的长度为0

如果精确的算,加上引用一个String的占用是

4+24+[8+4+2*length]

String s=""; 的占用是 28+16= 44

String s="ab" 的占用是 28+16= 44

String s="abc" 的占用是 28+24 = 52

要说的是,String是常用的类,这么看,String耗内存很多,所以jvm有优化,同样的内容尽量重用,所以除了28是必须的外,那个char[] 很可能一样

比方说

String[] s=new String[1000];

for(int i=0;i<1000;i++) {

s[i]=new String("abcdefasdjflksadjflkasdfj");

}

的占用的数量级是 28*1000,那 1000个字符串本身基本上不占内存,只有一份!!!!!!

反正String 至少是 28,最多也可能是28!!!!!!!!

比较占内存的数据结构,这个很重要:

基本上就是 primitive的包装

实例:

我以前用一个

Hashtable的结构,有100万个元素

改为String[]+int[]后,内存占用改观不少,速度也很快

100万的String[] 快排一下,也就2秒多,查找用2分,和hash也差不多少。

posted @ 2008-05-10 15:34 jadmin 阅读(69) | 评论 (0)编辑 收藏

1."-2147467259 (0x80004005)"错误

解决办法:

cmd进入命令提示窗,依次进行如下操作

regsvr32 vbscript.dll
regsvr32 jscript.dll
regsvr32 %windir%\system32\inetsrv\asp.dll


重新启动一下IIS,错误就解决了

2."Microsoft VBScript 运行时错误 (0x800A01AD)"错误,此错误属于没有FSO权限,需要注册scrrun.dll

解决办法:

运行-->输入命令regsvr32 scrrun.dll即可。

如果输入上述命令后,提示找不到模块,说明系统中缺少该组件,则进行如下操作

1.在安装文件(系统光盘)目录i386中找到scrrun.dl_,用winrar解压缩,得scrrun.dll,

2.复制到x(你的系统盘):\windows\system32\目录中。

3.运行-->运行命令regsvr32 scrrun.dll

补充:

取消FOS的命令是:运行regsvr32 scrrun.dll /u

posted @ 2008-05-06 17:58 jadmin 阅读(77) | 评论 (0)编辑 收藏

       系统要安全就需要经常的打补丁,经常的重装系统,当我们重装系统的时候挨个打补丁是非常麻烦的事情,因为补丁比较多,挨个启动比较烦琐,为了方便,我们可以自己制作一个补丁批处理安装程序。
       第一步:根据微软安全补丁最新发布通知和系统更新需要,下载所需的安全补丁(可以使用WUD下载)。在移动硬盘或U盘上建立目录,例如:“20071212xp_patch”,将下载的所有补丁文件复制到此文件夹内。
       第二步:微软KB类的安全补丁安装参数都很统一,主要分为“安装模式”、“重新启动选项”和“特别选项”三部分。其中,“/quiet”、“/passive”、“/norestart”和“/nobackup”安装参数分别表示“无用户操作或显示”、“无人参与模式”、“安装后不重启”和“不备份卸载需要的文件”。正确使用这几个参数可轻松实现补丁无人值守安装,并且能避免安装过程中重启电脑及在C盘Windows目录下备份无用的补丁文件。
       第三步:打开记事本程序,输入以下代码:

@echo off
for %%i in (*.exe) do %%i /passive /norestart /nobackup
shutdown -r                                          

       上面的代码,第二行是一个循环命令,循环执行同一目录下的所有补丁文件;最后一行“shutdown -r”,表示所有补丁安装后自动重启电脑(注:最后一行可以不加,省得象我一样这边下载着驱动,那边提示还有多少秒自动重启,晕啊 :)。把它保存为“updatexp.bat”,并复制到20071212xp_patch文件夹中。
       第四步:双击这个批处理文件,就可以了。

说明:
有时系统升级文件可能是SFX、CAB格式,这时你可以用以下代码:
@echo off
FOR /R %%F IN (*.exe) DO @((@findstr _SFX_CAB_EXE_PATH "%%F" >nul && @start /wait %%F /U /Z)|| @start /wait %%F)

一点使用说明:
①、@ 不是一个命令, 而是DOS 批处理的一个特殊标记符, 仅用于屏蔽命令行回显;
②、echo 表示显示此命令后的字符;
echo off 表示在此语句后所有运行的命令都不显示命令行本身;
@与echo off相象,但它是加在每个命令行的最前面,表示运行时不显示这一行的命令行(只能影响当前行)。
因此如果你想显示每次运行的命令行,可将第一行删除。

posted @ 2008-05-06 15:24 jadmin 阅读(145) | 评论 (0)编辑 收藏

首先安装IIS组件,注意安装所有"internet信息服务"的所有组件;确保正常运行asp程序.

其次创建php运行环境

1.下载和安装php程序,提供下载地址http://cn2.php.net/downloads.php ,我下载的版本是PHP5.2.5

2.解压缩到c:\php(自己指定目录),PHP文件夹中的php.ini-dist复制到C:\WINDOWS目录下并改名为php.ini,复制php5ts.dlllibmysql.dll两个文件到C:\WINDOWS\system32

3.为了让PHP支持MYSQLGD库需要编辑php.ini文件,用记事本打开该文件,查找“extension_dir”,然后把extension_dir = "./"修改为extension_dir = "C:\php\ext";另外还需要把“;extension=php_mysql.dll”前的分号去掉,改成extension=php_mysql.dll。把“;extension=php_gd2.dll”前的分号也去掉,修改为extension=php_gd2.dll

最后配置IISPHP映射

打开IIS,在默认网站上点右键选择"属性"---"主目录",更改执行权限为"脚本和可执行文件",再点"配置",打开添加和编辑应用程序扩展名映射窗口,添加C:\php\php5isapi.dll的可执行文件.扩展名输入.php,这样在解析过程中将自动调用php5isapi.dll解释PHP语言。

将以下代码另存为index.php

<?
echo phpinfo();
?>

创建虚拟目录执行.

如有异常,请在默认网站属性中的ISAPI筛选器标签,添加一个名为PHP的筛选器,可执行文件选择C:\php\php5isapi.dll即可.

本人机器环境为:WinXP SP2 + IIS5.1

posted @ 2008-05-06 04:46 jadmin 阅读(42) | 评论 (0)编辑 收藏

1.安装JDK

Groovy是基于JVM的,当然,先得装个JDK了,并配置好Java环境

2.下载并安装groovy

下载地址:http://groovy.codehaus.org/Download

我下载的是1.5.6 Binary Release.解压到指定目录(我的是C:\groovy),到这里groovy已经安装好了

3.配置环境变量

设置GROOVY_HOME   --->   C:\groovy

将%GROOVY_HOME%\bin追加到Path里

4.测试

开启命令提示符窗口,输入命令groovy -v

到此,Groovy环境配置成功。

注意:不用加载lib下面的jar到classpath中,groovy执行时会自动加载,在%GROOVY_HOME%\conf\groovy-starter.conf文件中已经加载了。

5.常用命令


Groovy –h 帮助
Groovy – v 打印使用的版本
Groovy – e “脚本内容”
Groovy filename 执行指定脚本文件内容
Groovy –Xmx128M 可以跟java命令使用的参数

编译groovy文件
groovyc –d classes 文件名.groovy (我这里为:Hello)

用java命令运行
java -cp %GROOVY_HOME%/embeddable/groovy-all-1.5.6.jar;classes 类名(我这里为:Hello)

6.Groovy开发工具:
默认支持
Groovysh           shell方式交互执行脚本
Groovy                命令行执行
Groovyconsole    gui方式执行

其他相关资源链接

Groovy轻松入门--搭建Groovy开发环境

http://hi.baidu.com/pplog/blog/item/d3f1b77e263f5b390dd7dac7.html

Groovy轻松入门--Grails实战基础篇

http://hi.baidu.com/pplog/blog/item/5aae560f0a39e92b6159f3c0.html

http://hi.baidu.com/pplog/blog/category/Groovy

http://groovy.codehaus.org/Eclipse+Plugin

posted @ 2008-05-06 03:43 jadmin 阅读(1227) | 评论 (0)编辑 收藏

Groovy是一种基于JVM的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性。 Groovy已在WebWork2中的应用。它可以被编译为标准的Java Bytecode。

该项目主页:http://groovy.codehaus.org/,在该网站上,Groovy是这样定义的:

An agile dynamic language for the Java Platform

Groovy...

posted @ 2008-05-06 03:19 jadmin 阅读(64) | 评论 (0)编辑 收藏

SWT程序

SWT是一套独立的库,用户可以在SWT的基础上很容易地建立自己的应用,本节的目的就是要在SWT上建立一个简单的“Hello world”程序,读者可以在这个简单的程序上建立自己的应用。

加入SWT依赖的包

SWT为用户提供了一套API,用户如果要使用SWT进行开发,必须要把所需的Jar包放在Eclipse项目的ClassPath中。在此笔者采用新建一个插件项目,插件项目会自动引用SWT相关的包,步骤如下。

1. 运行Eclipse。
单击“File”→“New”→“Other”菜单项,在弹出的对话框中选择 Select 对话框左边的 Plug-in Development 向导。如图3所示,选择 Plug-in Project。


图3 新建插件对话框


2. 单击“Next”按钮,弹出新建对话框,输入项目名称。此处使用了“com.free.swtjface.demo”。单击“Next”按钮后弹出新建对话框页,如图4所示。


图4 新建项目


3. 单击“Next”按钮,在接下来的对话框中单击“Finish”按钮,建立一个空的插件项目在此项目中,Eclipse中已经自动引入了此插件所需要依赖的包,其中包括SWT/JFace所用到的包如图5所示。


图5 插件依赖的包


如果用户想加入SWT/JFace相关的包,也可以手动加入下面几个包“org.eclipse.swt.win32. win32.x86_*.jar”、“org.eclipse.swt_*.jar”和“org.eclipse.jface_*.jar”到插件的类路径中。

提示:对于初学者,通过创建插件项目自动引入SWT的jar包是一个比较好的方法,如果开发插件项目,用户也不用但心SWT的jar包没有引入的问题。

“Hello world”SWT程序

现在SWT所依赖的包已经加入到了项目的ClassPath中,SWT程序和Java的程序一样,也是通过main函数运行的,如例程1所示。

例程1 HelloWorldSwt.java

public class HelloWorldSwt { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setText("hello world! Window"); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }

这段程序虽然很简单,但是它反映了书写SWT程序的步骤:
1. 创建一个Display对象。Display对象是SWT和操作系统通信的桥梁。
2. 创建一个或者多个Shell对象。可以认为Shell代表了程序的窗口。
3. 在Shell内创建各种部件(widget),如按钮、下拉框和列表等。
4. 对各个部件进行初始化(外观、状态等),同时为各种部件的事件创建监听器 (listener)。监听器可以监听窗口的消息,在此没有添加监听器,将会在后面的章节重点介绍监听器的注册。
5. 调用Shell对象的open()方法以显示窗体。
6. 对各种事件进行监听并处理,直到程序发出退出消息。
7. 调用Display对象的dispose()方法以结束程序。

提示:SWT程序的运行和例程1类似,读者如果不清楚SWT程序的运行机制,可以直接复制上面的样板代码就可以了。

运行SWT应用

SWT程序的运行要通过JNI调用相应的操作系统控件,运行SWT程序和运行Java应用程序有点不同,在Eclipse中用户可以选择运行SWT程序(SWT Application)可以运行SWT程序,步骤如下:
1. 打开Java的默认视图,在“Hello world”程序的文件上单击鼠标右键。
2. 选择“SWT Application”菜单,如图6所示。


图6 运行SWT程序


3. 单击“SWT Application”菜单运行,运行效果如图7所示。


图7 SWT“Hello world”程序


HelloWorldSwt程序只创建了一个窗口(shell),读者可以把shell当作其它组件的父窗口,创建其它组件。

提示:在Eclipse3.3以后,运行SWT程序和运行Java程序是一样的。

JFace程序

JFace 是一个用户界面工具箱,它提供很难实现的、用于开发用户界面功能部件的 helper 类,JFace 在原始的窗口小部件系统的级别之上运行。JFace 使用户可以专注于实现特定插件的功能,而不必花费精力来处理底层窗口小部件系统或者解决几乎在任何用户界面应用程序中都很常见的问题。

“Hello world”JFace程序

JFace的应用程序相对来说更简单,它通过“ApplicationWindow”类实现应用,“ApplicationWindow”把和操作系统交互的细节封装起来了,用户只需要关心自己窗口的建立,程序代码如例程2所示。
例程2

public class HelloWorldJFace extends ApplicationWindow { public HelloWorldJFace() { super(null); } protected Control createContents(Composite parent) { getShell().setText("hello world! Window"); parent.setSize(400, 250); return parent; } public static void main(String[] args) { HelloWorldJFace helloWorldApp = new HelloWorldJFace(); helloWorldApp.setBlockOnOpen(true); helloWorldApp.open(); Display.getCurrent().dispose(); } }

其中,JFace的应用程序可以通过重载“createContents(Composite parent)”方法添加窗口的组件到parent组件中。
运行JFace的程序和运行SWT程序一样。

Tags:java,rcp,jface,swt,ibm,eclipse,ui,gui

posted @ 2008-05-04 18:35 jadmin 阅读(132) | 评论 (0)编辑 收藏
Eclipse 是一个通用工具平台。它是一个开放的、可用于任何东西的可扩展 IDE,它为工具开发人员提供了灵活性以及对软件技术的控制能力。Eclipse 为开发人员提供了生产大量 GUI 驱动的工具和应用程序的基础。而这项功能的基础就是基于GUI库 的SWT 和 JFace。

SWT(Standard Widget Toolkit)本身仅仅是Eclipse组织为了开发Eclipse IDE环境所编写的一组底层图形界面 API。或许是无心插柳,或许是有意为之,至今为止,SWT无论在性能上还是外观上,都超越了Sun公司提供的AWT和Swing。

概述

SWT(Standard Widget Toolkit)是Eclipse中的窗口小部件工具箱,它是一组窗口组件的实现,并能底层操作系统图形用户界面平台紧密集成。另外,SWT定义了所有受支持平台上的公共可移植API,并尽可能地使用本机窗口小部件在每个平台上实现该API,这允许SWT在所有平台上维护一致的编程模型,且能立即反映底层操作系统图形用户界面外观中的任何更改。

JFace用来在 SWT 库顶部提供常见的应用程序用户界面功能。JFace并不试图“隐藏”SWT 或者替换它的功能。它提供一些类和接口,用来处理SWT对动态用户界面相关联的常见任务。

SWT/JFace是Eclispe的基础,Eclipse的Workbench就是建立在SWT/JFace之上的。另外,JFace是在SWT之上开发的,它和SWT形成一个交集,其中SWT提供最原始的组件,如图1所示。


图1 SWT/JFace和Workbench的关系


JFace对SWT进行了扩展,把用户熟悉的一些组件进行了封装,在开发中用户可以尽可能地用JFace组件来开发自己的应用。JFace程序和SWT程序类似,只不过JFace把常用的功能进行了提炼,使用户不必太关心SWT的一些细节。

提示:SWT提供了一套API,它因为Eclipse而生,但它完全可以脱离Eclipse而存在。

基本特性

SWT是一个套库函数,它创建了Java 版的本地操作系统 GUI 控件。它依赖于本机实现,这意味着基于SWT的应用程序具有以下几个关键特性。

 它们的外观、行为和执行类似于“本机”应用程序。
 所提供的窗口小部件(Widget)反映了主机操作系统上提供的窗口小部件(组件和控件)。
 主机 GUI 库的任何特殊行为都在 SWT GUI 中得到反映。

这些目标使得 SWT 不同于 Java 技术的 Swing,Swing 的设计目标是消除操作系统的差异。SWT 库反映了主机操作系统的基本窗口小部件,JFace 库有助于向 SWT 应用程序中添加大量服务,SWT 最重要的扩展之一是将应用程序的数据模型与显示及更改它的 GUI 隔离开来。

SWT中有如下一些基本的组件:
1. Widget:基本的 SWT GUI 组件(类似于 Java AWT 中的 Component 和 Swing 中的 JComponent),Widget 是一个抽象类。
2. Control:拥有操作系统的对等物的窗口小部件,Control 是一个抽象类。
3. Composite:包含其他控件的控件(类似于 Java AWT 中的 Container 和 Swing 中的JPanel)。
4. Item:其他控件包含的窗口小部件(该控件可能是复合控件),如列表和表。Item 是一个抽象类。
这些窗口组件(或小部件)被安排在继承层次结构中。其中Widget是底层的类,继承关系如图2所示。


图2 Widget类的继承关系


几乎所有SWT GUI都是从某些基础部分开始创建的。所有SWT窗口组件都可以在 org.eclipse.swt.widget 或 org.eclipse.swt.custom 包中找到(一些Eclipse插件还在其他包中提供了定制的窗口组件)。窗口组件包中包含一些基于操作系统控件的控件,而定制包中则包含一些超出操作系统控件集之外的控件。一些定制的软件包控件类似于窗口小部件包中的控件。为了避免命名冲突,定制控件的名称都是以“C”开始的(例如,比较 CLabel 与 Label)。

在SWT中,所有控件(除了一些高级控件,比如 shell)在创建的时候都必须有一个父控件(一个复合实例)。在创建的时候,这些控件被自动“添加”到父控件中,这与必须明确添加到父控件中的 AWT/Swing 中的控件有所不同,自动添加产生了一种“自上而下”地构造GUI的方法。这样,所有控件都可以采用一个复合父控件(或者一个子类)作为构造函数的参数。

大多数控件都有一些必须在创建时设置的标记选项,因此大多数控件还有另外一个构造函数参数,通常称为样式或风格,该参数提供了设置这些选项的标记。所有这些参数值都是 整型常量,并且都是在 org.eclipse.swt 包的 SWT 类中定义的。如果不需要任何参数,则可以使用 SWT.NONE 值。

提示:创建一个组件通常有两个参数,第一个为父组件,第二个组件的显示样式,例如:“Button button = new Button(shell, SWT.RADIO);”。

Tags:java,rcp,jface,swt,ibm,eclipse,ui,gui

posted @ 2008-05-04 18:28 jadmin 阅读(88) | 评论 (0)编辑 收藏

package swt.expandbar;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ExpandBar;
import org.eclipse.swt.widgets.ExpandItem;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;

public class WindowStyle {

public WindowStyle() {
   final Display display = Display.getDefault();
   final Shell shell = new Shell(SWT.MIN);
   shell.setText("ExpandBar练习");
   shell.setSize(200, 518);
   shell.setLayout(new FillLayout());
  
   ExpandBar expandBar = new ExpandBar(shell,SWT.V_SCROLL);
   {
    Composite comp1 = new Composite(expandBar,SWT.NONE);
    comp1.setLayout(new GridLayout(2,false));
    new Label(comp1,SWT.NONE).setImage(new Image(display,"icons/default.gif"));
    new Link(comp1,SWT.NONE).setText("<a>查看系统信息</a>");
   
    new Label(comp1,SWT.NONE).setImage(new Image(display,"icons/doc.gif"));
    new Link(comp1,SWT.NONE).setText("<a>添加/删除程序</a>");
   
    new Label(comp1,SWT.NONE).setImage(new Image(display,"icons/main.gif"));
    new Link(comp1,SWT.NONE).setText("<a>更改一个设置</a>");
    ExpandItem item1 = new ExpandItem(expandBar, SWT.NONE);
    item1.setText("系统任务");
    item1.setHeight(75);// 设置Item的高度
    item1.setControl(comp1);// setControl方法控制comp1的显现
   }
  
   {  
    Composite comp2 = new Composite(expandBar,SWT.NONE);
    comp2.setLayout(new GridLayout(2,false));
    new Label(comp2,SWT.NONE).setImage(new Image(display,"icons/computer.gif"));
    new Link(comp2,SWT.NONE).setText("<a>网上邻居</a>");
    new Label(comp2,SWT.NONE).setImage(new Image(display,"icons/inc.gif"));
    new Link(comp2,SWT.NONE).setText("<a>我的文档</a>");
    new Label(comp2,SWT.NONE).setImage(new Image(display,"icons/folder.gif"));
    new Link(comp2,SWT.NONE).setText("<a>共享文档</a>");
    new Label(comp2,SWT.NONE).setImage(new Image(display,"icons/cmd.gif"));
    new Link(comp2,SWT.NONE).setText("<a>控制面板</a>");
    ExpandItem item1 = new ExpandItem(expandBar, SWT.NONE);
    item1.setText("其他位置");
    item1.setHeight(95);// 设置Item的高度
    item1.setControl(comp2);// setControl方法控制comp1的显现
   }
  
   {
    Composite comp3 = new Composite(expandBar,SWT.NONE);
    comp3.setLayout(new GridLayout());
   
    // setup bold font
    Font boldFont = JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT);
    Label l = new Label(comp3,SWT.NONE);
    l.setText("我的电脑");
    l.setFont(boldFont);
   
    new Label(comp3,SWT.NONE).setText("系统文件夹");
   
    ExpandItem item1 = new ExpandItem(expandBar, SWT.NONE);
    item1.setText("详细信息");
    item1.setHeight(50);// 设置Item的高度
    item1.setControl(comp3);// setControl方法控制comp1的显现
   }
  
   shell.layout();
   shell.open();
  
   while(!shell.isDisposed()) {
    if(!display.readAndDispatch())
     display.sleep();
   }
   display.dispose();
}


public static void main(String[] args) {
   new WindowStyle();
}
}

以上程序中会用到7个icon小图片,把他们放置在工程根目录下的icons目录下

运行效果:

Tags:java,rcp,jface,swt,ibm,eclipse,ui,gui

posted @ 2008-05-04 16:42 jadmin 阅读(195) | 评论 (0)编辑 收藏
1. 软件版本阶段说明

             * Base版: 此版本表示该软件仅仅是一个假页面链接,通常包括所有的功能和页面布局,但是页面中的功能都没有做完整的实现,只是做为整体网站的一个基础架构。
             * Alpha版: 此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改。
             * Beta版: 该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过多次测试来进一步消除,此版本主要的修改对像是软件的UI。
             * RC版: 该版本已经相当成熟了,基本上不存在导致错误的BUG,与即将发行的正式版相差无几。
             * Release版: 该版本意味“最终版本”,在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。该版本有时也称为标准版。一般情况下,Release不会以单词形式出现在软件封面上,取而代之的是符号(R)。

   2. 版本命名规范

        软件版本号由四部分组成,第一个1为主版本号,第二个1为子版本号,第三个1为阶段版本号,第四部分为日期版本号加希腊字母版本号,希腊字母版本号共有5种,分别为:base、alpha、beta、RC、release。例如:1.1.1.051021_beta。



# 版本号定修改规则:

* 主版本号(1):当功能模块有较大的变动,比如增加多个模块或者整体架构发生变化。此版本号由项目决定是否修改。
* 子版本号(1):当功能有一定的增加或变化,比如增加了对权限控制、增加自定义视图等功能。此版本号由项目决定是否修改。
* 阶段版本号(1):一般是 Bug 修复或是一些小的变动,要经常发布修订版,时间间隔不限,修复一个严重的bug即可发布一个修订版。此版本号由项目经理决定是否修改。
* 日期版本号(051021):用于记录修改项目的当前日期,每天对项目的修改都需要更改日期版本号。此版本号由开发人员决定是否修改。
* 希腊字母版本号(beta):此版本号用于标注当前版本的软件处于哪个开发阶段,当软件进入到另一个阶段时需要修改此版本号。此版本号由项目决定是否修改。


# 文件命名规范

  文件名称由四部分组成:第一部分为项目名称,第二部分为文件的描述,第三部分为当前软件的版本号,第四部分为文件阶段标识加文件后缀,例如:项目外包平台测试报告1.1.1.051021_beta_b.xls,此文件为项目外包平台的测试报告文档,版本号为:1.1.1.051021_beta。



   3.
      如果是同一版本同一阶段的文件修改过两次以上,则在阶段标识后面加以数字标识,每次修改数字加1,项目外包平台测试报告1.1.1.051021_beta_b1.xls

        当有多人同时提交同一份文件时,可以在阶段标识的后面加入人名或缩写来区别,例如:项目外包平台测试报告 1.1.1.051021_beta_b_LiuQi.xls。当此文件再次提交时也可以在人名或人名缩写的后面加入序号来区别,例如:项目外包平台测试报告1.1.1.051021_beta_b_LiuQi2.xls

   4. 版本号的阶段标识

      软件的每个版本中包括11个阶段,详细阶段描述如下:

      阶段名称                    阶段标识
      需求控制                            a
      设计阶段                            b
      编码阶段                      c
      单元测试                      d
      单元测试修改                e
      集成测试                      f
      集成测试修改                g
      系统测试                      h
      系统测试修改                       i
      验收测试                          j
      验收测试修改                    k

===================================================

Alpha:
Alpha是内部测试版,一般不向外部发布,会有很多Bug.除非你也是测试人员,否则不建议使用.
是希腊字母的第一位,表示最初级的版本
alpha就是α,beta就是β
alpha版就是比beta还早的测试版,一般都是内部测试的版本
--------------------
Beta:
  很容易理解就是测试版,这个阶段的版本会一直加入新的功能。
RC:(Release Candidate)
  Candidate是候选人的意思,用在软件上就是候选版本。Release.Candidate.就是发行候选版本。和Beta版最大的差别在于Beta阶段会一直加入新的功能,但是到了RC版本,几乎就不会加入新的功能了,而主要着重于除错!

RTM:全称为Release to Manufacture。
  是给工厂大量压片的版本,内容跟正式版是一样的,不过 RTM.也有出120天评估版。但是说RTM.是测试版是错的。正式在零售商店上架前,是不是需要一段时间来压片,包装、配销呢?所以程序代码必须在正式发行前一段时间就要完成,这个完成的程序代码叫做Final.Code,这次Windows.XP开发完成,外国媒体用Windows XP.goes.gold来称呼。程序代码开发完成之后,要将母片送到工厂大量压片,这个版本就叫做RTM版。所以说,RTM版的程序码一定和正式版一样。但是和正式版也有不一样的地方:例如正式版中的OEM不能升级安装,升级版要全新安装的话会检查旧版操作系统光盘等,这些就是RTM和正式版不同的地方,但是它们的主要程序代码都是一样的。

OEM:
  是给计算机厂商随着计算机贩卖的,也就是随机版。只能随机器出货,不能零售。只能全新安装 ,不能从旧有操作系统升级。如果买笔记型计算机或品牌计算机就会有随机版软件。包装不像零售版精美,通常只有一面CD和说明书(授权书)。

RVL:
  号称是正式版,其实RVL.根本不是版本的名称。RVL.是一个Warez.Team,台湾分部叫RVL@TW.,它之间又释出一个.WinXP.RVL@TW.版本。它是某中文版+英文Corpfiles破解的。

EVAL:
  而流通在网络上的EVAL版,是微软帮媒体记者编辑上课给的,是所谓的「评估版」没错,你输入的金钥是30天的,就可用30天,输入的是180天的,就可用180天。功能上和零售版无乎没有区别。

RTL:Retail.(零售版)
  是真正的正式版,正式上架零售版。在安装盘的i386文件夹里有一个 eula.txt,最后有一行EULAID,就是你的版本。比如简体中文正式版是EULAID:WX.4_PRO_RTL_CN,繁体中文正式版是 WX.4_PRO_RTL_TW。其中:如果是WX.开头是正式版,WB.开头是测试版。_PRE,代表家庭版;_PRO,代表专业版。

===============================================================

版本号:

V(Version):即版本,通常用数字表示版本号。(如:EVEREST Ultimate v4.20.1188 Beta )

Build:用数字或日期标示版本号的一种方式。(如:VeryCD eMule v0.48a Build 071112)

SP:Service Pack,升级包。(如:Windows XP SP 2/Vista SP 1)
授权和功能划分:
试用版,通常都有时间限制,有些试用版软件还在功能上做了一定的限制。可注册或购买成为正式版。

Unregistered:未注册版,通常没有时间限制,在功能上相对于正式版做了一定的限制。可注册或购买成为正式版。

Demo:演示版,仅仅集成了正式版中的几个功能,不能升级成正式版。
Lite:精简版。
Full:完整版。

开发阶段划分:

α(Alpha)版:内测版,内部交流或者专业测试人员测试用。Bug较多,普通用户最好不要安装。

β(Beta)版:公测版,专业爱好者大规模测试用,存在一些缺陷,该版本也不适合一般用户安装。

γ(Gamma)版:相当成熟的测试版,与即将发行的正式版相差无几。

RC版:Release Candidate候选版本,处于Gamma阶段。从Alpha到Beta再到Gamma是改进的先后关系,但RC1、RC2往往是取舍关系。

Final:正式版。

语言划分:

SC:Simplified Chinese简体中文版。

GBK:简体中文汉字内码扩展规范版。

TC:Traditional Chinese繁体中文版。

BIG5:繁体中文大五码版。

UTF8:Unicode Transformation Format 8 bit,对现有的中文系统不是好的解决方案。

================================================================================

        ●alpha 内部测试版
  ●beta 外部测试版
  ●demo 演示版
  ●Enhance 增强版或者加强版 属于正式版
  ●Free 自由版
  ●Full version 完全版 属于正式版
  ●shareware 共享版
  ●Release 发行版 有时间限制
  ●Upgrade 升级版
  ●Retail 零售版
  ●Cardware 属共享软件的一种,只要给作者回复一封电邮或明信片即可。(有的作者并由此提供注册码等),目前这种形式已不多见。
  ●Plus 属增强版,不过这种大部分是在程序界面及多媒体功能上增强。
  ●Preview 预览版
  ●Corporation & Enterprise 企业版
  ●Standard 标准版
  ●Mini 迷你版也叫精简版,只有最基本的功能
  ●Premium -- 贵价版
  ●Professional -- 专业版
  ●Express -- 特别版
  ●Deluxe -- 豪华版
  ●Regged -- 已注册版
  ●CN -- 简体中文版
  ●CHT -- 繁体中文版
  ●EN -- 英文版
  ●Multilanguage -- 多语言版
  ●Rip 是指从原版文件(一般是指光盘或光盘镜像文件)直接将有用的内容(核心内容)分离出来,剔除无用的文档,例如PDF说明文件啊,视频演示啊之类的东西,也可以算做是精简版吧…但主要内容功能是一点也不能缺少的!另:DVDrip是指将视频和音频直接从DVD光盘里以文件方式分离出来。
  ●trail 试用版(含有某些限制,如时间、功能,注册后也有可能变为正式版)
  ●RC 版 就是Release Candidate(候选版本)的简称。从微软的惯例来看推出RC版操作系统就代表正式版的操作系统已经离我们不远了,因为微软操作系统的开发步骤是这样的:内部测试->alpha公测->beta公测->RC版->正式版上市;通常微软的RC版本筛选会经历2-3个过程,也就是说微软会推出RC1、RC2或者RC3的操作系统,而随后就是正式版操作系统上市了,因此通常来看RC1版操作系统已经同最终零售版操作系统相差无几了。该版本已经完成全部功能并清除大部分的BUG。到了这个阶段只会除BUG,不会对软件做任何大的更改。
  ●RTM 版。这基本就是最终的版本,英文是 Release To Manufactur,意思是发布到生产商。
  ●Original Equipment Manufacturer (OEM)
  You may license products through an Original Equipment Manufacturer (OEM). These products, such as Windows operating systems, come installed when you purchase a new computer.
  OEM软件是给电脑生产厂的版本,无需多说。
  ●Full Packaged Product (FPP)-Retail
  Physical, shrink-wrapped boxes of licensed product that can be purchased in a local retail store or any local software retailer.
  FPP就是零售版(盒装软件),这种产品的光盘的卷标都带有"FPP"字样,比如英文WXP Pro的FPP版本的光盘卷标就是WXPFPP_EN,其中WX表示是Windows XP,P是Professional(H是Home),FPP表明是零售版本,EN是表明是英语。获得途径除了在商店购买之外,某些MSDN用户也可以得到。
  ●Volume Licensing for Organizations (VLO)
  You may enjoy potentially significant savings by acquiring multiple product licenses. Depending on the size and type of your organization.
  团体批量许可证(大量采购授权合约),这是为团体购买而制定的一种优惠方式。这种产品的光盘的卷标都带有"VOL"字样,取"Volume"前3个字母,以表明是批量,比如英文 WXP Pro的VOL版本的光盘卷标就是WXPVOL_EN,其中WX表示是Windows XP,P是Professional(VOL没有Home版本),VOL表明是团体批量许可证版本,EN是表明是英语。获得途径主要是集团购买,某些 MSDN用户也可以得到。
这种版本根据购买数量等又细分为“开放式许可证”、“选择式许可证”、“企业协议”、“学术教育许可证”等以下5种版本
  ·Open License
  ·Select License
  ·Enterprise Agreement
  ·Enterprise Subscription Agreement
  ·Academic Volume Licensing
  由此可见,平时说的什么select/corp是许可证授权方式,他的出现是为了用若干种不同级别的优惠政策卖同一种软件,通过select/corp 许可证授权方式得到的xxx的光盘都是VOL这一种、是并不是有很多种,只不过是相同的VOL光盘配以不同的许可证方式;而Volume Licensing (Product) Keys,即VLK,它所指的只是一个Key(密匙),仅仅是一个为证明产品合法化、以及安装所使用的Key,因为根据VOL计划规定,VOL产品是不需要激活的!
  或者说,VLK不是指一种版本,而是指这种版本在部署(deploy)过程中所需要的Key,而需要VLK这种Key的版本应该叫做VOL!只不过在实际中,没有必要强调这种叫法、称呼的准确性,加之很多人的VOL版本光盘是通过企业的选择式许可证、企业协议等方式得到的等等原因,所以才会有很多人叫他为“选择版”等等。
官方网站有一个表格,上面有一句话:“Different products require different Volume Licensing Keys (VLKs). Refer to the table below to make sure you have the correct VLK for your Microsoft product.”,我想这就很好的说明了VLK指的是Key而不是产品了。 很明显的,FPP需要激活,VOL不需要激活

================================================================================

Beta 是希腊字母中的第二个字母β,在软件开发中指软件测试的第二阶段,由将来用户中的一部分人试用。以前,希腊字母alpha指软件开发过程中的第一阶段,包括部件测试,整件测试和系统测试。 Beta测试也指产品推出前的测试,软件商把beta测试版软件在网上发放给更多的用户进行实用测试为以后版本的出台做准备。

Alpha版(内部测试版):一般只在软件开发公司内部运行,不对外公开。主要是开发者自己对产品进行测试,检查产品是否存在缺陷、错误,验证产品功能与说明书、用户手册是否一致。

Beta版(外部测试版):软件开发公司为对外宣传,将非正式产品免费发送给具有典型性的用户,让用户测试该软件的不足之处及存在问题,以便在正式发行前进一步改进和完善。一般可通过Internet免费下载,也可以向软件公司索取。

Demo版(演示版):主要是演示正式软件的部分功能,用户可以从中得知软件的基本操作,为正式产品的发售扩大影响。如果是游戏的话,则只有一两个关卡可以玩。该版本也可以从Internet上免费下载。

Enhace版(增强版或加强版):如果是一般软件,一般称作“增强版”,会加入一些实用的新功能。如果是游戏,一般称作“加强版”,会加入一些新的游戏场景和游戏情节等。这是正式发售的版本。

Free版(自由版):这一般是个人或自由软件联盟组织的成员制作的软件,希望免费给大家使用,没有版权,一般也是通过Internet免费下载。

Full Version版(完全版):也就是正式版,是最终正式发售的版本。

Shareware版(共享版):有些公司为了吸引客户,对于他们制作的某些软件,可以让用户通过Internet免费下载的方式获取。不过,此版本软件多会带有一些使用时间或次数的限制,但可以利用在线注册或电子注册成为正式版用户。

Release版(发行版):不是正式版,带有时间限制,也是为扩大影响所做的宣传策略之一。比如Windows Me的发行版就限制了只能使用几个月,可从Internet上免费下载或由公司免费奉送。

Uprgade版(升级版):当你有某个软件以前的正式版本时,可以购买升级版,将你的软件升级为最新版。升级后的软件与正式版在功能上相同,但价格会低些,这主要是为了给原有的正版用户提供优惠。

测试版与演示版

α:代表该软件仅仅是一个初步完成品,通常只在软件开发者内部交流,也有很少一部分发布给专业测试人员。该版本软件的bug较多,而且极不稳定,用了之后也有可能导致系统崩溃。不过,普通用户难以得到它,即使得到最好也不要安装。

β:该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过大规模的发布测试来进一步消除bug。这一版本通常由软件公司免费发布,用户可从相关的站点下载。通过一些专业爱好者的测试,将结果反馈给开发者,后者再进行有针对性的修改。该版本也不适合一般用户安装。

γ:该版本已经相当成熟了,与即将发行的正式版相差无几,如果用户实在等不及了,尽可以装上一试。

trial:试用版。试用版的软件在最近几年颇为流行,主要是得益于因特网的迅速发展。该版本软件通常都有时间限制,如20天、1个月等等,时间一到,用户就无法再使用了,除非交纳一定的费用进行注册和购买正式版。有些试用版软件还在功能上做了一定的限制。

unregistered:未注册版。未注册版与试用版两者极其类似,只是未注册版通常没有时间限制,在功能上相对于正式版做了一定的限制,例如绝大多数网络电话软件的注册版和未注册版,两者之间在通话质量上有很大差距。还有些虽然在使用上与正式版毫无二致,但是动不动就会弹出一个恼人的消息框来提醒你注册,如看图软件 acdsee。用户如果对该软件感兴趣,可以通过指定的方法向软件公司注册。

demo:非正式版软件中数该版本的知名度最大,称为演示版。演示版仅仅集成了正式版中的几个功能,颇有点像unregistered。不同的是,demo版一般不能升级和通过注册的方法变为正式版。

以上是软件正式版本推出之前的几个版本,α、β、γ可以称为测试版,大凡成熟软件总会有多个测试版,如windows 98的β版,前前后后将近有10个。这么多的测试版一方面保证最终产品尽可能地满足用户的需要,另一方面也尽量减少了软件的bug,在这一点上很值得中国软件商学习。而trial、unregistered、demo有时统称为演示版,这一类版本的广告色彩较为浓厚,颇有点“先尝后买”的味道,对于普通用户而言自然是再好不过了。

正式版

不同类型软件的正式版本通常也有区别。

release:该版本意味“最终释放版”,在出了一系列的测试版之后,终归会有一个正式版本,对于用户而言,购买该版本的软件绝对不会错。该版本有时也称为标准版。值得一提的是release通常不会以单词形式出现在软件封面上,取而代之的是符号(r),如windows nt (r)4.0、ms-dos(r) 6.22等等。

registered: 很显然该版本是与unregistered相对的注册版,注册版、release和standard版一样,都是软件的正式版本。只是注册版软件的前身有很大一部分是从网上下载的。

standard:这是最常见的标准版,不论是什么软件,标准版一定存在。标准版中包含了该软件的基本组件及一些常用功能,可以满足一般用户的需求。其价格相对高一级版本而言还是平易近人的,如金山词霸Ⅲ标准版、office 97标准版等等。

deluxe:顾名思义即为“豪华版”,豪华版通常是相对于标准版而言,无非是多了几个“华而不实”的功能,价格却要高出一大块,因此不推荐一般用户购买。此版本通常是为那些追求“完美”的发烧友所准备的。

reference:该版本的型号常见于百科全书中,比较有名的是微软的encarta系列,reference是最高级别,其包含的主题、图像、影片剪辑等相对于 standard和deluxe版均有大幅增加,容量由一张光盘猛增至三张光盘,并且加入了不少新的交互功能,当然价格也不菲。可以这么说,这一版本的百科全书才能算是真正的百科全书,也是发烧友的收藏首选。

professional:专业版。专业版是针对某些特定的开发工具软件而言的,专业版中有许多内容是标准版中所没有的,这些内容对于一个专业的软件开发人员来说是极为重要的功能。如微软的visual foxpro标准版并不具备编译成可执行文件的功能(.exe文件),这对于一个完整的开发项目而言显然是无法忍受的,除非客户机上也有foxpro,如果用专业版就没有这个问题了。

enterprise:其中文译名为企业版。企业版是开发类软件中的极品(相当于百科全书中的 reference版)。拥有这一套版本的软件可以毫无障碍地开发任何级别的应用软件。如著名的visual c++的企业版相对于专业版包括了几个附加的特性:如sql调试、扩展的存储过程向导、支持as/400对ole db的访问等。而这一版本的价格也是普通用户难以问津的。如,microsoft的visual studio 6.0 enterprise中文版的价格为23000元。

其他版本

除了以上介绍的一些版本外,还有一些专有版本名称。

update:升级版。升级版的软件是不能独立使用的,该版本的软件在安装过程中会搜索原有的正式版,如不存在,则拒绝执行下一步。如microsoft office 97 升级版、windows 95升级版等等。不过有些升级版却是名不副实,经过改动,就可以……

oem:oem通常是出现在硬件中,如今软件业也出现了oem产品。将自己的产品交给别的公司去卖,并打上对方的标记,双方互惠互利,一举两得。

单机(网络)版:网络版在功能、结构上远比单机版复杂,如果留心一下软件的报价,你就会发现某些软件单机版和网络版的价格差别非常巨大,有些甚至多一个客户端口就要加不少钱。

普及版:该版本有时也会被称为共享版,其特点是:价格便宜(有些甚至完全免费),功能单一,针对性强(当然也有占领市场、打击盗版等因素)。与试用版不同的是,该版本的软件一般不会有时间上的限制,当然如果用户想升级,最好还是去购买正式版。

posted @ 2008-04-23 07:13 jadmin 阅读(62) | 评论 (0)编辑 收藏

具体操作如下:

右键单击我的电脑,选择“属性”单击“高级”选项卡,在“性能”里面单击“设置”按钮,选择“高级”选项卡,最下面有“虚拟内存”一项,单击“更改”按钮,在虚拟内存对话框里,选择C盘,选择“自定义大小”,然后在最小值输入框中输入“0”,最大值里也输入“0”,,单击“设置”。

然后选择D盘,设置你需要的最小值和最大值。完成后,单击“设置”按钮,最后,单击“确定”按钮,

系统可能要求重新启动,按“确定”,系统重新启动以后,虚拟内存就放到“D”盘了。

=============================

设置虚拟内存

虚拟内存的概念是相对于物理内存而言的,当系统的物理内存空间入不敷出时,操作系统便会在硬盘上开辟一块磁盘空间当做内存使用,这部分硬盘空间就叫虚拟内存。Windows 98中采用Win386.swp文件的形式,而Windows 2000/XP则采用页面文件pagefile.sys的形式来管理虚拟内存。
一、大小情况
1.一般情况
一般情况下,建议让Windows来自动分配管理虚拟内存,它能根据实际内存的使用情况,动态调整虚拟内存的大小。
2.关于最小值
Windows建议页面文件的最小值应该为当前系统物理内存容量再加上12MB,而对于物理内存容量小于256MB的用户,则建议将页面文件的最小值设得更大些:
①使用128MB或者更少内存的用户,建议将当前物理内存容量的1.75倍设置为页面文件的最小值。
②内存大小在128MB到256MB之间的用户,建议将当前物理内存容量的1.5倍设置为页面文件的最小值。
3.关于最大值
一般来说,页面文件的最大值设置得越大越好,建议设置为最小值的2到3倍。
4.极端情况
假如硬盘空间比较紧张,在设置页面文件时,只需保证它不小于物理内存的3/4即可。

如果物理内存很大(大于512MB),则可以将虚拟内存禁用。(上海 任亚维)
5.根据不同的任务环境设置
①以3D游戏为主的环境
3D游戏对CPU、显卡和内存要求都很高,如果物理内存小于256MB,建议把虚拟内存预设得大一点,这对提高游戏的稳定性和流畅性很有帮助。
②以播放视频为主的环境
视频应用对硬盘空间的“胃口”很大,不过千万不要像在3D游戏环境中一样把虚拟内存设得很大,尤其是Windows XP的用户。因为Windows XP不会自动把不需要的空间释放掉,也就是说那个Pagefiles.sys文件会越来越大。如果你把虚拟内存和Windows XP放在同一分区,播放RM、ASF等视频流文件以后,系统经常会提示你虚拟内存设得太小或是磁盘空间不足。查看此时的页面文件,已经足有1GB大小了。所以建议经常欣赏视频文件的Windows XP用户,把初始数值设小一点,或者将虚拟内存转移到系统盘以外的分区。
二、设置方法
下面以在Windows XP下转移虚拟内存所在盘符为例介绍虚拟内存的设置方法:进入“打开→控制面板→系统”,选择“高级”选项卡,点击“性能”栏中的“设置”按钮,选择“高级”选项卡,点击“虚拟内存”栏内的“更改”按钮,即可进入“虚拟内存”窗口;在驱动器列表中选中系统盘符,然后勾选“无分页文件”选项,再单击“设置”按钮;接着点击其他分区,选择“自定义大小”选项,在“初始大小”和“最大值”中设定数值,然后单击“设置”按钮,最后点击“确定”按钮退出即可。

posted @ 2008-04-22 13:28 jadmin 阅读(68) | 评论 (0)编辑 收藏

Ben Watson,知名开发者。任职于GeoEye,是其所属开发团队的领导者。本文发表于他自己的博客,阐述了十种学习新技术的方法。

1、要看书

在成千上万的编程图书中,可能很大一部分根本毫无用处。但是仍然有很多图书对你的(编程)能力有很大的提升。我一直坚持,相比在网络上查找很多有用信息,在同类图书中查找要来得更容易更快捷。阅读技术图书可心更好地抓住核心内容。对于理论,架构和设计等方面来说,图书也一样很有帮助。只不过要找到高质量的、权威的信息,就不如网络上可供查找的范围广。

2、读代码

这也是我很喜欢的一种方式。如果我并没有几年的专业编程工作经验,在学习之初我并不会去读很多复杂的代码。如果我要是早些开始学习,我将是一个比现在更好的程序员。但是,开始时我会从一些开源项目里,去学习那些源代码(当然,这些代码不能与我的工作有关,也不是我自己写的)。要从自己会使用到的,或者自己感兴趣的程序开始这项工作。我是从 Paint.net 这个网站里开始学习的,而且已经积累了很多关于 .NET 的编程技术。

读别人的代码可以为你提供更多不同的工作思路,这比你完全凭自己思考得到的工作方式要多。

3、写代码

谨记,要写大量的代码。从根本上来讲,最好的学习方法就是实践。如果不写代码,你根本不能把(某种语言中)内在的东西学习透彻。学习之初,可以从一些技术指南和图书中照搬一些尽量简单的程序。当然,这种照搬是要自己完全手工输入,而不是复制和粘贴,这两种之间的效果差别是很大的。这种方法的主旨就在于,使知识内在化,并思考其用法,而不是盲目照抄。学习过程中,要时常查找新的 API 调用方法,这其实是简单的事情。

最重要的是,要写一个你自己的程序,不管它是一个简单的游戏,或者是一个参与开源项目的程序,还是一个公为你自己使用的简单插件。用不同的方式来写程序,尽量尝试使用新的技术,新的技巧,新的设计方式。一定要让现在的项目比以往的项目更好。想要成一个优秀的开发者,这一点是核心。

4、与其他开发者交流

像Apple,微软,Google 等大公司一样的新闻描述的一样,(与其他开发者交流)可以让你解决一些复杂的问题。虽然这并不能让你感觉到自己已经成一个团队或是社区的成员,但是这种方法可以让你接触到更多不同的想法。

不同类型的项目要求不同的设计方法,代码技术,开发流程和设计思想。如果你工作在一个小团队里,你不必与太多的人接触,只要在用户群会议中找到一些人(来讨论)即可。如果这样还不行的话,参与到在线论坛中与其他人讨论(这时你需要更努力地寻找高质量的讨论内容)。

5、教会别人

相对于仅仅读代码之类的工作,教其他人学习可以让你更深入地学习某个技术,这种方法有着非凡的效果。教会别人某个技术,同样也会让你更专注于这种技术,并且可以比别人更深层次地理解它。同样你也会面对一些问题。

“如果你不能向一个六岁的儿童解析清楚一个概念,那么其实是你并没有完全理解它。”Albert Einstein说。

教学场景可以是无穷无尽的:与工作搭档一对一交流,休息碰面,非正式周会,学习茶会,教室,讨论发表会,等等。每周在相同理念开发者之间举办一次30分钟的非正式会议怎么样?每周,让几个人来就他们想要更深入了解话题,向大家传授这些技术知识,并且展开讨论。如果你知道你将要向团队成员们传授正学学习的知识,你是不是更想要了解这项技术的每个细节呢?

6、收听网络电台

如果你有空闲的时间,可以订阅网络电台节目。我现在最喜欢的编程节目就是.Net Rocks。这个节目还会做一些视频节目,叫做dnrTV 。这样会让你即时捕捉到最新最好的技术。一个人是不能学习到所有知识的,而网络电台刚是一个学习了解广泛知识的途径。

7、阅读博客

博客远远比阅读者要多,但是其中有一些却是极其优秀的。我这里不并不是要推荐博客,因为网络上已经有了足够多的博客。这是与真正在开发你所喜欢和使用的软件的开者联系的好方法。

8、学习新的语言

即使你已经在C(++,#) / JAVA 等语言上有很好的造诣,仍然有很多其他可以解决问题的编程语言。学习新的语言,是对你已有思维方式的挑战。这不仅仅是另一种语言,更是对思维的重新架构。的确,所有的语言最后都会被编译成汇编程序,但是这并不意味着高级语言毫无价值。

9、学习不正确的方式

除了要学习应该怎么做,还要学习不应该怎么做。经常阅读 Dailywtf.com ,学习你并不知道的经验与教训。学习适当的面向对象设计方式,代码写作方式和必须要写的代码等,是很好的方式,但是如果不细心的话也容易养成不良习惯。学习认识不正确的思路是负责项目开发至关重要的一环。

维基百科对很多觉的不正确方式有十分透彻的分类。

10、要谦虚。

学习,意味着:

◆用更好的知识代替不完美的知识;
◆增长你所不知道的知识。

只有承认自己有所不足,才能有学习的动力。归根到底,就是谦虚,不对吗?如果你开始认为你已经掌握了所有需要的知识,那么你就危险了。真正的学习是如饥似渴地追逐知识并使其内在化,这需要很大的努力。我们都知道这一点,但是要必须时常不断地提醒自己。

posted @ 2008-04-09 18:34 jadmin 阅读(101) | 评论 (0)编辑 收藏
 1、如何实现关机时清空页面文件

  打开“控制面板”,单击“管理工具→本地安全策略→本地策略→安全选项”,双击其中“关机:清理虚拟内存页面文件”一项,单击弹出菜单中的“已启用”选项,单击“确定”即可。

  2、如何自行配置WindowsXP的服务

  如果你是在单机使用WindowsXP,那么很多服务组件是根本不需要的,额外的服务程序影响了系统的速度,完全可将这些多余的服务组件禁用。单击“开始→控制面板→管理工具→服务”,弹出服务列表窗口,有些服务已经启动,有些则没有。我们可查看相应的服务项目描述,对不需要的服务予以关闭。如“Alerter”,如果你未连上局域网且不需要管理警报,则可将其关闭。

  3、Smartdrv程序有什么作用

  现象:在许多有关WindowsXP安装的介绍文章中都提到:“如果在DOS下安装WindowsXP非常慢,肯定是安装前未运行Smartdrv.exe。我想问这个Smartdrv.exe文件有什么饔?具体如何使用?

  Smartdrv.exe这个文件对于熟悉DOS的朋友肯定很清楚,主要作用是为磁盘文件读写增加高速缓存。大家知道内存的读写速度比磁盘高得多,如果将内存作为磁盘读写的高速缓存可以有效提高系统运行效率。Smartdrv.exe这个文件在Windows各个版本的安装光盘中或是硬盘上的Windows/command/里都有,只有几十KB,把这个文件复制到软盘下,启动系统后直接运行这个程序(可以不加参数,该程序会自动根据内存大小分配适当的内存空间作为高速缓存),再安装WindowsXP即可。另外提醒大家,这个程序在安装完Windows后,不要运行,否则Windows可用内存将减少。

  4、Win32k.sys是什么文件

  现象:我刚装了WindowsXP,可是接下去再装毒霸就发现病毒,位于F:\WINNT\SYSTEM32里的Win32k.sys文件,删又不可删,隔离又不行,在Windows98下或DOS下删就会导致WindowsXP不可启动,请问该文件是干什么用的,有什么方法解决?

  这个文件是WindowsXP多用户管理的驱动文件。在X:\Windows\System32\Dllcache目录下有此文件的备份。只要将此备份拷到X:\Windows\System32下替代带病毒的文件即可。做一张Windows98启动盘,并将Attrib.exe文件拷入软盘,此文件在装有Windows98的机器上的X:\Windows\Command目录下。在BIOS的AdvancedBIOSFeatures中将启动顺序调整为从A盘启动,进入DOS后,进入X:\Windows\System32目录,输入Attrib-s-h-rwin32k.sys,再进入X:\Windows\System32\dllcache目录下输入同样命令,再用copywin32k.sysX:\windows\System32覆盖原文件,再重新启动即可。

  

  5、WindowsXP的开机菜单有什么含义


  现象:最近我安装了WindowsXP操作系统,我知道在启动时按F8键或当计算机不能正常启动时,就会进入WindowsXP启动的高级选项菜单,在这里可以选择除正常启动外的8种不同的模式启动WindowsXP。请问这些模式分别代表什么意思?

  (1)安全模式:选用安全模式启动WindowsXP时,系统只使用一些最基本的文件和驱动程序启动。进入安全模式是诊断故障的一个重要步骤。如果安全模式启动后无法确定问题,或者根本无法启动安全模式,那你就可能需要使用紧急修复磁盘ERD的功能修复系统了。

  (2)网络安全模式:和安全模式类似,但是增加了对网络连接的支持。在局域网环境中解决WindowsXP的启动故障,此选项很有用。

  (3)命令提示符的安全模式:也和安全模式类似,只使用基本的文件和驱动程序启动WindowsXP。但登录后屏幕出现命令提示符,而不是Windows桌面。

  (4)启用启动日志:启动WindowsXP,同时将由系统加载的所有驱动程序和服务记录到文件中。文件名为ntbtlog.txt,位于Windir目录中。该日志对确定系统启动问题的准确原因很有用。

  (5)启用VGA模式:使用基本VGA驱动程序启动WindowsXP。当安装了使WindowsXP不能正常启动的新显卡驱动程序,或由于刷新频率设置不当造成故障时,这种模式十分有用。当在安全模式下启动WindowsXP时,只使用最基本的显卡驱动程序。

  (6)最近一次的正确配置:选择“使用‘最后一次正确的配置?启动WindowsXP”是解决诸如新添加的驱动程序与硬件不相符之类问题的一种方法。用这种方式启动,WindowsXP只恢复注册表项HklmSystemCurrentControlSet下的信息。任何在其他注册表项中所做的更改均保持不变。

  (7)目录服务恢复模式:不适用于WindowsXPProfessional。这是针对WindowsXPServer操作系统的,并只用于还原域控制器上的Sysvol目录和ActiveDirectory目录服务。

  (8)调试模式:启动WindowsXP,同时将调试信息通过串行电缆发送到其他计算机。如果正在或已经使用远程安装服务在你的计算机上安装WindowsXP,可以看到与使用远程安装服务恢复系统相关的附加选项。

  6、如何彻底删除XP

  现象:我装了WindowsMe和WindowsXP双系统,都是FAT32格式。C盘装WindowsMe,E盘装WindowsXP。昨天,WindowsXP系统丢失了SYSTEM32.DLL,启动不了。于是我在进入WindowsMe系统内,在E盘直接删除WindowsXP。但是,每次开机都出现多系统启动菜单,供选择。我该怎样才可以彻底删除XP?

  用一张Windows9x/Me的启动盘启动,在“A:”下输入“SYSC:”,给C盘重新传系统即可。

  7、如何处理WindowsXP不能自动关机现象

  


  现象:我的WindowsXP有时候不能自动关闭电脑,请问应该怎么办?

  安装完WindowsXP之后,有些计算机在单击关闭电脑之后并不能自动关闭,而需像以前的AT电源一样手动关闭。这主要是WindowsXP未启用高级电源管理。修正方法:单击“开始→控制面板→性能和维护→电源选项”,在弹出的电源选项属性设置窗口中,单击“高级电源管理”并勾选“启用高级电源管理支持”。

  8、如何创建“锁定计算机”的快捷方式

  因有急事而需要离开,但又不希望电脑进行系统注销,该怎么办?你完全可以通过双击桌面快捷方式来迅速锁定键盘和显示器,且无需使用“Ctrl+Alt+Del”组合键或屏幕保护程序。操作方法:在桌面上单击鼠标右键,在随后出现的快捷菜单上指向“新建”,并选择“快捷方式”。接着,系统便会启动创建快捷方式向导。请在文本框中输入下列信息:rundll32.exeuser32.dll,LockWorkStation,单击“下一步”。输入快捷方式名称。你可将其命名为“锁定工作站”或选用你所喜欢的任何名称,单击“完成”。你还可对快捷方式图标进行修改(我最喜欢的一个是由Shell32.dll所提供的挂锁图标)。如需修改快捷方式图标,请执行下列操作步骤:右键单击“快捷方式”,并在随后出现的快捷菜单上选择“属性”。选择“快捷方式”选项卡,接着,单击“更改图标”按钮。在以下文件中查找图标文本框中,输入Shell32.dll,单击“确定”。从列表中选择所需图标,并单击“确定”。你还可为快捷方式指定一组快捷键,比如“Ctrl+Alt+L”。这种做法虽然只能帮助你节省一次击键,但却可使操作变得更加灵便。如需添加快捷键组合,请执行下列操作步骤:右键单击“快捷方式”,并在随后出现的快捷菜单上选择“属性”。选择“快捷方式”选项卡,在快捷键文本框中,输入任何键值,而WindowsXP则会将其转换成快捷键组合(一般应采取Ctrl+Alt+任意键的形式)。如欲锁定键盘和显示器,只需双击相关快捷方式或使用所定义的快捷键即可。

  9、如何调整桌面图标颜色质量

  在桌面空白处单击鼠标右键,在打开的“显示属性”对话框中选择“设置”选项卡,通过“颜色质量”下拉列表你可以调整计算机的颜色质量。你也可以通过编辑注册表来调整桌面图标的颜色质量,具体操作步骤:

  打开注册表编辑器,进入HKEY_CURRENT_USER\ControlPanel\Desktop\WindowMetrics子键分支,双击ShellIconBPP键值项,在打开的“编辑字符串”对话框中,“数值数据”文本框内显示了桌面图标的颜色参数,系统默认的图标颜色参数为16。这里提供的可用颜色参数包括:4表示16种颜色,8表示256种颜色,16表示65536种颜色,24表示1600万种颜色,32表示TrueColor(真彩色)。你可以根据自己的不需要选择和设置你的桌面图标颜色参数。单击“确定”关闭“编辑字符串”对话框。注销当前用户并重新启动计算机后设置就生效。

  在桌面空白处单击鼠标右键,在打开的“显示属性”对话框中选择“外观”选项卡,在这里你可以方便地对整个桌面、窗口或者其他项目的字体和图标大小进行调整。

  不过,用这种方式设置图标大小有一定局限性,比如,用户只能选择系统已经提供的桌面大小方案,不能自己任意设置桌面图标的大小。如果你想随心所欲地对桌面图标大小进行调整,可以通过编辑注册表来达到目的。具体操作步骤是:打开注册表编辑器,进入HKEY_CURRENT_USER\ControlPanel\Desktop\WindowMetrics子键分支,双击ShellIconSize键值项,在打开的“编辑字符串”对话框中,“数值数据”文本框内显示了桌面图标的大小参数,系统默认29,用户可以根据自己的需要设置参数大小(参数越大,桌面图标也越大),然后单击“确定”关闭“编辑字符串”对话框。当你注销当前用户并重新启动计算机后设置就生效。

  

  10、如何对系统声音进行选择与设置


  系统声音的选择与设置就是为系统中的事件设置声音,当事件被激活时系统会根据用户的设置自动发出声音提示用户。选择系统声音的操作步骤如下:

  (1)在“控制面板”窗口中双击“声音及音频设备”图标,打开“声音及音频设备”属性对话框,它提供了检查配置系统声音环境的手段。这个对话框包含了音量、声音、音频、语声和硬件共5个选项卡。

  (2)在“声音”选项卡中,“程序事件”列表框中显示了当前WindowsXP中的所有声音事件。如果在声音事件的前面有一个“小喇叭”的标志,表示该声音事件有一个声音提示。要设置声音事件的声音提示,则在“程序事件”列表框中选择声音事件,然后从“声音”下拉列表中选择需要的声音文件作为声音提示。

  (3)用户如果对系统提供的声音文件不满意,可以单击“浏览”按钮,弹出浏览声音对话框。在该对话框中选定声音文件,并单击“确定”按钮,回到“声音”选项卡。

  (4)在WindowsXP中,系统预置了多种声音方案供用户选择。用户可以从“声音方案”下拉表中选择一个方案,以便给声音事件选择声音。

  (5)如果用户要自己设置配音方案,可以在“程序事件”列表框中选择需要的声音文件并配置声音,单击“声音方案”选项组中的“另存为”按钮,打开“将方案存为”对话框。在“将此配音方案存为”文本框中输入声音文件的名称后,单击“确定”按钮即可。如果用户对自己设置的配音方案不满意,可以在“声音方案”选项组中,选定该方案,然后单击“删除”按钮,删除该方案。

  (6)选择“音量”选项卡,打开“音量”选项卡。你可以在“设备音量”选项组中,通过左右调整滑块改变系统输出的音量大小。如果希望在任务栏中显示音量控制图标,可以启用“将音量图标放入任务栏”复选框。

  (7)你想调节各项音频输入输出的音量,单击“设备音量”区域中的“高级”按钮,在弹出的“音量控制”对话框里调节即可。这里列出了从总体音量到CD唱机、PC扬声器等单项输入输出的音量控制功能。你也可以通过选择“静音”来关闭相应的单项音量。

  (8)单击“音量”选项卡中的“扬声器设置”区域中的“高级”按钮后,在弹出的“高级音频属性”对话框你可以为自己的多媒体系统设定最接近你的硬件配置的扬声器模式。

  (9)在“高级音频属性”对话框中选择“性能”选项卡,这里提供了对音频播放及其硬件加速和采样率转换质量的调节功能。要说明的是,并不是所有的选项都是越高越好,你需要根据自己的硬件情况进行设定,较好的质量通常意味着较高的资源占有率。

  设置完毕后,单击“确定”按钮保存设置。

  

  11、如何分配临时管理权限


  许多程序在安装过程中都要求你具备管理权限。这里介绍了一种以普通用户身份登录的情况下,临时为自己分配管理权限的简单方法。在右键单击程序安装文件的同时按住“Shift”键。在随后出现的快捷菜单中单击“运行方式”,输入具有相应管理权限的用户名和密码。这种方式对于开始菜单上的应用程序同样适用。

  12、如何关闭WindowsXP的自动播放功能

  一旦你将多媒体光盘插入驱动器,自动运行就会从驱动器中读取数据,这会造成程序的设置文件和在音频媒体上的音乐立即开始。你可以用下面这个办法关闭这个功能:打开“开始→运行”,在对话框中输入“gpedit.msc”命令,在出现“组策略”窗口中依次选择“在计算机配置→管理模板→系统”,双击“关闭自动播放”,在“设置”选项卡中选“已启用”选项,最后单击“确定”按钮即可(图1-32)。

  

  13、如何恢复被破坏的系统引导文件


  现象:我只安装了WindowsXP系统,但在开机时显示“BOOT.INI非法,正从C:\WINDOWS\启动”,然后就进入了启动状态,并且也能照样工作,请问这是怎么一回事,能否在不重装系统的情况下使系统恢复到正常启动状态?

  出现这种情况是因为C盘下面的“Boot.ini”文件被破坏了。但是由于你的机器中只有一个操作系统,当然它就是默认的操作系统,即使“Boot.ini”文件被破坏了,也将自动地引导该系统进行装载。

  解决的办法是建立一个“Boot.ini”文件即可。其内容为:

  [BootLoader]

  Default=C:

  [OperatingSystems]

  C:\=“MicrosoftWindowsxp”

  14、如何恢复输入法图标

  现象:本人使用WindowsXP中文版,不慎使任务栏隐藏了输入法图标,请问该如何恢复输入法图标。

  打开“控制面板”,双击“区域和语言选项”图标,进入“区域和语言选项”对话框,选择“语言”选项卡,单击“详细信息”按钮,在弹出的对话框中单击“语言栏”按钮,在接着出现的“语言栏设置”对话框中勾选“在桌面上显示语言栏”选项。这时候桌面会出现语言栏,单击右上角的最小化按钮,输入法图标就回到任务栏中去了。

  15、如何恢复误删除的boot.ini文件

  现象:我第一次装WindowsXP时,重启后没有任何问题。但是由于误操作,删掉了C盘目录下的一个文件(文件名是:boot.ini),然后再重启时每次都显示两行字:“boot.ini是非法的。现在正从C:/Windows/下启动”。然后可以顺利进入WindowsXP。但是速度明显慢了,比没删这个文件时慢了很多,而且,每次都要看见那两行字。请问如何修复?

  boot.ini是系统启动时,需要查询的一个系统文件,它告诉启动程序本计算机有几个操作系统、各系统的位置在哪里等信息。重新恢复的方法如下:单击“开始”菜单,依次指向“程序→附件→记事本”,打开“记事本”,在记事本里输入:

  [bootloader]

  timeout=30

  default=multi(0)disk(0)rdisk(0)partition(1)\Windows

  [operatingsystems]

  multi(0)disk(0)rdisk(0)partition(1)\Windows=“MicrosoftWindowsXPProfessional”/fastdetect

  然后将它保存为名字是boot.ini的文件,并将此文件保存到C盘的根目录下即可。

  16、如何加快WindowsXP窗口显示速度

  我们可以通过修改注册表来改变窗口从任务栏弹出,以及最小化回归任务栏的动作,步骤如下:打开注册表编辑器,找到HKEY_CURRENT_USER\ControlPanel\Desktop\WindowMetrics子键分支,在右边的窗口中找到MinAnimate键值,其类型为REG_SZ,默认情况下此健值的值为1,表示打开窗口显示的动画,把它改为0,则禁止动画的显示,接下来从开始菜单中选择“注销”命令,激活刚才所作的修改即可。

  17、如何解决WindowsXP关机出现英文提示

  现象:我的WindowsXP关机时会出现一个进度条,并提示“Toreturntowindowsandcheckthestatusoftheprogramclickcancelifyouchoosetoendtheprogramimmediatelyyouwillloseanyunsaveddata.

  Toendtheprogramnowclickend.”然后就正常关机,但有时却不出现,我想会不会与我的东方影都3的记忆播放有关,但关闭记忆播放功能也无效,请问如何办?

  这是因为你关闭WindowsXP时还有程序在运行,请在关机之前保存并关闭一切应用程序。如果直接单击“End”按钮,那么未保存的任务会丢失,这时可以按“Ctrl+Alt+Del”打开任务管理器,然后关闭应用程序。如果在任务管理器列表中为空,那么就在“系统进程”中将它关闭。如果不进行任何操作,那么系统将在进度条到头时自动关闭未关闭的程序并关闭系统。请你在关机之前关闭一切应用程序、系统驻留程序就不会出现这个提示了。当你确定没有任何需要保存的任务时,可以不必理会此对话框。

  18、如何控制桌面的图标显示

  通常很多用户还是习惯于在桌面上保留“我的文档”及其他经常访问文件夹快捷方式以及经常使用的程序快捷方式。如果你想在桌面上显示“我的电脑”、“我的文档”、“网上邻居”、IE浏览器的快捷方式图标,只需进行如下操作:在桌面单击鼠标右键,在右键菜单中选择“属性”命令,在打开的“显示属性”对话框中选择“桌面”选项卡,单击“自定义桌面”按钮,打开“桌面”项目对话框。在“常规”选项卡的“桌面图标”栏中选择所需项目的复选框,然后单击“确定”返回上一级对话框,再单击“应用”按钮即可。

  19、如何删除WindowsXP的“更新”选项

  对于大多数的用户来说,WindowsXP的WindowsUpdate功能似乎作用不大,我们可以去掉它,操作步骤如下:打开注册表编辑器,找到HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer子键分支,选择“编辑”菜单下的“新建”命令,新建一个类型为REG_DWord的值,名称为NoCommonGroups,双击新建的NoCommonGroups子键,在“编辑字符串”文本框中输入键值“1”,然后单击“确定”按钮并重新启动系统即可。

  20、如何设置音频属性

  打开“控制面板”,双击“声音及音频设备”图标,在“声音及音频设备属性”对话框中,选择“音频”选项卡,在该选项卡中,你可以看到与“声音播放”、“录音”和“MIDI音乐播放”有关的默认设备。当你的计算机上安装有多个音频设备时,就可以在这里选择应用的默认设备,并且还可以调节其音量及进行高级设置。

  进行音频设置的操作步骤如下:

  (1)在“声音播放”选项组中,从“默认设备”下拉列表中选择声音播放的首选设备,一般使用系统默认设备。

  (2)用户如果希望调整声音播放的音量,可以单击“音量控制”窗口,在该窗口中,将音量控制滑块上下拖动即可调整音量大小。

  (3)在该窗口中,用户可以为不同的设备设置音量。例如,当用户在播放CD时,调节“CD音频”选项组中的音量控制滑块,可以改变播放CD的音量;当用户播放MP3和WAV等文件时,用户还可以在“音量控制”窗口进行左右声道的平衡、静音等设置。

  (4)用户如果想选择扬声器或设置系统的播放性能,可以单击“声音播放”选项组中的“高级”按钮,打开“高级音频属性”对话框,在“扬声器”和“性能”选项卡可以分别为自己的多媒体系统设定最接近你的硬件配置的扬声器模式及调节音频播放的硬件加速功能和采样率转换质量。

  (5)在“录音”选项组中,可以从“默认设备”下拉列表中选择录音默认设备。单击“音量”按钮,打开“录音控制”对话窗口。用户可以在该窗口中改变录音左右声道的平衡状态以及录音的音量大小。

  (6)在“MIDI音乐播放”选项组中,从“默认设备”下拉列表中选择MIDI音乐播放默认设备。单击“音量”按钮,打开“音量控制”窗口调整音量大小。

  (7)如果用户使用默认设备工作,可启用“仅使用默认设备”复选框。设置完毕后,单击“应用”按钮保存设置。

  21、如何设置语声效果

  用户在进行语声的输入和输出之前,应对语声属性进行设置。在“声音和音频设备属性”对话框中,选择“语声”选项卡,在该选项卡中,用户不但可以为“声音播放”和“录音”选择默认设备,而且还可调节音量大小及进行语声测试。

  (1)在“声音播放”选项组中,从“默认设备”下拉列表中选择声音播放的设备,单击“音量”按钮,打开“音量控制”窗口调整声音播放的音量。要设置声音播放的高级音频属性,单击“高级”按钮完成设置。

  (2)在“录音”选项组中,从“默认设备”下拉列表中选择语声捕获的默认设备,单击“音量”按钮,打开“录音控制”窗口调整语声捕获的音量。要设置语声捕获的高级属性,单击“高级”按钮完成设置。

  (3)单击“测试硬件”按钮,打开“声音硬件测试向导”对话框,该向导测试选定的声音硬件是否可以同时播放声音和注册语声。注释:要确保测试的准确性,在测试之前必须关闭使用麦克风的所有程序,如语声听写或语声通信程序。

  (4)单击“下一步”按钮,向导开始测试声音硬件,并通过对话框显示检测进度。

  (5)检测完毕后,打开“正在完成声音硬件测试向导”对话框,通告用户检测结果,单击“完成”按钮关闭对话框。

  (6)设置完毕后,单击“确定”按钮保存设置。

  22、如何手动使计算机进入休眠状态

  现象:请问如何用手动方式使WindowsXP的计算机进入休眠状态?

  休眠功能是WindowsXP提供的一项非常酷的特性,它“隐藏”在ShutDown(关机)对话框中。如果你的计算机支持休眠功能,那么借助以下技巧,可通过手动方式使其进入休眠状态。如需以手动方式使你的计算机进入休眠状态,请执行以下操作步骤:选择“开始→关闭计算机”,在关闭Windows对话框中,选择“休眠”。当你的计算机进入休眠状态后,内存中的内容将保存到硬盘上。当你将计算机唤醒时,进入休眠状态前打开的所有程序与文档都将恢复到桌面上。如需在你的计算机上激活休眠支持特性,请执行以下操作步骤:你必须以管理员、Administrators或PowerUsers组成员的身份登录。如果你的计算机与某个网络建立了连接,那么网络策略设置可能会导致这一操作过程无法实现。单击“开始→控制面板→性能和维护→电源选项”,选择“休眠”选项卡,选中“启用休眠”,单击“确定”关闭电源选项对话框。如果休眠选项卡不可用,则说明你的硬件设备无法支持该特性。

  23、如何提高WindowsXP的启动速度

  使用微软提供的“Bootvis”软件可以有效地提高WindowsXP的启动速度。这个工具是微软内部提供的,专门用于提升WindowsXP启动速度。下载解压缩到一个文件夹下,并在“Options”选项中设置使用当前路径。之后从“Trace”选项下拉菜单中选择跟踪方式。该程序会引导WindowsXP重新启动,并记录启动进程,生成相关的BIN文件。之后从Bootvis中调用这个文件,从Trace项下拉菜单中选择“Op-timizesystem”命令即可。

  WindowsXP虽然提供了一个非常好的界面外观,但这样的设置也在极大程度上影响了系统的运行速度。如果你的电脑运行起来速度不是很快,建议将所有的附加桌面设置取消,也就是将WindowsXP的桌面恢复到Windows2000样式。

  设置的方法非常简单:在“我的电脑”上单击鼠标右键,选择“属性”,在“高级”选项卡中单击“性能”项中的“设置”按钮,在关联界面中选择“调整为最佳性能”复选框即可。

  此外,一个对WindowsXP影响重大的硬件就是内存。使用256MB内存运行WindowsXP会比较流畅,512MB的内存可以让系统运行得很好。如果条件允许,最好增大内存。

  24、如何为WindowsXP减肥

  WindowsXP比以往的任何Windows系统都要庞大,其硬盘空间需求1.5GB。虽然相对于能跑WindowsXP的主流电脑来说,一般都拥有10GB以上的硬盘,但一些电脑发烧友有时还是乐于减少WindowsXP的体积。

  (1)删除驱动备份:Windows\Drivercache\i386目录下的Driver.cab文件(73MB)。

  (2)删除Help文档(减掉40多MB)。

  (3)删除Windows\Ime下不用的输入法(日文、韩文、约80MB)。

  (4)把我的文件、IE的临时文件夹转到其他硬盘(分区)。

  (5)把虚拟内存转到其他硬盘(分区)。

  25、如何卸载WindowsXP

  现象:我原来使用的操作系统是Windows98,最近听说WindowsXP非常好,就安装了该系统。第一次安装是从Windows98中安装,装完后觉得不太好,就格式化WindowsXP的分区后重新从DOS安装到D盘,安装完后发现多重启动菜单有三项(第一次装的WindowsXP那一项还在),请问如何删除多余的一项?另外,如果我要删除WindowsXP,除了格式化D盘外,怎样才能将它彻底删除?

  Windows98和WindowsXP双系统的启动菜单是由C盘根目录下的一个文件来控制的,通过修改该文件可以更改启动菜单。要想删除多余的WindowsXP项目,你可以打开C盘根目录下的boot.ini文件,其中有两行重复的“multi(0)…”,删除其中一行即可。要想彻底删除WindowsXP,除了格式化它所在分区之外,你还必须按下面的方法删除多重启动菜单和多余的系统文件:

  (1)制作一张Windows98启动盘,并将Windows98下的sys.com文件拷入该系统盘。

  (2)用该启动盘启动,在A:&gt;下执行sysC:命令。

  (3)删除C盘根目录下多余的文件,这些文件包括:boot.ini、bootfont.bin、bootsect.dos、ntdetect.com、pagefile.sys等。

  

  26、如何隐藏桌面图标


  在WindowsXP中增加了隐藏桌面图标的功能,你只需用鼠标单击桌面空白处,在弹出的右键菜单中选定“排列图标”命令,然后在其下一级级联菜单中取消对“显示桌面图标”命令的选定,系统就会自动将所有桌面图标隐藏。

  如果桌面上图标数量较多,可以用以下方法重新排列图标:在桌面空白区域单击鼠标右键,在弹出菜单中选择“排列图标”,然后在下一级菜单中单击图标排列规则即可。

  利用WindowsXP的“桌面清理”功能,可将你桌面上不使用的图标清理掉。方法是:在上面的“桌面”项目对话框的“常规”选项卡中,如果你选中“每60天运行桌面清理向导”复选框,系统就会每60天自动运行一次桌面清理向导,帮你清理掉桌面上不使用的图标。如果你单击“现在清理桌面”按钮,则系统会立即打开桌面清理向导,将你不使用的快捷方式图标移到一个名为“未使用的桌面快捷方式”的桌面文件夹中。该向导不移动、更改和删除任何程序,如果你想将某个图标重新移回桌面,可以从“未使用的桌面快捷方式”的桌面文件夹中将其还原。

  27、如何在WindowsXP中进行繁体字输入

  使用微软拼音3.0可以进行繁体字输入,你可在系统中选择微软拼音输入法,单击“选项”并在其中选中简、繁转换项,这时输入法状态条中就会有简、繁转换按钮,需要使用它切换即可进行繁体字输入了。

  当然还有其它更多的方法,就不再一一详述了。

  28、如何找回两台电脑相连图标

  现象:我的电脑装的是WindowsXP,在上网时系统托盘内的两台电脑相连的小图标不见了,使我经常不知道是否在线。请问应如何将它恢复?

  小图标不见了的原因在于网络连接的属性设置不对。在Windows98中拨号连接上互联网后,该连接的小图标将自动显示在任务栏上。如果小图标不见了,可右击“我的连接”,选择“属性→设置→选项”,选中“显示调制解调器状态”即可恢复。在Windows2000和WindowsXP中,用户可以控制和设置连接图标的显示和隐藏功能。具体的方法是打开拨号连接或者网络连接的“属性”对话框,然后选中或者清除“连接后在通知区域显示图标”复选框,就可以实现该图标的显示或隐藏了。

  29、如何制作自动系统恢复软盘

  现象:我在WindowsXP下未找到制作紧急修复磁盘的界面,请问如何制作?另外我的Windows98每次启动时都提示输入用户名与密码,请问如何消除?

  WindowsXP的紧急修复磁盘准确的名称应该是“自动系统恢复(ASR)软盘”,它可以备份那些启动系统所需的系统文件。制作方法是:单击“开始→所有程序→附件→系统工具”,然后单击“备份”,单击备份工具向导中的“高级模式”按钮。在“工具”菜单上,单击“ASR向导”;然后按照屏幕上的提示进行操作即可(注意:事先应准备好保存系统设置的1.44MB的空软盘)。使用方法也很简单:将WindowsXP系统的安装光盘插入CD驱动器中,重新启动计算机。在出现安装界面时,按F2,系统将提示你插入以前创建的ASR软盘(ASR不会还原数据文件)。请按照屏幕上的向导进行操作即可自动恢复系统。另外,即使没有“自动系统恢复(ASR)软盘”,也可以使用“修复”功能恢复WindowsXP系统引导菜单,只是某些个人的特殊设置会被恢复成默认设置,使用“自动系统恢复(ASR)软盘”则不会。

  Windows98每次启动时都提示输入用户名与密码,可以按“Esc”键进入Windows98桌面,这时可以将Windows目录下的密码文件*.pwl删除。例如用户名为“qq”,相应的密码文件就是“qq.pwl”,找到它并删除它,然后重新启动电脑,在出现登录窗口时输入原来的用户名“qq”,密码不填,这样就可以消除了。

  30、如何自动关闭停止响应的程序

  在WindowsXP操作系统中,这个设置可以使WindowsXP当诊测到某个应用程序已经停止响应时可以自动关闭它,而不需要进行麻烦的手工干预。想要实现这个功能,就请单击“开始→运行”输入“Regedit”打开注册表编辑器,找到HKEY_CURRENT_USER\ControlPanel\Desktop分支,将AutoEndTasks的键值设置为1即可。

  31、为何不能安装WindowsXP

  现象:我用Windows98启动盘启动计算机并安装WindowsXP,整整花了6个小时才完成,听朋友说他在一台配置和我一样的机器上只花了1个小时的时间就完成了WindowsXP的安装(也是用Windows98启动盘启动),请问WindowsXP正确的安装方法是怎样的?

  这是因为你没有加载磁盘高速缓存的缘故。此文件在Windows98的安装目录下,名为smartdrv.exe,将其拷入软盘,安装之前运行一下就可以了。另外你可以将启动顺序设为从光盘启动,这样WindowsXP的安装盘会自动加载磁盘高速缓存。

  

  32、为何不能在WindowsXP下安装软件


  现象:我在一台安装有WindowsXP家庭版的微机上安装软件时出现“Youdonothaveaccesstomaketherequriedsystemconfigurationmodifications.Pleasereturnthisinstallationfromanadministratorsaccount.”。请问这是为什么?这种软件在Windows98下可正常安装。请问如何解决?

  WindowsXP为了保护系统的安全和稳定,使用了用户账户和密码保护的方式来控制用户的操作。即只有指定的人才能干指定的事情。安装软件等修改系统的操作需要用户拥有该计算机管理员的权力才能执行,这就是你为什么不能安装软件的原因,相信还有很多操作你都执行不了。

  系统在安装时,默认“administrator”帐号是管理员的身份,你可以采用下面的方法使自己成为管理员:

  (1)首先要以管理员的身份进入计算机。单击“开始”菜单上的“注销”,确认进入等待登录的画面,同时按下键盘上的“Ctrl+Alt+Del”键。

  (2)在弹出的对话框中,在用户名框中输入“administrator”,密码框中输入安装时设置的密码。如果你在安装时没有设置密码,密码当然为空,然后回车进入计算机。

  (3)打开“控制面板中”的“用户账户”,就会看到自己的用户账户,在旁边写着“受限的账户”。

  (4)单击打开自己的账户,然后单击“更改账户类型”。在弹出的窗口中,单击选中“计算机管理员”的单选按钮,然后单击“更改账户类型”按钮,确认并退出。你现在就可以干自己想做的事情了。

  为了保证计算机的安全,建议用户设置用户密码,以避免发生安全问题而遭到不必要的损失。

  33、为何更改硬件配置就出现死机现象

  现象:在WindowsXP中只要一更改硬件配置,系统就启动不了,如何解决?

  这是因为WindowsXP中使用了激活产品程序,激活产品程序是微软在WindowsXP中最新加入的防盗版功能。由于激活产品程序会根据你的电脑硬件配置生成一个硬件号,因此如果你改变了改硬件配置,激活产品程序就会发现硬件配置与之不符,这时系统就会停止运行并要求你重新激活产品才可以重新运行。

  

  34、为什么WindowsXP磁盘可用空间不断减少


  现象:我使用WindowsXP时发现,随着不断地增加、删除应用程序,磁盘可用空间不断减少,这是为什么?

  从Windows2000开始,Windows会在每个硬盘分区中建立一个“SystemVolumeInfor-mation”文件夹,在该文件夹中提供的是系统默认保存的系统还原备份文件。不过Windows2000还没有正式提供系统还原功能,在WindowsXP中,你可以看到相关的选项。

  为防止这个问题发生,最简单的方法就是关闭“系统还原”功能。如果要删除这个文件夹中保存的文件,你需要以Administrator(管理员)身份登录系统。

  35、为什么WindowsXP所占空间很大

  


  现象:安装了WindowsXP后,使用一段时间发现经常登陆的一个用户的文件夹所占的空间特别大,大约1.2GB;可是其他不常登陆的只有10MB左右,这是怎么回事?

  WindowsXP为每个用户都设置了各自的文件夹,把登录用户在使用过程中的操作都记录下来,同时也产生各自的临时文件,上网缓存文件,安装软件时产生的共用文件(软件中共用),所以常登录的用户文件夹必然很大。把临时文件与上网的缓存文件删除可以省出很多空间。

  36、如何消除WindowsXP的文档保护功能

  怎样消除WindowsXP的文档保护功能?

  为了完全消除Windows文档保护功能,打开注册表编辑器,设置键值:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon,将SFCDisable的键值设置为0xFFFFFF9D即可

  破坏硬盘主引导扇区

  如果你有机会接触对方的电脑,无论是否是物理接触都可以,只要能在对方的电脑上运行下面这些代码,就可以破坏对方硬盘主引导扇区,象病毒一样厉害。方法是运行debug,然后输入:

  a100

  movax,301

  movbx,1000

  movcx,1

  movdx,80

  int13

  g=10010e
posted @ 2008-04-09 18:27 jadmin 阅读(61) | 评论 (0)编辑 收藏

怎么样用left join 把行改成列

怎么样用left join 把行改成列
有如下表a1

学号     姓名   课程类型   学分数
2001   李四     公共课         2
2001   李四     专业课         1
2001   李四     公共课         3
2001   李四     专业课         2

通过查询器查询成
学号         姓名     公共课学分数   专业课学分数
2001       李四           5                       3

用下面语句
select   学号,姓名,sum(学分数)   as   公共课学分数   from   A1   where   课程类型='公共课'   group   by   学号
left   join   .......
后面怎么写呢,请指教.要不要建个临时表. 20 回复次数:6

第1个回答


SQL code

--------------------------------------------------------------------------------/*

普通行列转换

(爱新觉罗.毓华 2007-11-18于海南三亚)

假设有张学生成绩表(tb)如下:

Name Subject Result

张三 语文  74

张三 数学  83

张三 物理  93

李四 语文  74

李四 数学  84

李四 物理  94

*/

-------------------------------------------------------------------------

/*

想变成

姓名         语文        数学        物理         

---------- ----------- ----------- -----------

李四         74          84          94

张三         74          83          93

*/

create table tb

(

   Name    varchar(10) ,

   Subject varchar(10) ,

   Result int

)

insert into tb(Name , Subject , Result) values('张三' , '语文' , 74)

insert into tb(Name , Subject , Result) values('张三' , '数学' , 83)

insert into tb(Name , Subject , Result) values('张三' , '物理' , 93)

insert into tb(Name , Subject , Result) values('李四' , '语文' , 74)

insert into tb(Name , Subject , Result) values('李四' , '数学' , 84)

insert into tb(Name , Subject , Result) values('李四' , '物理' , 94)

go

--静态SQL,指subject只有语文、数学、物理这三门课程。

select name 姓名,

max(case subject when '语文' then result else 0 end) 语文,

max(case subject when '数学' then result else 0 end) 数学,

max(case subject when '物理' then result else 0 end) 物理

from tb

group by name

/*

姓名         语文        数学        物理         

---------- ----------- ----------- -----------

李四         74          84          94

张三         74          83          93

*/

--动态SQL,指subject不止语文、数学、物理这三门课程。

declare @sql varchar(8000)

set @sql = 'select Name as ' + '姓名'

select @sql = @sql + ' , max(case Subject when ''' + Subject + ''' then Result else 0 end) [' + Subject + ']'

from (select distinct Subject from tb) as a

set @sql = @sql + ' from tb group by name'

exec(@sql)

/*

姓名         数学        物理        语文         

---------- ----------- ----------- -----------

李四         84          94          74

张三         83          93          74

*/

-------------------------------------------------------------------

/*加个平均分,总分

姓名         语文        数学        物理        平均分                总分         

---------- ----------- ----------- ----------- -------------------- -----------

李四         74          84          94          84.00                252

张三         74          83          93          83.33                250

*/

--静态SQL,指subject只有语文、数学、物理这三门课程。

select name 姓名,

max(case subject when '语文' then result else 0 end) 语文,

max(case subject when '数学' then result else 0 end) 数学,

max(case subject when '物理' then result else 0 end) 物理,

cast(avg(result*1.0) as decimal(18,2)) 平均分,

sum(result) 总分

from tb

group by name

/*

姓名         语文        数学        物理        平均分                总分         

---------- ----------- ----------- ----------- -------------------- -----------

李四         74          84          94          84.00                252

张三         74          83          93          83.33                250

*/

--动态SQL,指subject不止语文、数学、物理这三门课程。

declare @sql1 varchar(8000)

set @sql1 = 'select Name as ' + '姓名'

select @sql1 = @sql1 + ' , max(case Subject when ''' + Subject + ''' then Result else 0 end) [' + Subject + ']'

from (select distinct Subject from tb) as a

set @sql1 = @sql1 + ' , cast(avg(result*1.0) as decimal(18,2)) 平均分,sum(result) 总分 from tb group by name'

exec(@sql1)

/*

姓名         数学        物理        语文        平均分                总分         

---------- ----------- ----------- ----------- -------------------- -----------

李四         84          94          74          84.00                252

张三         83          93          74          83.33                250

*/

drop table tb   

---------------------------------------------------------

---------------------------------------------------------

/*

如果上述两表互相换一下:即

姓名 语文 数学 物理

张三 74  83  93

李四 74  84  94

想变成

Name       Subject Result     

---------- ------- -----------

李四         语文      74

李四         数学      84

李四         物理      94

张三         语文      74

张三         数学      83

张三         物理      93

*/

create table tb1

(

   姓名 varchar(10) ,

   语文 int ,

   数学 int ,

   物理 int

)

insert into tb1(姓名 , 语文 , 数学 , 物理) values('张三',74,83,93)

insert into tb1(姓名 , 语文 , 数学 , 物理) values('李四',74,84,94)

select * from

(

select 姓名 as Name , Subject = '语文' , Result = 语文 from tb1

union all

select 姓名 as Name , Subject = '数学' , Result = 数学 from tb1

union all

select 姓名 as Name , Subject = '物理' , Result = 物理 from tb1

) t

order by name , case Subject when '语文' then 1 when '数学' then 2 when '物理' then 3 when '总分' then 4 end

--------------------------------------------------------------------

/*加个平均分,总分

Name       Subject     Result              

---------- -------    --------------------

李四         语文      74.00

李四         数学      84.00

李四         物理      94.00

李四         平均分    84.00

李四         总分      252.00

张三         语文      74.00

张三         数学      83.00

张三         物理      93.00

张三         平均分    83.33

张三         总分      250.00

*/

select * from

(

select 姓名 as Name , Subject = '语文' , Result = 语文 from tb1

union all

select 姓名 as Name , Subject = '数学' , Result = 数学 from tb1

union all

select 姓名 as Name , Subject = '物理' , Result = 物理 from tb1

union all

select 姓名 as Name , Subject = '平均分' , Result = cast((语文 + 数学 + 物理)*1.0/3 as decimal(18,2)) from tb1

union all

select 姓名 as Name , Subject = '总分'
, Result = 语文 + 数学 + 物理 from tb1 ) t order by name , case Subject when '语文' then 1 when '数学' then 2 when '物理' then 3 when '平均分' then 4 when '总分' then 5 end drop table tb1


--------------------------------------------------------------------------------

第2个回答
有如下表a1  

学号           姓名       课程类型       学分数  
2001       李四           公共课                   2  
2001       李四           专业课                   1  
2001       李四           公共课                   3  
2001       李四           专业课                   2  

通过查询器查询成  
学号                   姓名           公共课学分数       专业课学分数  
2001               李四                       5                                               3  

用下面语句  
select       学号,姓名,sum(学分数)       as       公共课学分数       from       A1       where       课程类型='公共课'       group       by       学号  
left       join       .......  
后面怎么写呢,请指教.要不要建个临时表.  


SQL code

--------------------------------------------------------------------------------select 学号,姓名,

sum(case 课程类型 when '公共课' then 学分数 else 0 end) 公共课学分数,

sum(case 课程类型 when '专业课' then 学分数 else 0 end) 专业课学分数

from tb

group by 学号,姓名

--------------------------------------------------------------------------------

第3个回答


SQL code

----------------------------------------------------------------------------------静态SQL,指课程类型只有公共课,专业课

select 学号,姓名,

sum(case 课程类型 when '公共课' then 学分数 else 0 end) 公共课学分数,

sum(case 课程类型 when '专业课' then 学分数 else 0 end) 专业课学分数

from tb

group by 学号,姓名

--静态SQL,指课程类型不止公共课,专业课

declare @sql varchar(8000)

set @sql = 'select 学号,姓名'

select @sql = @sql1 + ' , sum(case 课程类型 when ''' + 课程类型 + ''' then 学分数 else 0 end) [' + 课程类型 + '学分数]'

from (select distinct 课程类型 from tb) as a

set @sql = @sql + ' from tb group by 学号,姓名'

exec(@sql)

--------------------------------------------------------------------------------

第4个回答


SQL code

--------------------------------------------------------------------------------create table tb(学号 int,姓名 varchar(10),课程类型 varchar(10),学分数 int)

insert tb select 2001,'李四','公共课',2

union all select 2001,'李四','专业课',1

union all select 2001,'李四','公共课',3

union all select 2001,'李四','专业课',2

select 学号,姓名,

       公共课=sum(case 课程类型 when '公共课' then 学分数 else 0 end),

       专业课=sum(case 课程类型 when '专业课' then 学分数 else 0 end)

from tb

group by 学号,姓名

--动态SQL

declare @sql varchar(4000)

select @sql='select 学号,姓名'

select @sql=@sql+',['+课程类型+']=sum(case 课程类型 when '''+课程类型+''' then 学分数 else 0 end)'

from tb group by 课程类型

exec(@sql+N' from tb group by 学号,姓名')

drop table tb

/*

学号          姓名         公共课         专业课        

----------- ---------- ----------- -----------

2001        李四         5           3

*/

--------------------------------------------------------------------------------

第5个回答
select     学号,姓名,
sum(decode(课程类型,'公共课',   学分数,   0)   公共课学分数,
sum(decode(课程类型,   '专业课',学分数,   0)   专业课学分数
from   tb
group   by   学号,姓名

posted @ 2008-04-08 21:49 jadmin 阅读(110) | 评论 (0)编辑 收藏

// 第一步:创建一个TableViewer对象。
TableViewer tv = new TableViewer(shell, SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION);

// 第二步:通过表格内含的Table对象设置布局方式
Table table = tv.getTable();
table.setHeaderVisible(true); // 显示表头
table.setLinesVisible(true); // 显示表格线
TableLayout layout = new TableLayout(); // 专用于表格的布局
table.setLayout(layout);

// 第三步:用TableColumn类创建表格列
layout.addColumnData(new ColumnWeightData(13));// ID列宽13像素
new TableColumn(table, SWT.NONE).setText("ID号");
layout.addColumnData(new ColumnWeightData(40));
new TableColumn(table, SWT.NONE).setText("姓名");
layout.addColumnData(new ColumnWeightData(20));
new TableColumn(table, SWT.NONE).setText("性别");
layout.addColumnData(new ColumnWeightData(20));
new TableColumn(table, SWT.NONE).setText("年龄");
layout.addColumnData(new ColumnWeightData(60));
new TableColumn(table, SWT.NONE).setText("记录建立时间");

// 第四步:设置内容器和标签器
tv.setContentProvider(new TableViewerContentProvider());
tv.setLabelProvider(new TableViewerLabelProvider());

// 第五步:用TableViewer的setInput方法将数据输入到表格
Object data = PeopleFactory.getPeoples();
tv.setInput(data);

其中:
TableViewerContentProvider.java 内容器
TableViewerLabelProvider.java    标签器
PeopleFactory.java               产生TableViewer的数据源

从整体上把握以上5点,TableViewer用起来就简单了

Tags:java,rcp,jface,swt,ibm,eclipse,ui,gui

posted @ 2008-03-31 11:53 jadmin 阅读(570) | 评论 (0)编辑 收藏

       近来看了很多程序员的言论……感觉都是满腹牢骚,一肚子愤懑。我想要说的是,程序员不是神。时下一些程序员所能作的,其实大多数普通人通过一段时间的培训和学习都可以作。编程工具已越来越容易使用,编程思想越来越成熟,计算机书籍更是琳琅满目,开发过程中的规范性也已经越来越重要。作为一个普普通通的程序员,只是几个通宵的投入,1-2个月囫囵吞枣的学习,又能有什么理由能奢求太多?

  其实当很多人计算着自己一行代码值几毛钱的时候,或许他从来没有踏踏实实去考虑自己的能力和水平究竟如何。坦诚的讲,大多数程序员的代码质量和设计质量充其量只能算入门水平,学什么东西也只是皮毛而已,尽管可能简历上写得是精通……

  这社会是现实的,发展的。十多年前,程序员是个真正的有门槛的行业。因为那时候没那么多漂亮的开发工具,没有高级语言的支持,甚至连面向对象的开发思想都还仅仅是萌芽,更不用说什么质量控制体系。那时候要掌握开发技术远比其他行业的技术困难得多,只有少数精英才能做到,而且还需要有过人的智力、耐心与毅力。要付出很多很多。这些“精英”,自然也能得到社会足够的尊重。而现在,在前辈的辛苦耕耘下,进入这个行业几乎已经没有了门槛。当你怀着对前人获得的财富、荣誉和使命感走上程序员这条路的时候,可能你根本就不知道等待自己的将是什么……其实,而今在自己公司一小群程序员里“冒充”某某方面专家的时候,很多人却不知道甚至根本无法想像,在不很久远的过去,自己或许只能选择中途放弃。

  虽然不是每个人都这样,但是我还是要说:有人根本没毅力,他们只想轻轻松松的赚钱,却从来不想承担自己该承担的责任;有人根本没创造性,他唯一会作的就是把别人的代码抓来抄袭一下;有人始终自以为是,代码能编译通过和运行了就觉得可以OK完工了;有人的设计和代码糟糕冗余,可被其他人精简2-3倍长,性能也可优化数十倍;有人技术视野狭隘,搞C++的就觉得Java是个Sun的新式玩具,搞Java就鄙夷C++为洪水猛兽和怪物;更有人还把用别人发现的漏洞、别人开发的工具去黑黑别人的电脑当做自己已经是牛人和所谓的“黑客”。这些“程序员”真的能算程序员么?如果这也算程序员,我认为他们也只是现在这个时代最平庸的一群程序员而已……

  其实任何行业、任何职业都会面临这样或那样的机遇,总有不知道的困难、烦恼在前面等着你。大家羡慕的只是成功时的鲜花和啤酒,又有几个人真正去羡慕成功背后的艰辛和苦楚呢?作销售好,作管理好,作老板好,作XX好……这样类似的话我听得都厌烦了。要我说,作自己最好。

  在这社会上,我们或许习惯了用社会的、他人的标准去衡量、去比较,比如什么女朋友好不好、老婆好不好、工作好不好、老板好不好之类的。诚然,人不能脱离现实、脱离社会,人需要生存。但今天社会给我们所提供的机会,已经比我们的父辈开阔了许多许多。我不认为一个有才能、肯吃苦的人会失去谋生的机会,或者说会生存得比我们的父辈还要艰辛,我们还有什么可抱怨的?也许我们没有搭上某趟快速致富的列车,也许我们看到其他搭上列车的人的成功心理难以平衡,但是不是说我们就因此无法自信的在朋友面前抬起头,无法博得一份属于自己的爱情,无法获得其他人的尊重,无法去作一份有意义的工作,无法享受一份美好的生活呢?

  其实,没人能剥夺别人的快乐,没人能总结出一条适用于所有人的所谓“成功”标准,没人能鱼和熊掌兼得。有些人茫然的来,如同另外一些人茫然的走,没有带来什么,更没有留下什么,这就是而今大多数浮躁的程序员的道路。或许,我们的国家现在还没有美国那样重视知识、重视科学、重视创新,然而除非你自己拒绝所有的机会,不然社会也同样不会让有开发知识、有创新能力的人都被逼得当街卖烧烤。

  不懂得珍惜现在的人,永远不可能把握未来……没有好的心态,就已经先失败了一半。作什么其实不重要,然而智慧的人知道怎么踏踏实实的去走脚下的路,平庸的人却只知道羡慕和抱怨,从来不留意脚下的路。

  最后用一句话来总结:程序员不是神,心态决定一切,成功在你脚下。

posted @ 2008-03-28 19:01 jadmin 阅读(81) | 评论 (0)编辑 收藏
Carlos Perez(著名的Java技术人员)最近发表了他认为值得在2008学习五种的JAVA技术,它们是:

OSGi(基于Java的动态模型规范)
注:OSGi(Open Service Gateway Initiative)指OSGi Alliance组织制定的一个基于Java语言的服务(业务)规范——OSGi服务平台(Service Platform)。 该规范和核心部分是一个框架 ,其中定义了应用程序的生命周期模式和服务注册。这个框架实现了一个优雅、完整和动态的组件模型。应用程序(称为bundle)无需重新引导可以被远程安装、启动、升级和卸载(其中Java包/类的管理被详细定义)。API中还定义了运行远程下载管理政策的生命周期管理。服务注册允许bundles去检测新服务和取消的服务,然后相应配合。

Java内容仓库,最早于2002年2月由JCP发布
注:JCP(Java Community Process) 是一个开放的国际组织,主要由Java开发者以及被授权者组成,职能是发展和更新Java技术规范、参考实现(RI)、技术兼容包(TCK)。JCP维护的规范包括J2ME、J2SE、J2EE,XML,OSS,JAIN等。组织成员可以提交JCR(Java SpECification RequESts),通过特定程序以后,进入到下一版本的规范里面。

Google Web Toolkit(最早发布于2006年5月)
注:GWT(Google Web Toolkit) 是 Google 推出的一个开发 Ajax 应用的框架,它支持用 Java 开发和调试 Ajax 应用。

Groovy(最早发布于2004年5月)
注:Groovy是一种面向对象的程序设计语言,作为Java程序设计语言的一种可选替代品,并增加了Python,Ruby和Smalltalk中的一些特性。

云雾计算(用于虚拟服务器的设计理念,或无需EJB的分布式计算)
注:“云雾计算”的英文即Cloud Computing。自谷歌发展起来之后,极大规模的服务器集中在一起,统一管理,形成了“云雾计算”(“Cloud Computing”)的物质基础。“云雾计算”是社会计算能力的大集中,也是所谓“SaaS”的客观基础。

有趣的是,其中有几种技术已经成熟,或者说是过时,并且正在其被推荐应用的项目中有成熟的应用。当然,这五种技术都很有价值。OSGi是Eclipse的建模系统,Goovry因其正式的规范说明和频繁的改进版发布赢得支持。GWT也算是成熟稳定,云雾计算正在受到更为广泛市场的接受。

JRC和云雾计算是最近才被广泛接受的技术,但是厂商正在利用竞争和商业关系猎取支持来发布相关的产品(比如GridGain,Gigaspaces,和Terracotta),而有的厂商还正在对此进行策划(TSS计划在TSSJS2008发布JCR相关的声明)。
posted @ 2008-02-12 16:30 jadmin 阅读(61) | 评论 (0)编辑 收藏

一些基本的命令往往可以在保护网络安全上起到很大的作用,下面几条命令的作用就非常突出。

一、检测网络连接
如果你怀疑自己的计算机上被别人安装了木马,或者是中了病毒,但是手里没有完善的工具来检测是不是真有这样的事情发生,那可以使用Windows自带的网络命令来看看谁在连接你的计算机。
具 体的命令格式是:netstat -an这个命令能看到所有和本地计算机建立连接的IP,它包含四个部分——proto(连接方式)、local address(本地连接地址)、foreign address(和本地建立连接的地址)、state(当前端口状态)。通过这个命令的详细信息,我们就可以完全监控计算机上的连接,从而达到控制计算机 的目的。


二、禁用不明服务
很多朋友在某天系统重新启动后会发现计算机速度变慢了,不管怎么优化都慢,用杀毒软件也查不出问题,这个时候很 可能是别人通过入侵你的计算机后给你开放了特别的某种服务,比如IIS信息服务等,这样你的杀毒软件是查不出来的。但是别急,可以通过“net start”来查看系统中究竟有什么服务在开启,如果发现了不是自己开放的服务,我们就可以有针对性地禁用这个服务了。
方法就是直接输入“net start”来查看服务,再用“net stop server”来禁止服务。


三、轻松检查账户
很 长一段时间,恶意的攻击者非常喜欢使用克隆账号的方法来控制你的计算机。他们采用的方法就是激活一个系统中的默认账户,但这个账户是不经常用的,然后使用 工具把这个账户提升到管理员权限,从表面上看来这个账户还是和原来一样,但是这个克隆的账户却是系统中最大的安全隐患。恶意的攻击者可以通过这个账户任意 地控制你的计算机。
为了避免这种情况,可以用很简单的方法对账户进行检测。
首先在命令行下输入net user,查看计算机上有些什么用户,然后再使用“net user+用户名”查看这个用户是属于什么权限的,一般除了Administrator是administrators组的,其他都不是!如果你发现一个 系统内置的用户是属于administrators组的,那几乎肯定你被入侵了,而且别人在你的计算机上克隆了账户。快使用“net user用户名/del”来删掉这个用户吧!
联网状态下的客户端。对于没有联网的客户端,当其联网之后也会在第一时间内收到更新信息将病毒特征库更新到最新版本。不仅省去了用户去手动更新的烦琐过程,也使用户的计算机时刻处于最佳的保护环境之下。

posted @ 2008-01-19 19:48 jadmin 阅读(72) | 评论 (0)编辑 收藏
1.avi格式代码片断如下:

<object id="video" width="400" height="200" border="0" classid="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA">
<param name="ShowDisplay" value="0">
<param name="ShowControls" value="1">
<param name="AutoStart" value="1">
<param name="AutoRewind" value="0">
<param name="PlayCount" value="0">
<param name="Appearance value="0 value=""">
<param name="BorderStyle value="0 value=""">
<param name="MovieWindowHeight" value="240">
<param name="MovieWindowWidth" value="320">
<param name="FileName" value="/Mbar.avi">
<embed width="400" height="200" border="0" showdisplay="0" showcontrols="1" autostart="1" autorewind="0" playcount="0" moviewindowheight="240" moviewindowwidth="320" filename="/Mbar.avi" src="Mbar.avi">
</embed>
</object>


2.mpg格式代码片断如下:

<object classid="clsid:05589FA1-C356-11CE-BF01-00AA0055595A" id="ActiveMovie1" width="239" height="250">
<param name="Appearance" value="0">
<param name="AutoStart" value="-1">
<param name="AllowChangeDisplayMode" value="-1">
<param name="AllowHideDisplay" value="0">
<param name="AllowHideControls" value="-1">
<param name="AutoRewind" value="-1">
<param name="Balance" value="0">
<param name="CurrentPosition" value="0">
<param name="DisplayBackColor" value="0">
<param name="DisplayForeColor" value="16777215">
<param name="DisplayMode" value="0">
<param name="Enabled" value="-1">
<param name="EnableContextMenu" value="-1">
<param name="EnablePositionControls" value="-1">
<param name="EnableSelectionControls" value="0">
<param name="EnableTracker" value="-1">
<param name="Filename" value="/mpeg/halali.mpg" valuetype="ref">
<param name="FullScreenMode" value="0">
<param name="MovieWindowSize" value="0">
<param name="PlayCount" value="1">
<param name="Rate" value="1">
<param name="SelectionStart" value="-1">
<param name="SelectionEnd" value="-1">
<param name="ShowControls" value="-1">
<param name="ShowDisplay" value="-1">
<param name="ShowPositionControls" value="0">
<param name="ShowTracker" value="-1">
<param name="Volume" value="-480">
</object>


3.smi格式代码片断如下:

<OBJECT id=RVOCX classid=clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA width=240 height=180>
<param name="_ExtentX" value="6350">
<param name="_ExtentY" value="4763">
<param name="AUTOSTART" value="-1">
<param name="SHUFFLE" value="0">
<param name="PREFETCH" value="0">
<param name="NOLABELS" value="-1">
<param name="SRC" value="rm.rm">
<param name="CONTROLS" value="ImageWindow">
<param name="CONSOLE" value="console1">
<param name="LOOP" value="0">
<param name="NUMLOOP" value="0">
<param name="CENTER" value="0">
<param name="MAINTAINASPECT" value="0">
<param name="BACKGROUNDCOLOR" value="#000000"><embed src="real.smi" type="audio/x-pn-realaudio-plugin" console="Console1" controls="ImageWindow" height="180" width="240" autostart="true"></OBJECT>



4.rm格式代码片断如下:

<OBJECT ID=video1 CLASSID="clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA" HEIGHT=288 WIDTH=352>
<param name="_ExtentX" value="9313">
<param name="_ExtentY" value="7620">
<param name="AUTOSTART" value="0">
<param name="SHUFFLE" value="0">
<param name="PREFETCH" value="0">
<param name="NOLABELS" value="0">
<param name="SRC" value="rtsp://203.*.*.35/vod/dawan-a.rm">
<param name="CONTROLS" value="ImageWindow">
<param name="CONSOLE" value="Clip1">
<param name="LOOP" value="0">
<param name="NUMLOOP" value="0">
<param name="CENTER" value="0">
<param name="MAINTAINASPECT" value="0">
<param name="BACKGROUNDCOLOR" value="#000000"><embed SRC type="audio/x-pn-realaudio-plugin" CONSOLE="Clip1" CONTROLS="ImageWindow" HEIGHT="288" WIDTH="352" AUTOSTART="false">
</OBJECT>



5.wmv格式代码片断如下:

<object id="NSPlay" width=200 height=180 classid="CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95" codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=6,4,5,715" standby="Loading Microsoft Windows Media Player components..." type="application/x-oleobject" align="right" hspace="5">
<param name="AutoRewind" value=1>
<param name="FileName" value="http://192.168.1.71/verypower/shichang.wmv">
<param name="ShowControls" value="1">
<param name="ShowPositionControls" value="0">
<param name="ShowAudioControls" value="1">
<param name="ShowTracker" value="0">
<param name="ShowDisplay" value="0">
<param name="ShowStatusBar" value="0">
<param name="ShowGotoBar" value="0">
<param name="ShowCaptioning" value="0">
<param name="AutoStart" value=1>
<param name="Volume" value="-2500">
<param name="AnimationAtStart" value="0">
<param name="TransparentAtStart" value="0">
<param name="AllowChangeDisplaySize" value="0">
<param name="AllowScan" value="0">
<param name="EnableContextMenu" value="0">
<param name="ClickToPlay" value="0">
</object>



6.wma格式 放在 里面。下面是部分解释:

<object classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95" id="MediaPlayer1" >
<param name="Filename" value="/blog/1.Wma"> <!--你文件的位置-->
<param name="PlayCount" value="1"><!--控制重复次数: “x”为几重复播放几次; x=0,无限循环。-->
<param name="AutoStart" value="0"><!--控制播放方式: x=1,打开网页自动播放; x=0,按播放键播放。-->
<param name="ClickToPlay" value="1"><!--控制播放开关: x=1,可鼠标点击控制播放或暂停状态; x=0,禁用此功能。-->
<param name="DisplaySize" value="0"><!--控制播放画面: x=0,原始大小; x=1,一半大小; x=2,2倍大小。-->
<param name="EnableFullScreen Controls" value="1"><!--控制切换全屏: x=1,允许切换为全屏; x=0,禁用此功能。-->
<param name="ShowAudio Controls" value="1"><!--控制音量: x=1,允许调节音量; x=0,禁止音量调节。-->
<param name="EnableContext Menu" value="1"><!--控制快捷菜单: x=1,允许使用右键菜单; x=0,禁用右键菜单。-->
<param name="ShowDisplay" value="1"><!--控制版权信息: x=1,显示电影及作者信息;x=0,不显示相关信息-->
</object>



7.Windows Media Player 系列(不同面板样式)综合型:

<object classid=clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95 codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=6,1,5,217"id=MediaPlayer type=application/x-oleobject width=210 height=340 standby="Loading Microsoft Windows Media Player components..." VIEWASTEXT align=MIDDLE>
<param name=AudioStream value=-1>
<param name=AutoSize value=0>
<param name=AutoStart value=1>
<param name=AnimationAtStart value=0>
<param name=AllowScan value=-1>
<param name=AllowChangeDisplaySize value=0>
<param name=AutoRewind value=0>
<param name=Balance value=0>
<param name=BaseURL value="">
<param name=BufferingTime value=5>
<param name=CaptioningID value="">
<param name=ClickToPlay value=0>
<param name=CursorType value=32512>
<param name=CurrentPosition value=-1>
<param name=CurrentMarker value=0>
<param name=DefaultFrame value=1>
<param name=DisplayBackColor value=0>
<param name=DisplayForeColor value=16777215>
<param name=DisplayMode value=0>
<param name=DisplaySize value=0>
<param name=Enabled value=-1>
<param name=EnableContextMenu value=-1>
<param name=EnablePositionControls value=0>
<param name=EnableFullScreenControls value=0>
<param name=EnableTracker value=1>
<param name=Filename value="http://flash.jninfo.net/swf/y1271.swf">
<param name=InvokeURLs value=-1>
<param name=Language value=-1>
<param name=Mute value=0>
<param name=PlayCount value=1>
<param name=PreviewMode value=0>
<param name=Rate value=1>
<param name=SAMILang value="">
<param name=SAMIStyle value="">
<param name=SAMIFileName value="">
<param name=SelectionStart value=0>
<param name=SelectionEnd value=true>
<param name=SendOpenStateChangeEvents value=-1>
<param name=SendWarningEvents value=-1>
<param name=SendErrorEvents value=-1>
<param name=SendKeyboardEvents value=0>
<param name=SendMouseClickEvents value=0>
<param name=SendMouseMoveEvents value=0>
<param name=SendPlayStateChangeEvents value=-1>
<param name=ShowCaptioning value=0>
<param name=ShowControls value=1>
<param name=ShowAudioControls value=1>
<param name=ShowDisplay value=1>
<param name=ShowGotoBar value=1>
<param name=ShowPositionControls value=1>
<param name=ShowStatusBar value=1>
<param name=ShowTracker value=1>
<param name=TransparentAtStart value=0>
<param name=VideoBorderWidth value=0>
<param name=VideoBorderColor value=0>
<param name=VideoBorder3D value=0>
<param name=Volume value=-1070>
<param name=WindowlessVideo value=1>
</object>



8 FLASH系列

<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0" width="266" height="240">
<param name="movie" value="http://user.net163.com/zjqz/mtv/fskl.swf">
<param name="quality" value="high">
<embed src="http://flash.jninfo.net/swf/y1271.swf" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="266" height="240"></embed></object>



简易型:

<EMBED src=http://vod.alibaba.com/50/06/71/71/50067171_56.asf style="HEIGHT: 45px; WIDTH: 190px" type=audio/mpeg AUTOSTART="1" loop="0">
</EMBED>



标签型:

<embed width=240 height=140 transparentatstart=true animationatstart=false autostart=true autosize=false volume=100 displaysize=0 showdisplay=true showstatusbar=true showcontrols=true showaudiocontrols=true showtracker=true showpositioncontrols=true balance=true src="http://vod.alibaba.com/50/06/71/71/50067171_56.asf">
</embed>



引用内容:媒体播放器的外观界面
在网页中,你可以通过相关属性来控制媒体播放器的哪些部分出现,哪些部分不出现。
媒体播放器包括如下元素:
Video Display Panel:视频显示面板;
Video Border:视频边框;
Closed Captioning Display Panel;字幕显示面板;
Track Bar;搜索栏;
Control Bar with Audio and Position Controls:带有声音和位置控制的控制栏;
Go To Bar:转到栏;
Display Panel:显示面板;
Status Bar:状态栏;
下面的属性用来决定显示哪一个元素:
ShowControls 属性:是否显示控制栏(包括播放控件及可选的声音和位置控件);
ShowAudioControls 属性:是否在控制栏显示声音控件(静音按钮和音量滑块);
ShowPositionControls 属性:是否在控制栏显示位置控件(包括向后跳进、快退、快进、向前跳进、预览播放列表中的每个剪辑);
ShowTracker 属性:是否显示搜索栏;
ShowDisplay 属性:是否显示显示面板(用来提供节目与剪辑的信息);
ShowCaptioning 属性:是否显示字幕显示面板;
ShowGotoBar 属性:是否显示转到栏;
ShowStatusBar 属性:是否显示状态栏;


□播放列表
媒体播放器提供下面的方法来访问播放列表中的剪辑:
Next 方法,跳到节目(播放列表)中的下一个剪辑;
Previous 方法,跳回到节目中的上一个剪辑;
媒体播放器的一个特性是能够预览节目中的每一个剪辑,使用如下属性:
PreviewMode 属性,决定媒体播放器当前是否处于预览模式;
CanPreview 属性,决定媒体播放器能否处于预览模式;
在windows 媒体元文件中,可以为每一个剪辑指定预览时间——PREVIEWDURATION,如果没有指定,那么默认的预览时间是10秒钟。
你也可以用Windows 媒体元文件来添加 watermarks 与 banners,元文件也支持插入广告时的无间隙流切换。



□节目信息
使用 GetMediaInfoString 方法可以返回相关剪辑或节目的如下信息:
文件名:File name
标题:Title
描述:Description
作者:Author
版权:Copyright
级别:Rating
URLs:logo icon、watermark、banner的地址
剪辑信息可以放在媒体文件中,也可以放在Windows 媒体元文件中,或者两者都放。如果在元文件中指定了剪辑信息,那么用 GetMediaInfoString 方法返回的就是元文件中的信息,而不会返回剪辑中包含的信息。
在元文件中,附加信息可以放置在每一个剪辑或节目的 PARAM标签中。你可以为每个剪辑添加任意多个 PARAM 标签,用来存储自定义的信息或链接到相关站点。在 PARAM 标签中的信息可以通过 GetMediaParameter 方法来访问。
下面的属性返回有关大小和时间的信息:
ImageSourceHeight、ImageSourceWidth:返回图像窗口的显示尺寸;
Duration 属性,返回剪辑的长度(秒), 要检测这个属性是否包含有效的数值,请检查IsDurationValid 属性。(对于广播的视频,其长度是不可预知的)。



□字幕
你可以用 .smi 文件来为你的节目添加字幕。媒体播放器支持下面的属性来处理字幕:
SAMIFileName 属性,指定 .smi 文件的名字;
SAMILang 属性,指定字幕的语言(如果没有指定则使用第一种语言);
SAMIStyle 属性,指定字幕的文字大小和样式;
ShowCaptioning 属性,决定是否显示字幕显示面板;



□脚本命令
伴随音频、视频流,你可以在流媒体文件中加入脚本命令。脚本命令是多媒体流中与特定时间同步的多对Unicode串。第一个串标识待发命令的类型,第二个串指定要执行的命令。
当流播放到与脚本相关的时间时,控件会向网页发送一个 ScriptCommand事件,然后由事件处理进程来响应这个事件。脚本命令字符串会作为脚本命令事件的参数传递给事件处理器。
媒体播放器会自动处理下面类型的内嵌脚本命令:
1)URL型命令:当媒体播放器控件收到一个URL型的命令时,指定的URL会被装载到用户的默认浏览器。如果媒体播放器嵌在一个分帧的HTML文件中,URL页可以装载到由脚本命令指定的帧内。如果脚本命令没有指定一个帧,将由 DefaultFrame 属性决定将 URL 页装入哪一帧。
你可以通过设置 InvokeURLs 属性来决定是否自动处理 URL 型的脚本命令。如果这个属性的值为 false ,媒体播放器控件将忽视 URL型命令。但是脚本命令事件仍会触发,这就允许你有选择地处理 URL 型命令。
URL 型命令指定的是 URL 的相对地址。基地址是由 BaseURL属性指定的。媒体播放器控件传送的脚本命令事件的命令参数是链接好的地址。
2)FILENAME型命令:当媒体播放器控件收到一个FILENAME型的命令时,它将 FileName属性设置为脚本命令提供的文件,之后媒体播放器会打开这个文件开始播放。 媒体播放器控件总是自动处理 FILENAME 型命令,不象 URL 型命令,它们不能被禁止。
3)TEXT型命令:当媒体播放器控件收到一个 TEXT型的命令时,它会将命令的内容显示在控件的字幕窗口。内容可以是纯文本的,也可以是 HTML。
4)EVENT型命令:当媒体播放器控件收到一个 EVENT型的命令时,它会在媒体元文件中搜索 EVENT 元素的 NAME 属性。如果 NAME 属性与脚本命令中的第二个字符串匹配,媒体播放器控件就执行包含在 EVENT 元素中的条目。
5)OPENEVENT型命令:当媒体播放器控件收到一个 OPENEVENT型的命令时,它会在媒体元文件中检查 EVENT 元素,并打开匹配的标题,但不播放,直到收到来自 EVENT型命令的同名真实事件。

□捕捉键盘和鼠标事件
EnableContextMenu 与 ClickToPlay 属性为用户提供了在图像窗口进行操作的方法。
如果 EnableContextMenu 属性为 true ,在图像窗口右击鼠标可以打开关联菜单,如果将ClickToPlay 属性设为 true ,用户可以单击图像窗口进行播放与暂停的切换。
要接收鼠标移动和单击事件,请将 SendMouseMoveEvents 和 SendMouseClickEvents 属性设为 true 。鼠标事件有:
MouseDown,当用户按下鼠标时产生;
MouseUp,当用户释放鼠标时产生;
MouseMove,当用户移动鼠标时产生;
Click,当用户在媒体播放器上单击鼠标按钮时产生;
DbClick,当用户在媒体播放器上双击鼠标按钮时产生;
要接收键盘事件,请将 SendKeyboardEvents 属性设为 true 。键盘事件有:
KeyDown,当用户按下一个键时产生;
KeyUp,当用户释放一个键时产生;
KeyPress,当用户按下并释放一个键时产生;



□监测流状态与网络链接
流状态属性包括:
PlayState:播放状态;
OpenState:打开状态;
Bandwidth:带宽;
支持的事件有:
OpenStateChange:打开状态改变(仅当SendOpenStateChangeEvents属性为true时触发)
PlayStateChange:播放状态改变(仅当SendPlayStateChangeEvents属性为true时触发)
EndOfStream:流结束时触发;
NewStream:打开新流时触发;
网络接收属性包括:
ReceptionQuality:接收质量;
ReceivedPackets:已经收到的包;
LostPackets:丢失的包;
监测缓冲的属性有:
BufferingTime:缓冲时间;
BufferingCount:缓冲次数;
BufferingProgress:缓冲进程;
Buffering:缓冲事件;



□错误处理
媒体播放器提供了内建的错误处理功能——在对话框或状态栏显示错误信息。 另外,你可以自己添加错误处理程序。如果 SendErrorEvents 属性设置为 true,将不会显示错误框,而是发送错误事件;如果 SendErrorEvents 属性设置为 false,将显示错误框,而是发送错误事件。
媒体播放器支持下面的错误处理事件:
Error 事件,指有危险性错误发生;
Warning 事件,指发生了非危险性的错误;
当你的应用程序接收到一个错误事件,你可以检测下面的属性来确定具体的错误信息:
HasError:检测目前的媒体播放器是否有错误;
ErrorCode:提供与该类型错误相关的代码值;
ErrorDescription:提供错误的描述信息;
ErrorCorrection:指定媒体播放器对该类型的错误进行校正;
posted @ 2008-01-19 19:41 jadmin 阅读(74) | 评论 (0)编辑 收藏
2008年1月16日这天,IT界最大的新闻是SUN 收购了MySQL.

Sun宣布已经与MySQL AB达成协议,以大约10亿美元收购MySQL AB,其中8亿美元现金收购MySQL AB的全部股权,另外的2亿美元作为期权。MySQL负责开发社区的副总裁 Kaj,在他的blog中分析了这单交易对于MySQL的用户、核心社区以及公司员工会带来哪些影响,还发表了他与MySQL的两位创始人Monty和David关于此事的交谈。这次收购使了Sun在IT企业中的地位进一步巩固,现在甚至包括了150亿美元的数据库市场,Sun不仅是一个领先的网络平台和环境的提供商,而且也是最大的开源商业贡献者。

SUN公司现已成为当今世界上产品经最为丰富的IT公司之一。软硬件统吃,从操作系统、到中件间、到数据库、再到应用软件。去年跟SUN中国工程院一位博士在聊天,他说SUN公司的产品线唯一就是缺少数据库。没想到时隔一年就把这个缺口给补上了。


现在很多用户关心的是MYSQL今后会怎么样?对于这个问题MYSQL的创始人给出了积极的态度:对于用户来说,MYSQL将会更加美好,MYSQL的开放理念与SUN公司的开放完全相同。MYSQL将会更加强大。
posted @ 2008-01-19 19:25 jadmin 阅读(63) | 评论 (0)编辑 收藏

有些道理总要等到适当的年纪才能明白,人生的哲理总是来得太迟。回想曾经让我们激动万分的事情,竟是如此微不足道,为什么当初就不能领悟?冤冤相报 何时了,这个道理谁都明白,可我们为什么还在沉溺于过去的痛苦无法自拔?如果你知道一个人能做的最大的冒险事情,就是乐意在公共场合经常暴露自己的愚昧。 你现在愿意这么做吗?-psytopic.com

许多人不遗余力地学习,遗憾的是真知往往来得太迟,以至于我们没法充分利用它。的确,我现有的知识在我早年的生活中肯定会更实用,这些知识能让我少走弯路,避免过去那些年受过的痛苦,而这些痛苦,我也让别人承受过。

我不相信生活会无故地变得富裕而有情趣,那只是浪漫的妄想。因为我一生都充满动荡和不安。失败不会主动传授你知识,我通过芸芸众生的无知、胆怯和愚 笨来获取真知。有了它们,生活会变得轻松,也更成功。因此,我要和大家分享一些姗姗来迟的道理,期待能让一些人避免重蹈我的覆辙。

很多事情其实无关紧要

许多令我激动、担忧或殚精竭虑的事情,到最后都变得不值一文。只有很少的事情确实关乎我们一生的快乐。我多么希望早点知道这些,以便能把精力都投入到这些关乎幸福的事,而不是其他。

沉溺于过去的痛苦是最大的痛苦来源

看那些恐怖组织和军事武装集团,往往都是为了陈年旧事,为了索取一小块土地的历史所有权,为了修正一项错误的历史决定。这些都成为了他们将杀戮正义化的基础。

等待有把握时再去做一件事,往往意味着永远的等待

一个人能做的最大的冒险事情,就是乐意在公共场合经常暴露自己的愚昧。没有什么能比这样学得更快。“哎呀”, 也是一种乐趣。

盲目追赶潮流是对精神和智力的扼杀

你可以成为一个廉价的时尚木偶,也可以成为独一无二的你,这些都在于自己的选择。信仰不是群众的鸦片,流行才是。

如果有人抱怨你太特立独行,恭喜你,你正走在正确的路上

谁愿意像动物一样活着?那些强有力的家伙们不希望你按照自己的意愿去做,他们希望你停止给他们制造麻烦并听从他们的命令。但你得知道,你无法做到在卑躬屈膝的同时又能活出自我。

如果你将工作等同于生活,那么你将为工作而生活

和很多人一样,当看到那些艺术家和音乐家的工作几乎是全部的生活,我感到很困惑。其实那不是工作,那是他们的自我。除非你有无法抵挡的激情,恰巧也 能让你从中得以谋生,否则请永远记住,工作只是一种手段,而不是目的,我们的最终目的是享受生活。在实现目的的同时,尽可能地少花时间在手段上。只有傻瓜 才是为工作而生活。

破坏关系的最快最简单的方法就是听信谣言

浪费你时间的最糟糕方式是传播这些谣言。传播流言蜚语的人好比瘟疫的携带者,相比之下,蟑螂都比他们干净、善良。

试图取悦别人是徒劳无益的做法

总有些人会对你发飙。很多你接触的人,在很多时候也会不喜欢、贬低、轻视或忽略你。另外,你永远也不知道别人真正的需要,因此你为此所做的所有努力 都会付诸东流。放松些吧,爱你的人终究会包容你的过失,他们才是值得我们在乎的人。其他那些人,他们甚至都不值得花5分钟去考虑。

没有永久的胜利者

踏上冠军宝座是件好事情。但不要梦想着可以永久占据这个位置,最糟糕的是,你正决定为达此目的而不择手段。

你不可能取悦、平息或改造一个混蛋

你能做的最好方式,是和他们敬而远之。变成混蛋也能传染,你和他们呆的时间越长,你也越有可能染上混蛋的习气或者你就成为混蛋。

努力加倍,期望减半

任何事情都是花掉你计划的两倍时间,最后却只换来你一半的期望结果。没什么好为此沮丧的,(中国俗话说:事倍功半)让它去吧,你要继续前行。

人是奇怪的偏执狂

撒谎者总是撒谎,骗子总要行骗。一个人对你倾诉的时候,通常已在其它人的面前倾诉过,只是可能没有得到想要回应。一位忠诚的朋友,无论遭受多大的冤屈依旧忠诚。

接纳自己

不管你怎么努力,你都无法逃避成为自己。除了自己,你还能成为谁呢?你可以扮演和假装,但进行扮演和假装的人还是你自己。如果你都无法接纳自己,没有努力挖掘自己已有的东西,那么谁有义务接纳你呢?

谈到公众谎言,没有比预算数字更令人震惊的

把时间折腾在这上面,是浪费时间。既使(奇迹中的奇迹!) 你是实事求是并且准确的, 其他人也不会那么愚蠢。

世界上最大的噪音是人们的抱怨

不要再增加了。



I don’t buy the romantic notion that my life has been somehow richer or more interesting because of all the times I screwed up; nor that the mistakes were “put” there to help me learn. I made them myself—through ignorance, fear, and a dumb wish to have everyone like me—and life and work would have been less stressful and more enjoyable (and certainly more successful) without them. So here are some of the things I wish I had learned long ago. I hope they may help a few of you avoid the mistakes that I made back then.

* Most of it doesn’t matter. So much of what I got excited about, anxious about, or wasted my time and energy on, turned out not to matter. There are only a few things that truly count for a happy life. I wish I had known to concentrate on those and ignore the rest.

* The greatest source of misery and hatred in this world is clinging to past hurts. Look at all the terrorists and militant groups that hark back to some event long gone, or base their justification for killing on claims of some supposed historical right to a bit of land, or redress for a wrong done hundreds of years ago.

* Waiting to do something until you can be sure of doing it exactly right means waiting for ever. One of the greatest advantages anyone can have is the willingness to make a fool of themselves publicly and often. There’s no better way to learn and develop. Heck, it’s fun too.

* Following the latest fashion, in work or in life, is spiritual and intellectual suicide. You can be a cheap imitation of the ideal of the moment; or you can be a unique individual. The choice is yours. Religion isn’t the opiate of the masses, fashion is.

* If people complain that you’re too fond of going your own way and aren’t fitting in, you must be on the right track. Who wants to live life as a herd animal? The guys in power don’t want you to fit in for your own sake; they want you to stop causing them problems and follow their orders. You can’t have the freedom to be yourself and meekly fit in at the same time.

* If you make your work your life, you’re making your life into hard work. Like most people, I confused myself by looking at people like artists and musicians whose life’s “work” fills their time. That isn’t work. It’s who they are. Unless you have some overwhelming passion that also happens to allow you to earn a living doing it, always remember that work should be a means to an end: living an enjoyable life. Spend as little time on the means as possible consistent with achieving the end. Only idiots live to work.

* The quickest and simplest way to wreck any relationship is to listen to gossip. The worst way to spend your time is spreading more. People who spread gossip are the plague-carriers of our day. Cockroaches are clean, kindly creatures in comparison.

* Trying to please other people is largely a futile activity. Everyone will be mad at you sometime. Most of the people you deal with will dislike, disparage, belittle, or ignore what you say or do most of the time. Besides, you can never really know what others do want, so a good deal of whatever you do in that regard will go to waste. Be comforted. Those who love you will probably love you regardless, and they are the ones whose opinions are worth caring about. The rest aren’t worth five minutes of thought between them.

* Every winner is destined to be a loser in due course. It’s great to be up on the winner’s podium. Just don’t imagine you can stay there for ever. Worst of all is being determined to do so, by any means available.

* You can rarely, if ever, please, placate, change, or mollify an asshole. The best thing you can do is stay away from every one you encounter. Being an asshole is a contagious disease. The more time you spend around one, the more likely you are to catch it and become one too.

* Everything takes twice as long as you plan for and produces results about half as good as you hoped. There’s no reason to be downhearted about this. Just allow for it and move on.

* People are oddly consistent. Liars usually tell lies. Cheaters cheat whenever it suits them. A person who confides in you has usually confided in several others first—but not got the response they wanted. A loyal friend will stay loyal under enormous amounts of thoughtless abuse.

* However hard you try, you can’t avoid being yourself. Who else could you be? You can act and pretend, but the person acting and pretending is still you. And if you won’t accept yourself—and do the best you can with what you have—who then has any obligation to accept you?

* When it comes to blatant lies, there are none more egregious than budget figures. Time spent agonizing over them is time wasted. Even if (miracle of miracles!) yours are honest and accurate, no one else will have been so foolish.

* The loudest noise in the world is the sound of people whining. Don’t add to it.

posted @ 2007-12-20 10:52 jadmin 阅读(51) | 评论 (0)编辑 收藏

如果你想出门,但电脑又正在进行工作,这时就要用到自动关机。大多数实现自动关机的方法都是使用一些第三方软件,这样不仅麻烦,而且为实现这个小功 能而专门动用一个软件,显的小题大做了!其实Windows XP(Windows 2000也可以)自身就具备定时关机的功能,下面我们就来看看如何实现Windows XP的自动关机。

    Windows XP的关机是由Shutdown.exe程序来控制的,位于Windows\System32文件夹中。如果想让Windows 2000也实现同样的效果,可以把Shutdown.exe复制到系统目录下。

    比如你的电脑要在22:00关机,可以选择“开始→运行”,输入“at 22:00 Shutdown -s”,这样,到了22点电脑就会出现“系统关机”对话框,默认有30秒钟的倒计时并提示你保存工作。如果你想以倒计时的方式关机,可以输入 “Shutdown.exe -s -t 3600”,这里表示60分钟后自动关机,“3600”代表60分钟。

    设置好自动关机后,如果想取消的话,可以在运行中输入“shutdown -a”。另外输入“shutdown -i”,则可以打开设置自动关机对话框,对自动关机进行设置。

    Shutdown.exe的参数,每个都具有特定的用途,执行每一个都会产生不同的效果,比如“-s”就表示关闭本地计算机,“-a”表示取消关机操作,下面列出了更多参数,大家可以在Shutdown.exe中按需使用。

-f:强行关闭应用程序

-m \\计算机名:控制远程计算机

-i:显示图形用户界面,但必须是Shutdown的第一个选项

-l:注销当前用户

-r:关机并重启

-t 时间:设置关机倒计时

-c "消息内容":输入关机对话框中的消息内容(不能超127个字符)

posted @ 2007-12-20 10:40 jadmin 阅读(41) | 评论 (0)编辑 收藏
最近discuz发布了新的版本,免费了,用的人更多了,以前使用其它论坛程序和discuz2.5/3.0的纷纷转换或升级到discuz4.0,可见discuz作为中国人开发的php论坛程序,确实是非常优秀的,在大家欣喜若狂的时候,也遇到了一些问题
  
  看到不少用户反映转换完以后是乱码的情况,出现这种现象的主要原因是这类用户使用的都是mysql4.1以上的版本.下面作一个说明,希望出现这个问题的朋友都能耐心的把这个文档看完!!!
  
  MySQL 4.1开始,对多语言的支持有了很大变化 (这导致了问题的出现)。尽管大部分的地方 (包括个人使用和主机提供商),MySQL 3、4.0 仍然占主导地位;但 MySQL 4.1 乃至5.0是 MySQL 官方推荐的数据库,已经有主机提供商开始提供并将会越来越多;因为 latin1 在许多地方 (下边会详细描述具体是哪些地方) 作为默认的字符集,成功的蒙蔽了许多 PHP 程序的开发者和用户,掩盖了在中文等语言环境下会出现的问题。
  
  MySQL 4.1开始把多国语言字符集分的更加详细,所以导致数据库迁移,或则dz论坛升级到4.0后(dz4.0开始使用gbk或utf-8编码)出现乱码问题。
  
  MySQL 4.1的字符集支持(Character Set Support)有两个方面:字符集(Character set)和排序方式(Collation)。对于字符集的支持细化到四个层次: 服务器(server),数据库(database),数据表(table)和连接(connection)。
  
  查看系统的字符集和排序方式的设定可以通过下面的两条命令:
  
QUOTE:
mysql> SHOW VARIABLES LIKE 'character_set_%';
  +--------------------------+----------------------------+
  | Variable_name | Value |
  +--------------------------+----------------------------+
  | character_set_client | latin1 |
  | character_set_connection | latin1 |
  | character_set_database | latin1 |
  | character_set_results | latin1 |
  | character_set_server | latin1 |
  | character_set_system | utf8 |
  | character_sets_dir | /usr/share/mysql/charsets/ |
  +--------------------------+----------------------------+
  7 rows in set (0.00 sec)
  
  mysql> SHOW VARIABLES LIKE 'collation_%';
  +----------------------+-------------------+
  | Variable_name | Value |
  +----------------------+-------------------+
  | collation_connection | latin1_swedish_ci |
  | collation_database | latin1_swedish_ci |
  | collation_server | latin1_swedish_ci |
  +----------------------+-------------------+
  3 rows in set (0.00 sec)

  MySQL 4.1 对于字符集的指定可以细化到一台机器上安装的 MySQL,其中的一个数据库,其中的一张表,其中的一栏,应该用什么字符集。但是,传统的 Web 程序在创建数据库和数据表时并没有使用那么复杂的配置,它们用的是默认的配置,那么,默认的配置从何而来呢?
  
  编译 MySQL 时,指定了一个默认的字符集,这个字符集是 latin1;
  安装 MySQL 时,可以在配置文件 (my.ini) 中指定一个默认的的字符集,如果没指定,这个值继承自编译时指定的;
  启动 mysqld 时,可以在命令行参数中指定一个默认的的字符集,如果没指定,这个值继承自配置文件中的;
  此时 character_set_server 被设定为这个默认的字符集;
  当创建一个新的数据库时,除非明确指定,这个数据库的字符集被缺省设定为 character_set_server;
  当选定了一个数据库时,character_set_database 被设定为这个数据库默认的字符集;
  在这个数据库里创建一张表时,表默认的字符集被设定为 character_set_database,也就是这个数据库默认的字符集;
  当在表内设置一栏时,除非明确指定,否则此栏缺省的字符集就是表默认的字符集;
  这个字符集就是数据库中实际存储数据采用的字符集,mysqldump 出来的内容就是这个字符集下的;
  当我们按照原来的方式通过PHP存取MySQL数据库时,就算设置了表的默认字符集为utf8并且通过UTF-8编码发送查询,你会发现存入数据库的仍然是乱码。问题就出在这个connection连接层上。
  想要进行“正确”的存储和得到“正确”的结果,最方便的是在所有query开始之前执行一下:
  
  SET NAMES 'gbk';
  其中gbk是数据库字符集。
  
  它相当于下面的三句指令:
  SET character_set_client = gbk;
  SET character_set_results = gbk;
  SET character_set_connection = gbk;
  
  4.1和5.0默认使用的是latin1字符集(木头:妈的,老外真霸道,妄想让全世界都是使用瑞典字符集吗)
  如果我们只想使用gbk字符集存储和获取数据,
  我们在编译mysql 4.1和 5.0的时候,需要注意在my.ini或者my.cnf中添加两处参数
  
CODE:
[mysqld]
  default-character-set=utf8

  
  
CODE:
#settings for clients (connection, results, clients)
  [mysql]
  default-character-set=utf8

  下面我们来说主题,如何转换数据库字符集
  两种方法,
  
QUOTE:
第一种----更改存储字符集
  主要的思想就是把数据库的字符集有latin1改为gbk,big5,或者utf8; 以下操作必须拥有主机权限。假设当前操作的数据库名为:database
  
  导出
  首先需要把数据导为mysql4.0的格式,具体的命令如下:
  mysqldump -uroot -p --default-character-set=latin1 --set-charset=gbk --skip-opt databse > d4.sql
  
  mysqldump的参数参照:
  MySql数据库备份mysqldump参数选项
  

  
  --default-characte-set 以前数据库的字符集,这个一般情况下都是latin1的,
  --set-charset 导出的数据的字符集,这个可以设置为gbk,utf8,或者big5
  导入
  首先使用下面语句新建一个GBK字符集的数据库(test)
  
  CREATE DATABASE `d4` DEFAULT CHARACTER SET gbk COLLATE gbk_chinese_ci;
  然后把刚才导出的数据导入到当前的数据库中就ok了。
  
  mysql -uroot -p --default-character-set=gbk -f d4 通过以上的导出和导入就把数据库的字符集改为正确的存储方式了。
  
  其中d4为新建库的名称,d4.sql为导出文件的名字
  
  但是这种方法,发现数据库数据存储量无端变大30%,真是郁闷

  
  
QUOTE:
另外一种其实原理相同,但是需要手动操作,一般用于第一种方法失败后的选择
  不过这种方法如果数据库很大,估计很难做,因为光打开文件就能让你死机
  
  首先还是用phpmyadmin或者用mysql本身的dump导出 .sql文件
  
  然后用UltraEdit打开你备份的所有xxxx.sql文件,查找
  
CODE:
DEFAULT CHARSET=latin1

  latin1这里也许是别的,反正是你不想要的,要转成gbk或者big5的字符集
  把这个替换为“空”
  在查找
  
CODE:
CREATE TABLE cdb_sessions (
  sid char(6) character set latin1 collate latin1_bin NOT NULL default '',
  ip1 tinyint(3) unsigned NOT NULL default '0',
  ip2 tinyint(3) unsigned NOT NULL default '0',
  ip3 tinyint(3) unsigned NOT NULL default '0',
  ip4 tinyint(3) unsigned NOT NULL default '0',
  uid mediumint(8) unsigned NOT NULL default '0',
  username char(15) NOT NULL default '',
  groupid smallint(6) unsigned NOT NULL default '0',
  styleid smallint(6) unsigned NOT NULL default '0',
  invisible tinyint(1) NOT NULL default '0',
  `action` tinyint(1) unsigned NOT NULL default '0',
  lastactivity int(10) unsigned NOT NULL default '0',
  fid smallint(6) unsigned NOT NULL default '0',
  tid mediumint(8) unsigned NOT NULL default '0',
  nickname char(15) NOT NULL default '',
  UNIQUE KEY sid (sid)
  ) ENGINE=HEAP MAX_ROWS=1000;

  替换为
  
CODE:
CREATE TABLE `cdb_sessions` (
  `sid` char(6) binary NOT NULL default '',
  `ip1` tinyint(3) unsigned NOT NULL default '0',
  `ip2` tinyint(3) unsigned NOT NULL default '0',
  `ip3` tinyint(3) unsigned NOT NULL default '0',
  `ip4` tinyint(3) unsigned NOT NULL default '0',
  `uid` mediumint(8) unsigned NOT NULL default '0',
  `username` char(15) NOT NULL default '',
  `groupid` smallint(6) unsigned NOT NULL default '0',
  `styleid` smallint(6) unsigned NOT NULL default '0',
  `invisible` tinyint(1) NOT NULL default '0',
  `action` tinyint(1) unsigned NOT NULL default '0',
  `lastactivity` int(10) unsigned NOT NULL default '0',
  `fid` smallint(6) unsigned NOT NULL default '0',
  `tid` mediumint(8) unsigned NOT NULL default '0',
  `nickname` char(15) NOT NULL default '',
  UNIQUE KEY `sid` (`sid`)
  ) TYPE=HEAP MAX_ROWS=2000;

  这一步更为简单的办法就是删除掉关于cdb_sessions表的这一段,将来全新装一个d4,将这个表导出
  将其内容复制,粘贴到 sql文件的最后面
  
  保存后,再把这个sql文件导入到你的库中
  
  就OK了

  用这两种方法就可以很方便的把4.1和5.0的mysql数据库降级到4.0
  简单的过程就是
  A导出4.1/5.0的库
  B进行处理,转换成gbk字符集
  C彻底卸载4.1或者5.0
  D安装4.0.26
  E然后导入处理完的库
  
  降级的时候导出库可以用这个方法
  mysqldump -uroot -p --default-character-set=latin1 --set-charset=gbk --skip-opt databse --compatible=mysql40 > d4.sql
  这样导出的就是4.0的库勒
  
  至于mysql版本的升级,
  如果数据文件中有中文信息,那么将MySQL 4.0的数据文件,直接拷贝到MySQL 4.1中就是不可以的,即便在my.ini中设置了default-character-set为正确的字符集。虽然貌似没有问题,但MySQL 4.1的字符集有一处非常恼人的地方,以gbk为例,原本MySQL 4.0数据中varchar,char等长度都会变为原来的一半,这样存储中文容量不变,而英文的存储容量就少了一半。这是直接拷贝数据文件带来的最大问题。
  
  所以,升级的根本,如果想使用“正确”的字符集,还是先用mysqldump导出成文件,然后导入。
  
  
  这里顺便提一个我的好友深海写的
  用于MySQL4.1的论坛数据库字符集整理工具。
  
  刚写的,处理部分代码可能写得有点龌龊,但是不影响使用,
  
  
  主要用于处理整理MySQL4.1指定数据库、表、字段的字符集。
  
  适用于将非允许的字符集范围内的数据结构(无数据!!)整理为适合Discuz!允许的字符集范围。
posted @ 2007-12-16 22:50 jadmin 阅读(46) | 评论 (0)编辑 收藏

1.the die is cast   一切已成定局

posted @ 2007-12-13 12:37 jadmin 阅读(51) | 评论 (0)编辑 收藏
  • ! (logical NOT)
  • != (not equal)
  • "
  • % (modulo)
  • % (wildcard character)
  • & (bitwise AND)
  • && (logical AND)
  • () (06-3)
  • (Control-Z) \z
  • * (multiplication)
  • + (addition)
  • - (subtraction)
  • - (unary minus)
  • / (division)
  • /etc/passwd
  • < (less than)
  • << (left shift)
  • <= (less than or equal)
  • <=> (Equal to)
  • <> (not equal)
  • = (equal)
  • > (greater than)
  • >= (greater than or equal)
  • >> (right shift)
  • \" (double quote)
  • \' (single quote)
  • \0 (ASCII 0)
  • \\ (escape)
  • \b (backspace)
  • \n (newline)
  • \r (carriage return)
  • \t (tab)
  • \z (Control-Z) ASCII(26)
  • ^ (bitwise XOR)
  • _ (wildcard character)
  • `
  • A

  • ABS()
  • ACOS()
  • ADDDATE()
  • addition (+)
  • AES_DECRYPT()
  • AES_ENCRYPT()
  • ALTER COLUMN
  • ALTER TABLE
  • AND, bitwise
  • AND, logical
  • AS
  • AS
  • ASCII()
  • ASIN()
  • ATAN()
  • ATAN2()
  • AVG()
  • B

  • backspace (\b)
  • BEGIN
  • BENCHMARK()
  • BETWEEN ... AND
  • BIGINT
  • BIN()
  • BINARY
  • BIT
  • BIT_AND()
  • BIT_COUNT()
  • BIT_LENGTH()
  • BIT_OR()
  • BLOB
  • BLOB
  • BOOL
  • C

  • CASE
  • CAST
  • CEILING()
  • CHAR
  • CHAR
  • CHAR VARYING
  • CHAR()
  • CHAR_LENGTH()
  • CHARACTER
  • CHARACTER VARYING
  • CHARACTER_LENGTH()
  • COALESCE()
  • Comment syntax
  • COMMIT
  • CONCAT()
  • CONCAT_WS()
  • CONNECTION_ID()
  • control flow functions
  • CONV()
  • CONVERT
  • COS()
  • COT()
  • COUNT()
  • COUNT(DISTINCT)
  • CREATE DATABASE
  • CREATE INDEX
  • CREATE TABLE
  • CROSS JOIN
  • CURDATE()
  • CURRENT_DATE
  • CURRENT_TIME
  • CURRENT_TIMESTAMP
  • CURRENT_USER()
  • CURTIME()
  • D

  • DATABASE()
  • DATE
  • DATE
  • DATE_ADD()
  • DATE_FORMAT()
  • DATE_SUB()
  • DATETIME
  • DATETIME
  • DAYNAME()
  • DAYOFMONTH()
  • DAYOFWEEK()
  • DAYOFYEAR()
  • DEC
  • DECIMAL
  • DECODE()
  • DEGREES()
  • DELAYED
  • DELETE
  • DES_DECRYPT()
  • DES_ENCRYPT()
  • DESC
  • DESCRIBE
  • DISTINCT
  • DIV
  • division (/)
  • DO
  • DOUBLE
  • DOUBLE PRECISION
  • double quote (\")
  • DROP DATABASE
  • DROP INDEX
  • DROP INDEX
  • DROP PRIMARY KEY
  • DROP TABLE
  • DUMPFILE
  • E

  • ELT()
  • ENCODE()
  • ENCRYPT()
  • ENUM
  • ENUM
  • equal (=)
  • escape (\\)
  • EXP()
  • EXPORT_SET()
  • EXTRACT()
  • F

  • FIELD()
  • FILE
  • FIND_IN_SET()
  • FLOAT
  • FLOAT
  • FLOAT(M,D)
  • FLOAT(precision)
  • FLOAT(precision)
  • FLOOR()
  • FORCE INDEX
  • FORCE INDEX
  • FORMAT()
  • FOUND_ROWS()
  • FROM_DAYS()
  • FROM_UNIXTIME()
  • G

  • GET_LOCK()
  • greater than (>)
  • greater than or equal (>=)
  • GREATEST()
  • GROUP_CONCAT()
  • H

  • HANDLER
  • HEX()
  • hexadecimal values
  • HOUR()
  • I

  • identifiers, quoting
  • IF()
  • IFNULL()
  • IGNORE INDEX
  • IGNORE INDEX
  • IGNORE KEY
  • IGNORE KEY
  • IN
  • INET_ATON()
  • INET_NTOA()
  • INNER JOIN
  • INSERT
  • INSERT ... SELECT
  • INSERT DELAYED
  • INSERT()
  • INSTR()
  • INT
  • INTEGER
  • INTERVAL()
  • IS NOT NULL
  • IS NULL
  • IS_FREE_LOCK()
  • ISNULL()
  • ISOLATION LEVEL
  • J

  • JOIN
  • K

    L

  • LAST_INSERT_ID([expr])
  • LCASE()
  • LEAST()
  • LEFT JOIN
  • LEFT OUTER JOIN
  • LEFT()
  • LENGTH()
  • less than (<)
  • less than or equal (<=)
  • LIKE
  • LIMIT
  • LN()
  • LOAD DATA INFILE
  • LOAD_FILE()
  • LOCATE()
  • LOCATE()
  • LOCK TABLES
  • LOG()
  • LOG10()
  • LOG2()
  • LONGBLOB
  • LONGTEXT
  • LOWER()
  • LPAD()
  • LTRIM()
  • M

  • MAKE_SET()
  • MASTER_POS_WAIT()
  • MATCH ... AGAINST()
  • MAX()
  • MD5()
  • MEDIUMBLOB
  • MEDIUMINT
  • MEDIUMTEXT
  • MID()
  • MIN()
  • minus, unary (-)
  • MINUTE()
  • MOD()
  • modulo (%)
  • MONTH()
  • MONTHNAME()
  • multiplication (*)
  • mysql_info()
  • mysql_info()
  • mysql_info()
  • mysql_info()
  • mysql_real_escape_string()
  • N

  • NATIONAL CHAR
  • NATURAL LEFT JOIN
  • NATURAL LEFT OUTER JOIN
  • NATURAL RIGHT JOIN
  • NATURAL RIGHT OUTER JOIN
  • NCHAR
  • newline (\n)
  • NOT BETWEEN
  • not equal (!=)
  • not equal (<>)
  • NOT IN
  • NOT LIKE
  • NOT REGEXP
  • NOT, logical
  • NOW()
  • NUL
  • NULL value
  • NULLIF()
  • NUMERIC
  • O

  • OCT()
  • OCTET_LENGTH()
  • OLD_PASSWORD()
  • OR, bitwise
  • OR, logical
  • ORD()
  • ORDER BY
  • P

  • parentheses ( and )
  • PASSWORD()
  • PERIOD_ADD()
  • PERIOD_DIFF()
  • PI()
  • POSITION()
  • POW()
  • POWER()
  • PRIMARY KEY
  • PRIMARY KEY
  • Q

  • QUARTER()
  • QUOTE()
  • quoting of identifiers
  • R

  • RADIANS()
  • RAND()
  • REAL
  • REGEXP
  • RELEASE_LOCK()
  • RENAME TABLE
  • REPEAT()
  • REPLACE
  • REPLACE ... SELECT
  • REPLACE()
  • return (\r)
  • REVERSE()
  • RIGHT JOIN
  • RIGHT OUTER JOIN
  • RIGHT()
  • RLIKE
  • ROLLBACK
  • ROUND()
  • RPAD()
  • RTRIM()
  • S

  • SEC_TO_TIME()
  • SECOND()
  • SELECT
  • SESSION_USER()
  • SET
  • SET
  • SET TRANSACTION
  • SHA()
  • SHA1()
  • SIGN()
  • SIN()
  • single quote (\')
  • SMALLINT
  • SOUNDEX()
  • SOUNDS LIKE
  • SPACE()
  • SQL_CACHE
  • SQL_NO_CACHE
  • SQRT()
  • START TRANSACTION
  • STD()
  • STDDEV()
  • STRAIGHT_JOIN
  • STRCMP()
  • SUBDATE()
  • SUBSTRING()
  • SUBSTRING()
  • SUBSTRING_INDEX()
  • subtraction (-)
  • SUM()
  • SYSDATE()
  • SYSTEM_USER()
  • T

  • tab (\t)
  • TAN()
  • TEXT
  • TEXT
  • TIME
  • TIME
  • TIME_FORMAT()
  • TIME_TO_SEC()
  • TIMESTAMP
  • TIMESTAMP
  • TINYBLOB
  • TINYINT
  • TINYTEXT
  • TO_DAYS()
  • TRIM()
  • TRUNCATE
  • TRUNCATE()
  • Types
  • U

  • UCASE()
  • unary minus (-)
  • UNION
  • UNIQUE
  • UNIX_TIMESTAMP()
  • UNLOCK TABLES
  • UPDATE
  • UPPER()
  • USE
  • USE INDEX
  • USE INDEX
  • USE KEY
  • USE KEY
  • USER()

    V

  • VARCHAR
  • VARCHAR
  • VARIANCE()
  • VERSION()
  • W

  • WEEK()
  • WEEKDAY()
  • Wildcard character (%)
  • Wildcard character (_)
  • X

  • XOR, bitwise
  • XOR, logical
  • Y

  • YEAR
  • YEAR
  • YEAR()
  • | (bitwise OR)
  • || (logical OR)
  • ~
  • posted @ 2007-12-03 07:26 jadmin 阅读(86) | 评论 (0)编辑 收藏

      每次我想要演示实际代码时,我会对mysql客户端的屏幕就出现的代码进行调整,将字体改成Courier,使他们看起来与普通文本不一样(让大家区别程序代码和正文)。在这里举个例子:


    mysql> DROP FUNCTION f;
    Query OK, 0 rows affected (0.00 sec)

      如果实例比较大,则需要在某些行和段落间加注释,同时我会用将"<--"符号放在页面的右边以表示强调。例如:


    mysql> CREATE PROCEDURE p ()
    -> BEGIN
    -> /* This procedure does nothing */ <--
    -> END;//
    Query OK, 0 rows affected (0.00 sec)

      有时候我会将例子中的"mysql>"和"->"这些系统显示去掉,你可以直接将代码复制到mysql客户端程序中(如果你现在所读的不是电子版的,可以在mysql.com网站下载相关脚本)

      所以的例子都已经在Suse 9.2 Linux、Mysql 5.0.3公共版上测试通过。在您阅读本书的时候,Mysql已经有更高的版本,同时能支持更多OS了,包括Windows,Sparc,HP-UX。因此这里的例子将能正常的运行在您的电脑上。但如果运行仍然出现故障,可以咨询你认识的资深Mysql用户,这样就能得到比较好的支持和帮助。

    Why Triggers 为什么要用触发器

      我们在MySQL 5.0中包含对触发器的支持是由于以下原因:

      MySQL早期版本的用户长期有需要触发器的要求。
      我们曾经许诺支持所有ANSI标准的特性。
      您可以使用它来检查或预防坏的数据进入数据库。
      您可以改变或者取消INSERT, UPDATE以及DELETE语句。
      您可以在一个会话中监视数据改变的动作。

      在这里我假定大家都读过"MySQL新特性"丛书的第一集--"MySQL存储过程",那么大家都应该知道MySQL至此存储过程和函数,那是很重要的知识,因为在触发器中你可以使用在函数中使用的语句。特别举个例子:

      复合语句(BEGIN / END)是合法的.

      流控制(Flow-of-control)语句(IF, CASE, WHILE, LOOP, WHILE, REPEAT, LEAVE,ITERATE)也是合法的.

      变量声明(DECLARE)以及指派(SET)是合法的.

      允许条件声明.

      异常处理声明也是允许的.

      但是在这里要记住函数有受限条件:不能在函数中访问表.因此在函数中使用以下语句是非法的。


    ALTER 'CACHE INDEX' CALL COMMIT CREATE DELETE

    DROP 'FLUSH PRIVILEGES' GRANT INSERT KILL

    LOCK OPTIMIZE REPAIR REPLACE REVOKE

    ROLLBACK SAVEPOINT 'SELECT FROM table'

    'SET system variable' 'SET TRANSACTION'

    SHOW 'START TRANSACTION' TRUNCATE UPDATE

      在触发器中也有完全一样的限制.

      触发器相对而言比较新,因此会有(bugs)缺陷.所以我在这里给大家警告,就像我在存储过程书中所说那样.不要在含有重要数据的数据库中使用这个触发器,如果需要的话在一些以测试为目的的数据库上使用,同时在你对表创建触发器时确认这些数据库是默认的。

    Syntax 语法

      1. Syntax: Name 语法:命名规则


    CREATE TRIGGER <触发器名称> <--
    { BEFORE | AFTER }
    { INSERT | UPDATE | DELETE }
    ON <表名称>
    FOR EACH ROW
    <触发器SQL语句>

      触发器必须有名字,最多64个字符,可能后面会附有分隔符.它和MySQL中其他对象的命名方式基本相象.

      这里我有个习惯:就是用表的名字+'_'+触发器类型的缩写.因此如果是表t26,触发器是在事件UPDATE(参考下面的点(2)和(3))之前(BEFORE)的,那么它的名字就是t26_bu。


      2. Syntax: Time 语法:触发时间


    CREATE TRIGGER <触发器名称>
    { BEFORE | AFTER } <--
    { INSERT | UPDATE | DELETE }
    ON <表名称>
    FOR EACH ROW
    <触发的SQL语句>

      触发器有执行的时间设置:可以设置为事件发生前或后。

      3. Syntax: Event语法:事件


    CREATE TRIGGER <触发器名称>
    { BEFORE | AFTER }
    { INSERT | UPDATE | DELETE } <--
    ON <表名称>
    FOR EACH ROW
    <触发的SQL语句>

      同样也能设定触发的事件:它们可以在执行insert、update或delete的过程中触发。

      4. Syntax: Table 语法:表


    CREATE TRIGGER <触发器名称>
    { BEFORE | AFTER }
    { INSERT | UPDATE | DELETE }
    ON <表名称> <--
    FOR EACH ROW
    <触发的SQL语句>

      触发器是属于某一个表的:当在这个表上执行插入、更新或删除操作的时候就导致触发器的激活.
    我们不能给同一张表的同一个事件安排两个触发器。

      5. Syntax: Granularity 语法:( :( 步长)触发间隔


    CREATE TRIGGER <触发器名称>
    { BEFORE | AFTER }
    { INSERT | UPDATE | DELETE }
    ON <表名称>
    FOR EACH ROW <--
    <触发的SQL语句>

      触发器的执行间隔:FOR EACH ROW子句通知触发器每隔一行执行一次动作,而不是对整个表执行一次。

      6. Syntax: Statement 语法:语句


    CREATE TRIGGER <触发器名称>
    { BEFORE | AFTER }
    { INSERT | UPDATE | DELETE }
    ON <表名称>
    FOR EACH ROW
    <触发的SQL语句> <--

      触发器包含所要触发的SQL语句:这里的语句可以是任何合法的语句,包括复合语句,但是这里的语句受的限制和函数的一样。

    Privileges权限

      你必须拥有相当大的权限才能创建触发器(CREATE TRIGGER)。如果你已经是Root用户,那么就足够了。这跟SQL的标准有所不同,我也希望能尽快改成标准的。

      因此在下一个版本的MySQL中,你完全有可能看到有一种叫做CREATE TRIGGER的新权限。然后通过这样的方法赋予:


    GRANT CREATE TRIGGER ON <表名称> TO <用户或用户列表>;

      也可以通过这样收回权限:


    REVOKE CREATE TRIGGER ON <表名称> FROM <用户或用户列表>;

    Referring to OLD and NEW columns 关于旧的和新创建的列的标识

      在触发器的SQL语句中,你可以关联表中的任意列。但你不能仅使用列的名称去标识,那会使系统混淆,因为那里可能会有列的新名(这可能正是你要修改的,你的动作可能正是要修改列名),还有列的旧名存在。因此你必须用这样的语法来标识:

      "NEW . column_name"或者"OLD . column_name".这样在技术上处理(NEW | OLD . column_name)新和旧的列名属于创建了过渡变量("transition variables")。

      对于INSERT语句,只有NEW是合法的;对于DELETE语句,只有OLD才合法;而UPDATE语句可以在和NEW以及OLD同时使用。下面是一个UPDATE中同时使用NEW和OLD的例子。


    CREATE TRIGGER t21_au
    BEFORE UPDATE ON t22
    FOR EACH ROW
    BEGIN
    SET @old = OLD . s1;
    SET @new = NEW.s1;
    END;//

      现在如果t21表中的s1列的值是55,那么执行了"UPDATE t21 SET s1 = s1 + 1"之后@old的值会变成55,而@new的值将会变成56。

    Example of CREATE and INSERT CREATE和INSERT的例子

      CREATE table with trigger创建有触发器的表

      这里所有的例程中我都假定大家的分隔符已经设置成//(DELIMITER //)。


    CREATE TABLE t22 (s1 INTEGER)//

    CREATE TRIGGER t22_bi
    BEFORE INSERT ON t22
    FOR EACH ROW
    BEGIN
    SET @x = 'Trigger was activated!';
    SET NEW.s1 = 55;
    END;//

      在最开始我创建了一个名字为t22的表,然后在表t22上创建了一个触发器t22_bi,当我们要向表中的行插入时,触发器就会被激活,执行将s1列的值改为55的动作。

      INSERT on table w ith a trigger使用触发器执行插入动作


    mysql> INSERT INTO t22 VALUES (1)//

      让我们看如果向表t2中插入一行数据触发器对应的表会怎么样?

      这里的插入的动作是很常见的,我们不需要触发器的权限来执行它。甚至不需要知道是否有触发器关联。


    mysql> SELECT @x, t22.* FROM t22//
    +------------------------+------+
    | @x | s1 |
    +------------------------+------+
    | Trigger was activated! | 55 |
    +------------------------+------+
    1 row in set (0.00 sec)

      大家可以看到INSERT动作之后的结果,和我们预期的一样,x标记被改动了,同时这里插入的数据不是我们开始输入的插入数据,而是触发器自己的数据。

    Example of a "check" constraint
    "check"完整性约束例子

      What's a "check" constraint 什么是"check"约束

      在标准的SQL语言中,我们可以在(CREATE TABLE)创建表的过程中使用"CHECK (condition)",
    例如:


    CREATE TABLE t25
    (s1 INT, s2 CHAR(5), PRIMARY KEY (s1),
    CHECK (LEFT(s2,1)='A'))
    ENGINE=INNODB;

      这里CHECK的意思是"当s2列的最左边的字符不是'A'时,insert和update语句都会非法",MySQL的视图不支持CHECK,我个人是很希望它能支持的。但如果你很需要在表中使用这样的功能,我建议大家使用触发器来实现。


    CREATE TABLE t25
    (s1 INT, s2 CHAR(5),
    PRIMARY KEY (s1))
    ENGINE=INNODB//

    CREATE TRIGGER t25_bi
    BEFORE INSERT ON t25
    FOR EACH ROW
    IF LEFT(NEW.s2,1)<>'A' THEN SET NEW.s1=0; END IF;//

    CREATE TRIGGER t25_bu
    BEFORE UPDATE ON t25
    FOR EACH ROW
    IF LEFT(NEW.s2,1)<>'A' THEN SET NEW.s1=0; END IF;//

      我只需要使用BEFORE INSERT和BEFORE UPDATE语句就行了,删除了触发器不会对表有影响,同时AFTER的触发器也不能修改NEW的过程变量(transition variables)。为了激活触发器,我执行了向表中的行插入s1=0的数据,之后只要执行符合LEFT(s2,1) <> 'A'条件的动作都会失败:


    INSERT INTO t25 VALUES (0,'a') /* priming the pump */ //
    INSERT INTO t25 VALUES (5,'b') /* gets error '23000' */ //

    Don't Believe The Old MySQL Manual
    该抛弃旧的MySQL的手册了

      我在这里警告大家不要相信过去的MySQL手册中所说的了。我们已经去掉了关于触发器的错误的语句,但是仍旧有很多旧版本的手册在网上,举个例子,这是一个德国的Url上的:


      http://dev.mysql.com/doc/mysql/de/ANSI_diff_Triggers.html.

      这个手册上说触发器就是存储过程,忘掉吧,你也已经看见了,触发器就是触发器,而存储过程还是存储过程。

      手册上还说触发器可以从其他表上来删除,或者是当你删除一个事务的时候激发,无论他说的是什么意思,忘掉吧,MySQL不会去实现这些的。

      最后关于说使用触发器会对查询速度产生影响的说法也是错的,触发器不会对查询产生任何影响。

      Bugs
      (不好的东西就不翻译了)
      On December 14 2004, I did an "Advanced Search" in http://bugs.mysql.com for 'trigger' or
      'triggers', I found that there were 17 active bugs as of that date. Of course they might disappear
      before you read this, but just in case they haven't, I'll mention the important ones. If they're still
      there, you'll have to work around them when you're trying triggers.


      Bug#5859 DROP TABLE does not drop triggers.
      (删除表的时候没有自动删除触发器)
      When you drop a table, dropping the table's triggers should be automatic.


      Bug#5892 Triggers have the wrong namespace.
      (触发器的命名空间有错,你必须在前面加上表的名字才能删除触发器,下面是例子)
      You have to say "DROP TRIGGER <table name> . <trigger name>".
      The correct way is "DROP TRIGGER <trigger name>".


      Bug#5894 Triggers with altered tables cause corrupt databases.
      (触发器对表的改变可能会造成数据库数据被破坏)
      Do not alter a table that has a trigger on it, until you know this is fixed.

    posted @ 2007-12-03 07:19 jadmin 阅读(81) | 评论 (0)编辑 收藏

      在数据库表丢失或损坏的情况下,备份你的数据库是很重要的。如果发生系统崩溃,你肯定想能够将你的表尽可能丢失最少的数据恢复到崩溃发生时的状态。有时,正是 MySQL 管理员造成破坏。管理员已经知道表已破坏,用诸如 vi 或 Emacs 等编辑器试图直接编辑它们,这对表绝对不是件好事!

      备份数据库两个主要方法是用 mysqldump 程序或直接拷贝数据库文件(如用 cp、cpio 或 tar 等)。每种方法都有其优缺点:

      mysqldump 与 MySQL 服务器协同操作。直接拷贝方法在服务器外部进行,并且你必须采取措施保证没有客户正在修改你将拷贝的表。如果你想用文件系统备份来备份数据库,也会发生同样的问题:如果数据库表在文件系统备份过程中被修改,进入备份的表文件主语不一致的状态,而对以后的恢复表将失去意义。文件系统备份与直接拷贝文件的区别是对后者你完全控制了备份过程,这样你能采取措施确保服务器让表不受干扰。

      mysqldump 比直接拷贝要慢些。

      mysqldump 生成能够移植到其它机器的文本文件,甚至那些有不同硬件结构的机器上。直接拷贝文件不能移植到其它机器上,除非你正在拷贝的表使用 MyISAM 存储格式。ISAM 表只能在相似的硬件结构的机器上拷贝。在 MySQL 3.23 中引入的 MyISAM 表存储格式解决了该问题,因为该格式是机器无关的,所以直接拷贝文件可以移植到具有不同硬件结构的机器上。只要满足两个条件:另一台机器必须也运行 MySQL 3.23 或以后版本,而且文件必须以 MyISAM 格式表示,而不是 ISAM 格式。

      不管你使用哪种备份方法,如果你需要恢复数据库,有几个原则应该遵守,以确保最好的结果:

      定期实施备份。建立一个计划并严格遵守。

      让服务器执行更新日志。当你在崩溃后需要恢复数据时,更新日志将帮助你。在你用备份文件恢复数据到备份时的状态后,你可以通过运行更新日志中的查询再次运用备份后面的修改,这将数据库中的表恢复到崩溃发生时的状态。

      以文件系统备份的术语讲,数据库备份文件代表完全倾倒(full dump),而更新日志代表渐进倾倒(incremental dump)。

      使用一种统一的和易理解的备份文件命名机制。象 backup1、buckup2 等不是特别有意义。当实施你的恢复时,你将浪费时间找出文件里是什么东西。你可能发觉用数据库名和日期构成备份文件名会很有用。例如:

      %mysqldump samp_db >/usr/archives/mysql/samp_db.1999-10-02

      %mysqldump menagerie >/usr/archives/mysql/menagerie.1999-10-02

      你可能想在生成备份后压缩它们。备份一般都很大!你也需要让你的备份文件有过期期限以避免它们填满你的磁盘,就象你让你的日志文件过期那样。

      用文件系统备份备份你的备份文件。如果遇上了一个彻底崩溃,不仅清除了你的数据目录,也清除了包含你的数据库备份的磁盘驱动器,你将真正遇上了麻烦。

      也要备份你的更新日志。

      将你的备份文件放在不同于用于你的数据库的文件系统上。这将降低由于生成备份而填满包含数据目录的文件系统的可能性。

      用于创建备份的技术同样对拷贝数据库到另一台机器有用。最常见地,一个数据库被转移到了运行在另一台主机上的服务器,但是你也可以将数据转移到同一台主机上的另一个服务器。

      1 使用 mysqldump 备份和拷贝数据库

      当你使用 mysqldumo 程序产生数据库备份文件时,缺省地,文件内容包含创建正在倾倒的表的 CREATE 语句和包含表中行数据的 INSERT 语句。换句话说,mysqldump 产生的输出可在以后用作 mysql 的输入来重建数据库。

      你可以将整个数据库倾倒进一个单独的文本文件中,如下:

      %mysqldump samp_db >/usr/archives/mysql/samp_db.1999-10-02

      输出文件的开头看起来象这样:

      # MySQL Dump 6.0# # Host: localhost Database: samp_db#-------------

      --------------------------# Server version 3.23.2-alpha-log## Table st

      ructure for table absence#CREATE TABLE absence( student_id int(10)

      unsigned DEFAULT 0 NOT NULL, date date DEFAULT 0000-00-00 NOT NUL

      L, PRIMARY KEY (student_id,date));## Dumping data for table absence

      #INSERT INTO absence valueS (3,1999-09-03);INSERT INTO absence value

      S (5,1999-09-03);INSERT INTO absence valueS (10,1999-09-08);......

      文件剩下的部分有更多的INSERT和CREATE TABLE语句组成。如果你想压缩备份,使用类似如下的命令:

      %mysqldump samp_db | gzip >/usr/archives/mysql/samp_db.1999-10-02.gz

      如果你要一个庞大的数据库,输出文件也将很庞大,可能难于管理。如果你愿意,你可以在 mysqldump 命令行的数据库名后列出单独的表名来倾到它们的内容,这将倾倒文件分成较小、更易于管理的文件。下例显示如何将 samp_db 数据库的一些表倾到进分开的文件中:

      %mysqldump samp_db student score event absence >grapbook.sql

      %mysqldump samp_db member president >hist-league.sql

      如果你生成准备用于定期刷新另一个数据库内容的备份文件,你可能想用 --add- drop-table 选项。这告诉服务器将 DROP TABLE IF EXISTS 语句写入备份文件,然后,当你取出备份文件并把它装载进第二个数据库时,如果表已经存在,你不会得到一个错误。

      如果你倒出一个数据库以便能把数据库转移到另一个服务器,你甚至不必创建备份文件。要保证数据库存在于另一台主机,然后用管道倾倒数据库,这样 mysql 能直接读取 mysqldump 的输出。例如:你想从主机 pit- viper.snake.net 拷贝数据库 samp_db 到 boa.snake.net,可以这样很容易做到:

      %mysqladmin -h boa.snake.net create samp_db

      %mysqldump samp_db | mysql -h boa.snake.net samp_db

      以后,如果你想再次刷新 boa.snake.net 上的数据库,跳过 mysqladmin 命令,但要对 mysqldump 加上--add-drop-table 以避免的得到表已存在的错误:

      %mysqldump --add-drop-table samp_db | mysql -h boa.snake.net samp_db

      mysqldump 其它有用的选项包括:

      --flush-logs 和 --lock-tables 组合将对你的数据库检查点有帮助。--lock-tables 锁定你正在倾倒的所有表,而 --flush-logs 关闭并重新打开更新日志文件,新的更新日志将只包括从备份点起的修改数据库的查询。这将设置你的更新日志检查点位备份时间。(然而如果你有需要执行个更新的客户,锁定所有表对备份期间的客户访问不是件好事。)

      如果你使用 --flush-logs 设置检查点到备份时,有可能最好是倾倒整个数据库。

      如果你倾倒单独的文件,较难将更新日志检查点与备份文件同步。在恢复期间,你通常按数据库为基础提取更新日志内容,对单个表没有提取更新的选择,所以你必须自己提取它们。

      缺省地,mysqldump 在写入前将一个表的整个内容读进内存。这通常确实不必要,并且实际上如果你有一个大表,几乎是失败的。你可用 --quick 选项告诉 mysqldump 只要它检索出一行就写出每一行。为了进一步优化倾倒过程,使用 --opt 而不是 --quick。--opt 选项打开其它选项,加速数据的倾倒和把它们读回。

      用 --opt 实施备份可能是最常用的方法,因为备份速度上的优势。然而,要警告你,--opt 选项确实有代价,--opt 优化的是你的备份过程,不是其他客户对数据库的访问。--opt 选项通过一次锁定所有表阻止任何人更新你正在倾倒的任何表。你可在一般数据库访问上很容易看到其效果。当你的数据库一般非常频繁地使用,只是一天一次地调节备份。

      一个具有 --opt 的相反效果的选项是 --dedayed。该选项使得 mysqldump 写出 INSERT DELAYED 语句而不是 INSERT 语句。如果你将数据文件装入另一个数据库并且你想是这个操作对可能出现在该数据库中的查询的影响最小,--delayed 对此很有帮助。

      --compress 选项在你拷贝数据库到另一台机器上时很有帮助,因为它减少网络传输字节的数量。下面有一个例子,注意到 --compress 对与远端主机上的服务器通信的程序才给出,而不是对与本地主机连接的程序:

      %mysqldump --opt samp_db | mysql --compress -h boa.snake.net samp_db

      2 使用直接拷贝数据库的备份和拷贝方法

      另一种不涉及 mysqldump 备份数据库和表的方式是直接拷贝数据库表文件。典型地,这用诸如 cp、tar 或 cpio 实用程序。本文的例子使用 cp。

      当你使用一种直接备份方法时,你必须保证表不在被使用。如果服务器在你则正在拷贝一个表时改变它,拷贝就失去意义。

      保证你的拷贝完整性的最好方法是关闭服务器,拷贝文件,然后重启服务器。如果你不想关闭服务器,要在执行表检查的同时锁定服务器。如果服务器在运行,相同的制约也适用于拷贝文件,而且你应该使用相同的锁定协议让服务器“安静下来”。

      假设服务器关闭或你已经锁定了你想拷贝的表,下列显示如何将整个 samp_db 数据库备份到一个备份目录(DATADIR 表示服务器的数据目录):

      %cd DATADIR%cp -r samp_db /usr/archive/mysql

      单个表可以如下备份:

      %cd DATADIR/samp_db%cp member.* /usr/archive/mysql/samp_db%cp score.*

      /usr/archive/mysql/samp_db ....

      当你完成了备份时,你可以重启服务器(如果关闭了它)或释放加在表上的锁定(如果你让服务器运行)。

      要用直接拷贝文件把一个数据库从一台机器拷贝到另一台机器上,只是将文件拷贝到另一台服务器主机的适当数据目录下即可。要确保文件是 MyIASM 格式或两台机器有相同的硬件结构,否则你的数据库在另一台主机上有奇怪的内容。你也应该保证在另一台机器上的服务器在你正在安装数据库表时不访问它们。

      3 复制数据库(Replicating Database)

      复制(Replication)类似于拷贝数据库到另一台服务器上,但它的确切含义是实时地保证两个数据库的完全同步。这个功能将在 3.23 版中出现,而且还不很成熟,因此本文不作详细介绍。

      4 用备份恢复数据

      数据库损坏的发生有很多原因,程度也不同。如果你走运,你可能仅损坏一两个表(如掉电),如果你倒霉,你可能必须替换整个数据目录(如磁盘损坏)。在某些情况下也需要恢复,比如用户错误地删除了数据库或表。不管这些倒霉事件的原因,你将需要实施某种恢复。

      如果表损坏但没丢失,尝试用 myisamchk 或 isamchk 修复它们,如果这样的损坏可有修复程序修复,你可能根本不需要使用备份文件。

      恢复过程涉及两种信息源:你的备份文件和个更新日志。备份文件将表恢复到实施备份时的状态,然而一般表在备份与发生问题之间的时间内已经被修改,更新日志包含了用于进行这些修改的查询。你可以使用日志文件作为 mysql 的输入来重复查询。这已正是为什么要启用更新日志的原因。

      恢复过程视你必须恢复的信息多少而不同。实际上,恢复整个数据库比单个表跟容易,因为对于数据库运用更新日志比单个表容易。

      4.1 恢复整个数据库

      首先,如果你想恢复的数据库是包含授权表的 mysql 数据库,你需要用 --skip -grant-table 选项运行服务器。否则,它会抱怨不能找到授权表。在你已经恢复表后,执行 mysqladmin flush-privileges 告诉服务器装载授权标并使用它们。

      将数据库目录内容拷贝到其它某个地方,如果你在以后需要它们。

      用最新的备份文件重装数据库。如果你用 mysqldump 产生的文件,将它作为 mysql 的输入。如果你用直接从数据库拷贝来的文件,将它们直接拷回数据库目录,然而,此时你需要在拷贝文件之前关闭数据库,然后重启它。

      使用更新日志重复做备份以后的修改数据库表的查询。对于任何可适用的更新日志,将它们作为 mysql 的输入。指定 --one-database 选项使得 mysql 只执行你有兴趣恢复的数据库的查询。如果你知道你需要运用所有更新日志文件,你可以在包含日志的目录下使用这条命令:

      % ls -t -r -1 update.[0-9]* | xargs cat | mysql --one-database db_name

      ls 命令生成更新日志文件的一个单列列表,根据服务器产生它们的次序排序(主意:如果你修改任何一个文件,你将改变排序次序,这导致更新日志一错误的次序被运用。)

      很可能你会是运用某几个更新日志。例如,自从你备份以来产生的更新日志被命名为 update.392、update.393 等等,你可以这样重新运行:

      %mysql --one-database db_name < update.392

      %mysql --one-database db_name < update.393

      .....

      如果你正在实施恢复且使用更新日志恢复由于一个错误建议的 DROP DATABASE、DROP TABLE 或 DELETE 语句造成丢失的信息,在运用更新日志之前,要保证从其中删除这些语句。

      4.2 恢复单个表

      恢复单个表较为复杂。如果你用一个由 mysqldump 生成的备份文件,并且它不包含你感兴趣的表的数据,你需要从相关行中提取它们并将它们用作 mysql 的输入。这是容易的部分。难的部分是从只运用于该表的更新日志中拉出片断。你会发觉 mysql_find_rows 实用程序对此很有帮助,它从更新日志中提取多行查询。

      另一个可能性是使用另一台服务器恢复整个数据库,然后拷贝你想要的表文件到原数据库中。这可能真的很容易!当你将文件拷回数据库目录时,要确保原数据库的服务器关闭。

    posted @ 2007-12-03 07:17 jadmin 阅读(85) | 评论 (0)编辑 收藏
    在Web应用中,经常需要动态生成图片,比如实时股市行情,各种统计图等等,这种情况下,图片只能在服务器内存中动态生成并发送给用户,然后在浏览器中显示出来。

    本质上,浏览器向服务器请求静态图片如jpeg时,服务器返回的仍然是标准的http响应,只不过http头的contentType不是text/html,而是image/jpeg而已,因此,我们在Servlet中只要设置好contentType,然后发送图像的数据流,浏览器就能正确解析并显示出图片。

    在Java中,java.awt和java.awt.image包提供了基本的绘制图像的能力,我们可以在内存中绘制好需要的图形,然后编码成jpeg或其他图像格式,最后发送相应给浏览器即可。下面是使用Servlet动态创建图像的详细步骤:

    1.创建BufferedImage对象,该对象存在内存中,负责保存绘制的图像;
    2.创建Graphics2D对象,该对象负责绘制所需的图像;
    3.当绘制完成后,调用com.sun.image.codec.jpeg包的JPEG编码器对其编码;
    4.最后将编码后的数据输出至HttpResponse即可。

    注意com.sun.image.codec.jpeg包位于JDK目录的rt.jar包中,它不是公开的API,需要将rt.jar复制到web应用程序的WEB-INF/lib下。

    我们先创建一个最简单的Servlet:

    public class CreateImageServlet extends HttpServlet {
         protected void doGet(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException
         {
             response.setContentType("image/jpeg");
         }
    }


    我们首先设置了response的contentType为image/jpeg,这样浏览器就可以正确识别。

    然后,创建一个大小为100x100的BufferedImage对象,准备绘图:

    int width = 100;
    int height = 100;
    BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);


    接着,BufferedImage对象中获取Graphics2D对象并绘图:

    Graphics2D g = bi.createGraphics(); // 创建Graphics2D对象
    // 填充背景为白色:
    g.setBackground(Color.BLUE);
    g.clearRect(0, 0, width, height);
    // 设置前景色:
    g.setColor(Color.RED);
    // 开始绘图:
    g.drawLine(0, 0, 99, 99); // 绘制一条直线
    // 绘图完成,释放资源:
    g.dispose();
    bi.flush();


    然后,对BufferedImage进行JPEG编码:

    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
    JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
    param.setQuality(1.0f, false);
    encoder.setJPEGEncodeParam(param);
    try {
         encoder.encode(bi);
    }
    catch(IOException ioe) {
         ioe.printStackTrace();
    }


    编码后的JPEG图像直接输出到了out对象中,我们只要传入response. getOutputStream()就可以直接输出到HttpResponse中。

    下面是完整的代码:

    package com.crackj2ee.web.util;

    import java.io.*;
    import java.awt.*;
    import java.awt.image.*;

    import javax.servlet.*;
    import javax.servlet.http.*;

    import com.sun.image.codec.jpeg.*;

    /**
    * @author Liao Xue Feng
    */
    public class CreateImageServlet extends HttpServlet {

         protected void doGet(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException
         {
             response.setContentType("image/jpeg");
             createImage(response.getOutputStream());
         }

         private void createImage(OutputStream out) {
             int width = 100;
             int height = 100;
             BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
             Graphics2D g = bi.createGraphics();
             // set background:
             g.setBackground(Color.BLUE);
             g.clearRect(0, 0, width, height);
             // set fore color:
             g.setColor(Color.RED);
             // start draw:
             g.drawLine(0, 0, 99, 199);
             // end draw:
             g.dispose();
             bi.flush();
             // encode:
             JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
             JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bi);
             param.setQuality(1.0f, false);
             encoder.setJPEGEncodeParam(param);
             try {
                 encoder.encode(bi);
             }
             catch(IOException ioe) {
                 ioe.printStackTrace();
             }
         }
    }


    最后将这个Servlet编译,注册到web.xml中,映射路径为/CreateImage,写一个简单的index.html测试:

    <html><head></head>
    <body>
    <img src="CreateImage">
    </body></html>


    如能正确显示,大功告成!
    posted @ 2007-12-02 17:11 jadmin 阅读(81) | 评论 (0)编辑 收藏
      在任何一个综合性网站,我们往往需要上传一些图片资料。但随着高分辨率DC的普及,上传的图片容量会很大,比如300万象素DC出来的文件基本不下600K。为了管理方便,大家可能不愿意每次都用ACDsee修改它,而直接上传到服务器。但是这种做法在客户端看来就没有那么轻松了,对于拨号上网的用户简直是一场恶梦,虽然你可以在图片区域设置wide和high!
      
      问题的解决之道来了!我们可以在类中处理一张大图,并缩小它。
      前提是需要JDK1.4,这样才能进行处理。按以下方法做:
      
      import java.io.File;
      import java.io.FileOutputStream;
      import java.awt.Graphics;
      import java.awt.Image;
      import java.awt.image.BufferedImage;
      
      import com.sun.image.codec.jpeg.JPEGCodec;
      import com.sun.image.codec.jpeg.JPEGImageEncoder;
      
      public class JpgTest {
      
      public void JpgTset() throws Exception{
      File _file = new File("/Order005-0001.jpg"); //读入文件
      Image src = javax.imageio.ImageIO.read(_file); //构造Image对象
      int wideth=src.getWidth(null); //得到源图宽
      int height=src.getHeight(null); //得到源图长
      BufferedImage tag = new BufferedImage(wideth/2,height/2,BufferedImage.TYPE_INT_RGB);
      tag.getGraphics().drawImage(src,0,0,wideth/2,height/2,null); //绘制缩小后的图
      FileOutputStream out=new FileOutputStream("newfile.jpg"); //输出到文件流
      JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
      encoder.encode(tag); //近JPEG编码
      //System.out.print(width+"*"+height);
      out.close();
      }
      }
      
      过程很简单,从本地磁盘读取文件Order005-0001.jpg(2032*1524),变成Image对象src,接着构造目标文件tag,设置tag的长宽为源图的一半,对tag进行编码,输出到文件流out,最后关闭文件流。
      
      还有一些问题需要说明:
      第一,目前只能支持JPG(JPEG)、GIF、PNG三种格式。
      第二,对于源图的容量有限制,最好不要超过1M,否则会抛内存不足的错误,不过我试验过1.8M的源图,可以成功,但是也很容易抛内存不足。
      
      引用一位前辈的话:图象运算本身是密集型运算,需要大量的内存存放象素值。我用VC试了一下,4M的图象也有问题,而且越是压缩比大的图片在内存中还原成BITMAP时需要的内存越大。解决的方法,可以重写编码类,先开一定的内存,然后一段一段编码写到临时文件中,输出的时候再一段一段读出来。或利用nio的内存映象来操作。JavaMail由于采用了Builder模式,先生成一个邮件的每一个部分,然后合并成一个完整的邮件对象,这样每个构件都要先生成到内存中,你如果发送一个上百兆的附件,那么在构造Part时肯定内存溢出,所以我就改写了BodyPart的构造,让他和一个临时文件关联,然后用临时文件保存Part而不是构造在内存中,这样任义大小的附件(硬盘能放得下为限)都可以发送了。
      
      最后,如果大家对图像处理有更高的要求,不妨关注一下开源项目。比如JMagick,可以使用JMagick来实现图片的复制、信息获取、斜角、特效、组合、改变大小、加边框、旋转、切片、改变格式、去色等等功能。
    posted @ 2007-12-02 17:07 jadmin 阅读(53) | 评论 (0)编辑 收藏
     有许多标准和实践准则可适用于Java开发者,但此处要说的,是每个Java开发者需坚守的基本原则。

      一、为代码加注释。虽然每个人都知道这点,但有时却不自觉忘了履行,今天你“忘了”加注释了吗?虽然注释对 程序的功能没什么“贡献”,但过一段时间,比如说两星期之后或者更长,回过头来看看自己的代码,说不定已经记不住它是干什么的了。如果这些代码是你个人 的,那还算是走运了,不幸的是,当然了,大多数时候都是别人的不幸,很多时候大家都是在为公司写代码,写代码的人也许早已经离开了公司,但别忘了一句古 话,有来有往嘛,为他人,也为我们自己,请为你的代码加上注释。

      二、不要让事情复杂化。程序员有时候总是对简单问题想出复杂的解决方案,比如说,在只有五个用户的程序中引 入EJB、对程序实现了并不需要的框架(framework),之类的还有属性文件、面向对象解决方案、多线程等等。为什么要这样做呢?也许我们并不知道 是否这样会更好,但这样做也许可以学到一些新东西,或者让自己更感兴趣一些。如果是不知道为什么这样做,建议多请教经验丰富的程序员,如果是为了个人的目 的,麻烦让自己更专业一点。

      三、始终牢记——“少即是好(Less is more)并不总是对的”。代码效率虽然很重要,但在许多解决方案中,编写更少的代码并不能改善这些代码的效率,请看下面这个简单的例子:


    if(newStatusCode.equals("SD") && (sellOffDate == null ||
    todayDate.compareTo(sellOffDate)<0 || (lastUsedDate != null &&
    todayDate.compareTo(lastUsedDate)>0)) ||
    (newStatusCode.equals("OBS") && (OBSDate == null ||
    todayDate.compareTo(OBSDate)<0))){
    newStatusCode = "NYP";
    }

      能看明白if条件语句是干什么的吗?能想出来是谁写的这段代码吗?如果把它分成两段独立的if语句,是不是更容易理解呢,下面是修改后的代码:


    if(newStatusCode.equals("SD") && (sellOffDate == null ||
    todayDate.compareTo(sellOffDate)<0 || (lastUsedDate != null &&
    todayDate.compareTo(lastUsedDate)>0))){
    newStatusCode = "NYP";
    }else
    if(newStatusCode.equals("OBS") && (OBSDate == null ||
    todayDate.compareTo(OBSDate)<0))
    {
    newStatusCode = "NYP";
    }

      是不是读起来容易多了呢,在此只是多加了一个if和两个花括号,但代码的可读性与可理解性就一下子提高了一大截。

      四、请不要硬编码。开发者经常有意“忘记”或忽略掉这点,因为有些时候开发日程逼得实在太紧。其实,多写一行定义静态变量的代码能花多少时间呢?


    public class A {
    public static final String S_CONSTANT_ABC = "ABC";
    public boolean methodA(String sParam1){
    if (A.S_CONSTANT_ABC.equalsIgnoreCase(sParam1)){
    return true;
    }
    return false;
    }
    }

      现在,每次需要将“ABC”与其他变量进行比较时,不必记住实际代码,直接引用A.S_CONSTANT_ABC就行了,而且在今后需要进行修改时,也可在一处修改,不会翻遍整个源代码逐个修改了。

      五、不要“创造”自己的框架(framework)。确切来说,有数以千计的各种框架存在,而且大多数是开 源的,这些框架都是优秀的解决方案,可用于日常程序开发中,我们只需使用这些框架的最新版本就行了,至少表面上要跟上形势吧。被大家广为接受的最为明显的 一个例子就是Struts了,这个开源web框架非常适合用在基于web的应用程序中。是不是想开发出自己的Struts呢,还是省点力气吧,回头看看第 二条——不要让事情复杂化。另外,如果正在开发的程序只有3个窗口,就不要使用Struts了,对这种程序来说,不需要那么多的“控制”。

      六、不要使用println及字符串连接。通常为了调试方便,开发者喜欢在可能的所有地方都加上 System.out.println,也许还会提醒自己回过头来再来删除,但有些时候,经常会忘了删除或者不愿意删除它们。既然使用 System.out.println是为了测试,那么测试完之后,为什么还要留着它们呢,因为在删除时,很可能会删除掉真正有用的代码,所以不能低估 System.out.println危害啊,请看下面的代码:


    public class BadCode {
    public static void calculationWithPrint(){
    double someValue = 0D;
    for (int i = 0; i < 10000; i++) {
    System.out.println(someValue = someValue + i);
    }
    }
    public static void calculationWithOutPrint(){
    double someValue = 0D;
    for (int i = 0; i < 10000; i++) {
    someValue = someValue + i;
    }
    }
    public static void main(String [] n) {
    BadCode.calculationWithPrint();
    BadCode.calculationWithOutPrint();
    }
    }

      从测试中可以发现,方法calculationWithOutPrint()执行用了0.001204秒,作为对比,方法calculationWithPrint()执行可是用了10.52秒。

      要避免浪费CPU时间,最好的方法是引入像如下的包装方法:


    public class BadCode {
    public static final int DEBUG_MODE = 1;
    public static final int PRODUCTION_MODE = 2;
    public static void calculationWithPrint(int logMode){
    double someValue = 0D;
    for (int i = 0; i < 10000; i++) {
    someValue = someValue + i;
    myPrintMethod(logMode, someValue);
    }
    }
    public static void myPrintMethod(int logMode, double value) {
    if (logMode > BadCode.DEBUG_MODE) { return; }
    System.out.println(value);
    }
    public static void main(String [] n) {
    BadCode.calculationWithPrint(BadCode.PRODUCTION_MODE);
    }
    }


      另外,字符串连接也是浪费CPU时间的一个大头,请看下面的示例代码:


    public static void concatenateStrings(String startingString) {
    for (int i = 0; i < 20; i++) {
    startingString = startingString + startingString;
    }
    }
    public static void concatenateStringsUsingStringBuffer(String startingString) {
    StringBuffer sb = new StringBuffer();
    sb.append(startingString);
    for (int i = 0; i < 20; i++) {
    sb.append(sb.toString());
    }
    }

      在测试中可发现,使用StringBuffer的方法只用了0.01秒执行完毕,而使用连接的方法则用了0.08秒,选择显而易见了。

      七、多关注GUI(用户界面)。再三强调,GUI对商业客户来说,与程序的功能及效率同等重要,GUI是一 个成功程序的最基本部分,而很多IT经理往往都没注意到GUI的重要性。在现实生活中,许多公司可能为了节省开支,没有雇用那些有着设计“用户友好”界面 丰富经验的网页设计者,此时Java开发者只能依赖他们自身的HTML基本功及在此领域有限的知识,结果,很多开发出来的程序都是“计算机友好”甚于“用 户友好”。很少有开发者同时精通软件开发及GUI设计,如果你在公司“不幸”被分配负责程序界面,就应该遵守下面三条原则:

      1、 不要再发明一次轮子,即不做无用功。现有的程序可能会有类似的界面需求。

      2、 先创建一个原型。这是非常重要一步,用户一般想看到他们将使用的东西,而且可以先利用这个原型征求用户的意见,再慢慢修改成用户想要的样子。

      3、 学会换位思考。换句话来说,就是从用户的角度来审查程序的需求。举例来讲,一个汇总的窗口可以跨页或者不跨页,作为一个软件开发者,可能会倾向于不跨页,因为这样简单一些。但是,从用户的角度来看,可能不希望看到上百行数据都挤在同一页上。

      八、文档需求不放松。每个商业需求都必须记录在案,这可能听上去像童话,似乎在现实生活中很难实现。而我们要做的是,不管开发时间多紧迫,不管最终期限多临近,对每个商业需求都必须记录在案。

      九、单元测试、单元测试、单元测试。关于什么是单元测试的最好方法,在此不便细说,只是强调,单元测试一定要完成,这也是编程中最基本的原则。当然了,如果有人帮你做单元测试自然是最好,如果没有,就自己来做吧,当创建一个单元测试计划时,请遵守以下三条最基本的原则:

      1、 先于编写类代码之前编写单元测试。

      2、 记录单元测试中的代码注释。

      3、 测试所有执行关键功能的公有方法,这里不是指set和get方法,除非它们是以自己独特方式执行set和get方法。

      十、质量,而不是数量。有些时候因为产品问题、期限紧迫、或一些预料之外的事情,导致常常不能按时下班,但一般而言,公司不会因为雇员经常加班而对之表扬和奖励,公司只看重高质量的工作。如果遵守了前九条原则,你会发现自己写出的代码bug少且可维护性高,无形中质量提高了一大步。
    posted @ 2007-12-02 01:26 jadmin 阅读(70) | 评论 (0)编辑 收藏

    http://www.javaworld.com.tw/confluence/pages/viewpage.action?pageId=833

    posted @ 2007-12-01 17:09 jadmin 阅读(58) | 评论 (0)编辑 收藏

    第一种方法:在tomcat中的conf目录中,在server.xml中的,<host/>节点中添加:
    <Context path="/hello" docBase="D:eclipse3.2.2forwebtoolsworkspacehelloWebRoot" debug="0" privileged="true">
    </Context>
    至于Context 节点属性,可详细见相关文档。

    第二种方法:将web项目文件件拷贝到webapps 目录中。

    第三种方法:很灵活,在conf目录中,新建 Catalina(注意大小写)\localhost目录,在该目录中新建一个xml文件,名字可以随意取,只要和当前文件中的文件名不重复就行了,该xml文件的内容为:
    <Context path="/hello" docBase="D:eclipse3.2.2forwebtoolsworkspacehelloWebRoot" debug="0" privileged="true">
    </Context>

    posted @ 2007-12-01 15:30 jadmin 阅读(44) | 评论 (0)编辑 收藏

    1、前言:

    CVS是版本控制的利器,目前在Linux和Windows下都有不同版本;但是国内大多数应用介绍都是基于Linux等开放源代码的开放性软件组织,而且讲解的也不系统,让人摸不着头脑;Windows下的CVS使用介绍更是了了无几。
    本文是针对Windows的LAN环境下使用CVS的经验介绍,一步一步的向您介绍如何配置和使用CVS的服务器端和客户端。同时,本文只使用到了CVS当中最基本的东西,还有很多更为高级的东西,本文暂不涉及。下面是本文的另一个连接映射,欢迎大家讨论使用,共同进步。
    文章连接http://www.kuihua.net/book/list.asp?id=66
    论坛连接http://www.kuihua.net/bbs/dispbbs.asp?boardID=1&;RootID=670&ID=670

    2、安装版本:

    2.1、服务器端(CVSNT)

    1. 本文使用的是CVSNT-2.0.4,这是一个比较稳定的版本,不要使用最新的CVSNT-2.1.1,本人在使用中发现有比较严重的Bug。
    2. 下载连接http://www.cvsnt.org 目前,它提供2.0.6和2.1.1版本的下载。
    3. 上面连接还提供源代码,有兴趣的朋友还可以下载下来仔细研究:)。
    4. 有心的朋友,仔细观察就会发现http://www.cvsnt.org 并没有提供任何客户端的下载,那是因为CVS.exe既可以用于服务器端又可以用于客户端,WinCVS是为了客户端使用的方便而定制的外壳。(关于这一点,本人未得到任何证实,只是本人在使用过程中的一种体会,欢迎大家讨论。)

    2.2、客户端(WinCVS)

    1. 本文使用的是WinCVS-1.3b13,这应该是一个最新版本:),本人在使用过程中并没有发现有任何严重的Bug。
    2. 下载连接http://sourceforge.net/projects/cvsgui/
    3. 此网站还提供丰富的CVS文档和相关源代码,以及多个OS下面的相关文档和代码;有收藏癖的朋友有福了:)。
    4. WinCVS-1.3b13 使用的CVSNT的版本是CVSNT-2.0.2,在与服务器端的CVSNT-2.0.4 版本配合使用时,未发现任何不兼容或冲突现象。
    5. 在本人的系统中用cvs version命令显示的结果如下:
    Client: Concurrent Versions System (CVSNT) 2.0.2 (client/server)
    Server: Concurrent Versions System (CVSNT) 2.0.4 (client/server)

    3、服务器端(CVSNT)的安装与配置:

    3.1、服务器端机器和环境配置:

    1. 操作系统:Windows 2000 Professional SP2中文版
    2. 机器名称:Server
    3. 机器地址:192.168.0.6 (内部IP)
    4. 网络环境:100兆交换局域网
    5. 硬盘分区格式:FAT32与NTFS都可以。
    6. 准备2个CVSNT的工作目录:
    F:\KHRoot (存放自己源代码的根目录)
    F:\KHTemp (存放CVS出错信息的目录)
    7. 本机上存在有的用户列表:(由NT或本机的使用者创建)
    Administrator (系统管理员)
    Jackey (普通用户)
    Goury (普通用户)
    Riolee (普通用户)

    3.2、安装CVSNT:

    1. 下载CVSNT-2.0.4;使用administrator登陆到Server机器上。
    2. 双击自解压的exe文件,选择Full Install,其它按照默认方式安装;安装完毕后可以在服务控制器中发现多了2个服务:cvsnt与cvslocking
    3. 发送Service Control Panel到桌面,形成快捷方式。
    4. 安装程序会自动将CVS安装路径,设置到系统的Path环境变量当中,因此使用者可以在控制台(cmd)中任意位置执行cvs.exe,这一点对下面的配置很重要!!

    3.3、配置CVSNT服务器:

    1. 双击Service Control Panel快捷方式,在Service Status页面,确认2个服务正常和稳定运行。
    2. 选择Repository页面,点按Add按钮,选择已经准备好的F:\KHRoot这个目录,确认,OK,Yes,这时会在F:\KHRoot下面建立CVSRoot目录,这是CVS默认的管理目录(默认模块)。如果报错,那是系统Path路径未设置正确。
    3. 选择Advanced页面,勾上Use local users for pserver ...,(Why? I don't know!J),在Temporary栏选择已经准备好的F:\KHTemp,确认,OK。
    4. 点按【应用】按钮,确认,退出,OK,搞定!!

    3.4、小结:

    1. 至此,CVSNT服务器端基本配置完毕,下面进行WinCVS的使用和管理。
    2. 由于CVS支持远程管理,也就是客户端与服务器集成的特性,因此,我们将添加用户、权限控制、模块维护等所有的管理工作都放到远端(WinCVS)进行管理,服务器端这时可以Ctrl+Atl+Del进入锁定状态了,下面的所有工作都交给远端的WinCVS来进行管理。

    4 客户端(WinCVS)的安装与配置:

    4.1 客户端机器和环境配置:

    1. 操作系统:Windows 2000 Professional SP2中文版
    2. 机器名称:YCW2000
    3. 机器地址:192.168.0.2 (内部IP)
    4. 网络环境:100兆交换局域网,可以直接访问到Server
    5. 硬盘分区格式:FAT32与NTFS都可以。

    4.2 安装WinCVS:

    1. 下载WinCVS 1.3b13,全部按照默认安装就可以了。
    2. 启动WinCVS,开始使用。特别注意:以下的所有操作都是在YCW2000(192.168.0.2)这台机器上远程完成的,此时的Server(192.168.0.6)主机已经进入了锁定状态。

    5 管理员使用WinCVS进行远程管理:

    5.1 配置WinCVS成管理员状态:

    1. 准备管理员工作目录:(在YCW2000机器上)
    E:\CVSClient\Admin (管理员工作目录)
    E:\CVSTemp (WinCVS临时目录)
    2. 第一次启动WinCVS时会自动弹出Preferences配置页面,也可以通过Admin=>Preference菜单进入;第一次使用时需要配置如下的3个页面:
    General页面设置:
    注:按照图示方式输入即可,需要注意的是Path部分的格式是Unix路径格式,它指的是CVSNT端设置的工作根目录。
    CVS页面设置: 注:Home路径是设置密码等文件的存放位置,必须指定,否则在登陆时,WinCVS也要弹出设置框。这个Home路径需要Python.exe这个外挂程序才有效。这里选择已经准备好的路径:E\CVSTemp
    WinCVS页面设置:
    注:此页面设置WinCVS的外挂编辑程序,通常使用UltraEdit比较方便。
    3. 设置管理员的工作路径:可以点按图标 ,或View=>Browse Location=>Change...菜单进行设置,选择已经准备好的路径:E:\CVSClient\Admin,确认,OK,这时此目录将出现在WinCVS的左边导航栏【Workspace】内。
    4. 至此,WinCVS就被配置成了远程的管理员使用状态,下面进行一般管理员需要的基本操作演练。演练的内容为:Administrator需要管理Jackey,Goury,Riolee三个用户,分别为这3个用户建立工作目录,每个人只能访问自己的工作目录。同时,只有Administrator能够进行权限分配,其它人没有任何管理权限。

    5.2 管理员进行管理演练:

    1. 登陆远程CVSNT:
      ◇ 选择Admin=>Login菜单,默认设置,OK。
      ◇ 弹出密码输入框,确认,OK。注意观察输出框【OutPut】的返回结果。
    2. Checkout默认模块:(CVSRoot管理模块)
      ◇ 在左边导航栏【Workspace】内,选择【Admin】点按右键,选择【Checkout
      modules...】,在【Checkout settings】中输入CVSRoot,确定,OK。如下图:
      ◇ 如果成功的话,会在【Admin】栏下增加一个【CVSRoot】目录。表示您已经将【
      CVSRoot】这个管理模块下载到本地了。

    3. CVS中目录权限介绍:

    ◇ 系统支持的目录权限列表:
    r (读取权限)
    w (写入权限)
    c (创建和删除权限)
    n (没有任何权限)
    ◇ 默认情况下,任何用户都拥有任何目录的所有权限。
    ◇ 任何情况下只有目录的拥有者和Administrator才有权力更改目录的使用权限。下面将会介绍如何修改目录权限和目录的拥有者。

    4. 修改CVSRoot的权限:只让Administrator拥有rcw三种全部权限。

    ◇ 选中刚刚下载的【CVSRoot】模块,【Ctrl+L】或Admin=>Command Line...,弹出Command Line Settings对话框,直接执行CVS命令。
    ◇ 取消所有用户的默认权限:cvs chacl default:n 回车,OK,完成。
    ◇ 设置Administrator拥有所有权限:cvs chacl administrator:rcw 回车,OK,完成。
    ◇ 更改【CVSRoot】的拥有者:cvs chown administrator 回车,OK,完成。
    ◇ 查看【CVSRoot】的权限状态:cvs lsacl 回车,OK,在【Output】中显示:
    Owner: administrator
    default:n
    administrator:rwc
    ◇【CVSRoot】的权限配置完毕。

    5. 编写代码库中的模块文件,便于多用户下载方便。

    l 需要自己编写的模块文件格式如下:(实现基本功能)
    【模块一的描述】【n个空格或参数】【相对目录路径一(DirA)】
    【模块二的描述】【n个空格或参数】【相对目录路径二(DirB)】
    ......
    【模块X的描述】【n个空格或参数】【相对目录路径X(DirX)】
    ◇【描述信息】与【相对路径】在字面上不一致时,需要使用-a参数。
    ◇ 【相对路径】指的是CVS会自动带上根路径,这里是F:\KHRoot,所以上面例子的完整路径为:F:\KHRoot\DirA
    ◇ 了解了模块文件结构,现在来实际操作一把:双击【CVSRoot】模块下的modules文件,用UltraEdit打开进行编辑。
    ◇ 为Jackey,Goury,Riolee三个用户分配工作目录和完成其它模块描述。
    CVSRoot CVSRoot
    Jackey工作目录 -a Jackey
    Goury工作目录 -a Goury
    Riolee工作目录 -a Riolee
    ◇ 编辑完毕,存盘。回到WinCVS,选中modules这个文件【Ctlr+M】或右键选择【Commit selection...】,默认设置,【确认】,OK,完成上传。

    6 . 为三个用户分别上传工作目录:

    ◇ 在YCW2000机器上的E:\CVSClient\Admin分别建立三个目录分别名为:Jackey,Goury,Riolee,作为临时交换目录。
    ◇ 在新创建的每个目录中用UltraEdit或拷贝一个Readme.txt作为引子文件!!:)
    ◇ 然后,回到WinCVS,在【Workspace】栏的【Admin】目录下形成如下的目录结构:
    ◇ 分别选中Goury,Jackey,Riolee,右键,点按【Import Module】,选择【Continue】,其它全部使用默认值,【确定】,OK,完成上传工作。
    ◇ 仔细观察【Output】窗口,确认都成功上传了。
    ◇ 转移到系统的Explore程序中,删除E:\CVSClient\Admin目录下的Jackey,Goury,Riolee三个目录。然后回到WinCVS当中。(一定要删除!!!:)
    ◇ 至此,就完成了工作目录的上传工作。

    7. 【Checkout】下载3个用户的工作目录到【Admin】目录下:

    ◇ 在【Workspace】栏选择【Admin】目录,右键,点按【Checkout Module...】,如下图:
    ◇ 选择【...】,得到CVSNT上最新的模块配置情况,弹出如下的信息框:
    ◇ 这个结构图就是刚才在modules当中编写的模块文件格式信息。选择【Jackey工作目录】,下载到YCW2000机器的E:\CVSClient\Admin目录下。
    ◇ 按照以上操作,依次分别下载【Goury工作目录】和【Riolee工作目录】。形成如下状态:


    8. 为三个用户分别设置各自目录的访问权限。(只有自己才能访问自己的工作目录)

    ◇ 选中【Goury】目录,【Ctrl+L】或Admin=>Command Line...,弹出Command Line Settings对话框,直接执行CVS命令。
    ◇ 取消所有用户的默认权限:cvs chacl default:n 回车,OK,完成。
    ◇ 设置Goury拥有所有权限:cvs chacl goury:rcw 回车,OK,完成。
    ◇ 查看【CVSRoot】的权限状态:cvs lsacl 回车,OK,在【Output】中显示:
    Owner: administrator
    default:n
    goury:rwc
    ◇ 按照以上的方法依次分别设置【Jackey】与【Riolee】的工作目录访问权限。
    ◇ 至此,完成了3个用户的目录权限分配。注意,虽然Administrator也没有权力再次【Checkout】那3个用户的工作目录,但是它是这些目录的拥有者又是Administrator,因此,只有它才有权力更改这些目录的访问权限。

    9. CVSNT系统中的用户管理原则:

    ◇ CVSNT的用户与本机(这里是Server机器)上的NT用户是相关联的,即CVSNT用的全是本机上存在的已有用户,因此在默认情况下可以不用设置任何用户名,只要使用本机上已经存在的用户名就可以用WinCVS进行登陆。
    ◇ 只有用Administrator身份登陆到CVSNT系统中,才有权力进行新用户的创建和删除。
    ◇ 使用CVS创建的新用户,必须与服务器端机器上的NT用户相绑定,才能生效;因此,这个新用户实际上是绑定它的NT用户的一个替身,在CVS系统中称为"别名"。
    ◇ 一个NT用户可以有多个‘替身'或‘别名',充当多个CVS用户。

    10. 用WinCVS进行新用户的添加和删除。(确保使用Administrator登陆)

    ◇ 【Ctrl+L】或Admin=>Command Line...,弹出Command Line Settings对话框,直接执行CVS命令。
    ◇ 添加新用户【Killer】:cvs passwd -a Killer 回车,设置密码,OK,完成。
    ◇ 绑定【Killer】到【Jackey】:cvs passwd -r Jackey Killer 回车,设置密码,OK,完成。
    ◇ 两次输入的密码可以不同,但以第二次输入的密码为最终密码。
    ◇ 删除用户【Killer】:cvs passwd -X Killer 回车,OK,完成。
    ◇ 其它特殊的功能查看passwd命令的帮助。

    11.使用完毕后,一定要【Logout】,因为WinCVS退出时并不会自动注销自己在远端的会话;这样做是为了防止其它人接着打开WinCVS,不用登陆就可以完成你能进行的所有操作了。

    6 WinCVS中常见的特殊操作:

    6.1 如何删除下载的文件或目录:

    1. 选中下载的某个或多个文件,执行【Remove】命令。
    2. 再次选中这些文件,执行【Commit】命令就完成了删除文件的操作。
    3. 本质上CVS是不会删除任何文件和目录的,即使是执行了以上操作,删除了某些文件,远端CVS实际执行的是将提交删除的文件放到了一个叫【Attic】的目录下,因此,这些被删除的文件是可以通过一定的方法恢复的。

    6.2 如何恢复已经删除的文件或目录:

    1. 在执行了【Remove】命令之后恢复文件。
    ◇ 【Ctrl+L】直接输入命令cvs add xxxxx,或执行【Add Selection】界面操作。
    ◇ 这样就可以直接恢复还未提交的删除文件。
    2. 在执行了【Commit】命令之后恢复文件。
    ◇ 只能用【Ctrl+L】直接输入命令cvs add xxxxx,这时会得到一个空的文件。
    ◇ 选中这个空文件,执行【Update】操作,得到这个文件的实体。
    ◇ 再次选中这个文件,执行【Commit】操作,得到这个文件最新版本。
    3. 由于CVS系统中本质上不会删除任何目录,因此,谈不上对目录的恢复,但是CVS系统默认情况下是要在用户本机上(如:YCW2000)要删除空目录,因此,可以用如下方法得到已被删除的空目录:cvs checkout -p xxx,也可以在Admin=>Preference的【Globals】页面进行设置。
    4. 可见,CVS系统能够保证:只要上传到服务器的文件,无论你怎么在远程进行如何的操作,正常的或非正常的操作,都可以用Administrator登陆上去,通过以上的方法找到丢失的文件。除非用户进入到远端服务器,将文件手动删除,那就没办法了:)

    6.3 如何得到以前版本的文件:

    1. 有时我们需要得到以前版本的文件,而WinCVS默认方式只传递最新的版本。
    2. 选中某个文件,【Ctrl+G】或右键,点按【Graph selection...】,使用默认设置,就可以得到该文件所以版本的图形结构描述。
    3. 选中一个版本,右键,点按【Retrieve revision】,就可以得到相应的老版本文件。当然也可以得到最新版本的文件:)

    6.4 有时WinCVS会变得异常缓慢,怎么办?

    1. 确认安装了WinCVS的机器上没有安装CVSNT服务器端,因为它们使用的版本有可能不一致。
    2. 只安装了WinCVS,但以前安装过其它版本的WinCVS,怎么办?
    3. 先卸载所有的WinCVS系统,删除安装目录下的残留文件。
    4. 打开注册表编辑器,全程查找cvs关键字,找到一个删除一个,一直到找不到为止!!:)
    5. 重新安装WinCVS,这个问题基本上就可以解决了,我就是这样解决,不晓得你那里如何?:)

    7 其它说明:

    1. 本文的重点在介绍如何让使用者搭建CVSNT+WinCVS这个系统,因此重点介绍了管理员的常用操作,至于一般用户使用到的操作,相对比较简单和单一,使用WinCVS的次数多了,很快就会熟悉它了。
    2. 这篇文档只是窥探了CVS的一点皮毛而已,CVS当中还有很多高级的用法,以及上百个命令,还有很多新鲜的管理源代码的方法,比如:tag,branch等模式;因此,热烈欢迎大家积极探索,不断共享,不断进步。。。。。。。。
    3. 另外,cvs.html这个帮助,里面的信息也很丰富,但是,很多地方写得不够清楚,需要不断猜测和实践才能知道怎么回事,本文的很多经验都是看这个帮助,如此这般,采用这个笨办法得到的。。。。。。
    4. 最后,祝愿看到此文的人,得到的帮助、提高等好处大于或等于浪费的时间、反而退步等坏处!!

    posted @ 2007-12-01 13:31 jadmin 阅读(63) | 评论 (0)编辑 收藏

    先说下CVSNT的用户验证方式,CVSNT的用户验证方式分两种:Windows系统用户与CVSNT用户共存的混合验证方式,及CVSNT用户 单一验证方式,默认工作在混合验证方式下,当然使用单一验证方式对用户的管理肯定比较方便一点,因此下面的配置就是围绕该方式进行的。各个资源库所使用的 验证方式及用户配置由其目录下CVSROOT里的配置文件决定,其中有几个比较重要的文件。


    1、config文件

    控制CVSNT的验证工作方式的就是config文件,注意该文件最前面的两行:

    #Set this to `no" if pserver shouldn"t check system users/passwords
    #SystemAuth=yes

    第二行就是我们要修改的内容,默认状态是被注释掉的,SystemAuth有两个值yes和no:

    yes:pserver将使用Windows系统用户和CVSNT用户来共同验证(若CVSNT用户未定义,则用Windows系统用户来进行验证),默认为yes,CVSNT用户在后面将要介绍的passwd文件中定义。

    no:只使用CVSNT用户来进行验证。

    该文件可以在客户端进行修改,因此我们可以将其checkout出来将第二行改为SystemAuth=no,并commit到CVSNT上就可以启用单一验证方式了,注意启用单一验证方式后原来的Windows系统用户将变为无效,因此要注意执行该步骤的时机。

    2、 admin文件

    该文件保存CVSNT管理员用户列表,内容很简单,形式如下:
    User1
    User2
    User3
    每一行定义一个管理 员用户,默认时没有该文件,但你可以在客户端自己添加并add上去,再commit到CVSNT上,但是光有这个文件还是不会生效的,还要将其添加到 checklist文件中,使CVSNT能够读取该文件的内容,在checklist中添加文件列表的格式为:

    [空格]文件名 出错信息

    其中文件名前的空格必须要有的,不然会出错。
    我们可以先添加admin文件到CVSNT中,再修改checklist文件commit,就可以使admin文件生效了。

    3、passwd文件

    服务器工作在CVSNT用户单一验证方式下的时候,这个文件定义了CVSNT的用户信息,这里面保存着用户名,用户密码,以及别名信息。默认状态下 没有该文件,但是我们可以在CVSNT还工作在混合验证方式下时,用系统管理员登录,通过添加用户命令来让CVSNT自动建立一个passwd文件。

    添加用户的命令的示例:

    cvs passwd –r administrator –a cvsadmin

    之后系统提示输入密码,输入后服务器会新建一个passwd文件。

    该文件的内容很简单,形式如下:

    cvsadmin:fqr1fS4gDghrt:administrator
    kid:aTXRfS31Bm6JA
    mystique:Yna4QcXz9dEqd

    以第一行为例:cvsadmin为用户名,fqr1fS4gDghrt为CVS使用UNIX标准加密函数对密码进行加密后的结果,administrator为该用户的别名,当使用混合验证方式时对应Windows系统用户名。

    注意:这个文件是不能在客户端进行修改的,不能checkout出来。

    4、group文件

    该文件定义CVSNT中组信息,同组里的用户拥有一样的权限,对组权限的修改和对用户权限的修改一样。

    group文件的内容为

    administrators:cvsadmin kid mystique
    users:User1 User2 User3

    可以看到该文件的内容也很简单,组名:用户名,多个用户名之间用空格隔开。

    Group文件可以在客户端修改,不用修改checkoutlist这个文件,系统会自动使其生效。

    作为组里面的特定成员可以赋给特定的权限。

    了解了以上内容,下面我说一下我自己的配置步骤,我没有使用WinCVS进行操作,是直接使用命令行进行修改的,觉得这样思路比较清晰:

    1、添加系统变量CVSROOT=E:/CVSNT/Repository,并把E:CVSNT加入到系统Path路径。

    2、进入命令提示符,因为此时为混合验证模式,可以不用不用登陆直接进行checkout。可以建立一个工作目录,在该目录下进行操作,我这里为E:/CVSNT/Works。

    检出CVSROOT目录:

    cvs co CVSROOT

    3、添加CVSNT系统管理员用户,此时会提示设置用户密码:

    cvs passwd –r administrator –a cvsadmin

    4、修改CVSROOT访问权限:

    cd CVSROOT
    cvs chown cvsadmin //更改所有者为cvsadmin
    cvs chacl default:n //默认权限为n
    cvs chacl cvsadmin:rwc //添加cvsadmin

    5、修改config文件,按上面的方法修改后commit:

    cvs ci

    6、此时单一验证方式已经启用了,也就是只能使用刚才添加的cvsadmin进行登录,此时可以把CVSNT控制面板上的Use local users for pserver authentication instead of domain users关掉。登录前还要改一下系统变量CVSROOT,关闭命令提示符窗口,修改CVSROOT为:

    :pserver:cvsadmin@192.168.0.1:4021/CVSNT/Repository

    这里的192.168.0.1是服务器的IP地址,/CVSNT/Repository就是前面设置Repository时设置的Name,可以改为你机器上的配置。修改系统变量之后以下的步骤在任何与服务器相连的机器上进行,当然该机器上应该有CVSNT的可执行文件。

    7、如果为了避免出现错误,先重启一下CVSNT服务器,再启动命令提示符来到E:/CVSNT/Works,因为已经启用单一验证方式,先要进行登录。

    cvs login

    输入密码,此时就是以cvsadmin登录了。

    8、添加admin文件,首先将CVSROOT检出,在CVSROOT下新建admin文件,内容为

    cvsadmin

    执行命令:

    cvs add admin
    cvs ci

    9、修改checklist文件,在该文件末尾添加一行:

    [空格]admin error message

    注意:admin前的空格不能少。

    执行命令:

    cvs ci

    经过以上步骤,可以说用户配置已经基本完成了,CVSNT可以很好的工作在单一验证方式下。进一步的管理可使用以下命令:

    添加用户: cvs passwd -a username,使用时不必理会需要添加别名的提示。

    修改用户权限:cvs chacl username:r|w|c|n,(r:Read w:write c:control n:none)

    要添加组管理,只需同添加admin步骤一样,按照格式要求新建group文件即可。

    如果还有不清楚的可以看看自带的文档,说得还是比较详细的。

    posted @ 2007-12-01 13:27 jadmin 阅读(39) | 评论 (0)编辑 收藏
    CVS

    一、什么是CVS?

    CVS――Concurrent Versions System并行版本系统;
    是一个标准的版本控制系统;
    对代码进行集中管理;
    记录代码所有的更改历史;
    提供协作开发的功能;
    支持多人同时CheckOut与合并。
    以客户端/服务器模式工作,所有的用户都在客户端进行CVS操作,而所有命令的执行都在CVS服务器端进行。

    二、CVS基本概念

    1. CVS仓库:又称主拷贝,是CVS系统保存软件资源的地方。所有项目的所有文件的所有版本都保存在这个仓库中。
    2. 版本:开发人员每次向CVS提交所做的修改就形成了一个新版本。
    3. 工作拷贝:从CVS服务器端取出的,保存在我们正在使用的客户端计算机上的代码拷贝。每个人员都有一个属于自己的工作拷贝。
    4. 检出代码(创建工作拷贝check out):从服务器取出代码,就是创建工作拷贝的过程。
    5. 提交代码(commit):将代码送到服务器保存,commit又叫作check in。
    6. 导入代码(import):将未被CVS进行版本管理的代码引入CVS系统中,由CVS开始对它进行版本管理。
    7. CVS日志:CVS用来记录每次操作的内容的信息。日志信息可以用cvs log命令来查看。
    8. 更新(update):在协同开发环境下,将其他人所作的最新修改从CVS仓库中取到你的工作拷贝中,从而使得你得工作拷贝与仓库中得最新版本保持一致。使用update是同步各个工作拷贝的手段。
    9. 冲突(conflict):在协同开发的环境下,当两个开发人员对同一个文件进行修改,并且依次提交CVS仓库时就发生了冲突。这种冲突需要开发人员手工消除,并提交到CVS仓库中形成解除冲突之后的新版本。

    三、CVS命令:

    1.设置环境变量。

    set CVSROOT=:pserver:xxx@192.168.0.226:e:/cvsroot
    
    set CVSROOT=:pserver:xxx@ansi.3322.org:e:/cvsroot

    2.签出工作版本到工作目录。
    $cd
    
    $cvs    co    account/src/common
    该命令只将account/src/common目录结构签出到本地。若使用
    $cvs co account
    则将account下所有目录结构签出来。

    3.提交修改的文件到CVS版本库中:

    $cvs    ci    <filename>
    注意若提交多个文件可以输入多个文件名,并以空格分开。若将该目录下所有文件都提交,那么只需
    $cvs    ci <回车>
    即可。

    4.提交新增加的目录或文件到CVS版本库中:

    $cvs    add    <dirname>
    
    $cvs    ci    <dirname>
    
    $cvs    add    <filename>
    
    $cvs    ci    <filename>

    5.删除目录及文件,需先删除目录下的文件

    $rm    <filename>
    
    $cvs    remove    <filename>
    
    $cvs    ci    <filename>

    再执行
    $cd ..
    $cvs up –P

    则将该空目录删除(只是删除本地工作拷贝的空目录)。

    6.查看文件状态

    $cvs status <filename>

    例如:
    $ cvs st ffun.c
    
    =================================================================
    
    File: ffun.c              Status: Up-to-date
    
         Working revision:      1.1       Wed Nov    6 11:29:04 2002
    
         Repository revision: 1.1       /szunicom/dev/billing/src/preproc/CDMA/ffun.c,v
    
         Sticky Tag:            (none)
    
         Sticky Date:           (none)
    
         Sticky Options:        (none)

    注意:最重要的是Status栏,可以有以下几种状态:

    Up-to-date

    :表明你的工作拷贝是最新的.
    Locally Modified:表明你曾经修改过该文件,但还没有提交,你的版本比仓库里的新.
    Needing Patch:表明有人已经修改过该文件并且已经提交了!你没有修改但你的工作拷贝的版本比仓库里的旧.
    Needs Merge:表明你修改了该文件但没有提交,而有人也修改了这个文件,并且提交给仓库了。

    Locally added

    :表明使用了"add"命令增加了该文件,但还没有"commit"

    Locally Removed

    :表明你使用了"remove"命令,但还没有"commit"

    Unkown

    :CVS不知道关于这个文件的情况.例如,你创建了一个新文件,而没有使用"add"命令
    解决办法:
    若状态为Locally Modified,则需执行$cvs ci <filename>
    若状态为Needing Patch或Needing Merge,则需执行$cvs up <filename>
    将版本库里的文件与工作拷贝合并后,再提交给版本库,使用命令:

    $cvs ci <filename>


    若状态为:Locallyadded,则需执行$cvs ci <filename>
    若状态为:Removed,则需执行$cvs ci <filename>
    若状态为:Unkown,则需执行$cvs add <filename>,$cvs ci <filename>。

    7.查看工作拷贝和仓库中最后版本之间的修改

    $ cvs diff src.c

    8.查看指定的两个版本之间的修改

    $ cvs diff -r 1.1 -r 1.2 <filename>

    9.版本回退(取出以前的某个版本)
    有两种方式:
    一是只把某一版本的文件输出到标准输出上:

    $cvs up –p –r <版本号> <filename>

    “-p”选项让CVS命令的结果只输出到标准输出,而不写入到结果文件中。

    另一种是将输出到标准输出的结果重定向到文件中:
    $cvs up –p –r <版本号> <filename> > <filename>

    如:目前abc.c文件的版本号为1.5,要取出1.2的版本,那么执行
    $cvs up –p –r 1.2 abc.c > abc.c

    若没有使用“-p”选项进行回退,而是使用了$cvs up –r 1.2 abc.c命令,之后若对1.2版本进行修改后再提交到CVS时,会出现如下提示信息:
    cvs ci
    
    cvs commit: Examining .
    
    cvs commit: sticky tag `1.2' for file `abc.c' is not a branch
    
    cvs [commit aborted]: correct above errors first!

    解决办法两种方式:

    1、修改CVS/Entries文件,将以下黄色标记部分删除即可。

    $cd CVS
    
    $ vi E*
    
    /abc.c/1.2/Tue Dec 17 13:33:06 2002//T1.2
    
    
    2、使用$cvs up –A abc.c命令来消除附着标签,但是该命令是将1.2版本与最新版本进行了合并,还需对abc.c进行修改再重新提交。

    10.如何恢复已经删除的文件或目录:
    1. 在执行了【Remove】命令之后恢复文件。 ◇ 【Ctrl+L】直接输入命令cvs add xxxxx,或执行【Add Selection】界面操作。 ◇ 这样就可以直接恢复还未提交的删除文件。
    2. 在执行了【Commit】命令之后恢复文件。 ◇ 只能用【Ctrl+L】直接输入命令cvs add xxxxx,这时会得到一个空的文件。 ◇ 选中这个空文件,执行【Update】操作,得到这个文件的实体。 ◇ 再次选中这个文件,执行【Commit】操作,得到这个文件最新版本。
    3. 由于CVS系统中本质上不会删除任何目录,因此,谈不上对目录的恢复,但是CVS系统默认情况下是要在用户本机上(如:YCW2000)要删除空目录,因此,可以用如下方法得到已被删除的空目录:cvs checkout -p xxx,也可以在Admin=>Preference的【Globals】页面进行设置。

    =============================================================================

    CVS是一个C/S系统,多个开发人员通过一个中心版本控制系统来记录文件版本,从而达到保证文件同步的目的。工作模式如下:

    CVS服务器(文件版本库)

    / | \ (版 本 同 步)

    / | \

    开发者1 开发者2 开发者3

    CVS(Concurrent Version System)版本控制系统是一种GNU软件包,主要用于在多人开发环境下的源码的维护。实际上CVS可以维护任意文档的开发和使用,例如共享文件的编辑修改,而不仅仅局限于程序设计。CVS维护的文件类型可以是文本类型也可以是二进制类型。CVS用Copy-Modify-Merge(拷贝、修改、合并)变化表支持对文件的同时访问和修改。它明确地将源文件的存储和用户的工作空间独立开来,并使其并行操作。CVS基于客户端/服务器的行为使其可容纳多个用户,构成网络也很方便。这一特性使得CVS成为位于不同地点的人同时处理数据文件(特别是程序的源代码)时的首选。

    所有重要的免费软件项目都使用CVS作为其程序员之间的中心点,以便能够综合各程序员的改进和更改。这些项目包括GNOME、KDE、THE GIMP和Wine等。

    CVS的基本工作思路是这样的:在一台服务器上建立一个源代码库,库里可以存放许多不同项目的源程序。由源代码库管理员统一管理这些源程序。每个用户在使用源代码库之前,首先要把源代码库里的项目文件下载到本地,然后用户可以在本地任意修改,最后用CVS命令进行提交,由CVS源代码库统一管理修改。这样,就好象只有一个人在修改文件一样,既避免了冲突,又可以做到跟踪文件变化等。

    CVS是并发版本系统(Concurrent Versions System)的意思,主流的开放源码网络透明的版本控制系统。CVS对于从个人开发者到大型,分布团队都是有用的:

    它的客户机/服务器存取方法使得开发者可以从任何因特网的接入点存取最 新的代码。它的无限制的版本管理检出(check out:注1)的模式避免了通常的因为排它 检出模式而引起的人工冲突。 它的客户端工具可以在绝大多数的平台上使用。

    CVS被应用于流行的开放源码工程中,象Mozilla,GIMP,XEmacs,KDE,和GNOME等。 那么它到底怎么样?

    你可能会说,它非常棒,但是对于 "我"来说它能做什么?首先,基本的 :一个版本控制系统保持了对一系列文件所作改变的历史记录。对于一个开发者来说,那就意味着在你对一个程 序所进行开发的整个期间,能够跟踪对其所作的所有改动的痕迹。对你来说,有没有出现过由于在令行上 按错键而导致一天的工作都白费的情况呢?版本控制系统给了你一个安全的网络。

    版本控制系统对任何人都有用,真的。(毕竟,谁不愿意使用一个安全的 网络呢?)但是它们经常被软件开发团队使用。在团队中工作的开发者需要能够调整他们的各自的修改;一个集 中式版本控制系统允许那样做。
    代码集中的配置

    个人开发者希望一个版本控制系统的安全网络能够运行在他们的本地的 一台机器上。然而,开发团队需要一个集中的服务器,所有的成员可以将服务器作为仓库来访问他们的代码。在 一个办公室中,没有问题 --只是将仓库连到本地网络上的一台服务器上就行了。对于开放源码项目...噢, 还是没有问题,这要感谢因特网。CVS内建了客户机/服务器存取方法,所以任何一个可以连到因特网上的开发 者都可以存取在一台CVS服务器上的文件。

    调整代码

    在传统的版本控制系统中,一个开发者检出一个文件,修改它,然后将 其登记回去。检出文件的开发者拥有对这个文件修改的排它权。没有其它的开发者可以检出这个文件 -- 并且只 有检出那个文件的开发者可以登记(check in:注2)所做的修改。(当然对于管理员有很多方法可以超越这个 限制。)

    想一下排它的检出可能会如何工作:Bob的兄弟检出 foo.java以便加入 注释,写好代码后他什么也没做。然后他去吃午饭了。Bob吃完午饭后,发现他的老板所指给他的一个bug在 foo.java里。他试图检出 foo.java ... 但是版本控制系统不允许他这样做,因为他的兄弟已经把它检出了。Bob不 得不等着他的兄弟吃完午饭回来(在这个 "好"日子用了两个小时),他才可以修正bug。

    在一个大型的开放源码工程中,因为开发者可能在任意的时区工作得很 晚,给予一个开发者阻止任意地方的其它开发者继续处理任意文件的能力很明显示无法运转。他们最终将因为不 能够在他们想要的时候开展项目而感到厌烦。

    CVS通过它的无限制的检出模式解决了这个问题。检出一个文件并不给定 开发者对那个文件的排它权。其它的开发者也可以对其检出,进行他们自己的修改,并且将其登记回去。

    "等一下!"你可能会说。"但是后面的登记不是会覆盖前面的吗?"回答 是不会。详细地回答就是当多个开发者对同一个文件作了修改CVS会检测,并且自动合并那些改变。

    哇噢。自动的?不用担心 -- CVS 会很小心,并且将会自动合并那些只 要不是对代码的同一行所作的改动。如果CVS不能安全的处理这些改动,开发者将不得不手工合并它们。 从此去往何处?

    到现在为止,你已经毫不犹豫地着迷于CVS 的潜力,并且急不可待地想 开始。第一步就是去得到 适合你的平台的CVS软件。安装CVS通常就是将其从你下载的压缩包中解开这么一件 事。配置CVS 可能要小心一些,它非常依赖于你使用的平台和你的CVS代码仓库的存放地。CVShome.org存放了大 量的CVS 文档:
    《Introduction to CVS》 Jim Blandy所写的一篇很棒地在线介绍。我也推荐《 Open Source Development with CVS》 Karl Fogel写的。你可以读一下我写的关 于它的评论在OpenAvenue VOX上。Karl已 经将书中关于CVS的部分置于GPL许可证之下;这篇文档在Karl的站点上以多种文档格式提供。

    《The Cederqvist》 -- 由Per Cederqvist所编写的CVS手册 -- 是一个关于CVS信息的全面资料。

    有大量的可用在许多平台上CVS 附加工具,它们给 CVS增加了功能或使得CVS更容易使用。

    posted @ 2007-11-27 20:33 jadmin 阅读(54) | 评论 (0)编辑 收藏
    SWT

           SWT-"Standard Widget Toolkit",它是一个Java平台下开放源码的Native GUI组件库,也是Eclipse平台的UI组件之一。从功能上来说,SWT与AWT/SWING是基本等价的。SWT以方便有效的方式提供了便携式的(即Write Once,Run Away)带有本地操作系统观感的UI组件。
           由于widget系统的固有复杂性以及平台之间微妙的差异,即使在理想情况下,能够达到工业标准的跨平台的widget类库也是很难编写和维护的。最早的AWT组件现在被认为是样貌丑陋的,而且存在很多问题;SWING组件虽然也是缺点多多,但是随着JDK版本的不断升高,它仍在不断进行着改进。我认为,SWT在功能上与AWT/SWING不相伯仲,但是组件更为丰富,平台表现稳定,BUG也相对较少。如果你的应用程序真的需要在多个平台上运行,需要更为美观的界面,又不那么依赖于其他基于AWT/SWING的图形库,那么SWT或许是一个比AWT/SWING更好的选择。

    =========================================

    为什么要使用SWT?

      SWT是一个IBM开发的跨平台GUI开发工具包。至于IBM费劲自己另起炉灶开发一个GUI工具包,而不是使用Sun有的由AWT, Swing, Java 2D, Java 3D等构成的Java GUI框架,那就话长了。(得在一个BBS读过一个SWT起源的的帖子)。

      在SWT之前,Sun提供了一个跨平台GUI开发工具包AWT (Abstract Windowing Toolkit)AWT框架也使用的是原生窗口部件(native widgets),但是它一直未能突破LCD问题LCD问题导致了一些主要平台特征的失。如果你不明白的(其我也没明白),话说,如果平台A有窗口部件(widgets1–40,而平台B有窗口部件(widgets20–25,那跨平台的AWT框架只能提供两个窗口部件集的交集。

      解决问题Sun建了一个新的框架。个框架不再使用原生窗口部件,而是使用仿真窗口部件(emulated widgets)。个方法然解决了LCD问题,并且提供了丰富的窗口部件集,但是它也来了新的问题。例如,Swing用程序的界面外不再和原生用程序的外相似。 然在JVMSwing用程序已得到了最大程度的性能改善,但是它们还是存在着其原生对应物所不具有的性能问题。并且,Swing用程序消耗太多的内存,这样Swing不适于一些小设备,如PDA和移动电话等。

      IBM行了尝试底解决AWTSwing框架来的上述问题。最IBM建了一个新的GUI就是SWTSWT框架通JNI访问原生窗口部件。如果在宿主(host)平台上无法找到一个窗口部件,SWT就会自地模它。

    =====================================

    Tags:java,rcp,jface,swt,ibm,eclipse,ui,gui

    posted @ 2007-11-27 15:04 jadmin 阅读(82) | 评论 (0)编辑 收藏

    在JDK环境配置好的情况下,进行如下操作:

    1.先下载最新版Derby数据库
    下载地址:http://db.apache.org/derby/
    本人下载的是:db-derby-10.3.1.4-bin.zip

    2.将db-derby-10.3.1.4-bin.zip解压到一目录下,我这里是才C:\Derby\db-derby-10.3.1.4-bin

    3.查看“系统属性”——“高级”——“环境变量”,在“系统变量”下面新建变量“DERBY_INSTALL”,值为第2步的路径值C:\Derby\db-derby-10.3.1.4-bin

    4.在CLASSPATH里增加“%DERBY_INSTALL%\lib\derby.jar;%DERBY_INSTALL%\lib\derbytools.jar;”内容

    5.进入Derby安装目录“%DERBY_INSTALL%\frameworks\embedded\bin”,双击运行文件setEmbeddedCP.bat

    6.测试Derby数据库环境是否配置成功,打开命令提示符窗口,输入信息“java org.apache.derby.tools.sysinfo”,如出现诸如下面的信息:

    C:\Documents and Settings\Administrator>java org.apache.derby.tools.sysinfo
    ------------------ Java 信息 ------------------
    Java 版本:        1.5.0_12
    Java 供应商:      Sun Microsystems Inc.
    Java 主目录:      C:\Program Files\Java\jdk1.5.0_12\jre
    Java 类路径:      .;C:\Program Files\Java\jdk1.5.0_12\lib;C:\Program Files\Java
    \jdk1.5.0_12\lib\dt.jar;C:\Program Files\Java\jdk1.5.0_12\lib\tools.jar;C:\Derby
    \db-derby-10.3.1.4-bin\lib\derby.jar;C:\Derby\db-derby-10.3.1.4-bin\lib\derbytoo
    ls.jar;C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\lib\msbase.jar
    ;C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\lib\mssqlserver.jar;
    C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\lib\msutil.jar;C:\Pro
    gram Files\MySQL\mysql-connector-java-5.0.7-bin.jar;C:\Program Files\Apache Soft
    ware Foundation\Tomcat 5.5\common\lib\servlet-api.jar;C:\Program Files\Libs\dom4
    j-1.6.1.jar
    OS 名:            Windows XP
    OS 体系结构:      x86
    OS 版本:          5.1
    Java 用户名:      Administrator
    Java 用户主目录:C:\Documents and Settings\Administrator
    Java 用户目录:    C:\Documents and Settings\Administrator
    java.specification.name: Java Platform API Specification
    java.specification.version: 1.5
    --------- Derby 信息 --------
    JRE - JDBC: J2SE 5.0 - JDBC 3.0
    [C:\Derby\db-derby-10.3.1.4-bin\lib\derby.jar] 10.3.1.4 - (561794)
    [C:\Derby\db-derby-10.3.1.4-bin\lib\derbytools.jar] 10.3.1.4 - (561794)
    ------------------------------------------------------
    ----------------- 语言环境信息 -----------------
    当前语言环境: [中文/中国 [zh_CN]]
    找到支持的语言环境:[cs]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[de_DE]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[es]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[fr]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[hu]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[it]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[ja_JP]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[ko_KR]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[pl]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[pt_BR]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[ru]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[zh_CN]
             版本:10.3.1.4 - (561794)
    找到支持的语言环境:[zh_TW]
             版本:10.3.1.4 - (561794)
    ------------------------------------------------------
    Derby数据库环境已经基本配置好了

    本文作者:曦勤

    http://hi.baidu.com/jadmin

    posted @ 2007-11-27 10:24 jadmin 阅读(125) | 评论 (0)编辑 收藏

    要用开源数据库Derby了,下面先转篇入门级的文章,学习学习!

    数据库做为数据持久化存储的重要手段怎么强度都不过分,但传统的数据库都比较庞大,需要安装配置等,对于一些比较轻量级的应用来说有点象杀鸡用牛刀一样.

    Derby做为一个开源的、纯Java数据库引起了越来越多的关注,它源自IBM的CloudScape,现在成了大名鼎鼎的Apache基金会的开源项目。Apache一项是开源项目的领导者,从他们手里出去的东西都很不错,在此感谢一下这些无私奉献的人们。

    Derby做为嵌入式数据库的一个方便之处就是对数据库的一切操控都可以在Java程序代码中实现,并且它非常的小,几个jar文件总共才2M多,非常轻巧,非常便于我们程序的移植。下面说一步步的来说明一下怎样使用。

    首先,从http://db.apache.org/derby/下载Derby的最新版本,直接解压到本地,然后设置程序运行的环境变量。

    在win2000/xp中“我的电脑”--》右键--》属性--》环境变量--》变量--》添加

    1.设置JAVA_HOME

    2.设置DERBY_INSTALL(一定要是这个名字,否则可能无法正常运行),值为解压的目录

    环境变量设置好了之后,我们就可以着手写第一个测试程序了。

    和使用其它的数据库一样,首先加载数据库驱动:

    Class.forName("org.apachy.derby.jdbc.EmbeddedDriver");

    然后我们创建一个数据库:

    Connection conn=DriverManager.getConnection("jdbc.derby.derbyDB;create=true","user","pwd");

    在上面的Url中指定create=true,则创建一个新的数据库。

    得到连接之后,我们就可以象访问其它数据库一样,进行相关操作了。

    Statement st=conn.createStatement();

    st.execute("create table test1(id int,name varchar(20));

    st.execute("insert into test1 values(1,'sinboy')");

    st.execute("inert into test1 values(2,'Tom')");

    ResultSet rs=st.executeQuery("select * from test1");

    while(rs.next){

       System.out.println("id:"+rs.getInt(1)+" name:"+rs.getString(2));

    }

    rs.close();

    st.close();

    conn.commit();

    conn.close();

    Derby的最大好处应该还是小巧、纯Java、好移植,比较适全小量的数据存储。

    posted @ 2007-11-25 20:21 jadmin 阅读(429) | 评论 (0)编辑 收藏

    做项目时,经常遇到要把数据库的内容放到javascript里。不管是单个字符串(String),还是集合(array)。javascript不能直接从数据库拿东西。所以只得借助一些其他条件。比如在页面上的标签里放id,name 之类的标志。

    假如:

    1,获取字符串:

    user.getName() 是一条单个的记录。

    <div id=a><%=user.getName() %></div>

    那么javascript很容易获取 : var jsa=     document.getElementById("a").innerText; (注:innerHTML也可以获取。)

    2,获取集合,数组:

    <form name="form1">
           <table>
                      <%
         Mgr mgr=new Mgr();
         ArrayList list=mgr.getonebbs();
         for(int i=0;i<list.size();i++)
         {
           Ext role=(Ext)list.get(i);
          %>
                   <tr>
                   <td id="cid<%=i %>"><%=role.getId()%></td>
                   <td id="cname<%=i %>"><%=role.getName()%></td>
                   <td id="cpass<%=i %>"><%=role.getPass()%></td>
                   <td id="ctel<%=i %>"><%=role.getTel()%></td>
                   </tr>
                <%
          }%>
         <input type="hidden" value="<%=list.size() %>" name="hid" >
          </table>
    </form>

    javascript获取:
             var cc = document.getElementById("hid").value; //首先获取长度,下面循环输出
              var a=new Array();
                var b=new Array();
                  var c=new Array();
                    var d=new Array();
              var myData=new Array();
            for(var j=0;j<cc;j++)
            {
              a[j]= document.getElementById("cid"+j).innerText;
              b[j]= document.getElementById("cname"+j).innerText;
              c[j]= document.getElementById("cpass"+j).innerText;
              d[j]= document.getElementById("ctel"+j).innerText;
                       //      alert(a+" "+b+" "+c+" "+d+" ");     //测试
                       myData[j] =     [a[j],b[j],c[j],d[j]] ;   
            }

    这样就把数组放到myData中去了。

    3,总结:
    先把输出放到jsp页面上,               //也就是把数据查询出来
    然后在js里面获取jsp上的数据,     //通过document.获取。 单个,循环。
    然后放到string 或者 array里面。//OK

    反正感觉多做了2步似的,绕了个圈,不过没办法,人家都是这样做的。。。

    js数组的写法:
    ArrI=new Array();
    ArrI[0] = new Array("username1","0","609");
    ArrI[1] = new Array("username2","609","610");
    ArrI[2] = new Array("username3","609","611");

    ArrII=new Array(
    new Array("username1","0","609"),
    new Array("username2","609","610"),
    new Array("username3","609","611")
    );

    ArrIII=[];
    ArrIII[0] = new Array("username1","0","609");
    ArrIII[1] = new Array("username2","609","610");
    ArrIII[2] = new Array("username3","609","611");

    ArrIIII=[
    ["username1","0","609"],
    ["username2","609","610"],
    ["username3","609","611"]
    ];
    一般最后一种..
    posted @ 2007-11-10 20:02 jadmin 阅读(94) | 评论 (0)编辑 收藏

    How to clone:

    1. Implement the Cloneable interface, and

    2. Redefine the clone method with the public access modifier.


    Cloneable:

    The Cloneable interface is one of a handful of tagging interfaces that Java provides.A tagging interface has no methods; its only purpose is to allow the use of instanceof in a type inquiry:
    if (obj instanceof Cloneable) . . .
    We recommend that you do not use this technique in your own programs.


    Shallow copy:

    Even if the default (shallow copy) implementation of clone is adequate, you still need to implement the Cloneable interface, redefine clone to be public, and call super.clone(). Here is an example:

    class Employee implements Cloneable
    {
       // raise visibility level to public, change return type
       public Employee clone() throws CloneNotSupportedException
       {
          return super.clone();
       }
       . . .
    }


    Deep copy:

    class Employee implements Cloneable
    {
       . . .
       public Object clone() throws CloneNotSupportedException
       {
          // call Object.clone()
          Employee cloned = (Employee) super.clone();

          // clone mutable fields
          cloned.hireDay = (Date) hireDay.clone()

          return cloned;
       }
    }

    1 The clone method of the Object class threatens to throw a CloneNotSupportedException—it does that whenever clone is invoked on an object whose class does not implement the Cloneable interface. Of course, the Employee and Date class implements the Cloneable interface, so the exception won't be thrown.

    2
    public Employee clone()
    {
       try
       {
          return super.clone();
       }
       catch (CloneNotSupportedException e) { return null; }
       // this won't happen, since we are Cloneable
    }

    This is appropriate for final classes. Otherwise, it is a good idea to leave the tHRows specifier in place. That gives subclasses the option of throwing a CloneNotSupportedException if they can't support cloning.


    Use clone:

    public static void main(String[] args) {
         try {
            Employee original = new Employee("John Q. Public", 50000);
            original.setHireDay(2000, 1, 1);
            Employee copy = original.clone();
            copy.raiseSalary(10);
            copy.setHireDay(2002, 12, 31);
            System.out.println("original=" + original);
            System.out.println("copy=" + copy);
        }
        catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

    posted @ 2007-11-10 10:39 jadmin 阅读(72) | 评论 (0)编辑 收藏

    1。list方法。   将 Enumeration 类型转换成list类型

    2。swap方法。方便的调换一个list中的两个元素的位置。

    3。lastIndexOfSubList方法。从一个list中从后面开始查找另外一个list第一次出现的位置。

    4。rotate方法。在一个list中,顺序移动每一个元素的位置到指定的位置。

    5。replaceAll方法。用指定的元素替换一个list中所用匹配的元素。

    6。indexOfSubList方法。从一个list中从前面开始查找另外一个list第一次出现的位置。

    示例程序:

    import java.util.*;

    class TestCollections {

    public static void main(String[] args) {
    TestCollections t = new TestCollections();
    t.testList();
    }
    public void testList() {
    Vector v = new Vector();
    v.add("a");
    v.add("b");
    Enumeration e = v.elements() ;
    List l = Collections.list(e);
    System.out.println(l);
    }

    public void testSwap() {
    List l = new ArrayList();
    l.add("t");
    l.add("a");
    l.add("n");
    l.add("k");
    l.add("s");
    System.out.println(l);
    Collections.swap(l,1,3);
    System.out.println(l);
    }

    public void testLastIndexOfSubList() {
    List l = new ArrayList();
    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");
    l.add("e");
    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");
    l.add("e");
    List l2 = new ArrayList();
    l2.add("b");
    l2.add("c");
    l2.add("d");
    int result = Collections.lastIndexOfSubList(l,l2);
    if(result != -1) {
       System.out.println("!!! " + result + ". Found from " + l + " with " + l2);
    } else {
       System.out.println("!!! Not found from " + l + " with " + l2);
    }
    List l3 = new ArrayList();
    l3.add("b");
    l3.add("d");
    l3.add("d");
    result = Collections.lastIndexOfSubList(l,l3);
    if(result != -1) {
       System.out.println("!!! " + result + ". Found from " + l + " with " + l3);
    } else {
       System.out.println("!!! Not found from " + l + " with " + l3);
    }
    }
    public void testRotate() {
    List l = new ArrayList();
    l.add("t");
    l.add("a");
    l.add("n");
    l.add("k");
    l.add("s");
    System.out.println(l);
    Collections.rotate(l,1);
    System.out.println(l);

    //
       l = new ArrayList();
    l.add("t");
    l.add("a");
    l.add("n");
    l.add("k");
    l.add("s");
    System.out.println(l);
    Collections.rotate(l,-4);
    System.out.println(l);

       l = new ArrayList();
    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");
    l.add("e");
    System.out.println(l);
    Collections.rotate(l.subList(1, 4), -1);
    System.out.println(l);


    }

    public void testReplaceAll() {
    List l = new ArrayList();
    l.add("t");
    l.add("a");
    l.add("n");
    l.add("a");
    l.add("s");
    System.out.println(l);
    boolean b = Collections.replaceAll(l,"a","hello");
    System.out.println(l);
    System.out.println(b);

    // not found
    b = Collections.replaceAll(l,"a","hello");
    System.out.println(b);

    }

    public void testIndexOfSubList() {
    List l = new ArrayList();
    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");
    l.add("e");
    List l2 = new ArrayList();
    l2.add("b");
    l2.add("c");
    l2.add("d");
    int result = Collections.indexOfSubList(l,l2);
    if(result != -1) {
       System.out.println("!!! " + result + ". Found from " + l + " with " + l2);
    } else {
       System.out.println("!!! Not found from " + l + " with " + l2);
    }
    List l3 = new ArrayList();
    l3.add("b");
    l3.add("d");
    l3.add("d");
    result = Collections.indexOfSubList(l,l3);
    if(result != -1) {
       System.out.println("!!! " + result + ". Found from " + l + " with " + l3);
    } else {
       System.out.println("!!! Not found from " + l + " with " + l3);
    }
    }
    }

    posted @ 2007-11-08 07:37 jadmin 阅读(73) | 评论 (0)编辑 收藏
    1.在一些字符串数组中,常会有重复的记录,比如手机号码,我们可以通过Hashtable来对其进行过滤
    public String[] checkArray(String[] str)...{
            Hashtable<String, String> hash=new Hashtable<String, String>();

            for(int i=0;i<str.length;i++)...{
                if(!hash.containsKey(str[i]))
                    hash.put(str[i], str[i]);
            }

            Enumeration enumeration=hash.keys();
            String[] str_new=new String[hash.size()];
            int i=0;

            while(enumeration.hasMoreElements())...{
                str_new[i]=enumeration.nextElement().toString();
                i++;
            }
            return str_new;
        }

    示例:
            String[] mobile={"13811071500","13811071500","13811071501","13811071503","13811071501"};
            mobile=checkArray(mobile);
            for(int i=0;i<mobile.length;i++)
                System.out.println(mobile[i]);
           输出结果为:
            13811071503
            13811071501
            13811071500
    2.A,B均为字符串数组,找出在A中存在,而在B中不存在的字符串
        public String[] compareArray(String[] A,String[] B){
            Hashtable<String, String> hash=new Hashtable<String, String>();
            Hashtable<String, String> hash_new=new Hashtable<String, String>();

            for(int i=0;i<B.length;i++)
                hash.put(B[i], B[i]);

            for(int i=0;i<A.length;i++){
                if(!hash.containsKey(A[i]))
                    hash_new.put(A[i], A[i]);
            }

            String[] C=new String[hash_new.size()];
            int i=0;
            Enumeration enumeration=hash_new.keys();

            while(enumeration.hasMoreElements()){
                C[i]=enumeration.nextElement().toString();
                i++;
            }
            return C;
        }
    示例:
            String[] mobile1={"13811071500","13811071501","13811071502","13811071503","13811071504"};
            String[] mobile2={"13811071500","13811071505","13811071502","13811071506","13811071504"};
            String[] mobile3=compareArray(mobile1,mobile2);
            for(int i=0;i<mobile3.length;i++)
                System.out.println(mobile[i]);
    输出结果:
        13811071503
        13811071501
    存在的问题:
    每次都是倒序,可以再对程序稍加改动,变成正序。

    3.将一个字符串数组中某一个特定的字符串过滤掉
    /** *//**检验一个字符串数组,若包含某一特定的字符串,则将该字符串从数组中删
    除,返回剩余的字符串数组
         * @param str_array 字符串数组
         * @param str_remove 待删除的字符串
         * @return 过滤后的字符串
         */
        public String[] removeStrFromArray(String[] str_array,String
    str_remove)...{
            Hashtable<String, String> hash=new Hashtable<String, String>();
            for(int i=0;i<str_array.length;i++)...{
                if(!str_array[i].equals(str_remove))
                    hash.put(str_array[i], str_array[i]);
            }
            //生成一个新的数组
            String[] str_new=new String[hash.size()];
            int i=0;
            Enumeration enumeration=hash.keys();
            while(enumeration.hasMoreElements())...{
                str_new[i]=enumeration.nextElement().toString();
                i++;
            }
            return str_new;
        }
    posted @ 2007-11-06 21:07 jadmin 阅读(93) | 评论 (0)编辑 收藏

    如果你的很多时间是用来敲纯文本,写程序或HTML,那么有效地使用一个好的编辑器能节省你不少时间。这篇文章里的指导和提示将有助于你更快工作,更少犯错误。

    文中采用开源文本编辑器Vim(Vi IMproved)说明有效编辑的思想,但这些思想也适用于其他编辑器。择合适的编辑器只是有效编辑的第一步,对于哪个编辑器更好的讨论将占很大地方,这里就不提了。如果你不知道该用哪个编辑器,或者对现在所使用的不太满意,不妨试试Vim;你是不会失望的。

    第一部分:编辑一个文件

    快速定位

    编辑中大部分时间是花费在阅读、查错和寻找应该进行编辑的地方上,而不是插入新文字或进行修改。在文件中不断定位(navigate)是经常要做的,所以最好学会如何快速地进行。

    你常会搜寻文档中的一些文字。或者找出包含特定词或词组的行。你当然可以使用搜寻命令 /pattern,不过还有更聪明的方法:

    * 如果你看到一个特定词,想看看其他地方是不是出现过同样的词,可以使用* 命令。它将对光标所指的词进行搜寻。
    * 如果设置了 ' incsearch' 选项,Vim将在你正在输入搜寻模式的时候就显示搜寻的结果(而不是等到你敲了回车之后)。这能够使你更快地找出拼写错误。
    * 如果设置了 ' hlsearch' 选项,Vim将使用黄色背景对搜寻结果进行高亮显示。你可以对搜寻的结果一目了然。应用在程序代码中可以显示变量的所有引用。你甚至不需要移动鼠标就能看到所有的搜寻结果。

    对于结构化的文档,快速定位的办法就更多了。Vim提供专门针对C程序(以及C++、Java等等)的特殊命令:

    * 使用 %可以从开始括号跳到对应的关闭括号。或者从 ``#if'' 跳到对应的 ``#endif''。事实上, % 可以完成许多对应项之间的跳转。可以用来检查if()和{}结构是否平衡。
    * 使用 [{可以在代码段(block)中跳回到段起始的 ``{``。
    * 使用 gb 可以从引用某个变量的地方跳转到它的局部声明。

    定位的方法当然不止这些。关键是你需要知道有这些命令。你也许会说不可能学会所有命令 — Vim里有成百个定位命令,有的很简单,有的很聪明 — 这需要几星期的学习。不过,你不必如此;你只需要了解自己的编辑特点,然后掌握相关的定位命令就可以了。

    可以采取三个基本步骤:

    1. 在你进行编辑的时候,注意那些重复进行的操作。
    2. 找出能快速进行这些操作的编辑命令。阅读文档,问问朋友,或者看看其他人是如何做的。
    3. 进行练习,知道熟练为止。

    让我们通过以下这个例子说明一下:

    1. 你发现在写C程序时,经常要查找函数定义。你目前使用 * 命令对函数名进行搜寻,但得到的往往是函数的引用而不是函数定义。你觉得一定会有更好的办法。
    2. 读过一篇快速参考以后,你发现关于定位标记的说明,里面说明了如何定位函数定义,这正是你要找的!
    3. 你试着生成了一个标记文件,使用Vim自带的ctags程序。你学会了使用CTRL-] 命令,发现这省了不少事。为了更方便,你在 Makefile 里加入了几行以自动生成标记文件。

    当你使用以上三个步骤时,有几点需要注意的地方:

    * ``我只想完成任务,不想去读那些文档来找新的命令。''。如果你真的是这么想的,那么你将永远停留在计算的石器时代。有些人编写什么都用Notepad,却总不明白为什么其他人总能用他一半的时间成任务。
    * 不要过分。如果你总为一点小事也要去找完美的命令,你就没法集中精力到你本要完成的任务上了。只要找出那些耗费过多时间的操作,然后使用相关的命令直到熟练就可以了。这以后你就能集中精力到自己的文档上了。

    下面这些章节给出了大多数人遇到的操作。你仿照它们在实际工作中使用

    三个基本步骤

    不要敲两次

    我们所使用的字词集合是有限的。既使是词组和句子也不过是有限的几个。对于程序来说更是如此。很明显,你不想把同样的东西敲上两遍。

    你经常会想把一个词替换成另一个。如果是全文件替换,你可以使用:s (substitute)命令。如果只是几个位置需要被替换,一个快速办法是使用 * 命令找出下一个词,使用 cw 来进行替换。然后敲n 找到下个词,再用 . 重复 cw 命令。

    . 命令重复上一个改变。这里的改变是插入、删除或替换操作。能够重复进行操作是个极为强大的机制。如果好好使用它,那么你大部分的编辑工作可能只不过是敲几下 . 的事。小心不要在两次重复之间做其他修改,因为这将改变你要重复的操作。如果确实需要如此,可以使用 m 命令记住要修改的位置,等重复操作进行完毕之后再回过头来修改它。

    有些函数名和变量名可能很难敲。你能准确无误地输入``XpmCreatePixmapFromData''么?Vim的自动补齐机制能给你省不少事。它查看你正在编辑的文件以及#include文件,你可以只敲入``XpmCr'',然后使用CTRL-N 命令让Vim把它补齐为``XpmCreatePixmapFromData''。这不但节省了输入时间,而且减少了输入的错误。

    如果你有同样的词组或句子需要输入多次,还有个更简单的办法。Vim可以进行录制宏。使用 qa 命令开始在'a'寄存器里录制宏。然后正常地输入编辑命令,最后用 q 退出录制状态。如果你想重复所录制的命令,只需执行 @a 命令。Vim总共提供26个这样的宏寄存器。

    使用宏录制功能可以记录各种操作,不只限于插入操作。如果你想重复一些东西,不妨一试。

    需要注意的是记录的命令会被原封不动地重复执行。在进行定位时简单的重复宏操作可能不是你想要的结果。比如对于一个词这里可能需要左移4个字符,在下个地方可能就要左移5个字符。所以必须定位到合适的位置再重复进行宏操作。

    如果你要重复的命令很复杂,把它们一次敲进去会很困难。这时你可以写一个脚本或宏。这常被用于建立代码模板;比如,一个函数头。你想做得多聪明就可以做得多聪明。

    知错就改

    编辑时经常会出错。无人能免。关键是快速发现并进行改正。编辑器应该提供这方面的支持,不过你必须告诉它什么是对什么是错。

    你可能常常会重复同样的错误,你的手指所做的并非是你要它做的。可以使用缩写(abbreviation)进行修正。下面是一些例子:


    * :abbr Lunix Linux
    * :abbr accross across
    * :abbr hte the


    这些词会在编辑时被自动改正。

    同样的机制也可以用于对很长的词语进行缩写。特别适用于输入那些你觉得很难敲的词,它可以避免出错。比如:

    * :abbr pn pinguin
    * :abbr MS Mandrake Software

    但有时候你想要的正是那些缩写,比如想插入``MS''。所以缩写中最好使用那些不会出现在文中的词。

    Vim提供了一个很聪明的高亮机制,一般用于程序的语法高亮,不过也可以用来查错。

    语法高亮会使用颜色显示注释。这听上去不是什么特别重要的功能,不过一旦用起来就会发现这其实很有用。你能够快速地发现那些没有高亮却本应作为注释的文字(可能是因为忘了敲注释符)。也可以发现一些被错误当成注释的代码(可能是因为忘了敲``*/'')。这些错误在黑白方式下是很难被发现的,浪费了不少调试时间。

    语法高亮也可以用来查找不匹配的括号。一个未被匹配的``)''会被亮红色背景加以标识。你可以使用 % 命令他们是被如何匹配的,然后把``(''或``)''插入到合适的位置。

    另一类常犯的错误也很容易发现,比如把 ``#include <stdio.h>''敲成了``#included <stdio.h>''。在黑白方式下这是很难发现的,但在语法高亮下则能很快发现``include''能被高亮而``included''没有。

    再看一个更复杂的例子:对于英文文本你可以定义一个所要使用的词的长列表。所有未在表中出现的词都可能是错误,并进行高亮显示。可以定义几个用于编辑词表的宏。这正是字处理器的拼写检查功能。Vim中是靠一些脚本来实现的,你也可以对它进行定制:比如,只对注释中的文字进行拼写检查。

    第二部分:编辑多个文件

    文件总是成帮结伙

    人们很少只编辑一个文件。一般需要顺序或同时编辑一些相关的文件。你应该利用编辑器使多文件编辑工作更为高效地。

    上面提到的标识(tag)机制也支持跨文件搜寻。一般做法是为项目的所有文件生成标识文件,然后在项目的所有文件中搜寻函数、结构、类型(typedef)等的定义。这比手工搜寻要快捷的多;我浏览一个程序要做的第一件事便是建立标识文件。

    另一个强大的功能是使用 :grep 命令对一组文件进行模式搜寻。Vim把搜寻结果做成一个列表,然后跳到第一个结果。使用 :cn 命令跳到下一个结果。如果你想改变一个函数调用的、参数个数,那么这个功能会很有用。

    头文件里有很多有用的信息。然而要知道一个声明出现在哪个头文件中却需要花不少时间。Vim能够理解头文件,能够从中找到你需要的东西。把光标移动到函数名下,然后敲 [I:Vim就会显示出一个头文件中该函数名的所有匹配。
    如果你想得到更详细的结果,可以直接跳到声明中。一个类似的命令可以用于检查你所使用的头文件是否正确。

    你可以把Vim的编辑区域进行分隔,用来编辑不同的文件。你可以对两个或多个文件进行比较,或者进行拷贝/粘贴。有许多命令用于打开关闭窗口,文件间跳转,暂时隐藏文件等等。可以再使用上面提到的三个基本步骤选择合适的命令进行学习。

    多窗口还有更多的用法。预览标识(preview-tag)就是个很好的例子。它打开一个特殊的预览窗口,光标还保留在你正在编辑的文件中。预览窗口中可以是光标所指函数的声明。如果你移动光标到另一个名字下,停留一两秒,预览窗口中就会显示那个名字的定义。名字还可以是头文件中声明的结构或函数。

    让我们一起来工作

    编辑器可以编辑文件。e-mail程序可以收发消息。操作系统可以运行程序。每个程序都有它自己的任务,而且应该做好。如果能让程序一同工作,那么就会实现很强大的功能。

    举个简单的例子:选择一个列表中的结构化的文字,并对它进行排序:!sort。这将使用外部命令``sort''来过滤文件。容易吧?排序功能是可以添加到编译器中的。不过看一下``man sort''就知道它有很多选项。它可能用了一个极为精巧的排序算法。你还打算把它加到编辑器中么?更何况还有其他不少过滤程序。编辑器可能会变得很大。

    Unix精神的一个体现就是提供独立的程序,各自做好自己的任务,然后组合起来完成更大的任务。不幸的是,许多编辑器不能很好地和其他程序一起工作,比如,你不能包Netscape的邮件编辑器换成其他编辑器。这样你只能使用那个不顺手的程序。另一个趋势是在编辑器里提供所有的功能,Emacs就是个代表(有人说Emacs其实是个操作系统,只是可以用来编辑文件)。

    Vim尽力和其他程序集成,但这需要经过斗争。目前Vim已经可以作为MS-Developer Studio和Sniff的编辑器。一些e-mail程序(比如Mutt)也支持外部编辑器。和Sun Workshop的集成工作正在进行中。总的来说这个领域还有待提高。将来我们会有一个大于其各部分总和的系统。

    文本结构化

    你可能经常会遇到有一些结构的文本,这些结构可能同于那些现有命令所支持的结构。这样你不得不利用那些底层的``砖头''创建你自己的宏和脚本。这里说明的就是这类更复杂的东西。

    有个简单的办法可以加速编辑-编译-修改这个循环。Vim提供 :make 命令,用于进行编译,并且获取错误输出,把你带到发生错误的地方进行修正。如果你使用了另一个编译器,那么错误就无法被Vim获得。如果不想自己动手,可以修改' errorformat'选项。告诉Vim错误是什么样子,以及如何从中获得文件名和行号。它支持复杂的gcc错误信息,所以应该也能支持其他编译器。

    有时处理一个新的文件类型只需要设置几个选项或写一些宏。比如,为了在man手册中进行跳转,你可以写一个宏获取光标下的词,清除缓冲区,然后读入新的man手册。这是简单而高效的参照(cross-reference)方法。

    使用三个基本步骤,你可以更有效地处理各种结构化文件。只需要想想你想对文件采取的操作,然后找到相应的命令去用就是了。就这么简单,你只要去做就成了。

    第三部分:磨刀

    养成习惯

    要学会开车必须下功夫。这是不是你只骑自行车的原因么?当然不是,你会发现你必须花时间来获得所需的技术。文本编辑也不例外。你需要学习新的命令,并使用它直至成为习惯。

    另一方面,你不应该试图学习编辑器提供的每个命令。这是彻底的浪费时间。大多数人只需要学习10%到20%的命令就足够工作了。但是每个人所需要的命令都各不相同。你需要不断学习,找出那些可以自动完成的重复操作。如果你只做一次操作,而且以后也不会再去做,那么就不需要进行优化。是如果你发现你在过去的一小时中重复了好几遍同样的操作,那么就有必要查看一下手册,看看能否更快速地完成。或者写一个宏来做。如果是是个不小的任务,比如对一类文本进行对齐,你需要阅读一下新闻组或看看Internet上是不是有人已经解决了同样的问题。

    最根本的步骤是最后的那一个。你可能能够找到一个重复性的任务,找到一个不错的作法,可过了一个周末就彻底忘了自己是怎么做的了。这不成。你必须重复你的作法直到烂熟于胸。只有这时你才真正获得了你需要的高效。一次不要学得太多。一次只试一些工作得很好的方法。对于那些不常用的技巧,你可能只需要把它记下来,留待以后查阅。总之,如果抱着这样的目标,你的编辑技能就会更加有效。

    最后需要指出的是,如果人们忽略了以上几点会发生什么:我仍然可以看到有人盯着屏幕看上半天,用两个指头敲几下,然后继续抬头看着屏幕,还抱怨自己太累.. 把十个指头都用上!这不光更快,还不累。每天抽出一个小时练习一下指法,只要几星期就足够了。

    后记

    书名得益于Stephen R. Covey所著的那本畅销书《高效人的七种习惯》(``The 7 habits of highly effective people'')。

    关于作者

    Bram Moolenaar是Vim的主要作者。他编写了Vim核心功能,并采纳了许多开发者提供的代码。他的e-mail地址是:Bram@Moolenaar.net

    posted @ 2007-11-06 11:14 jadmin 阅读(54) | 评论 (0)编辑 收藏

      1、Java编译器在对源文件编译前,会先把源文件转换为unicode编码,因为这个原因,我们在编译时一定要把源文件用的是什么编码方式正确无误的”告诉”编译器。

      例如:我们的源文件是以UTF-8的方式保存的,而在编译时却把它当作是用GBK方式保存的,这样编译器就会按照GBK->Unicode的编码转换方法对源文件进行转换,然后再编译,这样当然会出错,实际上编译器应当按照UTF-8->Unicode的编码转换方法来对源文件进行转换。

      a.对于控制台程序,编译器会把源文件看作是由系统默认的编码类型来编码的(系统默认的编码类型取决于在控制面板区域设置里的配置,中文win2k下通常是GBK),也可以使用-encoding参数来设置,如:javac -encoding UTF-8,这样编译器就会把源文件看作是用UTF-8编码的(这只是告诉编译器源文件的编码类型,而不是对源文件转码)。在各种语言的平台上只要在编译用时-encoding指定与源文件的编码相同的编码方式,就不会存在国际化的问题了。

      b.对于JSP,编译器则会根据设定的字符集来判断JSP文件使用的是什么编码方式,进而将其转换成unicode后进行编译;若JSP中未指定,编译器则会把JSP文件看作是按照系统默认的编码来保存的。在JSP2.0里新增了一个指令来通知编译器这个源文件所使用的编码方式。

      2、在处理输入输出时,注意设置输入流和输出流的编码类型与用户输入时和输出设备显示时采用的编码方式一致。

      由于JRE在处理输入输出时会将输入或输出的内容进行编码转换,对于输入会转换为unicode后再送入,因此要正确的匹配实际输入内容的编码方式和告知JRE的编码方式,对于输出,会由unicode转换为其他的编码再送出程序,因此要正确匹配输出设备显示时用的编码方式和告知JRE的编码方式。

      例如:程序中设置输入流的编码是new InputStreamReader(System.in,"GB2312");而程序运行后用户输入时用了繁体中文的输入法,输入了BIG5编码的内容,这样JRE把BIG5编码的内容当作GB2312的进行了GB2312->unicode的编码转换,这样转换后的结果显然不是用户想要输入的内容了。

      默认情况下,JRE会把输入输出的内容当作是按照系统默认编码方式编码的。

      3、在Servlet中,除了一定要把源文件用的是什么编码方式正确无误的”告诉”编译器外,还要注意实际提交的URL数据、表单数据的编码格式和request中声明的编码格式一致。

      客户端浏览器在通过表单和URL提交数据时,容器和JVM会将request中的数据看作是按照request所声明的编码方式来编码的,将数据由这种编码方式转换为unicode后再送入servlet(实际上容器会先将request中的数据转为一种中间编码方式,具体根据容器的配置而定,再由JVM由这种中间方式转换为unicode,通常这种中间格式是ISO)。servlet输出的unicode数据会由容器根据response中声明的编码方式进行转换,再送到客户端浏览器上。

      在接收客户端输入时,用request.setCharacterEncoding()声明请求中数据的编码方式。

      在向客户端输出时用response.setContentType("text/html;charset=");声明响应的数据的编码方式,告知浏览器以哪种编码方式显示。

      4、在JSP中,由于JSP本就会被JSP编译器编译为servlet来运行,因此情况与servlet相同。

      这两个JSP指令声明了请求和响应的编码方式。

      只要确保URL参数或表单中数据的编码方式和所声明的编码方式一致,再通过告知JSP编译器本JSP文件采用的编码方式及含有哪种字符,即可解决JSP的字符编码问题。

      这里是一个具体的例子:

      现在因为浏览器对UTF-8的支持,我们可以通过在源文件、请求、响应中都使用unicode编码方式,来轻松达到处理国际化和字符编码问题的目标。

      以我们使用的tomcat4.1.2为例,过程如下:

      1、编写JSP页面时:在每个JSP页面在页首都要增加一行:

      在编辑JSP页面时,一定要确保JSP文件以unicode的方式保存,目前几乎所有的编辑器都有以unicode编码保存或将文件内容转换成unicode的功能。

      2、增加一个用来声明request的CharacterEncoding的类SetCharacterEncodingFilter.java;

      SetCharacterEncodingFilter的这个类主要的作用就是:把request在从页面刚提交到server端的时候的encoding声明为我们想要的encoding,通过调用request的方法setCharacterEncoding (String encoding) 来改变,这样可以使request的从客户端传过来的时候,按我们在web.xml (在第二点可以讲到) 中配置的encoding来对提交的数据编码。

      3、修改web.xml文件,配置一个filter来过滤全部url请求,通过第二步中的类,声明所有url请求的编码类型未UTF-8。

      在web.xml文件中加上以下这段:

      

    posted @ 2007-11-03 16:43 jadmin 阅读(53) | 评论 (0)编辑 收藏

    今天看到有个网友写到“打字速度是程序员的命根子”的文章(调侃),引发俺进一步想,到底什么才是程序员的命根子。

    首先,程序员(初级)的主要<strong class="kgb" onmouseover="isShowAds = false;isShowAds2 = false;isShowGg = true;InTextAds_GgLayer="_u5DE5_u4F5C";KeyGate_ads.ShowGgAds(this,"_u5DE5_u4F5C",event)" style="border-top-width: 0px; padding-right: 0px; padding-left: 0px; font-weight: normal; border-left-width: 0px; border-bottom-width: 0px; padding-bottom: 0px; margin: 0px; cursor: hand; color: #0000ff; padding-top: 0px; border-right-width: 0px; text-decoration: underline" onclick="javascript:window.open("http://pagead2.googlesyndication.com/pagead/iclk?sa=l&ai=BwSwaDzAsR-a0H5ng6wPbwfy5CfqdyzOqkOW0A8CNtwGQvwUQARgBIIS04gcoFDgAUMSai7j4_____wFgnfHcgdAFoAH64sT8A6oBCjIwMDAwMjQxMjeyAQ1uZXdzLmNzZG4ubmV0yAEB2gEraHR0cDovL25ld3MuY3Nkbi5uZXQvbi8yMDA3MTEwMS8xMTAxNTIuaHRtbKkCLOcQc7QAgj7IAuL2oQGoAwHoA_EC9QMABAAA&num=1&adurl=http://www.chinahr.com/%3Fprj%3Dfa%26g%3D22&client=ca-pub-0892797939732602");GgKwClickStat("工作","chinahr.com","afc","2000024127");" onmouseout="isShowGg = false;InTextAds_GgLayer="_u5DE5_u4F5C"">工作是什么:根据<strong class="kgb" onmouseover="isShowAds = false;isShowAds2 = false;isShowGg = true;InTextAds_GgLayer="_u8BBE_u8BA1";KeyGate_ads.ShowGgAds(this,"_u8BBE_u8BA1",event)" style="border-top-width: 0px; padding-right: 0px; padding-left: 0px; font-weight: normal; border-left-width: 0px; border-bottom-width: 0px; padding-bottom: 0px; margin: 0px; cursor: hand; color: #0000ff; padding-top: 0px; border-right-width: 0px; text-decoration: underline" onclick="javascript:window.open("http://pagead2.googlesyndication.com/pagead/iclk?sa=l&ai=BYxtWDzAsR-a0H5ng6wPbwfy5CZaR2y2es9SAA8CNtwGQvwUQEBgQIIS04gcoFDgAUMnCw4D-_____wFgnfHcgdAFqgEKMjAwMDAyNDEyN7IBDW5ld3MuY3Nkbi5uZXTIAQHaAStodHRwOi8vbmV3cy5jc2RuLm5ldC9uLzIwMDcxMTAxLzExMDE1Mi5odG1sqQIs5xBztACCPsgC7qPNAqgDAegD8QL1AwAEAAA&num=16&adurl=http://www.team-top.com/&client=ca-pub-0892797939732602");GgKwClickStat("设计","www.team-top.com","afc","2000024127");" onmouseout="isShowGg = false;InTextAds_GgLayer="_u8BBE_u8BA1"">设计写代码;写文档;修改bug;功能测试;简单逻辑设计。

    如果只是看这些工作内容的话,程序员的命根子是什么呢?

    1:基础编码能力。这个能力其实不需要大学本科学历的,技校,自学,或者专业<strong class="kgb" onmouseover="isShowAds = false;isShowAds2 = false;isShowGg = true;InTextAds_GgLayer="_u57F9_u8BAD_u673A_u6784";KeyGate_ads.ShowGgAds(this,"_u57F9_u8BAD_u673A_u6784",event)" style="border-top-width: 0px; padding-right: 0px; padding-left: 0px; font-weight: normal; border-left-width: 0px; border-bottom-width: 0px; padding-bottom: 0px; margin: 0px; cursor: hand; color: #0000ff; padding-top: 0px; border-right-width: 0px; text-decoration: underline" onclick="javascript:window.open("http://pagead2.googlesyndication.com/pagead/iclk?sa=l&ai=BkCTxDzAsR-a0H5ng6wPbwfy5CZSctS342cKABcCNtwGQvwUQChgKIIS04gcoFDgAUJHV_foEYJ3x3IHQBaoBCjIwMDAwMjQxMjeyAQ1uZXdzLmNzZG4ubmV0yAEB2gEraHR0cDovL25ld3MuY3Nkbi5uZXQvbi8yMDA3MTEwMS8xMTAxNTIuaHRtbIACAakCLOcQc7QAgj7IAoS-twOoAwHoA_EC9QMABAAA&num=10&adurl=http://www.whpx.net/%3Fw%3Dgoog&client=ca-pub-0892797939732602");GgKwClickStat("培训机构","www.whpx.net","afc","2000024127");" onmouseout="isShowGg = false;InTextAds_GgLayer="_u57F9_u8BAD_u673A_u6784"">培训机构都可以学到。

    2:打字速度。

    当然,其他诸如“责任心”等不需赘述。综合来看,打字还真的是程序员的命根子,至少是两条腿中的一条。

    但是很明显,“打字是程序员的命根子”很多人都不认同,原因很简单,我们做的其实不仅仅是程序员(初级)的工作,而是兼作高级程序员,甚至UIDB、系统设计师,测试<strong class="kgb" onmouseover="isShowAds = false;isShowAds2 = false;isShowGg = true;InTextAds_GgLayer="_u5DE5_u7A0B_u5E08";KeyGate_ads.ShowGgAds(this,"_u5DE5_u7A0B_u5E08",event)" style="border-top-width: 0px; padding-right: 0px; padding-left: 0px; font-weight: normal; border-left-width: 0px; border-bottom-width: 0px; padding-bottom: 0px; margin: 0px; cursor: hand; color: #0000ff; padding-top: 0px; border-right-width: 0px; text-decoration: underline" onclick="javascript:window.open("http://pagead2.googlesyndication.com/pagead/iclk?sa=l&ai=BpgJSDzAsR-a0H5ng6wPbwfy5CY_v6izj8rLWAsCNtwGgjQYQEhgSIIS04gcoFDgAUMjeixlgnfHcgdAFqgEKMjAwMDAyNDEyN7IBDW5ld3MuY3Nkbi5uZXTIAQHaAStodHRwOi8vbmV3cy5jc2RuLm5ldC9uLzIwMDcxMTAxLzExMDE1Mi5odG1sqQIs5xBztACCPsgCl8itA6gDAegD8QL1AwAEAAA&num=18&adurl=http://www.gz-benet.com/&client=ca-pub-0892797939732602");GgKwClickStat("工程师","www.gz-benet.com/","afc","2000024127");" onmouseout="isShowGg = false;InTextAds_GgLayer="_u5DE5_u7A0B_u5E08"">工程师

    这就是为什么我们觉得“打字速度”只是我们工作中极小的一部分了。

    中国的专业分工并不细致,特别是在这个新兴的行业里。10年前程序员还是绝对的白领,高级技术<strong class="kgb" onmouseover="isShowAds = false;isShowAds2 = false;isShowGg = true;InTextAds_GgLayer="_u4EBA_u5458";KeyGate_ads.ShowGgAds(this,"_u4EBA_u5458",event)" style="border-top-width: 0px; padding-right: 0px; padding-left: 0px; font-weight: normal; border-left-width: 0px; border-bottom-width: 0px; padding-bottom: 0px; margin: 0px; cursor: hand; color: #0000ff; padding-top: 0px; border-right-width: 0px; text-decoration: underline" onclick="javascript:window.open("http://pagead2.googlesyndication.com/pagead/iclk?sa=l&ai=BJw-yDzAsR-a0H5ng6wPbwfy5Cfmbxi3v-OHCAcCNtwGgnAEQBRgFIIS04gcoFDgAUL3Z9Mj8_____wFgnfHcgdAFoAHHlpP_A6oBCjIwMDAwMjQxMjeyAQ1uZXdzLmNzZG4ubmV0yAEB2gEraHR0cDovL25ld3MuY3Nkbi5uZXQvbi8yMDA3MTEwMS8xMTAxNTIuaHRtbIACAagDAegD8QL1AwAEAAA&num=5&adurl=http://www.weaver.com.cn/solutions/HRM.asp&client=ca-pub-0892797939732602");GgKwClickStat("人员","www.weaver.com.cn","afc","2000024127");" onmouseout="isShowGg = false;InTextAds_GgLayer="_u4EBA_u5458"">人员。现在的程序员也就是个蓝领,工资顶多算是中等,辛苦却能排高等。做个纯粹的程序员似乎看不到前途。并且业届里有句老话“程序员不过三十”,吃的还是一碗青春饭。这不是纯粹的抱怨,事实如此。

    国外情况可能稍有不同。40岁的程序员也大有人在,不少人就喜欢这个工作,不像我们,必须得做的更高尚一点才能扬眉吐气。这也不能怪我们不够专业,不够踏实,行业特点、社会大环境、价值观等等决定了我们的选择----必须做的“高级”一点。

    说道做的高级一点,那就是作设计师,作项目经历,作老板。

    如果相作这些,仅仅编码和打字就远远不够了。我们中的大多数都在朝这个方向努力吧。学着设计,学着关系业界走向,学着创新。学习之路没有止境,但是打字速度却有止境。

    记得有篇文章说,人的职业生涯大体可以这样分(大意如此):

    兴奋期:刚入行(或者刚开始一份新工作),2-3个月。

    疲劳期:3-6个月,甚至更长。很多人在这个极端出局。

    成长期:安全度过疲劳期之后,可能需要几年

    成熟期:熟练掌握行业要领,开始置身于行业里思考。

    …..

    为了不出局,既需要努力,也需要要不断的学习和充实自己。

    所以,我们这种程序员(复合型)的命根子是什么?答案是:学习、思考和努力

    每天都学习一些新技术,不断充实自己。不要以为vs2008仅仅是个beta版就和我没关系,不要觉得SmartPhone还远,对于新技术不敏感,很难走很远。这个,是成长期必不可少的。

    思考是一切进步的积石。出了思考工作范围之内的,也可以思考整个行业的走向。把自己融入到行业里面才能想得到更深更透彻。等到能思考到创行业之新了,也就离功成名就不远了。这也就是进入成熟期的保证。

    而对于刚入行的年轻人,多思考也能让自己尽快度过疲劳期,不至于在这个阶段出局。

    然后再加上努力,还有什么事情是做不到的呢。

        共勉吧。

    posted @ 2007-11-03 16:26 jadmin 阅读(119) | 评论 (0)编辑 收藏

    最近装了个 Oracle,突然发现每次访问http://localhost:8080/ 都要求输入什么XDB的用户、口令

    确实不知道输入什么,不输又报错误,上网查了下,有两种解决办法:

    方法1:

    在安装Tomcat时设置其端口号为8081或其他,也可以在安装后找到conf目录下的server.xml文件,修改其端口号;

    方法2:

    重新建个oracle    database    -----database    configration    assistant-----创建新数据库new    database    在安装过程中去掉关于XDB数据库选项.安装完毕就可以了。   

    posted @ 2007-10-29 17:32 jadmin 阅读(343) | 评论 (0)编辑 收藏
    1、使用索引来更快地遍历表。

      缺省情况下建立的索引是非群集索引,但有时它并不是最佳的。在非群集索引下,数据在物理上随机存放在数据页上。合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
      a.有大量重复值、且经常有范围查询( > ,< ,> =,< =)和order by、group by发生的列,可考虑建立群集索引;
      b.经常同时存取多列,且每列都含有重复值可考虑建立组合索引;
      c.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。索引虽有助于提高性能但不是索引越多越好,恰好相反过多的索引会导致系统低效。用户在表中每加进一个索引,维护索引集合就要做相应的更新工作。

      2、在海量查询时尽量少用格式转换。
      3、ORDER BY和GROPU BY使用ORDER BY和GROUP BY短语,任何一种索引都有助于SELECT的性能提高。

      4、任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

      5、IN、OR子句常会使用工作表,使索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。
      6、只要能满足你的需求,应尽可能使用更小的数据类型:例如使用MEDIUMINT代替INT

      7、尽量把所有的列设置为NOT NULL,如果你要保存NULL,手动去设置它,而不是把它设为默认值。
      8、尽量少用VARCHAR、TEXT、BLOB类型
      9、如果你的数据只有你所知的少量的几个。最好使用ENUM类型
      10、正如graymice所讲的那样,建立索引。

      以下是我做的一个实验,可以发现索引能极大地提高查询的效率:
    我有一个会员信息表users,里边有37365条用户记录:
    在不加索引的时候进行查询:
    sql语句A:
    select * from users where username like '%许%';
    在Mysql-Front中的8次查询时长为:1.40,0.54,0.54,0.54,0.53,0.55,0.54 共找到960条记录
    sql语句B:
    select * from users where username like '许%';
    在Mysql-Front中的8次查询时长为:0.53,0.53,0.53,0.54,0.53,0.53,0.54,0.54 共找到836条记录
    sql语句C:
    select * from users where username like '%许';
    在Mysql-Front中的8次查询时长为:0.51,0.51,0.52,0.52,0.51,0.51,0.52,0.51 共找到7条记录
    为username列添加索引:
    create index usernameindex on users(username(6));
    再次查询:
    sql语句A:
    select * from users where username like '%许%';
    在Mysql-Front中的8次查询时长为:0.35,0.34,0.34,0.35,0.34,0.34,0.35,0.34 共找到960条记录
    sql语句B:
    select * from users where username like '许%';
    在Mysql-Front中的8次查询时长为:0.06,0.07,0.07,0.07,0.07,0.07,0.06,0.06 共找到836条记录
    sql语句C:
    select * from users where username like '%许';
    在Mysql-Front中的8次查询时长为:0.32,0.31,0.31,0.32,0.31,0.32,0.31,0.31 共找到7条记录

      在实验过程中,我没有另开任何程序,以上的数据说明在单表查询中,建立索引的可以极大地提高查询速度。

      另外要说的是如果建立了索引,对于like '许%'类型的查询,速度提升是最明显的。因此,我们在写sql语句的时候也尽量采用这种方式查询。

      对于多表查询我们的优化原则是:
      尽量将索引建立在:left join on/right join on ... +条件,的条件语句中所涉及的字段上。

      多表查询比单表查询更能体现索引的优势。

      11、索引的建立原则:
      如果一列的中数据的前缀重复值很少,我们最好就只索引这个前缀。Mysql支持这种索引。我在上面用到的索引方法就是对username最左边的6个字符进行索引。索引越短,占用的 磁盘空间越少,在检索过程中花的时间也越少。这方法可以对最多左255个字符进行索引。

      在很多场合,我们可以给建立多列数据建立索引。
      索引应该建立在查询条件中进行比较的字段上,而不是建立在我们要找出来并且显示的字段上
      12、一往情深问到的问题:IN、OR子句常会使用工作表,使索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。

    这句话怎么理解决,请举个例子
    例子如下:
    如果在fields1和fields2上同时建立了索引,fields1为主索引
    以下sql会用到索引
    select * from tablename1 where fields1='value1' and fields2='value2'
    以下sql不会用到索引
    select * from tablename1 where fields1='value1' or fields2='value2'
      13.索引带来查询上的速度的大大提升,但索引也占用了额外的硬盘空间(当然现在一般硬盘空间不成问题),而且往表中插入新记录时索引也要随着更新这也需要一定时间.
      有些表如果经常insert,而较少select,就不用加索引了.不然每次写入数据都要重新改写索引,花费时间; 这个视实际情况而定,通常情况下索引是必需的.

      14.我在对查询效率有怀疑的时候,一般是直接用Mysql的Explain来跟踪查询情况.
      你用Mysql-Front是通过时长来比较,我觉得如果从查询时扫描字段的次数来比较更精确一些.
    posted @ 2007-10-25 10:55 jadmin 阅读(73) | 评论 (0)编辑 收藏

      我们知道 XML+XSLT就可以直接输出到支持XML的浏览器上,如IE 5.0以上,但是,我们还要考虑到有不少浏览器不直接支持XML,在这种情况下,我们需要在服务器上进行转换成html输出到浏览器,这种临时过渡办法恐怕要在一段时间内一直要使用.

      使用Jsp 加上tablib标识库,我们可以完成这种转换。

      著名open source项目组jakarta.apache.org推出的系列标识库中,就有这个功能的tanglib:http://jakarta.apache.org/taglibs/doc/xsl-doc/intro.html

      按照jakarta配置方法,有点繁琐,需要修改或定义Web.xml,本人经过摸索,使用下列相当简单的办法,就可以使Jsp能成功运行XSL这个标识库了。

      xsl标识库有三个关键包:

    posted @ 2007-10-25 07:48 jadmin 阅读(92) | 评论 (0)编辑 收藏

    JSTL包括四个标签库,即Core标签库、XML标签库、国际化与格式化标签库和SQL标签库,这里介绍SQL标签库


       <%@    page    contentType="text/html;    charset=utf-8" %>   
       <%@    taglib    uri="http://java.sun.com/jsp/jstl/core"    prefix="c" %>   
       <%@    taglib    uri="http://java.sun.com/jsp/jstl/sql"    prefix="sql" %>


    一.连接MySQL的几种方式


       1.创建普通的数据源


         <sql:setDataSource var="example" driver="com.mysql.jdbc.Driver"   
           url="jdbc:mysql://127.0.0.1:3306/test"     
           user="root" password="" [scope="request"]/>  


       2.从JNDI名称空间中获得一个数据源


         <sql:setDataSource var="example" dataSource="jdbc/bn" />


    二.<sql:query>和<sql:update>(<sql:param>/<sql:dateParam>可以用于query和update)


         <sql:query    var="qurey"    dataSource="${example}"    sql="select    *    from    dept />


         <sql:query    var="qurey2"    dataSource="${example}">  


                select * from dept


         </sql:query>


         <sql:query var="qurey3" dataSource="${example}" [maxRows="20"] [startRow="1"]


          [scope="request"]>  


                select * from dept where deptid=? and deptname=? and createtime=?


                <sql:param value="1"/>


                <sql:param>wuhui</sql:param>


                <sql:dateParam>new Date()</sql:dateParam>


         </sql:query>


         <c:forEach var="row" items="${query.rows}"></c:forEach>//迭代


         <sql:update var="update"   dataSource="${example}" >


                update dept set deptid=? and deptname=?


                <sql:param value="1"/>


                <sql:param>wuhui</sql:param>


           update 处理增删改什么都可以,除了查询


         </sql:update>


          query和update语法基本一样


    3.<sql:transaction>事务处理标签


        <sql:transaction dataSource="example"


         [isolation="read_committed|read_uncommitted|repeatable_read|serializable"]>


              <sql:query>and<sql:update>语句


        </sql:transaction>

    posted @ 2007-10-24 22:18 jadmin 阅读(87) | 评论 (0)编辑 收藏

    本示例从sql2000的pubs数据库中的employee表取出first name、lname.

    <%@ page contentType="text/html; charset=utf-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <html>
    <head>
        <title>jstl连接SQL Server 2000数据库</title>
    </head>
    <body>
    <h3>本示例从sql2000的pubs数据库中的employee表取出first name、lname</h3>
    当然,运行本JSP页须机器已经安装了SQL Server 2000数据库<br>
        <sql:setDataSource driver="com.microsoft.jdbc.sqlserver.SQLServerDriver" url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=pubs" user="sa" password="pass"/>
        <sql:query var="rs">
       select * from employee
        </sql:query>
        <table border="1">
         <tr align="center">
          <td><strong>
           first name</strong>
          </td>
          <td><strong>
           last name
          </strong></td>
         </tr>
         <c:forEach items="${rs.rows}" var="row">
          <tr>
           <td>
            <c:out value="${row.fname}"></c:out>
           </td>
           <td>
            <c:out value="${row.lname}"></c:out>
           </td>
          </tr>
         </c:forEach>
        </table>
        <hr>
    </body>
    </html>

    结果:

    本示例从sql2000的pubs数据库中的employee表取出first name、lname

    当然,运行本JSP页须机器已经安装了SQL Server 2000数据库

    posted @ 2007-10-24 21:15 jadmin 阅读(81) | 评论 (0)编辑 收藏

    日期输入页面:

    dateInput.jsp

    <%@ page pageEncoding="UTF-8" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

    <html>
    <head>

    <title>Currency Formatting</title>
    </head>
    <body>


    <form method="post" action="doDateInput.jsp">
    Please enter your birthday:
    <select name="month">
    <option value="01">January</option>
    <option value="02">February</option>
    <option value="03">March</option>
    <option value="04">April</option>
    <option value="05">May</option>
    <option value="06">June</option>
    <option value="07">July</option>
    <option value="08">August</option>
    <option value="09">September</option>
    <option value="10">October</option>
    <option value="11">November</option>
    <option value="12">December</option>
    </select>

    <select name="day">
    <c:forEach begin="1" end="31" var="day">
    <option><c:out value="${day}"/></option>
    </c:forEach>
    </select>
    <select name="year">
    <c:forEach begin="1930" end="2003" var="year">
    <option><c:out value="${year}"/></option>
    </c:forEach>
    </select>
    <input type="submit" value="Submit" />
    </form>



    </body>
    </html>

    日期处理页面:

    doDateInput.jsp

    <%@ page pageEncoding="UTF-8" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

    <html>
    <head>

    <title>Currency Formatting</title>
    </head>
    <body>


    <fmt:parseDate
         
    var="date"
          parseLocale
    ="zh_CN"
          value
    ="${param.year}-${param.month}-${param.day}">
       
    </fmt:parseDate>

    <fmt:formatDate value="${date}" dateStyle="full"/>



    </body>
    </html>

    提交后,显示结果诸如1930年11月1日 星期六

    如果日期输入是en_US格式,也就是做以下改动

    <select name="month">
    <option value="Jan">January</option>
    <option value="Feb">February</option>
    <option value="Mar">March</option>
    <option value="Apr">April</option>
    <option value="May">May</option>
    <option value="Jun">June</option>
    <option value="Jul">July</option>
    <option value="Aug">August</option>
    <option value="Sep">September</option>
    <option value="Oct">October</option>
    <option value="Nov">November</option>
    <option value="Dec">December</option>
    </select>

    则日期处理页面也要做想应改动

    <fmt:parseDate
          var="date"
          parseLocale="en_US"
          value="${param.month} ${param.day}, ${param.year}">  

    注意逗号后有一个空格,因为英文的日期格式为"May 25, 1997"
    </fmt:parseDate>

    posted @ 2007-10-24 20:11 jadmin 阅读(90) | 评论 (0)编辑 收藏

    <%@ page contentType="text/html;charset=utf-8" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <%@ taglib uri="http://java.sun.com/jstl/sql" prefix="sql"%>
    <%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt"%>
    <%@ taglib uri="http://java.sun.com/jstl/xml" prefix="x"%>


           URL相关的标签

    <c:import>
    语法1:资源的内容使用String对象向外暴露
    <c:import url="url" [context="context"]
    [var-"varName"][scope="{page|request|session|application}"]
    [charEncoding="charEncoding"]>
    optional body content for <c:param> subtags
    </c:import>
    语法2:资源的内容使用Reader对象向外暴露
    <c:import url="url" [context="context"]
    varReader="varReaderName"
    [charEncoding="charEncoding"]>
    body content where varReader is consumed by another action
    </c:import>
    例1:                                                                                                                          
    <h3>绝对路径 URL</h3>
    <blockquote>
    <ex:escapeHtml>
       <c:import url="http://127.0.0.1:8080/ch15/beimport.jsp"/>
    </ex:escapeHtml>
    </blockquote>
    <h3>相对路径 URL</h3>
    <blockquote>
    <ex:escapeHtml>
       <c:import url="beimport.jsp"/>
    </ex:escapeHtml>
    </blockquote>
    <h3>encode:</h3>
    <a href=<c:url value="beimport.jsp"><c:param name="userName" value="hellking"/></c:url>>--></a>
    相当于<--jsp:include file=""/>
    <c:import var="myurl" url="http://127.0.0.1:8080/ch15/beimport.jsp"/>

       <c:out value="${myurl}"/>

    <c:import var="myurl2" url="beimport.jsp"/>
       <c:out value="${myurl2}"/>
    </pre>
    </blockquote>
    <h3>传递参数到指定的URL</h3>
    <blockquote>
       <c:import url="beimport.jsp" charEncoding="gb2312">
       <c:param name="userName" value="hellking"/>
    </c:import>  


    <c:redirect>
    把客户的请求重定向到另一个资源
    语法1:没有BODY时.
    <c:redirect url="value" [context="context"]/>
    例1:
    <c:url value="beimport.jsp" var="test"><c:param name="userName" value="hellking"/></c:url>
    <c:redirect url="${test}"/>
    语法2:在BODY中指定查询的参数.
    <c:redirect url="value" [context="context"]/>
    <c:param>subtags
    </c:redirect>
    例1:
    <c:redirect url="beimport.jsp">
    <c:param name="userName" value="hellking"/>
    </c:redirect>

    <c:url>
    用于构造URL,主要的用途是URL重写.
    语法1:No Body.
    <c:url value="value" [context="context"]
    [var="varName"][scope="{page|request|session|application}"]/>
    例1:
    <c:url value="beimport.jsp"/>
    语法2:Body.
    <c:url value="value" [context="context"]
    [var="varName"][scope="{page|request|session|application}"]>
    <c:param>subtags
    </c:url>
    例2:
    <c:url   var="myurl" value="beimport.jsp" scope="session">
    <c:param name="userName" value="hellking"/>
    </c:url>
    <c:out value="${myurl}"/>

    <c:param>
    在<c:import>.<c:url>.<c:redirect>中添加请求的参数.
    语法1:参数的值使用value属性指定.
    <c:param name="name" value="value"/>

    语法2:参数的值在标签的BODY中指定.
    <c:param name="name">
    parameter value
    </c:param>

           迭代标签
    <c:forEach>
    在一个包括一系列对象的Collection中迭代计算它的BodyContent,或者重复迭代固定的次数.
    语法1:在Collection中迭代.
    <c:forEach [var="varName"] items="collection"
    [varStatus="varStatusName"]   //这是迭代状态
    [begin="begin"][end="end"][step="step"]>
    body content
    </c:forEach>
    例1:迭代Collection
    <table border=1>
    <c:forEach var="users" items="${users}">
       <tr>
       <td><c:out value="${users.userName}"/></td>
       <td><c:out value="${users.password}"/></td>
       <td><c:out value="${users.age}"/></td>
       </tr>
    </c:forEach>
    </table>
    例2:other迭代
    <%
    int[] myIntArray=new int[]{1,2,3,4,5,65,34};
    String[] myStringArray=new String[]{"I ","am ","a ","Java","fans"};
    Vector v=new Vector();
    v.add("this");
    v.add("is");
    v.add("myEnumeration");
    v.add("!");
    Enumeration myEnumeration=v.elements();
    HashMap myNumberMap=new HashMap();
    myNumberMap.put("hellking","23");
    myNumberMap.put("guest","23");
    myNumberMap.put("guest2","223");
    myNumberMap.put("guest3","232");
    request.setAttribute("myIntArray",myIntArray);
    request.setAttribute("myStringArray",myStringArray);
    request.setAttribute("myEnumeration",myEnumeration);
    request.setAttribute("myNumberMap",myNumberMap);
    %>
    <h4>Array of primitives (int)</h4>
    <c:forEach var="i" items="${myIntArray}">
       <c:out value="${i}"/> ?
    </c:forEach>
    <h4>Array of objects (String)</h4>
    <c:forEach var="string" items="${myStringArray}">
       <c:out value="${string}"/><br>
    </c:forEach>
    <h4>myEnumeration (warning: this only works until myEnumeration is exhausted!)</h4>
    <c:forEach var="item" items="${myEnumeration}" begin="0" end="5" step="1">
       <c:out value="${item}"/><br>
    </c:forEach>
    <h4>Properties (Map)</h4>
    <c:forEach var="prop" items="${myNumberMap}" begin="1" end="5">
       <c:out value="${prop.key}"/> = <c:out value="${prop.value}"/><br>
    </c:forEach>
    <h4>String (Common Separated Values)</h4>
    <c:forEach var="token" items="red,blue,green">
       <c:out value="${token}"/><br>
    </c:forEach>


    语法2:迭代固定的次数.
    <c:forEach [var="varName"]
    [varStatus="varStatusName"]
    begin="begin" end="end"[step="step"]>
    body content
    </c:forEach>
    例1:
    <c:forEach var="i" begin="1" end="10">
       <c:out value="${i}"/> -->
    </c:forEach>

    例2:<h4>第二种迭代:1 to 10,step=3</h4>
    <c:forEach var="i" begin="1" end="10" step="3">
       <c:out value="${i}"/> -->
    </c:forEach>

    例3:迭代状态
       <td><c:out value="${status.index}"/></td>
       <td><c:out value="${status.count}"/></td>
       <td><c:if test="${status.first}">
          <b></c:if>
           <c:out value="${status.first}"/></b></td>
       <td><c:if test="${status.last}">
          <i> </c:if>
          <c:out value="${status.last}"/></i></td>    
       </tr>

    <c:forTokens>
    用于处理TokenString的迭代,可以指定一个或者多个分隔符.
    语法:
    <c:forTokens items="stringOfTokens" delims="delimiters"
    [var="varName"]
    [varStatus="varStatusName"]
    [begin="begin"][end="end"][step="step"]
    body content
    </c:forTokens>
    例:
    <h4>使用 '|' 作为分割符</h4>
    <c:forTokens var="token" items="blue,red,green|yellow|pink,black|white"
                   delims="|">
       <c:out value="${token}"/> &copy;
    </c:forTokens>
    <h4>使用 '|'和',' 作为分割符</h4>
    <c:forTokens var="token" items="blue,red,green|yellow|pink,black|white"
                   delims="|,">
       <c:out value="${token}"/> &copy;
    </c:forTokens>
    <h4>使用 '-' 作为分割符</h4>
    <c:forTokens var="token" items="blue--red--green--yellow--pink--black--white"
                   delims="--">
       <c:out value="${token}"/> &copy;
    </c:forTokens>


           一般用途

    输出
    <c:out value="&{XXX}" default="这个值不存在">
    设置一个变量
    <c:set>
    例:设置一个javaBean的属性<c:set value="hk2" target="${user}" property="userName"/>

    删除某个变量或者属性
    <c:remove var="${XXX}" [scope="{page|request|...}"]>

    捕获由嵌套在它里面的标签抛出的异常
    <c:catch [var="varName"]>
    nested actions
    </c:catch>


           条件标签
    <c:if>
    语法1:无Body的情况
    <c:if test="testCondition"
    var="varName" [scope="{page|request|session|application}"]/>
    例:
    <c:if test="${user.age<18}">
    对不起,你还的年龄过小,不能范围这个网页◎!
    </c:if>
    语法2:有Body的情况
    <c:if test="testCondition"
    var="varName" [scope="{page|request|session|application}"]>
    body content
    </c:if>


    <c:choose>   <c:when> <c:otherwise>
    例:
    <c:choose>
         <c:when test="${user.age <=18}">
           <font color="blue">
         </c:when>
         <c:when test="${user.age<=30&&user.age>18}">
           <font color="red">
         </c:when>
         <c:otherwise>
           <font color="green">
         </c:otherwise>
       </c:choose>
    你的年龄是:<c:out value="${user.age}"/>

    注意,它的Body内容只能由以下的元素构成:
    空格.
    0个或者多个<when>子标签,<when>必须在<otherwise>标签之前出现.
    0个或者多个<otherwise>子标签.


           数据库相关


    QUERY标签


    语法1   无SQL参数
    <sql:query sql="sqlQuery"
    var="varName"
    [scope="{page|request|session|application}"]
    [dataSource="dataSource"]
    [maxRows="maxRows"]
    [startRow="startRow"]>
    SQL
    </sql:query>

    例1:
    <sql:query var="query" dataSource="${example}">
    SELECT * FROM contact
    </sql:query>

    <table border="1">
       <c:forEach var="row" items="${qurey.rows}">
       <tr>
           <td>Name:<c:out value="${row[0]}"/></td>
           <td>mobile:<c:out value="{row.mobile}"/></td>
       </tr>
       </c:forEach>
    </table>
    <hr>

    例2:
    <sql:query var="result" dataSource="mbq">
    select   code, curprice, openprice, highprice, lowprice,   balanceprice, reservecount,totalamount,curamount, openamount,

    closeamount,   reservechange, totalmoney,   buyprice1, sellprice1, buyamount1, sellamount1, buyprice2, sellprice2, buyamount2,

    sellamount2, buyprice3, sellprice3, buyamount3, sellamount3, buyprice4, sellprice4, buyamount4, sellamount4, buyprice5,

    sellprice5, buyamount5, sellamount5, outamount, inamount, time,yesterbalanceprice,closeprice from currentdata WHERE Code <>

    'SYS' order by code
    </sql:query>代码 现价 开盘 最高 最低 平均 订货量 成交量
    <c:forEach var="row" items="${result.rowsByIndex}">${row[0]} ${row[1]} ${row[2]} ${row[3]} ${row[4]}

    ${row[5]} ${row[6]} ${row[7]}
    </c:forEach>

    语法2   有query参数
    <sql:query sql="sqlQuery"
    var="varName"
    [scope="{page|request|session|application}"]
    [dataSource="dataSource"]
    [maxRows="maxRows"]
    [startRow="startRow"]>
    [<sql:param>""]
    </sql:query>
    例1:变量传入
    <c:set var="aa">
    GX0511A
    </c:set>
    <sql:query var="result" dataSource="mbq" sql="select * from currentdata WHERE   code=? order by code">
       <sql:param>
        ${aa}
       </sql:param>
    </sql:query>代码 现价 开盘 最高 最低 平均 订货量 成交量
    <table border="1">
    <c:forEach var="row" items="${result.rowsByIndex}">
    <tr>
    <td>Name: ${row[0]} </td><td>2:${row[1]}</td> <td>3:$10:02 2005-11-17{row[2]}</td>
    </tr>
    <tr>
    <td>${row[3]}</td><td> ${row[4]}</td><td>${row[5]}</td><td>${row[6]}</td><td>${row[7]}</td>
    </tr>
    </c:forEach>
    </table>

    语法3 有query,且query在body中.
    <sql:query var="varName"
    [scope="{page|request|session|application}"]
    [dataSource="dataSource"]
    [maxRows="maxRows"]
    [startRow="startRow"]>
    query
    [<sql:param>""]
    </sql:query>
    例1:值传入
    <sql:query var="result" dataSource="mbq">
    select * from currentdata WHERE   code=? order by code
       <sql:param value="GX0511A"/>
    </sql:query>代码 现价 开盘 最高 最低 平均 订货量 成交量
    <table border="1">
    <c:forEach var="row" items="${result.rowsByIndex}">
    <tr>
    <td>Name: ${row[0]} </td><td>2:${row[1]}</td> <td>3:$10:02 2005-11-17{row[2]}</td>
    </tr>
    <tr>
    <td>${row[3]}</td><td> ${row[4]}</td><td>${row[5]}</td><td>${row[6]}</td><td>${row[7]}</td>
    </tr>
    </c:forEach>
    </table>


    UPDATA标签

    语法1
    <sql:update sql="sqlUpdate"
    [dataSource="dataSource"]
    [var="varName"][scope="{page|request|session|application}"]/>
    例1
    <sql:update var="update2" sql="insert into users values('测试','9999',1,'test')"
    dataSource="jdbc/quickdb2">

    </sql:update>

    语法2
    <sql:update sql="sqlUpdate"
    [dataSource="dataSource"]
    [var="varName"][scope="{page|request|session|application}"]>
    <sql:param>actions
    </sql:update>
    例1
    <sql:update var="update2" sql="delete from users where username=?" dataSource="jdbc/quickdb2">
       <sql:param value="测试"/>
    </sql:update>

    语法3
    <sql:update [dataSource="dataSource"]
    [var="varName"][scope="{page|request|session|application}"]>
    update statement
    optional<sql:param>actions
    </sql:update>
    例1
    <c:set var="aa" value="测试"/>

    <sql:update var="update2"   dataSource="jdbc/quickdb2">
    delete from users where username=?
       <sql:param>
        ${aa}
       </sql:param>
    </sql:update>


    TRANSACTION标签
    <sql:transaction [dataSource="dataSource"]
    [isolation=isolationLevel]>
    <sql:query>and<sql:update>statements
    </sql:transaction>
    isolationLevel::="read_committed"|"read_uncommitted"|"repeatable_read"|"serializable"
    注意:嵌套在它里面的<sql:query>和<sql:update>标签不用使用DataSource属性来另外指定数据源.


           XML标签
    <x:parse>
    用于解析XML文档
    语法1:解析由String或者Reader对象组成的XML文档.
    <x:parse xml="XMLDocument"
    {var="var" [scope="scope"]|varDom="var"[scopeDom="scope"]}
    [systemId="systemId"]
    [filter="filter"]/>

    语法2:解析在Body中指定的XML文档.
    <x:parse
    {var="var" [scope="scope"]|varDom="var"[scopeDom="scope"]}
    [systemId="systemId"]
    [filter="filter"]>
    XML Document to parse
    </x:parse>
    例1:
    <c:set var="xmlText">
       <a>
        <b>
         <c>
          test1
         </c>
        </b>
        <d>
          test2
        </d>
       </a>
    </c:set>
    <x:parse var="myxml" xml="${xmlText}" />
    <x:out select="$myxml/a/b/c"/>
    <x:out select="$myxml//d"/>
    <x:parse var="bookxml">
       <books>
          <book id="01">
             <name>jsp 应用开发详解</name>
             <price>59</price>
          </book>
       </books>
    </x:parse>
    <x:out select="$bookxml/books//name"/>


    <x:out>
    计算XPath表达式,并把结果返回给JspWriter对象进行输出
    语法:
    <x.out select="XpathExpression" [escapeXml="true|false}"]/>
    例1:
    <x:parse var="test">
       <books>
          <book id="01">
             <name>jsp 应用开发详解</name>
             <price>59</price>
          </book>     
       </books>
    </x:parse>
    <x:out select="$test/books//name"/><br>
    <x:out select="$test//name"/><br>
    <x:out select="$test/books/book/name"/><br>
    <x:out select="$test/books/book/price"/><br>


    <x:set>
    计算XPath表达式,并且把结果保存到某个范围的变量中.
    语法:
    <x:set select="XPathExpression"
    var="varName"[scope="{page|request|session|application}"]/>

    posted @ 2007-10-23 19:13 jadmin 阅读(48) | 评论 (0)编辑 收藏
    搭建的步骤:

    1、下载、安装、注册editplus2。

    2、修改几处设置,这是可选的。进入tools 》preferences》font,修改成大点的字体,小字累眼睛。在files选项卡里去掉创建备份文件,把Create backup file when saving前面的勾去掉。

    3、加入Java工具,这是最主要的。
    添加Java编译工具。在tools 》 configure user tools 》Add tool 》Program,在menu test 中输入“java编译”,command输入“javac”,Argument选择$(FilePath),Initial directory选择$(FileDir),再选上Capture output。点击Apply就ok了。
    添加Java解释器。在tools 》 configure user tools 》Add tool 》Program,在menu test 中输入“java运行”,command输入“java”,Argument选择$(FileNameNoExt),Initial directory选择$(FileDir)。点击Apply就ok了。
    添加当前文件目录下的Dos窗口。在tools 》 configure user tools 》Add tool 》Program,在menu test 中输入“cmd”,command输入“cmd”,Initial directory选择$(FileDir)。 点击Apply就ok了。
    实现的效果就是:用editplus编辑一个Java源文件,然后按CTRL + 1,编译;CTRL + 2,运行;当一个文件有多个class,你想运行其中的某一个,CTRL + 3,跳出cmd窗口,已经指向当前目录了,然后手工输入就可以了。

    4、修改Java文件模板。到editplus安装目录下,用记事本打开template.java文件,修改成你想要的新建Java文件的模板。这样每次从file 》 new 》java 创建的Java文件都是模板里的格式。

    5、然后下载Java语法高亮文件java.stx,以及Java自动完成文件,java.acp。(这种自动完成没有eclipse那么智能、变态)

    6、在editplus安装目录下面新建一个名为java.ctl的文本文件,内容为:
    #TITLE=JavaCodeClip
    #INFO
    #SORT=y
    重启editplus,在左上角的Cliptext的下拉列表中选择JavaCodeClip,点击add,然后可以添加常用的Java源代码片段。比如测试经常用的System.out.println();可以这样添加:在title里面输入print,在text body中输入System.out.println(^!); 其中^!的意思是双击在文件中插入代码之后,光标的位置。
    posted @ 2007-10-23 07:53 jadmin 阅读(52) | 评论 (0)编辑 收藏
    1用JAVA自带的函数
    publicstaticbooleanisNumeric(String str){
      
    for(inti=str.length();--i>=0;){   
       
    if(!Character.isDigit(str.charAt(i))){
        
    returnfalse;
        }
       }
      
    returntrue;
    }

    2用正则表达式
    public static boolean isNumeric(String str){
         Pattern pattern = Pattern.compile("[0-9]*");
         return pattern.matcher(str).matches();    
    }

    3用ascii码
    public static booleanisNumeric(String str){
      for(inti=str.length();--i>=0;){
          intchr=str.charAt(i);
           if(chr<48 || chr>57)
             return false;
        }
       return true;
    }
    posted @ 2007-10-14 01:31 jadmin 阅读(74) | 评论 (0)编辑 收藏

    import java.io.*;

    public class ToFile
    {
    public static void main(String[] args)
    {
       try
       {
        String filename = "out.txt";
        BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
        bw.write("Hello,China!");
        bw.write("\n");
        bw.write("Hello,World!");
        bw.close();
       }
       catch(IOException e)
       {
        System.out.println("IOException");
        e.printStackTrace();
       }
    }
    }

    posted @ 2007-10-08 21:45 jadmin 阅读(91) | 评论 (0)编辑 收藏

    古之欲明明德于天下者,先治其国;

    欲治其国者,先齐其家;欲齐其家者,先修其身;

    欲修其身者,先正其心;欲正其心者,先诚其意;

    欲诚其意者,先致其知,致知在格物。

    posted @ 2007-10-04 12:36 jadmin 阅读(125) | 评论 (0)编辑 收藏

      在网络给我们的工作学习带来极大方便的同时,病毒、木马、后门以及黑客程序也严重影响着信息的安全。这些程序感染计算机的一个共同特点是在注册表中写入信息,来达到如自动运行、破坏和传播等目的。以下是笔者在网上收集的,通过修改注册表来对付病毒、木马、后门以及黑客程序,保证个人计算机的安全。

      1.清理访问“网络邻居”后留下的字句信息

      在HEKY_CURRENT_USER\Network\Recent下,删除下面的主键。

      2.取消登陆时自动拨号

      在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Network\RealModeNet下修改右边窗口中的“autologon”为“01 00 00 00 00”。

      3.取消登录时选择用户

      已经删除了所有用户,但登录时还要选择用户,我们要取消登录时选择用户,就要在HKEY_LOCAL_MACHINENetworkLogon下,在右边的窗口中,修改"UserProfiles"值为"0"。

      4.隐藏上机用户登录的名字

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Winlogon下在右边的窗口中新建字符串"DontDisplayLastUserName",设值为"1"。

      5.预防Acid Battery v1.0木马的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices下若在右边窗口中如发现了“Explorer”键值,则说明中了YAI木马,将它删除。

      6.预防YAI木马的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices下若在右边窗口中如发现了“Batterieanzeige”键值,则说明中了YAI木马,将它删除。

      7.预防Eclipse 2000木马的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices下若在右边窗口中如发现了“bybt”键值,则将它删除。

      然后在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices下删除右边的键值“cksys”,重新启动电脑。

      8.预防BO2000的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices下若在右边窗口中如发现了“umgr32.exe”键值,则说明中了BO2000,将它删除。

      9.预防爱虫的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下若在右边窗口中如发现了“MSKernel32”键值,就将它删除。

      10.禁止出现IE菜单中“工具”栏里“interner选项”

      把c:\windows\system下的名为inetcpl.cpl更名为inetcpl.old或则别的名字后就会出现禁止使用的情况把名字再换回来,就可以恢复使用。

      11.预防BackDoor的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下若在右边窗口中如发现了“Notepad”键值,就将它删除。

      12.预防WinNuke的破坏

      在HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxDMSTCP下在右边的窗口中新建或修改字符串“BSDUrgent”,设其值为0。

      13.预防KeyboardGhost的破坏

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices下如发现KG.EXE这一键值,就将它删除,并查找KG.exe文件和kg.dat文件,将它们都删除。

      14.查找NetSpy黑客程序

      在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下,在右边的窗口中寻找键"NetSpy",如果存在,就说明已经装有NetSpy黑客程序,把它删除。

    posted @ 2007-10-03 10:00 jadmin 阅读(41) | 评论 (0)编辑 收藏

    package com.zh.util;

    import com.zh.conpool.Condata;
    import java.sql.ResultSet;
    import java.sql.SQLException;

    public class page
    {

         ResultSet rs;
         ResultSet rst;
         private int intCountTopic;
         public int intPageSize;
         public int intPageCount;
         public int intPage;
         private String Countsql;
         private String Pagisql;
         private String str;
         private String str_where;
         private String str_parameter;
         private String nowPage;
         private String HttpFile;
         Condata db;

         public page()
         {
             rs = null;
             rst = null;
             intCountTopic = 0;
             intPage = 0;
             Countsql = null;
             Pagisql = null;
             str = null;
             str_where = null;
             str_parameter = "";
             db = new Condata();
         }

         public static void main(String args[])
         {
         
         }

         public void setPages(int i)
         {
             intPageSize = i;
         }

         public String getPagisql()
         {
             return Pagisql;
         }

         public ResultSet setQuerysql(String s, String s1, String s2, String s3)
             throws SQLException
         {
             ResultSet resultset = null;
             nowPage = s3;
             HttpFile = s2;
             Countsql = "select count(*) from " + s + " " + s1;
             Pagisql = "select * from " + s + " " + s1 + " order by id desc";
             try
             {
                 Condata _tmp = db;
                 Condata.getConnection();
             }
             catch(Exception exception)
             {
                 exception.getMessage();
             }
             try
             {
                 resultset = querySql(Countsql, Pagisql);
             }
             catch(SQLException sqlexception)
             {
                 sqlexception.getMessage();
             }
             return resultset;
         }

         public ResultSet querySql(String s, String s1)
             throws SQLException
         {
             try
             {
                 Condata condata = db;
                 Condata.getConnection();
             }
             catch(Exception exception) { }
             if(nowPage == null)
             {
                 intPage = 1;
             } else
             {
                 intPage = Integer.parseInt(nowPage);
                 if(intPage < 1)
                     intPage = 1;
             }
             rs = db.executeQuery(s);
             if(rs.next())
                 intCountTopic = rs.getInt(1);
             intPageCount = intCountTopic % intPageSize == 0 ? intCountTopic / intPageSize : intCountTopic / intPageSize + 1;
             if(intPage > intPageCount)
                 intPage = intPageCount;
             rs.close();
             rst = db.executeQuery(s1);
             return rst;
         }

         public int getCountTopic()
         {
             return intCountTopic;
         }

         public int getPageCount()
         {
             return intPageCount;
         }

         public int getIntPage()
         {
             return intPage;
         }

         public String PageFooter()
         {
             String s = "<form action=" + HttpFile + " name=form1 methord=post>";
             int i = intPage - 1;
             int j = intPage + 1;
             int k = (intPageSize * getIntPage() + 1) - intPageSize;
             if(k < 0)
                 k = 0;
             s = s + "<font style='font-size: 9pt'>\u603B\u8BA1<font color='red'>" + getCountTopic() + "</font>\u6761\u8BB0\u5F55," + "\u3010\u5171<font   color='red'>" + getPageCount() + "</font>\u9875\u3011";
             s = s + "\u3010" + intPageSize + "\u6761/\u9875\u3011 \u5F53\u524D\u7B2C<font color='red'>" + getIntPage() + "</font>\u9875(\u5217\u51FA\u7B2C" + k + "\u5230\u7B2C" + getIntPage() * intPageSize + "\u6761) &nbsp; &nbsp; ";
             if(intPage > 1)
                 s = s + " <A href=" + HttpFile + "?pages=1" + str_parameter + ">\u9996\u9875</A> ";
             else
                 s = s + " \u9996\u9875 ";
             if(intPage > 1)
                 s = s + " <A href=" + HttpFile + "?pages=" + i + str_parameter + ">\u4E0A\u4E00\u9875</A> ";
             else
                 s = s + " \u4E0A\u4E00\u9875 ";
             if(intPage < intPageCount)
                 s = s + " <A href=" + HttpFile + "?pages=" + j + str_parameter + ">\u4E0B\u4E00\u9875</A> ";
             else
                 s = s + " \u4E0B\u4E00\u9875 ";
             if(intPageCount > 1 && intPage != intPageCount)
                 s = s + " <A href=" + HttpFile + "?pages=" + intPageCount + str_parameter + ">\u5C3E\u9875</A>";
             else
                 s = s + " \u5C3E\u9875</font>";
             s = s + "</form>";
             return s;
         }

         public void closeConn()
         {
             db.close();
         }
    }

    posted @ 2007-10-02 21:32 jadmin 阅读(59) | 评论 (0)编辑 收藏

    ============================JSP数据分页显示代码(完整、高效)============================

    <%@ page language="java" import="java.util.*,java.sql.*" %>
    <%@ page contentType="text/html;charset=gb2312" %>
    <jsp:useBean id="cn" scope="page" class="DBConnection.Conn" />
    <%
    //变量声明
    int intpagesize; //一页显示的记录数
    int introwcount; //记录总数
    int intpagecount; //总页数
    int intpage; //待显示页码

    //设置一页显示的记录数
    intpagesize = 20;
    //设置当前网页文件名
    string strpageurl="show.jsp";

    //取得待显示页码
    string strpage = request.getparameter("page");
    if(strpage==null){
    intpage = 1;
    }
    else{
    //将字符串转换成整型
    intpage = java.lang.integer.parseint(strpage);
    if(intpage<1) intpage = 1;
    }

    //获取记录总数
    ResultSet rsc=cn.rsexecuteQuery("Select count(id) as AllRecord from tablename");
    introwcount=rsc.getInt("AllRecord");
    rsc.close();
    //记算总页数
    intpagecount = (introwcount+intpagesize-1) / intpagesize;

    if(intpage>intpagecount) intpage = intpagecount;

    //取得记录集
    ResultSet rs=cn.rsexecuteQuery("select top "+intpagesize+" * from tablename where id not in (select top "+((intpage-1)

    *intpagesize)+" id from tablename order by id desc) order by id desc");
    while(rs.next) {
    %>

    ********这里写循环体*******

    <%
    }
    //关闭结果集
    rs.close();
    %>
    <%-- 下面为页码输出代码段 --%>
    共<%=intpagecount%>页&nbsp;当前页< %=intpage%>/<%=intpagecount%>&nbsp;
    <%if(intpage>1){%><a href="<%=strpageurl%>&amp;page=1">首页</a><%}%>&nbsp;<a href="<%=strpageurl%>&amp;page=<%=intpage-

    1%>">上一页</a>&nbsp;
    <%if(intpage<intpagecount){%><a href="<%=strpageurl%>&amp;page=<%=intpage+1%>">下一页</a>&nbsp;<a href="<%=strpageurl%

    >&amp;page=<%=intpagecount%>">末页</a><%}%>

    ============================jsp的分页显示代码============================

    <%@ page contentType="text/html;charset=gb2312" %>
    <%@ page language="java" import="java.sql.*" %>

    <script language="javascript">
    function newwin(url) {
    var

    newwin=window.open(url,"newwin","toolbar=no,location=no,directories=no,status=no,

    menubar=no,scrollbars=yes,resizable=yes,width=600,height=450");
    newwin.focus();
    return false;
    }
    </script>
    <script LANGUAGE="javascript">
    function submit10()
    {
    self.location.replace("fenye1.jsp")
    }
    </script>
    <%//变量声明
    java.sql.Connection sqlCon; //数据库连接对象
    java.sql.Statement sqlStmt; //SQL语句对象
    java.sql.ResultSet sqlRst; //结果集对象
    java.lang.String strCon; //数据库连接字符串
    java.lang.String strSQL; //SQL语句
    int intPageSize; //一页显示的记录数
    int intRowCount; //记录总数
    int intPageCount; //总页数
    int intPage; //待显示页码
    java.lang.String strPage;
    int i;
    //设置一页显示的记录数
    intPageSize = 4;
    //取得待显示页码
    strPage = request.getParameter("page");
    if(strPage==null){//表明在QueryString中没有page这一个参数,此时显示第一页数据
    intPage = 1;
    }
    else{//将字符串转换成整型
    intPage = java.lang.Integer.parseInt(strPage);
    if(intPage<1) intPage = 1;
    }
    //装载JDBC驱动程序
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    //设置数据库连接字符串
    strCon = "jdbc:odbc:heyang";
    //连接数据库
    sqlCon = java.sql.DriverManager.getConnection(strCon,"sa","");
    //创建一个可以滚动的只读的SQL语句对象
    sqlStmt =

    sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE,java.sql.Result

    Set.CONCUR_READ_ONLY);//准备SQL语句
    strSQL = "select user_id,user_name from userinfo order by user_id desc";
    //执行SQL语句并获取结果集
    sqlRst = sqlStmt.executeQuery(strSQL);
    //获取记录总数
    sqlRst.last();//??光标在最后一行
    intRowCount = sqlRst.getRow();//获得当前行号
    //记算总页数
    intPageCount = (intRowCount+intPageSize-1) / intPageSize;
    //调整待显示的页码
    if(intPage>intPageCount) intPage = intPageCount;
    %>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
    <title>会员管理</title>
    </head>
    <body>
    <form method="POST" action="fenye1.jsp">
    第<%=intPage%>页 共<%=intPageCount%>页

    <%if(intPage<intPageCount){%><a

    href="fenye1.jsp?page=<%=intPage+1%>">下一页

    </a><%}%> <%if(intPage>1){%><a href="fenye1.jsp?page=<%=intPage-1%>">

    上一页</a><%}%>
    转到第:<input type="text" name="page" size="8"> 页
    <span><input class=buttonface type=´submit´ value=´GO´ name=´cndok´></span>
    </form>
    <table border="1" cellspacing="0" cellpadding="0">
    <tr>
    <th>ID</th>
    <th>用户名</th>
    <th width=´8%´>删除</th>
    </tr>
    <%
    if(intPageCount>0){
    //将记录指针定位到待显示页的第一条记录上
    sqlRst.absolute((intPage-1) * intPageSize + 1);
    //显示数据
    i = 0;
    String user_id,user_name;
    while(i<intPageSize && !sqlRst.isAfterLast()){
    user_id=sqlRst.getString(1);
    user_name=sqlRst.getString(2);
    %>
    <tr>
    <td><%=user_id%></td>
    <td><%=user_name%></td>
    <td width=´8%´ align=´center´><a href="delete.jsp?user_id=<%=user_id%>"

    onClick="return newwin(this.href);">删除</a></td>
    </tr>
    <%
    sqlRst.next();
    i++;
    }
    }
    %>
    </table>


    </body>
    </html>
    <%
    //关闭结果集
    sqlRst.close();
    //关闭SQL语句对象
    sqlStmt.close();
    //关闭数据库
    sqlCon.close();
    %>

    posted @ 2007-10-02 20:49 jadmin 阅读(60) | 评论 (0)编辑 收藏
    前言
      在使用数据库的过程中,不可避免的需要使用到分页的功能,可是JDBC的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用Vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与JDBC本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与JDBC兼容性非常好的方案。
    JDBC和分页
      Sun的JDBC规范的制定,有时很让人哭笑不得,在JDBC1.0中,对于一个结果集(ResultSet)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次SQL查询的情况下无法获得结果集的大小。所以,如果你使用的是JDBC1.0的驱动,那么是几乎无法实现分页的。
      好在Sun的JDBC2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的ResultSet了。


    和具体数据库相关的实现方法
      有一些数据库,如Mysql, Oracle等有自己的分页方法,比如Mysql可以使用limit子句,Oracle可以使用ROWNUM来限制结果集的大小和起始位置。这里以Mysql为例,其典型代码如下:
    // 计算总的记录条数
    String SQL = "SELECT Count(*) AS total " + this.QueryPart;
    rs = db.executeQuery(SQL);
    if (rs.next())
    Total = rs.getInt(1);
    // 设置当前页数和总页数
    TPages = (int)Math.ceil((double)this.Total/this.MaxLine);
    CPages = (int)Math.floor((double)Offset/this.MaxLine+1);
    // 根据条件判断,取出所需记录
    if (Total > 0) {
    SQL = Query + " LIMIT " + Offset + " , " + MaxLine;
    rs = db.executeQuery(SQL);
    }
    return rs;
    }
      毫无疑问,这段代码在数据库是Mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。


    另一种繁琐的实现方法
      我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作ResultSet滚到相应的位置,再读取相应数量的记录。其典型代码如下:
    <%
    sqlStmt = sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE,
    java.sql.ResultSet.CONCUR_READ_ONLY);
    strSQL = "select name,age from test";
    //执行SQL语句并获取结果集
    sqlRst = sqlStmt.executeQuery(strSQL);
    //获取记录总数
    sqlRst.last();
    intRowCount = sqlRst.getRow();
    //记算总页数
    intPageCount = (intRowCount+intPageSize-1) / intPageSize;
    //调整待显示的页码
    if(intPage>intPageCount) intPage = intPageCount;
    %>
    <table border="1" cellspacing="0" cellpadding="0">
    <tr>
    <th>姓名</th>
    <th>年龄</th>
    </tr>
    <%
    if(intPageCount>0){
    //将记录指针定位到待显示页的第一条记录上
    sqlRst.absolute((intPage-1) * intPageSize + 1);
    //显示数据
    i = 0;
    while(i<intPageSize && !sqlRst.isAfterLast()){
    %>
    <tr>
    <td><%=sqlRst.getString(1)%></td>
    <td><%=sqlRst.getString(2)%></td>
    </tr>
    <%
    sqlRst.next();
    i++;
    }
    }
    %>
    </table>
      很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。


    使用Vector进行分页
      还见过另一些实现分页的类,是先将所有记录都select出来,然后将ResultSet中的数据都get出来,存入Vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入Vector中。
      扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, String类型还比较好处理,如果碰到Blob, Text等类型,实现起来就很麻烦了。这是一种更不可取的方案。


    一个新的Pageable接口及其实现
      很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原JDBC接口的使用方法保持一致;尽可能高的效率。
      首先,我们需要提供一个与java.sql.ResultSet向下兼容的接口,把它命名为Pageable,接口定义如下:
    public interface Pageable extends java.sql.ResultSet{
    /**返回总页数
    */
    int getPageCount();
    /**返回当前页的记录条数
    */
    int getPageRowsCount();
    /**返回分页大小
    */
    int getPageSize();
    /**转到指定页
    */
    void gotoPage(int page) ;
    /**设置分页大小
    */
    void setPageSize(int pageSize);
    /**返回总记录行数
    */
    int getRowsCount();
    /**
    * 转到当前页的第一条记录
    * @exception java.sql.SQLException 异常说明。
    */
    void pageFirst() throws java.sql.SQLException;
    /**
    * 转到当前页的最后一条记录
    * @exception java.sql.SQLException 异常说明。
    */
    void pageLast() throws java.sql.SQLException;
    /**返回当前页号
    */
    int getCurPage();
    }
      这是一个对java.sql.ResultSet进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。
      接着,我们需要实现这个接口,由于这个接口继承自ResultSet,并且它的大部分功能也都和ResultSet原有功能相同,所以这里使用了一个简单的Decorator模式。
      PageableResultSet2的类声明和成员声明如下:
    public class PageableResultSet2 implements Pageable {
    protected java.sql.ResultSet rs=null;
    protected int rowsCount;
    protected int pageSize;
    protected int curPage;
    protected String command = "";
    }
      可以看到,在PageableResultSet2中,包含了一个ResultSet的实例(这个实例只是实现了ResultSet接口,事实上它是由各个数据库厂商分别实现的),并且把所有由ResultSet继承来的方法都直接转发给该实例来处理。
      PageableResultSet2中继承自ResultSet的主要方法:
    //……
    public boolean next() throws SQLException {
    return rs.next();
    }
    //……
    public String getString(String columnName) throws SQLException {
    try {
    return rs.getString(columnName);
    }
    catch (SQLException e) {//这里是为了增加一些出错信息的内容便于调试
    throw new SQLException (e.toString()+" columnName="
    +columnName+" SQL="+this.getCommand());
    }
    }
    //……
      只有在Pageable接口中新增的方法才需要自己的写方法处理。
    /**方法注释可参考Pageable.java
    */
    public int getCurPage() {
    return curPage;
    }
    public int getPageCount() {
    if(rowsCount==0) return 0;
    if(pageSize==0) return 1;
    //calculate PageCount
    double tmpD=(double)rowsCount/pageSize;
    int tmpI=(int)tmpD;
    if(tmpD>tmpI) tmpI++;
    return tmpI;
    }
    public int getPageRowsCount() {
    if(pageSize==0) return rowsCount;
    if(getRowsCount()==0) return 0;
    if(curPage!=getPageCount()) return pageSize;
    return rowsCount-(getPageCount()-1)*pageSize;
    }
    public int getPageSize() {
    return pageSize;
    }
    public int getRowsCount() {
    return rowsCount;
    }
    public void gotoPage(int page) {
    if (rs == null)
    return;
    if (page < 1)
    page = 1;
    if (page > getPageCount())
    page = getPageCount();
    int row = (page - 1) * pageSize + 1;
    try {
    rs.absolute(row);
    curPage = page;
    }
    catch (java.sql.SQLException e) {
    }
    }
    public void pageFirst() throws java.sql.SQLException {
    int row=(curPage-1)*pageSize+1;
    rs.absolute(row);
    }
    public void pageLast() throws java.sql.SQLException {
    int row=(curPage-1)*pageSize+getPageRowsCount();
    rs.absolute(row);
    }
    public void setPageSize(int pageSize) {
    if(pageSize>=0){
    this.pageSize=pageSize;
    curPage=1;
    }
    }
      PageableResultSet2的构造方法:
    public PageableResultSet2(java.sql.ResultSet rs) throws java.sql.SQLException {
    if(rs==null) throw new SQLException("given ResultSet is NULL","user");

    rs.last();
    rowsCount=rs.getRow();
    rs.beforeFirst();

    this.rs=rs;
    }
      这里只是简单的取得一个总记录数,并将记录游标移回初始位置(before first),同时将参数中的ResultSet赋给成员变量。


    Pageable的使用方法
      因为Pageable接口继承自ResultSet,所以在使用方法上与ResultSet一致,尤其是在不需要分页功能的时候,可以直接当成ResultSet使用。而在需要分页时,只需要简单的setPageSize, gotoPage,即可。
    PreparedStatement pstmt=null;
    Pageable rs=null;
    ……//构造SQL,并准备一个pstmt.
    rs=new PageableResultSet2(pstmt.executeQuery());//构造一个Pageable
    rs.setPageSize(20);//每页20个记录
    rs.gotoPage(2);//跳转到第2页
    for(int i=0; i<rs.getPageRowsCount(); i++){//循环处理
    int id=rs.getInt(“ID”);
    ……//继续处理
    }


    总结
      一个好的基础类应该是便于使用,并且具备足够的可移植性,同时要保证其功能的完善。在上面的实现中,我们从java.sql.ResultSet接口继承出Pageable,并实现了它。这就保证了在使用中与JDBC原有操作的一致性,同时对原有功能没有缩减。
      同时它也是易于使用的,因为封装了一切必要的操作,所以在你的代码中唯一显得"难看"和"不舒服"的地方就是需要自己去构造一个PageableResultSet2。不过只要你愿意,这也是可以解决的。
      当然它也有具有充分的可移植性,当你将数据库由Oracle变为Mysql或者SQLServer的时候,你仍然可以使用这些分页的代码。它在使用中(或者说在移植的过程中)唯一的限制就是你必须要使用一个支持JDBC2的驱动(现在明白为什么我把类命名为PageableResultSet2了吧。:P),不过,好在JDBC2已经成为标准了,绝大多数的数据库(如Oracle, Mysql, SQLServer)都有自己的或者第三方提供的JDBC2的驱动。
      OK,这个分页的实现是否对你的编程有帮助呢?仔细看看,其实真正自己写的代码并不多的,大部分都只是简单的转发操作。一个合适的模式应用可以帮你很大忙。
    posted @ 2007-10-02 20:37 jadmin 阅读(48) | 评论 (0)编辑 收藏

                        1、不说“不可能”三个字;

                        2、凡事第一反应:找方法,而不是找借口;

                        3、遇到挫折对自己大声说:太棒了;

                        4、不说消极的话,不落入消极情绪,一旦出现立即正面处理;

                        5、凡事先订立目标,并且尽量制作“梦想版”;

                        6、凡事预先作计划,尽量前目标视觉化;

                        7、六点优先工作制,每一分,每一秒做生产力的事情;

                        8、随时用零碎的时间(如等人、排队等)做零碎的小活;

                        9、守时;

                        10、写下来,不要太依靠脑袋记忆;

                        11、随时记录灵感;

                        12、把重要的观念、方法写下来,并贴起来,以随时提示自己;

                        13、走路比平时快30%。走路时,脚尖稍用力推进;肢体语言健康有力,不懒散;

                        14、每天出门照镜子,给自己一个自信的笑容;

                        15、每天自我反省一次;

                        16、每天坚持一次运动;

                        17、听心跳1分钟。指在做重要事前,疲劳时,心情烦燥时,紧张时;

                        18、开会坐在前排;

                        19、微笑;

                        20、用心倾听,不打断对方说话;

                        21、说话时,声音有力。感觉自己声音似乎能产生有感染力的磁场;

                        22、同理心。说话之前,先考虑一下对方的感觉;

                        23、每天有意识、真诚地赞美别人三次以上;

                        24、及时写感谢卡,哪怕是用便条写;

                        25、不用训斥、指责的口吻跟别人说话;

                        26、控制住不要让自己做出为自己辩护的第一反应;

                        27、每天多做一件“分外事”

                        28、不管任何方面,每天必须至少做一次“进步一点点”;

                        29、每天提前15分钟上班,推迟30分钟下班;

                        30、每天在下班前5分钟的时间做一天的整理性工作;

                        31、定期存钱;

                        32、节俭;

                        33,当你有创意的时候用笔记下来,并努力去实现它。

    posted @ 2007-10-02 08:55 jadmin 阅读(53) | 评论 (0)编辑 收藏
      【放弃】把握的反面就是放弃,选择了一个机会,就等于放弃了其他所有的可能。当新的机会摆在面前的时候,敢于放弃已经获得的一切,这不是功亏一篑,这不是半途而废,这是为了谋求更大的发展空间;或者什么都不为,只因为喜欢这样做,因为,年轻就是最大的机会。人,只有在三十岁之前才会有这个胆量,有这个资本,有这个资格。



      【失恋】不是不在乎,是在乎不起。三十岁前最怕失去的不是已经拥有的东西,而是梦想。爱情如果只是一个过程,那么正是这个年龄应当经历的,如果要承但结果,三十岁以后,可能会更有能力,更有资格。其实,三十岁之前我们要做的事情很多,稍纵即逝,过久地沉溺在已经干涸的爱河的河床中,与这个年龄的生命节奏不合。



      【离婚】不是不在乎,是一切还来得及。一位三十八岁的女友与老公结婚十五年,冷战十三年,终于分手。她说:“如果说后来不愿意离婚是为了孩子,那第他第一次提出离婚我没有同意,现在想来真不知道为什么。如果那个时候早分手,我的生活绝不会是今天这个样子。现在再重新开始,总觉得一切都晚了。”



      【漂泊】漂泊不是一种不幸,而是一种资格。趁着没有家室拖累,趁着身体健康,此时不飘何时飘?当然,漂泊的不一定是身体,也许只是幻想和梦境。新世纪的时尚领袖是飘一代,渴望漂泊的人惟一不飘的是那颗心。



      【失业】三十岁以前就尝到失业的滋味当然是一件不幸的事,但不一定是坏事。三十岁之前就过早地固定在一个职业上终此一生也许才是最大的不幸。失业也许让你想起埋藏很久而尘封的梦想,也许会唤醒连你自己都从未知道的潜能。也许你本来就没什么梦想,这时候也会逼着你去做梦。



      【时尚】不要追赶时尚。按说青年人应该是最时尚的,但是独立思考和个性生活更重要。在这个物质社会,其实对时尚的追求早已经成为对金钱的追求。今天,时尚是物欲和世俗的同义语。



      【格调】这是小资的东西,"小资"这个词在今天又二度流行,追求格调就是他们的专利。小资们说,有格调要满足四大要件:智慧、素养、自信和金钱。格调就是把"高尚"理解成穿着、气质、爱好的品位和室内装潢。也就是大老粗只会表现谈吐的庸俗,"小资"们已经有能力庸俗他们的心灵了。主流观念倒不是非要另类,另类已经成为年轻人观念的主流了,在今天,老土倒显得另类。关键是当今社会是一个创造观念的时代,而不是一个固守陈旧观念的时代。



         【评价】我们最不应该做出的牺牲就是因为别人的评价而改变自我,因为那些对你指手画脚的人自己也不知道他们遵从的规则是什么。千万不要只遵从规矩做事,规矩还在创造之中,要根据自己的判断做每一件事,虽然这样会麻烦一点。



      【幼稚】不要怕人说我们幼稚,这正说明你还年轻,还充满活力。"成熟"是个吓人的词儿,也是个害人的词儿。成熟和幼稚是对一个人最大而无当、最不负责任、最没用的概括。那些庸人,绝不会有人说他们幼稚。不信,到哪天你被生活压得老气横秋,暮气沉沉的时候,人们一定会说你成熟了,你就会知道"成熟"是个什么东西。



      【不适应】在一首摇滚里有这么一句:"这个城市改变了我,这个城市不需要我。"不要盲目地适应你生存的环境,因为很可能这环境自身已经不适应这个社会的发展了。



      【失败】我的老师曾经跟我说,一个人起码要在感情上失恋一次,在事业上失败一次,在选择上失误一次,才能长大。不要说失败是成功之母那样的老话,失败来得越早越好,要是三十岁,四十岁之后再经历失败,有些事,很可能就来不及了。



      【错误】这是年轻人的专利。



      【浅薄】如果每看一次《泰坦尼克号》就流一次眼泪,每看一次《大话西游》就笑得直不起腰,就会有人笑你浅薄。其实那只能说明你的神经依旧非常敏锐,对哪怕非常微弱的刺激都会迅速做出适应的反应;等你的感觉迟钝了,人们就会说你深沉了。



      【明星】不是不必在乎,是不能在乎。明星在商品社会是一种消费品,花了钱,听了歌,看了电影,明星们的表现再好,不过是物超所值而己,也不值得崇拜呀?就像你在地摊上花五十块钱买的裙子,别人都猜是八百块钱买的,物超所值了吧?你就崇拜上这身裙子了?



      【代价】不是不计代价,而是要明白做任何事都要付出代价。对我们这个年龄的人来说,这绝不是一句废话。否则,要到三十岁的时候才会明白自己曾经付出了多少代价,却不明白为什么付出,更不明白自己得到了多少,得到什么。



      【孤独】这是为自由付出的代价。



      【失意】包括感情上的,事业上的,也许仅仅是今天花了冤枉钱没买到可心的东西,朋友家高朋满座自己却插不上一句话。过分在乎失意的感受不是拿命运的捉弄来捉弄自己,就是拿别人的错误来惩罚自己。



      【缺陷】也许你个子矮,也许你长得不好看,也许你的嗓音像唐老鸭……那么你的优势就是你不会被自己表面的浅薄的亮点所耽搁,少花一些时间,少走一些弯,直接发现你内在的优势,直接挖掘自己深层的潜能。


         【误会】如果出于恶意,那么解释也没有用;如果出于善意,就不需要解释。专门说到"误会"倒不是为一个人在三十岁之前被人误会的时候更多,而是这个年龄的人想不开的时候更多。



      【谣言】这是一种传染病,沉默是最好的疫苗。除非你能找出传染源,否则解释恰恰会成为病毒传播最理想的条件。



      【疯狂】这是年轻人最好的心理调适,只能说明你精力旺盛,身心健康。说你"疯狂"是某些生活压抑、心力交瘁的中老年人恶意的评价,他们就像一部年久修的机器,最需要调试,但只能微调,一次大修就会让他们完全报废。



      【稳定】三十岁之前就在乎稳定的生活,那只有两种可能,要么就是中了彩票,要么就是未老先衰。



      【压力】中年人能够承受多大压力检验的是他的韧性;年轻人能承受多大压力,焕发的是他的潜能。



      【出国】也许是个机会,也许是个陷阱。除非从考大学的那一刻你就抱着这个目标,否则,对待出国的态度应该像对待爱情一样,努力争取成败随缘。



      【薪水】只要是给人打工,薪水再高也高不到哪儿去。所以在三十岁之前,机会远比金钱重要,事业远比金钱重要,将来远比金钱重要。对大多数人来说,三十岁之前干事业的首要目标绝不是挣钱,而是挣未来。



      【存款】这倒不一定是因为我们钱少,年轻人现在谁都知道钱是有生命的。机会这么多,条件这么好,可以拿钱去按揭,做今天的事,花明天的钱;也可以拿钱去投资,拿钱去"充电"。钱只有在它流通的过程中才是钱,否则只是一沓世界上质量最好的废纸。



      【房子】除非你买房子是为了升值,要么就是你结婚了。我有个同学,家在外地,大学毕业之后,单位没有宿舍,家里就给他买了一套房子。他曾经有过去北京工作的机会,但是他觉得刚买了房子就离开这座城市说不过去,就放弃了。到现在他工作稳定,但一事无成。唯一的成就就是结婚了,并且有了孩子,因为他觉得该让这房子永远空着,所以房子变成了家。房子是都市生活的寓言,这个寓言不应该过早的和我们相关。



      【年龄】女孩子一过二十五就开始隐瞒自己的年龄,其实大可不必。现在青年期都延迟到四十五岁了,二十五又算得了什么呢?



      【在乎】这是一种拿不起、放不下的心态,它的反面不是放弃,而是天马行空,自由自在,永远保持革命乐观主义的精神。
    posted @ 2007-10-02 08:51 jadmin 阅读(54) | 评论 (0)编辑 收藏
      在基于 Java 语言的编程中,我们经常碰到汉字的处理及显示的问题。一大堆看不懂的乱码肯定不是我们愿意看到的显示效果,怎样才能够让那些汉字正确显示呢?Java语言默认的编码方式是UNICODE,而我们中国人通常使用的文件和数据库都是基于GB2312或者BIG5等方式编码的,怎样才能够恰当地选择汉字编码方式并正确地处理汉字的编码呢?本文将从汉字编码的常识入手,结合Java编程实例,分析以上两个问题并提出解决它们的方案。

      现在 Java 编程语言已经广泛应用于互联网世界,早在 Sun 公司开发 Java 语言的时候,就已经考虑到对非英文字符的支持了。Sun 公司公布的 Java 运行环境(JRE)本身就分英文版和国际版,但只有国际版才支持非英文字符。不过在 Java 编程语言的应用中,对中文字符的支持并非如同 Java Soft 的标准规范中所宣称的那样完美,因为中文字符集不只一个,而且不同的操作系统对中文字符的支持也不尽相同,所以会有许多和汉字编码处理有关的问题在我们进行应用开发中困扰着我们。有很多关于这些问题的解答,但都比较琐碎,并不能够满足大家迫切解决问题的愿望,关于 Java 中文问题的系统研究并不多,本文从汉字编码常识出发,分析 Java 中文问题,希望对大家解决这个问题有所帮助。

      汉字编码的常识

      我们知道,英文字符一般是以一个字节来表示的,最常用的编码方法是 ASCII 。但一个字节最多只能区分256个字符,而汉字成千上万,所以现在都以双字节来表示汉字,为了能够与英文字符分开,每个字节的最高位一定为1,这样双字节最多可以表示64K格字符。我们经常碰到的编码方式有 GB2312、BIG5、UNICODE 等。关于具体编码方式的详细资料,有兴趣的读者可以查阅相关资料。我肤浅谈一下和我们关系密切的 GB2312 和 UNICODE。GB2312 码,中华人民共和国国家标准汉字信息交换用编码,是一个由中华人民共和国国家标准总局发布的关于简化汉字的编码,通行于中国大陆地区及新加坡,简称国标码。两个字节中,第一个字节(高字节)的值为区号值加32(20H),第二个字节(低字节)的值为位号值加32(20H),用这两个值来表示一个汉字的编码。UNICODE 码是微软提出的解决多国字符问题的多字节等长编码,它对英文字符采取前面加“0”字节的策略实现等长兼容。如 “A” 的 ASCII 码为0x41,UNICODE 就为0x00,0x41。利用特殊的工具各种编码之间可以互相转换。

      Java 中文问题的初步认识

      我们基于 Java 编程语言进行应用开发时,不可避免地要处理中文。Java 编程语言默认的编码方式是 UNICODE,而我们通常使用的数据库及文件都是基于 GB2312 编码的,我们经常碰到这样的情况:浏览基于 JSP 技术的网站看到的是乱码,文件打开后看到的也是乱码,被 Java 修改过的数据库的内容在别的场合应用时无法继续正确地提供信息。

    String sEnglish = “apple”;

    String sChinese = “苹果”;

    String s = “苹果 apple ”;

      sEnglish 的长度是5,sChinese的长度是4,而 s 默认的长度是14。对于 sEnglish来说, Java 中的各个类都支持得非常好,肯定能够正确显示。但对于 sChinese 和 s 来说,虽然 Java Soft 声明 Java 的基本类已经考虑到对多国字符的支持(默认 UNICODE 编码),但是如果操作系统的默认编码不是 UNICODE ,而是国标码等。从 Java 源代码到得到正确的结果,要经过 “Java 源代码-> Java 字节码-> ; 虚拟机->操作系统->显示设备”的过程。在上述过程中的每一步骤,我们都必须正确地处理汉字的编码,才能够使最终的显示结果正确。

      “ Java 源代码-> Java 字节码”,标准的 Java 编译器 javac 使用的字符集是系统默认的字符集,比如在中文 Windows 操作系统上就是 GBK ,而在 Linux 操作系统上就是ISO-8859-1,所以大家会发现在 Linux 操作系统上编译的类中源文件中的中文字符都出了问题,解决的办法就是在编译的时候添加 encoding 参数,这样才能够与平台无关。用法是

      javac ?Cencoding GBK。

      “ Java 字节码->虚拟机->操作系统”, Java 运行环境 (JRE) 分英文版和国际版,但只有国际版才支持非英文字符。 Java 开发工具包 (JDK) 肯定支持多国字符,但并非所有的计算机用户都安装了 JDK 。很多操作系统及应用软件为了能够更好的支持 Java ,都内嵌了 JRE 的国际版本,为自己支持多国字符提供了方便。

      “操作系统->显示设备”,对于汉字来说,操作系统必须支持并能够显示它。英文操作系统如果不搭配特殊的应用软件的话,是肯定不能够显示中文的。

      还有一个问题,就是在 Java 编程过程中,对中文字符进行正确的编码转换。例如,向网页输出中文字符串的时候,不论你是用

    out.println(string);

      还是用<%=string%>,都必须作 UNICODE 到 GBK 的转换,或者手动,或者自动。在 JSP 1.0中,可以定义输出字符集,从而实现内码的自动转换。用法是

    <%@page contentType=”text/html; charset=gb2312” %>

      但是在一些 JSP 版本中并没有提供对输出字符集的支持,(例如 JSP 0.92),这就需要手动编码输出了,方法非常多。最常用的方法是

    String s1 = request.getParameter(“keyword”);

    String s2 = new String(s1.getBytes(“ISO-8859-1”),”GBK”);

      getBytes 方法用于将中文字符以“ISO-8859-1”编码方式转化成字节数组,而“GBK” 是目标编码方式。我们从以ISO-8859-1方式编码的数据库中读出中文字符串 s1 ,经过上述转换过程,在支持 GBK 字符集的操作系统和应用软件中就能够正确显示中文字符串 s2 。

      Java 中文问题的表层分析及处理

    背景
    开发环境 JDK1.15 Vcafe2.0 JPadPro
    服务器端 NT IIS Sybase System Jconnect(JDBC)
    客户端 IE5.0 Pwin98

      .CLASS 文件存放在服务器端,由客户端的浏览器运行 APPLET , APPLET 只起调入 FRAME 类等主程序的作用。界面包括 Textfield ,TextArea,List,Choice 等。

      I.用 JDBC 执行 SELECT 语句从服务器端读取数据(中文)后,将数据用 APPEND 方法加到 TextArea(TA) ,不能正确显示。但加到 List 中时,大部分汉字却可正确显示。

      将数据按“ISO-8859-1” 编码方式转化为字节数组,再按系统缺省编码方式 (Default Character Encoding) 转化为 STRING ,即可在 TA 和 List 中正确显示。

      程序段如下:

    dbstr2 = results.getString(1);
    //After reading the result from DB server,converting it to string.
    dbbyte1 = dbstr2.getBytes(“iso-8859-1”);
    dbstr1 = new String(dbbyte1);

      在转换字符串时不采用系统默认编码方式,而直接采用“ GBK” 或者 “GB2312” ,在 A 和 B 两种情况下,从数据库取数据都没有问题。

      II.处理方式与“取中文”相逆,先将 SQL 语句按系统缺省编码方式转化为字节数组,再按“ISO-8859-1”编码方式转化为 STRING ,最后送去执行,则中文信息可正确写入数据库。

      程序段如下:

    sqlstmt = tf_input.getText();
    //Before sending statement to DB server,converting it to sql statement.
    dbbyte1 = sqlstmt.getBytes();
    sqlstmt = newString(dbbyte1,”iso-8859-1”);
    _stmt = _con.createStatement();
    _stmt.executeUpdate(sqlstmt);
    ……

      问题:如果客户机上存在 CLASSPATH 指向 JDK 的 CLASSES.ZIP 时(称为 A 情况),上述程序代码可正确执行。但是如果客户机只有浏览器,而没有 JDK 和 CLASSPATH 时(称为 B 情况),则汉字无法正确转换。

      我们的分析:

      1.经过测试,在 A 情况下,程序运行时系统的缺省编码方式为 GBK 或者 GB2312 。在 B 情况下,程序启动时浏览器的 JAVA 控制台中出现如下错误信息:

      Can't find resource for sun.awt.windows.awtLocalization_zh_CN

      然后系统的缺省编码方式为“8859-1”。

      2.如果在转换字符串时不采用系统缺省编码方式,而是直接采用 “GBK” 或“GB2312”,则在 A 情况下程序仍然可正常运行,在 B 情况下,系统出现错误:

    UnsupportedEncodingException。

      3.在客户机上,把 JDK 的 CLASSES.ZIP 解压后,放在另一个目录中, CLASSPATH 只包含该目录。然后一边逐步删除该目录中的 .CLASS 文件,另一边运行测试程序,最后发现在一千多个 CLASS 文件中,只有一个是必不可少的,该文件是:

    sun.io.CharToByteDoubleByte.class。

      将该文件拷到服务器端和其它的类放在一起,并在程序的开头 IMPORT 它,在 B 情况下程序仍然无法正常运行。

      4.在 A 情况下,如果在 CLASSPTH 中去掉 sun.io.CharToByteDoubleByte.class ,则程序运行时测得默认编码方式为“8859-1”,否则为 “GBK” 或 “GB2312” 。

      如果 JDK 的版本为1.2以上的话,在 B 情况下遇到的问题得到了很好的解决,测试的步骤同上,有兴趣的读者可以尝试一下。

      Java 中文问题的根源分析及解决

      在简体中文 MS Windows 98 + JDK 1.3 下,可以用 System.getProperties() 得到 Java 运行环境的一些基本属性,类 PoorChinese 可以帮助我们得到这些属性。

      类 PoorChinese 的源代码:

      public class PoorChinese {}

      执行 java PoorChinese 后,我们会得到:

      系统变量 file.encoding 的值为 GBK ,user.language 的值为 zh , user.region 的值为 CN ,这些系统变量的值决定了系统默认的编码方式是 GBK 。

      在上述系统中,下面的代码将 GB2312 文件转换成 Big5 文件,它们能够帮助我们理解 Java 中汉字编码的转化:

    import java.io.*;
    import java.util.*;
    public class gb2big5
    {
     static int iCharNum=0;
     public static void main(String[] args) {
      System.out.println("Input GB2312 file, output Big5 file.");
      if (args.length!=2)
      {
       System.err.println("Usage: jview gb2big5 gbfile big5file");
       System.exit(1);
       String inputString = readInput(args[0]);
       writeOutput(inputString,args[1]);
       System.out.println("Number of Characters in file: "+iCharNum+".");
      }
      static void writeOutput(String str, String strOutFile)
      {
       try
       {
        FileOutputStream fos = new FileOutputStream(strOutFile);
        Writer out = new OutputStreamWriter(fos, "Big5");
        out.write(str);
        out.close();
       }
       catch (IOException e)
       {
        e.printStackTrace();
        e.printStackTrace();
       }
      }
      static String readInput(String strInFile)
      {
       StringBuffer buffer = new StringBuffer();
       try
       {
        FileInputStream fis = new FileInputStream(strInFile);
        InputStreamReader isr = new InputStreamReader(fis, "GB2312");
        Reader in = new BufferedReader(isr);
        int ch;
        while ((ch = in.read()) > -1)
        {
         iCharNum += 1; buffer.append((char)ch);
        }
        in.close();
        return buffer.toString();
       }
       catch (IOException e)
       {
        e.printStackTrace();
        return null;
       }
      }
     }

      编码转化的过程如下:

      GB2312------------------>Unicode------------->Big5

      执行 java gb2big5 gb.txt big5.txt ,如果 gb.txt 的内容是“今天星期三”,则得到的文件 big5.txt 中的字符能够正确显示;而如果 gb.txt 的内容是“情人节快乐”,则得到的文件 big5.txt 中对应于“节”和“乐”的字符都是符号“?”(0x3F),可见 sun.io.ByteToCharGB2312 和 sun.io.CharToByteBig5 这两个基本类并没有编好。

      正如上例一样, Java 的基本类也可能存在问题。由于国际化的工作并不是在国内完成的,所以在这些基本类发布之前,没有经过严格的测试,所以对中文字符的支持并不像 Java Soft 所声称的那样完美。前不久,我的一位技术上的朋友发信给我说,他终于找到了 Java Servlet 中文问题的根源。两周以来,他一直为 Java Servlet 的中文问题所困扰,因为每面对一个含有中文字符的字符串都必须进行强制转换才能够得到正确的结果(这好象是大家公认的唯一的解决办法)。

      后来,他确实不想如此继续安分下去了,因为这样的事情确实不应该是高级程序员所要做的工作,他就找出 Servlet 解码的源代码进行分析,因为他怀疑问题就出在解码这部分。经过四个小时的奋斗,他终于找到了问题的根源所在。原来他的怀疑是正确的, Servlet 的解码部分完全没有考虑双字节,直接把 %XX 当作一个字符。(原来 Java Soft 也会犯这幺低级的错误!)

      如果你对这个问题有兴趣或者遇到了同样的烦恼的话,你可以按照他的步骤 对Servlet.jar 进行修改:

      找到源代码 HttpUtils 中的 static private String parseName ,在返回前将 sb(StringBuffer) 复制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解码了:

    HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者

    form=HttpUtils.parsePostData(……)

      千万别忘了编译后放到 Servlet.jar 里面。

      关于 Java 中文问题的总结

      Java 编程语言成长于网络世界,这就要求 Java 对多国字符有很好的支持。 Java 编程语言适应了计算的网络化的需求,为它能够在网络世界迅速成长奠定了坚实的基础。 Java 的缔造者 (Java Soft) 已经考虑到 Java 编程语言对多国字符的支持,只是现在的解决方案有很多缺陷在里面,需要我们付诸一些补偿性的措施。而世界标准化组织也在努力把人类所有的文字统一在一种编码之中,其中一种方案是 ISO10646 ,它用四个字节来表示一个字符。当然,在这种方案未被采用之前,还是希望 Java Soft 能够严格地测试它的产品,为用户带来更多的方便。

      附一个用于从数据库和网络中取出 中文乱码的处理函数,入参是有问题的字符串,出参是问题已经解决了的字符串。
    posted @ 2007-10-02 08:17 jadmin 阅读(58) | 评论 (0)编辑 收藏
    ---- 在Oracle数据库系统中,用户如果要以特权用户身份(INTERNAL/SYSDBA/SYSOPER)登录Oracle数据库可以有两种身份验证的方法:即使用与操作系统集成的身份验证或使用Oracle数据库的密码文件进行身份验证。因此,管理好密码文件,对于控制授权用户从远端或本机登录Oracle数据库系统,执行数据库管理工作,具有重要的意义。

    ---- Oracle数据库的密码文件存放有超级用户INTERNAL/SYS的口令及其他特权用户的用户名/口令,它一般存放在ORACLE_HOME\DATABASE目录下。

    一、 密码文件的创建:
    ---- 在使用Oracle Instance Manager创建一数据库实例的时侯,在ORACLE_HOME\DATABASE目录下还自动创建了一个与之对应的密码文件,文件名为PWDSID.ORA,其中SID代表相应的Oracle数据库系统标识符。此密码文件是进行初始数据库管理工作的基础。在此之后,管理员也可以根据需要,使用工具ORAPWD.EXE手工创建密码文件,命令格式如下:
    C:\ >ORAPWD FILE=< FILENAME > PASSWORD
    =< PASSWORD > ENTRIES=< MAX_USERS >

    ---- 各命令参数的含义为:
    ---- FILENAME:密码文件名;

    ---- PASSWORD:设置INTERNAL/SYS帐号的口令;

    ---- MAX_USERS:密码文件中可以存放的最大用户数,对应于允许以SYSDBA/SYSOPER权限登录数据库的最大用户数。由于在以后的维护中,若用户数超出了此限制,则需要重建密码文件,所以此参数可以根据需要设置得大一些。

    ---- 有了密码文件之后,需要设置初始化参数REMOTE_LOGIN_PASSWORDFILE来控制密码文件的使用状态。

    二、 设置初始化参数REMOTE_LOGIN_PASSWORDFILE:
    ---- 在Oracle数据库实例的初始化参数文件中,此参数控制着密码文件的使用及其状态。它可以有以下几个选项:
    NONE:指示Oracle系统不使用密码文件,特权用户的登录通过操作系统进行身份验证;

    EXCLUSIVE:指示只有一个数据库实例可以使用此密码文件。只有在此设置下的密码文件可以包含有除INTERNAL/SYS以外的用户信息,即允许将系统权限SYSOPER/SYSDBA授予除INTERNAL/SYS以外的其他用户。

    SHARED:指示可有多个数据库实例可以使用此密码文件。在此设置下只有INTERNAL/SYS帐号能被密码文件识别,即使文件中存有其他用户的信息,也不允许他们以SYSOPER/SYSDBA的权限登录。此设置为缺省值。
    ---- 在REMOTE_LOGIN_PASSWORDFILE参数设置为EXCLUSIVE、SHARED情况下,Oracle系统搜索密码文件的次序为:在系统注册库中查找ORA_SID_PWFILE参数值(它为密码文件的全路径名);若未找到,则查找ORA_PWFILE参数值;若仍未找到,则使用缺省值ORACLE_HOME\DATABASE\PWDSID.ORA;其中的SID代表相应的Oracle数据库系统标识符。
    三、 向密码文件中增加、删除用户:
    ---- 当初始化参数REMOTE_LOGIN_PASSWORDFILE设置为EXCLUSIVE时,系统允许除INTERNAL/SYS以外的其他用户以管理员身份从远端或本机登录到Oracle数据库系统,执行数据库管理工作;这些用户名必须存在于密码文件中,系统才能识别他们。由于不管是在创建数据库实例时自动创建的密码文件,还是使用工具ORAPWD.EXE手工创建的密码文件,都只包含INTERNAL/SYS用户的信息;为此,在实际操作中,可能需要向密码文件添加或删除其他用户帐号。
    ---- 由于仅被授予SYSOPER/SYSDBA系统权限的用户才存在于密码文件中,所以当向某一用户授予或收回SYSOPER/SYSDBA系统权限时,他们的帐号也将相应地被加入到密码文件或从密码文件中删除。由此,向密码文件中增加或删除某一用户,实际上也就是对某一用户授予或收回SYSOPER/SYSDBA系统权限。

    ---- 要进行此项授权操作,需使用SYSDBA权限(或INTERNAL帐号)连入数据库,且初始化参数REMOTE_LOGIN_PASSWORDFILE的设置必须为EXCLUSIVE。具体操作步骤如下:

    创建相应的密码文件;

    设置初始化参数REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE;

    使用SYSDBA权限登录:
    CONNECT SYS/internal_user_passsword AS SYSDBA;

    启动数据库实例并打开数据库;

    创建相应用户帐号,对其授权(包括SYSOPER和SYSDBA):
    授予权限:GRANT SYSDBA TO user_name;
    收回权限:REVOKE SYSDBA FROM user_name;

    现在这些用户可以以管理员身份登录数据库系统了;
    四、 使用密码文件登录:
    ---- 有了密码文件后,用户就可以使用密码文件以SYSOPER/SYSDBA权限登录Oracle数据库实例了,注意初始化参数REMOTE_LOGIN_PASSWORDFILE应设置为EXCLUSIVE或SHARED。任何用户以SYSOPER/SYSDBA的权限登录后,将位于SYS用户的Schema之下,以下为两个登录的例子:
    ---- 1. 以管理员身份登录:

    ---- 假设用户scott已被授予SYSDBA权限,则他可以使用以下命令登录:

    ---- CONNECT scott/tiger AS SYSDBA

    ---- 2. 以INTERNAL身份登录:

    ---- CONNECT INTERNAL/INTERNAL_PASSWORD

    五、 密码文件的维护:
    ---- 1. 查看密码文件中的成员:
    ---- 可以通过查询视图V$PWFILE_USERS来获取拥有SYSOPER/SYSDBA系统权限的用户的信息,表中SYSOPER/SYSDBA列的取值TRUE/FALSE表示此用户是否拥有相应的权限。这些用户也就是相应地存在于密码文件中的成员。

    ---- 2. 扩展密码文件的用户数量:

    ---- 当向密码文件添加的帐号数目超过创建密码文件时所定的限制(即ORAPWD.EXE工具的MAX_USERS参数)时,为扩展密码文件的用户数限制,需重建密码文件,具体步骤如下:

    ---- a) 查询视图V$PWFILE_USERS,记录下拥有SYSOPER/SYSDBA系统权限的用户信息;

    ---- b) 关闭数据库;

    ---- c) 删除密码文件;

    ---- d) 用ORAPWD.EXE新建一密码文件;

    ---- e) 将步骤a中获取的用户添加到密码文件中。

    ---- 3. 修改密码文件的状态:

    ---- 密码文件的状态信息存放于此文件中,当它被创建时,它的缺省状态为SHARED。可以通过改变初始化参数REMOTE_LOGIN_PASSWORDFILE的设置改变密码文件的状态。当启动数据库事例时,Oracle系统从初始化参数文件中读取REMOTE_LOGIN_PASSWORDFILE参数的设置;当加载数据库时,系统将此参数与口令文件的状态进行比较,如果不同,则更新密码文件的状态。若计划允许从多台客户机上启动数据库实例,由于各客户机上必须有初始化参数文件,所以应确保各客户机上的初始化参数文件的一致性,以避免意外地改变了密码文件的状态,造成数据库登陆的失败。

    ---- 4. 修改密码文件的存储位置:

    ---- 密码文件的存放位置可以根据需要进行移动,但作此修改后,应相应修改系统注册库有关指向密码文件存放位置的参数或环境变量的设置。

    ---- 5. 删除密码文件:

    ---- 在删除密码文件前,应确保当前运行的各数据库实例的初始化参数REMOTE_LOGIN_PASSWORDFILE皆设置为NONE。在删除密码文件后,若想要以管理员身份连入数据库的话,则必须使用操作系统验证的方法进行登录。
    posted @ 2007-09-29 12:10 jadmin 阅读(91) | 评论 (0)编辑 收藏
    1、在计算机 开始--->管理-->服务中没有看到 OracleOraHome92TNSListener 服务,但服务已经启动
    2、C:\>lsnrctl start 执行完后报open service error。然后在计算机 开始--->管理-->服务中看到  了 OracleOraHome92TNSListener 服务
    3、运行regedit.exe启动注册表编辑器,在HKEY_LOCAL_MACHINE/SYSTEM/ControlSet002/下的Services和CurrentControlSet/Services下找到OracleOraHome92TNSListener项,在右边窗口按右键,新建/字符串,取名ImagePath。
    双击新见的建,在“数值数据”项输入D:\oracle\ora92\bin\TNSLSNR.EXE(根据你自己的实际情况进行修改),确定完成。
    再次在服务中双击打开OracleOraHome92TNSListener的服务看到其“可执行文件的路径”一栏已经显示了其正确的值。这时你可以启动监听了。
    4、C:>tnsping 数据库SID。看看tns服务没有起来。如果没有起来,用下面的命令
    c:\>lsnrctl
    lsnrctl>start把这个服务起动起来。

    c:\>lsnrctl start 后open service error错误消失,但例程中有一个状态为UNKNOWN,另一个为READY.命令执行成功。

    PL/SQL Developer连接Oracle报错:ORA-12514: TNS:监听程序当前无法识别连接描述符中请求的服务

    解决办法:

      首先重启下对应的数据库监听服务(OracleOraDb<?xml:namespace prefix = st1 />10g_home1TNSListener)和数据库服务(OracleServiceORCL),如果还没解决,就按下面的进行操作:

       1.打开%Oracle安装目录%/network/admin/listener.ora文件(C:\oracle\product\10.2.0\db_1\NETWORK\ADMIN\listener.ora
       2.
    添加:

    (SID_DESC =

        (GLOBAL_DBNAME = ORCL)

        (ORACLE_HOME = C:\oracle\product\10.2.0\db_1)  

        (SID_NAME = ORCL)

        )
       3.
    最后文件改成

    SID_LIST_LISTENER =

    (SID_LIST =

        (SID_DESC =

          (SID_NAME = PLSExtProc)

          (ORACLE_HOME = C:\oracle\product\10.2.0\db_1)

          (PROGRAM = extproc)

        )

       (SID_DESC =


        (GLOBAL_DBNAME = ORCL)

        (ORACLE_HOME = C:\oracle\product\10.2.0\db_1)  

        (SID_NAME = ORCL)

        )

    )

    LISTENER =

    (DESCRIPTION_LIST =

        (DESCRIPTION =

          (ADDRESS = (PROTOCOL = TCP)(HOST = zyk)(PORT = 1521))

        )

    )

    以上粗体部件为增加的内容,修改后重启监听服务后即可。

    posted @ 2007-09-29 02:57 jadmin 阅读(107) | 评论 (0)编辑 收藏

    主要思路:

    对于任意2个日期比如:date_start=2006-10-1、date_end=2006-10-14 ,首先计算这连个日期之间的时间间隔(天数),然后分别对date_start 和date_end 取得它们下一个星期一的日期,这样就可以得到一个新的可以整除7的完整日期间隔(这个新的日期间隔已经把星期几的问题剔出掉了),换一种说法就是我们可以得到,这两个新的日期之间的周数,拿这个周数乘以5就是工作日期了(tmpWorkingDays)。但是这个日期并不是我们所要的日期,接下来我们要做的就是计算date_start,date_end这两个日期对于根据它们所产生的新的日期之间的时间偏移量,date_start的偏移量(date_start_change)是需要加的,而date_end的这个偏移量(date_end_change)是需要减去的。 最后我们只要用tmpWorkingDays+date_start_change-date_end_change就是我们所要求的实际工作日了。以下是所有实现代码(两个日期跨年也没有问题)。

    希望我的方法能对大家有用,应为这个算法没有考虑到节假日的问题,我以后会像办法处理,呵呵,今天就到这里了。

    package com.date.utlit;

    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.GregorianCalendar;
    /**
    * 计算任意2个日期内的工作日(没有考虑到国定假日)
    * @author user
    *
    */
    public class DateCal {

    /**
        * @param args
        */
    public static void main(String[] args) {
        try {

         String strDateStart = "2006-10-1";
         String strDateEnd = "2006-10-14";
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
         Date date_start = sdf.parse(strDateStart);
         Date date_end = sdf.parse(strDateEnd);
         DateCal app = new DateCal();
         Calendar cal_start = Calendar.getInstance();
         Calendar cal_end = Calendar.getInstance();
         cal_start.setTime(date_start);
         cal_end.setTime(date_end);
         System.out.println("星期-->" + app.getChineseWeek(cal_start)
           + " 日期-->" + cal_start.get(Calendar.YEAR) + "-"
           + (cal_start.get(Calendar.MONTH) + 1) + "-"
           + cal_start.get(Calendar.DAY_OF_MONTH));
         System.out.println("星期-->" + app.getChineseWeek(cal_end) + " 日期-->"
           + cal_end.get(Calendar.YEAR) + "-"
           + (cal_end.get(Calendar.MONTH) + 1) + "-"
           + cal_end.get(Calendar.DAY_OF_MONTH));
         System.out.println("工作日为-->"
           + app.getWorkingDay(cal_start, cal_end));
         System.out.println("休息日-->"+app.getHolidays(cal_start, cal_end));
        } catch (Exception e) {
         // TODO: handle exception
        }
    }

    public int getDaysBetween(java.util.Calendar d1, java.util.Calendar d2) {
        if (d1.after(d2)) { // swap dates so that d1 is start and d2 is end
         java.util.Calendar swap = d1;
         d1 = d2;
         d2 = swap;
        }
        int days = d2.get(java.util.Calendar.DAY_OF_YEAR)
          - d1.get(java.util.Calendar.DAY_OF_YEAR);
        int y2 = d2.get(java.util.Calendar.YEAR);
        if (d1.get(java.util.Calendar.YEAR) != y2) {
         d1 = (java.util.Calendar) d1.clone();
         do {
          days += d1.getActualMaximum(java.util.Calendar.DAY_OF_YEAR);
          d1.add(java.util.Calendar.YEAR, 1);
         } while (d1.get(java.util.Calendar.YEAR) != y2);
        }
        return days;
    }

    /**
        * 计算2个日期之间的相隔天数
        * @param d1
        * @param d2
        * @return
        */
    public int getWorkingDay(java.util.Calendar d1, java.util.Calendar d2) {
        int result = -1;
        if (d1.after(d2)) { // swap dates so that d1 is start and d2 is end
         java.util.Calendar swap = d1;
         d1 = d2;
         d2 = swap;
        }

        int betweendays = getDaysBetween(d1, d2);

      
        int charge_date = 0;
        int charge_start_date = 0;//开始日期的日期偏移量
        int charge_end_date = 0;//结束日期的日期偏移量
         // 日期不在同一个日期内
         int stmp;
         int etmp;
         stmp = 7 - d1.get(Calendar.DAY_OF_WEEK);
         etmp = 7 - d2.get(Calendar.DAY_OF_WEEK);
         if (stmp != 0 && stmp != 6) {// 开始日期为星期六和星期日时偏移量为0
          charge_start_date = stmp - 1;
         }
         if (etmp != 0 && etmp != 6) {// 结束日期为星期六和星期日时偏移量为0
          charge_end_date = etmp - 1;
         }
    //    }
        result = (getDaysBetween(this.getNextMonday(d1), this.getNextMonday(d2)) / 7)
          * 5 + charge_start_date - charge_end_date;
        //System.out.println("charge_start_date>" + charge_start_date);
        //System.out.println("charge_end_date>" + charge_end_date);
        //System.out.println("between day is-->" + betweendays);
        return result;
    }

    public String getChineseWeek(Calendar date) {
        final String dayNames[] = { "星期日", "星期一", "星期二", "星期三", "星期四", "星期五",
          "星期六" };

        int dayOfWeek = date.get(Calendar.DAY_OF_WEEK);

        // System.out.println(dayNames[dayOfWeek - 1]);
        return dayNames[dayOfWeek - 1];

    }

    /**
        * 获得日期的下一个星期一的日期
        *
        * @param date
        * @return
        */
    public Calendar getNextMonday(Calendar date) {
        Calendar result = null;
        result = date;
        do {
         result = (Calendar) result.clone();
         result.add(Calendar.DATE, 1);
        } while (result.get(Calendar.DAY_OF_WEEK) != 2);
        return result;
    }

    /**
        *
        * @param d1
        * @param d2
        * @return
        */
    public int getHolidays(Calendar d1,Calendar d2){
        return this.getDaysBetween(d1, d2)-this.getWorkingDay(d1, d2);
      
    }

    }

    posted @ 2007-09-28 22:33 jadmin 阅读(65) | 评论 (0)编辑 收藏
    数值函数:
    abs(m) m的绝对值
    mod(m,n) m被n除后的余数
    power(m,n) m的n次方
    round(m[,n]) m四舍五入至小数点后n位的值(n缺省为0)
    trunc(m[,n]) m截断n位小数位的值(n缺省为0)
    --------------------------------------------------------------------------------

    字符函数:
    initcap(st) 返回st将每个单词的首字母大写,所有其他字母小写
    lower(st) 返回st将每个单词的字母全部小写
    upper(st) 返回st将每个单词的字母全部大写
    concat(st1,st2) 返回st为st2接st1的末尾(可用操作符"||")
    lpad(st1,n[,st2]) 返回右对齐的st,st为在st1的左边用st2填充直至长度为n,st2的缺省为空格
    rpad(st1,n[,st2]) 返回左对齐的st,st为在st1的右边用st2填充直至长度为n,st2的缺省为空格
    ltrim(st[,set]) 返回st,st为从左边删除set中字符直到第一个不是set中的字符。缺省时,指的是空格
    rtrim(st[,set]) 返回st,st为从右边删除set中字符直到第一个不是set中的字符。缺省时,指的是空格
    replace(st,search_st[,replace_st]) 将每次在st中出现的search_st用replace_st替换,返回一个st。缺省时,删除search_st substr(st,m[,n]) n=返回st串的子串,从m位置开始,取n个字符长。缺省时,一直返回到st末端
    length(st) 数值,返回st中的字符数
    instr(st1,st2[,m[,n]]) 数值,返回st1从第m字符开始,st2第n次出现的位置,m及n的缺省值为1 例:
         1. select initcap('THOMAS'),initcap('thomas') from test; initca initca ------ ------ Thomas Thomas
         2. select concat('abc','def') "first" from test; first ----- abcdef
         3. select 'abc'||' '||'def' "first" from test; first ----- abc def
         4. select lpad(name,10),rpad(name,5,'*') from test; lpad(name,10) rpad(name,5,'*') ------------ ---------------- mmx     mmx** abcdef abcde
         5. 去掉地址字段末端的点及单词st和rd select rtrim(address,'. st rd') from test
         6. select name,replace(name,'a','*') from test; name replace(name,'a','*') ---- --------------------- great gre*t
         7. select substr('archibald bearisol',6,9) a,substr('archibald bearisol',11) b from test; a b ------- ------- bald bear bearisol
         8. select name,instr(name,' ') a,instr(name,' ',1,2) b from test; name a b ------- -------- --------- li lei 3 0 l i l 2 4 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    转换函数:
    nvl(m,n) 如果m值为null,返回n,否则返回m
    to_char(m[,fmt]) m从一个数值转换为指定格式的字符串fmt缺省时,fmt值的宽度正好能容纳所有的有效数字 to_number(st[,fmt]) st从字符型数据转换成按指定格式的数值,缺省时数值格式串的大小正好为整个数附: to_char()函数的格式: --------------------------------- 符号 说明 --------------------------------- 9 每个9代表结果中的一位数字 0 代表要显示的先导0 $ 美元符号打印在数的左边 L 任意的当地货币符号 . 打印十进制的小数点 , 打印代表千分位的逗号 --------------------------------- 例:
    1. select to_number('123.45')+to_number('234.56') form test; to_number('123.45')+to_number('234.56') ---------------------------------------- 358.01 2. select to_char(987654321) from test; to_char(987654321) ------------------ 987654321 3. select to_char(123,'$9,999,999') a,to_char(54321,'$9,999,999') b,to_char(9874321,'$9,999,999') c from test; a b c ------- ---------- ----------- $123 $54,321 $9,874,321 4. select to_char(1234.1234,'999,999.999') a,to_char(0.4567,'999,999.999') b,to_char(1.1,'999,999.999') from test; a b c --------- ---------- ------------ 1,234.123 .457 1.100 --------------------------------------------------------------------------------

    分组函数:
    avg([distinct/all] n) 列n的平均值
    count([all] *) 返回查询范围内的行数包括重复值和空值
    count([distinct/all] n) 非空值的行数
    max([distinct/all] n) 该列或表达式的最大值
    min([distinct/all] n) 该列或表达式的最小值
    stdev([distinct/all] n) 该列或表达式的标准偏差,忽略空值
    sum([distinct/all] n) 该列或表达式的总和
    variance([distinct/all] n) 该列或表达式的方差,忽略空值 --------------------------------------------------------------------------------

    日期函数: >
    add_months(d,n) 日期d加n个月
    last_day(d) 包含d的月份的最后一天的日期
    month_between(d,e) 日期d与e之间的月份数,e先于d
    new_time(d,a,b) a时区的日期和时间d在b时区的日期和时间
    next_day(d,day) 比日期d晚,由day指定的周几的日期
    sysdate 当前的系统日期和时间
    greatest(d1,d2,...dn) 给出的日期列表中最后的日期
    least(d1,k2,...dn) 给出的日期列表中最早的日期
    to_char(d [,fmt]) 日期d按fmt指定的格式转变成字符串
    to_date(st [,fmt]) 字符串st按fmt指定的格式转成日期值,若fmt忽略,st要用缺省格式
    round(d [,fmt]) 日期d按fmt指定格式舍入到最近的日期
    trunc(d [,fmt]) 日期d按fmt指定格式截断到最近的日期附:
    日期格式:--------------------------------
    格式代码 
    说明
    举例或可取值的范围--------------------------------
    DD 该月某一天 1-3 DY    
    三个大写字母表示的周几 SUN,...SAT DAY    完整的周几,大写英文 SUNDAY,...SATURDAY MM 月份 1-12 MON      三
    个大写字母表示的月份 JAN,...DEC MONTH 完整 JANUARY,...DECEMBER RM 月份的罗马数字 I,...XII YY或YYYY 两位,四位数字年 HH:MI:SS   时:分:秒 HH12或HH24  以12小时或24小时显示 MI      分 SS      秒 AM或PM    上下午指示符 SP      后缀SP要求拼写出任何数值字段 TH      后缀TH表示添加的数字是序数 4th,1st FM 前缀对月或日或年值,禁止填充---------------------------------例:
    1. 下一个周五的日期 select next_day(sysdate,6) from test; 2. 两个月前的今天的日期 select add_months(sysdate,-2) from test;
    posted @ 2007-09-28 18:00 jadmin 阅读(66) | 评论 (0)编辑 收藏
    壹.呤红篇(红搂梦十二支曲)---一.呤<红搂梦>"引子"

    1.开辟鸿蒙情种生,情浓风月奈何天.愚衷谁遣翻添剧,悼玉悲金尘世缘.

    2.身心物否奈何天,忏悔情场曲解言.势溃利崩今世恨,四家结局尽哀怜.

    3.十二曲情绝可怜,红搂梦引泪涟涟.悲哀寂寞愤愁极,误尽韶华多少年.

    4.一部红搂千古恨,支支曲调诉衷纭.人寰无极因何故,苦乐酸甜年复年.

    5.兴尽悲来过后哀,人生结局使人骇.红颜艳艳天娇慧,误入情场悔不该.

    壹.呤红篇(红搂梦十二支曲)---二.呤"终身误"

    1.青根山峰顽石珍,灵河仙草绛珠身.相逢总是天真对,梦里难忘心里人.

    2.人说宝钗金玉缘,难忘竟是绛珠仙.阴差阳错终生苦,换栋偷梁终恨天.

    3.正统规身愿勿违,背经叛道自成其.婚姻自主难能贵,封建其时误尽余.

    4.移花接木王熙风,毁稿焚诗肠断时.鼓里良缘原是梦,醒来恸哭吊灵悲.

    5.卿卿我我忆当时,泪落荷锄花葬之.共读西厢曾几日,瑶琴不见实堪悲.

    壹.呤红篇(红搂梦十二支曲)---三.呤"枉凝眉"

    1.仙葩闻苑惜芳华,美玉无瑕证卞家.若说奇缘真不假,为何偏遇此奇葩.

    2.若说奇缘偏遇她,相逢心事话虚差.空劳牵挂空嗟讶,水里月光镜里花.

    3.万千心事从何诉,雁去燕来几度春.正值飞红花逝水,琼楼几度忆亲人.

    4.凝眉却是缘何事,屡屡诗成咏美人.一个空劳一个挂,天阶徒自看星辰.

    5.凝眉柢事欠情亏,我亦痴情大觉非.试问眼中多少泪,春流到夏几多回.

    6.凝眉深处忆非非,愁去愁来总自痴.叛逆道同同建爱,真心纯洁自无亏.

    7.婚姻政治互相谋,家世相同门户投.儿女同行难遂愿,终生悔恨感其由.

    8.王氏夫人熙风诡,大权操在伊心怀.爱情从此成悲剧,落入空门宝玉乖.

    9.绛珠仙草黄泉赴,频吊灵魂泪不干.遗恨终生何所谓,鼓钟经卷透心寒.

    10.枉自凝眉千百度,何心何意竟何之.何年何月花飞际,何地何天相去随.

    壹.呤红篇(红搂梦十二支曲)---四.呤"恨无常"

    1.荣华正茂赋青春,燕舞莺歌彩蝶频.烈火烹油大有得,鲜花着锦德妃珍.

    2.珍珠宝玉嵌玲珑,忧似黄莺囚入笼.统治集团为己利,娇身姿艳荐皇宫.

    3.贾府之门有靠山,仕途官场好售奸.回家之日荣华极,那个不惜天府攀.

    4.德才贤孝德贤妃,风藻尚书女史稀.荣府高堂贵显极,大观园耀乐何之.

    壹.呤红篇(红搂梦十二支曲)---五.呤"分骨肉"

    1.才自清明志自高,探春智慧胜诸曹.清风明月常相咏,调理家常总自豪.

    2.大厦将倾欲补天,祗缘尘世世情颠.身居琼阁有鲲志,欲出人头地涌先.

    3.封建末时万事非,狂澜欲挽费心机.闺中贤淑天真女,经世奇才才德施.

    4.一年容易又秋风,翠袖天寒易至冬.欲假他山诚不易,皆因自古有穷通.

    5.自古穷通何有定,悲欢离合岂无缘.命途多舛逢遭遇,天假前因作镜悬.

    6.虫鱼门户自良家,秋月春风度度华.远适他方终是恨,东皇误嫁悔何差.

    7.东风无力百花斜,蜂蝶有心逐丽华.雨雨风风谁作主,生非薄命不为花.

    8.各自纷飞奈若何,分奔离析感磋跎.心机费尽空捞月,潦倒乾坤我泣歌.

    9.一帆风顺路三千,骨肉分离痛故国.但愿爷娘莫念女,平安各保莫牵连.

    10.山水遥迢奴去也,身神消损误残年.人生离合竟如此,目断山高各一天.


    壹.呤红篇(红搂梦十二支曲)---六.呤"乐中悲"

    1.史氏湘云命苦殃,生来从小令双亡.家虽富贵谁人养,史鼎叔家爱不香.

    2.春风秋月等闲度,儿女情怀未系将.月貌花颜姿夭夭,天真烂漫意情芳.

    3.花宫月貌意情芳,咏月呤诗桃李香.最是撩人心绪处,栏杆独倚费思量.

    4.霁月风光照玉堂,性情豪爽度宏量.天真妖冶临风际,花欲言兮玉欲香.

    5.天真窕窈自天香,如愿东床如意郎.你我卿卿天比翼,前程地久与天长.

    6.老天为啥太荒唐,如意郎君早夭亡.天不由人何惨酷,年年月月泪双行.

    7.父母双亡夫夭亡,飘零花落水随将.贾家亲戚虽名府,寄以斯身无别望.

    8.身世坎坷命自伤,途穷湘水固湘江.多磨好事终难得,浮落高唐散雾茫.

    9.磋跎身世感沧桑,变换无穷恨命殃.泡影浮云何足虑,地天日月自消长.

    10.天地无仁残且酷,日无光彩月偏阴.鲜花虽美纷纷坠,满地残英任雨淋.


    壹.呤红篇(红搂梦十二支曲)---七.呤"世难容"

    1.窈窕身材如女仙,月宫有意任回旋.身居府院繁华处,大厦红楼梦好天.

    2.仕宦之家身世贤,才华出众赛神仙.天生孤僻人言鲜,气质如兰情意绵.

    3.质本为兰气洁馨,妙玉正是女娇星.他生未卜今生愿,落人红尘情惜惺.

    4.过分净洁俗凡嫌,高尚情操人妒贤.世时茫茫纷乱仄,当歌当泣诉琴弦.

    5.红尘总是老红尘,辜负华年绰约身.世道斯时多混浊,仙妹转眼变庸人.

    6.瞬息之间繁转湮,当时社会太私夤.妙玉本是天真女,落入尼庵避世尘.

    7.青灯一盏诵经频,人世无闻修后因.何必王孙公子叹,再生梦里化真人.

    8.碌碌红尘违尔愿,淖污畏陷早抽身.佛门静地空空也,超脱凡尘净度贫.

    9.无作无为度此身,天真红粉变尼人.荣华富贵如尘土,喜怒乐哀无别论.

    10.仕宦之家小姐身,穷途潦倒有原因.逃禅虽属归真法,净土难能净洁氤.

    11.昔时之事莫今量,消极何言没有方.如若尔身值此世,尔能展法度身芳.

    壹.呤红篇(红搂梦十二支曲)---八.呤"喜冤家"

    1. 何喜冤家冤恨长,中山狼本狠心狼.贪而无厌居心险,兽面蛇心黑肚肠.

    2.趋炎附势恶如魔,残暴荒淫罪孽多.可恨恶狼孙绍祖,天仙娇女吞泥窝.

    3.繁华四族已倾秃,高筑债台难活之.用迎春作抵押,鲜花坠入污泥溪.

    4.如花似玉天娇女,落入兽巢命陷沦.薄命如斯终古恨,侯门艳质化灰尘.

    5.迎春命苦归山狼,越想越思哭断肠.丽质天生遭兽污,呜呼一命付汪洋.

    6.法外逍遥孙绍祖,害人罪恶贾包之.骄奢淫佚寻常事,公辅千金作贱妓.

    7.负义忘恩孙绍祖,祖冤祖孽统乘之.寻欢作乐污多女,害命贪财胜虎豹.

    8.芳魂艳魄剧悠悠,害人黄泉恨不休.昔日恩由全不问,恩人当作是冤仇.

    9.迎春误入冤家孽,不了深仇众恨之.天否无睛人不管,冤冤怨怨报何为.

    10.人世偏多此恶官,胡为乱作喜欢欢.皇朝大厦将倾倒,罪孽深深后人寒.

    posted @ 2007-09-28 16:45 jadmin 阅读(190) | 评论 (0)编辑 收藏