2009年4月24日
> 引言
在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
用户自定义生成,需要由程序员手工给主键主动赋值
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 阅读(985) |
评论 (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) |
编辑 收藏
主要更新:
----------------------------------------
* [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&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&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://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 阅读(124) |
评论 (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的统一调度下进行数据块的创建、删除和复制。
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可靠性和性能的关键。优化的副本存放策略是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 阅读(97) |
评论 (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 阅读(104) |
评论 (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 阅读(100) |
评论 (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 阅读(108) |
评论 (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://localhost或http://127.0.0.1或http://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 阅读(114) |
评论 (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查看命令提示helpdb.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 阅读(87) |
评论 (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 阅读(104) |
评论 (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的方式,当用户新注册了一个账号,账号id的123,我们可以通过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,读操作可以在Master和Slave机器上进行,Slave与Master的结构完全一样,一个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机制, jdbc的connection机制.
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 阅读(158) |
评论 (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 阅读(93) |
评论 (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。
posted @
2010-07-23 12:47 jadmin 阅读(85) |
评论 (0) |
编辑 收藏
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 阅读(123) |
评论 (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 阅读(194) |
评论 (0) |
编辑 收藏
很多朋友都很喜欢在DOS命令行下来操作计算机,我也是。 如何打开DOS并定位到指定的路径呢?很多优化软件都提供了往右键菜单中加入“当前目录打开DOS”的功能,当右键点击文件夹时,就打开DOS并定位到该文件夹。网上也有修改注册表来实现的,其实还有更简单的方法来实现!
打开“我的电脑”,点击菜单中的“工具”-“文件夹选项”,选择“文件类型”,找到“(无)资料夹”,点“高级”,“新建”,在“操作”中填入 “DOS快速通道”(这里可以随便填),“用于执行操作的应用程序”中填入“CMD.exe /k cd %1”(这个是关键),确定即可。
右键打开任何一个文件夹、分区,点击“DOS快速通道”,就可以打开DOS并定位到你所点击的目录下!这里要告诉大家,其实许多问题都有更简单的方法,只要大家细心一点。都可以大大提高我们的办事效率!
posted @
2010-05-15 23:52 jadmin 阅读(94) |
评论 (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个线程,每个线程都打印从0到99这100个数字,我们希望线程之间不会出现交叉乱序打印,而是顺序地打印。
先来看第一段代码,这里我们在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中对LRU和MRU的使用来看一下两者在缓冲区工作机制中的作用和区别:
在Oracle 9i中有LRU List的概念: 我们可以把LRU List想象成是一连串的缓冲区集合,两端分别是LRU端和MRU端, 当数据库从磁盘上读取数据放入缓冲区时,系统必须先确定缓冲区中有free buffers,这个时候Oracle 9i会扫描LRU List,扫描的基本原则是:
1. 从LRU端到MRU端;
2. 当扫描到free buffer或已扫描的缓冲区数目超过临界值时,就会停止扫描动作;
如果在扫描过程顺利的在LRU List中找到了free buffer,那么Oracle 9i就把从磁盘读出的数据写到free buffer中然后把free buffer加到LRU List的MRU端.
那如果扫描过程没有在LRU List中找到free buffer怎么办?当然是从LRU List的LRU端开始清除缓冲区,如此一来就可以腾出新的空间了.
下图就是一个例子:
使用者查询数据A,初始的时候LRU List中没有数据A,于是Oracle 9i到磁盘读取A,然后放到LRU List的MRU端,使用者再从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’所示,你会发现图2的State m’与图1的State m缓冲区存放的数据完全一样但是存放位置不一样.此时LRU List满了,如果再放N的时候LRU List`淘汰的是B,因为A的查询率高于B,所以LRU List让A在缓冲区中呆上较长的时间而先淘汰掉”较不常用的”的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 阅读(88) |
评论 (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 阅读(84) |
评论 (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表达式的值。如下面的代码所示:
构造MapThe 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 2的OGNL示例业务控制器
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; } } |
该业务控制器分别在application、session和request中存入名为“msg”的字符串信息,另外定义了一个List类型属性,同时添加了两个Person类型元素。在配置文件中增加了相应的配置,代码如下:
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 2中OGNL表达式的使用有所帮助。
posted @
2009-08-15 17:29 jadmin 阅读(84) |
评论 (0) |
编辑 收藏
OGNL的集合操作
如果需要一个集合元素的时候(例如List对象或者Map对象),可以使用OGNL中同集合相关的表达式。
可以使用如下代码直接生成一个List对象:
该OGNL表达式中,直接生成了一个List对象,该List对象中包含3个元素:e1、e2和e3。如果需要更多的元素,可以按照这样的格式定义多个元素,多个元素之间使用逗号隔开。
如下代码可以直接生成一个Map对象:
#{key1:value1,key2:value2,…} |
Map类型的集合对象,使用key-value格式定义,每个key-value元素使用冒号标识,多个元素之间使用逗号隔开。
对于集合类型,OGNL表达式可以使用in和not in两个元素符号。其中,in表达式用来判断某个元素是否在指定的集合对象中;not in判断某个元素是否不在指定的集合对象中,如代码8.3所示。
代码8.3使用OGNL集合操作符
除了in和not in之外,OGNL还允许使用某个规则获得集合对象的子集,常用的有以下3个相关操作符。
>>>
?:获得所有符合逻辑的元素。
>>>
^:获得符合逻辑的第一个元素。
>>>
$:获得符合逻辑的最后一个元素。
例如代码:
person.relatives.{? #this.gender == 'male'} |
该代码可以获得person的所有性别为male的relatievs集合。
posted @
2009-08-15 17:25 jadmin 阅读(154) |
评论 (0) |
编辑 收藏
★注意★
使用索引,并不是直接获得指定的元素,而是从指定的索引位置搜索。
Struts 2中的OGNL Context是ActionContext,如图8.2所示。
|
图8.2 Struts 2的OGNL Context结构示意图 |
★说明★
图8.2只是说明Struts 2的OGNL Context结构,实际上Context还包含其他对象。
由于值栈是Struts 2中OGNL的根对象,如果用户需要访问值栈中的对象,则可以直接通过下面的代码访问值栈中的属性:
如果访问其他Context中的对象,由于不是根对象,在访问时,需要加#前缀。
>>
application对象:用于访问ServletContext,例如#application.userName或者#application['userName'],相当于调用Servlet的getAttribute("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) |
编辑 收藏
OGNL是Struts 2框架的默认表达式语言,增强了Struts 2的数据访问能力,同时简化了代码。
Struts 2的OGNL表达式
标准的OGNL会设定一个根对象(root对象)。假设使用标准OGNL表达式来求值(不使用Struts 2的OGNL表达式),如果OGNL上下文(OgnlContext Map类型)有两个对象:foo对象,在OgnlContext中名称为foo;bar对象,在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的根对象,假设值栈中存在两个对对象实例:Man和Animal,这两个对象实例都有一个name属性,Animal有一个species属性,Man有一个salary属性,假设Animal在值栈的顶部,Man在Animal后面,下面的代码片断会帮助读者更好地理解OGNL表达式:
// 调用animal.getSpecies() species // 调用man.getSalary() salary // 调用animal.getName(),因为Animal位于值栈的顶部 name |
最后一行示例代码,返回的是animal.getName()返回值,即返回了Animal的name属性,因为Animal是值栈的顶部元素,OGNL将从顶部元素搜索,所以会返回Animal的name属性值。如果要获得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 阅读(236) |
评论 (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对于此过程的一个更好的图示:
根据原理来回答下之前学习应用级协议带着的几个问题:
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中并返回。
图示以上过程:
同样来回答问题:
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; //属性的getter和setter方法 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; //属性的getter和setter方法 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嵌套属性示意图 |
运行该示例,控制器显示如下信息:
★说明★
OGNL可以使用非常简单的表达式来访问多层嵌套属性,为开发者提供了一个有力的工具。
posted @
2009-08-13 14:25 jadmin 阅读(130) |
评论 (0) |
编辑 收藏
基本的OGNL语法是十分简单的,当然OGNL支持丰富的表达式,一般情况下,不用担心OGNL的复杂性。例如有一个man对象,该对象有一个name属性,那么使用OGNL来获得该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组件到数据模型(
WebOGNL,
Tapestry,
WebWork等)
- 提供类似
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使用例:
- package com.test.ognl;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import junit.framework.TestCase;
- import ognl.Ognl;
- import ognl.OgnlContext;
-
- public class OgnlTest extends TestCase {
- public void testGetValue() throws Exception {
- OgnlContext context = new OgnlContext();
- Book book = new Book("book1");
- context.put("book", book);
-
- final String expression = "book.name";
- Object parseExpression = Ognl.parseExpression(expression);
- assertEquals("book1", Ognl.getValue(parseExpression, context));
-
- book.setName("book2");
- assertEquals("book2", Ognl.getValue(parseExpression, context));
- }
-
- public void testSetValue() throws Exception {
- OgnlContext context = new OgnlContext();
- Book book = new Book("book1");
- context.put("book", book);
-
- final String expression = "book.name";
- Object parseExpression = Ognl.parseExpression(expression);
- Ognl.setValue(parseExpression, context, "book2");
- assertEquals("book2", book.getName());
- }
-
- public void testCallStaticMethod() throws Exception {
- OgnlContext context = new OgnlContext();
-
- final String expression = "@com.test.ognl.Book@test()";
- Object parseExpression = Ognl.parseExpression(expression);
- assertEquals("Hello World", Ognl.getValue(parseExpression, context));
- }
-
- public void testArray() throws Exception {
- OgnlContext context = new OgnlContext();
-
- final String expression = "new int[]{1, 2, 3}";
- Object parseExpression = Ognl.parseExpression(expression);
- int[] ret = (int[]) Ognl.getValue(parseExpression, context);
-
- assertEquals(1, ret[0]);
- assertEquals(2, ret[1]);
- assertEquals(3, ret[2]);
- }
-
- public void testList() throws Exception {
- OgnlContext context = new OgnlContext();
-
- final String expression = "{1, 2, 3}";
- Object parseExpression = Ognl.parseExpression(expression);
- List ret = (List) Ognl.getValue(parseExpression, context);
-
- assertEquals(new Integer(1), ret.get(0));
- assertEquals(new Integer(2), ret.get(1));
- assertEquals(new Integer(3), ret.get(2));
- }
-
- public void testMap() throws Exception {
- OgnlContext context = new OgnlContext();
-
- final String expression = "#{\"name\" : \"book1\", \"price\" : 10.2}";
- Object parseExpression = Ognl.parseExpression(expression);
- Map value = (Map) Ognl.getValue(parseExpression, context);
- assertEquals("book1", value.get("name"));
- assertEquals(new Integer(10.2), value.get("price"));
- }
- }
-
- class Book {
- private int name;
-
- public Book(String bookName) {
- this.name = bookName;
- }
- public int getName() {
- return name;
- }
-
- public void setName(int Name) {
- this.name = name;
- }
-
-
- public static String hello() {
- return "Hello World";
- }
posted @
2009-08-12 18:19 jadmin 阅读(104) |
评论 (0) |
编辑 收藏
cellspacing ---> 单元格的元素与边界的距离
cellpadding ---> 单元格与单元格之间的距离
posted @
2009-08-09 19:34 jadmin 阅读(130) |
评论 (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 阅读(109) |
评论 (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 阅读(69) |
评论 (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页面导入声明
二、常用标签
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的分布式事务,只需简单地将JtaTransactionManager和LocalSessionFactoryBean的定义结合起来就可以了,其中每个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.xml(Hibernate的基本配置文件)起,Hibernate事务便轻易交由Spring拖管了。
说明:在上一章介绍IBatis和DAO的时候,曾经针对事务和DAO的关系简单的进行了探讨。通常DAO的粒度应该都是比较细的,即它们只是一些单步的CRUD操作,所以就需要引入一个业务对象来包裹DAO,这样,就可以在业务对象的基础上,提供更粗粒度的事务划分了(比如跨越多个DAO的方法调用进行事务管理)。
为了能对DAO进行更粗粒度的事务控制,需要为其增加一个业务对象。下面给出了该业务对象的接口和实现,如代码10.25~10.26所示。
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) |
编辑 收藏
1在Spring上下文中配置SessionFactory
通过上文的描述,可以知道,Spring使用JdbcTemplate时必须和特定的数据源进行绑定。而在Hibernate中,数据源是对用户屏蔽的,它使用一个称为“Session”的强劲武器。
Session具有建立或取消对象的持久关联、同步对象状态和数据库以及事务管理等等复杂功能。Session是Hibernate的核心概念,就如同SqlMap与之IBatis一样。Session的创建依赖于Hibernate的SessionFactory,SessionFactory和Session一样,也是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抽离出的数据源,现在换成了Apache的DBCP连接池。当然,也可以换作其他实现,比如从一个JNDI上获取的数据源:
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/myds"/>
</bean>
Spring提供了LocalSessionFactoryBean来创建本地的Hibernate SessionFactory,这就类似于上节中介绍的SqlMapClientFactoryBean(它用以创建IBatis的SqlMap)。当然也可以创建绑定在JNDI上的SessionFactory,不过这通常只在EJB环境下使用。
注意:代码10.20中被注释掉的部分,如果不使用LocalSessionFactoryBean的configLocations属性读取Hibernate的原生配置文件,可由Spring的LocalSessionFactoryBean负责配置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.javapackagesinosoft.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.java1packagesinosoft.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.java1packagesinosoft.dj.aop.staticaop;
2
3publicenumLevel{
4 INFO,DEBUGE;
5}
6 那我们去写个测试类看看,代码如下:Test.java1packagesinosoft.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
- <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property name="beanNames"><value>jdk*,onlyJdk</value></property>
- <property name="interceptorNames">
- <list>
- <value>myInterceptor</value>
- </list>
- </property>
- </bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames"><value>jdk*,onlyJdk</value></property>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
DefaultAdvisorAutoProxyCreator
- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
- <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
- <property name="transactionInterceptor" ref="transactionInterceptor"/>
- </bean>
- <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
- <bean id="businessObject1" class="com.mycompany.BusinessObject1">
- <!-- Properties omitted -->
- </bean>
- <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将使用JDK的java.lang.reflect.Proxy创建代理。这个类让Spring动态产生一个新的类,它实现了所需的接口,织入了通知,并且代理目标的所有请求。(这篇主要介绍这个方式)
2. 如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个子类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。
下面以一个实例说明Spring AOP的基本开发方法:
一.创建通知
Spring连接点模型是建立在方法拦截上,这意味着你编写的
Spring通知会在方法调用周围的各个地方织入系统中。
TestAopServiceAdvice实现了接口MethodBeforeAdvice(前置通知),并实现它的惟一的方法before,这个类就可以在调用目标对象前被调用。同样的还有AfterReturningAdvice(后置通知),MethodInterceptor(环绕通知),异常通知(ThrowsAdvice),引入通知等。
在这个方法中我们输出了一个字符串TestAopServiceAdvice,用于验证这个方法是否在目标对象前调用了。
注意:我们无法改变before方法中的参数args和target中的值,args中存的是原来要传入目标对象的变量,target即指目标对象。
二.配置Spring XML配置文件
要在Spring中实现AOP,一般情况下需要配置4个bean:
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的增强,增加了一些新的特性:
- 全面支持java6和javaEE5(JDBC 4.0, JTA 1.1, JavaMail 1.4, JAX-WS 2.0等)
- 全特性的注释驱动依赖注入,包括对限定词的支持
- 支持基于classpath的组件扫描,自动侦测有注释的类
- 支持AspectJ切点表达式中包含bean name切点元素
- 内置AspectJ加载时编织,基于LoadTimeWeaver 提取
- 更多的XML配置文件的名字空间支持,比如context和jms等,最大程度提高编写的方便性
- 全面修订集成测试框架,支持JUnit4和TestNG
- 新的基于注释的Servlet MVC和Portlet MVC控制器风格配置
- 扩展SimpleJdbcTemplate功能,支持命名的参数等
- 官方认可的Websphere支持,支持WebSphere 6 UOWManager 机制
- Spring框架的jar文件,兼容OSGi绑定,并能直接使用
- Spring ApplicationContext可被部署为JCA RAR文件,用于非主导应用模块
- 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 阅读(72) |
评论 (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 阅读(51) |
评论 (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 阅读(61) |
评论 (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&characterEncoding=utf8&autoReconnect=true&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元素里一起加载。
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的执行效果。
注意:使用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所示。
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 阅读(116) |
评论 (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 阅读(81) |
评论 (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 阅读(115) |
评论 (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 阅读(1154) |
评论 (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子元素的用法:
- < class name="onlyfun.caterpillar.User" table="USER">
- < id name="id" type="string" unsaved-value="null">
- < column name="USER_ID"/>
- < generator class="uuid.hex"/>
- < /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
- < ?xml version="1.0" encoding="utf-8"?>
- < !DOCTYPE hibernate-configuration
- PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
- "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
-
- < hibernate-configuration>
- < session-factory >
-
- < !-- local connection properties -->
- < property name="hibernate.connection.url">jdbc:mysql://localhost:3306/bookshop?zeroDateTimeBehavior=convertToNull< /property>
- < property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver< /property>
- < property name="hibernate.connection.username">root< /property>
- < property name="hibernate.connection.password">12345678< /property>
- < !-- property name="hibernate.connection.pool_size">< /property -->
-
- < !-- dialect for MySQL -->
- < property name="dialect">org.hibernate.dialect.MySQLDialect< /property>
-
- < property name="hibernate.show_sql">true< /property>
- < property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory< /property>
- < property name="hbm2ddl.auto">update< /property>
-
- < mapping resource="org/jskyme/data/local/po/Shop.hbm.xml"/>
- < /session-factory>
- < /hibernate-configuration>
data_server.cfg.xml
- < ?xml version="1.0" encoding="utf-8"?>
-
- < !DOCTYPE hibernate-configuration
-
- PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
-
- "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
-
- < hibernate-configuration>
-
- < session-factory >
-
- < !-- local connection properties -->
-
- < property name="hibernate.connection.url">jdbc:mysql://192.168.0.10:3306/bookshop?zeroDateTimeBehavior=convertToNull< /property>
-
- < property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver< /property>
-
- < property name="hibernate.connection.username">root< /property>
-
- < property name="hibernate.connection.password">12345678< /property>
-
- < !-- property name="hibernate.connection.pool_size">< /property -->
-
- < !-- dialect for MySQL -->
-
- < property name="dialect">org.hibernate.dialect.MySQLDialect< /property>
-
- < property name="hibernate.show_sql">true< /property>
-
- < property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory< /property>
-
- < property name="hbm2ddl.auto">update< /property>
-
- < mapping resource="org/jskyme/data/local/po/Shop.hbm.xml"/>
-
- < /session-factory>
-
- < /hibernate-configuration>
Hibernate访问多个数据库步骤二:数据库访问类:
数据库管理类:DataBaseManager
- package org.jskyme.hibernate.util;
-
- import java.util.HashMap;
-
- public class DataBaseManager extends HashMap {
- private static final long serialVersionUID = 6491666983237498097L;
- private static DataBaseManager inst = new DataBaseManager();
-
- public static DataBaseManager getInst() {
- return inst;
- }
-
- public SessionManager get(Object key) {
- return (SessionManager) super.get(key);
- }
-
- @Override
- public Object put(Object key, Object value) {
- return super.put(key, value);
- }
-
- public static void setInst(DataBaseManager inst) {
- DataBaseManager.inst = inst;
- }
-
- }
Hibernate连接数据库操作类:
- package org.jskyme.hibernate.util;
-
- import java.util.List;
-
- import org.hibernate.Criteria;
- import org.hibernate.Query;
- import org.hibernate.SQLQuery;
- import org.hibernate.Session;
- import org.hibernate.SessionFactory;
- import org.hibernate.Transaction;
- import org.hibernate.cfg.Configuration;
-
- public final class SessionManager {
- private Configuration config;
-
- private SessionFactory sessionFactory;
-
- private Session session;
-
- public Criteria createCriteria(Class persistentClass) {
- return session.createCriteria(persistentClass);
- }
-
- private void buildSession() {
- sessionFactory = config.buildSessionFactory();
- session = sessionFactory.openSession();
- }
-
- public SessionManager(String configFile) {
- config = new Configuration().configure(configFile);
- buildSession();
- }
-
- public Session getSession() {
- return session;
- }
-
- public void save(Object obj) {
- Transaction tx = session.beginTransaction();
- session.save(obj);
- tx.commit();
- }
-
- public Object load(Class clas, Integer priId) {
- return session.get(clas, priId);
- }
-
- public Query findbyhql(String hql) {
- return session.createQuery(hql);
- }
-
- public List pageSizeByhql(String hql) {
- return findbyhql(hql).list();
- }
-
- public SQLQuery findbysql(String sql) {
- return session.createSQLQuery(sql);
- }
-
- public void update(Object obj) {
- Transaction tx = session.beginTransaction();
- session.saveOrUpdate(obj);
- tx.commit();
- }
- public void delete(Class clas, Integer inte) {
- session.delete(load(clas, inte));
- }
-
- public void delete(Object obj) {
- session.delete(obj);
- }
-
- public void deletebyhql(String hql) {
- Query query = session.createQuery(hql);
- query.executeUpdate();
- }
-
- public Query createQuery(String hql) {
- return session.createQuery(hql);
- }
-
- }
Hibernate访问多个数据库步骤三:测试类
- package org.jskyme.data.test;
- import junit.framework.TestCase;
- import org.hibernate.Query;
- import org.jskyme.hibernate.util.DataBaseManager;
- import org.jskyme.hibernate.util.SessionManager;
- public class DataBaseManagerTest extends TestCase {
- DataBaseManager dbm = DataBaseManager.getInst();
- public void testDatabase() {
- setDatabase();
- SessionManager tempSess = dbm.get("dataLocal");
- Query query = tempSess.createQuery("from Shop");
- query.list();
-
- SessionManager tempSess27 = dbm.get("dateManage");
- Query query27 = tempSess27.createQuery("from Shop");
- query27.list();
- }
- private void setDatabase() {
- SessionManager dateManageLocal = new SessionManager("localhost.cfg.xml");
- SessionManager dateManage27 = new SessionManager("data_server.cfg.xml");
- dbm.put("dateManage", dateManage27);
- dbm.put("dataLocal", dateManageLocal);
- }
- }
posted @
2009-07-03 13:51 jadmin 阅读(68) |
评论 (0) |
编辑 收藏
Hibernate延时加载,其实这个异常写的非常之清楚,就是会话关闭,无法对Hibernate实体进行操作。造成这样的情况有很多,什么书写错误啊,逻辑错误啊。
但就此说一下关于lazy机制:
Hibernate延时加载包括延迟初始化错误,这是运用Hibernate开发项目时最常见的错误。如果对一个类或者集合配置了延迟检索策略,那么必须当代理类实例或代理集合处于持久化状态(即处于Session范围内)时,才能初始化它。如果在游离状态时才初始化它,就会产生延迟初始化错误。
下面把Customer.hbm.xml文件的< class>元素的lazy属性设为true,表示使用延迟检索策略:
- < 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属性:
- tx = session.beginTransaction();
- Customer customer=(Customer)session.load(Customer.class,new Long(1));
- customer.getName();
- tx.commit();
在运行session.load ()方 法时Hibernate不执行任何select语句,仅仅返回Customer类的代理类的实例,它的OID为1,这是由load()方法的第二个 参数指定的。当应用程序调用customer.getName()方法时,Hibernate会初始化Customer代理类实例,从数据库中加载 Customer对象的数据,执行以下select语句:
- select * from CUSTOMERS where ID=1;
- select * from ORDERS where CUSTOMER_ID=1;
当< class>元素的lazy属性为true,会影响Session的load()方法的各种运行时行为,下面举例说明。
1.如果加载的Customer对象在数据库中不存在,Session的load()方法不会抛出异常,只有当运行customer.getName()方法时才会抛出以下异常:
- ERROR LazyInitializer:63 - Exception initializing proxy
- net.sf.hibernate.ObjectNotFoundException: No row with the given identifier exists: 1, of class:
- mypack.Customer
2.如果在整个Session范围内,应用程序没有访问过Customer对象,那么Customer代理类的实例一直不会被初始化,Hibernate不会执行任何select语句。以下代码试图在关闭Session后访问Customer游离对象:
- tx = session.beginTransaction();
- Customer customer=(Customer)session.load(Customer.class,new Long(1));
- tx.commit();
- session.close();
- customer.getName();
由于引用变量customer引用的Customer代理类的实例在Session范围内始终没有被初始化,因此在执行customer.getName()方法时,Hibernate会抛出以下异常(Hibernate延时加载的问题之一):
- ERROR LazyInitializer:63 - Exception initializing proxy
- net.sf.hibernate.HibernateException: Couldnotinitializeproxy-theowningSessionwasclosed
由此可见,Customer代理类的实例只有在当前Session范围内才能被初始化。
3.net.sf.hibernate.Hibernate类的initialize()静态方法用于在Session范围内显式初始化代理类实例,isInitialized()方法用于判断代理类实例是否已经被初始化。例如:
- tx = session.beginTransaction();
- Customer customer=(Customer)session.load(Customer.class,new Long(1));
- if(!Hibernate.isInitialized(customer))
- Hibernate.initialize(customer);
- tx.commit();
- session.close();
- customer.getName();
以上代码在Session范围内通过Hibernate类的initialize()方法显式初始化了Customer代理类实例,因此当Session关闭后,可以正常访问Customer游离对象。
4.当应用程序访问代理类实例的getId()方法时,不会触发Hibernate初始化代理类实例的行为,例如:
- tx = session.beginTransaction();
- Customer customer=(Customer)session.load(Customer.class,new Long(1));
- customer.getId();
- tx.commit();
- session.close();
- customer.getName();
当应用程序访问customer.getId()方法时,该方法直接返回Customer代理类实例的OID值,无需查询数据库。由于引用变量 customer始终引用的是没有被初始化的Customer代理类实例,因此当Session关闭后再执行customer.getName()方法, Hibernate会抛出以下异常(Hibernate延时加载的问题之一):
- ERROR LazyInitializer:63 - Exception initializing proxy
- 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". 但有可能会引发另一个异常叫
- failed to lazily initialize a collection of role: XXXXXXXX, no session or session was closed
解决方法:在web.xml中加入
- < filter>
- < filter-name>hibernateFilter< /filter-name>
- < filter-class>
- org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
- < /filter-class>
- < /filter>
- < filter-mapping>
- < filter-name>hibernateFilter< /filter-name>
- < url-pattern>*.do< /url-pattern>
- < /filter-mapping>
就可以了。
以上文章转自:http://developer.51cto.com/art/200907/133249.htm
关键字:Hibernate,延时加载,lazy
posted @
2009-07-03 13:39 jadmin 阅读(62) |
评论 (0) |
编辑 收藏
众所周知,设计模式描述的就是针对软件设计中的常见问题做出的可重复使用的解决方案。而了解及使用这些模式则是SOA取得成功的根本。
众所周知,设计模式描述的就是针对软件设计中的常见问题做出的可重复使用的解决方案。而了解及使用这些模式则是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) |
编辑 收藏