天天看點

mybatis分頁pageHelper的源碼檢視

mybatis在springboot的內建

內建springboot很簡單,直接用 boot的starter即可,

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
           

配置加上:

# Mysql 注意替換相應配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://xx.xx.xx.xx:3306/aaaa?useUnicode=true&characterEncoding=utf8
spring.datasource.username=aaaa
spring.datasource.password=aaaa
           
如果不是用springboot,則自己網上找下教程吧.

mybatis的自動生成xml

mybatis的sql都要自己寫,比較麻煩,我們使用自動生成的方式

官網:http://www.mybatis.org/generator/index.html

使用步驟:

1,在pom.xml中增加插件配置

<!-- mybatis generator 自動生成代碼插件 -->
<plugin>
	<groupId>org.mybatis.generator</groupId>
	<artifactId>mybatis-generator-maven-plugin</artifactId>
	<version>1.3.2</version>
	<configuration>
		<configurationFile>src/main/resources/mapper/generatorConfig.xml</configurationFile>
		<overwrite>true</overwrite>
		<verbose>true</verbose>
	</configuration>
</plugin>
           

2,把要相應的配置檔案generatorConfig.xml加上

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!-- 資料庫驅動:選擇你的本地硬碟上面的資料庫驅動包-->
    <classPathEntry  location="src/main/resources/mapper/mysql-connector-java-5.1.46.jar"/>
    <context id="DB2Tables"  targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自動生成的注釋 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--資料庫連結URL,使用者名、密碼 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://xxxx:3306/aaa" userId="aaa" password="aaa">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 生成模型的包名和位置-->
        <javaModelGenerator targetPackage="com.saaa.bsss.test.model.po.db" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成映射檔案的包名和位置-->
        <sqlMapGenerator targetPackage="generator" targetProject="src/main/resources/mapper">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成DAO的包名和位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.saaa.bsss.test.dao.mapper.generator" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!-- 要生成的表 tableName是資料庫中的表名或視圖名 domainObjectName是實體類名-->

        <table tableName="t_aaa" domainObjectName="AaaPO"></table>
        <table tableName="t_bbb" domainObjectName="BaaPO"></table>

    </context>
</generatorConfiguration>
           
注意要把 mysql-connector-java-5.1.46.jar加到工程中.同時上面所涉及到檔案夾要存在。

3,執行mvn指令,生成相應的代碼:

mybatis-generator:generate -e
           
MyBatis Generator是一個maven插件,其運作時并不依賴 spring或 mybatis,隻是一個反向生成代碼的工具,根據 db中的表進行映射和生成.

生成的代碼運作例子

使用時,直接用生成的代碼來處理,如

@Autowired
    private UserPOExtMapper userDTOMapper;

     public   int insert(UserPO userDTO) {
        userDTO.setStatus(ProjectConstant.UserConstant.USER_STATUS_OK);
        userDTO.setCreateTime(new Date());
        return  userDTOMapper.insert(userDTO);
    }


    public UserPO findByUserid(Integer userid) {
        UserPOExample example = new UserPOExample();
        example.or().andUseridEqualTo(userid).andStatusEqualTo(ProjectConstant.UserConstant.USER_STATUS_OK);
        List<UserPO> userDTOS=  userDTOMapper.selectByExample(example);
        if (userDTOS == null || userDTOS.size() < 1) {
            return  null;
        }
        return userDTOS.get(0);
    }
           

mybatis的分頁

分頁,我們采用com.github.pagehelper

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.5</version>
</dependency>
           

在使用時,

public Page<AaaPO> findByUser(int userid,int pageNum, int pageSize) {
        Page<AaaPO> cpage = PageHelper.startPage(pageNum, pageSize);
        AaaPOExample example = new AaaPOExample();
        example.or();
        List<String> tostrList = new ArrayList<>();
        tostrList.add("all");
        tostrList.add(userid+"");
        example.or().andMToIn(tostrList);
        example.setOrderByClause("update_time desc");
        AaaPOMapper.selectByExample(example);
        return  cpage;
    }
           

說明, 如果我們看mapper.xml,會發現,裡面是沒有 資料庫相應的 分頁邏輯, 如mysql的limit, 但我們采用 com.github.pagehelper 卻可以分頁,這個很詭異的事情,同時com.github.pagehelper是用靜态方法來做的,高并發時不會出錯嗎?後續這方面的相應源碼.

com.github.pagehelper是一個開源的庫,是一個mybatis的插件,主要作用就是在mybatis查詢的時候,針對各資料庫加上分頁邏輯,同時傳回。

mybatis 中pageHelper的源碼

我們在

com.github.pagehelper.Page中的setTotal(long total)中打個斷點,

stack如下:

setTotal:189, Page (com.github.pagehelper)
afterCount:89, AbstractHelperDialect (com.github.pagehelper.dialect)
afterCount:82, PageHelper (com.github.pagehelper)
intercept:117, PageInterceptor (com.github.pagehelper)
invoke:61, Plugin (org.apache.ibatis.plugin)
query:-1, $Proxy170 (com.sun.proxy)
selectList:148, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:141, DefaultSqlSession (org.apache.ibatis.session.defaults)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:433, SqlSessionTemplate$SqlSessionInterceptor (org.mybatis.spring)
selectList:-1, $Proxy99 (com.sun.proxy)
selectList:230, SqlSessionTemplate (org.mybatis.spring)
executeForMany:139, MapperMethod (org.apache.ibatis.binding)
execute:76, MapperMethod (org.apache.ibatis.binding)
invoke:59, MapperProxy (org.apache.ibatis.binding)
selectByExample:-1, $Proxy135 (com.sun.proxy)
findByUseridAndAll:39, MyUserDAO (com.saaa.bsss.test.dao.db)
esTest:31, MyUserDAOTest (com.saaa.bsss.test.dao.db)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
runReflectiveCall:50, FrameworkMethod$1 (org.junit.runners.model)
run:12, ReflectiveCallable (org.junit.internal.runners.model)
invokeExplosively:47, FrameworkMethod (org.junit.runners.model)
evaluate:17, InvokeMethod (org.junit.internal.runners.statements)
evaluate:73, RunBeforeTestExecutionCallbacks (org.springframework.test.context.junit4.statements)
evaluate:83, RunAfterTestExecutionCallbacks (org.springframework.test.context.junit4.statements)
evaluate:75, RunBeforeTestMethodCallbacks (org.springframework.test.context.junit4.statements)
evaluate:86, RunAfterTestMethodCallbacks (org.springframework.test.context.junit4.statements)
evaluate:84, SpringRepeat (org.springframework.test.context.junit4.statements)
runLeaf:325, ParentRunner (org.junit.runners)
runChild:251, SpringJUnit4ClassRunner (org.springframework.test.context.junit4)
runChild:97, SpringJUnit4ClassRunner (org.springframework.test.context.junit4)
run:290, ParentRunner$3 (org.junit.runners)
schedule:71, ParentRunner$1 (org.junit.runners)
runChildren:288, ParentRunner (org.junit.runners)
access$000:58, ParentRunner (org.junit.runners)
evaluate:268, ParentRunner$2 (org.junit.runners)
evaluate:61, RunBeforeTestClassCallbacks (org.springframework.test.context.junit4.statements)
evaluate:70, RunAfterTestClassCallbacks (org.springframework.test.context.junit4.statements)
run:363, ParentRunner (org.junit.runners)
run:190, SpringJUnit4ClassRunner (org.springframework.test.context.junit4)
run:137, JUnitCore (org.junit.runner)
startRunnerWithArgs:68, JUnit4IdeaTestRunner (com.intellij.junit4)
startRunnerWithArgs:47, IdeaTestRunner$Repeater (com.intellij.rt.execution.junit)
prepareStreamsAndStart:242, JUnitStarter (com.intellij.rt.execution.junit)
main:70, JUnitStarter (com.intellij.rt.execution.junit)
           

會被調用的機制是什麼

說明pageHelper是使用 mybatis的攔截器進行操作

上面的 invoke:61, Plugin (org.apache.ibatis.plugin)

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
           

其中上面的 interceptor 對象,就是page的攔截器!!

那攔截器是哪裡被配置到mybatis的呢?

我們把pageHelper加到服務上,并沒有增加顯式的配置,說明有可能是用spring-boot的機制 來進行配置,

我們找到

pagehelper-spring-boot-autoconfigure-1.2.5.jar

中的 com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration 類

其中

@PostConstruct
    public void addPageInterceptor() {
        PageInterceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        properties.putAll(this.pageHelperProperties());
        properties.putAll(this.properties.getProperties());
        interceptor.setProperties(properties);
        Iterator var3 = this.sqlSessionFactoryList.iterator();

        while(var3.hasNext()) {
            SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var3.next();
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
        }

    }
           

上面的代碼的 SqlSessionFactory 是org.apache.ibatis.session.SqlSessionFactory ,這個方法把interceptor初始化後扔到sqlSessionFactory中

@PostConstruct,這個annotation可以了解為,在這個方法在此類調用了construct後執行.

攔截器裡面的邏輯?為什麼用靜态可以?

按圖索骥,我們找到相應的攔截器

com.github.pagehelper.PageInterceptor

在Page cpage = PageHelper.startPage(pageNum, pageSize);時

往裡走

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
...
LOCAL_PAGE.set(page);
           

這裡說明這個page對象 是和線程綁定的,在代碼運作的過程中,相應資料找到這個靜态,通過線程找到對象 ,進行回填。

資料是怎麼扔到page的?

邏輯上都是在 PageInterceptor中處理的,

回填是調用AbstractHelperDialect類的下面兩個方法:

@Override
    public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
        Page page = getLocalPage();
        page.setTotal(count);
        if (rowBounds instanceof PageRowBounds) {
            ((PageRowBounds) rowBounds).setTotal(count);
        }
        //pageSize < 0 的時候,不執行分頁查詢
        //pageSize = 0 的時候,還需要執行後續查詢,但是不會分頁
        if (page.getPageSize() < 0) {
            return false;
        }
        return count > 0;
    }
           
@Override
    public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
        Page page = getLocalPage();
        if (page == null) {
            return pageList;
        }
        page.addAll(pageList);
        if (!page.isCount()) {
            page.setTotal(-1);
        } else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
            page.setTotal(pageList.size());
        } else if(page.isOrderByOnly()){
            page.setTotal(pageList.size());
        }
        return page;
    }
           

mybatis分頁對頁數超出範圍的處理

分頁時,如果總11條資料,5條一頁,則pageNum最大是3,如果我們傳5,理論上是傳回空數組。

中有一個設定,“分頁合理化,針對不合理的頁碼自動處理”,即,如果傳pageNum為5,分頁時其會按3來查.具體源碼如下:

com.github.pagehelper.Page

public void setTotal(long total) {
        this.total = total;
        if (total == -1) {
            pages = 1;
            return;
        }
        if (pageSize > 0) {
            pages = (int) (total / pageSize + ((total % pageSize == 0) ? 0 : 1));
        } else {
            pages = 0;
        }
        //分頁合理化,針對不合理的頁碼自動處理
        if ((reasonable != null && reasonable) && pageNum > pages) {
            pageNum = pages;
            calculateStartAndEndRow();
        }
    }
           

如果要修改,設定 page.setReasonable(false); 即可

參考:

mybatis的插件怎麼做?

http://www.mybatis.org/mybatis-3/configuration.html#plugins

中文:

http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins

pageHelper官網

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md