4.分页操作
4.1 逻辑分页
MyBatis里面有一个逻辑分页对象RowBounds,里面主要有两个属性,offset和limit(从第几条开始,查询多少条)。我们可以在Mapper接口的方法上加上这个参数,不需要修改xml里面的SQL语句。 复制代码
接口中定义
public List<User> queryUserList(RowBounds rowBounds); 复制代码
测试类
@Test public void test01() throws Exception{ init(); UserMapper mapper = session.getMapper(UserMapper.class); // 设置分页的数据 RowBounds rowBounds = new RowBounds(1,3); List<User> users = mapper.queryUserList(rowBounds); for (User user : users) { System.out.println(user); } } 复制代码
RowBounds的工作原理其实是对ResultSet的处理。它会舍弃掉前面offset条数据,然后再取剩下的数据的limit条。 复制代码
// DefaultResultSetHandler.java private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext(); ResultSet resultSet = rsw.getResultSet(); this.skipRows(resultSet, rowBounds); while(this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, (String)null); Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null); this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } 复制代码
很明显,如果数据量大的话,这种翻页方式效率会很低(跟查询到内存中再使用subList(start,end)没什么区别)。所以我们要用到物理翻页。
4.2 物理分页
物理翻页是真正的翻页,它是通过数据库支持的语句来翻页。
第一种简单的办法就是传入参数(或者包装一个page对象),在SQL语句中翻页。
<select id="selectUserPage" parameterType="map" resultMap="BaseResultMap"> select * from t_user limit #{curIndex} , #{pageSize} </select> 复制代码
第一个问题是我们要在Java业务代码里面去计算起止序号;第二个问题是:每个需要翻页的Statement都要编写limit语句,会造成Mapper映射器里面很多代码冗余。
那我们就需要一种通用的方式,不需要去修改配置的任何一条SQL语句,我们只要传入当前是第几页,每页多少条就可以了,自动计算出来起止序号。
我们最常用的做法就是使用翻页的插件,比如PageHelper。
// pageSize每一页几条 PageHelper.startPage(pn, 10); List<Employee> emps = employeeService.getAll(); // navigatePages 导航页码数 PageInfo page = new PageInfo(emps, 10); return Msg.success().add("pageInfo", page); 复制代码
PageHelper是通过MyBatis的拦截器实现的,插件的具体原理我们后面的课再分析。
简单地来说,它会根据PageHelper的参数,改写我们的SQL语句。比如MySQL会生成limit语句,Oracle会生成rownum语句,SQL Server会生成top语句。
5.MBG与Example
https://github.com/mybatis/generator 复制代码
我们在项目中使用MyBaits的时候,针对需要操作的一张表,需要创建实体类、Mapper映射器、Mapper接口,里面又有很多的字段和方法的配置,这部分的工作是非常繁琐的。而大部分时候我们对于表的基本操作是相同的,比如根据主键查询、根据Map查询、单条插入、批量插入、根据主键删除等等等等。当我们的表很多的时候,意味着有大量的重复工作。 复制代码
所以有没有一种办法,可以根据我们的表,自动生成实体类、Mapper映射器、Mapper接口,里面包含了我们需要用到的这些基本方法和SQL呢? 复制代码
MyBatis也提供了一个代码生成器,叫做MyBatis Generator,简称MBG(它是MyBatis的一个插件)。我们只需要修改一个配置文件,使用相关的jar包命令或者Java代码就可以帮助我们生成实体类、映射器和接口文件。 复制代码
MBG的配置文件里面有一个Example的开关,这个东西用来构造复杂的筛选条件的,换句话说就是根据我们的代码去生成where条件。 复制代码
原理:在实体类中包含了两个有继承关系的Criteria,用其中自动生成的方法来构建查询条件。把这个包含了Criteria的实体类作为参数传到查询参数中,在解析Mapper映射器的时候会转换成SQL条件。 复制代码
5.1 添加配置文件
我们添加如下的配置文件 复制代码
<?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="C:\Users\dpb\.m2\repository\mysql\mysql-connector-java\8.0.11\mysql-connector-java-8.0.11.jar" /> <context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 去掉生成文件中的注释 --> <commentGenerator> <property name="suppressAllComments" value="true" /> </commentGenerator> <!-- 数据库链接URL、用户名、密码 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC" userId="root" password="123456"> <property name="nullCatalogMeansCurrent" value="true" /> </jdbcConnection> <!-- <jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@localhost:1521:XE" userId="car" password="car"> </jdbcConnection> --> <javaTypeResolver > <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- 生成模型的包名和位置 --> <javaModelGenerator targetPackage="com.boge.vip.domain" targetProject="./src/main/java"> <!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] --> <property name="enableSubPackages" value="false" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- 生成的映射文件包名和位置 --> <sqlMapGenerator targetPackage="com.boge.vip.mapper" targetProject="./src/main/java"> <property name="enableSubPackages" value="false" /> </sqlMapGenerator> <!-- 生成DAO的包名和位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.boge.vip.mapper" targetProject="./src/main/java"> <property name="enableSubPackages" value="false" /> </javaClientGenerator> <table tableName="t_user" domainObjectName="User" /> </context> </generatorConfiguration> 复制代码
5.2 添加插件
我们需要在pom.xml中添加对应的插件 复制代码
<build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <!-- 指定配置文件的位置 --> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> </configuration> </plugin> </plugins> </build> 复制代码
5.3 生成
然后我们就可以利用插件帮助我们快速生成我们需要的表结构对应的相关文件 复制代码
6. 通用Mapper
问题:当我们的表字段发生变化的时候,我们需要修改实体类和Mapper文件定义的字段和方法。如果是增量维护,那么一个个文件去修改。如果是全量替换,我们还要去对比用MBG生成的文件。字段变动一次就要修改一次,维护起来非常麻烦。 复制代码
6.1 方式一
第一个,因为MyBatis的Mapper是支持继承的(见:https://github.com/mybatis/mybatis-3/issues/35)。所以我们可以把我们的Mapper.xml和Mapper接口都分成两个文件。一个是MBG生成的,这部分是固定不变的。然后创建DAO类继承生成的接口,变化的部分就在DAO里面维护。 复制代码
public interface UserMapperExt extends UserMapper { public List<User> selectUserByName(String userName); } 复制代码
对应的映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.boge.vip.mapper.UserMapperExt" > <resultMap id="BaseResultMapExt" type="com.boge.vip.domain.User" > <id column="id" property="id" jdbcType="INTEGER" /> <result column="user_name" property="userName" jdbcType="VARCHAR" /> <result column="real_name" property="realName" jdbcType="VARCHAR" /> <result column="password" property="password" jdbcType="VARCHAR" /> <result column="age" property="age" jdbcType="INTEGER" /> <result column="d_id" property="dId" jdbcType="INTEGER" /> <result column="i_id" property="iId" jdbcType="INTEGER" /> </resultMap> <select id="selectUserByName" resultMap="BaseResultMapExt" > select * from t_user where user_name = #{userName} </select> </mapper> 复制代码
在全局配置文件中我们也需要扫描
<mappers> <mapper resource="mapper/UserMapper.xml"/> <mapper resource="mapper/UserMapperExt.xml"/> </mappers> 复制代码
所以以后只要修改Ext的文件就可以了。这么做有一个缺点,就是文件会增多。
6.2 方式二
既然针对每张表生成的基本方法都是一样的,也就是公共的方法部分代码都是一样的,我们能不能把这部分合并成一个文件,让它支持泛型呢? 复制代码
当然可以!
编写一个支持泛型的通用接口,比如叫GPBaseMapper 复制代码
,把实体类作为参数传入。这个接口里面定义了大量的增删改查的基础方法,这些方法都是支持泛型的。
自定义的Mapper接口继承该通用接口,例如BlogMapper extends GPBaseMapper 复制代码
,自动获得对实体类的操作方法。遇到没有的方法,我们依然可以在我们自己的Mapper里面编写。
我们能想到的解决方案,早就有人做了这个事了,这个东西就叫做通用Mapper。 复制代码
用途:主要解决单表的增删改查问题,并不适用于多表关联查询的场景。
除了配置文件变动的问题之外,通用Mapper还可以解决:
- 每个Mapper接口中大量的重复方法的定义;
- 屏蔽数据库的差异;
- 提供批量操作的方法;
- 实现分页。
使用方式:在Spring中使用时,引入jar包,替换applicationContext.xml中的sqlSessionFactory和configure。
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.boge.crud.dao"/> </bean> 复制代码
7.MyBatis-Plus
MyBatis-Plus是原生MyBatis的一个增强工具,可以在使用原生MyBatis的所有功能的基础上,使用plus特有的功能。
MyBatis-Plus的核心功能:
通用 CRUD:定义好Mapper接口后,只需要继承BaseMapper 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件。
条件构造器:通过EntityWrapper(实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的SQL。
代码生成器:支持一系列的策略配置与全局配置,比MyBatis的代码生成更好用。
另外MyBatis-Plus也有分页的功能。