企业邮件系统搭建的关键要素与效率提升策略探讨
2529
2022-05-30
1.概述
这一次想把MyBatis的XML声明SQL的方式大概说一下。使用的demo可以参考:
《spring boot整合Mybatis3.5.4使用XML定义SQL》
MyBatis可以通过注解使用声明,也可以xml文件来声明SQL。前者简单,不灵活,后者不仅方便灵活,还方便优。通过XML来编写映射的SQL也是MyBatis所推荐的。MyBatis的一个映射器类就对象一个xml文件,xml文件写SQL语句。
xml文件的根元素是mapper,mppper元素可以包含以下子元素:
cache:指定的命名空间的缓存配置
cache-ref:引用其他命名空间缓存配置
resultMap:描述如何从数据库结果集中加载到对象中
parameterMap:在MyBatis3.5.4中弃用了!
sql:定义可重用的SQL块
insert:用于声明INSERT语句
update:用于声明UPDATE语句
delete:用于声明DELETE语句
select:用于声明SELECT语句
下面逐一讲解。
2.元素讲解
2.1.cache
默认情况下:
1
只启用本地会话缓存,只在会话期间缓存数据。有如下特点:
所有查询出来的结果集都会被缓存起来;
所有在映射声明文件中的insert、update、delete语句都会被放到缓存里;
使用最近最少使用的(LRU)逐出算法 ;
缓存不会按任何基于时间的计划刷新
缓存可以存储1024个列表或对象的个引用;
缓存可读可写,意味着缓存的对象是不共享的,因此能够被调用者安全地修改,因为不存在其他调用者或线程潜在的修改。
缓存配置只会对cache标记所在的映射文件中的语句有起作用。如果不想用默认的配置,可以修改以上缓存的属性,如:
1
2
3
4
5
创建一个FIFO缓存,并且每隔60秒就刷新一次,最多可以存储512个返回的结果对象或列表或对象的引用,而且缓存只能读不能写。 因为写可能会在多个调用者或线程之间才生冲突。
缓存用的逐出策略有以下这些:
LRU(默认的):最近最少使用,移除那些长时间不使用的对象
FIFO:先进先出, 按照他们进入缓存的顺序移除对象
SOFT:软引用,基于垃圾收集器状态和软引用移除对象。
WEAK:弱引用, 更积极地基于垃圾收集器状态和弱引用规则移除对象
cache元素的属性:
使用自定义的cache:
1
自定义cache要实现org.apache.ibatis.cache.Cache接口,MyBatis的Cache接口是这样的:
public interface Cache { String getId(); int getSize(); void putObject(Object key, Object value); Object getObject(Object key); boolean hasKey(Object key); Object removeObject(Object key); void clear(); }
1
2
3
4
5
6
7
8
9
实现Cache接口:
public class MyCustomCache implements Cache{ // ... }
1
2
3
点到为止,更多内容请到网上查阅!
2.2.cache-ref
引用其他命名空间的缓存配置。
1
2.3.resultMap
resultMap是MyBatis中最重要的元素,因为将结果集映射到对象中,基本都用resultMap来完成。使用resultMap可以让你省去90%的JDBC代码,resultMap其甚至可以实现一些JDBC做不到的事。MyBatis自动创建ResultMap,基于名称自动映射列到JavaBean的属性上, 如果列名和JavaBean的属性匹配不上,我们可以在列名上使用select子句别名(标准SQL特性)来创建标签匹配:
1
2
3
4
5
6
7
8
除了上面这种给列名取个与JavaBean属性匹配得了的别名外,还可以使用
(1)第一步:使用resultMap标签建立列名与属性名的对应关系
1
2
3
4
5
(2)第二步:引用resultMap
1
2
3
4
5
resultMap不能和resultType同时使用,继续阅读你就明白的了。
数据库很难做到时时刻刻都能够和我们的JavaBean的属性匹配上。而且不是所有数据库都能很好的实现数据库设计第三范式或BCNF范式。这些问题都使处有时并不能简单地通过自动映射来完成,对于这些复杂的映射关系的处理可以用resultMap来解决表列名与JavaBean字段映射的问题。这也是它存在的原因。举例说明,下面这个复杂的SQL的映射问题:
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
30
31
32
33
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
从上面这个例子,我们可以看到resultMap标签有很多子标签,下面我们一个一个来看。
(1)constructor :在类实例化时将结果注入到类的构造函数中,如将结果果集的内容注入到下面这个类的构造函数中:
public class User { //... public User(Integer id, String username, int age) { //... } //... }
1
2
3
4
5
6
7
使用constructor时,要保证结果集内容的顺序要和构造函数的参数顺序一致,如下面的映射,MyBatis会搜索定义了三个参数(java.lang.Integer ,java.lang.String ,int)且出现顺序一样的构造函数:
1
2
3
4
5
如果不想维护arg元素的顺序,可以为每一个arg元素指定一个名称,使其与构造函数名称对应上(对应的注解方式是使用@Param ):
1
2
3
4
5
constructor元素的子元素:
(2)id & result:
1
2
这两个是结果映射中最基本的元素。id和result都是映射一个列值到一个简单类型(String, int, double, Date等)的属性或字段。 它们唯一的区别是id结果标识为一个标识符属性,在比较对象实例时使用,有助于提高总体性能。特别是提高缓存和嵌套result映射的性能,如SQL中带join语句的映射。
它们有以下属性:
JDBC支持的类型:
(3)association:复杂类型的组合,它是处理 “has-one” 这种关系的,例如Blog类中有个Author类型时的属性。MyBatis有以下两种方式来实现association加载:
嵌套Select:通过执行另一个SQL语句来返回这种嵌套的复杂类型,但这种式对大数量的查询的性能不是很好。
1
2
3
4
5
6
7
8
9
10
11
嵌套result:
先介绍association的一些属性:
下面是嵌套result的例子:
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
上面我们把author独立写了个resultMap这样author部分就可以重用了,如果我们不想重用,可以将其写在一起,如:
1
2
3
4
5
6
7
8
9
10
11
如果Blog中有两个字段author、coauthor,它们都是Author对象,resultMap又该如何写呢?select语句如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resultMap可以这样写:
1
2
3
4
5
6
association处理多结果集(调用存储过程):从MyBatis 3.2.3开始支持。有一些数据库允许调存储过程来执行一或多条语句,并返回一或多个结果集。这种可以不使用join连接查询。假设我们调用数据库里存储过程来执行以下查询并返回两个结果集(blogs,authors):
SELECT * FROM BLOG WHERE ID = #{id} SELECT * FROM AUTHOR WHERE ID = #{id}
1
2
在xml映射文件中,使用resultSets属性指定结果集名称,多个结果集名称之间用逗号分隔:
1
2
3
现在我们可以把authors结果集数据填充到 “author” association中:
1
2
3
4
5
6
7
8
9
10
11
(4)collection:复杂类型的集合。collection元素与association元素很像,但后者是解决“has-one",前者是解决”has-many“这种关系。举个例子:
Blog有许多Posts(帖子),在Blog类里,帖子被定义列表:
private List
1
那么resultMap就应该这样写:
1
2
3
4
5
与association元素一样,collection元素也有两种方式来处理结果集:
嵌套select:
1
2
3
4
5
6
7
8
9
10
嵌套result:
首先,来看看根据blog id拿帖子的SQL:
1
2
3
4
5
6
7
8
9
10
11
12
resultMap我们可以这样写:
1
2
3
4
5
6
7
8
9
10
也可以将Post的resultMap单独写,以便重用:
1
2
3
4
5
6
7
8
9
10
11
collection处理多个resultSet的情况(调用存储过程):
和前面association一样,我们可以调用一个存储过程来执行两个查询,并返回两个结果集,一个是Blogs的,别一个是Posts的:
SELECT * FROM BLOG WHERE ID = #{id} SELECT * FROM POST WHERE BLOG_ID = #{id}
1
2
在xml映射文件中,使用resultSets指定每个结果集的名字,多个结果集名称之间用逗号隔开:
1
2
3
将posts集合填充到对象:
1
2
3
4
5
6
7
8
9
collection的相关属性:
(5)discriminator :
1
2
3
有时候一个数据库查询可能会返回许多不同的数据类型的结果集。discriminator元素就是设计用来处理这种情况的。discriminator有点java中的switch语句。discriminator的定义指定column和javaType属性,column就是MyBatis将要对比的值所在的地方。javaType是确保对比的类型正确。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
与下面是等价的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
在上面这个例子里,MyBatis会收到结果集中每条记录,然后对比它的vehicle_ type的值,如果它匹配上了discriminator cases,那么就用指定的resultMap。如果没有一个匹配上,那么MyBatis将使用定义在discriminator块外的resultMap。carResult定义如下,如果vehicle_type是1,那么它就被使用:
1
2
3
我们知道car也是vehicle,所以我们想使用carResult的同时也把vehicleResult的字段加载进来,这其实就是car继承vehicle,那么resultMap也是可以继承的,上面的carResult可以继承vehicleResult,这样也会把vehicleResult的字段加载进来:
1
2
3
2.4.sql
这个元素是用于定义一些可重用的SQL片段,这些片段可以被包含在其他的语句中。如定义以下SQL片段:
1
在select语句中包含此片段:
1
2
3
4
5
6
7
8
9
10
11
属性值也可以用于include refid属性里,或者include子句内的属性值,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2.5.insert、update、delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
insert、update、delete的属性:
列子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
insert语句自动生成id列:
1
2
3
4
5
如果数据库支持同时多行插入,那么我们可以给对象传个list或数组:
1
2
3
4
5
6
如果数据库不支持自动生成列类型或者还不支持JDBC驱动自动生成keys,那么可以使用以下selectKey方式来生成key,下面这个列子是随机生成一个ID:
1
2
3
4
5
6
7
8
9
selectKey子元素:
1
2
3
4
5
2.6.select
1
2
3
4
5
6
7
8
9
10
11
12
select元素的属性:
select举列:
1
2
3
2.7.参数类型
一般来说,我们可以通过parameterType指定参数类型,如果指定的类型与数据库中的类型不一样的话,MyBatis提供一种方式给我们指定参数类型,格式:
#{property,javaType=int,jdbcType=NUMERIC}
1
javaType通常由参数对象决定,除非对象是一个HashMap。指定javaType可以确保使用正确的TypeHandler。jdbcType是 JDBC需要的,用于所有可以为空的列,如果null作为一个值传递的话,就应该指定jdbcType。
我们还可以指定TypeHandler类或别名:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
1
对于数字类型,有一个numericScale可以决定精确到小数点后几位:
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
1
mode模式属性可以是IN 、OUT、INOUT,如果是OUT或INOUT,那么参数对象属性的真实值会被改变。如果模式是OUT或INOUT ,并且jdbcType=CURSOR
,那么你必须指定一个resultMap来映射ResultSet到参数类型,此时javaType属性是可选的:
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentRes}
1
MyBatis也支持高级数据类型,如结构体structs,但你必须告知语句类型名字:
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentRes}
1
大多数时候,我们都只简单地指定属性名,然后MyBatis就会帮我们搞掂剩下的这些。因此,我们只需要为可空的列指定jdbcType就可以了,如:
#{firstName} #{middleInitial,jdbcType=VARCHAR} #{lastName}
1
2
3
2.8.置换字符串
默认情况下,使用#{}语法,会让MyBatis生成PreparedStatement属性,并将值安全地设置到PreparedStatement的参数里。这种方式既安全又快速。有时,我们想直接在SQL语句注入未修改的字符串,当SQL语句中的元数据(如表名,列名)是动态变化的,字符串置换就显得很有用了。例如,你要从一张表中通过任意一个列来选择:
@Select("select * from user where ${column} = #{value}") User findByColumn(@Param("column") String column, @Param("value") String value);
1
2
接受从用户输入或提供SQL语句中未修改的元数据名称是不安全的。这存在潜在的SQL注入攻击。因此如果使用这种方式,我们要做到不允许用户输入这些字段,我们自己应该做好转义和检查,以此来提高安全性。
3.别名
使用别名,你就不需要写全限定路径了。
在springboot的application.yml加入以下配置:
mybatis: # 指定MyBatis的配置文件位置 config-location: classpath:config/mybatis-mapper-config.xml # 指定映射器的xml文件的位置 mapper-locations: classpath:mybatis/mapper/*.xml
1
2
3
4
5
~/Desktop/MyBatisXMLDemo$ touch src/main/resources/config/mybatis-mapper-config.xml ~/Desktop/MyBatisXMLDemo$ touch src/main/resources/mybatis/mapper/PersonMapper.xml
1
2
1
2
3
4
5
6
7
1
2
3
4
5
6
7
8
9
10
11
12
还可以把数据库连接的信息从application.yml文件中移到MyBatis的配置文件mybatis-mapper-config.xml中:
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
4.动态SQL
MyBatis可以实现动态SQL。
if
choose (when, otherwise)
trim (where, set)
foreach
4.1.if
1
2
3
4
5
6
7
再如:
1
2
3
4
5
6
7
8
9
4.2.choose (when, otherwise)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
4.3.trim (where, set)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
where元素可以很方便处理SQL的where子句,当where元素中的if元素有符合的即需要插入where子句的),where元素就会在SQL语句后面添加where子句。如果where子句是以 “AND” 或 "OR"开头,它会把它去掉,再添加到where后面。如果where元素都还满足不了你的需求,Mybatis提供trim元素让你来自定义,如下面的trim等价于where元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
prefix是要插入的,prefixOverrides是要移除的。prefixOverrides属性接受要重写的以管道分隔的文本列表,注意空格的地方,它是必要的。简单点来说,trim的结果就是prefix属性的值加上trim元素里的值,如果trim里没有值,则不在SQL里插入,如果trim里的值是以prefixOverrides里的值开头的则先移除再和prefix属性值一起插入到SQL语句里。
有一种相似的动态update语句的解决方案叫set。set元素可以用来动态包含要更新的列,而排除不需要更新的列:
1
2
3
4
5
6
7
8
9
10
上面也可以用trim来自定义,如:
1
2
3
4
5
6
7
8
9
10
11
4.4.foreach
1
2
3
4
5
6
7
8
foreach元素允许我们指定一个集合collection,声明item和index变量,这两个变量可以在foreach元素内部使用。foreach也允许我们指定开始和结束的字符串,和添加在迭代之间的分隔符 。foreach元素不会意外地附加额外的分隔符,这一点可以放心。
我们可以传递任何迭代对象,如List、Set等,同样也可传递Map或Array对象到foreach作为集合参数。当使用Iterable 或 Array,index表示当前迭代的下标,item表示当前的值。当使用Map,如 Map.Entry 对象的集合,index就是key对象,item就是值对象。
4.5.script
在映射器类里使用注解的方式使用动态SQL,需要使用script 元素:
@Update({""}) void updateAuthorValues(Author author);
1
2
3
4
5
6
7
8
9
10
11
4.6.bind
bind元素可以让你在OGNL表达式之外,创建一个变量,并将其绑定到上下文中:
1
2
3
4
5
5. 多数据库供应商支持
说白了就是连接多个数据库。如果有一个databaseIdProvider配置了“_databaseId"变量并且对于动态代码来说是可用的。那么,我们可以根据不同的数据库提供商来建不同的语句,如:
1
2
3
4
5
6
7
8
9
10
11
上面大概把MyBatis使用xml映射文件说了一遍。
MyBatis XML
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。