问题描述:
现在在做一个多线程的爬虫,爬虫后的数据存入数据库。
使用的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所以导致了事务的嵌套呢?
因为这只是自己的一个猜测,也不敢十分肯定,所以特意来问问大家。
能看到这里无论您是否知道解决方法已经很感谢了。
但是还是求帮助啊啊啊啊啊啊啊!!!
先谢谢了!!
问题猜测我个人觉得是合理的,针对这个问题有以下几个方向可以去考虑下:
1:WorkDaoImpl这个类是否有必要做成单例的,如果这个impl只是对这一种情况的话我觉得原生态的应该也没问题;
2:如果该类仍是以单例存在的话,入库操作建议还是单线程去跑它,你可以将所有需要存入数据库的数据放入阻塞队列中,形成类似于生产者消费者模式去逐一进行入库操作;
3:所谓的嵌套事务是你的事务都在单例实例中,是否可以通过声明注解式事务将事务的开启放在Spider_ZhiLian中;这样每个线程一个事务,应该能解决这个问题
以上几点只是提供参考;