博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
mybatis 使用经验小结 good
阅读量:5954 次
发布时间:2019-06-19

本文共 15583 字,大约阅读时间需要 51 分钟。

 

一、多数据源问题

主要思路是把dataSource、sqlSesstionFactory(用来产生sqlSession)、MapperScannerConfigurer在配置中区分开,各Mapper对应的包名、类名区分开

Tips:
MapperScannerConfigurer的作用:
通过AOP来完成Mapper接口文件,Mapper xml文件以及SqlSessionFactory之间的关联关系。这样SqlSessionTemplate就可以不用配置了

 

上面的配置,一个连h2的a数据库,一个连h2的b数据库,至于事务管理器,大家可参考这个思路,建二个,各管各的。

项目中mapper接口及映射文件均用包名区分开,如下图:

resultMap用来引用Mapper.xml文件中定义的resultMap(数据库字段和DO字段之间的映射关系) 

resultType用来引用java代码中定义的数据结构,多为DO或BO

paramerterType定义的是入参的数据结构,@Param修饰的参数会被包装成一个map。如果不指定parameterType参数,默认就是指Map 

<![CDATA[ and created_time>#{beginTime}]]> 

include可以引用其它xml。如果是在ResultMap定义文件中可以使用extends来引用另外一个Mapper.xml

choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。

当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。
类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。
例如下面例子,同样把所有可以限制的条件都写上,方面使用。choose会从上到下选择一个when标签的test为true的sql执行。安全考虑,我们使用where将choose包起来,放置关键字多于错误。

http://www.cnblogs.com/caoyc/p/5574948.html

INSERT INTO tb_demo (id,name,update_time) VALUES (#{id},#{name},now())ON DUPLICATE KEYupdate  update_time = now()

 

二、如何使用Map做为参数及动态条件生成

...
D_RECID, D_USER_NAME, D_NAME, D_TYPE, ...

 

14-31演示了如何使用Map做为参数,动态传入查询条件,及List参数生成in(...)条件

java端代码示例:

PrintLayoutMapper mapper = context.getBean(PrintLayoutMapper.class);Map
map = new HashMap
();map.put("userName", "ADMIN");map.put("awbType", "CARGOLABEL_MU");map.put("recId", 1);List
ids = new ArrayList
();ids.add(0, 1);ids.add(0, 2);ids.add(0, 3);map.put("ids", ids);List
list = mapper.select(map);

 

其实PrintLayoutMapper接口的定义为:

public interface PrintLayoutMapper {        ...        List
select(Map
map);}

 

最终生成的SQL语句为:

select D_RECID, D_USER_NAME, D_NAME, D_TYPE, ... from T_PRINT_LAYOUT where D_USER_NAME = ? and D_TYPE = ? and D_RECID = ? or D_RECID in ( ? , ? , ? )

三、兼容不同的数据库

select seq_users.nextval from dual
select nextval for seq_users from sysibm.sysdummy1"
insert into users values (#{id}, #{name})

这是官方文档上的示例,演示了如何兼容oracle与db2这二种不同的数据库,来获取序列的下一个值

mybatis的一个内置参数_parameter    

1、内置的_parameter变量,对应转换后的传递参数,在只传递单参数且是没有使用@Param注解对参数命名的时候如果我们需要通过${var}的形式来访问传递的单参数,则可以使用${_parameter};

2、如果对应的Mapper接口方法是多参数或者拥有@Param命名的参数时可以使用param1、paramN的形式;
3、如果对应的Mapper接口方法参数是@Param命名的方法参数,则可以使用@Param指定的名称;
4、如果对应的Mapper接口方法拥有多个参数,且拥有没有使用@Param命名的参数,那没有使用@Param命名的参数可以通过0、1、N形式访问。
根据上述规则如我们有一个findById的方法其接收一个Long类型的参数作为ID,当使用${var}的形式引用变量时则可以写成如下这样:

当我们的Mapper接口方法参数使用了@Param命名的时候,我们还可以使用@Param指定的参数名。

public interface UserMapper {   User findById(@Param("id") Long id);}

注意,但是使用了@Param对单参数命名后我们就不能再在Mapper语句中通过${_parameter}来引用接口方法参数传递过来的单参数了,因为此时其已经被包装为一个Map了,

如果要通过_parameter来引用的话,我们应该使用${_parameter.param1}或${_parameter.varName},对于上面的示例来说就是${_parameter.param1}或${_parameter.id}。
下面我们来看一个传递多参数的,假设我们有如下这样的一个Mapper语句及对应的Mapper接口方法,这个Mapper接口方法接收两个参数,第一个参数是用@Param注解指定了参数名为name,第二个参数是没有使用注解的,
具体如下:

List
findByNameAndMobile(@Param("name") String name, String mobile);

那我们的Mapper.xml文件中的对应语句需要Mapper接口方法参数时有哪几种方式呢?

按照之前的规则,对于第一个方法参数name而言,因为使用了@Param指定了参数名name,所以我们可以在Mapper.xml文件中通过变量name和param1来引用它,
而第二个参数mobile是没有使用@Param指定参数名的,则我们可以使用param2和参数相对位置“1”来引用它。
如上面的示例中,我们就是通过第二个参数的相对位置“1”来引用的。
如果我们把第二个参数改为${mobile}是引用不到,且系统会报如下错误,有兴趣的朋友可以试一下。
org.apache.ibatis.binding.BindingException: Parameter 'mobile' not found. Available parameters are [1, name, param1, param2]
一般情况下为了防止SQL注入问题,是不建议直接在where条件中使用${var}的形式访问变量的,通常会用预编译形式的#{var}。
而${var}往往是用来传递一些非where条件的内容,比如排序信息,具体用法请根据实际情况决定

http://elim.iteye.com/blog/2357967

Caused by: org.apache.ibatis.binding.BindingException: Parameter 'userId' not found. Available parameters are [arg1, arg0, param1, param2]	at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:202) ~[mybatis-3.4.4.jar:3.4.4]	at org.apache.ibatis.reflection.wrapper.MapWrapper.get(MapWrapper.java:45) ~[mybatis-3.4.4.jar:3.4.4]	at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122) ~[mybatis-3.4.4.jar:3.4.4]	at org.apache.ibatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:219) ~[mybatis-3.4.4.jar:3.4.4]	at org.apache.ibatis.executor.CachingExecutor.createCacheKey(CachingExecutor.java:146) ~[mybatis-3.4.4.jar:3.4.4]	at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:84) ~[pagehelper-5.0.4.jar:na]	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.4.4.jar:3.4.4]	at com.sun.proxy.$Proxy201.query(Unknown Source) ~[na:na]	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148) ~[mybatis-3.4.4.jar:3.4.4]	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141) ~[mybatis-3.4.4.jar:3.4.4]	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77) ~[mybatis-3.4.4.jar:3.4.4]	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433) ~[mybatis-spring-1.3.1.jar:1.3.1]	... 126 common frames omitted

解决办法:

Caused by: org.apache.ibatis.binding.BindingException: Parameter 'loginId' not found. Available parameters are [1, 0, param1, param2]Mapper.java文件中的方法要这样写:void userInfoLogin(@Param("id")String id, @Param("loginId")String loginId);或者不改传参,改sql:sql中的参数用 #{0},#{1}代替即可

报错原因:

Mapper.java文件中的入参String 类型的userId必须添加使用@Param注解。非String类型的,如果只有一个参数,可以不加@Param注解

MyBatis 参数类型为String时常见问题及解决方法

1. 参数为String时的插值问题
假设有下面一Dao接口方法

public Account findByAccountType (String type)throws DaoException;

对应的Mapper.xml

一般我们都是按这样的方式来写的,对于其他类型是没错的,但是如果为String的话会抛下面的异常:

There is no getter for property named 'type ' in 'class java.lang.String'
因为MyBatis要求如果参数为String的话,不管接口方法的形参是什么,在Mapper.xml中引用时需要改变为_parameter才能识别 :

2. 对字符串参数进行是否相等 比较时的问题

错误:

type=#{_parameter}

正确:

type=#{_parameter}
type=#{_parameter}

注:上述问题不仅限于<if>标签,其他动态sql标签在对String进行处理时也会出现同样的问题

http://www.ctolib.com/topics-113837.html
http://blog.csdn.net/whatsyournamejack/article/details/52345989

四、加强版的分支、选择判断

这也是官方文档上的示例,因为<if>...</if>并没对应的<else>标签,所以要达到<if>...<else>...</else> </if>的效果,得借助<choose>、<when>、<otherwise>组合使用。

and d.id = #{demoInfo.id}

五、避免Where 空条件的尴尬

如果state参数为空时,最终生成SQL语句为

SELECT * FROM BLOG WHERE

执行会出错,当然,你可以在where 后加一个1=1,改成

但是这个做法不太“环保”(毕竟引入了一个垃圾条件),其实只要改成<where>...</where>即可

 

六、$与#的区别

select * from T_PRINT_LAYOUT where  D_RECID = ${recId}

最后生成的SQL为:

select * from T_PRINT_LAYOUT where  D_RECID = 1

即:直接将参数值替换到了原来${recId}的位置,相当于硬拼SQL

select * from T_PRINT_LAYOUT where  D_RECID = #{recid,jdbcType=DECIMAL}

最后生成的SQL为:

select * from T_PRINT_LAYOUT where  D_RECID = ?

即:#{...}被识别为一个SQL参数

 

七、大量数据的批量insert

大量数据(条数>10000)做insert时,如果按常规方式,每条insert into table(...) values(...);来提交,速度巨慢。

改善性能的思路是多条insert批量提交。

oracle环境中,有一种批量insert的小技巧,原理是 insert into ... select from ...,套在mybatis上,变形为:

INSERT INTO T_TEST  (ID, COL_A, COL_B)  SELECT SEQ_TEST.NEXTVAL, A.*    FROM (              SELECT 'A1', 'B1' FROM DUAL    UNION ALL SELECT 'A2', 'B2' FROM DUAL    UNION ALL SELECT 'A3', 'B3' FROM DUAL    UNION ALL SELECT 'A4', 'B4' FROM DUAL    UNION ALL SELECT 'A5', 'B5' FROM DUAL    UNION ALL SELECT 'A6', 'B6' FROM DUAL    ) A

中间的部分非常有规律,可以用foreach标签生成,参考下面的片段:

select SEQ_CTAS_SHARK_FLT.nextval as recId from dual
insert into CTAS_SHARK_FLT (
) SELECT SEQ_TEST.NEXTVAL, A.* FROM (
select #{item.awbType,jdbcType=VARCHAR}, #{item.awbPre,jdbcType=VARCHAR},... from dual
) A

即使这样,也不能直接run,oracle中一次执行的sql语句长度是有限制的,如果最后拼出来的sql字符串过长,会导致执行失败,所以java端还要做一个分段处理,参考下面的处理:

List
data = new ArrayList
();for (TSharkFlt f : sharkFlts) { data.add(getSharkFlt(f));}System.out.println(data.size());long beginTime = System.currentTimeMillis();System.out.println("开始插入...");SqlSessionFactory sqlSessionFactory = ctx.getBean(SqlSessionFactory.class);SqlSession session = null;try { session = sqlSessionFactory.openSession(ExecutorType.BATCH, false); int a = 2000;//每次提交2000条 int loop = (int) Math.ceil(data.size() / (double) a); List
tempList = new ArrayList
(a); int start, stop; for (int i = 0; i < loop; i++) { tempList.clear(); start = i * a; stop = Math.min(i * a + a - 1, data.size() - 1); System.out.println("range:" + start + " - " + stop); for (int j = start; j <= stop; j++) { tempList.add(data.get(j)); } session.insert("ctas.importer.writer.mybatis.mappper.SharkFltMapper.insertBatch2", tempList); session.commit(); session.clearCache(); System.out.println("已经插入" + (stop + 1) + " 条"); }} catch (Exception e) { e.printStackTrace(); session.rollback();} finally { if (session != null) { session.close(); }}long endTime = System.currentTimeMillis();System.out.println("插入完成,耗时 " + (endTime - beginTime) + " 毫秒!");

 

13,27-29这几行是关键,这一段逻辑会经常使用,为了重用,可以封装一下:

/** * 批量提交数据 * @param sqlSessionFactory * @param mybatisSQLId SQL语句在Mapper XML文件中的ID * @param commitCountEveryTime 每次提交的记录数 * @param list 要提交的数据列表 * @param logger 日志记录器 */private 
void batchCommit(SqlSessionFactory sqlSessionFactory, String mybatisSQLId, int commitCountEveryTime, List
list, Logger logger) { SqlSession session = null; try { session = sqlSessionFactory.openSession(ExecutorType.BATCH, false); int commitCount = (int) Math.ceil(list.size() / (double) commitCountEveryTime); List
tempList = new ArrayList
(commitCountEveryTime); int start, stop; Long startTime = System.currentTimeMillis(); for (int i = 0; i < commitCount; i++) { tempList.clear(); start = i * commitCountEveryTime; stop = Math.min(i * commitCountEveryTime + commitCountEveryTime - 1, list.size() - 1); for (int j = start; j <= stop; j++) { tempList.add(list.get(j)); } session.insert(mybatisSQLId, tempList); session.commit(); session.clearCache(); } Long endTime = System.currentTimeMillis(); logger.debug("batchCommit耗时:" + (endTime - startTime) + "毫秒"); } catch (Exception e) { logger.error("batchCommit error!", e); e.printStackTrace(); session.rollback(); } finally { if (session != null) { session.close(); } }}

对应的,如果是批量update,也是类似的思路,只不过要注意一点:oracle环境中,多条语句提交的sql语句为

begin  update xxx set xxx =xxx ;  update xxx set xxx =xxx;end;

 

用mytais拼的时候,参考下面的写法:

update xxx set x=#{item.x,jdbcType=VARCHAR} where x =#{item.x,jdbcType=VARCHAR};

 

关于批量提交的性能,Oracle环境下,我大概测试了一下:

insert into ... select xxxunion all select yyyunion all select zzz;

最快,其次是

begininsert into ... values ...;insert into ... values ...;end;

 

当然最慢是逐条insert提交,最后谈下Spring与mybatis集成后,AOP事务管理 对 批量提交的影响 ,通常情况下,我们会这样配置AOP事务管理:

 

这样,ctas.service(及子包)下的所有方法都被拦截,而且只有do开头的方法,具有可写的事务(即:能insert/update/delete记录),而其它方法是只读事务(即:只能select数据),但是我们前面谈到的批量提交操作,都是写代码手动提交的,不需要spring管理,所以配置中需要将某些方法排除,可以约定self开头的方法,由开发者自己管理事务,不需要spring代为管理,上面的配置要改成:

 

通过 and !execution(...) 将self开头的方法排除就可以了,前面的批量操作代码写到selfXXX方法中。

 

关于批量提交,还有一种情况:父子表的批量插入。思路还是一样的,但是SQL的写法有点区别,原理参考下面的语句(Oracle环境)

DECLARE  BASE_ID   INTEGER;  DETAIL_ID INTEGER;BEGIN  --第1组记录  SELECT SEQ_T_BASE.NEXTVAL INTO BASE_ID FROM DUAL;  INSERT INTO T_BASE (ID, FEE) VALUES (BASE_ID, ?);    SELECT SEQ_T_DETAIL.NEXTVAL INTO DETAIL_ID FROM DUAL;  INSERT INTO T_DETAIL (ID, BASE_ID, FEE) VALUES (DETAIL_ID, BASE_ID, ?);  SELECT SEQ_T_DETAIL.NEXTVAL INTO DETAIL_ID FROM DUAL;  INSERT INTO T_DETAIL (ID, BASE_ID, FEE) VALUES (DETAIL_ID, BASE_ID, ?);    --第2组记录  SELECT SEQ_T_BASE.NEXTVAL INTO BASE_ID FROM DUAL;  INSERT INTO T_BASE (ID, FEE) VALUES (BASE_ID, ?);    SELECT SEQ_T_DETAIL.NEXTVAL INTO DETAIL_ID FROM DUAL;  INSERT INTO T_DETAIL (ID, BASE_ID, FEE) VALUES (DETAIL_ID, BASE_ID, ?);  SELECT SEQ_T_DETAIL.NEXTVAL INTO DETAIL_ID FROM DUAL;  INSERT INTO T_DETAIL (ID, BASE_ID, FEE) VALUES (DETAIL_ID, BASE_ID, ?);    --...END;

 

xml映射文件中的写法:

DECLARE base_id INTEGER ; detail_id INTEGER ;
select seq_t_base.nextval into base_id from dual; insert into t_base(id, fee) values(base_id, #{item.baseEntity.fee,jdbcType=DECIMAL});
select seq_t_detail.nextval into detail_id from dual; insert into t_detail(id, base_id, fee) values(detail_id,base_id,#{detail.fee,jdbcType=DECIMAL});

 

List中的Dto定义

public class BaseDetailDto {    private TBase baseEntity;    private List
details; public TBase getBaseEntity() { return baseEntity; } public void setBaseEntity(TBase baseEntity) { this.baseEntity = baseEntity; } public List
getDetails() { return details; } public void setDetails(List
details) { this.details = details; }}

 

你可能感兴趣的文章
度量时间差
查看>>
通过jsp请求Servlet来操作HBASE
查看>>
Shell编程基础
查看>>
Shell之Sed常用用法
查看>>
3.1
查看>>
校验表单如何摆脱 if else ?
查看>>
<气场>读书笔记
查看>>
领域驱动设计,构建简单的新闻系统,20分钟够吗?
查看>>
web安全问题分析与防御总结
查看>>
React 组件通信之 React context
查看>>
Centos下基于Hadoop安装Spark(分布式)
查看>>
3D地图的定时高亮和点击事件(基于echarts)
查看>>
mysql开启binlog
查看>>
设置Eclipse编码方式
查看>>
分布式系统唯一ID生成方案汇总【转】
查看>>
并查集hdu1232
查看>>
Mysql 监视工具
查看>>
从前后端分离到GraphQL,携程如何用Node实现?\n
查看>>
Linux Namespace系列(09):利用Namespace创建一个简单可用的容器
查看>>
nginc+memcache
查看>>