Hibernate 事务嵌套问题

问题描述:

现在在做一个多线程的爬虫,爬虫后的数据存入数据库。
使用的Spring SpringMVC Hibernate
但是当数据爬取6万或者8万9万(不定)的时候。会出现以下错误:

org.hibernate.TransactionException: nested transactions not supported
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:152)
    at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:1396)
    at sun.reflect.GeneratedMethodAccessor71.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:352)

    at $Proxy31.beginTransaction(Unknown Source)
    at com.whr.Dao.Impl.WorkDaoImpl.saveWorkList(WorkDaoImpl.java:60)
    at com.whr.Spiders.Spider_ZhiLian.getJobs(Spider_ZhiLian.java:119)
    at com.whr.Spiders.Spider_ZhiLian.run(Spider_ZhiLian.java:50)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:619)

从控制台的打印来看,问题是事务嵌套了。

各种代码:

但是我的Dao代码如下:

@Repository
public class WorkDaoImpl implements WorkDao {

    @Autowired
    @Qualifier("sessionFactory")
    private SessionFactory sessionFactory;


    public Session getSession() 
    {
        // 事务必须是开启的(Required),否则获取不到
        return sessionFactory.getCurrentSession();
    }
    
    public List<Work> getAllWork() {

        String hql = "from Work";
        getSession().beginTransaction();
        List<Work> list = (List<Work>) getSession().createQuery(hql).list();
        getSession().getTransaction().commit();
        return list;
    }

    public void saveWork(Work work)
    {    
        getSession().save(work);
    }

    public void updateWork(Work work) 
    {
        getSession().update(work);
        getSession().flush();
    }

    public void deleteWork(Work work) 
    {
        getSession().delete(work);
        getSession().flush();
    }

    public void saveWorkList(List<Work> list) 
    {
        
        Transaction tran = getSession().beginTransaction();
        for (Work work : list) {
            getSession().save(work);
        }
        tran.commit();
        getSession().close();
    }

从这段代码来看并没有进行事务的嵌套。

对问题更为细节的描述:

项目的思路是这样的有一个类叫Spider类(用于爬取数据),这个类实现了Runnable接口。原本计划Service层调用这个类,再在这个类中调用Dao层的东西。然后进行数据存储,然而后来发现多线程的类中并不能注入一个Dao的接口。所以做法是在Spider类的构造函数中加入了一个Dao的参数,在Service注入Spider的时候将Dao作为一个参数传入到Spider中。
这么说可能有点乱,各位见谅了。下面是各个类的代码:

Spider类的实现:

@Component
public class Spider_ZhiLian implements Runnable{
    
    private WorkDao workDao;
    private int start;//开始的页码
    private int end;//结束的页码
    private static Logger logger = Logger.getLogger(Spider_ZhiLian.class);
    private CountDownLatch countDownLatch;//计数器,用于统计线程的时间
    private WorkType workType;
    public Spider_ZhiLian(WorkDao workDao,int start,int end,CountDownLatch countDownLatch,WorkType workType) 
    {  
        this.workDao = workDao;
        this.end = end;
        this.start = start;
        this.countDownLatch = countDownLatch;
        this.workType = workType;
    }
    public Spider_ZhiLian() 
    {  
    }
    public void run() {
        // TODO Auto-generated method stub
        getJobs();
    }

Service层:

private final int THREAD_NUMBER = 8;//开辟多少个线程完成这个任务
    private static Logger logger = Logger.getLogger(WorkServiceImpl.class);
    @Autowired
    private WorkDao workDao;
    @Autowired  
    private TaskExecutor taskExecutor;// 线程池
    
    @Autowired
    private Spider_ZhiLian spider_ZhiLian;
    
    public List<Work> getAllWork() {
        return workDao.getAllWork();
        
    }

    
    public void saveWork(int start, int end, WorkType workType)
    {
        long startTime= System.currentTimeMillis();//开始时间 
        
        int totalPage = end - start;//总共有多少页要解析
        int everyThreadPage = totalPage/THREAD_NUMBER;//每个线程分到多少个页面
        
        final CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUMBER);//计数器,倒数到0的时候主线程统计总共用了多少时间
        int i = 0;
        
        for(i =0;i<totalPage-everyThreadPage;i+=everyThreadPage)
        {
            this.taskExecutor.execute(new Spider_ZhiLian(workDao,i,i+everyThreadPage,countDownLatch,workType));
            //logger.info("从  "+ i+ " 页到  "+ (i+everyThreadPage) +" 页 ");
        }
        this.taskExecutor.execute(new Spider_ZhiLian(workDao,i,totalPage,countDownLatch,workType));
        //logger.info("从  "+ i+ " 页到  "+ totalPage +" 页 ");
        try {  
            countDownLatch.await();  
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        //System.out.println("所有线程执行总时间:"+(System.currentTimeMillis()-startTime)/1000+"  秒");  
    }

还有就是上面的Dao了。
事情就是这样的,还望大家帮忙分析分析是什么问题了。程序刚开始跑的时候没问题,时间长了就会出现最开始说的那个问题。

自己对问题的原因猜测:

由于创建多线程的时候Dao作为一个参数传入的,那么是不是所有的线程都用的同一个Dao的实例呢。如果是的话,那是不是在执行:

Transaction tran = getSession().beginTransaction();

的时候,一个线程的事务已经begin了,另一个线程又来begin所以导致了事务的嵌套呢?

因为这只是自己的一个猜测,也不敢十分肯定,所以特意来问问大家。

能看到这里无论您是否知道解决方法已经很感谢了。

但是还是求帮助啊啊啊啊啊啊啊!!!
先谢谢了!!

阅读 4.8k
2 个回答
新手上路,请多包涵

问题猜测我个人觉得是合理的,针对这个问题有以下几个方向可以去考虑下:
1:WorkDaoImpl这个类是否有必要做成单例的,如果这个impl只是对这一种情况的话我觉得原生态的应该也没问题;
2:如果该类仍是以单例存在的话,入库操作建议还是单线程去跑它,你可以将所有需要存入数据库的数据放入阻塞队列中,形成类似于生产者消费者模式去逐一进行入库操作;
3:所谓的嵌套事务是你的事务都在单例实例中,是否可以通过声明注解式事务将事务的开启放在Spider_ZhiLian中;这样每个线程一个事务,应该能解决这个问题
以上几点只是提供参考;

你看看配置文件中,是否有事务的配置,如果对Dao层整体做了事务的配置。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题