目录
4.1 分页插件
分页插件使用步骤
4.1.1 在pom.xml中添加依赖
<!-- 设置分页插件依赖-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
4.1.2 在核心配置文件中配置分页插件
<plugins>
<!-- 设置分页查询插件 intercept:拦截器,value值是固定的-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
4.1.3 分页插件的使用步骤
// 查询第二页,一页有两条数据
@Test
public void testPage(){
// 1)开启分页功能:参数1(pageNum)代表是第几页,参数2(pageSize)代表是每页显示几条数据
// 拼接出来的sql语句是 第一个占位符是从第几条数据开始,第二个数据是每页显示几条数据
// select * from dept LIMIT ?, ?
PageHelper.startPage(2,2);
// 2)查询获取结果
List<Dept> depts = mapper.selectAll();
//返回的是Page对象,Page是ArrayList的子类。由于Page重写了toString方法
// 3)使用pageInfo功能获取分页相关数据
// 参数1:list:这里是depts,是分页使用的数据
// 参数2:navigatePages:导航分页的页码数
PageInfo<Dept> pageInfo = new PageInfo<>(depts);
System.out.println("pageInfo = " + pageInfo);
//SQL查询的数据总条数
System.out.println("total:数据总条数"+pageInfo.getTotal());
//总分页数
System.out.println("pages:总分页数"+pageInfo.getPages());
//自动生成一个分页导航,大小为8(如果满足)[1, 2, 3, 4, 5, 6, 7, 8]
System.out.println("navigatepageNums:分页导航"+ Arrays.toString(pageInfo.getNavigatepageNums()));
//分页导航的第一页
System.out.println("navigateFirstPage:分页导航的第一页"+pageInfo.getNavigateFirstPage());
//分页导航的最后一页
System.out.println("navigateLastPage:分页导航的最后一页"+pageInfo.getNavigateLastPage());
//分页导航的总页数
System.out.println("navigatePages:分页导航的总页数"+pageInfo.getNavigatePages());
//当前页
System.out.println("pageNum:当前页"+pageInfo.getPageNum());
//当前页的上一页
System.out.println("prePage:当前页的上一页"+pageInfo.getPrePage());
//当前页的下一页
System.out.println("nextPage:当前页的下一页"+pageInfo.getNextPage());
//每页的数据条数
System.out.println("pageSize:每页的数据条数"+pageInfo.getPageSize());
//当前页的开始行号
System.out.println("startRow:当前页的开始行号"+pageInfo.getStartRow());
//当前页的结束行号
System.out.println("endRow:当前页的结束行号"+pageInfo.getEndRow());
}
分页查询结果:
// 此时数据库中只有三条信息,查询第二页,每页两条信息获得的结果
// pageInfo = PageInfo{pageNum=2, pageSize=2, size=1, startRow=3, endRow=3, total=3, pages=2,
// list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=3, pages=2, reasonable=false, pageSizeZero=false}
// [Dept(deptno=3, dname1=null, loc=郑州)], prePage=1, nextPage=0, isFirstPage=false, isLastPage=true,
// hasPreviousPage=true, hasNextPage=false, navigatePages=2, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}
total:数据总条数3
pages:总分页数2
navigatepageNums:分页导航[1, 2]
navigateFirstPage:分页导航的第一页1
navigateLastPage:分页导航的最后一页2
navigatePages:分页导航的总页数2
pageNum:当前页2
prePage:当前页的上一页1
nextPage:当前页的下一页0
pageSize:每页的数据条数2
startRow:当前页的开始行号3
endRow:当前页的结束行号3
分页查询结果中的常用数据:
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
list:查询的结果相关的信息
4.2 通用Mapper插件
通用Mapper插件使用步骤
4.2.1 在pom.xml中添加依赖
<!-- 设置通用mapper-->
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>mapper</artifactId>
<version>3.0.1</version>
</dependency>
4.2.2 在核心配置文件中添加插件
<plugin interceptor="com.github.abel533.mapperhelper.MapperInterceptor">
<!--主键自增回写方法,默认值MYSQL -->
<!--<property name="IDENTITY" value="MYSQL" />-->
<!--通用Mapper默认接口,我们定义的Mapper接口需要实现该接口,本例中的DeptMapper就实现了该接口 -->
<property name="mappers" value="com.github.abel533.mapper.Mapper" />
</plugin>
4.2.3 编写接口继承Mapper
//使用通用Mapper插件,这里必须继承Mapper类,里面的泛型是对应的实体类
public interface DeptMapper extends Mapper<Dept> {
}
4.2.4 配置实体类与数据库表格之间的关系
@Data
//实体类这里需要写明数据库中对应的表名,如果类名除首字母大写,其他都与数据库表名一致时,这里的@Table可以省略
@Table(name = "dept")
public class Dept implements Serializable {
// @id代表该表的主键,设置之后才能通过主键进行查询
@Id
private Integer deptno;
// 当实体类属性与数据库中的字段名不一致时进行设置别名,name值为数据库字段名
@Column(name = "dname")
private String dname1;
private String loc;
}
说明:
1)表名默认使用类名,驼峰转下划线(只对大写字母进行处理),如UserInfo
默认对应的表名为user_info
。
2)表名可以使用@Table(name = "tableName")
进行指定,对不符合第一条默认规则的可以通过这种方式指定表名.
3)字段默认和@Column
一样,都会作为表字段,表字段默认为Java对象的Field名字驼峰转下划线形式.
4)可以使用@Column(name = "fieldName")
指定不符合第3条规则的字段名
5)使用@Transient
注解可以忽略字段,添加该注解的字段不会作为表字段使用.
6)建议一定是有一个@Id
注解作为主键的字段,可以有多个@Id
注解的字段作为联合主键.
7)如果是MySQL的自增字段,加上@GeneratedValue(generator = "JDBC")
即可。
8)实体里面不建议使用基本数据类型如int类型默认是为0而且无法消除
继承通用的Mapper,必须指定泛型一旦继承了Mapper,继承的Mapper就拥有了Mapper所有的通用方法:
4.2.5 测试:
SqlSession sqlSession;
DeptMapper mapper;
@Before
public void before(){
sqlSession = SqlSessionFactoryUtil.getSqlSession();
mapper = sqlSession.getMapper(DeptMapper.class);
}
@After
public void after(){
sqlSession.commit();
sqlSession.close();
}
// 无条件全部查询
@Test
public void testMapper(){
List<Dept> select = mapper.select(null);
System.out.println("select = " + select);
}
// 根据实体中的属性值进行查询,查询条件使用等号
// 即使查询到多条数据也不会报错
@Test
public void testSelect(){
Dept dept = new Dept();
dept.setDname1("学术部");
List<Dept> select = mapper.select(dept);
System.out.println("select = " + select);
}
// 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
// 这里的地址属性是郑州,数据库中有多个值,所以这里会报错,如果采用名字属性来查询,这里不会报错
@Test
public void testSelectOne(){
Dept dept = new Dept();
dept.setDname1("学术部");
// dept.setLoc("郑州");
Dept dept1 = mapper.selectOne(dept);
System.out.println("dept1 = " + dept1);
}
// 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
// 这里一定要对实体类进行设置主键
@Test
public void testPrimaryKey(){
Dept depts = mapper.selectByPrimaryKey(3);
System.out.println("dept = " + depts);
}
// 根据实体中的属性查询总数,查询条件使用等号
// 数据库中有三条数据loc值为郑州,所以这里的查询结果为3
@Test
public void testSelectCount(){
Dept dept = new Dept();
dept.setLoc("郑州");
int i = mapper.selectCount(dept);
System.out.println("i = " + i);
}
//保存一个实体,null的属性也会保存,不会使用数据库默认值,这里的loc属性默认是郑州,不设置loc属性时不会自动填值
@Test
public void testInsert(){
Dept dept = new Dept();
dept.setDname1("通用Mapper的测试");
// dept.setLoc("金水区");
int insert = mapper.insert(dept);
System.out.println("insert = " + insert);
}
//保存一个实体,null的属性不会保存,会使用数据库默认值,这里的loc属性默认是郑州,不设置loc属性时会自动填值:郑州
@Test
public void testInsertSelective(){
Dept dept = new Dept();
dept.setDname1("通用Mapper的测试");
int insert = mapper.insertSelective(dept);
System.out.println("insert = " + insert);
}
// 根据主键更新实体全部字段,null值会被更新
@Test
public void testUpdate(){
Dept dept = new Dept();
dept.setDeptno(25);
dept.setDname1("通用Mapper的修改测试");
int insert = mapper.updateByPrimaryKey(dept);
System.out.println("insert = " + insert);
}
// 根据主键更新属性不为null的值
@Test
public void testUpdateSelective(){
Dept dept = new Dept();
dept.setDeptno(25);
dept.setDname1("通用Mapper的二次修改测试");
int insert = mapper.updateByPrimaryKeySelective(dept);
System.out.println("insert = " + insert);
}
// 根据实体属性作为条件进行删除,查询条件使用等号
@Test
public void testDelete(){
Dept dept = new Dept();
dept.setDeptno(24);
int delete = mapper.delete(dept);
System.out.println("delete = " + delete);
}
// 根据主键字段进行删除,方法参数必须包含完整的主键属性
@Test
public void testDeleteSelective(){
int i = mapper.deleteByPrimaryKey(25);
System.out.println("i = " + i);
}
// 根据Example条件进行查询,重点:这个查询支持通过Example类指定查询列,通过selectProperties方法指定查询列
@Test
public void testSelectByExample(){
// 本例代码拼接出来的SQL语句
//==> Preparing: SELECT DEPTNO,DNAME DNAME1,LOC FROM dept WHERE ( DEPTNO between ? and ? ) or ( DNAME like ? ) order by deptno desc
// 获取要当例子的对象,通过反射获取对应的实体类
Example example = new Example(Dept.class);
// createCriteria:方法构建一个实例:查询deptno在10-22之间的
example.createCriteria().andBetween("deptno", 10, 22);
// 追加条件:或者dname包含李的(模糊查询,这里因为在实体类中设置的属性为dname1)
example.or(example.createCriteria().andLike("dname1", "李"));
// 设置排序条件:按照deptno进行降序排列
example.setOrderByClause("deptno desc");
// 通过例子查询的方法进行查询,获取结果
List<Dept> list = mapper.selectByExample(example);
System.out.println(list);
}
// 根据Example条件进行查询总数
@Test
public void testSelectCountByExample(){
Example example = new Example(Dept.class);
example.createCriteria().andBetween("deptno",2,3);
int i = mapper.selectCountByExample(example);
System.out.println("i = " + i);
}
// 以下是对应的修改和删除方法
// int updateByExample(@Param("record") T record, @Param("example") Object example);
// 说明:根据Example条件更新实体record包含的全部属性,null值会被更新
// 方法:int updateByExampleSelective(@Param("record") T record, @Param("example") Object example);
// 说明:根据Example条件更新实体record包含的不是null的属性值
// 方法:int deleteByExample(Object example);
// 说明:根据Example条件删除数据
4.3 插件开发(拦截器)
官网地址:https://mybatis.org/mybatis-3/zh/configuration.html#plugins
Mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。通过Mybatis拦截器我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。所以Mybatis拦截器的使用范围是非常广泛的。
Mybatis里面的核心对象还是比较多,如下:
Mybatis核心对象 | 解释 |
---|---|
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合 |
ParameterHandler | 负责对用户传递的参数转换成JDBC Statement 所需要的参数 |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条mapper.xml文件里面 select 、update、delete、insert节点的封装 |
SqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
Configuration | MyBatis所有的配置信息都维持在Configuration对象之中 |
Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler四个对象里面的方法。
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
-
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
-
ParameterHandler (getParameterObject, setParameters)
-
ResultSetHandler (handleResultSets, handleOutputParameters)
-
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。
如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
4.3.1 接口
Executor是 Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。
StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。
ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。
ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。
4.3.2 分页插件的开发
实现原理:
我们知道要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,
Mybatis在执行Sql语句前也会产生一个包含Sql语句的Statement对象,
而且对应的Sql语句是在Statement之前产生的,
所以我们就可以在它成Statement之前对用来生成Statement的Sql语句下手。
在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare
方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler
接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,
之后再调用StatementHandler对象的prepare方法,即调用invocation.proceed()。
实现代码:
员工实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer empno;
private String ename;
private String sex;
private Double sal;
private Date hiredate;
}
分页实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Page {
/**
* 当前页码
*/
private Integer pageIndex;
/**
* 每页数据条数
*/
private Integer pageSize;
/**
* 总数据数
*/
private Integer total;
/**
* 总页数
*/
private Integer totalPage;
public Page(Integer pageIndex, Integer pageSize) {
this.pageIndex = pageIndex;
this.pageSize = pageSize;
}
}
Mapper接口:
public interface EmpMapper {
List<Emp> findAll(Page page);
}
对应的Mapper映射文件:
<select id="findAll" resultType="com.lwl.entity.Emp">
select * from emp
</select>
映射文件要在核心配置文件中进行注册!
<mapper resource="mapper/EmpMapper.xml"></mapper>
编写的分页插件:
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
@Intercepts标记了这是一个Interceptor
@Signature 定义拦截点
@type:需要拦截的对象,只可取四大对象之一
@method:拦截的对象方法。
@args:拦截的对象方法参数。
//这里写签名,这里选择拦截的就是StatementHandler
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class}),
})
public class PagePlugins implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取一下当前的代理的对象是什么
Object target = invocation.getTarget();
System.out.println("代理的对象是什么===="+target);
//获取第一个参数
Connection conn = (Connection) invocation.getArgs()[0];
//获取代理的对象的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
//
BoundSql value = (BoundSql) metaObject.getValue("delegate.boundSql");
System.out.println("绑定的sql语句===="+value.getSql());
String oldsql=value.getSql();
//开始组装新的sql语句 如果value中有参数 而且参数是Page的话就执行分页
Object parameterObject = value.getParameterObject();
if(parameterObject instanceof Page){
Page page=(Page)parameterObject;
int pageNum = page.getPageIndex();//当前的页数
int pageSize = page.getPageSize();//每页显示的条数
//开始执行分页,这里需要给每一个派生类起一个别名
String countsql="select count(*) cn from ( "+oldsql+" )e";
PreparedStatement statement = conn.prepareStatement(countsql);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()){
//获取总的条数
int total = resultSet.getInt("cn");
page.setTotal(total);
//一共有多少页
int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
page.setTotalPage(totalPage);
}
String newSql = oldsql + " limit ?,?";
//占位符进行赋值 JDBC
//PreparedStatement ps=Connection.
PreparedStatement ps = conn.prepareStatement(newSql);
//占位符 赋值
ps.setInt(1, (pageNum - 1) * pageSize);
ps.setInt(2, pageSize);
return ps;
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
编写完的插件要在核心配置文件中进行注册:
<!-- 自写的分页插件-->
<plugin interceptor="com.lwl.mybatisPlugins.PagePlugins"></plugin>
测试类:
SqlSession sqlSession;
EmpMapper mapper;
@Before
public void before(){
sqlSession = SqlSessionFactoryUtil.getSqlSession();
mapper = sqlSession.getMapper(EmpMapper.class);
}
@After
public void after(){
sqlSession.commit();
sqlSession.close();
}
@Test
public void testPlugin(){
Page page = new Page(1,2);
List<Emp> all = mapper.findAll(page);
System.out.println("all = " + all);
}