在初学Spring的时候, 会被告知, 在想要成为事务的方法之前加上一个@Transaction注解, 就可以将方法中的所有数据库操作都变成事务了.

现在从IOC和AOP一路学过来, 知道了注解背后肯定都隐藏着一批对应的类, 现在就和原来一样, 先直接看具体操作类, 再回头去看注解, 就会更加清晰了.

  1. Spring的事务类
  2. 三个接口
  3. PlatformTransactionManager实现类
  4. 事务同步管理器
  5. 事务传播行为
  6. 编程式的事务管理
  7. XML声明式事务
  8. 注解配置事务

Spring的事务类

Spring事务的亮点在于声明式事务管理. 在单数据源的情况下直接使用DataSource, 多数据源才会使用Java EE服务器的支持.

Spring 的 SPI (Service Provider Interface)提供了如下三个接口, 用于管理事务, 都在org.springframework.transaction包里:

  1. TransactionDefiniation, 这个接口负责从外部文件中载入XML配置或者注解, 或者手工编程的方式实现, 其实是一个属性类, 创建之后包含了描述一个事务的全部属性.
  2. TransactionStatus, 这个接口可以认为就代表了事务, 其中描述了这个事务的状态, 通过这个接口操作事务
  3. PlatformTransactionManager, 这个就是事务管理器, 其实就是使用第一个接口加载事务的属性, 然后根据事务的属性来操作第二个接口的对象.

来简单看一下这三个接口, 我把其称为事务属性, 事务状态, 事务管理器三个接口

三个接口

TransactionDefiniation这个接口是属性类, 说白了就是描述一个事务是什么样子的, 其中规定了如下属性:

  1. 事务隔离, 这个采用和Connection中的常量一致的方式定义事务隔离级别.
  2. 事务传播, 这个暂时还不太懂, 看上去应该是操作事务的方式.
  3. 事务超时, 即事务在超时之前能运行多久, 如果超过时间后, 事务就被回滚.
  4. 只读状态, 只读事务不会修改任何数据, 具体的事务管理器可以针对只读事务进行优化. 只读数据中如果试图修改数据, 则会触发异常.

TransactionStatus这个接口实际上就是一个事务的抽象, 事务管理器可以通过这个接口获取事务运行时候的状态, 然后可以通过这个接口回滚事务. 这个接口实际上继承自SavepointManager接口(JDBC 3.0提供嵌套事务的接口).

SavepointManager的方法如下:

  1. Object createSavepoint(), 创建保存点对象
  2. void rollbackToSavepoint(Object savepoint), 回滚到一个指定的保存点对象, 也就是上一个方法中创建的对象
  3. void releaseSavepoint(Object savepoint), 释放一个保存点, 也就是让一个保存点不再生效. 如果事务已经提交, 所有的保存点都会被释放

上边的三个方法如果失败, 都会抛出NestedTransactionNotSupported异常.

TransactionStatus又扩展了一些方法:

  1. boolean hasSavepoint(), 这个用来判断当前事务是否有一个保存点, 这是为了支持嵌套事务采用的方法.
  2. boolean isNewTransaction(), 判断当前事务是不是一个新的事务, 如果返回false表示是已经存在的事务, 或者当前操作没有运行在事务环境中.
  3. boolean isCompleted(), 判断当前事务是否已经结束, 结束就是提交或者回滚两种情况之一.
  4. boolean isRollbackOnly(), 判断当前事务使用已经被表示为仅能回滚
  5. boolean setRollbackOnly(), 将当前事务设置为仅能回滚, 只要进行了此标识, 事务管理器就只能回滚该事务, 会调用显式回滚命令或者干脆抛异常回滚事务.

PlatformTransactionManager是事务管理器, 也是Spring提供的高层事务抽象接口, 只有三个方法:

  1. TransactionStatus getTransaction(TransactionDefiniation transactionDefiniation), 获取一个已经存在的事务或者创建一个新的事务, 要根据传入其中的事务属性来决定
  2. void commit(TransactionStatus TransactionStatus), 根据事务的状态提交事务, 如果事务已经被标记为只能回滚, 提交也相当于回滚事务.
  3. void rollback(TransactionStatus TransactionStatus), 将事务回滚, 如果commit方法执行中有任何异常, 这个方法会被隐式的调用.

PlatformTransactionManager实现类

前边通过三个接口, 实际上搭建出了上层操作事务的抽象框架. 由于Spring将事务管理委托给具体的底层持久化框架来完成, 所以为不同的持久化框架提供了不同的实现类, 来看一下:

  1. org.org.springframework.orm.jpa.JpaTransactionManager, 对应JPA
  2. org.springframework.orm.hibernateX.HibernateTransactionManager, 对应Hibernate
  3. org.springframework.jdbc.datasource.DataSourceTransactionManager, 对应直接使用DataSource
  4. org.springframework.orm.jdo.JdoTransactionManager, 对应JDO
  5. org.springframework.transaction.jta.JtaTransactionManager, 这个是对应多个数据源的全局事务, 不分具体的持久化技术, 都使用这个事务管理器.

来看一下具体用法, 首先是需要使用DataSource的JDBCTemplate和Mybatis, 都需要一个DataSource的Bean, 然后配置DataSourceTransactionManager的Bean:

<bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:defaultAutoCommit="true" id="source"
      p:driverClassName="com.mysql.cj.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/sia5"
      p:username="root"
      p:password="******"
/>

<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="source" id="transactionManager"/>

这样就定义了一个叫做source的DataSource Bean, 一个叫做transactionManager的事务管理器类, 跑起来看看:

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

public class DataSourceTest {

    public static void main(String[] args) {

        Resource res = new FileSystemResource("D:\\Coding\\Java\\practice\\src\\main\\java\\spconfig.xml");
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions(res);

        DataSourceTransactionManager transactionManager = (DataSourceTransactionManager) factory.getBean("transactionManager");
        System.out.println(transactionManager);
    }
}

如果查看DataSourceTransactionManager可以知道, 这个有一个DataSource的依赖注入, 已经在XML中配置了, 可以想到的是直接用代码操作也是可以的.

再来看JPA, JPA是Sun官方的持久化规范, 其中管理事务的具体类有点绕, 想使用JPA, 先要去安装JPA的具体实现, 比较流行的Hibernate开发的JPA实现:

Hibernate ORM的页面可以看到, 其中一条是作为一个JPA Provider的实现. 写本文的时候稳定版是5.4, 6.0.0 alpha3正在发布中.

5.4 的兼容性是Java 8 or 11 和 JPA 2.2, 对于正好是使用Java 11的我来说很合适. Hibernate全套加上文档有65M大小, 所以还是通过Maven配置比较好:

<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.9.Final</version>
</dependency>

JPA的方式是:

  1. EntityManagerFactory依赖一个DataSource
  2. EntityManagerFactory.createEntityManager()创建一个EntityManager对象
  3. EntityManager对象调用getTransaction()方法获取一个EntityTransaction对象
  4. EntityTransaction就是JPA的事务管理器, 也是Spring的接口依赖的JPA事务管理器

在实际中, Spring的JPATransactionManager需要注入EntityManagerFactory类型即可, 所以需要配数据源, 然后配EntityManagerFactory, 最后配JPATransactionManager.

这其中要注意的是EntityManagerFactory只是一个接口, 具体的实现类, Spring提供了一个, 看下边XML配置的红色部分:

<bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:defaultAutoCommit="true" id="source"
      p:driverClassName="com.mysql.cj.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/sia5"
      p:username="root"
      p:password="********"
/>

<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" p:dataSource-ref="source" id="factory"/>

<bean class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="factory" id="jpaTransactionManager"/>

通过XML配置其实也可以看出来各个类的内部属性. 用代码启动就不放了, 因为使用到JPA的话, 还需要配置persistence.xml文件, 这个暂时还不会.

Hibernate相比前边两个, 是一个完整的ORM框架, 使用自己的Session对象封装了Connection和DataSource对象, 所以需要创建一个Session的Bean, 然后再创建Spring 的 HibernateTransactionManager:

<bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:defaultAutoCommit="true" id="source"
   p:driverClassName="com.mysql.cj.jdbc.Driver"
   p:url="jdbc:mysql://localhost:3306/sia5"
   p:username="root"
   p:password="******"
/>

<bean class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" p:dataSource-ref="source" id="sessionFactory"/>

<bean class="org.springframework.orm.hibernate4.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory" id="hibernateTransactionManager"/>

也可以用代码来验证:

public static void main(String[] args) {
    Resource res = new FileSystemResource("D:\\Coding\\Java\\practice\\src\\main\\java\\spconfig.xml");
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.loadBeanDefinitions(res);

    HibernateTransactionManager hibernateTransactionManager = (HibernateTransactionManager) factory.getBean("hibernateTransactionManager");

    System.out.println(hibernateTransactionManager);
}

可以看到, Spring针对不同的技术, 首先使用自己提供的那个技术的一个实现对象, 以符合那个框架要求并且封装DataSource, 然后再用那个技术对应的事务管理器对象, 将刚才的技术实现对象封装在其中. 这样就实现了对不同技术的适配.

事务同步管理器

这是个小知识点, 由于这些技术的底层都是用到DataSource对象或者是包装而来的Session对象, 而这些资源是不能够多线程共享的.

Spring提供了一个org.springframework.transaction.support.TransactionSynchronizationManager作为基础, 内部使用ThreadLocal来存放不同的资源副本.

Spring默认要装配成单例, 一定要多线程共享, 所以就通过辅助工具类, 为每个线程提供了不同的资源副本, 以此来达到可以多线程使用的效果.

这些类如果你使用模板, 后台会自动操作, 但如果手工操作, 还是需要知道一下:

  1. org.springframework.jdbc.datasource.DataSourceUtils
  2. org.springframework.orm.hibernateX.SessionFactoryUtils
  3. org.springframework.orm.jpa.EntityManagerFactoryUtils
  4. org.springframework.orm.jdo.PersistenceManagerFactoryUtils

这些Utils的作用都是从TransactionSynchronizationManager类中获取和当前线程绑定的资源, 比如JDBC系的就是获取Connection, 而Hibernate系的就是获取Session.

如果直接将模板制作成Bean, 则无需考虑这些, 如果手工的话, 就要在线程中使用这些Utils获取.

事务传播行为

所谓事务传播行为, 就是在调用一个服务的时候, 内部还会调用其他服务, 这个时候事务必须以合理的方式继续控制嵌套的其他服务, 否则就会出问题.

事务传播行为的具体定义, 在TransactionDefinition接口中规定, 一共有7种:

int PROPAGATION_REQUIRED = 0; //如果当前没有事务, 则新建事务, 如果有事务, 则加入到这个事务中, 这是默认配置, 也是最常见的用法, 即嵌套的事务会累加成一个事务
int PROPAGATION_SUPPORTS = 1; //使用当前事务, 如果当前没有事务, 就以非事务方式执行
int PROPAGATION_MANDATORY = 2; //使用当前的事务, 如果没有, 就抛异常
int PROPAGATION_REQUIRES_NEW = 3; //新建事务, 如果当前存在事务, 把当前事务挂起
int PROPAGATION_NOT_SUPPORTED = 4; //以非事务方式执行, 如果存在事务, 就把事务挂起
int PROPAGATION_NEVER = 5; //以非事务方式执行, 如果存在事务, 就抛异常
int PROPAGATION_NESTED = 6; //如果当前存在事务, 则在嵌套事务内执行; 如果当前没有事务, 则执行与PROPAGATION_REQUIRED一样的操作. 这个需要JDBC 3.0的支持, 并且需要支持保存点事务.

如果不是特殊的情况, 一般都采用第一种, 这样嵌套的所有服务都会追加到同一个事务中, 要么一起失败, 要么全部完成.

编程式的事务管理

前边的三个类和对应的技术, 实际上通过学习可以知道, 在使用Spring提供的模板时候可以自动被应用.

当然Spring也提供了手动管理事务的类org.springframework.transaction.support.TransactionTemplate, 看来模板还真多, 连事务也有管理模板.

这个模板的核心就是先要设置一个TransactionManager对象, 然后还可以设置一个回调接口.

模板中如果需要访问底层连接, 必须使用Utils工具, 不能直接访问. 如果模板中的访问数据对象是Spring提供的模板类, 就不需要.

这个简单了解一下即可.

XML声明式事务

走到这一步, 基本上就是使用声明或者注解, 让Spring使用AOP技术, 将应用程序员编写的与数据持久化相关的类, 进行改造, 让其可以进行事务的过程.

前边的所有类, 最终目的都是为了将服务接口(即经常编写的XXXXService接口及其实现类)来进行事务增强. 有两种办法, 一种是XML配置, 一种是在接口的实现类上添加注解.

这里之前配好了DataSource, 而且可以正常工作, 现在可以编写一个简单的查找和添加的接口和实现类, 然后对齐进行增强, 看一下两种配置方式.

一个比较典型的DAO接口及其实现类, Service接口及实现类如下:

public interface SomeDao {

    User getUser();

    void updateUser(User user);

}
public class SomeDaoImpl implements SomeDao {

    @Override
    public User getUser() {
        return new User("cony", 5);
    }

    @Override
    public void updateUser(User user) {
        System.out.println("成功添加 " + user);
    }
}
public interface SomeService {

    User getUser();

    void update(User user);
}
public class SomeServiceImpl implements SomeService {

    private SomeDao someDao;

    public SomeServiceImpl(SomeDao someDao) {
        this.someDao = someDao;
    }

    @Override
    public User getUser() {
        return someDao.getUser();
    }

    @Override
    public void update(User user) {
        someDao.updateUser(user);
    }
}

很显然, 如果配置在Spring中的话, SomeServiceImpl及依赖注入的SomeDao的实现类都会被配置为一个单例的Bean, 这里假设SomeDao的实现类使用了Spring提供的那些模板读取数据, 那么很显然, 需要将SomeServiceImpl改造成支持事务的类, 以让其中的业务方法都带上事务.

通过前边学习可以知道, XML配置事务管理, 一定要先把事务管理器给配出来, 在Spring2.0之前的版本, Spring是通过一个特殊的代理类TransactionProxyFactoryBean来提供事务增强的, 配置如下:

<!--DataSource-->
<bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:defaultAutoCommit="true" id="source"
      p:driverClassName="com.mysql.cj.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/sia5"
      p:username="root"
      p:password="********"/>

<!--TrasactionManager-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="source"
      id="transactionManager"/>

<!--把两个接口实现类的Bean配进来-->
<!--SomeDao的实现类-->
<bean id="somedaoimpl" class="cc.conyli.trasaction.SomeDaoImpl"/>
<!--SomeServiceImpl的实现类, 这个也是要进行业务增强的Bean-->
<bean id="serviceimpl" class="cc.conyli.trasaction.SomeServiceImpl">
    <constructor-arg ref="somedaoimpl"/>
</bean>

<!--配置代理类-->
<!--<bean id="transaction"-->
<bean id="enhanced" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
      p:transactionManager-ref="transactionManager"
      p:target-ref="serviceimpl">
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED, readOnly</prop>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

这里使用的是JDBC的DataSource-事务管理器类. 关键的代理类是TransactionProxyFactoryBean, 这个类使用要增强的类外加事务管理器类来生成一个代理类, 使用了AOP技术.

其中的transactionAttributes是配置事务的属性, prop中的key指的是用字符串的形式匹配方法名, 标签主体是事务属性信息.

事务属性信息由五部分组成, 分别是 传播行为, 隔离级别, 是否为只读事务, 发生哪些异常要回滚, 发生哪些异常要继续提交事务.

传播行为必须要设置, 如果不设置, 就说明不使用事务. 后四个都是可选的, 这些也都是字符串匹配规则.

TransactionProxyFactoryBean是比较老的配置方法, 而且要定义多个Bean. 实际上在Spring2.0就开始引入了AOP, 然后在XML配置的Schema中引入了一个新的tx命名空间, 专门用来配置事务.

使用如今的Spring配置的话, 需要结合aop命名空间和tx命名空间来进行配置:

<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:util="http://www.springframework.org/schema/util"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"

           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <util:properties id="properties" location="config.properties"/>
    <context:property-placeholder properties-ref="properties"/>
    <context:component-scan base-package="cc.conyli"/>

        <!--DataSource依然需要-->
    <bean class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:defaultAutoCommit="true" id="source"
          p:driverClassName="com.mysql.cj.jdbc.Driver"
          p:url="jdbc:mysql://localhost:3306/sia5"
          p:username="root"
          p:password="********"
    />

        <!--TrasactionManager事务管理器也需要-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="source"
          id="transactionManager"/>

        <!--创建一个增强准备织入给要进行事务增强的类-->
    <tx:advice id="txadvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="false"/>
            <tx:method name="add*" rollback-for="Exception"/>
            <tx:method name="update*"/>
        </tx:attributes>
    </tx:advice>

        <!--    把增强织入到实现类中, 直接创建一个代理类, 这个是之前看过的内容了, 创建一个切点, 然后使用增强Bean来创建一个切面进行织入-->
    <aop:config>
        <aop:pointcut id="serviceMethod" expression="execution(* cc.conyli.trasaction.SomeServiceImpl.*(..))"/>
        <aop:advisor advice-ref="txadvice" pointcut-ref="serviceMethod"/>
    </aop:config>

</beans>

XML要注意, 命名空间上边有xmlns:tx=”http://www.springframework.org/schema/tx”, 在xsi:schemaLocation中就必须要有一对地址, 包括上边的命名空间和以xsd结尾的地址.

这个配置依然是先装配DataSource和事务管理器, 不同点在于不需再去创建两个业务Bean了, 直接使用事务管理器创建增强, 再织入到指定的方法里.

这里如果初看, 会想transactionManager并不是一个advice, 怎么就直接织入了. 这实际上是使用了特别的tx命名空间, 这个标签在内部会使用我们创建的事务管理器来生成一个增强类, 并不需要手工编写增强类.

tx:advice生成的增强类, 就可以被aop标签给织入指定的类的方法中了. 只要理解了tx命名空间的自动生成advice的功能就可以了.

tx:method中的配置和之前差不多, 都是一条字符串的匹配对应一条属性定义的规则, 主要规则如下:

  1. name, 表示方法名匹配字符串, 必需配置, 其他都是不必需的
  2. propagation, 事务传播行为
  3. isolation, 隔离级别
  4. timeout, 默认是-1, 表示交给具体的事务系统决定
  5. read-only, 默认是false, 表示事务是否只读
  6. rollback-for, 默认所有异常都回滚, 也是字符串匹配, 可以设置多个
  7. no-rollback-for-for, 默认所有检查型异常都不回滚, 用于配置不触发回滚的异常, 也是字符串匹配.

注解配置事务

前边的所有铺垫, 其实都是为了引入注解配置事务.

如果都使用默认的话, 只需要在要进行注解增强的类之前加上@Transactional注解即可.

刚才的类如果开启扫描自动装配的话, 就是这样:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Service
public class SomeServiceImpl implements SomeService {

    private SomeDao someDao;

    public SomeServiceImpl(SomeDao someDao) {
        this.someDao = someDao;
    }

    @Override
    public User getUser() {
        return someDao.getUser();
    }

    @Override
    public void update(User user) {
        someDao.updateUser(user);
    }
}

注意这里的@Transactional是Spring提供的注解类, 不是javax.transaction.Transactional, 当然, 和其他注解一样, 要生效的话, 还必须在XML中加上一条配置:

<tx:annotation-driven transaction-manager="transactionManager"/>

可见XML中DataSource和事务管理器还是要配的, 但是使用了这条tx:annotation-driven, 学过前边AOP就可以知道, 其他的不用配了, 然后Spring在扫描到@Transactional注解的时候, 就会使用transactionManager按照注解的元数据生成对应的切面并织入了.

现在就来看看@Transactional注解类的源代码:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    //事务管理器的名称, 如果不指定, 而且容器里只有一个, 就会用这个, 否则需要指定.
    String value() default "";

    //传播方式
    Propagation propagation() default Propagation.REQUIRED;

    //隔离级别
    Isolation isolation() default Isolation.DEFAULT;

    //过期时间
    int timeout() default -1;

    //只读事务
    boolean readOnly() default false;

    //一组异常类名, 遇到就回滚
    String[] rollbackForClassName() default {};

    //一组异常类, 遇到不回滚
    Class<? extends Throwable>[] noRollbackFor() default {};

    //一组异常类名, 遇到不回滚
    String[] noRollbackForClassName() default {};
}

可见这个注解可以定义在方法或者类上, 定义在方法上注解会覆盖使用类的注解. 在实际业务中, 要将这个注解使用在实现类上, 而不是接口上, 因为注解是无法继承的.

这里唯一要解释的就是String value() default “”; 这其中的字符串, 是可以设置为一个名称, 在XML中的事务管理器中配置, 比如:

<!--TrasactionManager事务管理器也需要-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="source"
      id="transactionManager">
    <qualifier value="saner"/>
</bean>  

红字部分定义了一个名称, 如果有多个事务管理器的话, 就可以通过这个名称来引用, 比如:

@Transactional("saner")
@Service
public class SomeServiceImpl implements SomeService {
......
}

如果事务管理器比较多, 可以定义一个带有@Trasaction(“XXX”)的注解, 来使用自定义的注解.

总结一下来说, 如果底层使用了Spring的模板, 就解决了多线程的问题, 然后只需要辅之以XML配置和注解在Service类上即可.

如果手工编程, 首先需要改造DataSource, 让Dao类可以多线程操作, 根据不同的技术来创建具体的事务管理器, 最后再操作.

现在理论看完了, 一般Web程序都分为Controller-Web展现层, Service, Dao三层, 实践中到底怎么用, 继续研究吧.