mysql映射文件_Mybatis sql映射文件浅析 Mybatis简介(三)

简介

除了配置相关之外,另一个核心就是SQL映射,MyBatis 的真正强大也在于它的映射语句。

Mybatis创建了一套规则以XML为载体映射SQL

之前提到过,各项配置信息将Mybatis应用的整体框架搭建起来,而映射部分则是准备了一次SQL操作所需的信息

一次SQL执行的主要事件是什么?

输入参数解析,绝大多数SQL都是需要参数的

SQL,通过SQL与数据库交互,所以最根本的是SQL,如果连SQL都没有,还扯个蛋蛋?

结果映射,Mybatis可以帮我们完成字段与Java类型的映射

f559e1a93e996a6ad2f7fc104af6a868.png

所以说SQL映射的核心内容为:

SQL内容指定

参数信息设置

输出结果设置

当然,每个SQL都需要指定一个ID作为用于执行时的唯一标识符

比如下面示例

SELECT* FROM PERSON WHERE ID =#{id}

SELECT * FROM PERSON WHERE ID = #{id}  为SQL内容部分

parameterType="int" 以及SQL中的#{id}为参数信息设置部分

resultType="hashmap" 为输出结果设置部分

概况

如上所述,核心内容为:

ID

SQL内容

入参设置

结果配置

ID用于执行时唯一定位一个映射

对于SQL内容,也没有什么特别的,就是平常所说的数据库可以执行的SQL语句

对于SQL内容中的参数,MyBatis 会通过 JDBC创建一个预处理语句参数

这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,类似这样:

//Similar JDBC code, NOT MyBatis…String selectPerson ="SELECT * FROM PERSON WHERE ID=?";PreparedStatement ps = conn.prepareStatement(selectPerson);

ps.setInt(1,id);

输入的类型使用parameterType进行指定(parameterMap – 已废弃!)

输出信息使用resultMap或者resultType进行指定

从包含的信息的角度分析Mybatis 映射文件的核心内容

如下图所示:

c6a3cf069200076c809aaeba50ed13ac.png

而对于数据库的CRUD操作,Mybatis的XML配置中分别使用了 insert、select、update、delete四个标签进行分别处理

所以一个映射(映射文件中的一个)常见的形式如下,parameterType以及resultType | resultMap 会根据SQL的类型需要或者不需要

SQL内容......

核心信息为通过Mybatis执行一次SQL的必备信息,Mybatis还可以提供更多的功能设置

所以对于不同类型的SQL,还会有更多的一些配置条目

比如之前提到过的数据库厂商标识符 databaseId,所有类型的SQL映射都可以设置这一属性

而对于其他的附加辅助属性配置,有些是所有类型共同的,而有些是特有的

databaseId就是共有的,比如用于返回自动生成的键的配置useGeneratedKeys 只有insert与update才拥有

文档结构解析

所以从文档结构的形式角度看SQL映射,有四种类型的映射 select、insert、update、delete

每种类型又都有各自的属性设置,有一些是共同的,有一些是特有的

下图如果不清楚,请到评论区中,右键,新标签查看图片,可以查看到大图

0b9d9afeeda1377e1a84753c3a478e0a.png

属性角度解析

如果从属性的角度去看待各自的归属,每种属性都有各自的作用功能

他们自身的功能也决定了那些类型才能拥有他

比如键值的返回相关的useGeneratedKeys,就只可能发生在insert或者update中,只有他们才可能自动生成键

33b19ad97ebeec7eacc1c615137704b3.png

以上为SQL映射文件的核心关键信息以及属性的解读

有些细节还需要注意,关于flushCache以及userCache,前者是是否清空清空本地缓存和二级缓存,后者是本条语句的结果是否进行二级缓存,含义完全不一样

四种类型都有flushCache属性,对于select默认false,对于insert、update、delete默认是true

而userCache只有select有,默认是true

因为缓存机制,比如update 的时候如果 设置flushCache="false",则当你更新后,查询的数据数据还是老的数据。

额外的馈赠-语法糖

在编程实践中,经常有一些公共的方法或者处理逻辑,我们通常将他们提取单独封装,以便提高代码复用程序

那么,对于SQL的编写呢?

Mybatis也提供了封装提取的手段---SQL元素标签

........

然后可以使用include,将他包含到指定的位置

这是一种静态的织入,通过SQL元素,你可以方便的完成公共SQL片段的提取封装

如果有两个表,都有name、age等字段,我想将他们封装,但是表名却又不一样怎么办?

SQL元素还提供了别名的设置,可以很容易的解决这个问题,请参考官方文档

${alias}.id,${alias}.username,${alias}.password

这个 SQL 片段可以被包含在其他语句中,例如:

select,from some_table t1

cross join some_table t2

上面示例中包含了两次SQL片段,第一次中alias被替换为t1 ,第二次中的alias被替换为t2,最终的结果形式为:

select

t1.id,

t1.username,

t1.password,

t2.id,

t2.username,

t2.password

from

some_table t1

cross join some_table t2

深入映射

参数(Parameters)细节配置

SELECT * FROM PERSON WHERE ID = #{id}

示例中入参类型通过parameterType指定为int,参数占位符为#{id},这是最简单的一种形式了,入参只是一个Java基本类型(非自定义的对象类型)

对于对象类型Mybatis也可以很好的完成工作,不管是入参时的解析,还是输出结果的映射解析

能够根据属性的名称进行自动的配对

select id, username, password

from users

where id = #{id}

insert into users (id, username, password)

values (#{id}, #{username}, #{password})

不仅仅支持对象,而且还支持map,当parameterType="map"时,map的key会被用来和占位符中的名称进行匹配

也就是说对于: SELECT * FROM PERSON WHERE ID = #{id}    ,当parameterType="map"时,你的参数map需要存在  key=id  的元素

parameterType也支持list,当parameterType="list"时,可以借助于动态SQL的foreach 进行循环

如果是基本数据类型的List,比如List 那么直接循环即可;如果是List,可以通过遍历每个元素,然后通过#{item.username}、#{item.password}的形式进行读取

INSERT INTO xxx_table(

username,

password,

createTime

)

values(

#{item.username},

#{item.password},

#{item.createTime}

)

可以看得出来,类型的形式很丰富,Mybatis很多时候都可以自动处理,但是你可以对他进行显式的明确指明,比如

#{property,javaType=int,jdbcType=NUMERIC}

property表示字段名称,javaType为int,jdbcType为NUMERIC

(jdbcType是JDBC对于数据库类型的抽象定义,详见java.sql.JDBCType 或者java.sql.Types,可以简单认为数据库字段类型

javaType 通常可以由参数对象确定,除非该对象是一个 HashMap,是map的时候通常也可以很好的工作,但是建议在入参类型是Map对他进行明确的指定

需要注意的是:如果一个列允许 null 值,并且会传递值 null 的参数,就必须要指定 JDBC Type

当你在插入时,如果需要使用自定义的typeHandler ,也应该在此处进行指定

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

对于数值类型,还可以设置保留小数的位数

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

对于参数的细化配置也很容易理解,他要么是用于使用时确定入参或者数据库字段的具体类型,如javaType或者jdbcType

要么就是在字段处理过程中增加的一些处理所需要的信息,比如是不是需要按照自定义处理器处理后在执行到数据库?是不是将数值的小数位数处理后在去执行数据库?

另外对于存储过程的调用Mybatis也是有支持的,mode 属性允许你指定 IN,OUT 或 INOUT 参数。

通常我们使用#{}的格式进行字符串处理,这样可以安全,是通常的首选,但是如果你就是想直接插入一个字符串到SQL中,可以使用${},不过很显然,$的使用你要非常慎重

ResultMap-别名映射

Mybatis好用的一大神器就是ResultMap,可以让你高效灵活的从结果集映射到你想要的类型中,能够进行很多高级的映射

一般的映射可以借助于resultType就可以解决了,resultType后面的值同parameterType类似

parameterType  resultType的值都用于明确类型,可以使用完全限定名

不过你是否还记得入门简介中关于typeAlias中的介绍?

Mybatis内置了Java基础类型的别名,你都可以直接使用

借助于resultType可以完成一些基本的诉求,比如从单表到对应实体类对象的映射,能够自动的根据字段名称和属性名称进行匹配

fae97e11486f5c519fde713994e0c66b.png

但是如果名称不对应又该怎么办?

如果你的实体中的属性名称为userName,数据库字段名为name,Mybatis真的敢擅自的将这两者对应起来么?

如下图所示,将之前的第一个示例稍作修改,增加一个StudentAnother,name更改为了userName,并将测试代码稍作修改

从结果可以看得到,实体中的userName是null ,Mybatis肯定不敢擅自映射

222d9f03262d1ebc3d8ce7cca7ca655c.png

一种可行的方式是使用别名,通过数据库字段AS设置别名,就可以成功的完成映射

cd7aa0f2bdc13b193dea9827c6e15e78.png

通过别名,将数据库列名通过别名与属性字段建立映射关系,然后Mybatis就可以进行自动匹配了

但是这种形式如果有多条SQL,每个SQL中都需要有别名,而且,如果后续有原因修改对象的字段名字,怎么办?

另外的方式就是使用ResultMap,ResultMap的基础用法就是相当于设置别名

但是借助于ResultMap,将别名的映射关系,维护在ResultMap中,所有使用到此映射类型的SQL都只需要关联这个ResultMap即可,如果有变更,仅仅需要变更ResultMap中的属性字段对应关系

所有的SQL中的内容并不需要变动

如下图所示,SQL中字段与实体类中不匹配,查询的结果为null

右侧通过ResultMap将userName与列名name进行了映射,就可以成功读取数据

54d564c7d13e70923e8444ce7b342d93.png

ResultMap最基础的形式如下

ResultMap需要id和type,id用于唯一标识符,type用于指明类型,比如Blog

ResultMap最基础的两个信息是id和result元素

他们的内容均为property="......." column="...........",property(对象的属性字段)和clumn(数据库的列名)

对于基础性的映射借助于id和result就可以完全搞定, id 表示的结果将是对象的标识属性,可以认为对象的唯一标识符用id指定,这对于性能的提高很有作用

小结

对于ResultMap就是做字段到属性的映射,id和result都是这个作用,但是如果是唯一标识符请使用id来指定

另外对于每一个字段,还可以明确的声明javaType和jdbcType,以及typeHandler用于更加细致的解析映射

所以说基本元素为:

f36e61b6a94709872163cd649af47ef8.png

ResultMap-高级映射

ResultMap当然不仅仅是像上面那样只是别名的转换,还可以进行更加复杂的映射

对于结果集返回有哪些场景?

“将一行记录映射为一个对象”与“将多行记录映射为对象列表”这两者本质是一样的,因为所需要做的映射是一样的

比如上面数据库列名name到字段userName 的映射,不管是一行记录还是多行记录,他们都是一样的

所以下面就以一个对象为例

单纯的映射

比如上面的例子,数据库列名与实体类中的字段一一对应(尽管名称不完全匹配,但是仍旧是一一对应的)

组合的映射

对于关系型数据库存在着关联关系的说法,一对一,一对多等

这些关联关系最终也是要映射到对象中的, 所以对象中经常也会存在多种对应关系

比如下面官方文档中的示例----查询博客详情

一个博客Blog 对应着一个作者Author ,一个作者可能有多个博文Post,每篇博文有零或多条的评论Post_Tag 和标签Tag

308145620764a55e3793ddb08e6ab0a2.png

select

B.id as blog_id,

B.title as blog_title,

B.author_id as blog_author_id,

A.id as author_id,

A.username as author_username,

A.password as author_password,

A.email as author_email,

A.bio as author_bio,

A.favourite_section as author_favourite_section,

P.id as post_id,

P.blog_id as post_blog_id,

P.author_id as post_author_id,

P.created_on as post_created_on,

P.section as post_section,

P.subject as post_subject,

P.draft as draft,

P.body as post_body,

C.id as comment_id,

C.post_id as comment_post_id,

C.name as comment_name,

C.comment as comment_text,

T.id as tag_id,

T.name as tag_name

from Blog B

left outer join Author A on B.author_id = A.id

left outer join Post P on B.id = P.blog_id

left outer join Comment C on P.id = C.post_id

left outer join Post_Tag PT on PT.post_id = P.id

left outer join Tag T on PT.tag_id = T.id

where B.id = #{id}

对于实体类,一种可能的形式如下

Blog中有一个Author,有一个List ,每一个Post中又有List 和  List

044c6a829d7de412cc8ec89e0b9f4883.png

可以看得出来对于组合映射又有一对一以及一对多两种形式

(尽管Blog存在List postList; 但是在Mybatis中使用时,对于关系是从紧邻的上一层确定的,比如对于Comment看Post,对于Post看Blog,而不是从Blog看Comment  )

Mybatis的ResultMap可以完成类似上述SQL与实体类的映射

在Mybatis中只有两种情况,一对一和一对多

一对一Association

对于一对一被称作关联,在ResultMap中使用association元素表示这种关系

含义为:

association中的所有的字段 映射为association元素上property指定的一个属性

比如下面示例,将id和username 映射为author,谁的author?他的直接外层是谁就是谁!

对于association的基本格式如下,相当于在基础的ResultMap中插入了一个“一对一”的对应

association中对于字段和属性的映射也是使用id和result,对于唯一标志使用id来表示

关联的嵌套查询

对于一个association还可以对他进行嵌套查询,也就是在查询中进行查询

比如官方示例中

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM AUTHOR WHERE ID = #{id}

当执行selectBlog时,会执行 SELECT * FROM BLOG WHERE ID = #{id}  ,查询得到的结果映射到blogResult,在这个ResultMap中使用了association元素

这个association元素使用select标签进行了嵌套查询,也就是使用另外的一个映射selectAuthor进行处理

处理流程:

先查询selectBlog查询所有的结果

对于每一条结果,然后又再一次的select,这就是嵌套查询

这会出现“N+1 查询问题”,查询一次SQL查询出一个列表(这是1)然后对于这个列表的每一个结果都再次的查询(这是N)性能有些时候很不好

嵌套查询使用select,还有一个重要的就是association 上的 column,这个column用于指定嵌套查询的参数

比如上面的例子,将会使用author_id传递给 SELECT * FROM AUTHOR WHERE ID = #{id}中的id,然后进行查询

此处仅仅只是一个参数,如果是多个参数仍旧可以,使用 column= ” {prop1=col1,prop2=col2} ”的形式

比如:

ccde721b2c7790d03a14f99baccd010e.png

上面就是通过column指定将要传递给嵌套内查询的参数

鉴于ResultMap可以提供很好地映射,所以上面的示例完全可以修改为普通的association形式,通过join将关联查询的结果映射到指定的对象中,而不是借助于select元素进行嵌套查询

一对多collection

对于一对多关系,Mybatis使用collection

collection的逻辑本质上与association是一样的,都是对象字段映射

只不过用于区分,也用于在除了数据时,具体的指定类型

一个collection形式为:

内部依然是使用id和result完成字段和属性的映射

但是collection上使用ofType来指定这个属性的类型,而不是之前的javaType

这也很好理解,对于一对一或者检查的查询,他就是一个对象类型,所以使用JavaType

对于集合的映射,我们很清楚的知道他是一个集合,所以集合类型是他的javaType,比如 javaType="ArrayList",Mybatis 在很多情况下会为你算出来,所以可以省略javaType

但是,什么类型的集合?还需要说明,所以使用ofType进行指定,看起来更加清晰

使用collection的基础形式为:

集合的嵌套查询

对于collection也可以采用类似association中的select元素进行嵌套查询

原理也是类似,当检索出来结果后,借助于select指定的查询语句,循环查询

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM POST WHERE BLOG_ID = #{id}

ResultMap的嵌套

在前面的叙述中,所有的内部的关联或者集合的属性映射都是直接嵌套在外部ResultMap中的

186b0c69dbd1f34b1a12596556d36b8c.png

借助于嵌套查询的形式 select属性,可以进行嵌套查询,通过嵌套查询的方式,相当于经过这个select,内部的字段映射部分被路由到另一个ResultMap(ResultType)中了

而不需要在这个ResultMap中逐个重新的进行字段的映射指定

但是select会有1+N的问题,但是使用select时这种使用外部ResultMap(resultType)的形式却是很有实用意义

因为如果可以进行分离,被剥离的那一部分既可以单独使用,又可以嵌套在其他的ResultMap中,组合成更加强大的形式

Mybatis是支持ResultMap嵌套的

a80075704466db30aad522770ce55623.png

不仅仅association支持ResultMap的嵌套,collection也是支持的

209b96e3a47e7fe9ad98d925cb2212cc.png

可以看得出来,不管是借助于select的嵌套查询,还是ResultMap的嵌套,都只是在association上或者collection上附加select或者resultMap属性即可

然后就可以省略掉标签内部的字段映射处理了(id和result)

除非开发前对ResultMap的层级结构进行过统一设计布局,否则,内嵌其他人开发的ResultMap,也并不一定总是好事,当内嵌的ResultMap发生变动时,某些情况可能会导致问题

嵌套的ResultMap一定需要是本文件中的吗?当然不是必须的,比如下面示例中借助于:接口的全限定名称进行索引

ResultMap的重用

ResultMap的嵌套也是一种复用,此处说的重用非解耦后的复用

在ResultMap中,我们通过id或者result 将数据库字段和实体类中的属性名进行对应

列名和属性名的对应,以及列名和属性名全部都是固定的了,如下图所示,username就是和author_username对应

bd02045185c13b8ed9851a76b027e18b.png

在之前的例子中,一个blog有一个作者,但是如果一个博客还有一个联合作者怎么办?就像很多书可能不仅仅只有一个作者

在这种场景下:有两个作者,他们的java类型必然都是Author

而且他们的字段也是相同的,但是你不得不将他们进行区分,如下面SQL中所示,关联了两次Author表,通过前缀进行了区分

一种解决方法就是将映射部分也重写两次,就像关联两次那样,仅仅是列名column前缀不同(可以将这两个ResultMap嵌入到blogResult中或者内容移入到外层ResultMap中,总之是写两遍映射)

faf48fc70c820b45f648909c9fb9b7c5.png

还有一种方法就是借助于columnPrefix,如下图所示,Blog中有两个Author的实例,一个是author另一个是coAuthor,关联关系,使用association

他们都是Author类的实例,所以使用同样的ResultMap,通过columnPrefix对其中一个映射添加列前缀

通过这个列前缀,就相当于有了另外的一个ResultMap,这个ResultMap就是指定的ResultMap中的column中每一个值都加上一个前缀

cb4ed01cbe7eb231ab836bafea222a1a.png

构造方法字段值注入

使用Mybatis的核心就是为了执行SQL以及完成结果映射,结果的映射必然要创建最终需要映射的结果的对象

通过ResultMap中的id和result指定的字段值都是通过setter设置器方法进行值的设置的

既然最终就是要创建一个指定类型并且具有指定属性的对象结果,那么为什么一定非得是通过setter,难道不能在创建对象的时候通过构造方法初始化对象吗?

Mybatis的ResultMap是支持构造方法设置的

对于构造方法的属性值设置,通过constructor进行

将之前的例子稍作修改,增加一个构造方法,复制一个ResultMap,添加constructor,就可以完成映射

2df17c064e4b48b2a086e4345b7e9415.png

借助于constructor与使用id和result映射在业务逻辑上没有什么本质的区别,都是将列名与字段进行映射,变的是形式

因为是借助于构造函数,所以constructor中与ResultMap中的其他字段映射是有区别的,不是使用id和result 使用的是 arg 参数

简言之,使用构造方法需要根据方法签名进行匹配,方法签名就是类型和个数的匹配,所以需要javaType

对于有些场景你可能不希望暴露某些属性的共有setter设置器,就可以使用构造方法的形式

上面的示例中没有通过constructor对id进行映射,如果对id进行映射需要使用(没写错 就是idArg)

对于使用constructor对值进行解析映射,根本就是匹配正确的构造方法,除了使用javaType还有name,通过name指定构造方法参数的名称

从版本 3.4.3 开始,如果指定了名称name,就不需要严格死板的按照顺序对应了,可以打乱顺序。

没有人会刻意的打乱顺序,但是永远的保证映射的顺序不变动是很难得

5a67e030155b640f950a721984632fe4.png

鉴别器

重新建一个表作为示例,配置信息还是如原来一样,SQL映射文件也是在第一个示例中的XML中编写的

主要的信息如下,表以及数据以及实体类以及映射文件等

435183ac99fd6638e78686f500119887.png

定义了三个类,一个Person类作为抽象模型(尽管我这个不是抽象类)

一个成人类Adult和一个儿童类Child

Adult增加了company属性,Child增加了school属性

8ffaa11e080b1a9a09522d275367a82c.png

每个类都有setter和getter方法,并且还重写了toString方法

映射文件

f099750cf35ba02421d0c2129f947594.png

测试类

packagethird;importfirst.StudentAnother;importjava.io.InputStream;importjava.util.Collections;importjava.util.List;importorg.apache.ibatis.io.Resources;importorg.apache.ibatis.session.SqlSession;importorg.apache.ibatis.session.SqlSessionFactory;importorg.apache.ibatis.session.SqlSessionFactoryBuilder;public classTest {public static void main(String[] args) throwsException {/** 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。

* SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。

* 而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

**/String resource= "config/mybatis-config.xml";

InputStream inputStream=Resources.getResourceAsStream(resource);

SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream,"development");/** 从 SqlSessionFactory 中获取 SqlSession

**/SqlSession session=sqlSessionFactory.openSession();try{

List personList = session.selectList("mapper.myMapper.selectPerson");

personList.stream().forEach(i->{

System.out.print(i);

System.out.println(i.getClass().getName());

});

}finally{

session.close();

}

}

}

测试结果

97bfd4010e1db4ed5c4b684acddb7589.png

Mybatis很神奇的将结果映射为了不同的子类对象

所以说如果一条记录可能会对应多种不同类型的对象,就可以借助于discriminator,通过某个字段的数据鉴别,映射为不同的类

ResultMap中的type对应了父类型,discriminator上的column对应了需要鉴别的列名

每一个case对应着一种类型或者一个ResultMap,通过discriminator就可以根据鉴别的值的不同进行动态的选择

discriminator可以很轻松的处理者中类层次关系中数据的映射

使用discriminator的结果处理步骤

MyBatis将会从结果集中取出每条记录,然后比较它的指定鉴别字段的值。

如果匹配任何discriminator中的case,它将使用由case指定的resultMap(resultType)

如果没有匹配到任何case,MyBatis只是简单的使用定义在discriminator块外面的resultMap

如果将映射关系中case后面的值设置为3和4(数据库中只有1和2)

结果如下,仅仅匹配了discriminator外面的部分

0f2aeda65c212093214b569aac211b00.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值