本文同时发表在我在google Appengine 上的搭建的博客:[url]http://blogfor11lu.appspot.com/articleaction_view.action?article.id=agtibG9nZm9yMTFsdXIPCxIHQXJ0aWNsZRjBtQMM[/url]
之前用JDO 和 Struts2 在google Appengine 上试着写了一个简单的blog程序,但我还是希望使用Spring的依赖注入和事务管理等方面的功能,于是着手搭建环境。
这里,我使用了GAE1.3.5 Spring2.5.6 Struts2.1.8
首先是测试了一下JPA,在Eclipse开发环境下,GAE已经把相应的包都放到了Lib目录下了,只要在src/META-INF下建立persistence.xml文件就可以了,具体内容从GAE文档中复制过来就OK了。写一个简单的的类,测试一下,这个比较简单,GAE文档中都有。很快就成功了。
接下来,我把spring和struts的相关jar包拷到lib目录下,并配置好相关xml,总会出现或这或那的错误,这都是由于jar包不完整或者是jar包之间的版本不配比造成的,还再次出现了上一次集成struts和gae时提示xalan这个jar包没有的错误。后来发现是struts和spring-struts-plugin的版本不一致造成的(这个错误的出现有两种,一个是ongl没处理,一个就是版本不配比)。
经过一大堆错误,最后还是决定一个一个来集成。先集成struts,把前次的jar包拷贝过来,简单做一个action,测试一下,OK。
复制spring的jar包,根据网上的提示,不用all in on的那个,主要是因为GAE在file.io方面不能写文件,使用modules下面的jar包,凭感觉挑了一些,然后,再复制lib目录下需要使用的一些jar包,这个基本参考网上和以前SSH整合时用的jar包。不断测试,根据提示把jar包补齐。一般的,提示没找到指定的类错误,是没有相应jar包,提示没有指定的方法,是jar包的版本不一致。最后要把spring-struts-plugin2.1.8.jar复制到lib目录下。
集成测试了,在集成测试时,测试了@Controller、@Resource、@Transaction等几个注解,首先测的是@Controller,这个测试比较顺利。接着用@Resource 注入一个DAO操作的类。问题出来了:错误信息如下:
Error creating bean with name 'personAction': Injection of resource fields failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'personService' must be of type [com_11lu.GAE.PersonService], but was actually of type [$Proxy17]
最开始,我并没有PersonService,而是直接使用DaoSupport,这是一个抽象类实现Dao接口,DaoSupport是从网上拷来的,当我注意到是抽象类时,把它的abstract去掉,错误依旧,再次,把PersonService继承DaoSupport,还是一样的错误。从这个错误信息我们可以看出,大致的意思的bean的type不对。于是在google上找,说到这个找,关键词很重要,最先是找Injection of resource fields failed这个,没有得到合适的内容。后来找must be of type but was actually of type,在一个外国论坛上看到了答案。问题出在@Transaction注解,在spring的文档中,实际上有说明,对@Transaction的类,要设置proxy-target-class="true" ,否则就不能将这个类注入。也就是会出现上面的信息。具体设置是:<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>。
由于我的英文实在是差,没搞明白proxy-target-class="true"设置在哪,也搞不懂注解方式这个如何设,最终是试着放在这的。我想,如果不是用注解方式,可以放在<bean/>里。
再次运行,提试CGLIB要复制到CLASSPAH下,在spring的Lib目录中找到,复制好就OK了。
下面我把用到的Jar包列出,另外我把我测试用的project也上传到,大家可以下载看看。
我写的记录了我的整个过程,这个过程,我用了好几个晚上的功夫,文字有点哆嗦。
解释一下为什么@Transaction的类,会出现上述错误,在spring中@Transaction是通过AOP实现的,而spring对AOP有两种实现方式,一种是动态代理,它是通过接口方式实现的,要求所代理的类一定是实现了某一个接口,对一般的类就无法代理,spring默认是这种;通过设置proxy-target-class="true",则是使用CGLIB实现AOP,CGLIB直接生成二进制码,使得普通类也可以实现AOP。在没有设置proxy-target-class="true"时,使用动态代理,是一个临时生成的类,如proxy17,它不是@Resource指定的类,因此出现了上述错误。
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="com_11lu.GAE" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"
lazy-init="true">
<property name="persistenceUnitName" value="transactions-optional" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<!-- Activates @Transactional for DefaultImageDatabase -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
</beans>
package com_11lu.GAE;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public abstract class DaoSupport<T> implements DAO{
@PersistenceContext protected EntityManager em;
public void clear(){
em.clear();
}
public <T> void delete(Class<T> entityClass,Object entityid) {
delete(entityClass, new Object[]{entityid});
}
public <T> void delete(Class<T> entityClass,Object[] entityids) {
for(Object id : entityids){
em.remove(em.getReference(entityClass, id));
}
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> T find(Class<T> entityClass, Object entityId) {
return em.find(entityClass, entityId);
}
public void save(Object entity) {
em.persist(entity);
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> long getCount(Class<T> entityClass) {
return (Long)em.createQuery("select count("+ getCountField(entityClass) +") from "+ getEntityName(entityClass)+ " o").getSingleResult();
}
public void update(Object entity) {
em.merge(entity);
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> QueryResult<T> getScrollData(Class<T> entityClass,
int firstindex, int maxresult, LinkedHashMap<String, String> orderby) {
return getScrollData(entityClass,firstindex,maxresult,null,null,orderby);
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> QueryResult<T> getScrollData(Class<T> entityClass,
int firstindex, int maxresult, String wherejpql, Object[] queryParams) {
return getScrollData(entityClass,firstindex,maxresult,wherejpql,queryParams,null);
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> QueryResult<T> getScrollData(Class<T> entityClass, int firstindex, int maxresult) {
return getScrollData(entityClass,firstindex,maxresult,null,null,null);
}
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> QueryResult<T> getScrollData(Class<T> entityClass) {
return getScrollData(entityClass, -1, -1);
}
@SuppressWarnings("unchecked")
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public <T> QueryResult<T> getScrollData(Class<T> entityClass, int firstindex, int maxresult
, String wherejpql, Object[] queryParams,LinkedHashMap<String, String> orderby) {
QueryResult qr = new QueryResult<T>();
String entityname = getEntityName(entityClass);
Query query = em.createQuery("select o from "+ entityname+ " o "+(wherejpql==null? "": "where "+ wherejpql)+ buildOrderby(orderby));
setQueryParams(query, queryParams);
if(firstindex!=-1 && maxresult!=-1) query.setFirstResult(firstindex).setMaxResults(maxresult);
qr.setResultlist(query.getResultList());
query = em.createQuery("select count("+ getCountField(entityClass)+ ") from "+ entityname+ " o "+(wherejpql==null? "": "where "+ wherejpql));
setQueryParams(query, queryParams);
qr.setTotalrecord((Long)query.getSingleResult());
return qr;
}
protected void setQueryParams(Query query, Object[] queryParams){
if(queryParams!=null && queryParams.length>0){
for(int i=0; i<queryParams.length; i++){
query.setParameter(i+1, queryParams[i]);
}
}
}
/**
* 组装order by语句
* @param orderby
* @return
*/
protected String buildOrderby(LinkedHashMap<String, String> orderby){
StringBuffer orderbyql = new StringBuffer("");
if(orderby!=null && orderby.size()>0){
orderbyql.append(" order by ");
for(String key : orderby.keySet()){
orderbyql.append("o.").append(key).append(" ").append(orderby.get(key)).append(",");
}
orderbyql.deleteCharAt(orderbyql.length()-1);
}
return orderbyql.toString();
}
/**
* 获取实体的名称
* @param <T>
* @param entityClass 实体类
* @return
*/
protected <T> String getEntityName(Class<T> entityClass){
String entityname = entityClass.getSimpleName();
Entity entity = entityClass.getAnnotation(Entity.class);
if(entity.name()!=null && !"".equals(entity.name())){
entityname = entity.name();
}
return entityname;
}
protected <T> String getCountField(Class<T> clazz){
String out = "o";
try {
PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
for(PropertyDescriptor propertydesc : propertyDescriptors){
Method method = propertydesc.getReadMethod();
if(method!=null && method.isAnnotationPresent(EmbeddedId.class)){
PropertyDescriptor[] ps = Introspector.getBeanInfo(propertydesc.getPropertyType()).getPropertyDescriptors();
out = "o."+ propertydesc.getName()+ "." + (!ps[1].getName().equals("class")? ps[1].getName(): ps[0].getName());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return out;
}
}
问题:
1.在一个service连对一个实例两次保存
SystemUser user = (SystemUser) getSystemUser();
initSystemDao.save(user);
initSystemDao.save(user);
引起:TransactionOptions.Builder.withXGfound both Element
解决:在persistence.xml增加
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true"/>
antlr-2.7.6.jar
aopalliance.jar
appengine-api-1.0-sdk-1.3.5.jar
appengine-api-labs-1.3.5.jar
appengine-jsr107cache-1.3.5.jar
cglib-nodep-2.1_3.jar
commons-beanutils.jar
commons-collections.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-lang.jar
commons-logging-1.0.4.jar
commons-logging.jar
datanucleus-appengine-1.0.7.final.jar
datanucleus-core-1.1.5.jar
datanucleus-jpa-1.1.5.jar
freemarker-2.3.13.jar
geronimo-jpa_3.0_spec-1.1.1.jar
geronimo-jta_1.1_spec-1.1.1.jar
jdo2-api-2.3-eb.jar
jsr107cache-1.1.jar
ognl-2.6.11.jar
slf4j-api-1.5.8.jar
slf4j-simple-1.5.8.jar
[color=red][b]spring-aop.jar
spring-beans.jar
spring-context.jar
spring-core.jar
spring-jdbc.jar
spring-orm.jar
spring-tx.jar
spring-web.jar[/b][/color]
struts2-core-2.1.8.1.jar
struts2-gae-0.1.jar
struts2-spring-plugin-2.1.8.1.jar
xwork-core-2.1.6.jar