在工作时遇到一种麻烦的问题,我在spring-quartz服务中调用service的方法,该方法主要是查询操作,但始终报错,下面我贴出代码以及配置:
首先是定式服务调用service方法 DoGoodsSync:
public void doGoodsSync() { List<Map<String, Object>> goodsList = remoteDataSourceDao.getGoodsList(); for (Map item : goodsList) { saveItem(item); } } public void saveItem(Map item){ if (goodsSyncDao.specExist(Long.valueOf(item.get("spec_id").toString()))) { goodsSyncDao.saveSpec(Long.valueOf(item.get("goods_id").toString()),Long.valueOf(item.get("spec_id").toString()), (String) item.get("goods_name"), PHPSerializeUtil.unserializeValue((String) item.get("spec_name")), PHPSerializeUtil.unserializeValue((String) item.get("spec_goods_spec")),(String) item.get("spec_goods_serial")); } }
Dao层 GoodsSyncDao:
public boolean specExist(Long goods_spec_id) { return existsSQLQuery("select 1 from hcj_goods_spec where spec_id = ?",goods_spec_id); } public void saveSpec(Long goods_id,Long spec_id,String goods_name,String spec_name,String spec_goods_spec,String spec_goods_serial) { String insert = "insert into hcj_goods_spec values (seq_hcj_goods_spec.nextval,?,?,?,?,?,?)"; createSQLQuery(insert,goods_id,spec_id,goods_name,spec_name,spec_goods_spec,spec_goods_serial).executeUpdate(); }
spring配置信息:
<?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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" default-lazy-init="true"> <bean id="applicationContext" class="org.apache.axis2.extensions.spring.receivers.ApplicationContextHolder" lazy-init="false"></bean> <bean class="com.huicj.jxc.util.ApplicationInstance" lazy-init="false"></bean> <!--扫描组件--> <context:component-scan base-package="com.huicj"/> <!--读取配置文件--> <bean id="configBean" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:dataSource.properties</value> </list> </property> </bean> <!-- 该 BeanPostProcessor 将自动起作用,对标注 @Autowired 的 Bean 进行自动注入 --> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> <!--数据源--> <!--<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">--> <!--<property name="driverClassName" value="${connection.driver_class}"/>--> <!--<property name="url" value="${connection.url}"/>--> <!--<property name="username" value="${connection.username}"/>--> <!--<property name="password" value="${connection.password}"/>--> <!--</bean>--> <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource"> <property name="driver" value="${connection.driver_class}"/> <property name="driverUrl" value="${connection.url}"/> <property name="user" value="${connection.username}"/> <property name="password" value="${connection.password}"/> <!-- 最大连接数(默认20个) --> <property name="maximumConnectionCount" value="50"/> <!-- 最小连接数(默认5个) --> <property name="minimumConnectionCount" value="5"/> <!-- alias 属性可以覆盖默认的别名 --> <property name="alias" value="autumn"/> <!-- trace为true,记录数据库每一步操作 --> <property name="trace" value="true"/> <!-- proxool自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回 收,超时的销毁 (默认30秒) --> <!--<property name="houseKeepingSleepTime"> <value>90000</value> </property> --> <!-- 最少保持的空闲连接数 (默认5个) --> <property name="prototypeCount" value="5"/> <!--verbose:If this is true then we start logging a lot of stuff everytime we serve a connection and everytime the house keeper and prototyper run. Be prepared for a lot of debug! --> <property name="verbose" value="true"/> </bean> <!-- Hibernate配置 --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <!--<prop key="hibernate.connection.isolation">${hibernate.isolation}</prop>--> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.max_fetch_depth">${hibernate.max_fetch_depth}</prop> <prop key="hibernate.connection.release_mode">after_transaction</prop> <!--<prop key="hibernate.hbm2ddl.auto"></prop>--> </props> </property> <!--注入系统实体--> <property name="packagesToScan"> <list> <value>com.huicj.jxc</value> </list> </property> </bean> <!--事务管理器--> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> <property name="nestedTransactionAllowed" value="true"/> </bean> <!--将该事务管理器绑定到@Transaction--> <tx:annotation-driven transaction-manager="transactionManager"/> <!--交易系统正式库数据源--> <bean id="dataSource1" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${connection.driver_class_r}"/> <property name="url" value="${connection.url_r}"/> <property name="username" value="${connection.username_r}"/> <property name="password" value="${connection.password_r}"/> </bean> <!--jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource"> <ref local="dataSource1"/> </property> </bean> <!--事务管理器--> <!-- <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource1"/> </property> </bean>--> <!--<tx:advice id="txadvice1" transaction-manager="transactionManager1"> <tx:attributes> <tx:method name="get*" propagation="NOT_SUPPORTED"/> <tx:method name="bgcgdSync*" propagation="REQUIRED" rollback-for="Throwable"/> </tx:attributes> </tx:advice>--> </beans>
就在定时服务启动时,specExist方法报错,错误信息:
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63) at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:700) at net.ruixin.orm.hibernate.SimpleHibernateTemplate.getSession(SimpleHibernateTemplate.java:43) at net.ruixin.orm.hibernate.SimpleHibernateTemplate.createSQLQuery(SimpleHibernateTemplate.java:341) at net.ruixin.orm.hibernate.SimpleHibernateTemplate.existsSQLQuery(SimpleHibernateTemplate.java:367) at com.huicj.jxc.dao.goods.GoodsSyncDao.specExist(GoodsSyncDao.java:14) at com.huicj.jxc.service.taskdeal.DoGoodsSync.doGoodsSync(DoGoodsSync.java:25) at com.huicj.jxc.quartz.GoodsSyncJob.executeInternal(GoodsSyncJob.java:17) at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:113) at org.quartz.core.JobRunShell.run(JobRunShell.java:223) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:549) 一月 05, 2015 11:17:00 上午 org.quartz.core.ErrorLogger schedulerError 严重: Job (DEFAULT.goodsSyncJob threw an exception. org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here] at org.quartz.core.JobRunShell.run(JobRunShell.java:234) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:549) Caused by: org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63) at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:700) at net.ruixin.orm.hibernate.SimpleHibernateTemplate.getSession(SimpleHibernateTemplate.java:43) at net.ruixin.orm.hibernate.SimpleHibernateTemplate.createSQLQuery(SimpleHibernateTemplate.java:341) at net.ruixin.orm.hibernate.SimpleHibernateTemplate.existsSQLQuery(SimpleHibernateTemplate.java:367) at com.huicj.jxc.dao.goods.GoodsSyncDao.specExist(GoodsSyncDao.java:14) at com.huicj.jxc.service.taskdeal.DoGoodsSync.doGoodsSync(DoGoodsSync.java:25) at com.huicj.jxc.quartz.GoodsSyncJob.executeInternal(GoodsSyncJob.java:17) at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:113) at org.quartz.core.JobRunShell.run(JobRunShell.java:223) ... 1 more
这就是一个简单的查询,以前写查询是也没有遇到过这种情况,后来翻阅资料,发现以前的代码在web.xml中已经配置了HibernateOpenSessionView
<filter> <filter-name>hibernateOpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>sessionFactoryBeanName</param-name> <param-value>sessionFactory</param-value> </init-param> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> </filter <filter-mapping> <filter-name>hibernateOpenSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这个filter是保持在整个请求中,始终保持hibernate session的open状态,所以查询方法如果没有@Transactional注解会自动加上@Transactional(readOnly = true)的注解,而定时器是不经过filter的,只能乖乖在方法上加上@Transactional。
@Transactional public void saveItem(Map item){ if (goodsSyncDao.specExist(Long.valueOf(item.get("spec_id").toString()))) { goodsSyncDao.saveSpec(Long.valueOf(item.get("goods_id").toString()),Long.valueOf(item.get("spec_id").toString()), (String) item.get("goods_name"), PHPSerializeUtil.unserializeValue((String) item.get("spec_name")), PHPSerializeUtil.unserializeValue((String) item.get("spec_goods_spec")),(String) item.get("spec_goods_serial")); } }
问题来了,加上注解以后依然报错,代码跟踪,发现时getCurrentSession()报错,还是没有事物debug后发现Transactional注解根本就是无效的,没办法了继续debug,发现spring对@Transactional的切面是通过代理对象封装并环绕增强来实现的。以下是摘自iteye博文的内容:
在网络应用中,我们几乎总是需要严密控制我们spring应用中的数据库事务的启动和结束。为做到这一点,我们或多或少都已经通过AOP来做这些事情。但一般都是在XXService、或XXController中封装处理请求的方法。
Spring有内置注解支持,因而你可以简单地通过@Transactional 注解你的方法或类,并在配置文件中添加<tx:annotation-driven />,把相应方法封装于一个事务之中。这听起来好像很简单。
但是,所有这些都是通过Spring 的代理对象封装并环绕增强原来的被注解@Transactional 的类来实现的,但这种做法只有当事务方法是public的、并且是被代理类外部调用的情况下才会正常工作(可以参看Spring 事务处理模型图就明白,否则代理对象自己调用自己就会绕过对它的环绕事务增强,其他切面增强也是一样)。这就很不爽了,意味着你不能在XXService或XXController内部串联处理一些具各自独立的事务,例如在XXController调用handleRequestInternal。解决的办法是使用全功能完整成熟的AspectJ织入。AspectJ织入方式同样支持@Transactional (其他自定义注解也行^_^),同时能被织入到所有方法中(不只是public),并且在内不外部都可以。
AspectJ有三种方式织入事务代码
a.编译时(CTW). 拥有所有需要的源代码
b.运行时(LTW).
c.字节码织入(BTW).没有织入目标的源代码(如只有jar)
这里我们使用CTW的方式
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"> <aop:spring-configured> <bean id="annotationTransactionAspect" factory-method="aspectOf" class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"> <property name="transactionManager" ref="transactionManager"></property> </bean> <!-- the rest of your application here --> </beans>
编译后把你的war发布到任何web容器中他就能工作了,所有注解了@Transactional 的方法(各种可见度)都能正常的处理事务,如果是类级@Transactional 注解,该类的就所有public方法都有事务。而且被注解类的内外都能调用,这样,你完全可以撇开spring那麻烦的代理了,还补充一句,如果你使用了 DWR做为你的ajax后台的话,服务层如果是JDK代理的话,将无法工作。只能使用Cglib方式的代理。还有很多情况,Spring 代理模式和其他一些框架配合工作的时候会有问题,全部使用AspectJ,撇开Spring 代理模式,你会觉得真的很free。