忙里偷闲,继续上周的话题,记录Mybatis的扩展。

  扩展5:设置默认的返回结果类型

  大家知道,在Mybatis的sql-mapper配置文件中,我们需要给<select>元素添加resultType或resultMap属性,这两个属性有且只能有一个。2013年我在做一个系统的时候,因为业务关系,查询出的结果集字段经常变化,为了简化处理,采用map作为返回数据的载体,然后不得不在绝大多数<select>元素上添加类似 resultType='java.util.HashMap'(Mybatis有HashMap的简写形式,这里为了更清晰,使用全限定符),于是催生了一个想法,能不能设置默认的返回结果类型?后面经过调试,继承SqlSessionFactoryBean添加如下代码实现:

 /**
* 设置默认的查询结果返回类型
* @param configuration
* @param cls
* @throws Exception
*/
private void setDefaultResultType(Configuration configuration, Class<?> cls) throws Exception{
try {
Field resultMaps = MappedStatement.class.getDeclaredField("resultMaps");
resultMaps.setAccessible(true);
for(Iterator<MappedStatement> i = configuration.getMappedStatements().iterator(); i.hasNext();){
Object o = i.next();
/**
* 这里添加类型判断,是因为Mybatis实现中还存放了Ambiguity对象(sql-id的最后一段id重复情况下)
*/
if(o instanceof MappedStatement){
MappedStatement ms = (MappedStatement)o;
if(SqlCommandType.SELECT.equals(ms.getSqlCommandType()) && ms.getResultMaps().isEmpty()){
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration,ms.getId()+"-Inline",cls,new ArrayList<ResultMapping>(),null);
ResultMap resultMap = inlineResultMapBuilder.build();
List<ResultMap> rm = new ArrayList<ResultMap>();
rm.add(resultMap);
resultMaps.set(ms, Collections.unmodifiableList(rm));
}else{
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}

这个实现有很多写死的代码,也没有做足够完备的测试,不过到目前为止,总算还没有出错。

  我设置的默认返回结果类型是map,当然,这里其实可以更进一步扩展,添加一个SqlID模式和默认结果类型的映射接口,然后就根据需要去实现这个映射关系了。

  扩展6:自动扫描类型简称

  还是在SqlSessionFactoryBean继承类中,另外实现的一个扩展就是自动扫描类型简称。类型简称的用法如下:

(1)在mybatis全局配置文件中添加别名

 <typeAliases>
<typeAlias alias="RoleBean" type="com.forms.beneform4j.webapp.systemmanage.role.bean.RoleBean" />
</typeAliases>

(2)在sql-mapper文件中使用alias。

  通过添加自动扫描类型简称,就可以将第一段配置去掉,而直接使用别名了。具体实现大概如下所示:

 private void scanTypeAliases(){
if (this.autoScanTypeAliases && hasLength(this.typeAliasesScanPackage) && null != baseClass) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesScanPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
List<Class<?>> list = new ArrayList<Class<?>>();
List<String> alias = new ArrayList<String>();
MetaObject meta = SystemMetaObject.forObject(this);
Class<?>[] orig = (Class<?>[])meta.getValue("typeAliases");
if(null != orig)
{
for(Class<?> t : orig){
list.add(t);
alias.add(t.getSimpleName().toLowerCase());
}
}
for (String packageToScan : typeAliasPackageArray) {
for(Class<?> type : CoreUtils.scanClassesByParentCls(packageToScan, baseClass)){
String a = type.getSimpleName().toLowerCase();
if (!alias.contains(a)) {
list.add(type);
alias.add(a);
}else{
CommonLogger.warn("Mybatis在自动扫描注册别名时,发现有多个可简写为"+type.getSimpleName()+"的类,将取第一个类,忽略"+type);
}
}
}
super.setTypeAliases(list.toArray(new Class<?>[list.size()]));
}
}

这里属性autoScanTypeAliases表示是否需要自动扫描,typeAliasesScanPackage表示扫描的包,baseClass表示扫描的接口或父类。

  

  我们使用Mybatis,可以在父类中注入SqlSessionTemplate,然后子类调用相关方法,也可以通过写Dao的接口,让mybatis自动生成动态代理类,还可以编写一个静态帮助类,在这个帮助类中注入SqlSessionTemplate,然后提供相应的静态方法。这三种方法,以前用的多的是第三种方法,而现在,因为要让其他同事更容易接受,模块划分更清晰,采用了第二种方法。但这三种方法都有一个特点,那就是只使用Mybatis的SqlSession接口的原生方法。不能直接调用批处理、存储过程等,于是,我在SqlSession基础上,添加了一个IDaoTemplate接口:

 public interface IDaoTemplate{

     /**
* 查询单笔数据
* @param sqlId SQL-ID
* @return 单个对象
*/
public <T> T selectOne(String sqlId); /**
* 查询单笔数据
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 单个对象
*/
public <T> T selectOne(String sqlId, Object parameter); /**
* 查询列表数据
* @param sqlId SQL-ID
* @return 对象列表
*/
public <E> List<E> selectList(String sqlId); /**
* 查询列表数据
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 对象列表
*/
public <E> List<E> selectList(String sqlId, Object parameter); /**
* 查询分页列表数据
* @param sqlId SQL-ID
* @param page 分页对象
* @return 指定页的对象列表
*/
public <E> List<E> selectList(String sqlId, IPage page); /**
* 查询分页列表数据
* @param sqlId SQL-ID
* @param parameter 参数对象
* @param page 分页对象
* @return 指定页的对象列表
*/
public <E> List<E> selectList(String sqlId, Object parameter, IPage page); /**
* 流式查询
* @param sqlId SQL-ID
* @return 流式操作接口
*/
public <E>IListStreamReader<E> selectListStream(String sqlId); /**
* 流式查询
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 流式操作接口
*/
public <E>IListStreamReader<E> selectListStream(String sqlId, Object parameter); /**
* 流式查询
* @param sqlId SQL-ID
* @param fetchSize 每次读取的记录条数(0, 5000]
* @return 流式操作接口
*/
public <E>IListStreamReader<E> selectListStream(String sqlId, int fetchSize); /**
* 流式查询
* @param sqlId SQL-ID
* @param parameter 参数对象
* @param fetchSize 每次读取的记录条数(0, 5000]
* @return 流式操作接口
*/
public <E>IListStreamReader<E> selectListStream(String sqlId, Object parameter, int fetchSize); /**
* 新增
* @param sqlId SQL-ID
* @return 影响的记录条数
*/
public int insert(String sqlId); /**
* 新增
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 影响的记录条数
*/
public int insert(String sqlId, Object parameter); /**
* 修改
* @param sqlId SQL-ID
* @return 影响的记录条数
*/
public int update(String sqlId); /**
* 修改
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 影响的记录条数
*/
public int update(String sqlId, Object parameter); /**
* 删除
* @param sqlId SQL-ID
* @return 影响的记录条数
*/
public int delete(String sqlId); /**
* 删除
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 影响的记录条数
*/
public int delete(String sqlId, Object parameter); /**
* 执行批量:一个SQL执行多次
* @param sqlId SQL-ID
* @param parameters 参数对象数组
* @return 批量执行的影响记录数组
*/
public int[] executeBatch(String sqlId, List<?> parameters); /**
* 执行批量:一次执行多个SQL
* @param sqlIds 要执行的一组SQL-ID
* @return 批量执行的影响记录数组
*/
public int[] executeBatch(List<String> sqlIds); /**
* 执行批量:一次执行多个SQL
* @param sqlIds 要执行的一组SQL-ID
* @param parameters 参数对象数组
* @return 批量执行的影响记录数组
*/
public int[] executeBatch(List<String> sqlIds, List<?> parameters); /**
* 打开批量执行模式
*/
public void openBatchType(); /**
* 恢复打开批量执行模式之前的执行模式
*/
public void resetBatchType(); /**
* 获取批量执行结果
* @return
*/
public int[] flushBatch(); /**
* 调用存储过程
* @param sqlId SQL-ID
* @return 存储过程返回结果接口
*/
public ICallResult call(String sqlId); /**
* 调用存储过程
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 存储过程返回结果接口
*/
public ICallResult call(String sqlId, Object parameter);
}

可以看到,其中部分是简单调用SqlSession接口,但也有部分是我们的扩展。

  扩展7:流式查询

  流式查询有四个重置方法,sql-Id是必须的参数,查询参数parameter和每次处理的记录条数fetchSize是可选的。流式查询的结果接口如下:

 public interface IListStreamReader<T> {

     /**
* 读取当前批次的数据列表,如果没有数据,返回null
* @return 当前批次的数据列表
*/
public List<T> read(); /**
* 重置读取批次
*/
public void reset();
}

只有两个方法,其中关键方法是获取当前批次的数据结果集,辅助方法是重置读取批次。

  流式查询本质上并没有执行查询,而只是将查询需要的要素包装成为一个对象,当调用者调用这个对象的read方法时,才真正执行数据库查询,而执行查询又使用实现内中内置的分页对象,每次读取只读取当前批次(当前页数)的结果集,查询之后,就内置分页对象的当前页数指向下一页。

  流式查询适用于大数据量的查询处理,比如大数据量的数据需要生成Excel文件供客户端下载,一次性查询很容易内存溢出,使用流式查询就可以很好的解决这个问题。

  把流式查询结果对象的抽象实现贴在这里,应该更便于理解:

 public abstract class AbstractListStreamReader<T> implements IListStreamReader<T>{

     /**
* 默认的每次读取记录数
*/
private static final int defaultFetchSize = 1000; /**
* 最大的每次读取记录数
*/
private static final int maxFetchSize = 5000; /**
* 实际的每次读取记录数
*/
private final int fetchSize; /**
* 分页对象
*/
private final IPage page; /**
* 是否完成的标志
*/
private transient boolean finish = false;//是否完成 /**
* 无参构造函数
*/
public AbstractListStreamReader() {
this(defaultFetchSize);
} /**
* 使用指定读取数大小的构造函数
* @param fetchSize 每次读取的记录条数
*/
public AbstractListStreamReader(int fetchSize) {
if(fetchSize <= 0 || fetchSize > maxFetchSize){
Throw.throwRuntimeException(DaoExceptionCodes.BF020012, fetchSize, "(0, "+maxFetchSize+"]");
}
this.fetchSize = fetchSize;
BasePage page = new BasePage();
page.setPageSize(fetchSize);
this.page = page;
} /**
* 读取当前批次的列表数据,读取的时候会加锁
*/
@Override
public synchronized List<T> read() {
if(!finish){
List<T> rs = doRead(page);//查询当前页数据
if(page.hasNextPage()){//有下一页,游标指向下一页
page.setPageProperty(page.getTotalRecords(), page.getCurrentPage()+1, fetchSize);
}else{//没有下一页,完成
finish = true;
}
return rs;
}
return null;
} /**
* 执行实际的读取操作
* @param page 分页对象
* @return 和分页对象相对应的数据记录列表
*/
abstract protected List<T> doRead(IPage page); /**
* 重置读取批次,重置过程中会加锁
*/
@Override
public synchronized void reset(){
this.finish = false;
this.page.setPageProperty(this.page.getTotalPages(), 1, fetchSize);
}
}

至于具体的实现,只要继承抽象实现,然后实现

abstract protected List<T> doRead(IPage page);

就可以了,而这个方法只是一个简单的分页查询,实现起来没有任何难度。

  扩展8:调用存储过程

  Mybatis中可以调用存储过程,但直接使用并不方便,我们将其封装如下:

 public interface IDaoTemplate{

     /**
* 这里省略了其它方法
*/ /**
* 调用存储过程
* @param sqlId SQL-ID
* @return 存储过程返回结果接口
*/
public ICallResult call(String sqlId); /**
* 调用存储过程
* @param sqlId SQL-ID
* @param parameter 参数对象
* @return 存储过程返回结果接口
*/
public ICallResult call(String sqlId, Object parameter);
}

这样调用就非常方便了,那么这里的ICallResult是什么呢?看一下它的定义:

 public interface ICallResult {

     /**
* 获取存储过程返回值
* @return
*/
public <T> T getResult(); /**
* 根据参数名称返回输出参数
* @param name 输出参数名称
* @return 和输出参数名称相对应的返回结果,如果不存在输出参数,抛出平台运行时异常
*/
public <T> T getOutputParam(String name); /**
* 返回输出参数名称的迭代器
* @return 输出参数名迭代器
*/
public Iterator<String> iterator();
}

使用过Mybatis调用存储过程的朋友,看了这个借口应该就能明白,但鉴于存储过程调用并不普通,这里举一个例子:

 <?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.forms.beneform4j.core.dao.mybatis.mapper.call.ICallDao"> <select id="call" statementType="CALLABLE">
{call BF_TEST_PACKAGE.BF_TEST_PROCEDURE(
#{input, jdbcType=VARCHAR},
#{output1, mode=OUT, jdbcType=VARCHAR},
#{output2, mode=OUT, jdbcType=VARCHAR},
#{rs1, mode=OUT, jdbcType=CURSOR},
#{rs2, mode=OUT, jdbcType=CURSOR}
)}
</select>
</mapper>

如上配置,传入sqlId和参数对象(含input属性)后,返回的ICallResult接口中,可以通过如下的方式获取存储过程的返回值(如果有)和输出参数:

 @Repository
interface ICallDao { public ICallResult call(@Param("input")String input);
} @Service
public class ICallDaoTest { @Autowired
private ICallDao dao; @Test
public void call() throws Exception {
ICallResult rs = dao.call("1");
//直接访问返回结果和输出参数
Object returnValue = rs.getResult();
Object output1 = rs.getOutputParam("output1");
List<Object> rs1 = rs.getOutputParam("rs1"); //循环访问输出参数
Iterator<String> i = rs.iterator();
String name = "";
while(i.hasNext()){
name = i.next();
System.out.println(name + "============" + rs.getOutputParam(name));
}
}
}

  说完了调用存储过程的用法,回过头来简单的提一下调用存储过程的实现:实际上很简单,只要添加一个Mybatis的拦截器即可,拦截结果处理接口ResultSetHandler的方法handleOutputParameters,然后将返回结果和输出参数包装到一个Map对象中即可,具体代码就不贴了。

  时间关系,今天写到这里。下次再继续写Dao接口中SqlID的重定向、IDaoTemplate接口中的批量处理相关的扩展。

Java EE开发平台随手记3——Mybatis扩展2的更多相关文章

  1. Java EE开发平台随手记4——Mybatis扩展3

    接着昨天的Mybatis扩展——IDaoTemplate接口. 扩展9:批量执行 1.明确什么是批量执行 首先说明一下,这里的批量执行不是利用<foreach>标签生成一长串的sql字符串 ...

  2. Java EE开发平台随手记2——Mybatis扩展1

    今天来记录一下对Mybatis的扩展,版本是3.3.0,是和Spring集成使用,mybatis-spring集成包的版本是1.2.3,如果使用maven,如下配置: <properties&g ...

  3. Java EE开发平台随手记6——Mybatis扩展4

    这篇博客中来说一下对Mybatis动态代理接口方式的扩展,对于Mybatis动态代理接口不熟悉的朋友,可以参考前一篇博客,或者研读Mybatis源码. 扩展11:动态代理接口扩展 我们知道,真正在My ...

  4. Java EE开发平台随手记5——Mybatis动态代理接口方式的原生用法

    为了说明后续的Mybatis扩展,插播一篇广告,先来简要说明一下Mybatis的一种原生用法,不过先声明:下面说的只是Mybatis的其中一种用法,如需要更深入了解Mybatis,请参考官方文档,或者 ...

  5. Java EE开发平台随手记1

    过完春节以来,一直在负责搭建公司的新Java EE开发平台,所谓新平台,其实并不是什么新技术,不过是将目前业界较为流行的框架整合在一起,做一些简单的封装和扩展,让开发人员更加易用. 和之前负责具体的项 ...

  6. Java EE开发课外事务管理平台

    Java EE开发课外事务管理平台 演示地址:https://ganquanzhong.top/edu 说明文档 一.系统需求 目前课外兴趣培训学校众多,完善,但是针对课外兴趣培训学校教务和人事管理信 ...

  7. Java EE开发环境——MyEclipse2017破解 和 Tomcat服务器配置

    Java EE开发,我们可以搭建如下开发环境: 底层运行环境:jdk 和 jre. Web服务器:Tomcat 后台数据库:SQL Server 可视化集成开发环境:MyEclipse Java EE ...

  8. JEECG 3.7.1 版本发布,企业级JAVA快速开发平台

    JEECG 3.7.1 版本发布,企业级JAVA快速开发平台 ---------------------------------------- Version:  Jeecg_3.7.1项 目:   ...

  9. JEECG 4.0 版本发布,JAVA快速开发平台

    JEECG 4.0 版本发布,系统全面优化升级,更快,更稳定!         导读                               ⊙平台性能优化,系统更稳定,速度闪电般提升      ...

随机推荐

  1. Java程序,猜大小游戏

    一个骰子,通常有1.2.3.4.5.6等6种点数.我们将1.2.3记作“小”,将4.5.6记作“大”.猜中显示“猜对了”,猜错记作“猜错了”之类的字样.本程序可以用Java实现. import jav ...

  2. 为什么匿名内部类只能访问final变量【转】

    是变量的作用域的问题,因为匿名内部类是出现在一个方法的内部的,如果它要访问这个方法的参数或者方法中定义的变量,则这些参数和变量必须被修饰为final.因为虽然匿名内部类在方法的内部,但实际编译的时候, ...

  3. tail queue代码阅读

    tail queue是bdb中用的最多的数据结构. 定义在 src/dbinc/queue.h: 注: TRACEBUF,QMD_TRACE_HEAD等是为了 queue代码的debug, 这里移除出 ...

  4. 关于原生JS获取类相关的代码

    <script> var FungetElementsByClassName = function(str,root,tag){ if(root){ root = typeof root ...

  5. Java获取Web服务器文件

    Java获取Web服务器文件 如果获取的是服务器上某个目录下的有关文件,就相对比较容易,可以设定死绝对目录,但是如果不能设定死绝对目录,也不确定web服务器的安装目录,可以考虑如下两种方式: 方法一: ...

  6. idea中maven报错:无效的目标发行版: 1.8

    1.project.pom中修改版本 <maven.compiler.source>1.7</maven.compiler.source><maven.compiler. ...

  7. VS2008 工程只生成dll不生成lib的解决方案

    http://topic.csdn.net/u/20081216/22/b12d1450-7585-4c9f-867a-7c181737c328.html 问题:vs2008版本的,不知道为什么只生成 ...

  8. andriod studio

    初衷:使用andriod的webview调用html页面,生成app. AVD注意细节: RAM : 1G VM heap:228MB Graphics:software - GLES 2.0 存在的 ...

  9. Bookstore project using XAMPP 详细配置 Part 1

    这是学校的一个project,记录在这里,以备复习.主要是用XAMPP通过phpMyAdmin连接MySQL数据库,实现一个简单的查询功能. Outline Setup of XAMPP Implem ...

  10. 原来在linux上切换jdk的版本是这么简单

    上次在linux上切换jdk版本的时候,还配置了半天的环境变量,今天又查了一下,原来是这么的简单 1. 查看相应的jdk是否在 ubuntu的jdk菜单里,查看: (输全哦) update-alter ...