`

实例详解 EJB 中的六大事务传播属性

 
阅读更多

前言

事务 (Transaction) 是访问并可能更新数据库中各种数据项的一个程序执行单元 (unit)。在关系数据库中,一个事务可以是一条或一组 SQL 语句,甚至整个程序。它有通常被称为 ACID 的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability)四大特性:

原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(Durability):持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

在 Java EE 的应用开发中,事务的应用是必不可少的,同时由于方法调用的原因,比如方法 A 调用方法 B 的时候。如果方法 A 在事务环境中运行,那么方法 B 是否也要在事务中运行呢,方法 B 是要和方法 A 在同一个事务中运行还是新开起一个事物呢?等等。要弄清楚这些问题,就要牵涉到事务传播属性的问题,EJB 中针对不同的的情况提供了下面六种不同的事物传播属性:

Required:用该属性标注的方法或组件总是在事务中运行。如果客户端已经在事务中,则在原事务中运行;如果没有事务,则开启一个新事务,在其中运行。

Requires_New:方法或组件总是在新开启的事务中运行。如果客户端已经在事务中,则首先将原事务挂起,然后新开启一个事务,在其中运行,新事务结束之后,原来事务从挂起点继续执行;如果没有事务,则开启一个新事务,在其中运行。

Supports:和 Required 属性的不同点是,在没有事务的环境中不会开启一个新事务;如果存在事务的话则加入其中运行,这点和 Reuqired 相同。

Not_Supported:如果事务已经存在的情况下,则原来的事务要挂起,然后调用标注该属性的方法或组件,调用结束之后,继续原来的事务;无事务环境中调用的时候,不开启新事务,这点和 Supports 相同。

Mandatory:调用标注该属性的方法或组件的客户端,必须已经在事务中,如果不在事务中则会抛出异常;如果已经在事务中,则加入原来事务运行。和 Required 不同的是,该属性不会自动开启新的事务;

Never用 Never 属性标注的方法或组件,不能在事务中运行。如果调用该方法或组件的客户端已经在事务中,则抛出异常。

下面就实例详细介绍一下 EJB 中这六种不同的事务传播属性。

 

前期准备工作

首先,我们创建如下几个类,来作为我们后续中的实例。


清单 1. Address 实体 Bean

				 
 @Entity 
 public class Address implements Serializable { 
    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id; 
    private String country; 
    private String city; 
    private String street; 
    private String postCode; 
    private String TsAttribute; 
    @Temporal(javax.persistence.TemporalType.TIMESTAMP) 
 private Date createTime; 
 // Getters and Setters 
 } 

 

我们在 Address 实体 Bean 中添加一些字段:


清单 2. Person 实体 Bean

				 
 @Entity 
 public class Person implements Serializable { 
    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Long id; 
    private String firstName; 
    private String lastName; 
    private int age; 
    private String TsAttribute; 
     @Temporal(javax.persistence.TemporalType.TIMESTAMP) 
 private Date createTime; 
 // Getters and Setters 
 } 

 

同样我们在 Person 实体 Bean 中添加一些字段:


清单 3. 无状态的 SessionBean CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createAddress() { 
     
        Address address = new Address(); 
			       /* 
			        * 
			        * 对 address 对象
			        * 的属性进行赋值
			        * 
			        */ 
			
        em.persist(address); 
       
    } 
 } 

 

我们在 CommonEJB 中创建了一个名为 createAddress() 的业务方法,使用这个方法来持久化 Address 实体 bean,因此我们也使用 @PersistenceContext 注入了相应的持久化单元,我们将会将 Address 持久化到这个持久化单元对应的数据库中。


清单 4. 无状态的 SessionBean ClientEJB

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 

			        /*
			        *
			        * 对 person 对象
			        * 的属性进行赋值
			        *
			        */
			 
        em.persist(person); 
        commonEJB.createAddress();// 调用 CommonEJB 中的 createAddress 方法
        
    } 
 } 

 

同样,我们在 ClientEJB 创建了一个名为 createPerson() 的业务方法,使用这个方法来持久化 Person 实体 bean。稍有不同的是我们不仅注入了相应的持久化单元,而且注入了 CommonEJB 到这个 EJB 中,并且在 createPerson() 方法中调用了 CommonEJB 中的业务方法。

好了现在,我们所有的准备工作都已完成下面我们开始逐一介绍这六大事务传播属性。

 

传播属性实例祥解

Required

当一个方法的事务传播属性被设置成为“Required”的时候,说明该方法需要在事务的环境中运行。如果调用该方法的客户端不在事务中,这个时候,当该方法执行的时候就会开启一个新的事务;相反,如果调用该方法的方法已经运行在一个事务之中,那么该方法就会加入原来的事务运行。

下面举例说明一下


清单 5. 调用 ClientEJB 的 Main 函数

				 
 public class Main { 
    @EJB 
    private static ClientEJBRemote clientEJB; 
    
    public static void main(String[] args) { 
        
      clientEJB.createPerson(); 
    } 
 } 

 

我们使用 NetBeans 中独有的技术来调用 EJB,这样的话我们就不需要手动使用 JNDI 了,直接使用依赖注入的技术,在 main() 方法中注入我们要调用的 EJB,这里我们调用的是名为 ClientEJB 的 EJB。根据自 Java EE 5 引入的“Configuration by Exception”的原则,确省情况下这个 EJB 使用的是”Required”这个事务传播属性,根据”Required”事务传播属性的要求,ClientEJB 被 main() 方法调用的时候会开启一个新的事务。


清单 6.ClientEJB 持久化 Person 实体 Bean

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(100l); 
        person.setFirstName("Leo"); 
        person.setLastName("Wang"); 
        person.setAge(88); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
       
        commonEJB.createAddress(); 

        System.out.println("----ClientEJB Excute completely!-------"); 
    } 
 } 

 

在 ClientEJB 这个无状态 EJB 中的 createPerson() 业务方法中我们持久化了一个 Person 对象到数据库中。


清单 7.CommonEJB 持久化 Address 实体 Bean

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public void createAddress() { 
      
        Address address = new Address(); 
        address.setId(200l); 
        address.setCountry("China"); 
        address.setCity("Beijing"); 
        address.setStreet("Long Jin"); 
        address.setPostCode("102208"); 
        address.setTsAttribute("REQUIRED"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
       
        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

在 CommonEJB 这个无状态 EJB 中的 createAddress () 业务方法中我们持久化了一个 Person 对象到数据库中。

TransactionAttribute 这个注解的定义如下:


清单 8.TransactionAttribute 注解定义

				 
 @Target(value={METHOD,TYPE}) 
 @Retention(value=RUNTIME) 

 

从它的定义中可以看出,这个注解可以定义在类的层次上,也可以定义在方法上。如果两个层次同时定义的话,定义在方法上的属性会覆盖定义在类层次上的属性。EJB 中类层次上的和方法层次上的传播属性默认均为”Required”。

这里我们显示地将 CommonEJB 中的 CreateAddress() 方法的传播属性设置成了”Required”,虽然我们没有必要这么做,他确省就是”Required”。我们在 ClientEJB 中的 CreatePerson() 方法中调用了这个 CreateAddress() 方法,根据”Required”传播属性的定义,CreateAddress() 方法将会加入调用者 CreatePerson() 开启的事务当中,成为其中的一部分。下面是这个程序运行的结果


图 1.GlassFish 控制台输出
图 1.GlassFish 控制台输出 

图 1 显示两个方法均执正常行完毕,没有任何异常抛出。


图 2. 实体 Bean Person 对应的数据库表
图 2. 实体 Bean Person 对应的数据库表 

CreatePerson() 方法正常执行完毕后,ID 为 200 的人被持久化到数据库中。


图 3. 实体 Bean Address 对应的数据库表
图 3. 实体 Bean Address 对应的数据库表 

CreateAddress() 方法正常执行完毕后,ID 为 100 的地址被持久化到数据库中。

这就说明这两个方法均在事务的环境中进行了持久化的操作,且没有回滚。

下面我们使用依赖注入,将 SessionContext 注入到 ClientEJB,并在 createPerson() 中调用 setRollbackOnly() 将这个方法所在的事务设置成 Doomed 事务。Doomed 事务就是那些铁定要回滚的事务,无论他进行了什么操作,无论成功与否,都要回滚,这就是他的宿命。


清单 9. 客户端 createPerson() 回滚

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
 @Resource 
    private SessionContext ctx; 
    @Override 
  
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(100l); 
        person.setFirstName("Leo"); 
        person.setLastName("Wang"); 
        person.setAge(88); 
        person.setHeight(170); 
        person.setWeight(65); 
        person.setCreateTime(new Date()); 
        em.persist(person); 
       
        commonEJB.createAddress(); 

        ctx.setRollbackOnly(); 

        System.out.println("-----ClientEJB Excute completely!---------"); 
    } 
 } 

 

这个时候 我们保持 CommonEJB 不变,然后执行这个程序。

使用同样的办法,我们将 ClientEJB 保持不变,将 CommonEJB 中的 createAddress() 方法所在的事务设置成 Doomed 事务,然后执行程序。


清单 10. 被调用者 createAddress() 回滚

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
 EntityManager em; 
 @Resource 
 private SessionContext ctx; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public void createAddress() { 
      
        Address address = new Address(); 
        address.setId(200l); 
        address.setCountry("China"); 
        address.setCity("Beijing"); 
        address.setStreet("Long Jin"); 
        address.setPostCode("102208"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
       
 ctx.setRollbackOnly(); 
        System.out.println("--------ClientEJB Excute completely!--------"); 
    } 
 } 

 

上述两种情况,从控制台上我们均可以看出两个方法正确执行完毕,但是两个方法中数据均没有被持久化到数据库中,这就是说,他们所在的事务一起进行了回滚。出现这样的情况是因为,”Required”属性是加入原有事务,也就是说它们处于同一个事物当中,一方滚另一方也回滚。

Requires_New

当一个方法的事务传播属性被设置成为“Requires_New”的时候。如果调用该方法的客户端不在事务中,这个时候,当该方法执行的时候就会开启一个新的事务;相反,如果调用该方法的方法已经运行在一个事务之中,那么该方法同样会开启一个新的事务,并在新事物中运行,而原来的事务则挂起,等待新开启的事情执行完毕之后再继续执行。


清单 11 .Requires_New 属性中 ClientEJB 客户端

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(88l); 
        person.setFirstName("Tom"); 
        person.setLastName("Zhang"); 
        person.setAge(88); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

我们仍然保持 ClientEJB 的默认属性不变,而仅仅将 CommonEJB 中的 createAddress() 方法的事务传播属性设置成”Requires_New”,如清单 12 所示


清单 12. Requires_New 属性中 CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void createAddress() { 
      
        Address address = new Address(); 
        address.setId(55l); 
        address.setCountry("China"); 
        address.setCity("Shanghai"); 
        address.setStreet("Long Jin"); 
        address.setPostCode("102208"); 
        address.setTsAttribute("REQUIRES_NEW"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
       
        System.out.println("----------ClientEJB Excute completely!--------"); 
    } 
 } 

 

当我们运行程序的时候其结果如下,说明程序正确执行完毕,数据也持久化到了数据库中


图 4 .Person 表
图 4 .Person 表 

CreatePerson() 方法正常执行完毕后,ID 为 88 的人被持久化到数据库中。


图 5 .Address 表
图 5 .Address 表 

CreateAddress() 方法正常执行完毕后,ID 为 55 的地址被持久化到数据库中。

下面我们将 ClientEJB 设置为 Doomed 事务,而保持 CommonEJB 不变,看看是什么情况。

外围事务回滚,不影响新开启的事务。


清单 13. Requires_New 属性中被设定为 Doomed 的 ClientEJB

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Resource 
    SessionContext ctx; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(88l); 
        person.setFirstName("Tom"); 
        person.setLastName("Zhang"); 
        person.setAge(88); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
        
        ctx.setRollbackOnly(); 

        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

当我们执行完程序之后,Glass Fish 控制台显示程序正确执行没有异常抛出,但是数据库中显示只有 Address 被持久化到了数据库中。


图 6.Address 表
图 6.Address 表 

这就因为 createPerson() 所在的事务进行了回滚,而 createAddress() 所在的事务没有回滚。

内围事务回滚,不影响外围事务。

下面我们将 CommonEJB 设置为 Doomed 事务,而保持 ClientEJB 不变,看看是什么情况。


清单 14.Requires_New 属性中被设定为 Doomed 的 CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Resource 
    SessionContext ctx; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public void createAddress() { 
      
        Address address = new Address(); 
        address.setId(55l); 
        address.setCountry("China"); 
        address.setCity("Shanghai"); 
        address.setStreet("Long Jin"); 
        address.setPostCode("102208"); 
        address.setTsAttribute("REQUIRES_NEW"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
        
        ctx.setRollbackOnly(); 
       
        System.out.println("----------ClientEJB Excute completely!--------"); 
    } 
 } 

 

当我们正确执行完程序之后,数据库中只有 Person 的记录,这就说明 createAddress() 所在的方法进行了回滚,而 createPerson() 没有。


图 7.Person 表
图 7.Person 表 

以上两种情况说明,Require_New 开启的是一个新事务,外围事务也就是调用者或客户端所在事务的回滚,不会影响到这个新开起的事务;同样,新开起的事务的回滚与否对外围事务也没有任何影响。

Supports

当一个方法的事务传播属性被设置成为“Supports”的时候,如果调用该方法的客户端不在事务中,这个时候,当该方法执行的时候不会开启一个新的事务,仍会在无事务的环境中运行;相反,如果调用该方法的方法已经运行在一个事务之中,那么该方法就会加入原来的事务运行,这点和“Required”相同。


清单 15.Supports 属性中 ClientEJB 客户端

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(33l); 
        person.setFirstName("Jerry"); 
        person.setLastName("Leoo"); 
        person.setAge(22); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 

        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

我们仍然保持 ClientEJB 的默认属性不变,而仅仅将 CommonEJB 中的 createAddress() 方法的事务属性设置成“Supports“。


清单 16.Supports 属性中 CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.SUPPORTS) 
    public void createAddress() { 
      
        Address address = new Address(); 
        address.setId(66l); 
        address.setCountry("USA"); 
        address.setCity("NewYork"); 
        address.setStreet("Seventh Avenue"); 
        address.setPostCode("123-456"); 
        address.setTsAttribute("SUPPORTS"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
       
        System.out.println("----------ClientEJB Excute completely!--------"); 
    } 
 } 

 

当我们执行之后,可以看到两个方法中的数据均持久化到了数据库中


图 8.Person 表
图 8.Person 表 

图 9.Address 表
图 9.Address 表 

当调用者自己新开起一个事务,或已经处于某个事务之中的时候,被调用者会加入调用者的事务。这样调用者和被调用者就处于同一个事务之中,在任何一个方法内出现引起事务回滚的事件,调用者和被调用者都要回滚。

当调用者不在事务中运行,而被调用者的事务传播属性为“SUPPORTS”时,被调用者也不会开启新事务,仍旧在无事务的环境中运行,这个时候和普通得 Java 方法之间的调用毫无区别,任何一个 Java 程序员对这种情况都会司空见惯,这里也不再赘叙。

Not_Supported

当一个方法的事务传播属性被设置成为“Not_Supported”的时候,如果调用该方法的客户端不在事务中,这个时候,当该方法执行的时候也不会开启一个新的事务,而仍是在无事务中的环境中运行,这点和用无事务的方法调用传播属性为”Suppots”的方法相同,不再赘叙;相反,如果调用该方法的客户端已经运行在一个事务之中,那么原来的事务则挂起,等待被调用者在无事务的环境中运行完毕之后,再继续执行原来挂起的事务,原来事务的回滚与否对被调用者均无影响。


清单 17.Not_Supported 属性中 ClientEJB 客户端

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(123l); 
        person.setFirstName("Marry"); 
        person.setLastName("Bush"); 
        person.setAge(22); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 

        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

ClientEJB 中 createPerson() 方法我们仍然使用默认传播属性,而将 CommonEJB 中的 createAddress() 的传播属性设置成了 Not_Supported。


清单 18.Not_Supported 属性中 CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 
    public void createAddress() { 

			       /*
			        * 此处省略了一些业务处理的代码 ,
			        * 因为 persist(Object obj) 必须在事务中执行
			        * 所以此处暂时不能使用
			        *
			        */
			 
        System.out.println("----------ClientEJB Excute completely!--------"); 
    } 
 } 

 

运行程序完毕之后,从图 10 中可以看到 ID 为 123 的 Person 已经持久化到了数据库中,说明 createPerson() 所在事务在调用 createAddress() 方法时,把事务挂起,当 createAddress() 执行完毕,继续事务的过程中完成了提交。


图 10.Person 表
图 10.Person 表 

如下所示,我们将 createPerson() 方法新开起的事务设定成 Doomed 事务,执行完毕后,由于事务的回滚,Person 数据并没有持久化到数据库中。而 createAddress() 方法一直在无事务的环境中运行,所以当外围事务回滚的时候,对他并没有人很影响。


清单 19.Not_Supported 属性中 ClientEJB 客户端

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Resource 
    SessionContext ctx; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(123l); 
        person.setFirstName("Marry"); 
        person.setLastName("Bush"); 
        person.setAge(22); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
        ctx.setRollbackOnly(); 
        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

Mandatory

当一个方法的事务传播属性被设置成为“Mandatory”的时候,说明这个方法必须在一个事务环境下执行。如果调用该方法的客户端不在事务中,这个时候,调用该方法时,就会抛出 javax.ejb.EJBTransactionRequiredException 异常;相反,如果调用该方法的方法已经运行在一个事务之中,那么该方法就会加入原来的事务运行,这点和“Required”相同。


清单 20.Mandatory 属性中 ClientEJB 客户端

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(88l); 
        person.setFirstName("Min"); 
        person.setLastName("Zhao"); 
        person.setAge(22); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

我们仍然保持了 ClientEJB 中 createPerson() 方法的默认传播属性,而将 CommonEJB 中的 createAddress() 方法事务传播属性设置成了”Mandatory”。


清单 21.Mandatory 属性中 CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.MANDATORY) 
    public void createAddress() { 
     
        Address address = new Address(); 
        address.setId(66l); 
        address.setCountry("Japan"); 
        address.setCity("Tokyo"); 
        address.setStreet("Seventh Avenue"); 
        address.setPostCode("444-789"); 
        address.setTsAttribute("MANDATORY"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
       
        System.out.println("----------ClientEJB Excute completely!--------"); 
    } 
 } 

 

运行结果如下图所示,说明两个方法都是在事务中得到了执行。由于使用“Mandatory”传播属性的方法是加入原来的外围事务,也就是说它们处于同一个事务当中,所以在任何一个方法中如果调用 setRollbackOnly() 方法将事务设定成 Doomed 事务后,事务中的所有方法的持久化操作都会随着事务的回滚而回滚。这里不再重复举例。


图 11.Person 表
图 11.Person 表 

图 12.Address 表
图 12.Address 表 

如清单 22 我们把 ClientEJB 的传播属性修改为”Mandatory”,因为 Main() 不在事务中运行,所以在 Main() 方法中调用 ClientEJB 是就会抛出异常。

例如:


清单 22.Mandatory 属性中 ClientEJB

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.MANDATORY) 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(88l); 
        person.setFirstName("Min"); 
        person.setLastName("Zhao"); 
        person.setAge(22); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

当我们再次运行的时候,控制台就会抛出如下异常:


图 13.EJBTransactionRequiredException
图 13.EJBTransactionRequiredException 

Never

当一个方法的事务传播属性被设置成为“Never”的时候,说明这个方法必须在无事务环境下执行。如果调用该方法的方法不在事务中,该方法将会在无事务环境中运行;相反,如果调用该方法的方法已经运行在一个事务之中,那么调用该方法的时候就会抛出“RemoteException”异常。


清单 23.Never 属性中的 ClientEJB 客户端

				 
 @Stateless 
 public class ClientEJB implements ClientEJBRemote { 
    @EJB 
    private CommonEJBRemote commonEJB; 
    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    public void createPerson() { 
        Person person = new Person(); 
        person.setId(88l); 
        person.setFirstName("Ying"); 
        person.setLastName("Tong"); 
        person.setAge(22); 
        person.setTsAttribute("Required"); 
        
        person.setCreateTime(new Date()); 
        em.persist(person); 
        commonEJB.createAddress(); 

        System.out.println("------ClientEJB Excute completely!---------"); 
    } 
 } 

 

ClientEJB 中 createPerson() 方法采用了默认的传播属性,CommonEJB 中 createAddress() 方法使用了”Never”的传播属性。


清单 24.Never 属性中的 CommonEJB

				 
 @Stateless 
 public class CommonEJB implements CommonEJBRemote { 

    @PersistenceContext(unitName = "Transaction-ejbPU") 
    EntityManager em; 
    @Override 
    @TransactionAttribute(TransactionAttributeType.NEVER) 
    public void createAddress() { 
     
        Address address = new Address(); 
        address.setId(66l); 
        address.setCountry("Korea"); 
        address.setCity("Souel"); 
        address.setStreet("Tian Jian"); 
        address.setPostCode("4444444"); 
        address.setTsAttribute("NEVER"); 
        address.setCreateTime(new Date()); 
        em.persist(address); 
       
        System.out.println("----------ClientEJB Excute completely!--------"); 
    } 
 } 

 

当我们运行的时候控制台抛出了如下图所示的异常,由于程序未能正确执行,所以方法内持久化操作均不能完成,数据库中也没有相应的数据。


图 14.Never 中抛出异常
图 14.Never 中抛出异常 

综上所述,EJB 中事务的传播属性可以用如下表格进行概括:


表 1. 事务传播属性总表

事务传播属性 客户端在事务中运行 客户端不在事务中运行
MANDATORY 加入客户端所在的事务 抛出 TransactionRequiredException异常
NEVER 抛出 RemoteException 异常 方法在无事务环境中运行
NOT_SUPPORTED 客户端的事务挂起,方法在无事务环境中运行,客户端的事务继续执行 方法在无事务环境中运行
REQUIRED 加入客户端所在的事务 为该方法新开起一个事务
REQUIRES_NEW 客户端的事务挂起,为该方法新开起一个事务,客户端的事务继续执行 为该方法新开起一个事务
SUPPORTS 加入客户端所在的事务 方法在无事务环境中运行

 

Nested

Spring 中还有一个没有在 EJB 标准中定义的 Nested 的事务传播属性,这个属性和 Requires_New 极为类似,同样被已在事务环境中的 Client 调用的时候须开启一个新事务。唯一不同的就是 Nested 事务传播属性,更像一个嵌套属性,也就是说它新开起的这个事物要依附于父事务,如果父事务提交或者回滚了,它也需要跟着提交或者回滚。而 Requies_New 新开起的事务和父事务没有这种关系,它们是完全独立的两个事务,一方提交或者回滚不影响另一方。因为这个并不是 Java EE 标准中的事务传播属性,所以在这里也就不再赘叙了,有兴趣的读者可以参看相关资料。

 

总结

逻辑层是 Java EE 项目开发中的核心层,而事物是逻辑层中的灵魂所在,所以掌握好逻辑层中的事物处理方式,对 Java EE 开发起到关键的作用,虽然开源领域中,事务处理的方式各有不同,但是在逻辑层事物传播属性中,上面所提到的六大传播属性,已经被大家认可并广泛接受,已经被不同的逻辑层中间件普遍采用,使用已经很广,所以掌握了上面的那些传播属性的使用方法,我们就可以在以后 Java EE 项目逻辑层的开发中以不变应万变了。

 

参考资料

学习

  • Enterprise JavaBeans 3.1:EJB 是 sun 的服务器端组件模型,设计目标与核心应用是部署分布式应用程序。凭借 java 跨平台的优势,用 EJB 技术部署的分布式系统可以不限于特定的平台。EJB (Enterprise JavaBean) 是 J2EE 的一部分,定义了一个用于开发基于组件的企业多重应用程序的标准。其特点包括网络服务支持和核心开发工具 (SDK)。在 J2EE 里,Enterprise Java Beans(EJB) 称为 Java 企业 Bean,是 Java 的核心代码,分别是会话 Bean(Session Bean),实体 Bean(Entity Bean)和消息驱动 Bean(MessageDriven Bean)。从 EJB3.1 开始原来的实体 Bean (Entity Bean)已经独立出来作为数据持久化的技术规范— -JPA 而单独存在,并且从 EJB 3.1 起,EJB 又添加了一个新成员单例会话 Bean(Singleton Session Bean)。 

  • Spring In Action:Spring 是一个开源框架,它由 Rod Johnson 创建。它是为了解决企业应用开发的复杂性而创建的。Spring 使用基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。然而,Spring 的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。 

  • Spring 事务管理高级应用难点剖析,第 1 部分”(developerWorks,2010 年 3 月):Spring 的事务管理是被使用得最多的功能之一,虽然 Spring 事务管理已经帮助程序员将要做的事情减到了最小。但在实际开发中,如果使用不当,依然会造成数据连接泄漏等问题。本系列以实际应用中所碰到的各种复杂的场景为着眼点,对这些应用的难点进行深度的剖析。

  • Spring 事务管理高级应用难点剖析:第 2 部分”(developerWorks,2010 年 3 月):在本文中,作者将继续深入剖析在实际 Spring 事务管理应用中容易遇见的一些难点,包括混合使用多种数据访问技术(如 Spring JDBC + Hibernate)的事务管理问题,以及通过 Spring AOP 增强的 Bean 存在的一些比较特殊的情况。

  • Spring 事务管理高级应用难点剖析:第 3 部分”(developerWorks,2010 年 3 月):本文是“Spring 事务管理高级应用难点剖析”系列文章的第 3 部分,作者将继续深入剖析在实际 Spring 事务管理应用中容易遇见的一些难点,包括在使用 Spring JDBC 时如果直接获取 Connection,哪些情况会造成数据连接的泄漏与如何应对,以及除 Spring JDBC 外,其它数据访问技术数据连接泄漏的应对方案。

  • developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。 

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

关于作者

王汉敏拥有 Sun 公司的 Sun Certified Java Programmer, Sun Certified Web Component Developer Sun Certified Developer for Java Web Services Sun Certified Business Component Developer 认证 , 一直致力于 J2EE 领域软件的开发。

分享到:
评论

相关推荐

    智能车竞赛介绍(竞赛目标和赛程安排).zip

    全国大学生智能汽车竞赛自2006年起,由教育部高等教育司委托高等学校自动化类教学指导委员会举办,旨在加强学生实践、创新能力和培养团队精神的一项创意性科技竞赛。该竞赛至今已成功举办多届,吸引了众多高校学生的积极参与,此文件为智能车竞赛介绍

    集字卡v4.3.4微信公众号原版三种UI+关键字卡控制+支持强制关注.zip

    字卡v4.3.4 原版 三种UI+关键字卡控制+支持获取用户信息+支持强制关注 集卡模块从一开始的版本到助力版本再到现在的新规则版本。 集卡模块难度主要在于 如何控制各种不同的字卡组合 被粉丝集齐的数量。 如果不控制那么一定会出现超过数量的粉丝集到指定的字卡组合,造成奖品不够的混乱,如果大奖价值高的话,超过数量的粉丝集到大奖后,就造成商家的活动费用超支了。我们冥思苦想如何才能限制集到指定字卡组合的粉丝数,后我们想到了和支付宝一样的选一张关键字卡来进行规则设置的方式来进行限制,根据奖品所需的关键字卡数,设定规则就可以控制每种奖品所需字卡组合被粉丝集到的数量,规则可以在活动进行中根据需要进行修改,活动规则灵活度高。新版的集卡规则,在此次政府发布号的活动中经受了考验,集到指定字卡组合的粉丝没有超出规则限制。有了这个规则限制后,您无需盯着活动,建好活动后就无人值守让活动进行就行了,您只需要时不时来看下蹭蹭上涨的活动数据即可。 被封? 无需担心,模块内置有防封功能,支持隐藏主域名,显示炮灰域名,保护活动安全进行。 活动准备? 只需要您有一个认证服务号即可,支持订阅号借用认证服务号来做活动。如果您

    出口设备线体程序详解:PLC通讯下的V90控制与开源FB284工艺对象实战指南,出口设备线体程序详解:PLC通讯与V90控制集成,工艺对象与FB284协同工作,开源学习V90控制技能,出口设备1200

    出口设备线体程序详解:PLC通讯下的V90控制与开源FB284工艺对象实战指南,出口设备线体程序详解:PLC通讯与V90控制集成,工艺对象与FB284协同工作,开源学习V90控制技能,出口设备1200线体程序,多个plc走通讯,内部有多个v90,采用工艺对象与fb284 共同控制,功能快全部开源,能快速学会v90的控制 ,出口设备; 1200线体程序; PLC通讯; 多个V90; 工艺对象; FB284; 功能开源; V90控制。,V90工艺控制:开源功能快,快速掌握1200线体程序与PLC通讯

    基于Arduino与DAC8031的心电信号模拟器资料:心电信号与正弦波的双重输出应用方案,Arduino与DAC8031心电信号模拟器:生成心电信号与正弦波输出功能详解,基于arduino +DAC

    基于Arduino与DAC8031的心电信号模拟器资料:心电信号与正弦波的双重输出应用方案,Arduino与DAC8031心电信号模拟器:生成心电信号与正弦波输出功能详解,基于arduino +DAC8031的心电信号模拟器资料,可输出心电信号,和正弦波 ,基于Arduino;DAC8031;心电信号模拟器;输出心电信号;正弦波输出;模拟器资料,基于Arduino与DAC8031的心电信号模拟器:输出心电与正弦波

    (参考项目)MATLAB口罩识别检测.zip

    MATLAB口罩检测的基本流程 图像采集:通过摄像头或其他图像采集设备获取包含面部的图像。 图像预处理:对采集到的图像进行灰度化、去噪、直方图均衡化等预处理操作,以提高图像质量,便于后续的人脸检测和口罩检测。 人脸检测:利用Haar特征、LBP特征等经典方法或深度学习模型(如MTCNN、FaceBoxes等)在预处理后的图像中定位人脸区域。 口罩检测:在检测到的人脸区域内,进一步分析是否佩戴口罩。这可以通过检测口罩的边缘、纹理等特征,或使用已经训练好的口罩检测模型来实现。 结果输出:将检测结果以可视化方式展示,如在图像上标注人脸和口罩区域,或输出文字提示是否佩戴口罩。

    kernel-debug-devel-3.10.0-1160.119.1.el7.x64-86.rpm.tar.gz

    1、文件内容:kernel-debug-devel-3.10.0-1160.119.1.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/kernel-debug-devel-3.10.0-1160.119.1.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊

    day02供应链管理系统-补充.zip

    该文档提供了一个关于供应链管理系统开发的详细指南,重点介绍了项目安排、技术实现和框架搭建的相关内容。 文档分为以下几个关键部分: 项目安排:主要步骤包括搭建框架(1天),基础数据模块和权限管理(4天),以及应收应付和销售管理(5天)。 供应链概念:供应链系统的核心流程是通过采购商品放入仓库,并在销售时从仓库提取商品,涉及三个主要订单:采购订单、销售订单和调拨订单。 大数据的应用:介绍了数据挖掘、ETL(数据抽取)和BI(商业智能)在供应链管理中的应用。 技术实现:讲述了DAO(数据访问对象)的重用、服务层的重用、以及前端JS的继承机制、jQuery插件开发等技术细节。 系统框架搭建:包括Maven环境的配置、Web工程的创建、持久化类和映射文件的编写,以及Spring配置文件的实现。 DAO的需求和功能:供应链管理系统的各个模块都涉及分页查询、条件查询、删除、增加、修改操作等需求。 泛型的应用:通过示例说明了在Java语言中如何使用泛型来实现模块化和可扩展性。 文档非常技术导向,适合开发人员参考,用于构建供应链管理系统的架构和功能模块。

    基于四旋翼无人机的PD控制研究 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    C#与VB实现欧姆龙PLC的Fins TCP通信案例源码:调用动态链接库进行数据读写,定时器与计数器数据区的简洁读写操作示例,C#与VB实现欧姆龙PLC的Fins TCP通信案例源码:调用动态链接库进

    C#与VB实现欧姆龙PLC的Fins TCP通信案例源码:调用动态链接库进行数据读写,定时器与计数器数据区的简洁读写操作示例,C#与VB实现欧姆龙PLC的Fins TCP通信案例源码:调用动态链接库进行读写操作,涵盖定时器计数器数据区学习案例,C#欧姆龙plc Fins Tcp通信案例上位机源码,有c#和VB的Demo,c#上位机和欧姆龙plc通讯案例源码,调用动态链接库,可以实现上位机的数据连接,可以简单实现D区W区定时器计数器等数据区的读写,是一个非常好的学习案例 ,C#; 欧姆龙PLC; Fins Tcp通信; 上位机源码; 动态链接库; 数据连接; D区W区读写; 定时器计数器; 学习案例,C#实现欧姆龙PLC Fins Tcp通信上位机源码,读写数据区高效学习案例

    可调谐石墨烯超材料吸收体的FDTD仿真模拟研究报告:吸收光谱的化学势调节策略与仿真源文件解析,可调谐石墨烯超材料吸收体:化学势调节光谱的FDTD仿真模拟研究,可调谐石墨烯超材料吸收体FDTD仿真模拟

    可调谐石墨烯超材料吸收体的FDTD仿真模拟研究报告:吸收光谱的化学势调节策略与仿真源文件解析,可调谐石墨烯超材料吸收体:化学势调节光谱的FDTD仿真模拟研究,可调谐石墨烯超材料吸收体FDTD仿真模拟 【案例内容】该案例提供了一种可调谐石墨烯超材料吸收体,其吸收光谱可以通过改变施加于石墨烯的化学势来进行调节。 【案例文件】仿真源文件 ,可调谐石墨烯超材料吸收体; FDTD仿真模拟; 化学势调节; 仿真源文件,石墨烯超材料吸收体:FDTD仿真调节吸收光谱案例解析

    RBF神经网络控制仿真-第二版

    RBF神经网络控制仿真-第二版

    松下PLC与威纶通触摸屏转盘设备控制:FPWINPRO7与EBPRO智能编程与宏指令应用,松下PLC与威纶通触摸屏转盘设备控制解决方案:FPWINPRO7与EBPRO协同工作,实现多工位转盘加工与IE

    松下PLC与威纶通触摸屏转盘设备控制:FPWINPRO7与EBPRO智能编程与宏指令应用,松下PLC与威纶通触摸屏转盘设备控制解决方案:FPWINPRO7与EBPRO协同工作,实现多工位转盘加工与IEC编程模式控制,松下PLC+威纶通触摸屏的转盘设备 松下PLC工程使用程序版本为FPWINPRO7 7.6.0.0版本 威纶通HMI工程使用程序版本为EBPRO 6.07.02.410S 1.多工位转盘加工控制。 2.国际标准IEC编程模式。 3.触摸屏宏指令应用控制。 ,松下PLC; 威纶通触摸屏; 转盘设备控制; 多工位加工控制; IEC编程模式; 触摸屏宏指令应用,松下PLC与威纶通HMI联控的转盘设备控制程序解析

    基于循环神经网络(RNN)的多输入单输出预测模型(适用于时间序列预测与回归分析,需Matlab 2021及以上版本),基于循环神经网络(RNN)的多输入单输出预测模型(matlab版本2021+),真

    基于循环神经网络(RNN)的多输入单输出预测模型(适用于时间序列预测与回归分析,需Matlab 2021及以上版本),基于循环神经网络(RNN)的多输入单输出预测模型(matlab版本2021+),真实值与预测值对比,多种评价指标与线性拟合展示。,RNN预测模型做多输入单输出预测模型,直接替数据就可以用。 程序语言是matlab,需求最低版本为2021及以上。 程序可以出真实值和预测值对比图,线性拟合图,可打印多种评价指标。 PS:以下效果图为测试数据的效果图,主要目的是为了显示程序运行可以出的结果图,具体预测效果以个人的具体数据为准。 2.由于每个人的数据都是独一无二的,因此无法做到可以任何人的数据直接替就可以得到自己满意的效果。 这段程序主要是一个基于循环神经网络(RNN)的预测模型。它的应用领域可以是时间序列预测、回归分析等。下面我将对程序的运行过程进行详细解释和分析。 首先,程序开始时清空环境变量、关闭图窗、清空变量和命令行。然后,通过xlsread函数导入数据,其中'数据的输入'和'数据的输出'是两个Excel文件的文件名。 接下来,程序对数据进行归一化处理。首先使用ma

    【图像识别】手写文字识别研究 附Matlab代码+运行结果.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    旅游管理系统(基于springboot,mysql,java).zip

    旅游管理系统中的功能模块主要是实现管理员;首页、个人中心、用户管理、旅游方案管理、旅游购买管理、系统管理,用户;首页、个人中心、旅游方案管理、旅游购买管理、我的收藏管理。前台首页;首页、旅游方案、旅游资讯、个人中心、后台管理等功能。经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与旅游管理系统实现的实际需求相结合,讨论了Java开发旅游管理系统的使用。 从上面的描述中可以基本可以实现软件的功能: 1、开发实现旅游管理系统的整个系统程序;  2、管理员;首页、个人中心、用户管理、旅游方案管理、旅游购买管理、系统管理等。 3、用户:首页、个人中心、旅游方案管理、旅游购买管理、我的收藏管理。 4、前台首页:首页、旅游方案、旅游资讯、个人中心、后台管理等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流查看及回复相应操作。

    Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基

    Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构,Simulink建模,MPPT最大功率点追踪,扰动观察法采用功率反馈方式,若ΔP>0,说明电压调整的方向正确,可以继续按原方向进行“干扰”;若ΔP<0,说明电压调整的方向错误,需要对“干扰”的方向进行改变。 ,Boost升压;光伏并网结构;Simulink建模;MPPT最大功率点追踪;扰动观察法;功率反馈;电压调整方向。,光伏并网结构中Boost升压MPPT控制策略的Simulink建模与功率反馈扰动观察法

    基于matlab平台的图像去雾设计.zip

    运行GUI版本,可二开

    Deepseek相关参考资源文档

    Deepseek相关主题资源及行业影响

    WP Smush Pro3.16.12 一款专为 WordPress 网站设计的图像优化插件开心版.zip

    WP Smush Pro 是一款专为 WordPress 网站设计的图像优化插件。 一、主要作用 图像压缩 它能够在不影响图像质量的前提下,大幅度减小图像文件的大小。例如,对于一些高分辨率的产品图片或者风景照片,它可以通过先进的压缩算法,去除图像中多余的数据。通常 JPEG 格式的图像经过压缩后,文件大小可以减少 40% – 70% 左右。这对于网站性能优化非常关键,因为较小的图像文件可以加快网站的加载速度。 该插件支持多种图像格式的压缩,包括 JPEG、PNG 和 GIF。对于 PNG 图像,它可以在保留透明度等关键特性的同时,有效地减小文件尺寸。对于 GIF 图像,也能在一定程度上优化文件大小,减少动画 GIF 的加载时间。 懒加载 WP Smush Pro 实现了图像懒加载功能。懒加载是一种延迟加载图像的技术,当用户滚动页面到包含图像的位置时,图像才会加载。这样可以避免一次性加载大量图像,尤其是在页面内容较多且包含许多图像的情况下。例如,在一个新闻网站的长文章页面,带有大量配图,懒加载可以让用户在浏览文章开头部分时,不需要等待所有图片加载,从而提高页面的初始加载速度,同时也能

    1. Download this file: https://cdn-media.huggingface.co/frpc-gradio-0.3/frpc-windows-amd64.exe

    Could not create share link. Missing file: C:\Users\xx\.conda\envs\omni\Lib\site-packages\gradio\frpc_windows_amd64_v0.3 1. Download this file: https://cdn-media.huggingface.co/frpc-gradio-0.3/frpc_windows_amd64.exe 2. Rename the downloaded file to: frpc_windows_amd64_v0.3 3. Move the file to this location: C:\Users\xx\.conda\envs\omni\Lib\site-packages\gradio

Global site tag (gtag.js) - Google Analytics