K.L Repository

Subscribe to RSS feed

Sticky post

hibernate:unsave-value

http://forum.javaeye.com/viewtopic.php?t=1604&highlight=unsaved-value

当你显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的。某些情况下(父子表关联保存),当你在程序中并没有显式的使用save或者update一个持久对象,那么Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象,是一个尚未被持久化过的内存临时对象。例如:

java代码:

Session session = ...;
Transaction tx = ...;

Parent parent = (Parent) session.load(Parent.class, id);

Child child = new Child();
child.setParent(parent);
child.setName("sun");

parent.addChild(child);
s.update(parent);

s.flush();
tx.commit();
s.close();



在上例中,程序并没有显式的session.save(child); 那么Hibernate需要知道child究竟是一个临时对象,还是已经在数据库中有的持久对象。如果child是一个新创建的临时对象(本例中就是这种情况),那么Hibernate应该自动产生session.save(child)这样的操作,如果child是已经在数据库中有的持久对象,那么Hibernate应该自动产生session.update(child)这样的操作。

因此我们需要暗示一下Hibernate,究竟child对象应该对它自动save还是update。在上例中,显然我们应该暗示Hibernate对child自动save,而不是自动update。那么Hibernate如何判断究竟对child是save还是update呢?它会取一下child的主键属性 child.getId() ,这里假设id是 java.lang.Integer类型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate认为child是新的内存临时对象,发送save,如果不相等,那么Hibernate认为child是已经持久过的对象,发送update。

unsaved-value="null" (默认情况,适用于大多数对象类型主键 Integer/Long/String/...)

当Hibernate取一下child的Id,取出来的是null(在上例中肯定取出来的是null),和unsaved-value设定值相等,发送save(child)

当Hibernate取一下child的id,取出来的不是null,那么和unsaved-value设定值不相等,发送update(child)

例如下面的情况:

java代码:

Session session = ...;
Transaction tx = ...;

Parent parent = (Parent) session.load(Parent.class, id);
Child child = (Child) session.load(Child.class, childId);

child.setParent(parent);
child.setName("sun");

parent.addChild(child);
s.update(parent);

s.flush();
tx.commit();
s.close();



child已经在数据库中有了,是一个持久化的对象,不是新创建的,因此我们希望Hibernate发送update(child),在该例中,Hibernate取一下child.getId(),和unsave-value指定的null比对一下,发现不相等,那么发送update(child)。

BTW: parent对象不需要操心,因为程序显式的对parent有load操作和update的操作,不需要Hibernate自己来判断究竟是save还是update了。我们要注意的只是child对象的操作。另外unsaved-value是定义在Child类的主键属性中的。

java代码:

<class name="Child" table="child">
<id column="id" name="id" type="integer" unsaved-value="null">
<generator class="identity"/>
</id>
...
</class>



如果主键属性不是对象型,而是基本类型,如int/long/double/...,那么你需要指定一个数值型的unsaved-value,例如:

java代码:

unsaved-null="0"



在此提醒大家,很多人以为对主键属性定义为int/long,比定义为Integer/Long运行效率来得高,认为基本类型不需要进行对象的封装和解构操作,因此喜欢把主键定义为int/long的。但实际上,Hibernate内部总是把主键转换为对象型进行操作的,就算你定义为int/long型的,Hibernate内部也要进行一次对象构造操作,返回给你的时候,还要进行解构操作,效率可能反而低也说不定。因此大家一定要扭转一个观点,在Hibernate中,主键属性定义为基本类型,并不能够比定义为对象型效率来的高,而且也多了很多麻烦,因此建议大家使用对象型的Integer/Long定义主键。

unsaved-value="none"和
unsaved-value="any"

主主要用在主键属性不是通过Hibernate生成,而是程序自己setId()的时候。

在这里多说一句,强烈建议使用Hibernate的id generator,或者你可以自己扩展Hibernate的id generator,特别注意不要使用有实际含义的字段当做主键来用!例如用户类User,很多人喜欢用用户登陆名称做为主键,这是一个很不好的习惯,当用户类和其他实体类有关联关系的时候,万一你需要修改用户登陆名称,一改就需要改好几张表中的数据。偶合性太高,而如果你使用无业务意义的id generator,那么修改用户名称,就只修改user表就行了。

由这个问题引申出来,如果你严格按照这个原则来设计数据库,那么你基本上是用不到手工来setId()的,你用Hibernate的id generator就OK了。因此你也不需要了解当

unsaved-value="none"和
unsaved-value="any"

究竟有什么含义了。如果你非要用assigned不可,那么继续解释一下:

unsaved-value="none" 的时候,由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对child对象发送update(child)

unsaved-value="any" 的时候,由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对child对象发送save(child)

大多数情况下,你可以避免使用assigned,只有当你使用复合主键的时候不得不手工setId(),这时候需要你自己考虑究竟怎么设置unsaved-value了,根据你自己的需要来定。

BTW: Gavin King强烈不建议使用composite-id,强烈建议使用UserType。

因此,如果你在系统设计的时候,遵循如下原则:

1、使用Hibernate的id generator来生成无业务意义的主键,不使用有业务含义的字段做主键,不使用assigned。

2、使用对象类型(String/Integer/Long/...)来做主键,而不使用基础类型(int/long/...)做主键

3、不使用composite-id来处理复合主键的情况,而使用UserType来处理该种情况。

那么你永远用的是unsaved-value="null" ,不可能用到any/none/..了。

Sticky post

Hibernat: Lazy Initialization过程

在Hibernate手册中是这样描述 Lazy Initialization-延迟加载的

集合(不包括数组)是可以延迟初始化的,意思是仅仅当应用程序需要访问时,才载入他们的值。对于使用者来说,初始化是透明的, 因此应用程序通常不需要关心这个(事实上,透明的延迟加载也就是为什么Hibernate需要自己的集合实现的主要原因)。

另外一种选择是不使用延迟初始化集合。既然延迟初始化可能引起上面这样错误,默认是不使用延迟初始化的。但是, 为了效率的原因, 我们希望对绝大多数集合(特别是实体集合)使用延迟初始化。


如果你不描述,则Lazy的属性为False(即不使用延迟加载!这让我想到原来我们的一位同事没有Load出List就怪罪于Hibernate,看来是子虚乌有的!) 所以Lazy属性的设置与你内存的大小是有很大关系的,如果你采用Hibernate默认的设置,也许不经意(就是Hibernate"透明持久化")Load出主表的记录就连带着将子表的一个上千万的List给Load进内存中,你的服务器不崩溃才怪呢!!

Javaeye的那些个高手总是不解释"透明持久化"这个概念,让很多初学者非常的疑惑(比如我),在打印出Hibernate的调试信息后,一切都变得自然。。。
你可以尝试着将Lazy更换成true|false
<list name="currentSteps" cascade="all-delete-orphan" lazy="false" >
<key column="entry_Id"/>
<index column="stepIndex"/>
<one-to-many class="com.company.engine.workflow.po.HibernateCurrentStep"/>
</list>

当你将lazy更改成false(也就是默认值)时,并在Load出主记录后关闭session,比如如下程序
try
{
List result = session.find(ENTRY_SEARCH_SQL, new Long(entryId), Hibernate.LONG);
return (WorkflowEntry) result.get(0);
}
catch (HibernateException he)
{
throw new StoreException(he);
}finally
{
if(session != null)
try
{
session.close();
}
catch (HibernateException he)
{
throw new StoreException(he);
}
}

当你执行如下测试的时候
try
{
HibernateWorkflowEntry entry = (HibernateWorkflowEntry) store.findEntry(0);
logger.debug("find entry by id - " + entry);
assertNotNull(entry);

List cl = entry.getCurrentSteps();
for(int i=0;i<cl.size();i++)
{
HibernateCurrentStep step = (HibernateCurrentStep) cl.get(i);
logger.debug("get entry current steps["+i+"] = " + step);
logger.debug("get entry current steps["+i+"] = " + step.getEntry());

}

}
catch (Exception e)
{
e.printStackTrace();
assertTrue(false);
}

就会接受到如下的异常信息

net.sf.hibernate.LazyInitializationException: Failed to lazily initialize a collection - no session or session was closed
at net.sf.hibernate.collection.PersistentCollection.initialize(PersistentCollection.java:209)
at net.sf.hibernate.collection.PersistentCollection.read(PersistentCollection.java:71)
at net.sf.hibernate.collection.List.size(List.java:79)
at test.com.company.engine.workflow.store.ExtHibernateWorkflowStoreTest.testFindEntry(ExtHibernateWorkflowStoreTest.java:82)
........

net.sf.hibernate.collection.List
/**
* @see java.util.List#size()
*/
public int size() {
read();
return list.size();
}

这里的read()就会反向的再次读取数据库记录,目的是再次的Load出子记录来!可是这个时候你已经将Session关闭了!!所以Hibernate也就无法完成加载的任务,LazyInitializationException异常自然就扔了出来


但是现在就有一个问题:当list执行read()方法的时候,给它的session是什么?(答案肯定是你Open的那个session)但是hibernate如何确保session的唯一性呢?这个问题比较的BT,需要看SessionFactoryImpl这个构造函数到底怎么维护session的

Sticky post

Java基础问题:java.lang.String - intern()

原文: http://forum.javaeye.com/viewtopic.php?t=17912


搂主的问题问的含含糊糊:flyjie给出了非常详细的解释,
不过就是没有解释String实例化的特殊方面以及Intern()方法的含义

-------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
java代码:
String str ;

这样声明str它只支是一个对象的reference,不会产生实际的对象。如果没有初始化str,编译时便会发生错误。
java代码:
String str1=new String("test");
String str2 = "test";

str1是一个新的对象。new关键字的意思就是创建某个新的对象。而str2是一个对象的引用。 它们的内容相同,但内存地址是不一样的。 java中对象的引用存在Stack(栈)中,而对象由Heap(堆)分配空间。

3、引用==变量?  不一定
java代码:

  public class TestString {
public static void main(String[] args) {
String s1 = "test";
String s2 = new String("test");
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))
System.out.println("s1 equals s2");
else System.out.println("s1 not equals s2");
}
}

我们将 s2 用 new 操作符创建程序输出:s1 != s2 s1 equals s2.
java代码:

s2 = s2.intern();

在你加上这句话后,上面的程序输入:s1 == s2 s1 equals s2

而String a = "test" ; String b = "test" ; a == b 会返回true; 这里a="test"时创建一个在栈中的reference, b=test时jvm发现栈中已存在名为"test"的字符串,直接引用。结论:String 是个对象,要对比两个不同的String对象的值是否相同明显的要用到 equals() 这个方法. 而== 比较的是内存地址的值。

4、private final String a = "test", 这个a属于常量,存放在常量存储空间(CS)中。

5、建议你看看<<深入浅出java虚拟机>>一书。

-------
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------



总结

1. 在学习JAVA的时候就知道==比较的是内存地址.而equals比较的是内存地址对应的值!(可是还是有很多的人问来问去的,真不知道他们JAVA的基础课程是怎么学的?!)

2. JAVA所有的对象都是存放在堆中的!你获取的"对象"仅仅只是对象的引用而已

3. String是比较特殊的对象,特殊在
3.1 > String a = new String("test") -此时你是在堆中实例化了一个字符串对象
3.2 > String b = "test"-此时JVM会先去堆中寻找这样的对象;如果有就返回此对象的引用;如果没有就重新实例化一个这样的对象!基于这样的一个过程所以JAVA要求String不可以更改值的。

3.3 >intern()方法就是试图完成这样的一个寻找过程
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.



这里有一份详细的参考资料:

关于Java栈与堆的思考 http://www.javafan.net/article/20051123115654293.html

Sticky post

Java Nested class

URL : http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html

网上的一些关于内部类的概念是不完整的,还是看看SUN的文档上的标准答案。
...

Like other members, a nested class can be declared static (or not). A static nested class is called just that: a static nested class. A nonstatic nested class is called an inner class.

- Nested class分为静态Static nested class 的和非静态的 inner class, 在SUN的眼里只有Nested Class!!


As with static methods and variables, which we call class methods and variables, a static nested class is associated with its enclosing class. And like class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class — it can use them only through an object reference.

- 静态的Static nested class是不可以直接调用它的外部类enclosing class的,但是可以通过外部类的引用来调用,就像你在一个类中写了main方法一样。
...

As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's instance variables and methods. Also, because an inner class is associated with an instance, it cannot define any static members itself.

-非静态类inner class 可以自由的引用外部类的属性和方法,但是它与一个实例绑定在了以其,不可以定义静态的属性、方法(这点不是很理解,可能需要看JVM的类实现)

...
class EnclosingClass {
...
class InnerClass {
...
}
}


The interesting feature about the relationship between these two classes is not that InnerClass is syntactically defined within EnclosingClass. Rather, it's that an instance of InnerClass can exist only within an instance of EnclosingClass and that it has direct access to the instance variables and methods of its enclosing instance. The next figure illustrates this idea.


-图形化的嵌入类与外部类的关系

Sticky post

java内部类

java可以实现内部类,但对于内部类的使用以前根本就不关心,
今天需要实现一个基于Spring的工厂时,才觉得可以使用内部类来实现,而产生这样的需求是由于以下的原因
1。我不希望调用者看到具体的工厂实现
2。工厂的概念定义有重复,Spring已经为我们实现了一些工厂比如:XmlBeanFactory,而我只是希望在其上包装成具体应用需要的工厂,而"脱离"Spring的素服-如果可以的话!:)
3。对外提供的GearWheelBeanFactory工厂,只提供一些静态的方法

解决
问题二、问题三
定义的重复,比如Spring已经提供了它安装好的工厂,比如XmlBeanFactory,但我希望在其上再实现应用要的工厂,更具有说服力的就是我需要实现的工厂比GearWheelBeanFactory需要实现特定的接口,并且需要"松耦合"Spring这样的框架(比如以后希望使用PicoContainer Framework)这样就需要建立应用级别的工厂抽象-Factory

问题一
问题一是产生内部类这个实现的最基础的动机,并不希望用户看到这样的Factory实现类?为什么不希望调用者看到?-主要是希望调用者面对的仅仅只是GearWheelBeanFactory的纯净的应用工厂,看到了Factory,一些聪明的调用就可以"看到"(反编译)具体的实现!!

所以采用内部类实现看来就是我要的,以下是GearWheelBeanFactory0.1 版本的实现

public class GearWheelBeanFactory
{

/**
* Static method: get bean from factory
* @param _configFile
* @param _beanName
* @return
*/
public static Object getBean(String _configFile , String _beanName)
{
Factory f = new Factory(_configFile);
return f.getBean(_beanName);
}



/*
* Inner class ; but you can't refers to it by out class
* even you didn't know whether is exist! and you can't find it too
*
*/
private static class Factory
{
/* */
private ClassPathResource config = null;

/* */
private XmlBeanFactory agent = null;

public Factory(String _configFile)
{
config = new ClassPathResource(_configFile);
agent = new XmlBeanFactory(config);
}
/**
* Get Bean by name
* @param beanName
* @return
*/
public Object getBean(String _beanName)
{
return agent.getBean(_beanName);
}
}

}


总结:
内部类的一些使用范例在网络上有很多,而且也有很多的注意事项
可是最关键的是它的适用范围,而这一切都源于你是否希望将内部类(不是匿名内部类)暴露给外界?!

内部类的实现比较BT,让人费解!!尤其是静态内部类、匿名内部类!
Faint!!看来要好好补补了

Sticky post

初识Java内部类

url : http://www.frontfree.net/view/article_704.html

初识Java内部类
原创:morgan83 2002年12月31日



提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比。内部类从表面上看,就是在类中又定义了一个类(下文会看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,但是随着对它的深入了解,你会发现Java的设计者在内部类身上的确是用心良苦。学会使用内部类,是掌握Java高级编程的一部分,它可以让你更优雅地设计你的程序结构。下面从以下几个方面来介绍:

第一次见面
public interface Contents {
int value();
}

public interface Destination {
String readLabel();
}

public class Goods {
private class Content implements Contents {
private int i = 11;
public int value() {
return i;
}
}

protected class GDestination implements Destination {
private String label;
private GDestination(String whereTo) {
label = whereTo;
}
public String readLabel() {
return label;
}
}

public Destination dest(String s) {
return new GDestination(s);
}
public Contents cont() {
return new Content();
}
}

class TestGoods {
public static void main(String[] args) {
Goods p = new Goods();
Contents c = p.cont();
Destination d = p.dest("Beijing");
}
}

在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了——隐藏你不想让别人知道的操作,也即封装性。
同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:

outerObject=new outerClass(Constructor Parameters);

outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);


注意在创建非静态内部类对象时,一定要先创建起相应的外部类对象。至于原因,也就引出了我们下一个话题——

非静态内部类对象有着指向其外部类对象的引用
对刚才的例子稍作修改:

public class Goods {

private valueRate=2;

private class Content implements Contents {
private int i = 11*valueRate;
public int value() {
return i;
}
}

protected class GDestination implements Destination {
private String label;
private GDestination(String whereTo) {
label = whereTo;
}
public String readLabel() {
return label;
}
}

public Destination dest(String s) {
return new GDestination(s);
}
public Contents cont() {
return new Content();
}
}


修改的部分用蓝色显示了。在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。我们发现,value()可以访问valueRate,这也是内部类的第二个好处——一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。

有人会问,如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了,怎么办?没事,Java里用如下格式表达外部类的引用:

outerClass.this

有了它,我们就不怕这种屏蔽的情况了。

静态内部类
和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。这实际上和C++中的嵌套类很相像了,Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用这一点上,当然从设计的角度以及以它一些细节来讲还有区别。

除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。这也算是两者的第二个区别吧。

局部内部类
是的,Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。

public class Goods1 {
public Destination dest(String s) {
class GDestination implements Destination {
private String label;
private GDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new GDestination(s);
}

public static void main(String[] args) {
Goods1 g= new Goods1();
Destination d = g.dest("Beijing");
}
}


上面就是这样一个例子。在方法dest中我们定义了一个内部类,最后由这个方法返回这个内部类的对象。如果我们在用一个内部类的时候仅需要创建它的一个对象并创给外部,就可以这样做。当然,定义在方法中的内部类可以使设计多样化,用途绝不仅仅在这一点。

下面有一个更怪的例子:

public class Goods2{
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
}

public void track() { internalTracking(true); }

public static void main(String[] args) {
Goods2 g= new Goods2();
g.track();
}
}


你不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类TrackingSlip和其他类一样同时被编译,只不过它由它自己的作用域,超出了这个范围就无效,除此之外它和其他内部类并没有区别。

匿名内部类
java的匿名内部类的语法规则看上去有些古怪,不过如同匿名数组一样,当你只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:

new interfacename(){......}; 或 new superclassname(){......};

下面接着前面继续举例子:

public class Goods3 {
public Contents cont(){
return new Contents(){
private int i = 11;
public int value() {
return i;
}
};
}
}

这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象,看上去的确十分简洁。

在java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:

frame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
});

有一点需要注意的是,匿名内部类由于没有名字,所以它没有构造函数(但是如果这个匿名内部类继承了一个只含有带参数构造函数的父类,创建它的时候必须带上这些参数,并在实现的过程中使用super关键字调用相应的内容)。如果你想要初始化它的成员变量,有下面几种方法:

如果是在一个方法的匿名内部类,可以利用这个方法传进你想要的参数,不过记住,这些参数必须被声明为final。
将匿名内部类改造成有名字的局部内部类,这样它就可以拥有构造函数了。
在这个匿名内部类中使用初始化代码块。
为什么需要内部类?
java内部类有什么好处?为什么需要内部类?

首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。

不过你可能要质疑,更改一下方法的不就行了吗?

的确,以此作为设计内部类的理由,实在没有说服力。

真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题——没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。

本文的目的只是向大家介绍一下内部类的概念以及使用方法,在后续文章里,将会针对本文中的内容举更多具体的例子,以及介绍如何使用内部类构建一个Applicaton Framework。

Sticky post

WorkFlow的事务回滚实现

Workflow Project 目前状态
版本 0.11

已经完成
1。完成了接口1 和接口2 的方法
2。完成接口3的默认实现
3。完成事务回滚的实现方法-等待测试

未完成
1。接口3的注册与实例化解决方案
2。应用的并发访问问题以及解决数据的脏读问题
3。与具体的某个应用挂接并测试


-事务的回滚
OSWorkFlow的事务回滚是依靠WorkflowContext这个接口来实现的,在New出某个WorkFlow的时候需要声明WorkflowContext的实现类,一般会采用uper.context = new GearWheelWorkFlowContext(_caller);方法
比如这样实现:

public GearWheelWorkFlow(String _caller)
{
super.context = new GearWheelWorkFlowContext(_caller);
}

但OSWorkFlow的WorkflowContext的默认实现BasicWorkFlowContext中根本没有实现setRollbackOnly方法,也就没有了参考的可能

再看看这个接口的其他实现类也都是建立在JTA这样的跨Session的事务服务上,比如它的EJB的实现也是要调用容器提供的JTA实现才行!而JTA的实现比如要JNDI到数据库池,此时的应用光JTA+JNDI就已经宣布 -这样的例子必须生存在应用服务器的环境下!!

可是,我不死心,我记得Hibernate可以实现本地事务,也就是依靠JDBC本身的事务处理能力,而要实现这样的功能就需要在数据库连接的获取上下一些功夫,也就是要保证回滚的数据库连接必须是获取时的那个连接,而存储连接就成了一个需要首先解决的问题。

解决数据库连接的存储问题
目前存储数据库连接除了依靠静态类外,还有一个通用的方法ThreadLocal类,这样获取数据库连接的方法写成了如下的形式:

package com.company.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
public class DB2ConnectFactory
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(DB2ConnectFactory.class);
private static ThreadLocal threadLocal = new ThreadLocal();
//~
private Connection connect = null;
private Statement state = null;
private ResultSet result = null;
private boolean closeConnWhenDone = false;
//~
private String url = "jdbc:db2:WORKFLOW";
private String user = "";
private String password = "";
private String driverClassName = "COM.ibm.db2.jdbc.app.DB2Driver";

public DB2ConnectFactory() throws SQLException
{
this.init();
}

/**
* 获取数据库连接
* @return
* @throws SQLException
*/
public Connection getConn() throws SQLException
{
return (Connection)threadLocal.get();
}

/**
* 初始化数据库,并在缓冲中注册数据库连接
* @throws SQLException
*/
private void init() throws SQLException
{
try
{
// Get connect object
Class.forName(driverClassName);
closeConnWhenDone = true;

connect = DriverManager.getConnection(url, user, password);
state = connect.createStatement();

//Register the connection object in the threadlocal
threadLocal.set(connect);
}
catch (Exception e)
{
e.printStackTrace();
throw new SQLException(e.getMessage());
}
}

}

解决事务回滚
刚才说了需要实现WorkflowContext接口
package com.company.engine.workflow;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.log4j.Logger;
import com.company.common.DB2ConnectFactory;
import com.opensymphony.workflow.WorkflowContext;
public class GearWheelWorkFlowContext implements WorkflowContext
{
/**
* Logger for this class
*/
private static final Logger logger = Logger.getLogger(GearWheelWorkFlowContext.class);

private static ThreadLocal threadLocal = new ThreadLocal();

// ~ Instance fields
// ////////////////////////////////////////////////////////
private String caller;
// ~ Constructors
// ///////////////////////////////////////////////////////////
public GearWheelWorkFlowContext(String caller)
{
this.caller = caller;
}
// ~ Methods
// ////////////////////////////////////////////////////////////////
public String getCaller()
{
return this.caller;
}
/**
* Tranaction : Set Roll back
* @throws SQLException
*/
public void setRollbackOnly()
{
Connection connect = null;
try
{
DB2ConnectFactory factory = new DB2ConnectFactory();
connect = factory.getConn();
if(connect != null) connect.rollback();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
this.clostConnection(connect);
}
}

private void clostConnection(Connection connect)
{
try
{
if(connect != null) connect.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

总结
1。我们可以看到由于接口中setRollbackOnly没有异常的声明,方法中即使抛出了异常也要自己"忍了"!看来良好的接口声明其实是非常重要的。

2。而且需要重载原来JDBCWorkflow 中的cleanup方法,将其中的代码屏蔽掉!数据库的关闭放在了setRollbackOnly访访的finally中,原因就是由于我们要统一的管理数据库连接所引发的,我们不能够在WorkFlowStore的每一个方法执行完毕后就关闭连接,因为这样的话你根本没有了事务回滚的可能,所以此时的连接需要在WorkflowContext中来处理。


感触
OSWorkFlow的实现方法并不是像网上所说的那样的优秀和文雅,更像是一个未完成任务的"半成品",Heni被网上鼓吹为大牛,但一个不写注释和文档的人,根本称不上什么大牛!
OSWorkFlow更多的是实现了一个微内核,而它的用户模式是与OSUser这样的框架耦合的(偶已经将这样的耦合打开了,也就是接口3的定义),它的相关数据是与PropertySet框架耦合的(也就是接口2的定义),而且采用OSWorkFlow要经过很原始的修改(比如我实现了DB2下的WorkFlowStore的实现)。

不过也好即使以后不采用OSWorkFlow,自己实现一个这样的引擎也应该没有什么问题的,有时间了我倒是很想看看别的工作流的产品。

Sticky post

workflow接口划分

workflow接口划分

1。应用接口 Application Interface
--interface1 工作流自身提供的服务接口
--interface2 工作流与应用之间的接口(主要是提供相关数据的调用接口)

2。扩展接口 PlugIn Interface
--interface3 工作流与组织机构之间的接口
--interface4 工作流与其他工作流之间的接口

将接口划分成应用接口与扩展接口主要是依据工作流与相关应用的调用关系,比如工作流与组织机构之间,是工作流调用组织机构中的人员信息,所以主动者是WORKFLOW、被动方是组织机构,所以应该采用扩展接口来实现

在扩展接口上应该采用Adapter模式,从而使工作流不局限于某个特定的实现

目前的进展
0。Application Interface接口已经基本实现了
PlugIn Interface接口目前基本完工,但OSWorkflow的实现实在是非常的丑陋,需要更改的地方太多,而且对于Interface3不可以使用它采用的User / Group模型(而且它使用了OSUser这个框架,对于多数的应用程序基本可以说不适合,而且它的User类竟然是Final ?!而且我发现它的很多类的属性都是Protected!也就是说除了他们自己根本没有办法扩展,即使扩展也是很丑陋的方式)

1。现在最大的问题是它的WorkStore接口的扩展,我采用DB2的方式实现了它的接口,但这样的方式会与DB2绑定在一起,如果自己写实现就要根据不同的DB采用不同的SQL语言-也就是不同的方言策略?!而且考虑到性能估计不是什么好主意,看来明天需要更换成HibernateWorkStore的形式,这样工作流的持久层接口将工作在Hibernate之上,看来很完美的解决了这个问题。

2。而且我扩展了它的PropertySet,使其不再依靠JNDI寻找DataSource,而是通过嵌入在程序内部采用JDBC的形式寻找数据库连接,这样我就不必为了验证一个问题去建立那该死的数据库缓冲池了(而且JNDI的形式也就不可避免的要用到容器,太重了!)

3。我编写了UserGroupCondition的实现类,这个类的作用就是调用Interface3的方法,从而判断某个用户是否属于某个组(现在的做法是让WorkStore实现Interface3的偷懒办法,但很乱,看来还是要写一个Adapter去实现interface3才对!)

4。目前工作流引擎的工厂类已经实现完工并测试通过。


用了近一个月的时间完成了这些工作,看起来很少但是基本上大量的时间花费在熟悉工作流规范、WFMC标准、以及学习和扩展OSWorkflow接口上,不过对OSWorkflow的实现基本上掌握了,如果抛开OSWorkflow自己也可以采用自己的方式去实现,或者会考虑使用Spring的方式(Interface3的Adapter不行就采用Spring实现)。

BTW:
OSWorkflow的实现其实比较的丑陋!而且编码根本没有什么规范,接口的定义也是天马行空,看来Heni除了他的大嘴外应该好好的提高自己的技术修养。-实在不敢恭维这位"大师"的编码水平!

Sticky post

OSPropertySet - PropertySet

OSPropertySet的最新版本是1.3 Date: 9/22/2003 ,不要使用OSWorkflow中自带的OSPropertySet.jar ,主要是因为它的版本为propertyset-1.3-21Apr04,甚至里面的接口PropertySet竟然私自更换了(这个版本的remove()方法是抽象的,而新版本已经将这个方法命名为Public!!)

Sticky post

JDBC2.0 - Tomcat5.0配置DB2数据库池

在Google查询了一个下午,寻找到的资料要麽运行不了,要麽就是没有DB2的配置信息,以下是参考OSWorkflow以及Tomcat的关于Oracle的配置信息,测试通过的实例
测试环境
OS:WindowxXP Professional
DB:DB2 8.1
Web Server: Tomcat5.0



1。首先要下载DB2的驱动程序 db2java.zip ,将db2java.zip更改成db2java.jar(因为tomcat只认识*.jar文件)拷贝到%TOMCAT%/common/lib下

2。修改%TOMCAT%/conf/server.xml 。寻找到<Host>项,添加如下的信息(copy from osworkflow)

<Context path="/osworkflow_example" docBase="osworkflow-2.7.0-example"
debug= "99" reloadable="true" crossContext="true"

verbosity="DEBUG">
<!- debug level is set to paranoid, to know what is happening,
turn it off once you do not need it ->

<Logger className="org.apache.catalina.logger.FileLogger"
prefix="OSWorkflow." suffix=".log" timestamp="true"/>
<!-
put log4j.jar into:
TOMCAT_ROOT/webapp/osworkflow-2.7.0-example/WEB-INF/lib
if you want to use it for logging
->

<Resource name="jdbc/DefaultDS" auth="Container"
type="javax.sql.DataSource"/>
<!- name="jdbc/DefaultDS" is used in other components of the
Example App, do not change it here! ->

<ResourceParams name="jdbc/DefaultDS">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<parameter>
<name>driverClassName</name>
<value>oracle.jdbc.driver.OracleDriver</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:oracle:thin:@yourserver.com:port:SID</value>
</parameter>
<parameter>
<name>username</name>
<value>your_database_user_name_here</value>
</parameter>
<parameter>
<name>password</name>
<value>your_password_here</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>20</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>10</value>
</parameter>
<parameter>
<name>maxWait</name>
<value>-1</value>
</parameter>
</ResourceParams>
</Context>

<!- OSWorkflow JNDI Data Source ->
3。在应用的web.xml文件中添加(其实这个步骤可以省略!)
<resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/workflow</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
4。编写测试文件**.jsp
 <%@page import="javax.naming.InitialContext"%><%@page import="java.sql.*"%><%@page import="javax.sql.DataSource"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML><HEAD> New Document <META NAME="Generator" CONTENT="EditPlus"><META NAME="Author" CONTENT=""><META NAME="Keywords" CONTENT=""><META NAME="Description" CONTENT=""></HEAD>
<BODY>
<%try{InitialContext ctx=new InitialContext();DataSource ds=(DataSource)ctx.lookup("java:/comp/env/jdbc/workflow");Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();out.println("Connection = " + conn);out.println("Statement = " + stmt);}catch(Exception ex) { out.print(" error , info = "+ ex.getMessage()); ex.printStackTrace();}%>
</BODY></HTML>
注意:
如果是Tomcat5.0之前的版本,可能需要在执行1之前
Jakarta-Commons DBCP 1.0 Jakarta-Commons Collections 2.0 Jakarta-Commons Pool 1.0
将上面的jar包下载到%TOMCAT%/common/lib下


总结:
1。DB2的连接池其实和Oracle的配置没有区别,关键是Tomcat的比较麻烦

2。在遇到问题的时候最好参考标准的文档,而不是网上的垃圾文章,这样可以省去很多的弯路
February 2012
S M T W T F S
January 2012March 2012
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