pagehelper / Mybatis-PageHelper

Mybatis通用分页插件
https://mybatis.io
MIT License
12.18k stars 3.13k forks source link

高并发测试时,出现RuntimeException #56

Closed xzhiwei closed 7 years ago

xzhiwei commented 7 years ago

使用版本:4.1.6 说明:查看5.0.1版本源码,该部分代码没变,应该也可以重现该bug 异常现场

### Error querying database.  Cause: java.lang.RuntimeException: 无法处理该类型[class com.github.pagehelper.sqlsource.PageDynamicSqlSource]的SqlSource
### Cause: java.lang.RuntimeException: 无法处理该类型[class com.github.pagehelper.sqlsource.PageDynamicSqlSource]的SqlSource
    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:79)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:447)
    at com.sun.proxy.$Proxy72.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:231)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:128)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:68)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53

重现: 在启一个线程池,进行同一个分页查询操作时,出现RuntimeException,无法处理该类型PageDynamicSqlSource

所用代码如下:

    @Test
    public void search() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i = 0;i<10;i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    Object result = testService.search(1, new Random().nextInt(1000));
                    logger.info(JSONObject.toJSONString(result));
                }
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isTerminated()){
                break;
            }
            Thread.sleep(100);
        }
    }

异常分析: 在SqlUtils方法中有以下处理:

     //判断并处理为PageSqlSource
        if (!isPageSqlSource(ms)) {
            processMappedStatement(ms);
        }

其中: processMappedStatement方法为:

public void processMappedStatement(MappedStatement ms) throws Throwable {
        SqlSource sqlSource = ms.getSqlSource();
        MetaObject msObject = SystemMetaObject.forObject(ms);
        SqlSource pageSqlSource;
        if (sqlSource instanceof StaticSqlSource) {
            pageSqlSource = new PageStaticSqlSource((StaticSqlSource) sqlSource);
        } else if (sqlSource instanceof RawSqlSource) {
            pageSqlSource = new PageRawSqlSource((RawSqlSource) sqlSource);
        } else if (sqlSource instanceof ProviderSqlSource) {
            pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) sqlSource);
        } else if (sqlSource instanceof DynamicSqlSource) {
            pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) sqlSource);
        } else if(sqlSource instanceof PageDynamicSqlSource) {
            pageSqlSource = sqlSource;
        }else {
            throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
        }
        msObject.setValue("sqlSource", pageSqlSource);
        //由于count查询需要修改返回值,因此这里要创建一个Count查询的MS
        msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms));
    }

由于processMappedStatement是非线程安全的方法,虽然前面进行过if判断,进入这个方法体后sqlSource对象还是有可能是处理过的PageSqlSource,此时instanceof 判断失败,抛出RuntimeException

建议订正: 在条件判断最后,再进行一次PageSqlSource的判断,如果是,直接返回:

else if(sqlSource instanceof PageSqlSource){
    pageSqlSource  = sqlSource ;
}else {
            throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
 }
abel533 commented 7 years ago

5.0.0+不使用sqlsource进行处理了,所以应该没有这个问题,建议你试试最新版本。