Roo中的@Version

问题提出

当我们为entity添加@RooJpaActiveRecord注解时,Roo为我们自动生成了一个名为Entity_Roo_Jpa_Entity.aj的文件,其中有一句:

这是什么意思呢?“Version”是版本的意思,那么“@Version”注解是版本号的意思吗?Entity为什么需要版本管理? 下面我们对此展开探寻。

线索

注意到Entity_Roo_Jpa_Entity.aj与@RooJpaActiveRecord有关,由上一篇博客可推测,@Version与JPA有关。打开Entity_Roo_Jpa_Entity.aj文件,鼠标移到@Version注解上,显示:

Specifies the version field or property of an entity class that serves as its optimistic lock value. The version is used to ensure integrity when performing the merge operation and for optimistic concurrency control.

可知,@Version的作用是声明注释的属性或者返回值作为optimistic lock value。version属性的作用是optimistic concurrency control

Lock

optimistic lock的一般译为“乐观锁”,属于数据库事务并发控制领域的概念。数据库并发控制一般由“Lock(锁)”来实现的。这里提到:

Transactional isolation is usually implemented by locking whatever is accessed in a transaction. There are two different approaches to transactional locking: Pessimistic locking and optimistic locking.

可知transaction的isolation性质(ACID中的I)通常通过lock进行访问控制来实现。lock具体可分为pessimistic lock(悲观锁)和optimistic lock(乐观锁)。

pessimistic lock(悲观锁)

pessimistic lock的意思是:

a resource is locked from the time it is first accessed in a transaction until the transaction is finished, making it inaccessible to other transactions during that time.

也就是说,在一个transaction中,data会被完全锁住,直到transaction结束。另一个transaction若要访问该data,则需要等待。注意pessimistic lock可能会引起循环等待导致死锁。由于pessimistic lock的特性,采取该并法策略可能会导致运行效率大幅度下降,故并不适合高并发、长事务的场景。

optimistic lock(乐观锁)

这里提到:

Optimistic locking does not use exclusive locks when reading. Instead, a check is made during the update to make sure that the record has not been changed since it was read.

这里也提到:

At commit time, when the resource is about to be updated in persistent storage, the state of the resource is read from storage again and compared to the state that was saved when the resource was first accessed in the transaction. If the two states differ, a conflicting update was made, and the transaction will be rolled back.

可以看出optimistic lock的实现原理是通过在准备同步数据库时(commit时)对比数据在transaction中初次读取时和当前在数据库中的状态,看其是否满足某种规则来决定此次同步是否成功,若不成功则rollback。

具体的实现,这里举了一个例子:

一般的应用是采用数据版本的方式(version)实现,在读取数据的时候将version读取出来,在保存数据的时候判断version 的值是否小于数据库中的version的值,小于则不允许更新,否则可以更新。

值得注意的是,optimistic lock的实现并不依赖于数据库,而是基于系统中的数据存储逻辑。故系统外部的更新并不受optimistic lock的控制,可能会造成脏数据被更新到系统当中。

@Version

那么,@Version注解的具体用法是怎么样的呢?结合API文档,如上所述,我们只需要在某个数值属性或者timestamp类型的属性上标注@Version注解即可:

或者:

要注意:

  1. 每个class只能有一个@Version标注的属性。
  2. @Version标注的属性应该属于该class的primary table(主表)。
  3. @Version标注的属性的类型包括:int, Integer, short, Short, long, Long, java.sql.Timestamp
  4. 程序不应该手动修改@Version标注的属性(交由框架自动处理)。

Roo中的ActiveRecord以及EntityManagement

《ROO中的*.aj文件》一文中提到,当我们在Roo Shell中新建Entity时,Roo会自动生成一个名为***_Roo_Jpa_ActiveRecord.aj的文件,里面定义了关于该Entity的CRUD操作的方法。为什么Roo要采取这种模式呢?如何理解这种模式?

Active Record

***_Roo_Jpa_ActiveRecord.aj切面文件代表了一种领域模型的模式,称之为Active RecordWiki上有这样的一段话:

Active Record是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。

提出者Martin Fowler对该模式作了如下定义:

An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.

简单地说就是:

An object carries both data and behavior.

下图是一个简单的例子:

例子

可以看到,Person类除了属性之外,还负责了本身的CRUD操作,还有一些属于自己的业务逻辑。我们新建Person类的时候,这样写就可以了:

由此可以总结一下Active Record的特性:

  1. Entity中封装了数据库访问。
  2. 每个Entity代表数据库中的一行数据。
  3. Entity是一种领域模型,封装了部分业务逻辑。

由于这些特性,Active Record模式对于数据库的操作(单表操作)特别方便,对于简单的业务来说逻辑比较清晰明了,故特别适用于web快速开发。

JPA

**_ActiveRecord.aj文件中,有类似如下的代码:

@PersistenceContextEntityManager是什么呢?这牵涉到一个名为“JPA”的概念。Spring Roo in Action对JPA下了这样一个定义:

The Java Persistence API, or JPA for short, was created to provide a standard program­ ming interface for object-oriented database mapping.

可以看出JPA实际上是一系列接口的定义:

Hibernate, EclipseLink, Open- JPA‚ and other frameworks implement the JPA 2.0 specification, which allows developers to write code to a single API regardless of the implementing vendor.

JPA为我们提供了统一的数据访问层API。具体地,由Roo的API文档

The Spring JPA, available under the org.springframework.orm.jpa package, offers comprehensive support for the Java Persistence API in a similar manner to the integration with Hibernate or JDO, while being aware of the underlying implementation in order to provide additional features.

可以看出,Spring JPA是一种JAP的实现,在org.springframework.orm.jpa包中。但它的ORM功能主要是集成其他框架(例如Hibernate)的,并在其之上添加了一些其他的功能。

所以,当我们在Roo Shell中输入:

时,Roo为我们添加了JAP、Hibernate的依赖jar包,在applicationContext.xml中添加了相应的JDBC datasource,建立了persistence.xml文件来关联JAP与数据库,建立了数据库配置文件database.properties文件和加载了Bean Validation Framework等等一系列的工作。注意到该Roo命令中--provider--database两个参数,前者指定了JAP的实现框架,后者指定了数据库种类。

下面看一下JAP的几个主要的组件:

  1. JPA entity
    用注解或者xml文件定义的一个映射到数据库中某个表的java类。
  2. persistence context
    负责管理entity的生命周期,依赖关系的context类,拥有自己的线程或者session。
  3. EntityManager API
    提供了persistence context的访问接口。
  4. JPA configuration file
    实现了JAP标准的ORM框架的配置文件,通常为META-INF/persistence.xml

后面我们会讲到,在***_ActiveRecord.aj文件中可以找到这些组件的踪迹。

实例

下面我们以Sensor类为例,其中Sensor_Roo_Jpa_ActiveRecord.aj文件代码如下所示:

我们可以看到,Roo为Sensor类自动生成了CRUD方法和一些finder方法,注意其中的@PersistenceContext注解和@Transactional注解,后面会进行说明。

Sensor_Roo_Jpa_Entity.aj文件代码如下所示:

Sensor.java文件如下所示:

下面分点进行解析。

@RooJpaActiveRecord注解

根据Spring Roo in Action中介绍,当我们为一个java类添加@RooJpaActiveRecord标签时,Roo自动为其生成了***_Roo_Configurable.aj,***_Roo_Jpa_ActiveRecord.aj,***_Roo_Jpa_Entity.aj,三个切面文件,分别为该java类添加了Configurable支持,JPA-based methods(delete()、save()等)和id、version属性。

@Entity注解

可以看到在Sensor_Roo_Jpa_Entity.aj中有如下代码:

本blog在这里解释过declare @type: 的用途和用法,这行代码的意思是为每个Sensor类添加了@Entity注解。那么如何理解@Entity?

entity annotation jpa为关键词在google上进行搜索,找到这里

JPA entities are plain POJOs. Actually, they are Hibernate persistent entities. Their mappings are defined through JDK 5.0 annotations instead of hbm.xml files.

JPA annotations are in the javax.persistence.* package.

我们可以知道,在JDK 5.0及以上的版本中可以通过annotations来配置JPA,而不是一个xml配置文件,还有javax.persistence.*包里的类都是于JPA有关。

Every persistent POJO class is an entity and is declared using the @Entity annotation (at the class level)

可以知道,@Entity注解声明了对应的pojo为实体类,即声明了该pojo交由JAP管理,JAP会自动地根据该pojo的属性进行底层数据库处理(除非你自己用@Table等注解进行配置)。

所以@Entity使Sensor纳入到JAP的持久化管理之中,@Id注解声明了Sensor.id为主键,@Column(name=**)为Sensor的属性指定了映射的列名,@Version声明了Sensor.version为实体类版本号属性(提供了optimistic locking功能?)。总的来说,Sensor_Roo_Jpa_Entity切面使得Sensor成为了JAP中的一个Entity。

@PersistenceContext注解

google到了这里

Expresses a dependency on a container-managed EntityManager and its associated persistence context.

前面我们提到,EntityManager是访问persistence context的接口。那么这句话的意思是@PersistenceContext的作用是使spring注入一个EntityManager实例。在这段代码:

中我们也可以看出,@PersistenceContext令spring将EntityManager注入到Sensor.entityManager中,以供下面的CRUD方法使用。

这里解释了PersistenceContext和EntityManager之间的关系:

The persistence context is the collection of all the managed objects of an EntityManager.

另外,在项目中搜索“entityManagerFactory”,在applicationContext.xml文件中有如下配置声明:

可以看出,@PersistenceContext每次注入的EntityManager实例都是由LocalContainerEntityManagerFactoryBean产生(关于FactoryBean请看这里中的介绍)。

其中的<property name="dataSource" ref="dataSource"/>由以下代码指定:

另外要注意的是

EntityManager instances should not be shared between concurrently executing threads. According to JPA specification, the methods of the EntityManagerFactory are thread-safe (this is also explicitly stated in Hibernate documentation). Hence, you can use a single instance of an EntityManagerFactory for every data source in your application.

即EntityManager不是线程安全的,EntityManagerFactory是线程安全的。所以确保EntityManager实例不要在线程之间共享

@Transactional注解

这里有对@Transactional的详细描述:

Describes transaction attributes on a method or class.

“transaction”是什么?wiki上是这样定义的:

Transactions provide an “all-or-nothing” proposition, stating that each work-unit performed in a database must either complete in its entirety or have no effect whatsoever. Further, the system must isolate each transaction from other transactions, results must conform to existing constraints in the database, and transactions that complete successfully must get written to durable storage.

也就是说,“事务”是指一系列数据库操作的集合。该集合中所有操作要么全部执行成功(然后被持久化),要么全部无效(日志回滚)。数据库系统要保证事务之间的独立性(锁机制),事务处理结果要满足数据库的约束性(一致性)。可以简单地概括成“ACID”性质:

A database transaction, by definition, must be atomic, consistent, isolated and durable. Database practitioners often refer to these properties of database transactions using the acronym ACID.

ACID的详细介绍可以看这里

由此我们可以推断,@Transactional注解可以为entity或者某个method添加”事务特性“。例如:

DefaultFooService类的每个public方法便有了事务特性。那么@Transactional注解是如何起作用的呢?在项目中搜索transactionManager,在applicationContext.xml文件中会发现如下声明:

这句声明使得系统自动扫描java文件,对@Transactional标注的类进行处理,添加事务支持。注意到如下声明:

该声明指定了org.springframework.orm.jpa.JpaTransactionManager为transactionManager的实现,还指定了entityManagerFactory属性的引用。

综上,可以看出,由于效率方面的考虑,Sensor_Roo_Jpa_ActiveRecord切面中并没有对整个类标注@Transactional,而是在方法粒度上进行事务管理。由于EntityManager不是线程安全的,所以Sensor_Roo_Jpa_ActiveRecord中CRUD方法中引用到entityManager的方法都添加了@Transactional进行事务管理。也就是说,当我们自定义某些持久化相关的方法时,如果引用了entityManager,要给该方法标注@Transactional以防止出现问题,或者直接用entity的save、flush方法。

Sensor.entityManager()静态方法

我们把目光集中在如下几个方法上:

这里有一段关于entity的描述:

Entity objects are in-memory instances of entity classes (persistable user defined classes), which can represent physical objects in the database.

可以看出,entity存在于内存之中,不一定存在于数据库中,所以才有“持久化”一说。也就是说,必须存在一种机制去管理entity的“状态”,让entity和数据库数据之间切换和同步。于是可以引入“Entity Object Life Cycle”这个概念:

The life cycle of entity objects consists of four states: New(Transient), Managed, Removed and Detached.

状态转换

上述图片展示了entity在其生命周期内各个状态、状态之间的转换规则和触发转换的条件。具体的转换规则如下所示:

  1. 当一个entity被初始化的时候(通过new语句),该entity的状态是New,表明该实体尚未交由EntityManager管理,也尚未存在于数据库中。
  2. 当entity通过EntityManager的persist()方法持久化后(commit/flush后才被写进数据库),该entity处于Managed状态。
    当entity通过EntityManager从数据库中retrieve出来时,也处在Managed状态。
  3. 当entity通过EntityManager的remove()方法删除后,处在Removed状态。commit后entity对应的数据将从数据库中删除。
  4. 当entity脱离EntityManager的管理(通过调用EntityManager的close()或clear()方法)时,entity处于Detached状态。

由于Roo默认采取了ActiveRecord模式,所以对EntityManager的方法进行了包装,我们直接调用entity.save()、entity.delete()等即可。值得注意的是,当我们调用entity.persist()后该entity并没有马上被写进数据库,它只是标记了该entity可以被持久化。必须等待EntityManager调用flush()之后entity才正式持久化(EntityManager什么时候调用flush()默认的FlushMode为AUTO,即在query execution之前的某个时刻自动调用flush())。

总结

Roo为了实现快速web开发,引入了ActiveRecord模式来管理entity以及他们之间的关系。我们可以直接通过调用实体类的save()、remove()方法来CRUD。Roo利用切面文件为entity添加ActiveRecord模式,通过向entity注入EntityManager实例实现了JPA的支持,并结合spring的事务管理方式,通过注解解决了多线程下entity的同步问题。