# XML

back

# xml中语句判断的问题

返回顶部

# 待判断的常量字符串如果2个以上(含2个)字符时则可使用单引号

当然使用双引号肯定更没问题

<if test="param.searchType=='01'">
</if>
1
2

# 带判断的常量字符串只有1个字符或者空字符串时,不可使用单引号

此时java会将其解析成char类型,将会导致==的判断无法按照预期

此时需要将判断的句子中的单个字符必须是双引号"包裹,外层套单引号'

<if test='@com.fourfaith.base.mybatis.Ognl@isNotEmpty(params.searchType) and params.searchType=="2"'>
</if>

<when test='params.status == "0"'>
    l.${fire}STATUS = #{params.status} or l.${fire}STATUS ='1'
</when>
1
2
3
4
5
6

# XML文件加载顺序

返回顶部

从我标记的三个重点加上小学简单的英文就可以看出,在解析UserPersonalMapper.xml时,没有找到BaseResultMap,
另外一点,从我标记中的重点中也可以看出,这个BaseResultMap是在另外一个XML,也就是UserMapper.xml中声明的. 这个时候可能就有朋友想到,那是不是加载UserPersonalMapper.xml的时候,UserMapper.xml还没加载导致的呢.导致无法找到UserMapper.xml定义的BaseResultMap,
坦白说,这个猜测,一点毛病都没有,非常合情合理. 然后你本地启动,和jar启动,看日志输出.

那么,这两个分支究竟有什么区别呢?这个本地代码走的代码分支,有一段很重要的逻辑.
他这里会根据资源文件进行排序,那么到底根据什么规则排序.

此时豁然开朗.为什么本地一直没有重现?因为本地跑的时候,他走的代码逻辑已经把资源文件默认按照文件名排序了,导致了这个UserMapper.xml一直在UserPersonalMapper.xml之前加载.

其实这种两个mapper引用的做法我认为是非常不规范的.因为资源文件打进jar的文件排序是非常不可控的,

# 一步步放弃mybatis的xml

返回顶部

# foreach用法:List、Array、Map三种类型遍历

# foreach元素的属性

  • collection: 需做foreach(遍历)的对象,作为入参时,list、array对象时,collection属性值分别默认用"list"、"array"代替,Map对象没有默认的属性值。但是,在作为入参时可以使用@Param(“keyName”)注解来设置自定义collection属性值,设置keyName后,list、array会失效;
  • item: 集合元素迭代时的别名称,该参数为必选项;
  • index: 在list、array中,index为元素的序号索引。但是在Map中,index为遍历元素的key值,该参数为可选项;
  • open: 遍历集合时的开始符号,通常与close=")"搭配使用。使用场景IN(),values()时,该参数为可选项;
  • separator: 元素之间的分隔符,类比在IN()的时候,separator=",",最终所有遍历的元素将会以设定的(,)逗号符号隔开,该参数为可选项;
  • close: 遍历集合时的结束符号,通常与open="("搭配使用,该参数为可选项;

# foreach时,collection属性值的三种情况

  • 如果传入的参数类型为List时,collection的默认属性值为list(即Map<list, value>,否则就需要自行包装成Map),同样可以使用@Param注解自定义keyName;
  • 如果传入的参数类型为array时,collection的默认属性值为array,同样可以使用@Param注解自定义keyName;
  • 如果传入的参数类型为Map时,collection的属性值可为三种情况:(1.遍历map.keys;2.遍历map.values;3.遍历map.entrySet()),稍后会在代码中示例;

# collection属性值类型为List

建议做if test="xxxx !=null and xxxx.size()>0"的校验,比较严谨。array为.length()

List<UserList> getUserInfo(@Param("userName") List<String> userName);
1
 <select id="getUserInfo" resultType="com.test.UserList">
    SELECT
    *
    FROM user_info
    where
    <if test="userName!= null and userName.size() >0">
        USERNAME IN
        <foreach collection="userName" item="value" separator="," open="(" close=")">
            #{value}
        </foreach>
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
  • 以下为任由mybatis自行处理list参数
List<UserList> getUserInfo(List<String> userName);
1
 <select id="getUserInfo" resultType="com.test.UserList" parameterType="list">
    SELECT
    *
    FROM user_info
    where
    <if test="userName!= null and userName.size() >0">
        USERNAME IN
        <foreach collection="list" item="value" separator="," open="(" close=")">
            #{value}
        </foreach>
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12

# collection属性值类型为Array

建议做if test="xxxx !=null and xxxx.length()>0"的校验,比较严谨

List<UserList> getUserInfo(@Param("userName") String[] userName);
1
<select id="getUserInfo" resultType="com.test.UserList">
    SELECT
        *
    FROM user_info
    where
    <if test="userName!= null and userName.length() >0">
        USERNAME IN
        <foreach collection="userName" item="value" separator="," open="(" close=")">
            #{value}
        </foreach>
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12

# collection属性值类型为Map

建议做 if test="xxxx !=null and xxxx.size()>0"的校验,比较严谨

 Map<String, String> patientMap = userList.stream().filter(item -> StringUtils.hasText(item.getUserName()) &&
                    StringUtils.hasText(item.getAge())).collect(Collectors.toMap(UserList::getUserName, UserList::getAge));

List<UserList> getUserInfo(@Param("user") Map<String,String> user);
1
2
3
4
  • 第一种:获取Map的键值对,多字段组合条件情况下,一定要注意书写格式:括号()
eg: SELECT * FROM user_info WHERE (USERNAME,AGE) IN (('张三','26'),('李四','58'),('王五','27'),......);
1
<select id="getUserInfo" resultType="com.test.UserList">
    SELECT
        *
    FROM user_info
    where
    <if test="user!= null and user.size() >0">
        (USERNAME,AGE) IN
        <foreach collection="user.entrySet()" item="value" index="key" separator="," open="(" close=")">
            (#{key},#{value})
        </foreach>
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
  • 第二种:参数Map类型,只需要获取key值或者value值
<select id="getUserInfo" resultType="com.test.UserList">
    SELECT
        *
    FROM user_info
    where
    <if test="user!= null and user.size() >0">
        (USERNAME) IN
        <foreach collection="user.keys" item="key"  separator="," open="(" close=")">
            #{key}
        </foreach>
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
<select id="getUserInfo" resultType="com.test.UserList">
    SELECT
        *
    FROM user_info
    where
    <if test="user!= null and user.size() >0">
        (USERNAME) IN
        <foreach collection="user.values" item="value"  separator="," open="(" close=")">
            #{key}
        </foreach>
    </if>
</select>
1
2
3
4
5
6
7
8
9
10
11
12

# 参数为map查询条件在json字符串里

<if test="@com.fourfaith.base.mybatis.Ognl@isNotEmpty(params.searchMap)">
    <foreach collection="params.searchMap.entrySet()" item="value" index="key" separator=" ">
        <if test="@com.fourfaith.base.mybatis.Ognl@isNotEmpty(value)">
            and json_unquote(json_extract(sce.data,'$."${key}"')) = #{value}
        </if>
    </foreach>
</if>
1
2
3
4
5
6
7

# 一个方法实现插入或更新,必须有主键

<insert id="updateBatch" parameterType="java.util.List">
    insert into standard_relation(id,relation_type, standard_from_uuid,
    standard_to_uuid, relation_score, stat,
    last_process_id, is_deleted, gmt_created,
    gmt_modified,relation_desc)VALUES
    <foreach collection="list" item="item" index="index" separator=",">
        (#{item.id,jdbcType=BIGINT},#{item.relationType,jdbcType=VARCHAR}, #{item.standardFromUuid,jdbcType=VARCHAR},
        #{item.standardToUuid,jdbcType=VARCHAR}, #{item.relationScore,jdbcType=DECIMAL}, #{item.stat,jdbcType=TINYINT},
        #{item.lastProcessId,jdbcType=BIGINT}, #{item.isDeleted,jdbcType=TINYINT}, #{item.gmtCreated,jdbcType=TIMESTAMP},
        #{item.gmtModified,jdbcType=TIMESTAMP},#{item.relationDesc,jdbcType=VARCHAR})
    </foreach>
    ON DUPLICATE KEY UPDATE
    id=VALUES(id),relation_type = VALUES(relation_type),standard_from_uuid = VALUES(standard_from_uuid),standard_to_uuid = VALUES(standard_to_uuid),
    relation_score = VALUES(relation_score),stat = VALUES(stat),last_process_id = VALUES(last_process_id),
    is_deleted = VALUES(is_deleted),gmt_created = VALUES(gmt_created),
    gmt_modified = VALUES(gmt_modified),relation_desc = VALUES(relation_desc)
</insert>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# OGNL

Object-Graph Navigation Language(对象视图导航语言)

# 直接调用java中的方法

rate = '${@com.ht.port.util.TransferUtil@findRate(item)}'
1

其中findRate必须是公共静态方法

# if标签test上使用

<if test='@org.apache.commons.lang3.StringUtils@isNotBlank(paramName)'>t.name=#{paramName}</if>

<if test='@com.ht.constant.LcdConstant@ONE == 变量名'></if>
1
2
3

# 直接调用java中静态变量

'${@全路径名@常量名}'

t.name='${@com.ht.common.Constant@DEFAULT_NAME}'
1

# 使用枚举值

  • '${@全路径名@枚举值.get属性()}'
  • '${@全路径名@枚举值}' -- 拿到枚举值的name
t.name=${@com.ht.common.HtEunm@ONE.getCode()}
t.name='${@com.ht.common.HtEunm@ONE}'
1
2

# bind标签中引用

<bind name='rateValue' value='@com.ht.port.util.TransferUtil@findRate(item)' />
1

但是请注意,bind标签有个坑,因为它是把value的值绑定到finalItem中,即finalItem相当于一个静态变量,如果在循环中就会出问题,如下,就会发现rate绑定的参数是同一个值

<foreach item='item' index='index' collection='keyWord.split(",")' open='AND (' separator='AND' close=')'>
 (name LIKE binary CONCAT('%',trim(#{item}),'%'))
 <bind name='rateValue' value='@com.ht.port.util.TransferUtil@findRate(item)'/>
 OR rate = #{rateValue}
</foreach>
1
2
3
4
5

# 使用内部类

外部类与内部类采用美元符号【$】隔开(package.OuterClass$InnerClass

t.LCS_CD in
 ${@com.ht.common.enums.IAbnmlAccEnum$AbnmalLcsCdENum@findCheckedLcsCds(unCd)}
1
2

# 类型别名

别名不区分大小写

# 内置类型别名

546512717259197.png

Alias Mapped Type
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal Bigdecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

# 自定义别名

# 1、配置指定别名

<typeAliases>
  <typeAlias type='com.ht.pojo.Dept' alias='dept1'/>
</typeAliases>
1
2
3

# 2、指定包下取别名(类名即别名)

<typeAliases>
  <typeAlias type='com.ht.pojo.Dept' alias='dept1'/>
  <package name='com.ht.pojo' />
</typeAliases>
1
2
3
4

(以上,dept1与Dept都可以作为别名)

# 3、注解取别名

@Alias放在类上,并取名,如:@Alias('HtDept') 注意:此时配置指定别名仍可使用,但是指定包名下取别名则不可用

# trim

一般用于去除sql语句里多余的'and'关键字、逗号(',')、或者给sql语句拼接'where'、'set'以及'values('等前缀或者添加')'

# prefix

给sql语句添加前缀

# suffix

给sql语句添加后缀

# prefixOverrides

去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设prefixOverrides='AND',当sql语句的开头位AND时,trim标签则会删除该AND

# suffixOverrides

同上,去除sql语句末尾的关键字或者字符

# 其他实现动态sql的元素

if、choose(when,otherwise)、foreach

# where

智能优化where条件,自动忽略首部的and、or之类的关键字

# set

如果set包含内容为空时,会报错。

# 进阶

# 如何获取新插入数据的ID信息

<insert id="addJobTpl" parameterType="com.ccb.nrmm.data.po.JobTplPo" useGeneratedKeys="true" keyProperty="tplId">
    insert into rwa_tpl_info(tpl_nm,tpl_dsc,ceate_id,create_time,update_time)
    values (#{tplNm},#{tplDsc},#{createId},sysdate(),sysdate())
</insert>
1
2
3
4

关键信息useGeneratedKeyskeyProperty 经过上述改造后的插入语句会在入库后将自增长的id信息写入对象里指定的id属性中,上述例子则会写到tplId

<insert id="addJobTpl" parameterType="com.ccb.nrmm.data.po.JobTplPo">
    insert into rwa_tpl_info(tpl_nm,tpl_dsc,ceate_id,create_time,update_time)
    values (#{tplNm},#{tplDsc},#{createId},sysdate(),sysdate())
    <selectKey>
        select @@identity
    </selectKey>
</insert>

<!-- 或者 -->
<insert id="addJobTpl" parameterType="com.ccb.nrmm.data.po.JobTplPo">
    insert into rwa_tpl_info(tpl_nm,tpl_dsc,ceate_id,create_time,update_time)
    values (#{tplNm},#{tplDsc},#{createId},sysdate(),sysdate())
    <selectKey>
        select LAST_INSERT_ID()
    </selectKey>
</insert>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上述方式,不保险,无法保证数据里最新插入的id是当前表的,容易错乱,不建议使用

# xml里批量更新数据

批量插入和批量更新的原理:把多条sql拼接为一条

材料准备:测试表 test1

-- 测试表 test1
CREATE TABLE `test1` (
  `id` int(14) NOT NULL,
  `name` varchar(50) DEFAULT NULL,
  `remark1` varchar(50) DEFAULT NULL,
  `remark2` varchar(50) DEFAULT NULL,
  `remark3` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1
2
3
4
5
6
7
8
9

# 一、批量插入

例如:要插入3条数据,那么非批量的插入数据,执行sql分别为:

insert into test1(id,name,remark1,remark2,remark3) 
    values(1,'sb','rmk11','rmk12','rmk13');
 
insert into test1(id,name,remark1,remark2,remark3) 
    values(2,'sb','rmk21','rmk22','rmk23');
 
insert into test1(id,name,remark1,remark2,remark3) 
    values(3,'sb','rmk33','rmk32','rmk33');
1
2
3
4
5
6
7
8

我们可以将以上的3条sql转变为1条sql:

insert into test1(id,name,remark1,remark2,remark3) 
VALUES
(1,'sb','rmk11','rmk12','rmk13'),
(2,'sb','rmk21','rmk22','rmk23'),
(3,'sb','rmk33','rmk32','rmk33');
1
2
3
4
5

这样就只需要连接一次数据库,并且执行一条sql就可以插入多条数据了。

当然我们如果使用了mybatis,那么在插入多条数据的时候,不需要自己拼接sql,mybatis可以帮我们拼接。

TestBatchService 中的方法只需要把,需要插入的数据放入一个集合List中,然后直接调用mapper方法。

public void testInsertBatch() {
 
        List<Test1> list = new ArrayList<>();
        for (int i=0;i<10000;i++){
            //拼装测试数据
            Test1 test1 = new Test1();
            test1.setId(i);
            test1.setName("sb"+i);
            test1.setRemark1("remark1_"+i);
            test1.setRemark2("remark2_"+i);
            test1.setRemark3("remark3_"+i);
            list.add(test1);
        }
        testBatchMapper.insertBatch(list);
 
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在使用mybatis时,mapper我们可以这么配置,例如:在TestBatchMapper.xml中

    <!-- 批量插入 -->
    <insert id="insertBatch">
        insert into test1
        (id,name,remark1,remark2,remark3)
        values
        <foreach collection ="list" item="tt" separator =",">
            (#{tt.id}, #{tt.name}, #{tt.remark1}, #{tt.remark2}, #{tt.remark3})
        </foreach>
    </insert>
1
2
3
4
5
6
7
8
9

这样mybatis就帮我们把list的数据一个个拿出来拼接成一条sql,最后执行。性能可以说是非常高效,本机测试10000条数据的插入在2秒内完成!

# 二、批量更新

非批量更新的做法,在循环体中逐条更新数据。

 public void testUpdate() {
        long startTime = System.currentTimeMillis();
        System.out.println("开始时间为:"+startTime);
        for (int i=0;i<10000;i++){
            Test1 test1 = new Test1();
            test1.setId(i);
            test1.setName("update_sb"+i);
            test1.setRemark1("update_remark1_"+i);
            test1.setRemark2("update_remark2_"+i);
            test1.setRemark3("update_remark3_"+i);
            this.update(test1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("结束时间为:"+endTime);
        long totalTime = endTime - startTime ;
        System.out.println("总耗时:"+totalTime);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

批量更新的方法把数据放到一个集合list中,然后一次更新:

public void testUpdateBatch() {
        List<Test1> list = new ArrayList<>();
        long startTime = System.currentTimeMillis();
        System.out.println("开始时间为:"+startTime);
        for (int i=0;i<10000;i++){
            Test1 test1 = new Test1();
            test1.setId(i);
            test1.setName("batch_update_sb"+i);
            test1.setRemark1("batch_update_remark1_"+i);
            test1.setRemark2("batch_update_remark2_"+i);
            test1.setRemark3("batch_update_remark3_"+i);
            list.add(test1);
        }
        this.updateBatch(list);
        long endTime = System.currentTimeMillis();
        System.out.println("结束时间为:"+endTime);
        long totalTime = endTime - startTime ;
        System.out.println("总耗时:"+totalTime);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

TestBatchMapper.xml的配置为:

    <!-- 高效的批量更新 本地测试10000条数据 -->
    <update id="updateBatch" parameterType="java.util.List">
        update test1
        set  name=
        <foreach collection="list" item="item" index="index"
                 separator=" " open="case ID" close="end">
            when #{item.id} then #{item.name}
        </foreach>,
        remark1 =
        <foreach collection="list" item="item" index="index"
                 separator=" " open="case ID" close="end">
            when #{item.id} then #{item.remark1}
        </foreach>,
        remark2 =
        <foreach collection="list" item="item" index="index"
                 separator=" " open="case ID" close="end">
            when #{item.id} then #{item.remark2}
        </foreach>,
        remark3 =
        <foreach collection="list" item="item" index="index"
                 separator=" " open="case ID" close="end">
            when #{item.id} then #{item.remark3}
        </foreach>
        where id in
        <foreach collection="list" index="index" item="item"
                 separator="," open="(" close=")">
            #{item.id,jdbcType=BIGINT}
        </foreach>
    </update>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

批量更新利用了mysql的 cas ... when ... then 的原理,把sql拼接为一条,最后执行。本机测试结果:使用普通更新10000条数据需要 100多秒,使用批量更新10000条数据只需要8秒,批量性能还是挺高效的

# 三、mysql的 case ... when ... then 的简单

既然上面提到了mysql的case ... when ... then 的使用,那么我们简单分析一下其用法,

例如我们要更新3条数据到test1表中:

原数据

id	name	remark1
1	sb	muji
2	sb	muji
3	sb	muji
1
2
3
4

需要更新成以下数据

id	name	remark1
1	sb1	remarksb1
2	sb22	remarksb2
3	sb333	remarksb3
1
2
3
4

那么我们可以把这3条数据拼成以下sql:

update test1 
	set 
			name = 
				case 
					when id=1 then 'sb1'
					when id=2 then 'sb22'
					when id=3 then 'sb333'
				end
				,
			remark1 = 
				case 
					when id=1 then 'remarksb1'
					when id=2 then 'remarksb2'
					when id=3 then 'remarksb3'
				end
where id in(1,2,3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这样就可以使用一条数据,更新多条数据了,以上mybatis的批量更新就是利用了这个原理。

# 四、使用批量插入和批量更新的缺点

使用批量处理性能很高,但是存在以下缺点:

  • 1、在批量处理过程中,使用了事务如果出现其中的某些数据执行出错,那么会全部回滚,并且无法定位到错误的数据。
  • 2、因为是将多条sql拼接为一条所以数据量大会导致sql的数据很长,超过设置的长度会报错。

基于缺点1,我们可以将数据按批次批量处理,例如 把10000条拆成每次1000条按10次完成,这样就可以平衡性能和数据的出错时候定位数据范围的问题。 基于缺点2:使用 查看mysql的 sql的最大长度:

show variables like '%max_allowed_packet%';
1

当然我们可以在mysql的配置文件(my.ini)中的 max_allowed_packet = 60M 修改此项,来解决问题。

本机性能测试结果

数据量(条) insert insertBatch update updateBatch
1000 条 15765 ms 166 ms 7268 ms 246 ms
5000 条 65906 ms 973 ms 12201 ms 2587 ms
10000 条 127057 ms 1283 ms 109010 ms 8815 ms