星期一, 三月 12, 2007

JBoss Seam Develop tips

1. 分离测试和业务代码,新建一个源代码目录“test”,修改build.xml:
--在Property声明部分增加“test.java.dir" Property:
《!--src.java.dir was generated by seam gen--》
《property name="src.java.dir" value="src" /》
《!--test.java.dir added by developer manually--》
《property name="test.java.dir" value="test" /》
--修改target ”compile“的”javac“,添加test.java.dir到源代码目录:
《!--javac was generated by seam gen--》
《javac classpathref="build.classpath" destdir="${jar.dir}"
debug="${javac.debug}" deprecation="${javac.deprecation}" nowarn="on"》
《src path="${src.java.dir}" /》
《!--test.java.dir added by developer manually--》
《src path="${test.java.dir}" /》
《/javac》
--修改target “test“,把test.java.dir中的testng测试用例定义文件copy到测试classpath,增加以下语句:
《copy todir="${test.dir}" flatten="true"》
《fileset dir="${test.java.dir}"》
《include name="**/*Test.xml" /》
《/fileset》
《/copy》
--添加target “test_clean",并修改target ”test“,把”test_clean“加入”depends“属性:
《target name="test_clean"》
《delete》
《fileset dir="${test.dir}"》
《include name="**/*Test.xml" /》
《/fileset》
《/delete》
《/target》
《target name="test" depends="test_clean,jar,war,ear" description="Run the tests"》

2.测试驱动开发,Jboss Seam采用TestNG作为测试框架,同时支持Unit Testing(单元测试)和Intergration Testing(集成测试),其中前者主要用于分离地测试各层中特定的逻辑组件,
而后者负责测试除了页面以外的整个应用系统覆盖了各层的功能实现。
--随Jboss Seam 1.1.0.GA分发的Hibernate的JPA实现包hibernate-all.jar无法正常工作,需要更换最新的实现。
--Jboss Seam 1.1.0.GA文档中的”19.1. Unit testing Seam components“ 示例代码无法正常工作,建议参考example中的booking示例的BookingUnitTest.java,单元测试类直接继承于SeamTest。
--为了便于测试,简化测试代码的复杂度和提高测试执行效率,把有关EnityManager的管理代码抽取到一个共同的测试基类中,并且使用TestNG框架的方法级别和类级别的前置和后置代码功能,为每个测试类的每个线程保持一个EntityManager,每个方法绑定到一个Transaction,在执行方法的测试代码前自动打开事务,在方法执行完毕之后回滚事务,通过这种方法可以隔离各个测试方法不受外界影响也避免了频繁创建EntityManager带来的系统消耗。代码如下:
package com.psims.test;

import javax.ejb.CreateException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.jboss.seam.mock.SeamTest;
import org.testng.annotations.Configuration;

import com.psims.base.BaseConstants;

public class BaseTest extends SeamTest {
private static EntityManagerFactory emf;

private ThreadLocal entityManagerLocal = new ThreadLocal();

public EntityManager getEntityManager() {
EntityManager em = this.entityManagerLocal.get();
if (em == null || !em.isOpen()) {
em = getEntityManagerFactory().createEntityManager();
this.entityManagerLocal.set(em);
}
return em;
}

public EntityManagerFactory getEntityManagerFactory() {
if (emf == null || !emf.isOpen()) {
emf = Persistence
.createEntityManagerFactory(BaseConstants.PERSISTENT_UNIT);
}
return emf;
}

@Configuration(beforeTestMethod = true)
public void beginTransaction() {

EntityManager em = getEntityManager();
em.getTransaction().begin();
}

@Configuration(afterTestMethod = true)
public void closeTransaction() {
EntityTransaction et = getEntityManager().getTransaction();
if (et != null && et.isActive()) {
et.rollback();
}
}

@Configuration(beforeTestClass = true)
public void createEntityManager() {
this.getEntityManager();
}

@Configuration(afterTestClass = true)
public void closeEntityManager() {
EntityManager em = getEntityManager();
EntityTransaction et = em.getTransaction();
if (et != null && et.isActive()) {
et.rollback();
}
em.close();
this.entityManagerLocal.remove();
if (emf != null && emf.isOpen()) {
emf.close();
}
}
}
3.在ManyToMany映射时,如果是双向关联需要在被控方设置”mappedBy“属性。和inverse雷同。
4.难道Session Context范围的Component可以自动初始化,在使用@In注入的时候,不需要(create = true)?
5.把Entity Bean直接使用@Name声明为Seam Component使用时,如果在使用集群的时候,效率会有问题,尽量
把Entity Bean作为Session Bean的一个属性来使用。
6.使用Jsf组件调用后台服务的时候,可以传递参数
action="#{hotelBooking.selectHotel(hot)}"
可以实现选择DataTable中的一行,这样就可以避免在后台服务组件中声明@DataModelSelection属性。
7.可以在不提交任何jsf Form的情况下调用Action并传递request parameter,而且他没有使用
任何的javascript,可以通过右键,“在新窗口中打开”的方式执行。
8.由于默认一个Conversation的生存范围仅仅持续一个Request,若需要跨多个Request,必须使用@Begin
和 @End 来限定。
9.在实现service层时,把实现查询功能的seesionbean的生存范围设置为Session而不是Conversation,
可以避免频繁的创建和销毁Component实例。
10.区分哪些状态信息适合于生存在哪个上下文中是合理正确使用Seam框架的基础。
11.EnityQuery 对象实现了对Enity的分页查询功能,继承者直接Override public String getEjbql() 方法,
并返回实际的ql语句即可,在使用Ejb继承EnityQuery时,还需要Override public setEntityManager(EntityManager em)方法,
以@PersistenceContext形式注入EntityManager。
12.Query是EntityQuery的抽象基类,它在内部封装了大部分通用的查询逻辑,其中getEjbql()方法负责返回ql查询语句的模板,如:
select e from Person e where lower(e.lastName) like lower( #{examplePerson.lastName} + '%' ) and
e.age = #{examplePerson.age} and e.gender = #{examplePerson.gender}
而parseEjbql()方法负责解析ql语句的格式,把需要设置的表达式查询参数和约束参数分解出来,其中queryParameters中
保存了形如:
#{examplePerson.age}
#{examplePerson.gender}
的表达式EL查询参数名称,
parsedEjbql保存使用符号参数替换EL查询参数后的ql语句了形如
select e from Person e where lower(e.lastName) like lower( :p1 + '%' ) and e.age = :p2 and e.gender = :p3
parsedRestrictions保存了约束语句,restrictionParameters保存了约束参数。
13.在实体Bean中不要直接使用HashCodeBuilder.reflectionHashCode and EqualsBuilder.reflectionEquals,
它们会引发[LazyInitializationException]异常,估计是由于动态代理的原因。
14.场景:Service由SFSB实现,DAO由SLSB实现,Service使用@In注入DAO实现,DAO使用@PersistenceContext由注入EntityManager.
现象:在页面上执行CUD操作时,无法完成操作,页面和后台均无无错误信息,或者提示session is null or closed
原因:使用@PersistenceContext注入的EntityManager的事务为标准类型,即边界为最外层SB的方法,而Seam中的Transaction
需要使用type=Extended类型的EntityManager
方案:使用@PersistenceContext(type = EXTENDED),需要使用SFSB 或使用@In(create = true) 注入在components.xml中定义好的 core:managed-persistence-context.
15.Seam Managed Transactions
Transaction management for extended persistence contexts. A transaction spans the restore view, apply request values, process validations,update model values and invoke application phases. It is committed when invoke application is complete, or renderResponse() or responseComplete() is called. A second transaction spans the render response phase.
场景:SB拥有声明性事务管理,默认容器会自动在SB中的个方法开始调用的时候开启新的事务,在方法结束的时候提交事务.
(或者如果是SB调用SB,延用已存在的事务)
现象:在使用ORM的lazy fecthing的时候会抛出LazyInitializationExceptions异常.
原因:当在View中访问惰性加载的属性时,已经超出了Transaction的边界,所以需要使用open session in view模式来将整个
request生命周期纳入Transaction中,在View 执行Render操作之后commit,这个会有一个问题,当事务失败的时候,view已经
被渲染完毕,使view表现了与persistence不一致的状态.
方案:首先使用Extended persistence context,并将scope拓展为conversation,而不是request.
其次采用为每个request启用两阶段Transaction,第一阶段始于JSF的"restore view" Phase,终于"invoke application" Phase,第二个阶段覆盖"render response" Phase.
16.扩展的持久上下文extended persistence context, The entity retrieved in the query remain in the managed state as long as the bean exists, so any subsequent method calls to the stateful bean can update them without needing to make any explicit call to the EntityManager.
17.测试的时候使用setField(hb, "facesMessages", new FacesMessages());
setField(hb, "log", new LogImpl(HotelBookingAction.class));
setField(hb, "events", new Events() { @Override public void raiseEvent(String type, Object... params) { assert "bookingConfirmed".equals(type); } } );

没有评论: