技术博客
  • 9.1. Introduction 简介

    One of the most compelling reasons to use Spring is the transaction support. Spring provides a consistent abstraction for transaction management that delivers the following benefits: 一个使用Spring的引人注目的因素是事务支持。 Spring提供了一致的事务管理抽象,这带来了以下好处:

    • Provides a consistent programming model across different transaction APIs such as JTA, JDBC, Hibernate, JPA, and JDO. 为不同的事务API提供了一致的编程模型,如JTA、JDBC、Hibernate、JPA和JDO

    • Supports declarative transaction management. 支持声明式事务管理

    • Provides a simpler, easier to use, API for programmatic transaction management than a number of disparate transaction APIs such as JTA. 提供比大多数事务API如JTA更简单的,易于使用的编程式事务管理API

    • Integrates with Spring's various data access abstractions. 整合Spring的各种数据访问抽象

    This chapter is divided up into a number of sections; these sections are described below, along with a link so that you can navigate directly to the section which interests you. 这章被分成几个小节,每节的描述和链接列在下面,你可以直接跳到感兴趣的部分。

    • The first section, entitled Motivations, describes why one would want to use Spring's transaction abstraction as opposed to EJB CMT or driving transactions via a proprietary API such as Hibernate. 第一节,动机,描述为何 你愿意使用Spring的事务抽象,而不是EJB CMT或者一个私有的API,比如Hibernate的事务处理。

    • The second section, entitled Key abstractions outlines the core classes in Spring's transaction support, as well as how to configure and obtain DataSource instances from a variety of sources. 第二节,关键抽象, 列举了Spring事务支持的核心类,以及如何从多种数据源去配置并获得一个 DataSource实例。

    • The third section, entitled Declarative transaction management covers, Spring's support for declarative transaction management. 第三节,声明式事务管理, 讲述了Spring如何支持声明式事务管理。

    • The fourth section, entitled Programmatic transaction management covers, Spring's support for programmatic (i.e. explicitly coded) transaction management. 第四节,编程式事务管理, 介绍了Spring如何支持编程式(即书写代码)事务管理。

    The chapter closes up with some discussion of best practices surrounding transaction management (for example, choosing between declarative and programmatic transaction management). 本章末尾讨论了一些关于事务管理的最佳实践(比如,如何在编程式和声明式事务管理之间做选择)。

    9.2. Motivations 动机

    Traditionally, J2EE developers have had two choices for transaction management: global or local transactions. Global transactions are managed by the application server, using the Java Transaction API (JTA). Local transactions are resource-specific: the most common example would be a transaction associated with a JDBC connection. This choice has profound implications. For instance, global transactions provide the ability to work with multiple transactional resources (typically relational databases and message queues). With local transactions, the application server is not involved in transaction management and cannot help ensure correctness across multiple resources. (It is worth noting that most applications use a single transaction resource.) 传统上,J2EE开发者有两个事务管理的选择: 全局 局部事务。全局事务由应用服务器管理,使用JTA。局部事务是和资源相关的:例如, 一个和JDBC连接关联的事务。这个选择有深刻的含义。例如,全局事务可以用于多个事务性的资源 (典型例子是关系数据库和消息队列)。使用局部事务,应用服务器不需要参与事务管理,并且不 能帮助确保跨越多个资源(需要指出的是多数应用使用单一事务性的资源)的事务的正确性。

    Global Transactions 全局事务. Global transactions have a significant downside, in that code needs to use JTA, and JTA is a cumbersome API to use (partly due to its exception model). Furthermore, a JTA UserTransaction normally needs to be sourced from JNDI: meaning that we need to use both JNDI and JTA to use JTA. Obviously all use of global transactions limits the reusability of application code, as JTA is normally only available in an application server environment. 全局事务有一个重大的缺陷,代码需要使用JTA:一个笨重的API(部分是因为它的异常模型)。 此外,JTA的UserTransaction通常需要从JNDI获得,这意味着 我们为了JTA,需要 同时 使用JNDI JTA。 显然全部使用全局事务限制了应用代码的重用性,因为JTA通常只在应用服务器的环境中才能使用。 Previously, the preferred way to use global transactions was via EJB CMT (Container Managed Transaction): CMT is a form of declarative transaction management (as distinguished from programmatic transaction management). EJB CMT removes the need for transaction-related JNDI lookups - although of course the use of EJB itself necessitates the use of JNDI. It removes most of the need (although not entirely) to write Java code to control transactions. The significant downside is that CMT is tied to JTA and an application server environment. Also, it is only available if one chooses to implement business logic in EJBs, or at least behind a transactional EJB facade. The negatives around EJB in general are so great that this is not an attractive proposition, especially in the face of compelling alternatives for declarative transaction management. 以前,使用全局事务的首选方式是通过EJB的 CMT容器管理事务): CMT是声明式事务管理的一种形式(区别于编程式事务管理)。 EJB的CMT不需要任何和事务相关的JNDI查找,虽然使用EJB本身肯定需要使用JNDI。它消除大多数(不是全部)书写Java代码去控制事务。 重大的缺陷是CMT绑定在JTA和应用服务器环境上,并且只有我们选择使用EJB实现业务逻辑,或者至少处于一个事务化EJB的外观(Facade) 后才能使用它。EJB有如此多的诟病,尤其是存在其它声明式事务管理时,EJB不是一个吸引人的建议。

    Local Transactions 局部事务. Local transactions may be easier to use, but have significant disadvantages: they cannot work across multiple transactional resources. For example, code that manages transactions using a JDBC connection cannot run within a global JTA transaction. Another downside is that local transactions tend to be invasive of the programming model. 局部事务容易使用,但也有明显的缺点:它们不能用于多个事务性资源。 例如,使用JDBC连接事务管理的代码不能用于全局的JTA事务中。 另一个缺点是局部事务趋向于入侵式的编程模型

    Spring resolves these problems. It enables application developers to use a consistent programming model in any environment. You write your code once, and it can benefit from different transaction management strategies in different environments. Spring provides both declarative and programmatic transaction management. Declarative transaction management is preferred by most users, and is recommended in most cases. Spring解决了这些问题。它使应用开发者能够使用在任何环境 下使用一致的编程模型。你可以只写一次你的代码,这在不同环境 下的不同事务管理策略中很有益处。Spring同时提供声明式和编程式事务管理。 声明事务管理是多数使用者的首选,在多数情况下是被推荐使用的。

    With programmatic transaction management, developers work with the Spring transaction abstraction, which can run over any underlying transaction infrastructure. With the preferred declarative model, developers typically write little or no code related to transaction management, and hence don't depend on Spring's transaction API (or indeed on any other transaction API). 使用编程式事务管理,开发者直接使用Spring事务抽象,这个抽象可以使用在任何 底层事务基础之上。使用首选的声明式模型,开发者通常书写很少的或没有与事务 相关的代码,因此不依赖Spring或任何其他事务API。

    9.3. Key abstractions 关键抽象

    The key to the Spring transaction abstraction is the notion of a transaction strategy. A transaction strategy is defined by the org.springframework.transaction.PlatformTransactionManager interface, shown below: Spring事务抽象的关键是事务策略的概念。这个概念由 org.springframework.transaction.PlatformTransactionManager 接口定义,如下:

    public interface PlatformTransactionManager {
    
        TransactionStatus getTransaction(TransactionDefinition definition)
            throws TransactionException;
    
        void commit(TransactionStatus status) throws TransactionException;
    
        void rollback(TransactionStatus status) throws TransactionException;
    }

    This is primarily an SPI interface, although it can be used programmatically. Note that in keeping with Spring's philosophy, PlatformTransactionManager is an interface, and can thus be easily mocked or stubbed as necessary. Nor is it tied to a lookup strategy such as JNDI: PlatformTransactionManager implementations are defined like any other object (or bean) in a Spring IoC container. This benefit alone makes it a worthwhile abstraction even when working with JTA: transactional code can be tested much more easily than if it used JTA directly. 这首先是一个SPI接口,虽然它也可以在 编码中使用。注意按照Spring的哲学,PlatformTransactionManager 是一个接口。因而如果需要它可以很容易地被模拟和 桩化。它也没有和一个查找策略如JNDI捆绑在一起:PlatformTransactionManager 的实现定义和其他Spring IoC容器中的对象一样。这个好处使得即使使用JTA,也 是一个很有价值的抽象:事务代码可以比直接使用JTA更加容易测试。

    Again in keeping with Spring's philosophy, the TransactionException that can be thrown by any of the PlatformTransactionManager interface's methods is unchecked (i.e. it extends the java.lang.RuntimeException class). Transaction infrastructure failures are almost invariably fatal. In rare cases where application code can actually recover from a transaction failure, the application developer can still choose to catch and handle TransactionException. The salient point is that developers are not forced to do so. 继续Spring哲学,可由任何PlatformTransactionManager的接口方法抛出的 TransactionException是unchecked(继承自 java.lang.RuntimeException)的。 底层的事务失败几乎总是致命的。很少情况下应用程序代码可以从它们 中恢复,不过应用开发者依然可以捕获并处理 TransactionException,他们可以自由决定怎么干。

    The getTransaction()method returns a TransactionStatus object, depending on a TransactionDefinition parameter. The returned TransactionStatus might represent a new or existing transaction (if there were a matching transaction in the current call stack - with the implication being that (as with J2EE transaction contexts) a TransactionStatus is associated with a thread of execution). getTransaction()方法根据一个类型为 TransactionDefinition的参数返回一个 TransactionStatus对象。返回的 TransactionStatus对象可能代表一个新的或已经存在的事 务(如果在当前调用堆栈有一个符合条件的事务。如同J2EE事务环境,一个 TransactionStatus也是和 执行线程关联的)。

    The TransactionDefinition interface specifies: TransactionDefinition接口指定:

    • Isolation: the degree of isolation this transaction has from the work of other transactions. For example, can this transaction see uncommitted writes from other transactions? 事务隔离:当前事务和其它事务的隔离的程度。 例如,这个事务能否看到其他事务未提交的写数据?

    • Propagation: normally all code executed within a transaction scope will run in that transaction. However, there are several options specifying behavior if a transactional method is executed when a transaction context already exists: for example, simply running in the existing transaction (the most common case); or suspending the existing transaction and creating a new transaction. Spring offers all of the transaction propagation options familiar from EJB CMT. 事务传播:通常在一个事务中执行的所有代码都会在这个事务中运行。 但是,如果一个事务上下文已经存在,有几个选项可以指定一个事务性方法的执行行为: 例如,简单地在现有的事务中运行(大多数情况);或者挂起现有事务,创建一个新的事务。 Spring提供EJB CMT中常见的事务传播选项。

    • Timeout: how long this transaction may run before timing out (and automatically being rolled back by the underlying transaction infrastructure). 事务超时: 事务在超时前能运行多久(自动被底层的事务基础设施回滚)。

    • Read-only status: a read-only transaction does not modify any data. Read-only transactions can be a useful optimization in some cases (such as when using Hibernate). 只读状态: 只读事务不修改任何数据。 只读事务在某些情况下(例如当使用Hibernate时),是一种非常有用的优化。

    These settings reflect standard transactional concepts. If necessary, please refer to a resource discussing transaction isolation levels and other core transaction concepts: understanding such core concepts is essential to using Spring or indeed any other transaction management solution. 这些设置反映了标准概念。如果需要,请查阅讨论事务隔离层次和其他核心事务概念的资源: 理解这些概念在使用Spring和其他事务管理解决方案时是非常关键的。

    The TransactionStatus interface provides a simple way for transactional code to control transaction execution and query transaction status. The concepts should be familiar, as they are common to all transaction APIs: TransactionStatus 接口为处理事务的代码提供一个简单的控制事务执行和查询事务状态的方法。 这个概念应该是熟悉的,因为它们在所有的事务API中是相同的:

    public interface TransactionStatus {
    
        boolean isNewTransaction();
    
        void setRollbackOnly();
    
        boolean isRollbackOnly();
    }

    Regardless of whether you opt for declarative or programmatic transaction management in Spring, defining the correct PlatformTransactionManager implementation is absolutely essential. In good Spring fashion, this important definition typically is made using IoC. 使用Spring时,无论你选择编程式还是声明式的事务管理,定义一个正确的 PlatformTransactionManager 实现都是至关重要的。 按照Spring的风格,这种重要定义都是通过IoC实现的。

    PlatformTransactionManager implementations normally require knowledge of the environment in which they work: JDBC, JTA, Hibernate, etc The following examples from the dataAccessContext-local.xml file from Spring's jPetStore sample application show how a local PlatformTransactionManager implementation can be defined. (This will work with plain JDBC.) 一般来说,选择PlatformTransactionManager实现时需要知道 当前的工作环境,如JDBC、JTA、Hibernate等。下面的例子来自Spring范例应用—— jPetStore——中的dataAccessContext-local.xml文件,其中 展示了一个局部PlatformTransactionManager实现是怎么定义 的(仅限于纯粹JDBC环境)

    We must define a JDBC DataSource, and then use the Spring DataSourceTransactionManager, giving it a reference to the DataSource. 我们必须先定义一个JDBC DataSource,然后使用Spring DataSourceTransactionManager,并传入指向 DataSource的引用。

            
      
      
      
      
    

    The related PlatformTransactionManager bean definition will look like this: PlatformTransactionManager bean的定义如下:

            
      
    

    If we use JTA, as in the dataAccessContext-jta.xml file from the same sample application, we need to use a container DataSource, obtained via JNDI, and a JtaTransactionManager implementation. The JtaTransactionManager doesn't need to know about the DataSource, or any other specific resources, as it will use the container's global transaction management infrastructure. 如果我们使用JTA,就像范例中dataAccessContext-jta.xml文件所示, 我们将从JNDI中获取一个容器管理的DataSource,以及一个 JtaTransactionManager实现。 JtaTransactionManager不需要知道 DataSource和其他特定的资源,因为它将使用容器提供的全局事务管理。

    
    
    
      
    
      
    
      
      
      
      
    
    
    [Note] Note

    The above definition of the 'dataSource' bean uses the 'jndi-lookup' tag from the 'jee' namespace. For more information on schema-based configuration, see Appendix A, XML Schema-based configuration, and for more information on the tags see the section entitled Section A.2.3, “The jee schema”. 上面'dataSource' bean的定义使用了'jee' 名称空间下的'jndi-lookup'标签。想了解更多的配置信息, 请看 Appendix A, XML Schema-based configuration, 关于标签的信息,可参考Section A.2.3, “The jee schema”节。

    We can use Hibernate local transactions easily, as shown in the following examples from the Spring PetClinic sample application. In this case, we need to define a Hibernate LocalSessionFactoryBean, which application code will use to obtain Hibernate Session instances. 我们可以很容易地使用Hibernate局部事务,就像下面的Spring的PetClinic示例应用中的例子一样)。 这种情况下,我们需要定义一个Hibernate的LocalSessionFactoryBean, 应用程序从中获取到Hibernate Session 实例。

    The DataSource bean definition will be similar to one of the above examples, and is not shown. (If it is a container DataSource, it should be non-transactional as Spring, rather than the container, will manage transactions.) DataSource bean的定义同上例类似,这里不再展示。 (不过,如果是一个容器提供的DataSource, 它将由容器自身,而不是Spring,来管理事务)。

    The 'txManager' bean in this case is of class HibernateTransactionManager. In the same way as the DataSourceTransactionManager needs a reference to the DataSource, the HibernateTransactionManager needs a reference to the SessionFactory. 这种情况中'txManager' bean的类型为HibernateTransactionManager。 同样地,DataSourceTransactionManager需要一个指向 DataSource的引用,而 HibernateTransactionManager需要一个指向 SessionFactory的引用。

            
      
      
        
          org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml
        
      
      
        
    	  hibernate.dialect=${hibernate.dialect}
    	
      
    
    
    
      
    

    With Hibernate and JTA transactions, we can simply use the JtaTransactionManager as with JDBC or any other resource strategy. 我们可以简单地使用JtaTransactionManager来处理Hibernate事务和JTA事务, 就像我们处理JDBC,或者任何其它的资源策略一样。

            
            

    Note that this is identical to JTA configuration for any resource, as these are global transactions, which can enlist any transactional resource. 注意任何资源的JTA配置都是这样的,因为它们都是全局事务,可以支持任何事务性资源。

    In all these cases, application code won't need to change at all. We can change how transactions are managed merely by changing configuration, even if that change means moving from local to global transactions or vice versa. 在所有这些情况下,应用程序代码根本不需要做任何改动。我们仅仅通过改变配置就可以改变事务如何被管理, 即使这些更改意味着在局部事务和全局事务间切换。

    9.4. Resource synchronization with transactions 使用事务来同步资源

    It should now be clear how different transaction managers are created, and how they are linked to related resources which need to be synchronized to transactions (i.e. DataSourceTransactionManager to a JDBC DataSource, HibernateTransactionManager to a Hibernate SessionFactory, etc). There remains the question however of how the application code, directly or indirectly using a persistence API (JDBC, Hibernate, JDO, etc), ensures that these resources are obtained and handled properly in terms of proper creation/reuse/cleanup and trigger (optionally) transaction synchronization via the relevant PlatformTransactionManager. 现在应该比较清楚了,不同的事务管理器是如何创建的,以及它们如何被连接到相应的 需要被同步到事务的资源上(例如,DataSourceTransactionManager 对应到JDBC DataSourceHibernateTransactionManager对应到Hibernate SessionFactory等)。可是,剩下的问题是, 直接或间接地使用一种持久化API(JDBC,Hibernate,JDO等)的应用代码,如何确保通过 相关的PlatformTransactionManager来恰当地 获取并操作资源,来满足事务同步(包括:创建/复用/清理 和 触发(可能没有))。

    9.4.1. High-level approach 高层次方式

    The preferred approach is to use Spring's highest level persistence integration APIs. These do not replace the native APIs, but internally handle resource creation/reuse, cleanup, optional transaction synchronization of the resources and exception mapping so that user data access code doesn't have to worry about these concerns at all, but can concentrate purely on non-boilerplate persistence logic. Generally, the same template approach is used for all persistence APIs, with examples such as JdbcTemplate, HibernateTemplate, JdoTemplate, etc These integration classes are detailed in subsequent chapters of this reference documentation. 首选的方法是使用Spring的高层持久化集成APIs。这种方式不会替换原始的APIs,而 是在内部封装了资源创建、复用、清理、事务同步以及异常映射等功能,这样 用户的数据访问代码就不必关心这些,而集中精力于自己的持久化逻辑。通常,对所有 持久化API都采用这种 模板 方法,例如 JdbcTemplateHibernateTemplateJdoTemplate等。这些集成功能类在这份参考文档后面的章节中详细叙述。

    9.4.2. Low-level approach 低层次方式

    At a lower level exist classes such as DataSourceUtils (for JDBC), SessionFactoryUtils (for Hibernate), PersistenceManagerFactoryUtils (for JDO), and so on. When it is preferable for application code to deal directly with the resource types of the native persistence APIs, these classes ensure that proper Spring-managed instances are obtained, transactions are (optionally) synchronized, and exceptions which happen in the process are properly mapped to a consistent API. 在较低层次上,有这些类: DataSourceUtils (for JDBC), SessionFactoryUtils (for Hibernate), PersistenceManagerFactoryUtils (for JDO),等等。 当对应用代码来说,直接同原始持久化API特有的资源类型打交道是更好的选择时,这些类 确保应用代码获取到正确的Spring受管bean,事务被正确同步,处理过程中的异常被映射 到一致的API。

    For example, in the case of JDBC, instead of the traditional JDBC approach of calling the getConnection() method on the DataSource, you would instead use Spring's org.springframework.jdbc.datasource.DataSourceUtils class as follows: 例如,在JDBC环境下,你不再使用传统的调用DataSourcegetConnection()方法的方式,而是使用Spring的 org.springframework.jdbc.datasource.DataSourceUtils,像这样:

                Connection conn = DataSourceUtils.getConnection(dataSource);

    If an existing transaction exists, and already has a connection synchronized (linked) to it, that instance will be returned. Otherwise, the method call will trigger the creation of a new connection, which will be (optionally) synchronized to any existing transaction, and made available for subsequent reuse in that same transaction. As mentioned, this has the added advantage that any SQLException will be wrapped in a Spring CannotGetJdbcConnectionException - one of Spring's hierarchy of unchecked DataAccessExceptions. This gives you more information than can easily be obtained from the SQLException, and ensures portability across databases: even across different persistence technologies. 如果已有一个事务及与之关联的connection存在,该实例将被返回。否则, 该方法调用将触发起一个新的connection的创建动作,该connection(可选地)被同步到任何现有的事务, 并可以在同一事务范围内被后续的调用复用。正如上面提到的, 这个过程有一个额外的好处,就是任何SQLException将被包装为Spring的 CannotGetJdbcConnectionException, 该类是Spring的unchecked的DataAccessExceptions层次体系中的一员。这将给你比从 SQLException中简单所得更多的信息,而且保证了跨 数据库——甚至其他持久化技术——的移植性。

    It should be noted that this will also work fine without Spring transaction management (transaction synchronization is optional), so you can use it whether or not you are using Spring for transaction management. 应该指出的是,这些类同样可以在没有Spring事务管理的环境中良好工作(事务同步能力是可选的), 所以无论你是否使用Spring的事务管理,你都可以使用这些类。

    Of course, once you've used Spring's JDBC support or Hibernate support, you will generally prefer not to use DataSourceUtils or the other helper classes, because you'll be much happier working via the Spring abstraction than directly with the relevant APIs. For example, if you use the Spring JdbcTemplate or jdbc.object package to simplify your use of JDBC, correct connection retrieval happens behind the scenes and you won't need to write any special code. 当然,一旦你用过Spring的JDBC支持或Hibernate支持,你一般就不再会选择 DataSourceUtils或是别的辅助类了,因为你会更乐意 与Spring抽象一起工作,而不是直接使用相关的API。例如,如果你使用Spring JdbcTemplatejdbc.object包来 简化使用JDBC,Spring会在幕后替你正确地获取连接,而你不需要写任何特殊代码。

    9.4.3. TransactionAwareDataSourceProxy

    At the very lowest level exists the TransactionAwareDataSourceProxy class. This is a proxy for a target DataSource, which wraps the target DataSource to add awareness of Spring-managed transactions. In this respect, it is similar to a transactional JNDI DataSource as provided by a J2EE server. TransactionAwareDataSourceProxy类工作在底层。 这是一个对目标DataSource的代理,它包装了 目标DataSource,提供对Spring管理事务的可知性。 在这点上,它类似于一个J2EE服务器提供的事务性JNDI DataSource

    It should almost never be necessary or desirable to use this class, except when existing code exists which must be called and passed a standard JDBC DataSource interface implementation. In that case, it's possible to still have this code be usable, but participating in Spring managed transactions. It is preferable to write your new code using the higher level abstractions mentioned above. 该类应该永远不需要被应用代码使用,除非现有代码存在需要直接传递一个标准的JDBC DataSource的情况。这时可以通过参与Spring 管理事务让这些代码仍然有用。书写新的代码时,首选的方法是采用上面提到的Spring 高层抽象。

    9.5. Declarative transaction management 声明式事务管理

    Most Spring users choose declarative transaction management. It is the option with the least impact on application code, and hence is most consistent with the ideals of a non-invasive lightweight container. 大多数Spring用户选择声明式事务管理。这是对应用代码影响最小的选择,因此也最符合 非侵入式轻量级容器的理念。

    Spring's declarative transaction management is made possible with Spring AOP, although, as the transactional aspects code comes with Spring and may be used in a boilerplate fashion, AOP concepts do not generally have to be understood to make effective use of this code. Spring的声明式事务管理是通过Spring AOP实现的,因为事务方面的代码与Spring绑定并 以一种样板式风格使用,不过尽管如此,你一般并不需要理解AOP概念就可以有效地使用 Spirng的声明式事务管理。

    It may be helpful to begin by considering EJB CMT and explaining the similarities and differences with Spring declarative transaction management. The basic approach is similar: it is possible to specify transaction behavior (or lack of it) down to individual method level. It is possible to make a setRollbackOnly() call within a transaction context if necessary. The differences are: 从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。 它们的基本方法是相似的:都可以指定事务管理到单独的方法;如果需要可以在事务上下文调用 setRollbackOnly()方法。不同之处如下:

    • Unlike EJB CMT, which is tied to JTA, Spring declarative transaction management works in any environment. It can work with JDBC, JDO, Hibernate or other transactions under the covers, with configuration changes only. 不象EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。 只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作。

    • Spring enables declarative transaction management to be applied to any class (and attendant instances of that class), not merely special classes such as EJBs. Spring的声明式事务管理可以被应用到任何类(以及那个类的实例)上,不仅仅是像 EJB那样的特殊类。

    • Spring offers declarative rollback rules: a feature with no EJB equivalent, which we'll discuss below. Rollback can be controlled declaratively, not merely programmatically. Spring提供了声明式的回滚规则:EJB没有对应的特性, 我们将在下面讨论。回滚可以声明式的控制,不仅仅是编程式的。

    • Spring gives you an opportunity to customize transactional behavior, using AOP. For example, if you want to insert custom behavior in the case of transaction rollback, you can. You can also add arbitrary advice, along with the transactional advice. With EJB CMT, you have no way to influence the container's transaction management other than setRollbackOnly(). Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务回滚中插入定制的行为。 你也可以增加任意的通知,就象事务通知一样。使用EJB CMT,除了使用setRollbackOnly(), 你没有办法能够影响容器的事务管理。

    • Spring does not support propagation of transaction contexts across remote calls, as do high-end application servers. If you need this feature, we recommend that you use EJB. However, consider carefully before using such a feature. Normally, we do not want transactions to span remote calls. Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如果你需要这些特性, 我们推荐你使用EJB。然而,不要轻易使用这些特性。通常我们并不希望事务跨越远程调用。

    The concept of rollback rules is important: they enable us to specify which exceptions (and throwables) should cause automatic roll back. We specify this declaratively, in configuration, not in Java code. So, while we can still call setRollbackOnly() on the TransactionStatus object to roll the current transaction back programmatically, most often we can specify a rule that MyApplicationException should always result in roll back. This has the significant advantage that business objects don't need to depend on the transaction infrastructure. For example, they typically don't need to import any Spring APIs, transaction or other. 回滚规则的概念比较重要:它使我们能够指定什么样的异常(和throwables)将导致自动回滚。 我们在配置文件中声明式地指定,无须在Java代码中。同时,我们仍旧可以通过调用 TransactionStatussetRollbackOnly() 方法编程式地回滚当前事务。通常,我们定义一条规则,声明 MyApplicationException应该总是导致事务回滚。这种方式 带来了显著的好处,它使你的业务对象不必依赖于事务设施。典型的例子是你不必在代码中导入Spring API,事务等。

    While the EJB default behavior is for the EJB container to automatically roll back the transaction on a system exception (usually a runtime exception), EJB CMT does not roll back the transaction automatically on an application exception (i.e. a checked exception other than java.rmi.RemoteException). While the Spring default behavior for declarative transaction management follows EJB convention (roll back is automatic only on unchecked exceptions), it's often useful to customize this. 对EJB来说,默认的行为是EJB容器在遇到系统异常(通常指运行时异常)时自动回滚当前事务。 EJB CMT遇到应用异常(例如,除了java.rmi.RemoteException外别的checked exception)时 并不会自动回滚。默认式Spring处理声明式事务管理的规则遵守EJB习惯(只在遇到unchecked exceptions时自动回滚), 但通常定制这条规则会更有用。

    9.5.1. Understanding Spring's declarative transaction implementation 理解Spring的声明式事务管理实现

    The aim of this section is to dispel the mystique that is sometimes associated with the use of declarative transactions. It is all very well for this reference documentation simply to tell you to annotate your classes with the @Transactional annotation, add the line ('') to your configuration, and then expect you to understand how it all works. This section will explain the inner workings of Spring's declarative transaction infrastructure to help you navigate your way back upstream to calmer waters in the event of transaction-related issues. 本节的目的是消除与使用声明式事务管理有关的神秘性。简单点儿总是好的,这份参考文档 只是告诉你给你的类加上@Transactional注解, 在配置文件中添加('')行, 然后期望你理解整个过程是怎么工作的。此节讲述Spring的声明式事务管理内部的工作机制, 以帮助你在面对事务相关的问题时不至于误入迷途,回朔到上游平静的水域。

    [Tip] Tip

    Looking at the Spring source code is a good way to get a real understanding of Spring's transaction support. You should find the Javadoc informative and complete. We suggest turning the logging level to 'DEBUG' in your Spring-enabled application(s) during development to better see what goes on under the hood. 阅读Spring源码是理解清楚Spring事务支持的一个好方法。Spring的Javadoc提供的信息丰富而完整。 我们建议你在开发自己的Spring应用时把日志级别设为'DEBUG'级,这样你能更清楚地看到幕后发生的事。

    The most important concepts to grasp with regard to Spring's declarative transaction support are that this support is enabled via AOP proxies, and that the transactional advice is driven by metadata (currently XML- or annotation-based). The combination of a proxy with transactional metadata yields an AOP proxy that uses a TransactionInterceptor in conjunction with an appropriate PlatformTransactionManager implementation to drive transactions around method invocations. 在理解Spring的声明式事务管理方面最重要的概念是:Spring的事务管理是通过 AOP代理实现的。其中的事务通知由元数据 (目前基于XML或注解)驱动。代理对象与事务元数据结合产生了一个AOP代理,它使用 一个PlatformTransactionManager实现品配合 TransactionInterceptor,在方法调用 前后实施事务。

    [Note] Note

    Although knowledge of AOP (and specifically Spring AOP) is not required in order to use Springs declarative transaction support, it can help. Spring AOP is thoroughly covered in the chapter entitled Chapter 6, Aspect Oriented Programming with Spring 使用Spring进行面向切面编程(AOP). 尽管使用Spring声明式事务管理不需要AOP(尤其是Spring AOP)的知识,但了解这些是很有帮助的。 你可以在Chapter 6, Aspect Oriented Programming with Spring 使用Spring进行面向切面编程(AOP)章找到关于Spring AOP的全部内容。

    Conceptually, calling a method on a transactional proxy looks like this... 概念上来说,在事务代理上调用方法的工作过程看起来像这样:

    9.5.2. A first example 第一个例子

    Consider the following interface, and its attendant implementation. The intent is to convey the concepts, and using Foo and Bar means that you can concentrate on the transaction usage and not have to worry about the domain model. 请看下面的接口和它的实现。这个例子的意图是介绍概念,使用 FooBar 这样的名字只是为了让你关注于事务的用法,而不是领域模型。

     
                
                
                
    
    package x.y.service;
    
    public interface FooService {
    
        Foo getFoo(String fooName);
    
        Foo getFoo(String fooName, String barName);
    
        void insertFoo(Foo foo);
    
        void updateFoo(Foo foo);
    }
     
                上面接口的实现
                
                
    
    package x.y.service;
    
    public class DefaultFooService implements FooService {
    
        public Foo getFoo(String fooName) {
            throw new UnsupportedOperationException();
        }
    
        public Foo getFoo(String fooName, String barName) {
            throw new UnsupportedOperationException();
        }
    
        public void insertFoo(Foo foo) {
            throw new UnsupportedOperationException();
        }
    
        public void updateFoo(Foo foo) {
            throw new UnsupportedOperationException();
        }
    }

    (For the purposes of this example, the fact that the implementation class (DefaultFooService) throws UnsupportedOperationException instances in the body of each implemented method is good; it allows us to see transactions being created and then rolled back in response to UnsupportedOperationException instances being thrown.) (对该例的目的来说,上例中实现类(DefaultFooService) 的每个方法在其方法体中抛出UnsupportedOperationException的做法 是恰当的,我们可以看到,事务被创建出来,响应 UnsupportedOperationException的抛出,然后回滚。)

    Let's assume that the first two methods of the FooService interface (getFoo(String) and getFoo(String, String)) have to execute in the context of a transaction with read-only semantics, and that the other methods (insertFoo(Foo) and updateFoo(Foo)) have to execute in the context of a transaction with read-write semantics. 我们假定,FooService的前两个方法( getFoo(String)getFoo(String, String))必须 执行在只读事务上下文中,其余方法(insertFoo(Foo)updateFoo(Foo))必须执行在读写事务上下文中。

    To configure this scenario in a declarative fashion using XML-based metadata, you would write the following configuration (don't worry about taking it in all at once; everything will be explained in detail in the next few paragraphs). 使用XML方式元数据的声明式配置的话,你得这么写(不要想着一次全部理解,所有内容会在后面的章节详细讨论):

     
                
                
                
    
    
           xmlns:tx="http://www.springframework.org/schema/tx"
           
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           
           
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           
           
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
      
      <!-- this is the service o
  • 亲爱的Mrs TableMM:
            每次你微笑的看着我,都会引发使我心跳加速的触发器,我发现自己已深深地爱上了你,无法逃避,因为我们在同一个Database里。经过我长期的查询分析,对你表结构的了解也越来越清晰,你温柔美丽,高雅贤淑,简直就是我心目中的BCD。我多想JOIN你,但找不到合适的id.If你能和我在一起,你就是我的unique,我决不会三心二意,去找其他的foreign key。为了你,我会DELETE自己所有的坏脾气,也会时常UPDATE自己。你交给我的transaction,无论@@error等于几,我都会commite,尽心尽力。我会紧紧地FETCH,我们在一起的美好回忆,将它变成加密的存储过程,而不是转瞬即逝的tempdb。我将这份日志sp_sendmail给你,向你declare了我的心意,如果你set nocount off,我就认为你default了我们的实体关系,不要再犹豫迟疑,相信自己的select,我一定会爱你,直到系统死机。让我们挑一个服务器空闲的日子,参加批处理婚礼,由sa为我们见证,从此紧紧地union在一起,永不分离,共同create一片美好的天地。
    祝:  MIN(烦恼),MAX(美丽)
    FROM: 爱你的Mr TableGG    于 Getdate()

    很专业的一份情书哦,而且,有一个小细节,可以发现这位GG是用sql server数据库的.细心的你有没有发现呢?

  • 一、安装篇

      jspSmartUpload是由www.jspsmart.com网站开发的一个可免费使用的全功能的文件上传下载组件,适于嵌入执行上传下载操作的JSP文件中。该组件有以下几个特点:

    1、使用简单。在JSP文件中仅仅书写三五行JAVA代码就可以搞定文件的上传或下载,方便。

    2、能全程控制上传。利用jspSmartUpload组件提供的对象及其操作方法,可以获得全部上传文件的信息(包括文件名,大小,类型,扩展名,文件数据等),方便存取。

    3、能对上传的文件在大小、类型等方面做出限制。如此可以滤掉不符合要求的文件。

    4、下载灵活。仅写两行代码,就能把Web服务器变成文件服务器。不管文件在Web服务器的目录下或在其它任何目录下,都可以利用jspSmartUpload进行下载。

    5、能将文件上传到数据库中,也能将数据库中的数据下载下来。这种功能针对的是MYSQL数据库,因为不具有通用性,所以本文不准备举例介绍这种用法。

      jspSmartUpload组件可以从www.jspsmart.com网站上自由下载,压缩包的名字是jspSmartUpload.zip。下载后,用WinZip或WinRAR将其解压到Tomcat的webapps目录下(本文以Tomcat服务器为例进行介绍)。解压后,将webapps/jspsmartupload目录下的子目录Web-inf名字改为全大写的WEB-INF,这样一改jspSmartUpload类才能使用。因为Tomcat对文件名大小写敏感,它要求Web应用程序相关的类所在目录为WEB-INF,且必须是大写。接着重新启动Tomcat,这样就可以在JSP文件中使用jspSmartUpload组件了。

      注意,按上述方法安装后,只有webapps/jspsmartupload目录下的程序可以使用jspSmartUpload组件,如果想让Tomcat服务器的所有Web应用程序都能用它,必须做如下工作:

    1.进入命令行状态,将目录切换到Tomcat的webapps/jspsmartupload/WEB-INF目录下。

    2.运行JAR打包命令:jar cvf jspSmartUpload.jar com

    (也可以打开资源管理器,切换到当前目录,用WinZip将com目录下的所有文件压缩成jspSmartUpload.zip,然后将jspSmartUpload.zip换名为jspSmartUpload.jar文件即可。)

    3.将jspSmartUpload.jar拷贝到Tomcat的shared/lib目录下。

    二、相关类说明篇

    ㈠ File类

      这个类包装了一个上传文件的所有信息。通过它,可以得到上传文件的文件名、文件大小、扩展名、文件数据等信息。

      File类主要提供以下方法:

    1、saveAs作用:将文件换名另存。

    原型:

    public void saveAs(java.lang.String destFilePathName)



    public void saveAs(java.lang.String destFilePathName, int optionSaveAs)

    其中,destFilePathName是另存的文件名,optionSaveAs是另存的选项,该选项有三个值,分别是SAVEAS_PHYSICAL,SAVEAS_VIRTUAL,SAVEAS_AUTO。SAVEAS_PHYSICAL表明以操作系统的根目录为文件根目录另存文件,SAVEAS_VIRTUAL表明以Web应用程序的根目录为文件根目录另存文件,SAVEAS_AUTO则表示让组件决定,当Web应用程序的根目录存在另存文件的目录时,它会选择SAVEAS_VIRTUAL,否则会选择SAVEAS_PHYSICAL。

    例如,saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)执行后若Web服务器安装在C盘,则另存的文件名实际是c:\upload\sample.zip。而saveAs("/upload/sample.zip",SAVEAS_VIRTUAL)执行后若Web应用程序的根目录是webapps/jspsmartupload,则另存的文件名实际是webapps/jspsmartupload/upload/sample.zip。saveAs("/upload/sample.zip",SAVEAS_AUTO)执行时若Web应用程序根目录下存在upload目录,则其效果同saveAs("/upload/sample.zip",SAVEAS_VIRTUAL),否则同saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)。

    建议:对于Web程序的开发来说,最好使用SAVEAS_VIRTUAL,以便移植。

    2、isMissing

    作用:这个方法用于判断用户是否选择了文件,也即对应的表单项是否有值。选择了文件时,它返回false。未选文件时,它返回true。

    原型:public boolean isMissing()

    3、getFieldName

    作用:取HTML表单中对应于此上传文件的表单项的名字。

    原型:public String getFieldName()

    4、getFileName

    作用:取文件名(不含目录信息)

    原型:public String getFileName()

    5、getFilePathName

    作用:取文件全名(带目录)

    原型:public String getFilePathName

    6、getFileExt

    作用:取文件扩展名(后缀)

    原型:public String getFileExt()

    7、getSize

    作用:取文件长度(以字节计)

    原型:public int getSize()

    8、getBinaryData

    作用:取文件数据中指定位移处的一个字节,用于检测文件等处理。

    原型:public byte getBinaryData(int index)。其中,index表示位移,其值在0到getSize()-1之间。

    ㈡ Files类

      这个类表示所有上传文件的集合,通过它可以得到上传文件的数目、大小等信息。有以下方法:

    1、getCount

    作用:取得上传文件的数目。

    原型:public int getCount()

    2、getFile

    作用:取得指定位移处的文件对象File(这是com.jspsmart.upload.File,不是java.io.File,注意区分)。

    原型:public File getFile(int index)。其中,index为指定位移,其值在0到getCount()-1之间。

    3、getSize

    作用:取得上传文件的总长度,可用于限制一次性上传的数据量大小。

    原型:public long getSize()

    4、getCollection

    作用:将所有上传文件对象以Collection的形式返回,以便其它应用程序引用,浏览上传文件信息。

    原型:public Collection getCollection()

    5、getEnumeration

    作用:将所有上传文件对象以Enumeration(枚举)的形式返回,以便其它应用程序浏览上传文件信息。

    原型:public Enumeration getEnumeration()

    ㈢ Request类

      这个类的功能等同于JSP内置的对象request。只所以提供这个类,是因为对于文件上传表单,通过request对象无法获得表单项的值,必须通过jspSmartUpload组件提供的Request对象来获取。该类提供如下方法:

    1、getParameter

    作用:获取指定参数之值。当参数不存在时,返回值为null。

    原型:public String getParameter(String name)。其中,name为参数的名字。

    2、getParameterValues

    作用:当一个参数可以有多个值时,用此方法来取其值。它返回的是一个字符串数组。当参数不存在时,返回值为null。

    原型:public String[] getParameterValues(String name)。其中,name为参数的名字。

    3、getParameterNames

    作用:取得Request对象中所有参数的名字,用于遍历所有参数。它返回的是一个枚举型的对象。

    原型:public Enumeration getParameterNames()

    ㈣ SmartUpload类这个类完成上传下载工作。

    A.上传与下载共用的方法:

    只有一个:initialize。

    作用:执行上传下载的初始化工作,必须第一个执行。

    原型:有多个,主要使用下面这个:

    public final void initialize(javax.servlet.jsp.PageContext pageContext)

    其中,pageContext为JSP页面内置对象(页面上下文)。

    B.上传文件使用的方法:

    1、upload

    作用:上传文件数据。对于上传操作,第一步执行initialize方法,第二步就要执行这个方法。

    原型:public void upload()

    2、save

    作用:将全部上传文件保存到指定目录下,并返回保存的文件个数。

    原型:public int save(String destPathName)

    和public int save(String destPathName,int option)

    其中,destPathName为文件保存目录,option为保存选项,它有三个值,分别是SAVE_PHYSICAL,SAVE_VIRTUAL和SAVE_AUTO。(同File类的saveAs方法的选项之值类似)SAVE_PHYSICAL指示组件将文件保存到以操作系统根目录为文件根目录的目录下,SAVE_VIRTUAL指示组件将文件保存到以Web应用程序根目录为文件根目录的目录下,而SAVE_AUTO则表示由组件自动选择。

    注:save(destPathName)作用等同于save(destPathName,SAVE_AUTO)。

    3、getSize

    作用:取上传文件数据的总长度

    原型:public int getSize()

    4、getFiles

    作用:取全部上传文件,以Files对象形式返回,可以利用Files类的操作方法来获得上传文件的数目等信息。

    原型:public Files getFiles()

    5、getRequest

    作用:取得Request对象,以便由此对象获得上传表单参数之值。

    原型:public Request getRequest()

    6、setAllowedFilesList

    作用:设定允许上传带有指定扩展名的文件,当上传过程中有文件名不允许时,组件将抛出异常。

    原型:public void setAllowedFilesList(String allowedFilesList)

    其中,allowedFilesList为允许上传的文件扩展名列表,各个扩展名之间以逗号分隔。如果想允许上传那些没有扩展名的文件,可以用两个逗号表示。例如:setAllowedFilesList("doc,txt,,")将允许上传带doc和txt扩展名的文件以及没有扩展名的文件。

    7、setDeniedFilesList

    作用:用于限制上传那些带有指定扩展名的文件。若有文件扩展名被限制,则上传时组件将抛出异常。

    原型:public void setDeniedFilesList(String deniedFilesList)

    其中,deniedFilesList为禁止上传的文件扩展名列表,各个扩展名之间以逗号分隔。如果想禁止上传那些没有扩展名的文件,可以用两个逗号来表示。例如:setDeniedFilesList("exe,bat,,")将禁止上传带exe和bat扩展名的文件以及没有扩展名的文件。

    8、setMaxFileSize

    作用:设定每个文件允许上传的最大长度。

    原型:public void setMaxFileSize(long maxFileSize)

    其中,maxFileSize为为每个文件允许上传的最大长度,当文件超出此长度时,将不被上传。

    9、setTotalMaxFileSize

    作用:设定允许上传的文件的总长度,用于限制一次性上传的数据量大小。

    原型:public void setTotalMaxFileSize(long totalMaxFileSize)

    其中,totalMaxFileSize为允许上传的文件的总长度。

    C.下载文件常用的方法

    1、setContentDisposition

    作用:将数据追加到MIME文件头的CONTENT-DISPOSITION域。jspSmartUpload组件会在返回下载的信息时自动填写MIME文件头的CONTENT-DISPOSITION域,如果用户需要添加额外信息,请用此方法。

    原型:public void setContentDisposition(String contentDisposition)

    其中,contentDisposition为要添加的数据。如果contentDisposition为null,则组件将自动添加"attachment;",以表明将下载的文件作为附件,结果是IE浏览器将会提示另存文件,而不是自动打开这个文件(IE浏览器一般根据下载的文件扩展名决定执行什么操作,扩展名为doc的将用word程序打开,扩展名为pdf的将用acrobat程序打开,等等)。

    2、downloadFile

    作用:下载文件。

    原型:共有以下三个原型可用,第一个最常用,后两个用于特殊情况下的文件下载(如更改内容类型,更改另存的文件名)。

    ① public void downloadFile(String sourceFilePathName)

    其中,sourceFilePathName为要下载的文件名(带目录的文件全名)

    ② public void downloadFile(String sourceFilePathName,String contentType)

    其中,sourceFilePathName为要下载的文件名(带目录的文件全名),contentType为内容类型(MIME格式的文件类型信息,可被浏览器识别)。

    ③ public void downloadFile(String sourceFilePathName,String contentType,String destFileName)

    其中,sourceFilePathName为要下载的文件名(带目录的文件全名),contentType为内容类型(MIME格式的文件类型信息,可被浏览器识别),destFileName为下载后默认的另存文件名。

    三、文件上传篇

    ㈠ 表单要求

    对于上传文件的FORM表单,有两个要求:

    1、METHOD应用POST,即METHOD="POST"。

    2、增加属性:ENCTYPE="multipart/form-data"

    下面是一个用于上传文件的FORM表单的例子:

    <FORM METHOD="POST" ENCTYPE="multipart/form-data"
          ACTION="/jspSmartUpload/upload.jsp">
          <INPUT TYPE="FILE" NAME="MYFILE">
          <INPUT TYPE="SUBMIT">
          </FORM>


    ㈡ 上传的例子

    1、上传页面upload.html

    本页面提供表单,让用户选择要上传的文件,点击"上传"按钮执行上传操作。

    页面源码如下:

    <!--
          文件名:upload.html
          作  者:纵横软件制作中心雨亦奇(zhsoft88@sohu.com)
          -->
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
          <head>
          <title>文件上传</title>
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
          </head>
          <body>
          <p>&nbsp;</p>
          <p align="center">上传文件选择</p>
          <FORM METHOD="POST" ACTION="jsp/do_upload.jsp"
          ENCTYPE="multipart/form-data">
          <input type="hidden" name="TEST" value="good">
          <table width="75%" border="1" align="center">
          <tr>
          <td><div align="center">1、
          <input type="FILE" name="FILE1" size="30">
          </div></td>
          </tr>
          <tr>
          <td><div align="center">2、
          <input type="FILE" name="FILE2" size="30">
          </div></td>
          </tr>
          <tr>
          <td><div align="center">3、
          <input type="FILE" name="FILE3" size="30">
          </div></td>
          </tr>
          <tr>
          <td><div align="center">4、
          <input type="FILE" name="FILE4" size="30">
          </div></td>
          </tr>
          <tr>
          <td><div align="center">
          <input type="submit" name="Submit" value="上传它!">
          </div></td>
          </tr>
          </table>
          </FORM>
          </body>
          </html>


    2、上传处理页面do_upload.jsp

    本页面执行文件上传操作。页面源码中详细介绍了上传方法的用法,在此不赘述了。

    页面源码如下:

    <%--
          文件名:do_upload.jsp
          作  者:纵横软件制作中心雨亦奇(zhsoft88@sohu.com)
          --%>
          <%@ page contentType="text/html; charset=gb2312" language="java"
          import="java.util.*,com.jspsmart.upload.*" errorPage="" %>
          <html>
          <head>
          <title>文件上传处理页面</title>
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
          </head>
          <body>
          <%
          // 新建一个SmartUpload对象
          SmartUpload su = new SmartUpload();
          // 上传初始化
          su.initialize(pageContext);
          // 设定上传限制
          // 1.限制每个上传文件的最大长度。
          // su.setMaxFileSize(10000);
          // 2.限制总上传数据的长度。
          // su.setTotalMaxFileSize(20000);
          // 3.设定允许上传的文件(通过扩展名限制),仅允许doc,txt文件。
          // su.setAllowedFilesList("doc,txt");
          // 4.设定禁止上传的文件(通过扩展名限制),禁止上传带有exe,bat,
          jsp,htm,html扩展名的文件和没有扩展名的文件。
          // su.setDeniedFilesList("exe,bat,jsp,htm,html,,");
          // 上传文件
          su.upload();
          // 将上传文件全部保存到指定目录
          int count = su.save("/upload");
          out.println(count+"个文件上传成功!<br>");
          // 利用Request对象获取参数之值
          out.println("TEST="+su.getRequest().getParameter("TEST")
          +"<BR><BR>");
          // 逐一提取上传文件信息,同时可保存文件。
          for (int i=0;i<su.getFiles().getCount();i++)
          {
          com.jspsmart.upload.File file = su.getFiles().getFile(i);
          // 若文件不存在则继续
          if (file.isMissing()) continue;
          // 显示当前文件信息
          out.println("<TABLE BORDER=1>");
          out.println("<TR><TD>表单项名(FieldName)</TD><TD>"
          + file.getFieldName() + "</TD></TR>");
          out.println("<TR><TD>文件长度(Size)</TD><TD>" +
          file.getSize() + "</TD></TR>");
          out.println("<TR><TD>文件名(FileName)</TD><TD>"
          + file.getFileName() + "</TD></TR>");
          out.println("<TR><TD>文件扩展名(FileExt)</TD><TD>"
          + file.getFileExt() + "</TD></TR>");
          out.println("<TR><TD>文件全名(FilePathName)</TD><TD>"
          + file.getFilePathName() + "</TD></TR>");
          out.println("</TABLE><BR>");
          // 将文件另存
          // file.saveAs("/upload/" + myFile.getFileName());
          // 另存到以WEB应用程序的根目录为文件根目录的目录下
          // file.saveAs("/upload/" + myFile.getFileName(),
          su.SAVE_VIRTUAL);
          // 另存到操作系统的根目录为文件根目录的目录下
          // file.saveAs("c:\temp\" + myFile.getFileName(),
          su.SAVE_PHYSICAL);
          }
          %>
          </body>
          </html>


    四、文件下载篇

    1、下载链接页面download.html

    页面源码如下:

    <!--
          文件名:download.html
          作  者:纵横软件制作中心雨亦奇(zhsoft88@sohu.com)
          -->
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
          <head>
          <title>下载</title>
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
          </head>
          <body>
          <a href="jsp/do_download.jsp">点击下载</a>
          </body>
          </html>


    2、下载处理页面do_download.jsp do_download.jsp展示了如何利用jspSmartUpload组件来下载文件,从下面的源码中就可以看到,下载何其简单。

    源码如下:

    <%@ page contentType="text/html;charset=gb2312"
          import="com.jspsmart.upload.*" %><%
          // 新建一个SmartUpload对象
          SmartUpload su = new SmartUpload();
          // 初始化
          su.initialize(pageContext);
          // 设定contentDisposition为null以禁止浏览器自动打开文件,
          //保证点击链接后是下载文件。若不设定,则下载的文件扩展名为
          //doc时,浏览器将自动用word打开它。扩展名为pdf时,
          //浏览器将用acrobat打开。
          su.setContentDisposition(null);
          // 下载文件
          su.downloadFile("/upload/如何赚取我的第一桶金.doc");
          %>


    注意,执行下载的页面,在Java脚本范围外(即<% ... %>之外),不要包含HTML代码、空格、回车或换行等字符,有的话将不能正确下载。不信的话,可以在上述源码中%><%之间加入一个换行符,再下载一下,保证出错。因为它影响了返回给浏览器的数据流,导致解析出错。

    3、如何下载中文文件

    jspSmartUpload虽然能下载文件,但对中文支持不足。若下载的文件名中有汉字,则浏览器在提示另存的文件名时,显示的是一堆乱码,很扫人兴。上面的例子就是这样。(这个问题也是众多下载组件所存在的问题,很少有人解决,搜索不到相关资料,可叹!)

    为了给jspSmartUpload组件增加下载中文文件的支持,我对该组件进行了研究,发现对返回给浏览器的另存文件名进行UTF-8编码后,浏览器便能正确显示中文名字了。这是一个令人高兴的发现。于是我对jspSmartUpload组件的SmartUpload类做了升级处理,增加了toUtf8String这个方法,改动部分源码如下:

    public void downloadFile(String s, String s1, String s2, int i)
          throws ServletException, IOException, SmartUploadException
          {
          if(s == null)
          throw new IllegalArgumentException("File '" + s +
          "' not found (1040).");
          if(s.equals(""))
          throw new IllegalArgumentException("File '" + s +
          "' not found (1040).");
          if(!isVirtual(s) && m_denyPhysicalPath)
          throw new SecurityException("Physical path is
          denied (1035).");
          if(isVirtual(s))
          s = m_application.getRealPath(s);
          java.io.File file = new java.io.File(s);
          FileInputStream fileinputstream = new FileInputStream(file);
          long l = file.length();
          boolean flag = false;
          int k = 0;
          byte abyte0[] = new byte[i];
          if(s1 == null)
          m_response.setContentType("application/x-msdownload");
          else
          if(s1.length() == 0)
          m_response.setContentType("application/x-msdownload");
          else
          m_response.setContentType(s1);
          m_response.setContentLength((int)l);
          m_contentDisposition = m_contentDisposition != null ?
          m_contentDisposition : "attachment;";
          if(s2 == null)
          m_response.setHeader("Content-Disposition",
          m_contentDisposition + " filename=" +
          toUtf8String(getFileName(s)));
          else
          if(s2.length() == 0)
          m_response.setHeader("Content-Disposition",
          m_contentDisposition);
          else
          m_response.setHeader("Content-Disposition",
          m_contentDisposition + " filename=" + toUtf8String(s2));
          while((long)k < l)
          {
          int j = fileinputstream.read(abyte0, 0, i);
          k += j;
          m_response.getOutputStream().write(abyte0, 0, j);
          }
          fileinputstream.close();
          }
          /**
          * 将文件名中的汉字转为UTF8编码的串,以便下载时能正确显示另存的文件名.
          * 纵横软件制作中心雨亦奇2003.08.01
          * @param s 原文件名
          * @return 重新编码后的文件名
          */
          public static String toUtf8String(String s) {
          StringBuffer sb = new StringBuffer();
          for (int i=0;i<s.length();i++) {
          char c = s.charAt(i);
          if (c >= 0 && c <= 255) {
          sb.append(c);
          } else {
          byte[] b;
          try {
          b = Character.toString(c).getBytes("utf-8");
          } catch (Exception ex) {
          System.out.println(ex);
          b = new byte[0];
          }
          for (int j = 0; j < b.length; j++) {
          int k = b[j];
          if (k < 0) k += 256;
          sb.append("%" + Integer.toHexString(k).
          toUpperCase());
          }
          }
          }
          return sb.toString();
          }


    注意源码中粗体部分,原jspSmartUpload组件对返回的文件未作任何处理,现在做了编码的转换工作,将文件名转换为UTF-8形式的编码形式。UTF-8编码对英文未作任何处理,对中文则需要转换为%XX的形式。toUtf8String方法中,直接利用Java语言提供的编码转换方法获得汉字字符的UTF-8编码,之后将其转换为%XX的形式。

    将源码编译后打包成jspSmartUpload.jar,拷贝到Tomcat的shared/lib目录下(可为所有WEB应用程序所共享),然后重启Tomcat服务器就可以正常下载含有中文名字的文件了。另,toUtf8String方法也可用于转换含有中文的超级链接,以保证链接的有效,因为有的WEB服务器不支持中文链接。
  • Date(日期)对象可以使用Date()构造器来创建,它没有参数,返回的数值就是当前的日期。下面的表格显示了为日期构造器的有效输入:

    var today = new Date();

    返回当前的日期和时间

    var newyear = new Date("December 31, 1998 23:59:59");

    输入的是表单的字符串 “月 日,年 小时:分钟:秒”

    var bday = new Date(75, 1, 16);

    参数是年份,月,日

    var bday = new Date(75, 1, 16, 12, 0, 0);

    参数是年份,月,日,小时,分钟,秒



    这里作点补充:月份是从0开始的,比如一月份=0,二月份=1,三月份=3等等。

    从上面可以看出创建一个日期对象是相对地简单,以下的表格是一系列的函数可以用于改变或者访问这些对象的属性:

    日期访问方法

    Method(方法)

    Description (描述)

    Value(数值)

    getYear()

    返回年份的最后两位数字

    2001

    getMonth()

    返回年份中的第几月(0到11)

    5

    getDate()

    返回月份中的第几日(1到31)

    2

    getDay()

    返回星期中的第几天 (0到6)

    6

    getTimezoneOffset()

    返回当地时间与格林尼治天文台平均时间的差别

    -480 (-8h)

    getHours()

    返回一天中的时针点数(0到23)

    16

    getMinutes()

    返回分钟 (0..59)

    8

    getSeconds()

    返回时间的秒 (0到59)

    24

    getTime()

    返回自从公元1970年1月1日的毫秒数

    991469304470



    这里注意:IE浏览器的一些版本返回Timezoneoffset数值是用错误的符号,比如用”-”代替”+”等等。

    日期设置方法

    setDate()

    设置每月中的第几天(从0到30)

    setHours()

    设置小时(从0到23)

    setMinutes()

    设置分钟(从0到59)

    setMonth()

    设置月份(从0到11)

    setSeconds()

    设置秒数(从0到59)

    setTime()

    设置时间(从公元以来的毫秒数)

    setYear()

    设置年份



    其它的日期方法

    parse

    转化日期字符串为自从公元以来的毫秒数,比如Date.parse(“date string”)

    toString()

    Sat Jun 2 16:08:24 UTC+0800 2001

    toGMTString()

    Sat, 2 Jun 2001 08:08:24 UTC

    toLocaleString()

    2001年6月2日 16:08:24



    所有的这些函数引用于独立的日期对象。如果你具有深厚的Java编程背景,那么你可以将它们认为是Date类的一些公共的方法而已。下面给出一个典型的例子来设置日期对象到当前时间加1年:

    var nextYear = new Date(); // 初始化日期对象

    nextyear.setYear(nextYear.getYear() + 1); // 增加1年

    实际上,parse函数是Date对象的一个方法,而不是一个独立的日期变量,如果使用Java术语,它就称为Date类的一个静态方法。这个正是我们为什么使用Date.pase()而不使用somedate.parse()的原因啦。
  • blogbus,你好

    2006-08-25

    循着朋友的足迹来到这里.感觉满清新的.

    决定在这里为自己安一个技术类博客的家.

    blogbus,你好.

    初次到来请多关照!