Categories

Links

osworkflow 整合spring和hibernate提示

SpringTypeResolver

 

这里最先需要介绍的是com.opensymphony.workflow.util.SpringTypeResolver这个类使在spring中配置FunctionProvider,Validator,Condition等成为可能

 

<bean name="typeResolver" class="com.opensymphony.workflow.util.SpringTypeResolver">

</bean>

 

你现在你可以在spring配置FunctionProvider

<bean name="myfunction" class="net.sohoworks.activemerchant.b2b.test.MyFunction"/>

 

 

在工作流文件中ProviderFunction,Validator,Conditiontype设置为spring,并设置一个参数名为bean.name其值为spring中配置的bean 名称

<post-functions>

    <function type="spring">

              <arg name="bean.name">

                 myfunction

              </arg>

            </function>

</post-functions>

 

spring配置Workflow的实现类的resolver属性最后面有例子

 

使用HibernatePropertySet

 

如果你使用HibernatePropertySet在目前的实现中你就不能使用PropertySet存储proeprties,xml,object等数据类型。

classpath 的根放置一个proeprtyset.xml文件

<?xml version="1.0" encoding="gb2312" ?>

<propertysets>

    <propertyset name="hibernate" class="com.opensymphony.module.propertyset.hibernate.HibernatePropertySet">

    </propertyset>

 

</propertysets>

 

配置Hibernate

 

一般我们需要配置一个DataSource,SessionFactoryBean,其中,SessionfActoryBeanmappingResoures需要声明HibernatePropertySet需要的hbmHibernateWorkflowStore需要的hbm 文件一个例子如下

<bean id="dataSource"

        class="org.springframework.jdbc.datasource.DriverManagerDataSource">

        <property name="driverClassName">

            <value>net.sourceforge.jtds.jdbc.Driver</value>

        </property>

        <property name="url">

            <value>jdbc:jtds:sqlserver://localhost/b2b;SelectMethod=cursor;charset=CP936</value>

        </property>

        <property name="username">

            <value>sa</value>

        </property>

        <property name="password">

            <value/>

        </property>

 

    </bean>

    <bean id="sessionFactory"

        class="org.springframework.orm.hibernate.LocalSessionFactoryBean">

        <property name="dataSource">

            <ref local="dataSource"/>

        </property>

        <property name="hibernateProperties">

            <props>

                <prop key="hibernate.show_sql">true</prop>

                <prop key="hibernate.use_outer_join">true</prop>

                <prop key="hibernate.dialect">

                       net.sf.hibernate.dialect.SQLServerDialect

                </prop>

 

            </props>

        </property>

        <property name="mappingResources">

            <list>

          

                <value>com/opensymphony/workflow/spi/hibernate/HibernateWorkflowEntry.hbm.xml</value>

                <value>com/opensymphony/workflow/spi/hibernate/HibernateHistoryStep.hbm.xml</value>

                <value>com/opensymphony/workflow/spi/hibernate/HibernateCurrentStep.hbm.xml</value>

                <value>com/opensymphony/module/propertyset/hibernate/PropertySetItemImpl.hbm.xml</value>

 

            </list>

        </property>

    </bean>

 

创建工作流文件

 

 

创建workflows.xml文件

这个例子中没有声明type这样系统将从classpath读取工作流文件

<workflows>

  <workflow name="myworkflow" type="" location="myworkflow.xml"/>

</workflows>

 

配置 SpringHibernateWorkflowStore

 

 <bean name="workflowStore" class="com.opensymphony.workflow.spi.hibernate.SpringHibernateWorkflowStore">

        <property name="sessionFactory">

            <ref bean="sessionFactory"/>

        </property>

    </bean>

配置 SpringWorkflowFactory

 

    <bean name="workflowFactory" class="com.opensymphony.workflow.loader.SpringWorkflowFactory" init-method="init">

        <property name="resource"><value>workflows.xml</value>

 </property>

 

配置SpringSpringConfiguration

 

<bean id="configuration" class="com.opensymphony.workflow.config.SpringConfiguration">

<property name="factory"><ref bean="workflowFactory"/></property>

<property name="store"><ref bean="workflowStore"/>

</bean>

 

 

创建工作流对象

 

    <bean name="workflow" class="com.opensymphony.workflow.basic.BasicWorkflow">

        <constructor-arg><value>jjx</value>

 </constructor-arg>

        <property name="configuration"><ref bean="workflowConfiguration"/></property>

        <property name="resolver"><ref bean="typeResolver"/></property>

    </bean>

 

测试

 

Workflow wf=(Workflowcontext.getBean("workflow");

wf.Initialize("myworkflow",999,null);

....

[2005-04-02 23:15:03 | Author:jiangjianxiao ] [] 3 comments

从一段webwork的应用代码谈起

这段代码来自 一个讨论 http://www.hibernate.org.cn/viewtopic.php?t=6103&postdays=0&postorder=asc&highlight=webwork&start=15 ,readonly提出了一种看似可行的方案,但这个方案存在的一些陷井

 

一个Action,有删节

public abstract class AbstractUserAction extends ActionSupport {

 

     protected User user;

     protected IUserManager userManager;

     public void setUser(User user) {

         this.user = user;

     }

 

     public User getUser() {

        if (user!=null) return user;

         if (ActionContext.getContext().getParameters().get("userId")!=null){

            user=getUserManager().findById((String)ActionContext.getContext().getParameters().get("userId"));

                   user=new User();

         }

        else

        {

            user=new User();

        }

         return user;

        

     }

}

 

外部的ActionContext

 

webwork的测试中总是传递一个extraContextmapaction执行后这个extraContext将不存在的

在下面的测试中,ViewUser action只是简单的继承AbstractUserAction

 

 

     Map parameters=new HashMap();

        parameters.put("userId","01000000031");

 

        Map extraContext=new HashMap();

        extraContext.put(ActionContext.PARAMETERS,parameters);

        ActionProxy proxy=ActionProxyFactory.getFactory().createActionProxy("","viewuser",extraContext);

        proxy.setExecuteResult(false);

        proxy.execute();

 

 

        AbstractUserAction action=(AbstractUserAction)proxy.getAction();

     Assert.assertTrue(action.getUser().getUserName().equals("jjx"));

 

注意最后一句测试代码,将会有错误 ,实际上是不能够运行

 

错误有两个原因造成,第一是可能没有调用getUser()(如果测试时没有存取ognl表达式),因此user为null,第二extraContext 已经不存在,因此ActionContext.getContext().getParameters()将返回null

 

ActionContext.getContext().getParameters()

 

显然,如果需要传递一个userId,在测试时你简单的写上

parameters.put("userId","01000000031");

然而,对于一个url来说,如,http://localhost:8080/viewuser.action?userId=01000000031

 

当你使用ActionContext.getContext().getParameters().get("userId")时,你却取到的不是字符串,而是字符串数组(因为web的特征造成的),因此,上面的代码在测试时没有错误,在web中运行时将会有一个class cast错误

 

因此,显然让webwork自动装配参数/避免直接查询参数是一个好主意,这样就不会 遇到参数转换的问题,并使测试和实际运行保持一致性

 

 

 

参数装配的无序性

 

按照上面的建议,我们将userId声明为一个属性

ParametersInterceptor 拦截器在装配时根据参数在HashMap中的顺序进行装配,但是特殊的是时候,我们希望他按一定的顺序装配,如下面的代码

 

private String userId;

 

public User getUser(){

   return getUserManager().findById(userId);

}

 

如果传递了userId,user.userName,user.fullName等参数我们显然希望拦截器首先装配userId这样user.userName,user.fullName的装配才会成功

 

在贴子中,有两种方案,一种是修改params拦截器,一种是手动拷贝。这两种都不是好的方法。

 

手动拷贝是个多余的过程,而使用拦截器,显然需要解决这些问题:主键的命名的不确定性和主键可能是复合主键(即有多个)

 

可以通过spring来创建一个可配置的参数拦截器,但这个显然需要在spring中进行大量的配置,基本上是一个实体一个参数拦截器,因此也不可取。

 

我的解决方案是hack StaticParameterInterceptor

 

一个action可以在配置中定义的静态的参数值,如

 <!-- 传递 userId参数-->

          <action name="doprofile"

              class="net.sohoworks.activemerchant.action.ViewUser">

            <param name="userId">123456</param>

              <result name="success" type="dispatcher">

                   profile.jsp

              </result>

         </action>

 

xwork中默认实现中这个值是一个不变的字符串我们通过引入%{}来获取在ActionContext中的值因此就有了这样的写法

 <!-- 传递 userId参数-->

         <action name="doprofile"

              class="net.sohoworks.activemerchant.action.ViewUser">

            <param name="userId">%{#parameters.userId}</param>

              <result name="success" type="dispatcher">

                   profile.jsp

              </result>

         </action>

 

将问题就简单的解决了

 

<interceptors>

        <interceptor name="static-params2" class="net.sohoworks.activemerchant.interceptor.StaticParametersInterceptor"/>

        <interceptor-stack name="defaultStack2">

            <interceptor-ref name="static-params2"/>

                   <interceptor-ref name="params"/>

                   <interceptor-ref name="conversionError"/>

            <interceptor-ref name="validation"/>

                   <interceptor-ref name="workflow"/>

        </interceptor-stack>

    </interceptors>

         <default-interceptor-ref name="defaultStack2" />

 

 

代码基本没有自己的东西,只是简单的组合了webwork的现在元素

/*

 * Copyright (c) 2002-2003 by OpenSymphony

 * All rights reserved.

 */

package net.sohoworks.activemerchant.interceptor;

 

import com.opensymphony.xwork.Action;

import com.opensymphony.xwork.ActionContext;

import com.opensymphony.xwork.ActionInvocation;

import com.opensymphony.xwork.interceptor.AroundInterceptor;

import com.opensymphony.xwork.config.entities.ActionConfig;

import com.opensymphony.xwork.config.entities.Parameterizable;

import com.opensymphony.xwork.util.OgnlValueStack;

 

import java.util.Iterator;

import java.util.Map;

 

 

/**

 * Populates the Action with the static parameters defined in the Action

 * configuration by treating the Action as a bean.  If the  Action is

 * {@link Parameterizable}, a map of the static parameters will be also be

 * passed directly to the Action.

 * <p>

 * Parameters are defined with &lt;param&gt; elements within the Action

 * configuration.

 *

 * @author $Author: jcarreira,,jjx$

 * @version $Revision: 1.4 $

 */

public class StaticParametersInterceptor extends AroundInterceptor {

    public static String translateVariables(String expression, OgnlValueStack stack) {

        while (true) {

            int x = expression.indexOf("%{");

            int y = expression.indexOf("}", x);

 

            if ((x != -1) && (y != -1)) {

                String var = expression.substring(x + 2, y);

 

                Object o = stack.findValue(var, String.class);

 

                if (o != null) {

                    expression = expression.substring(0, x) + o + expression.substring(y + 1);

                } else {

                    // the variable doesn't exist, so don't display anything

                    expression = expression.substring(0, x) + expression.substring(y + 1);

                }

            } else {

                break;

            }

        }

 

        return expression;

    }

    //~ Methods ////////////////////////////////////////////////////////////////

 

    protected void after(ActionInvocation invocation, String result) throws Exception {

    }

 

    protected void before(ActionInvocation invocation) throws Exception {

        ActionConfig config = invocation.getProxy().getConfig();

        Action action = invocation.getAction();

 

        final Map parameters = config.getParams();

 

        if (log.isDebugEnabled()) {

            log.debug("Setting static parameters " + parameters);

        }

 

        // for actions marked as Parameterizable, pass the static parameters directly

        if (action instanceof Parameterizable) {

            ((Parameterizable) action).setParams(parameters);

        }

 

        if (parameters != null) {

            final OgnlValueStack stack = ActionContext.getContext().getValueStack();

 

            for (Iterator iterator = parameters.entrySet().iterator();

                    iterator.hasNext();) {

                Map.Entry entry = (Map.Entry) iterator.next();

                String key=    entry.getKey().toString();

                String value=(String)entry.getValue();

                if (value.indexOf("%{")!=-1 && value.indexOf("}")!=-1)

                    stack.setValue(key,translateVariables(value,stack));

                else

                    stack.setValue(key, entry.getValue());

            }

        }

    }

}

 

[2005-03-30 23:48:09 | Author:jiangjianxiao ] [] 1 comments

ThreadStatic 变量的初始化

被标记为ThreadStaticAttribute的变量不在线程之间共享,相当于java中的theadLocal,实际上使用的是com中最常见的tls概念,因此,在多线程应用程序中,应该使用如下初始化方式,以静态变量为例

 

[ThreadStatic]

private static IDictionary resourceHolder;

IDictionary ResourceHolder{

    get{

    if (resourceHolder==null)

        resourceHolder=new Hashtable();

    return resourceHolder;

    }

}

 

应该不必使用synclock/lock之类的语句,因为这不是线程共享变量

 

如果你使用

private static IDictionary resourceHolder=new Hashtable();

 

或是

 

private static IDictionary resourceHolder;

static 静态构造函数(){

 

   resourceHolder=new Hashtable();

}

则显然只初始化了第一个线程中的变量第二、第三个线程中该变量没有被初始化结果出现异常

 

我的数据框架中DataSourceUtils 类中显然犯了这个错误,如果用了这些代码,希望能自行改正过来,目前我没有更新那些代码的打算。

 

vb6中,所有在模块级的变量都可以看成是ThreadStatic的,不过vb6会在每个线程开始时自动调用main过程,因此,你可以将初始化过程放在这个地方,也可以使用我讲的第一种方式。原理是类似的

 

这里的说明我尚未到官方的说明(可能是我没有发现),可能有不正确的地方(目前我对多线程理解不够,因为用的不多,看来以后要恶补这方面的东西了),如果有错误,希望能给我指出来.

 

btw

写这篇blog时,在cnblogsnhibernate搜了一下,看到

http://cnblogs.com/jiezhi/archive/2005/01/17/92004.aspx

 

引用:

 

当有多个线程的时候,ThreadStaticAttribute的变量被第一个线程初始化后,其它的线程访问到的都是null,而每个HttpRequest则可能有多个线程为其服务,因而有人称ThreadStatic is evil,对于此,可以参考这个Blog。考虑到上述原因,在Web Form中使用TLSThreadStatic属性是不合适的

 

显然,这段话是错误的,在spring(java)中处理hibernatesessionjdbcconnection都是用ThreadLocal的,而且我现在的spring.netnhibernate 集成也是这样干的(这个集成我将在这两周内整理出来给大家做个参考)

 

 

spring.web中的DataBinding的错误处理有些问题,如果你的DataModel在获取时出现错误,则这个错误只在第一次出现,其后每次均出现数据绑定的错误。

其原因在于InitializeBinding的判断条件

错误几乎都在targetExp初始化的时候触发,因此在第二次时, sourceExp已经被初始化,所以这个过程被跳过了,在数据绑定时才抛出异常

 private void InitializeBinding(object source, object target)

        {

            // initialize expressions first time method is called

            if (sourceExp == null)

            {

            sourceExp = new NavigationExpression(source, _propertyName);

                    targetExp = new NavigationExpression[_bindingTarget.Length];

               

                    for (int i = 0; i < _bindingTarget.Length; i++)

                    {

                        targetExp[i] = new NavigationExpression(target, _bindingTarget[i]);

                    }

           

            }

        }

因此我建议修改为

 private void InitializeBinding(object source, object target)

        {

            // initialize expressions first time method is called

            if (sourceExp == null)

            {

                sourceExp = new NavigationExpression(source, _propertyName);

            }

            if (targetExp==null)

                targetExp = new NavigationExpression[_bindingTarget.Length];

           

            for (int i = 0; i < _bindingTarget.Length; i++)

            {

                if (targetExp[i]==null)

                    targetExp[i] = new NavigationExpression(target, _bindingTarget[i]);

            }

   

        }

 

显然,这样处理的效率有所不足,但不会屏蔽原来的错误,大家有更好的方式可以指出来

 

[2005-03-19 21:17:33 | Author:jiangjianxiao ] [] 1 comments

Spring.Data 中的事务抽象

Spring.Data 中的事务抽象

 

以当前最新的cvs中的代码说明(2005-3-18)

 

显然,当前spring的事务实现要求具体的业务代码获得transaction对象,transaction一般附在ITransactionStatus的实现类中,你可以从从PlatformTransactionManager去主动获取(要求了解ITransactionDefinition)或是TransactionTemplate回调给你(同样要求了ITransactionDefinition),在声明性事务中,我当前只发现从TransactionAspectSupport的CurrentTransactionStatus中获取。

显然,当前的代码既无法改造原有的代码,就是新的代码要遵循这种模式,也是相当困难和难以通用。其原因,是因为没有像spring(java)版本一样,从抽象数据源接口出发。

 

结论是,尚无法使用

 

模式一:使用PlatformTransactionManager

 

IDbConnection con=new SqlConnection("server=(local);user id=sa;pwd=;database=zhengda");

            IPlatformTransactionManager transactionManager=new AdoPlatformTransactionManager(con);

       

            DefaultTransactionDefinition def = new DefaultTransactionDefinition();

            def.PropagationBehavior=TransactionPropagation.Required;

 

            ITransactionStatus status = transactionManager.GetTransaction(def);

 

            try

            {

                IAdoOperations adoTemplate=new SqlClientTemplate();

 

                adoTemplate.ExecuteNonQuery((IDbTransaction)status.Transaction,CommandType.Text, "Insert into operators (username,password,role) values ('xxx','xxx','xxx')");

                adoTemplate.ExecuteNonQuery((IDbTransaction)status.Transaction,CommandType.Text, "Insert into operators (username,password,role) values ('xxx','xxx','xxx')");

                transactionManager.Commit(status);

 

            }

            catch

            {

                transactionManager.Rollback(status);

                throw ;

            }

            finally

            {

       

                con.Close();

            }

 

 

 

模式二:使用TransactionTemplate

 

        [Test]

        public void TestTransactionTemplate()

        {

            IDbConnection con=new SqlConnection("server=(local);user id=sa;pwd=;database=zhengda");

            IPlatformTransactionManager transactionManager=new AdoPlatformTransactionManager(con);

       

            DefaultTransactionDefinition def = new DefaultTransactionDefinition();

            def.PropagationBehavior=TransactionPropagation.Required;

 

            TransactionTemplate template=new TransactionTemplate(transactionManager);

            try

            {

                template.Execute(new DoInTransaction(this.DoInTransactionImpl));

            }

            finally

            {

                con.Close();

            }

 

 

        }

        private Object DoInTransactionImpl(ITransactionStatus status)

        {

            IAdoOperations adoTemplate=new SqlClientTemplate();

            adoTemplate.ExecuteNonQuery((IDbTransaction)status.Transaction,CommandType.Text, "Insert into operators (username,password,role) values ('xxx','xxx','xxx')");

            adoTemplate.ExecuteNonQuery((IDbTransaction)status.Transaction,CommandType.Text, "Insert into operators (username,password,role) values ('xxx','xxx','xxx')");

            return null;

        }

 

模式三 声明性事务

 

声明性事务是由多个部分组成

 

·    隔离级

·    超时

·    只读

·    事务类别

·    递交或回退

其中

隔离级用ISOLATION_ 开头,System.Data.IsolationLevel定义了所有隔离级别,默认是Unspecified

事务类别使用PROPAGATION_开头,枚举类型TransactionPropagation定义了以下属性

·    Required

·    RquiredNew

·    Supports

·    Mandatory

·    NotSupported

·    Never

·    Nested

只读使用readOnly表示(注意大小写)

超时设置使用timeout_开头

 

递交和回退是说明在发生什么异常时进行事务递交和回退,用+为前缀表示递交,-表示回退,

默认,spring.data会将加上 ",-SystemException"作为回退规则

查看DefaultTransactionDefinition 的DefinitionDescription属性过程就可以了解基本的表达形式

 

下面是一个例子

PROPAGATION_Required,ISOLATION_Unspecified,timeout_30,readOnly,-MyException

 

让我们来看一个例子,这是配置文件 transaction.xml

 

<?xml version="1.0" encoding="utf-8" ?>

<objects>

    <!-- 需要一个连接对象 -->

    <object name="connection" type="System.Data.SqlClient.SqlConnection,System.Data">

        <property name="ConnectionString"><value>server=(local);user id=sa;pwd=;database=yellowpage</value></property>

    </object>

    <!-- 一个事务管理器 -->

    <object name="transactionManager" type="Spring.Ado.Transaction.AdoPlatformTransactionManager,Spring.Data">

        <constructor-arg><ref object="connection"/></constructor-arg>

    </object>

    <!-- service类进行声明性事务-->

<object name="service"     type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject,Spring.Data">

    <property name="PlatformTransactionManager"><ref object="transactionManager"/></property>

    <property name="Target"><ref object="serviceTarget"/></property>

    <property name="TransactionAttributes">

        <name-values>

            <add key="Insert*" value="PROPAGATION_Required,-MyException"/>

            <add key="Update*" value="PROPAGATION_Required"/>

            <add key="*" value="PROPAGATION_Required,readOnly"/>

      

    </name-values>

    </property>

</object>

<!--实际的service -->

<object name="serviceTarget"  type="YellowPage.Test.Service,YellowPage">

</object>

 

</objects>

 

 

简单的运行代码

    IApplicationContext context=new XmlApplicationContext("transaction.xml");

    IService service=(IService)context["service"];

    service.Insert();

 

下面是我们的测试类

namespace YellowPage.Test

{

 

    public interface IService{

        void Insert()   ;

        void Update();

        void GetList();

    }

    public class MyException :ApplicationException

    {

        public MyException ():base()

        {

           

        }

        public MyException(String message):base(message)

        {

           

        }

        public MyException(String message,Exception inner):base(message,inner)

        {

           

        }

    }

    public class Service : IService

    {

        private IDbConnection connection;

        public IDbConnection Connection

        {

            set

            {

                this.connection=value;

            }

        }

        public void Insert()

        {

            //如何获取活动的事务对象

            IDbTransaction trans=(IDbTransaction)TransactionAspectSupport.CurrentTransactionStatus.Transaction;

            IAdoOperations adoTemplate=new SqlClientTemplate();

            adoTemplate.ExecuteNonQuery(trans,CommandType.Text,"select * from users");

 

 

            //throw new MyException("insert error");

        }

 

        public void Update()

        {

            Debug.WriteLine("Update");

        }

 

        public void GetList()

        {

            Debug.WriteLine("GetList");

        }

    }

}

 

[2005-03-18 11:39:27 | Author:jiangjianxiao ] [] 2 comments

SBF .net 版本数据访问框架设计(草稿)

数据访问框架是ado.net的一个包装,它是orm的一种补充,同vb6版本一样,我们希望达到以下几个目标

数据源抽象

意思是不依赖具体的提供者,像SqlClient或是OracleClient,OleDb,Odbc

事务抽象

有两层意义,其一是通常我们在最初编程时不考虑事务,事实上,我们建议在dao中是不做事务处理的,事务处理通常在service层,甚至在 Action(SBFOperation)中进行。这样,在后期,我们就需要将一些不在事务的一个或多个dao方法连接到一个事务中。在.net中,这种目标的实现要我们多遵守一些规则,我们会在下面实现具体原因。

其次,就是能抽象本地事务和com+事务,你的系统应该能在两者之间切换

当然,我们的99% 的努力都放在本地事务上。实现第一个要求是我们的重点。

.net 2.0TransactionScope

显然TransactionScope能达到我们的需求,但是它在sql server2000上启动的是com+事务,显然,这并不是我们真正想要的结果

简化访问

提供一个类似于microsoft data acess block 之类的封装,但这个封装能支持上面的数据源抽象或事务抽象。

在我的实现中,借鉴了spring (java)版本的设计思想,但总的来说,这里的实现非常简单而有效,两个接口IdataSource,IadoOperations,一个实用类,DataSourceUtils,几个缺省实现:SqlClientDataSource,OledbDataSource,SqlClientTemplate 就是全部。

在具体使用代码中,只使用接口IdataSource,IadoOperations去进行具体的操作,实现类通过ioc注入

数据源抽象

有多种方法可以做到数据源抽象,通常使用工厂方法,这是ado.net 2.0的做法。

   DbConnection con = DbProviderFactories.GetFactory("System.Data.SqlClient").CreateConnection();

            con.ConnectionString = "server=(local);user id=sa;pwd=;database=mis2";

            con.Open();

我不喜欢工厂方法,我更喜欢spring java版本的数据访问方式:通过一个IdataSource接口来获得具体的数据库连接。这种方式像策略模式。

public interface IDataSource

{

int Timeout

           {

                    get;

           }

           IDbConnection GetConnection();

           bool ShouldClose(IDbConnection con);

}

通过实现SqlClientDataSource,OleDbDataSource,OracleDataSource,OdbcDataSource ,我们就可以隔离数据源的差异(实际上是数据库连接的差异)

这里,大家可以看到,我们还增加了ShouldClose,从spring (java)SmartDataSource抄袭而来的这个特性使我们能够自由的在程序中使用断开或不断开的连接而对程序本身没有影响。

事务抽象

我们知道.net的事务模型是需要传递一个IDbTransaction的实例,这样你的自动事务处理的代码和显式事务处理的代码是不同的:

 

自动事务处理代码

[code]

 

//获取连接

IdbCommand cmd=con.CreateCommand();

cmd.CommandText=””;

cmd.ExecuteNonQuery();

//关闭连接

[/code]

显式事务处理的代码

[code]

//获取连接

IdbCommand cmd=con.CreateCommand();

cmd.CommandText=””;

cmd.Transaction=trans; //trans可能是通过函数参数或是其它方式获取

cmd.ExecuteNonQuery();

//关闭连接

[/code]

这非常糟糕,因为通常我们刚开始编码时是不做事务处理的,而且作为一种原则[b]dao中是没有也是不应该有事务处理的[/b],事务是一种粗粒度的东西,它应该在服务或是服务更上的facade层,按这种方式,可能你一开始必须要按第二种方式编码,否则会在以后遭遇麻烦(当你将多个dao方法组合在一个业务逻辑中时)。

注:在SohoWorks.BusinessFramework.Data.SqlServer命名空间下的SqlServerTransactionManager可以使你不必一定要按第二种方式编码,它对原来的代码影响更小,不过当前,我们仅有sql server的实现,由于我们工作的数据库范围较小,我们现在还没有探讨其它实现的可能性

内幕:以sqlclient为例

 .net 1.1中,sqlconnection实际上是持有这个transaction(LocalTransaction属性持有对BeginTranaction过程产生的事务对象的一个弱引用,不过LocalTransaction属性是不公开的),因此我们想ms可以在CreateCommand或设置连接时传递这个transaction对象,而不用我们去做。当然,它没有做,接口上也没有约束这点,其它实现也不会这样做。

 

.net 2.0sqlconnection实际上不持有这个事务了,事实上.net 2.0的事务同.net 1.1有很大的差异,在.net 1.1中,Sqltransaction显式的向数据库发出begin transaction ,commit transaction等命令,而在.net中,一切都交由一个内部的类SqlInternalConnection去处理了。

当然,在第二种方法中,trans绝对不应该是通过函数参数来得到的,其实当你启动事务时,你应该将它保存在一个threadstatic的词典中,然后,在这个上下文的应用就可以获取这个transaction,在我们的业务框架中,这是通过DataSourceUtils来实现的

下面是具体使用的代码

 

 

[code]

                    IDataSource dataSource=new SqlClientDataSource("server=(local);user id=sa;pwd=;database=zhengda");

                   

                    IDbConnection con=DataSourceUtils.GetConnection(dataSource);

                    IDbCommand cmd=con.CreateCommand();

                    cmd.Transaction=DataSourceUtils.GetTransaction(dataSource);

 

                    IDataParameter p1=cmd.CreateParameter();

                    p1.ParameterName="@username";p1.Value="admin";

                    IDataParameter p2=cmd.CreateParameter();

                    p2.ParameterName="@password";p2.Value="123456";

 

 

                    cmd.CommandText="select username from operators where username=@username and password=@password";

                    cmd.Parameters.Add(p1);

                    cmd.Parameters.Add(p2);

                    IDataReader reader=cmd.ExecuteReader();

                    while (reader.Read())

                    {

                             //做一些事情

                    }

                    reader.Close();

                    DataSourceUtils.CloseConnectionIfNecessary(con,dataSource);

 

[/code]

 

注意DataSourceUtils.GetTransaction 可能返回null或是返回一个有效的事务对象,你不必关注这点,事务管理器的实现和DataSourceUtils之间有一种默契。

在我们的业务框架中,一般是通过IAdoOperations的实现类来做数据操作的,因此你不必考虑这点

[code]

                    IDataSource dataSource=new SqlClientDataSource("server=(local);user id=sa;pwd=;database=zhengda");

                   

                    IAdoOperations template=new SqlClientTemplate(dataSource);

                    IDataParameter[] parameters=new IDataParameter[2];

                    parameters[0]=template.CreateParamter("@username","admin");

                    parameters[1]=template.CreateParamter("@password","123456");

 

                    SafeDataReader reader=template.ExecuteReader("select username from operators where username=@username and password=@password",CommandType.Text,parameters);

                    while (reader.Read())

                    {

                             //做一些事情

                    }

                    reader.Close();

DataSourceUtils.CloseConnectionIfNecessary(con,dataSource);

 

[/code]

这种事务抽象的过程如下

当事务管理器开始事务时,将事务对象或连接对象放到一个threadStatic的词典中

从词典中获取事务对象,传给command

    递交事务时,从词典中获取事务对象,并递交

④回退时,从词典中获取事务对象,并回退

当事务递交和回退时,从词典中删除事务对象,并执行关闭连接过程

 

由于现在通常是用断开连接的方式工作,每个过程都会打开或断开连接,因此,实际上要对这个过程进行抽象,如果不这样做,怎么能保证事务在运行在几个过程中(想想看某一个过程在结束后把连接都关闭了,让事务怎么继续进行)

在我们的框架中,DataSourceUtilsgetConnectionCloseConnectionIfNecessary就做这些事情

①如果有一个事务存在,则从该事务中获取连接 (GetConnection)

②如果有一个事务存在,则不关闭连接(CloseConnectionIfNecessary)

 

adojdbc中,事务的执行是通过连接对象来执行的,这样,我们其实只是保存连接的实例,过程同上面类似,在vb6sbf框架实现中,我就是这样干的。在SqlServerTransactionManager实现中,也是通过保存连接的实例来进行的。

上面的几句话实际上就是spring(java)版本事务抽象的实质,当然,对于spring(java),实际的实现会更复杂。

我在vb6.net中也实现了这种抽象,当然,目前的状态仅满足自用,它不考虑嵌套事务的实现,我的目标主要是进行跨方法组合事务和抽象本地事务和com+事务的区别

对于spring.net data项目,我觉的非常的糟糕,spring.java版本的数据功能是建立在dataSource接口的基础上的,这个接口抽象了具体数据源的差异,但spring.net data项目一开始就背离了这个路线,目前,还是个半拉子的项目。所有当前模块中,最最差的

SafeDataReader

在上个例子中,大家可以看到我们这里返回的是SafeDataReader,这是数据访问框架的一部分,SafeDataReader CSLA中偷过来的概念,它主要解决以下问题:

l         按列名查询值

l         对于DBNull返回原始类型,如int32就返回0

在我们这个框架中,一个内部DataReaderClosed事件被添加,一个内部的属性Connection被添加,这样,当外部的reader.Close一旦被执行,AdoTemplate内部就会调用

DataSourceUtils.CloseConnectionIfNecessary(reader.Connection,dataSource);

这样,同我们整个处理框架保持一致。

当然,IdataReader实际上应该在内部持有创建它的command对象的,当然,也是隐藏着的

事务实现

事务的实现非常简单,首先,我们抽象一个接口ItransactionManager

然后,我们创建一个AdoTransactionManager,大家可以从代码中看到AdoTransactionManager如何同DataSourceUtils的内部方法进行交互,这部分代码可参见

SohoWorks.BusinessFramework.Data.AdoTransactionManager

 

我们还可以创建一个ComPlusTransactionManager,这个管理器非常简单

这部分代码参见 SohoWorks.BusinessFramework.Data.ComPlusTransactionManager部分

IadoOperations

IadoOperations完成第三个目标,IadoOperations针对不同数据源有不同的实现,这主要是由ado.net设计的另一个缺陷造成的,就是IDataAdapter,不像IdbCommand,只有IdataAdapter的创建依赖于具体的实现,如SqlDataAdapter IadoOperations定义了常见的操作如

ExecuteNonQuery

ExecuteReader

ExecuteScalar

ExecuteDataSet

UpdateDataSet

CreateDbCommand

CreateParameter

 

UpdateDataSet是最复杂的方法,有两种实现,1是使用CommandBuilder,二是通过传递insertcommand,updatecommand,deleteComand

使用CommandBuilder

public void TestUpdateDataSet1()

           {

                    IDataSource dataSource=new SqlClientDataSource("server=(local);user id=sa;password=;database=zhengda");

                    ITransactionManager trans=new AdoTransactionManager(dataSource);

                    trans.BeginTransaction();

                    IAdoOperations adoTemplate=new SqlClientTemplate(dataSource);

                    IDbCommand selectCmd=adoTemplate.CreateDbCommand("select * from operators");

                           DataSet ds=adoTemplate.ExecuteDataSet(selectCmd);

                   

 

                    ds.Tables[0].Rows.Add(new object[]{"jjx","123456","Admins"});

                    try

                    {

 

                             adoTemplate.UpdateDataSet(ds,selectCmd);

                             ds.Tables[0].PrimaryKey=new DataColumn[]{ds.Tables[0].Columns["username"]};

                             DataRow row=ds.Tables[0].Rows.Find("jjx");

                             if (row!=null)

                             {

                                       row["username"]="jianxiao";

                                       adoTemplate.UpdateDataSet(ds,selectCmd);

                             }

                             if (row!=null)

                             {

                                       row.Delete();

                                       adoTemplate.UpdateDataSet(ds,selectCmd);

                             }

                             trans.CommitTransaction();

                    }

                    catch(Exception ex)

                    {

                             trans.RollbackTransaction();

                             throw ex;

                            

                    }

           }

传递InsertCommand,UpdateCommand,DeleteCommand

           public void TestUpdateDataSet()

           {

                    IDataSource dataSource=new SqlClientDataSource("server=(local);user id=sa;password=;database=zhengda");

                    ITransactionManager trans=new AdoTransactionManager(dataSource);

                    trans.BeginTransaction();

                    IAdoOperations adoTemplate=new SqlClientTemplate(dataSource);

                    IDbCommand insertCmd=adoTemplate.CreateDbCommand("insert into operators (username,password,role) values (@username,@password,@role)");

 

           insertCmd.Parameters.Add(adoTemplate.CreateParameter("@username","username",DataRowVersion.Current));

           insertCmd.Parameters.Add(adoTemplate.CreateParameter("@password","password",DataRowVersion.Current));

           insertCmd.Parameters.Add(adoTemplate.CreateParameter("@role","role",DataRowVersion.Current));

 

                   

                    IDbCommand updateCmd=adoTemplate.CreateDbCommand("update operators set username=@username,password=@password,role=@role where username=@oldusername");

 

           updateCmd.Parameters.Add(adoTemplate.CreateParameter("@username","username",DataRowVersion.Current));

           updateCmd.Parameters.Add(adoTemplate.CreateParameter("@password","password",DataRowVersion.Current));

           updateCmd.Parameters.Add(adoTemplate.CreateParameter("@role","role",DataRowVersion.Current));

           updateCmd.Parameters.Add(adoTemplate.CreateParameter("@oldusername","username",DataRowVersion.Original));

 

                    IDbCommand deleteCmd=adoTemplate.CreateDbCommand("delete from operators where username=@username");

           deleteCmd.Parameters.Add(adoTemplate.CreateParameter("@username","username",DataRowVersion.Original));

 

 

                    DataSet ds=adoTemplate.ExecuteDataSet("select * from operators");

                   

 

                    ds.Tables[0].Rows.Add(new object[]{"jjx","123456","Admins"});

                    try

                    {

 

                             adoTemplate.UpdateDataSet(ds,insertCmd,updateCmd,deleteCmd);

                             ds.Tables[0].PrimaryKey=new DataColumn[]{ds.Tables[0].Columns["username"]};

                             DataRow row=ds.Tables[0].Rows.Find("jjx");

                             if (row!=null)

                             {

                                       row["username"]="jianxiao";

                                       adoTemplate.UpdateDataSet(ds,insertCmd,updateCmd,deleteCmd);

                             }

                             if (row!=null)

                             {

                                       row.Delete();

                                       adoTemplate.UpdateDataSet(ds,insertCmd,updateCmd,deleteCmd);

                             }

                             trans.CommitTransaction();

                    }

                    catch(Exception ex)

                    {

                             trans.RollbackTransaction();

                             throw ex;

                            

                    }

     }

 

大家看到,第二种方式看起来相当可怕,但实际上,通过外部配置,这一切都可以缓解,事实上,我们要求你在利用ioc外部配置所有的数据访问Command

关于SqlServerTransactionManager

如果仅仅是支持sql server ,其实有更简单的方法,只要调整获取连接和关闭连接部分即可,不必为IDbCommand传递事务对象,这样,更容易移值原来的代码(不必使用我们的IAdoperations实现,但我们的实现仍能同这种方式协同工作)。因此,现在的版本修改了DataSourceUtilsAddTransactionHolder/AddTransactionHolder内部方法为AddResourceHolder/RemoveResourceHolder

 

SqlServer命名空间下的SqlServerTransactionManager holder活动的连接对象,简单的在这个连接对象中执行begin transaction,commit transaction,rollbacktransaction ,这部分尚未加错误处理,但它能够正常的工作

 

 

    IDataSource dataSource=new SqlClientDataSource("server=(local);user id=sa;pwd=;database=zhengda");

 

            ITransactionManager trans=new SqlServerTransactionManager(dataSource);

            IAdoOperations adoTemplate=new SqlClientTemplate(dataSource);

 

            const string deleteSql = "delete from operators where username='xxx'";

            const string countSql = "select count(*) from operators";

            const string sql = "insert into operators (username,password,role) values ('xxx','xxx','xxx')";;

            adoTemplate.ExecuteNonQuery(deleteSql);

            trans.BeginTransaction();

 

            //传统的代码能工作在事务下

 

            IDbConnection con=DataSourceUtils.GetConnection(dataSource);

 

            IDbCommand cmd=con.CreateCommand();

           

            int count=(int)adoTemplate.ExecuteScalar(countSql);

 

            cmd.CommandText=sql;

            cmd.ExecuteNonQuery();

            Assert.IsTrue((count+1)==(int)adoTemplate.ExecuteScalar(countSql));

 

            cmd.CommandText=deleteSql;

 

            cmd.ExecuteNonQuery();

   

            DataSourceUtils.CloseConnectionIfNecessary(con,dataSource);

            // 传统代码结束

       

            adoTemplate.ExecuteNonQuery(sql);

 

            Assert.IsTrue((count+1)==(int)adoTemplate.ExecuteScalar(countSql));

 

 

            trans.RollbackTransaction();

            Assert.IsTrue((count)==(int)adoTemplate.ExecuteScalar(countSql));

 

 

            trans.BeginTransaction();

            adoTemplate.ExecuteNonQuery(sql);

            trans.CommitTransaction();

            Assert.IsTrue((count+1)==(int)adoTemplate.ExecuteScalar(countSql));

 

            adoTemplate.ExecuteNonQuery(deleteSql);

 

结合orm

是否能结合orm使用,取决于orm的设计是否开放,我们在以后将尝试整合一些orm

[2005-03-14 11:40:55 | Author:jiangjianxiao ] [] 0 comments

Total 91 Display 56 of 60
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Powered by Google App Engine