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是不被事务管理的,后其他都没有关系

7个回答

呵呵 我以为你的事务陪在了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]

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问