这 些场景包括数据库的BLOB字段的读写、批量更新、调度存储过程、分页、使用参数作为 列名、分表等内容。这些场景在大量的编码中使用,具备较强的实用价值,这些内容都是 笔者通过实战得来的,供读者们参考。

一、数据库BLOB字段读写

字段进行支持的,所以我们先看看

章配置里面,我们谈到了 typeHandler,实际上MyBatis在其默认的类型处理器 中为我们提供了 BlobTypeHandler 和 BlobByteObjectArrayTypeHandler0 其中最常用的是 BlobTypeHandler,而 BlobByteObjectArrayTypeHandler 是用于数据库兼容性的,并不常用。 为了方便举例讲解,我们要先建一个数据库表,如代码清单9.1所示。

java 读取mybatis数据源 mybatis读取blob_分页

 

 然后,建一个POJO与之对应,如代码清单9.2所示。

java 读取mybatis数据源 mybatis读取blob_SQL_02

 

   最后,给出一个映射器的XML文件,如代码清单9.3所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_03

 

   我们将其注册在配置文件里面,这样便可以测试代码了,如代码清单9.4所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_04

 

 

java 读取mybatis数据源 mybatis读取blob_分页_05

 

 

  完成上面的操作就能够正确读取BLOB字段了。

  但是更多的时候我们都应该有一个文件服务器,数据库读取文件路径即可,而不把文 件写入数据库。因为一旦文件很大,那么这个方法就很容易引起内存溢出。所以这样读写

二、批量更新

中,我们可以修改配置文件中

 

java 读取mybatis数据源 mybatis读取blob_分页_06

 

 

   当然我们也可以用Java代码来实现批量执行器的使用,如代码清单9.6所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_07

 

 

如代码清 单9-7所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_08

 

 

   批量执行需要注意的问题是,一旦使用了批量执行器,那么在默认的情况下,它在commit后才发送SQL到数据库,此时我们需要注意代码清单9-8所示的问题。

java 读取mybatis数据源 mybatis读取blob_存储过程_09

 

 

   我们运行上面的代码后,出现了下面这行代码:

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_10

 

 

   抛出空异常,然后事务回滚。

  从代码上看,我们先插入了 role到数据库,它运行了代码role2依旧为空,所以打印其 名称的时候将抛出异常,为什么会这样呢?由于我们釆用了批量的执行器,则更新数据SQL 的执行操作是要到session.commit()中才会被MyBatis发送到数据库执行的,所以在我们执 行下面的操作之前,insert在数据库中根本没有被执行,于是便出现了这句话获取一个空对 象的情况。这是我们需要注意的。

 

方法调用前并不想提交事务,因为后面可能还有其他更新的数据库语 句要执行,这个时候我们只要执行SqlSession的flushstatements方法便可以了,它的含义 是将当前缓存的SQL发送给数据库执行。于是我们按照代码清单9-9的方法修改代码。

java 读取mybatis数据源 mybatis读取blob_存储过程_11

 

 

查不出来的情况,代码就能运行 成功了,我们在使用批量更新的时候要特别注意这个问题。

三、调用存储过程

1、MyBatis对存储过程提供了调用功能,并且支持对游标数据的转化功能,让我们在这 里学习它们。

(1)存储过程in和out参数的使用

是如何实现对存储 过程支持的。这里笔者采用了 Oracle数据库,我们先来新建一个存储过程,如代码清单

java 读取mybatis数据源 mybatis读取blob_分页_12

 

 

参数, 两个out参数。in参数是一个输入的参数,而out参数则是一个输出的参数。首先我们把模 糊查询的结果保存到countjotal这个out参数中,并且将当前日期保存在out_date这个参 数中,然后结束过程。

  我们首先定义一个POJO来反映这个存储过程的参数,如代码清单9-11所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_13

 

 

映射器 中配置它们,如代码清单9.12所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_14

我们将用存储过程的方式去执行它。 如果不声明它,程序将会抛出异常。参数定义,mode=IN的时候为输入参数,mode=OUT 的时候为输出参数,jdbcType定义为数据库的类型。当我们这样写的时候,MyBatis会帮我 们回填result和execDate。当然也可以使用Map,但是我们不推荐那么做,因为Map将失 去业务可读性。为了测试需要请声明一下Mapper接口,如代码清单9-13所示。

 

java 读取mybatis数据源 mybatis读取blob_存储过程_15

 

 

   这样我们便能够测试一下这个接口,让我们看看测试代码,如代码清单9.14所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_16

 

 

参数到过程中,再通过接口调度过程,最后打印一下 返回的其他属性。让我们看看测试结果。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_17

 

 

我们通过日志可以发现过程已经被我们调用了,而结果也正确打印出来了。这样我们 就可以轻松使用存储过程来获取我们想要的数据了。

(2)存储过程游标

节看到了 in和out参数的使用过程,还是比较简单的,但是在存储过程中 往往还需要返回游标。MyBatis对存储过程的游标提供了一个JdbcType=CURSOR的支持, 它可以智能地把游标读到的数据通过配置的映射关系映射到某个类型的POJO上,方便了 我们的使用,让我们看看它的用法。

查询角色,但是往往查询需要考虑分页的效果, 所以新加了 p_start和p_end参数来确定从数据库的第几行到第几行,从而确定分页。而分 页还需要一个总数,我们用存储过程的。ut参数r_count记录,而查询到的具体角色用游标

java 读取mybatis数据源 mybatis读取blob_存储过程_18

 

 

 

java 读取mybatis数据源 mybatis读取blob_存储过程_19

 

 

   这里统计了满足条件的总数,并用游标打开返回满足条件的模糊查询记录。这个游标中每行的数据需要一个POJO进行保存,我们先定义游标返回的POJO,如代码清单9-16所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_20

 

 

 

java 读取mybatis数据源 mybatis读取blob_分页_21

 

 

和游标的返回值是——对应的。但是返回的不单单是游标,还有另外一个 总数和其他参数。那么让我们在游标的POJO的基础上再定义一个POJO,如代码清单9-17 所示。

 

java 读取mybatis数据源 mybatis读取blob_存储过程_22

 

 

 

java 读取mybatis数据源 mybatis读取blob_分页_23

 

 

代表总数。然后定义游标返回的映 射规则,如代码清单9-18所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_24

 

 

的映射,这样我们就在过程 游标输出参数里面定义了。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_25

 

 

   此时MyBatis就知道游标的数据集可以依赖于roleMap定义的规则去转化为roleList列表对象。其他的参数规则与9.3.1节讲到的in和out参数规则相同。

  最后,我们定义接口,如代码清单9.19所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_26

 

 

   现在测试这段代码,如代码清单9.20所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_27

 

 

   运行一下代码,我们便可以得到想要的结果。

java 读取mybatis数据源 mybatis读取blob_SQL_28

 

 

 

java 读取mybatis数据源 mybatis读取blob_SQL_29

 

 

   一个游标便被映射成了我们想要的POJO对象返回给调用者了。

四、分表

  在大型互联网中应用表的数据会很多,为了减少单表的压力,提高性能,我们往往会 考虑分表的算法。

可能有上亿条,对于这种情况我们往 往需要进行分表处理。账单表有许多数据,我们可以把2015年的账单保存在表(t_bill_2015) 中,2016年的账单保存在表(t_bill_2016)中,未来我们还需要建2020年的表(t_bill_2020)o MyBatis允许我们把表名作为参数传递到SQL中,这样就能迅速解决这些问题了。

以查找账单,那么我们可以知 道两个参数,即年份和id。其中年份对账单表的名称产生影响,id则是我们查询的参数。

  让我们先定义接口参数,如代码清单9.21所示。

java 读取mybatis数据源 mybatis读取blob_分页_30

 

 

 很普通的定义,然后我们看看映射器XML的定义代码,如代码清单9.22所示。

java 读取mybatis数据源 mybatis读取blob_分页_31

 

 

中。换句话说,我们可以使用这样的一个规则: 让SQL的任何部分都可以被参数改写,包括列名,以此来满足不同的需求。但是这样是危 险的,比如把year参数修改为1900,那么这条语句的SQL就变为了查询t_bill_1900,而这个表根本就不存在,这会导致发生错误。如果不是很有必要,笔者不推荐使用。对于参 数笔者还是建议使用“#{}”的形式。让我们测试代码清单9-23。

java 读取mybatis数据源 mybatis读取blob_分页_32

 

 

   运行一下,得到下面的结果。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_33

 

 

   从日志我们可以看出表名参数传递完全成功。

五、分页

具有分页功能,它里面有一个类——RowBounds,我们可以使用RowBounds 分页。但是使用它分页有一个很严重的问题,那就是它会在一条SQL中查询所有的结果出 来,然后根据从第几条到第几条取出数据返回。如果这条SQL返回很多数据,毫无疑问, 系统就很容易抛出内存溢出的异常。因此我们需要用其他方法去处理它。这里将分别讨论 用RowBounds传递参数的分页方法和使用插件的SQL分页方法。

1、RowBounds 分页

语句中都可以使用它。我们 来掌握一下RowBounds的源码,如代码清单9-24所示。

java 读取mybatis数据源 mybatis读取blob_分页_34

 

 

 

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_35

 

 

代表从第几行开始读取 数据,而limit则是限制返回的记录数。在默认的情况下,offset的默认值为0,而limit 则是Java所允许的最大整数(2147483647)o不过在一些大数据的场合,一次性取出大量 的数据,比方说从一张表中一次性取出上百万条记录,这对内存的消耗是很大的,性能 差不说,这么多的数据还会引起内存溢出的问题,所以在大数据的查询场景下要慎重使 用它。

  我们看一个简单的查询,通过角色名称模糊查询角色信息,如代码清单9.25所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_36

 

 

   接口定义需要修改为下面的形式,如代码清单9.26所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_37

 

 

   这样便可以使用这个参数了,现在让我们测试一下代码清单9-27。

  测试结果如下。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_38

 

 

 

java 读取mybatis数据源 mybatis读取blob_存储过程_39

 

 

显然系统限制了 5条记录,在一些不需要考虑大数据量的场景下我们可以使用它,比 较方便和简易。

査询 出所有结果的基础上截取数据的,所以在大数据量返回的SQL中并不适用。RowBounds 分页更适合在一些返回数据结果较少的查询中使用。

2、插件分页

节我们谈到了 RowBounds分页的不足,大数据量下会常常发生内存溢出,为了 避免这个问题,我们需要修改SQL。因此,我们往往需要提供一个插件重写SQL来进行分 页,以避免大数据量的问题。

章的内容,只有在掌握了 SqlSession下 四大对象的运作过程和插件开发的过程,才能写出安全高效的插件。

方法中进行的,因 此我们需要在此方法运行之前去创建计算总数SQL,并且通过它得到查询总条数,然后将 当前要运行的SQL改造为分页的SQL,这样就能保证SQL分页。

  为了方便分页插件的使用,这里先定义一个POJO对象,如代码清单9-28所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_40

 

 

去定义当前的页码,每页的条数,是否启用插件,是否检 测当前页码的有效性,通过这些属性可以控制插件的行为。而total和totalPage则是等待插 件回填的两个数据,通过回填的数据,调用者就可以轻易得到这条SQL运行的总数和总页 数。

只能是四大对象中的一个),方法名称(method)和方法参数(args)o由于我们 拦截的是StatementHandler对象的prepare方法,它的参数是Connnection对象’所以就可 以得到如代码清单9-29所示的分页插件签名。

java 读取mybatis数据源 mybatis读取blob_SQL_41

 

 

接口,它定义了

  • intercept 。
  • plugin 。
  • setProperties 。

属性的一些默认值,有了默认值可以更加方便地使用分页插件。我们 可以通过插件接口所提供的setProperties(Propterties porps)方法进行设置,因此只要在分页 插件中配置这些默认值就可以了。而plugin()方法用于生成代理对象,可以使用MyBatis的 方法Plugin.wrap(),至于其原理请查看第7章的内容。我们很快就可以完成plugin方法和

java 读取mybatis数据源 mybatis读取blob_SQL_42

 

 

 

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_43

 

 

这里使用了

种传递分页参数的方法:

作为参数;

    使用注解@卩3他!11传递PageParams对象;

    使用Map传递参数。

  使用其中任意一种都是支持的,稍后会给出分离分页参数的方法。

进而算岀最大页数,回填之 前定义的POJOo而拿到当前运行的SQL去构建统计总条数的SQL还是比较容易的,但是 这里的难点是给构造的计算总条数SQL设置参数。这是头疼的问题,不过应该注意到它和 查询语句的参数是一致的,因此可以利用MyBatis自身提供的类来设置参数,在第6章讲 述过它是通过ParameterHandler对象完成的,因此需要构建一个新的ParameterHandler对象, 在MyBatis中默认是使用DefaultParameterHandler来实现ParameterHandler的,使用它就可 以给总条数SQL设置参数,所以先看看它的构造方法,如代码清单9.31所示。

java 读取mybatis数据源 mybatis读取blob_存储过程_44

 

 

而 BoundSql则要使用统计总数的SQL。因此,在构建新的ParameterHander之前,需要构建 一个新的BoundSql,它的构造方法如代码清单9-32所示。

java 读取mybatis数据源 mybatis读取blob_SQL_45

 

 

和 parameterobject 都可以在当前执行查询 SQL 的 BoundSql中获得,而我们仅仅需要修改统计的SQL而已。我们来看看intercept的实现,如 代码清单9・33所示。

java 读取mybatis数据源 mybatis读取blob_分页_46

 

 

 

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_47

 

 

其中加粗的代码是需要后续讨论的方法。首先需要从代理对象中分离出真实对象,通 过MetaObject绑定这个非代理对象来获取各种参数值,这是插件中常常用到的方法。让我 们看看获取真实对象的方法,如代码清单9.34所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_48

 

 

语句我们才进行分页处 理,否则直接通过反射执行原有的prepare方法,所以这里有一个判断的方法,如代码清单

java 读取mybatis数据源 mybatis读取blob_存储过程_49

 

从映射 器的内部组成的参数规则可以知道@Param方式在MyBatis也是一种Map传参。获取分页 参数的方法,如代码清单9.36所示。

java 读取mybatis数据源 mybatis读取blob_分页_50

 

就判断它是不是继承了 PageParams类,如果是就直接返回。一旦得到的这个分页参数为null 或者分页参数指示不启用插件,那么就直接执行原来拦截的方法返回。

 

  得到分页参数后,要获取总数。获取总数是分页插件最难的部分,但是根据之前的分 析我们也有了应对的方法,这个获取总数的方法,如代码清单9.37所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_51

 


 

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_52

 

对它进行改写就可以得到我们统计的 SQL,然后使用Connection预编译。设置参数是难点,因为参数规则总数和当前要执行的 查询是一致的,所以使用MyBatis提供的ParameteHandler进行参数设置即可。在此之前我 们分析过,需要构建一个BoundSql对象,而除了计算总数的SQL,所有的参数都可以从原 来的BoundSql对象中获得。然后进一步利用MyBatis提供的DefaultParameterHandler构建 ParameterHandler对象,并使用setParameters设置参数,釆用JDBC的方式计算出总数并将 其返回,但是这里不能够关闭Connection对象,因为后面的查询还需要用到它。

  得到这个总数后将它回填到分页参数中,这样调用者就可以得到这两个在分页中很重 要的参数,如代码清单9-38所示。

java 读取mybatis数据源 mybatis读取blob_java 读取mybatis数据源_53

 

然后,根据分页参数的设置判断是否启用检测页码正确性的处理,当当前页码大于最大页码的时候抛出异常,提示错误,如代码清单9.39所示。

java 读取mybatis数据源 mybatis读取blob_SQL_54

 

这里根据设置的参数,判断是需要检测当前页码的有效性,当无效的时候抛出异常, 这样MyBatis就会停止以后的工作,如果正常就继续。最后,我们修改当前SQL为分页的

java 读取mybatis数据源 mybatis读取blob_分页_55

 

 

java 读取mybatis数据源 mybatis读取blob_存储过程_56

 

然后,回填到对 象中,但是改写后我们多加了两个分页参数,因此调度原有的方法(invocation.proceedQ) 后还差这两个参数没有设置。所以我们在后面再设置它,这样就可以调用原来的prepare方 法对SQL进行预编译,完成了使用插件的任务,以后我们的查询都可以得到分页。

的四大对象十分了解,才能编写出想要的插件, 所以第6章和第7章是这个分页插件的学习基础。注意,在MyBatis中使用插件要慎重, 因为插件将覆盖原有对象的方法,所以必须慎用插件,能够不用尽量不要用它。