Spring事务的问题,bulkUpdate,或query.executeUpdate
今天在测试spring任务调度时,突然发现我配的声明事务不起作用了,找了好久才发现不是我的事务的问题,是我在Dao中用了一个方法有问题
方法如下:
public void updateByIds(final Set updateIds)throws DaoException{
try {
String queryString="update from Yaoyueyingyue y set y.state='2' where y.yaoyueid in (?)";
getHibernateTemplate().bulkUpdate(queryString, updateIds.toArray());
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
updateIds是一个包含要更新的编号集合,我发现用这个方法在我Manager中调用Dao事务就不起作用了,后来又改成这样
public void updateByIds(final Set updateIds)throws DaoException{
try {
getHibernateTemplate().execute(new HibernateCallback(){
final String hql="update Yaoyueyingyue y set y.state=2 where yaoyueid in (:yaoyueid)";
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query query=session.createQuery(hql);
query.setParameterList("yaoyueid", updateIds);
query.executeUpdate();
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
}
事务还是不行,这两个方法好像不受spring AOP事务管理,只要执行到这个Dao的方法就自动提交了,出来异常也不能回滚,真是郁闷,小弟对这块不是很明白,为什么事务就不行了呢,希望那位牛人,帮我解释下,谢谢了,
我的spring声明事务大概如下:
true
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300"/>
</bean>
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager"><ref bean="atomikosTransactionManager" /></property>
<property name="userTransaction"><ref bean="atomikosUserTransaction" /></property>
</bean>
aop:config
<!--
This definition creates auto-proxy infrastructure based on the given pointcut,
expressed in AspectJ pointcut language. Here: applying the advice named
"txAdvice" to all methods on classes named PetStoreImpl.
-->
advice-ref="txAdvice" />
advice-ref="txAdvice" />
/aop:config
<!-- @Transactional 时要使用下面一行 -->
<!-- -->
<!-- Transaction advice definition, based on method name patterns.
Defaults to PROPAGATION_REQUIRED for all methods whose name starts with
"insert" or "update", and to PROPAGATION_REQUIRED with read-only hint
for all other methods.-->
<!-- 引用springTransactionManager -->
tx:attributes
....
/tx:attributes
/tx:advice
atomikosTransactionManager这个东西不用管,是一个开源的支持JTA分布式的JAR,希望有人能够为我解答........
[b]问题补充:[/b]
谢谢你的解答,但是我还是不明白你的意思,你是指我的Dao中用了内部类吗,你所指的 “调用updateByIds方法 的代码 跳出你的当前类 然后在别的类调用当前了类的接口中的方法updateByIds ”是指什么意思,能不能说明白点,谢谢了!!!
[b]问题补充:[/b]
谢谢你的答复,你的意思我也理解,但好像不是这个问题,因为我并没有像你说的那样在类的内部调用,我的所有方法都是在业务逻辑层调用的Manager层,Manager层我是在Spring中配置了的声明事务的,我给你据个例子:
这两个方法都是Dao中的
方法一:
[code="java"]
public void updateByIds(final Set updateIds)throws DaoException{
try {
/*String queryString="update from Yaoyuepub y set y.state='2' where y.yaoyueid in (?)";
getHibernateTemplate().bulkUpdate(queryString, updateIds.toArray());*/
getHibernateTemplate().execute(new HibernateCallback(){
final String hql="update Yaoyuepub y set y.state=2 where yaoyueid in (:yaoyueid)";
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query query=session.createQuery(hql);
query.setParameterList("yaoyueid", updateIds);
query.executeUpdate();
return null;
}
}
);
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
}
[/code]
这个方法是把所有的要改的ID都一次性更新调,我是不想执行多条sql,在Manager中调用这个方法事务就起不了作用,还有一个方法,就是普通的更新对象
[code="java"]
public void update(T t) throws DaoException {
try {
getHibernateTemplate().update(t);
} catch (Exception e) {
throw new DaoException(getClassName() + " update exception...",e);
}
}
[/code]
要是把刚才Manager中调用改成循环执行下面的方法一个一个对象,就是有事务的,所以调用都一样,更类的内部调用应给没有关系的,我认为不管是query.executeUpdate还是spring自己提供的bulkUpdate这两个方法都是要写sql的,目的是满足批量更新和更大的灵活性,但是事务就不行了,我认为肯定可以让声明式事务支持这两个方法,就是不知道怎么配置一下,你可以自己在代码中分别做个例子试试,看看是不是用批量更新事务就控制不了了,
这就是我的理解,还请多多指教,谢谢诶!!!
[b]问题补充:[/b]
我测试过了,以为可以了,但是还是不行,下面是我调用的一小部分代码
[code="java"]
public void runThread() {
Set updateIds = new HashSet();
for (Yaoyueyingyue yaoyueyingyue : yaoyueyingyues) {
updateIds.add(yaoyueyingyue.getYaoyueid());
}
if (updateIds.size() > 0) {
yaoyuepubDao.updateByIds(updateIds);
if(true)
throw new RuntimeException("AAAAAAAAAAAAAAAAAAAAAAAAA");
yaoyueyingyueDao.updateByIds(updateIds);
}
[/code]
上面是我Manager中Spring任务调度自动执行的方法的一小部分,我中间估计抛出了异常,但是yaoyuepubDao数据库中都更新了,事务不起作用,我估计其实就是和bulkUpdate方法一样,只要这个方法能用事务控制了的话,应该没问题了
[b]问题补充:[/b]
下面是我在网上拷贝的--------------------------------
Spring的HibernateTemplate提供了Hibernate的完美封装,即通过匿名类实现回调,来保证Session的自动资源管理和事务的管理。其中核心方法是:
java代码:
HibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
....
}
}
回调方法提供了session作为参数,有了session,就可以自由的使用Hibernate API编程了。使用了spring的之后,代码修改如下:
web层代码:
java代码:
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Department.class);
detachedCriteria.createAlias("employees", "e").add(Restrictions.eq("name", "department")).add(Restrictions.gt(("e.age"), new Integer(20)));
departmentManager.findByCriteria(detachedCriteria);
构造detachedCriteria,作为参数传递给departmentManager
业务层代码使用spring,DepartmentManager的findByCriteria如下:
java代码:
public List findByCriteria(final DetachedCriteria detachedCriteria) {
return (List) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
return criteria.list();
}
});
}
实际上也就是:
java代码:
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
return criteria.list();
而已
但是该程序代码执行,会抛出强制类型转换异常!
我跟踪了一下spring和Hibernate源代码,原因如下:
spring的HibernateTemplate的execute方法提供的回调接口具有Session作为参数,但是实际上,默认情况下,HibernateTemplate传递给回调接口的session并不是org.hibernate.impl.SessionImpl类,而是SessionImpl类的一个Proxy类。之所以替换成为一个Proxy类,HibernateTemplate的注释说明,Proxy提供了一些额外的功能,包括自动设置Cachable,Transaction的超时时间,Session资源的更积极的关闭等等。
java代码:
private boolean exposeNativeSession = false;
...
execute方法内部:
Session sessionToExpose = (exposeNativeSession ? session : createSessionProxy(session));
但是遗憾的是,Hibernate的DetachedCriteria的setExecutableCriteria方法却要求将session参数强制转为SessionImpl,但是spring传过来的却是一个Proxy类,因此就报错了。
java代码:
public Criteria getExecutableCriteria(Session session) {
impl.setSession( (SessionImpl) session ); // 要求SessionImpl,Spring传递的是Proxy
return impl;
}
解决方法,禁止Spring的HibernateTemplate传递Proxy类,强制要求它传递真实的SessionImpl类,即给exexute方法增加一个参数,提供参数为true,如下:
java代码:
public List findByCriteria(final DetachedCriteria detachedCriteria) {
return (List) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
return criteria.list();
}
}, true);
}
[b]问题补充:[/b]
bulkUpdate这个方法按你给的源码,那我是用错了,但是你所说的我的模拟异常不再AOP的事务之内,我就不同意你的观点了,[code="java"]
if (updateIds.size() > 0) {
yaoyuepubDao.updateByIds(updateIds); // 事务开启 执行updateByIds 事务提交
if(true)
throw new RuntimeException("AAAAAAAAAAAAAAAAAAAAAAAAA");
yaoyueyingyueDao.updateByIds(updateIds); // 事务开启 执行updateByIds 事务提交
}
[/code]
我这段代码是两个Dao的操作,而这两个Dao的操作是被封装在一个Manger中的方法中的,Manager的每个方法都是有事务的,在操作玩第一个Dao后抛出一个RunTime异常,这时候第一个Dao操作已经执行了,这时候事务应该回滚的,不应该去更新的第一个Dao的操作,Manager中本来就业务层,中间有好多的Dao操作,事务应该控制这些Dao要不都提交,要不都回滚,你说呢,而你说的在11-12行之间加异常,那在一个Dao中,再说的的Dao是没有配事务的,又何谈回滚呢,要是把我上面两个Dao操作改成普通的对象更新,是可以回滚的,这个我肯定
比如这样
[code="java"]
yaoyuepubDao.update(yaoyuepub);
if(true)
throw new RuntimeException("AAAAAAAAAAAAAAAAAAAAAAAAA");
yaoyueyingyueDao.update(yaoyueyingyue);
[/code]
这个时候如果抛出异常,yaoyuepub是不会更新到数据库的,会回滚的,所以我总结就是executeUpdate(sql)这个方法我们直接sql,和操作对象是不一样的的,具体我也没有研究
[b]问题补充:[/b]
[code="java"]
public void updateByIds(final Set updateIds)throws DaoException{
try {
getHibernateTemplate().execute(new HibernateCallback(){
final String hql="update Yaoyuepub y set y.state=2 where yaoyueid in (:yaoyueid)";
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query query=session.createQuery(hql);
query.setParameterList("yaoyueid", updateIds);
query.executeUpdate();
return null;
}
}
, true);
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
}
[/code]
把上面的updateIds方法改成:
[code="java"]
public void updateByIds(final Set updateIds)throws DaoException{
try {
DetachedCriteria dc=DetachedCriteria.forClass(Yaoyuepub.class);
dc.add(Restrictions.in("yaoyueid", updateIds));
List yaoyuepubs=select(dc);
for(Yaoyuepub y:yaoyuepubs){
update(y);
}
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
}
[/code]
所有的调用都不变,spring事务就起作用了,说明自己createQuery然后executeUpdate是不被事务管理的,后其他都没有关系
呵呵 我以为你的事务陪在了Dao这一层了
如果是你说的Manager那么这样抛出异常模拟事务回滚是对的
至于你说自己createQuery 执行hql不受事务控制 我可以明确的告诉你 没有这种说法
我之前三四个Spring+Hibernate的项目经验告诉我事务这样控制是没有问题的
找找其他原因吧 不可能是你总结的那个原因
因为代理类实现机制的原因 类的内部方法调用不会被Spring事务拦截 即使你的方法名满足你的事务声明也没有作用
所以你需要修改 调用updateByIds方法 的代码 跳出你的当前类 然后在别的类调用当前了类的接口中的方法updateByIds 从而间接调用updateByIds 这样事务才能起作用
[code="java"]
class ADao {
public void updateByIds() {
// do something.
}
public void someMethod() {
updateByIds(); // 这一行就是类内部的方法调用
}
}
[/code]
在你的例子中意思就是 有某个方法调用了 updateByIds, 但是这个调用并不会被你声明的事务拦截到, 也就是你的事务声明对这个调用[code="java"]updateByIds(); // 这一行就是类内部的方法调用[/code]不起作用
所谓跳出当前类 举个例子 将调用updateByIds()的方法移除dao,放到service中
[code="java"]
class ADao {
public void updateByIds() {
// do something.
}
}
class AService {
private ADaoInterface aDao; // 在配置文件中或Annotation注入
public void someMethod() {
aDao.updateByIds(); // 这样调用可以被事务声明拦截到
}
}
[/code]
或者
[code="java"]
class ADao {
private ADaoInterface self; // 在配置文件中或Annotation注入
public void updateByIds() {
// do something.
}
public void someMethod() {
self.updateByIds(); // 这样调用可以被事务声明拦截到
}
}[/code]
如果你理解了上面的意思, someMethod 放在哪里, 怎么调用 updateByIds 可以自己看着b办. 总之问题就是类内部的方法调用是不会被Spring AOP拦截到的.
试一试像这样
[code="java"]
public void updateByIds(final Set updateIds)throws DaoException{
try {
/*String queryString="update from Yaoyuepub y set y.state='2' where y.yaoyueid in (?)";
getHibernateTemplate().bulkUpdate(queryString, updateIds.toArray());*/
getHibernateTemplate().execute(new HibernateCallback(){
final String hql="update Yaoyuepub y set y.state=2 where yaoyueid in (:yaoyueid)";
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query query=session.createQuery(hql);
query.setParameterList("yaoyueid", updateIds);
query.executeUpdate();
return null;
}
}
, true);
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
}
[/code]
调用这个方法
HibernateTemplate.execute(HibernateCallback action, boolean exposeNativeSession)
第二个参数给true.
看看有没有效果
因为我看Spring代码里 HibernateTemplate.update 也使用excute不过给了第二个参数true.
从字面上理解 exposeNativeSession 隐藏 native session, 给true表示不使用native session, 也就是使用Spring管理的session. 我想native session是不是默认没有事务, 而Spring控制的事务只会作用在Spring管理的session上.
你这样试试 看看有没有效果
[code="java"]
Object execute(HibernateCallback action, boolean enforceNativeSession)
Deprecated. as of Spring 2.5, in favor of executeWithNativeSession(org.springframework.orm.hibernate3.HibernateCallback)
Object executeWithNativeSession(HibernateCallback action)
Execute the action specified by the given action object within a native Session.
Object executeWithNewSession(HibernateCallback action)
Execute the action specified by the given action object within a new Session.
[/code]
注意 我的Spring代码是2.0的 如果你用的是2.5的 那么这个方法是被deprecate掉了
所以如果是2.5的Spring 那么使用 executeWithNewSession
[quote]中间估计抛出了异常[/quote]估计是什么意思呢。。
你的代码
[code="java"]
if (updateIds.size() > 0) {
yaoyuepubDao.updateByIds(updateIds); // 事务开启 执行updateByIds 事务提交
if(true)
throw new RuntimeException("AAAAAAAAAAAAAAAAAAAAAAAAA");
yaoyueyingyueDao.updateByIds(updateIds); // 事务开启 执行updateByIds 事务提交
}
[/code]
这段代码中 你抛出的异常并不在AOP拦截的代码中间 所以不会造成回滚
然后你的这段代码是有问题的
[code="java"]
String queryString="update from Yaoyueyingyue y set y.state='2' where y.yaoyueid in (?)";
getHibernateTemplate().bulkUpdate(queryString, updateIds.toArray());
[/code]
bulkUpdate的第二个参数是所有要传入query的参数 附上Spring源码
[code="java"]
public int bulkUpdate(final String queryString, final Object[] values) throws DataAccessException {
Integer updateCount = (Integer) executeWithNativeSession(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
Query queryObject = session.createQuery(queryString);
prepareQuery(queryObject);
if (values != null) {
for (int i = 0; i < values.length; i++) {
queryObject.setParameter(i, values[i]);
}
}
return new Integer(queryObject.executeUpdate());
}
});
return updateCount.intValue();
}
[/code]
而你的query其实只有一个参数 而你传入的是将你的那一个参数分成了多个 这样 bulkUpdate只会传入你的updateIds.toArray()后的数组中的第一个元素
注意看Spring代码的这一行
[code="java"]queryObject.setParameter(i, values[i]);[/code]
比如 updateIds = 【1, 2, 3, 4, 5】 而你这样使用 最后传进去给query的只有 1
也就是说queryString最后等于 "update from Yaoyueyingyue y set y.state='2' where y.yaoyueid in (1)"
如果真想模拟抛出异常 可以在下面代码的第11-12行之间加上你的throw代码
[code="java"]
public void updateByIds(final Set updateIds)throws DaoException{
try {
/*String queryString="update from Yaoyuepub y set y.state='2' where y.yaoyueid in (?)";
getHibernateTemplate().bulkUpdate(queryString, updateIds.toArray());*/
getHibernateTemplate().execute(new HibernateCallback(){
final String hql="update Yaoyuepub y set y.state=2 where yaoyueid in (:yaoyueid)";
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query query=session.createQuery(hql);
query.setParameterList("yaoyueid", updateIds);
query.executeUpdate();
return null;
}
}
);
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(this.getClassName()+e.getMessage());
}
}
[/code]