一、概念
1.什么是Mbaytis?
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
2.JDBC
谈及mybatis,必然需要先了解Java和数据库的连接技术——JDBC(Java DataBase Connectivity)。但是原始JDBC操作中,却存在如下缺点:
- 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能。
- sql语句在代码中硬编译,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变Java代码。
- 查询操作时,需要手动将结果集中的数据手动封装到实体中;插入操作时,需要手动将实体的数据设置到sql语句的占位符位置。
如下为原始JDBC操作:
public class JDBCDemo {
public static void main(String[] args) throws Exception{
//获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1","root","root");
//定义sql语句
String sql = "select * from db1.emp";
//获取执行sql的对象Statement
Statement stmt = conn.createStatement();
//执行sql
ResultSet resultSet = stmt.executeQuery(sql);
//处理结果
while(resultSet.next()) {
int id = resultSet.getInt(1); //获取id
String name = resultSet.getString("ename"); //获取姓名
System.out.println("id:" + id + " name:" + name);
}
//释放资源
stmt.close();
conn.close();
}
}
而面对上述缺点,我们的解决方案是:
-
使用数据库连接池技术(C3P0或者Druid)初始化连接资源;
-
将sql语句抽取到xml配置文件中(解耦合);
-
使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射(比较困难)。
因此,mybatis应运而生!
3.实现介绍
每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。
用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或file://开头的url创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。
4.Mybatis特点
-
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
-
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
-
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
-
提供映射标签,支持对象与数据库的ORM字段关系映射。
-
提供对象关系映射标签,支持对象关系组建维护。
-
提供xml标签,支持编写动态sql。
对象
持久化是一种对象服务,就是把内存中的对象保存到外存中,让以后能够取回。需要实现至少3个接口:
- void Save(object o) 把一个对象保存到外存中
- Object Load(object oid) 通过对象标识从外存中取回对象
- boolExists(object oid) 检查外存中是否存在某个对象
为什么需要持久化服务呢?那是由于内存本身的缺陷引起的:
内存掉电后数据会丢失,但有一些对象是无论如何都不能丢失的,比如银行账号,遗憾的是,人们还无法保证内存永不掉电。
内存过于昂贵,与硬盘、磁带、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。所以即使对象不需要永久保存,也会因为内存的容量限制不能一直呆在内存中,需要持久化来缓存到外存。
序列化
序列化也是一种对象服务,就是把内存中的对象序列化成流、或者把流反序列化成对象。需要实现2个接口:
- void Serialize(Stream stream,object o) 把对象序列化到流中
- object Deserialize(Stream stream) 把流反序列化成对象
序列化和持久化很相似,有些人甚至混为一谈,其实还是有区别的,序列化是为了解决对象的传输问题,传输可以在线程之间、进程之间、内存外存之间、主机之间进行。我之所以在这里提到序列化,是因为我们可以利用序列化来辅助持久化,可以说凡是可以持久化的对象都可以序列化,因为序列化相对容易一些(也不是很容易),所以主流的软件基础设施,比如.net和java,已经把序列化的框架完成了。
持久化方案可以分为关系数据库方案、文件方案、对象数据库方案、xml数据库方案,现今主流的持久化方案是关系数据库方案,关系数据库方案不仅解决了并发的问题,更重要的是,关系数据库还提供了持久化服务之外的价值:统计分析功能。刚才我说到,凡是可以序列化的对象都可以持久化,极端的说,我们可以只建立一个表Object(OID,Bytes),但基本上没有人这么做,因为一旦这样,我们就失去了关系数据库额外的统计分析功能。
关系数据库和面向对象之间有一条鸿沟,因为二者模式不匹配,所以就存在一个OR映射问题。
5.ORM
对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换 [1] 。从效果上说,它其实是创建了一个可在编程语言里使用的--“虚拟对象数据库”。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。
对象关系映射(Object-Relational Mapping)提供了概念性的、易于理解的模型化数据的方法。ORM方法论基于三个核心原则:
- 简单:以最基本的形式建模数据。
- 传达性:数据库结构被任何人都能理解的语言文档化。
- 精确性:基于数据模型创建正确标准化的结构。
典型地,建模者通过收集来自那些熟悉应用程序但不熟练的数据建模者的人的信息开发信息模型。建模者必须能够用非技术企业专家可以理解的术语在概念层次上与数据结构进行通讯。建模者也必须能以简单的单元分析信息,对样本数据进行处理。ORM专门被设计为改进这种联系。
简单的说:ORM相当于中继数据。具体到产品上,例如ADO.NET Entity Framework。DLINQ中实体类的属性[Table]就算是一种中继数据。
二、MyBatis的映射文件
1.XML概念
可扩展标记语言(标准通用标记语言的子集)是一种简单的数据存储语言。
使用一系列简单的标记描述数据,而这些标记可以用方便的方式建立,虽然可扩展标记语言占用的空间比二进制数据要占用更多的空间,但可扩展标记语言极其简单易于掌握和使用。
可扩展标记语言(XML)与Access,Oracle和SQL Server等数据库不同,数据库提供了更强有力的数据存储和分析能力,例如:数据索引、排序、查找、相关一致性等,XML的宗旨传输数据的,而与其同属标准通用标记语言的HTML主要用于显示数据。事实上XML与其他数据表现形式最大的不同是:他极其简单。这是一个看上去有点琐细的优点,但正是这点使XML与众不同。
XML的简单使其易于在任何应用程序中读写数据,这使XML很快成为数据交换的唯一公共语言,虽然不同的应用软件也支持其它的数据交换格式,但不久之后他们都将支持XML,那就意味着程序可以更容易的与Windows、Mac OS, Linux以及其他平台下产生的信息结合,然后可以很容易加载XML数据到程序中并分析他,并以XML格式输出结果。
2.XML的发展历程
XML的发展历程大致经历了GML、SGML、HTML、XML4个阶段,如下图所示。
⭐第一阶段:GML(Generalized Markup Language)
GML是IBM的研究人员为了建立一种通用的文档格式,以提高系统的可移植性,与1969年创建的一种通用标记语言。GML是一种IBM格式化文档语言,用于就数据的组织结构、各部件及其之间的关系进行文档描述。GML将这些描述标记为章节、重要小节和次重要小节(通过标题的级来区分)、段落、列表、表等。GML并没有得到广泛的应用,原因是当时计算机发展还处于起步阶段(打孔式),应用场景有限。但是IBM的研究人员提供了一种数据交互的思想。
⭐第二阶段:SGML(Standard Generalized Markup Language)
1985年,IBM研究人员在GML的基础上,进一步完善并规范了GML,形成了SGML。1986年,国际标准化组织(ISO)采纳SGML作为工业标准。SGML曾经被广泛地运用在各种大型的文件计划中,但是SGML是一种非常严谨的文件描述法,导致过于庞大复杂(标准手册就有500多页),难以理解和学习,进而影响其推广与应用。即使SGML的主要供应厂商ArborText研发的产品,也没有百分之百的支持SGML标准。
⭐第三阶段:HTML(HyperText Markup Language)
1993年6月,HTML作为互联网工程工作小组的草案发布。又经过了多轮迭代,包括HTML 2.0,3.2,4.0,4.01,直到2014年10月,W3C将HTML5作为推荐标准。HTML是Web编程的基础,也就是说万维网是建立在超文本基础之上的,网页的本质就是超级文本标记语言。在结合其他的Web技术,可以创造出功能强大的网页。正如前面介绍的,HTML是一种展示型标记语言,是便于肉眼可读的,并不适用于数据交换。
⭐第四阶段:XML(eXtensible Markup Language)
1998年2月,XML正式成为W3C的推荐标准。得益于XML的可读性、可扩展性、可移植性、数显分离、便于存储、便于检索等诸多优点,后期在各行业衍生出了很多语言,包括XHTML(可扩展超文本标记语言)、SVG(可缩放矢量图形语言)、SMIL(同步多媒体综合语言)、HDML(手持设备标记语言)、OEB(开放电子结构规范)等。
3.XML基本语法
XML的语法格式:
第一行用了定义xml文件的,写版本号和字符集
1.注释不能放到第一行
1.必需有声明,声明必需在第一行。
2.文档声明的字符编码必需和文档本身的编码一致
2.标签不能交叉嵌套使用(标签名字随便定义)
3.有开始有结束
4.不数字开头,只能以字母或下划线开头;
5.只能有一个根标签;
6.严格区分大小写
7.一个标签不能有两个相同属性(属性名随便定义)
属性直接写在头标签内,格式:属性名="属性值"
8.特殊符号需要转义 或者可以放到CDATA区 [![CDATA[这里写特殊符号]]] html特殊符号,转义符号
9.编码格式统一:
1.开发环境:项目环境、系统环境
2.当前文件内encoding字符集
10.不能以xml(或者XML,Xml等)开头W3C保留日后使用;
例如:最好不要使用 <xml:xx></xml: xx>W3C保留以后在使用
11.名称字符之间不能有空格或者制表符; 例如<四川 省>
12.名称字符之间不能使用冒号;<xml:xx></xml: xx>
a.语法
XML声明:
<!ATTLIST 元素名字 属性名称 类型 描述>
所有的 XML 元素都必须有一个关闭标签:
在 HTML 中,某些元素不必有一个关闭标签:
<p>This is a paragraph.
<br>
注释:从上面的实例中,您也许已经注意到 XML 声明没有关闭标签。这不是错误。声明不是 XML 文档本身的一部分,它没有关闭标签。
XML 标签对大小写敏感:
XML 标签对大小写敏感。标签 <Letter> 与标签 <letter> 是不同的。
必须使用相同的大小写来编写打开标签和关闭标签:
<Message>这是错误的</message>
<message>这是正确的</message>
注释:打开标签和关闭标签通常被称为开始标签和结束标签。不论您喜欢哪种术语,它们的概念都是相同的。
XML 必须正确嵌套:
在 HTML 中,常会看到没有正确嵌套的元素:
<b><i>This text is bold and italic</b></i>
在 XML 中,所有元素都必须彼此正确地嵌套:
<b><i>This text is bold and italic</i></b>
在上面的实例中,正确嵌套的意思是:由于 <i> 元素是在 <b> 元素内打开的,那么它必须在 <b> 元素内关闭。
实体引用:
在 XML 中,一些字符拥有特殊的意义。
如果您把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。
这样会产生 XML 错误:
<message>if salary < 1000 then</message>
为了避免这个错误,请用实体引用来代替 "<" 字符:
<message>if salary < 1000 then</message>
在 XML 中,有 5 个预定义的实体引用:
< | < | less than |
> | > | greater than |
& | & | ampersand |
' | ' | apostrophe |
" | " | quotation mark |
注释:在 XML 中,只有字符 "<" 和 "&" 确实是非法的。大于号是合法的,但是用实体引用来代替它是一个好习惯。
XML 中的注释:
在 XML 中编写注释的语法与 HTML 的语法很相似。
<!-- This is a comment -->
b.属性类型
- ID :给节点赋一个id号,id号不可以重复
- CDATA :文本型
- IDREF: 引用类型,输入需要引用的元素的ID
- (男|女):要么是男要么是女,也可以设置别的,比如1|2,要么为1要么为2
c.属性描述
- #REQUIRED:必填
- #IMPLED:非必填
- "默认值",只有像(男|女)类型时,描述才可以用默认值的方式。
d.XML文档
XML文档中包含众多的节点。节点分为以下几类:元素节点、属性节点、文本节点、文档节点等,在实际指代中,我们可以省略“节点”二字,也可以将以上各类统称“节点”。
下面代码给出了一个XML文档。
<?xml version="1.0" encoding="UTF-8"?>
<members>
<user type="student">
<id>1</id>
<name>小吴</name>
<shcool>Sunny School</shcool>
</user>
<user type="student">
<id>2</id>
<name>小白</name>
<shcool>Garden School</shcool>
</user>
</members>
文档第一行为XML声明,它声明了XML的版本是1.0,使用的编码是`UTF-8`。
XML中从一个标签开始(含)到一个标签结束(含)的部分叫作元素节点,例如从第一个“`<user>`”到第一个“`</user>`”之间的部分就是一个`user`元素节点。元素节点可以有属性节点,例如“`type="student"`”。元素节点可以包含其他元素节点,例如`user`元素包含了`id`、`name`、`school`这三个元素节点。元素节点中也可以有文本节点,例如第一个`name`元素节点中就包含了文本节点,值为“易哥”。
上述XML中,`members`元素位于最顶层,因此是根元素。每一个XML文档都必须要有一个根元素。
XML文档实际上表述了一棵树。下图展示了上述XML对应的结构树。
从这里可以看出,XML本身就是一种树形结构的表达形式。
4.XML约束
DTD
什么是DTD
DTD的全称为Document Type Definition,是一种文件定义格式。
DTD规定了XML文件结构为XML文件提供了语法与规则。
在DTD中定义XML文件的结构,然后按照DTD的声明来编写XML文件。就好像编程语言中的函数定义,在使用函数时要根据函数声明的格式进行来引用。
PS:简而言之,DTD就是用来约束XML文档的,使其在一定的规范下使用,除了DTD技术外,还有Schema技术,也是用于约束XML文档的。
DTD声明语句
在XML中加入DTD声明 <!DOCTYPE root[]>,root---->根元素
元素分类
<!ELEMENT element-name (#PCDATA)>:文本元素
<!ELEMENT name (#PCDATA)>
<!ELEMENT element-name EMPTY>:空元素
<!ELEMENT br EMPTY>
<!ELEMENT element-name (e1,e2)>:混合元素
<!ELEMENT contact (phone?,email?)>
代码示例:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 标签约束 -->
<!DOCTYPE persons[
<!-- 给每一个标签名添加约束 -->
<!ELEMENT persons (person+)>
<!ELEMENT person (name,age,contact,br?)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT contact (phone|email)>
<!ELEMENT phone (#PCDATA)>
<!ELEMENT email (#PCDATA)>
<!ELEMENT br EMPTY>
]>
<!--
建立XML约束:首先得分析xml结构
1.根标签persons
2.persons中有子标签就是混合标签
3.person中的子标签(混合标签) name age contact br
4.name age没有子标签,有内容(文本标签)
5.contact混合标签
6.phone 文本标签
7.br 既没有子标签也没有文本内容(空标签)
8.email 文本标签
<!DOCTYPE root [...]>
ctrl+shift+x 将小写改为大写
-->
<persons>
<person>
<name>张小明</name>
<age>10</age>
<contact>
<phone>1234567</phone>
</contact>
<br/>
</person>
<person>
<name>张大明</name>
<age>35</age>
<contact>
<email>123@qq.com</email>
</contact>
</person>
</persons>
Schema
XML全称为Extensible Markup Language,中文名为:可扩展标记语言。 而其一个重要的特点就是:可拓展。而可拓展就是通过XML Schema实现的。
Schema就是架构、结构的意思。所以,XML Schema就是描述XML结构的语言。
XML可扩展的一个重要表现就是XML文档的结构是可以自由定义的。定义XML文档可以使用XML Schema,此类文件的后缀名为xsd。此外,也可以使用DTD(Document Type Definition,即:文档类型定义,这类文件的后缀为dtd)。
上面的d.XML文档中,可以设置members、user的标签,以及每个标签可以设定什么属性值,都是定义好的。不是随便给定的。而设定这些标签、属性、类型等,就是通过XML Schema或者DTD。
以XML Schema文档为例,我们可以使用下面的代码来定义上述XML片段。
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="XML Schema">
<xs:element name="members">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="user">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:unsignedByte" />
<xs:element name="name" type="xs:string" />
<xs:element name="school" type="xs:string" />
</xs:sequence>
<xs:attribute name="type" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
所以,XML Schema就是XML可拓展的重要体现,因为通过XML Schema我们可以自由定义一个XML的结构关系、节点类型、节点属性等等。
5.XML解析
所有现代浏览器都内建了供读取和操作 XML 的 XML 解析器。解析器把 XML 转换为 XML DOM 对象 - 可通过 JavaScript 操作的对象。
DOM、SAX都是一组解析XML文档的规范,其实就是接口,这说明需要有实现者能使用,而解析器就是对DOM、SAX的实现。一般解析器都会实现DOM、SAX两个规范。
-
Crimson(sun):JDK1.4之前,Java使用的解析器。性能效差。
-
Xerces(IBM):IBM开发的DOM、SAX解析器,现在已经由Apache基金会维护,是当前最为流行的解析器之一。在1.5之后,已经添加到JDK之中,也是JAXP的默认使用解析器,但不过在JDK中的包名与Xerces不太一样。例如:org.apache.xerces包名改为了com.sun.org.apache.xerces.internal包名,也就是说JDK1.5中的Xerces是被包装后的XML解析器,但二者区别很小。
-
Aelfred2(dom4j):DOM4J默认解析器,当DOM4J找不到解析器时会使用他自己的解析器。
Dom
使用DOM要求解析器把整个XML文档装载到一个Document对象中。Document对象包含文档元素,即根元素,根元素包含N个子元素。
一个XML文档解析后对应一个Document对象,这说明使用DOM解析XML文档方便使用,因为元素与元素之间还保存着结构关系。
优先:使用DOM,XML文档的结构在内存中依然清晰。
缺点:如果XML文档过大,那么把整个XML文档装载进内存,可能会出现内存溢出的现象。
DOM解析XML文件步骤
1.创建解析器工厂对象
2.解析器工厂对象创建解析器对象
3.解析器对象指定XML文件创建Document对象
4.以Document对象为起点操作DOM树
对XML文件进行添加元素,删除元素,修改元素,查找元素,保存修改后的XML文件这五项操作,涉及Document接口,DocumentBuilderFactory类,DocumentBuilder类,Element接口,Node接口,NodeList接口,TransformerFactory类,Transformer类,DOMSource类,StreamResult类。通过对象调用涉及以上接口,类的方法来执行对XML文件的操作
Dom4j
什么是Dom4j
Dom4j 是 sourceforge.net 上一个开源的 Java 项目,主要用于操作 XML 文档,如创建 XML 文档和解析 XML 文档。dom4j 应用于 Java 平台,是一款优秀的 XML 解析器,它具有性能优异、功能强大和易使用等特点。目前,多数 Java 产品中解释 XML 数据都是使用 dom4j 技术来完成的。
注意:项目中使用Dom4j需要先下载Dom4j的jar包添加至项目构建路径,否则无法使用
DOM4j解析XML文件
DOM4J 最大的特色是使用大量的接口。它的主要接口都在org.dom4j里面定义
- Document:定义XML文档
- Element:定义XML元素
- Text:定义XML文本节点
- Attribute:定义了XML 的属性
JDK中是没有DOM4j解析XML文件所需的类和接口,需要从网页下载,网址如下:https://dom4j.github.io/#parsing
实现步骤
解析XML步骤: 1. 在项目下创建一个lib文件夹folder 2. 复制dom4j的jar包到lib文件夹中 3. 选中jar右键buildpath 4. 在测试类中使用dom4j解析XML。(今天开始学习别人的api)
dom4j中使用到的方法: 1.创建SaxReader对象 SAXReader reader = new SAXReader();
*******************************这是使用约束的用法************************************ 2.设置命名空间 (如果XML使用约束且属性elementFormDefault值是:qualified,则必须要设置)【简化自定义命名空间使用过程】 HashMap<String, String> hashMap = new HashMap<String, String>(); map.put("myKey", "自定义命名空间名");// key的名字自己取 reader.getDocumentFactory().setXPathNamespaceURIs(map); *******************************************************************
3. SAXReader对象调用read方法,将当前XML文件,转换为Document对象 获取字节流FileReader 用缓冲流包装一下BufferedReader Document document = reader.read(缓冲流);
4. 获取根节点 root = document.getRootElement();
5. 通过父签添加子标签(元素) Element element = root.addElement("标签名");//返回值就是要添加的元素对象
给子标签添加值 element.setText("标签值");
6. 给当前标签添加属性:xxx ,值是:xxx Element attribute = linkman.addAttribute("属性名","值");
通过当前元素获取属性对象 element.attribute("属性名");
通过属性对象attribute获取属性值 attribute.setText("值");
通过属性对象attribute获取属性值 String 值 = attribute.getText();
7. 获取当前元素标签名 String name = e.getName();
获取当前元素标签值 String text = e.getText();
8. 获取指定名字的子标签(元素) 当前标签.element(String name);
9. 获取所有子标签(元素) 当前标签.elements();
10. 删除子元素,必须通过父元素remove(子元素对象)完成 父元素.remove(子元素对象);
10.在dom4j里面提供了两个方法,用来支持xpath selectNodes("xpath表达式"),获取当前名字的多个节点 selectSingleNode("xpath表达式"),获取一个节点 【注意:】 在多层级的xpath使用的语法://命名空间:a[@id='b1']/命名空间:元素
|
查询元素
需求:查询id="69"的linkman的name值
public class XmlParseTest1 {
@Test // 查询id="69"的linkman的name值
public void testGet() throws Exception {
// 1.创建SaxReader对象
SAXReader reader = new SAXReader();
// 2.用自动关流语法,创建字符流对象和缓冲流对象
try (
FileReader fr = new FileReader("src/cn/itsource/parse/contacts.xml");
BufferedReader buff = new BufferedReader(fr);
) {
// 3.SAXReader对象调用read方法,将当前XML文件,转换为Document对象
Document document = reader.read(buff);
// System.out.println(document);
// 4.获取根节点, 通过document对象调用方法:getRootElement();
Element rootElement = document.getRootElement();
// System.out.println(rootElement);
// 5.通过当前根标签获取所有下一级子标签,返回一个集合,调用方法:elements()
List<Element> elements = rootElement.elements(); // 为了避免强制转换,手动加上泛型Element
// System.out.println(elements);
/*// 6.遍历集合elements
for (Element linkman : elements) {
// 7.通过linkman获取id属性,调用方法:attribute("属性名")
Attribute idAttribute = linkman.attribute("id");
// 8.通过属性对象idAttribute再获取属性值,调用方法:getText()
String idText = idAttribute.getText();
if ("69".equals(idText)) {
// 9.再通过linkman获取下一级元素对象,调用方法:element("元素名");
Element nameElement = linkman.element("name");
// 10.通过nameElement获取值,调用方法:getText()
System.out.println(nameElement.getText());
}
}
用stream流优化代码
*/
Stream<Element> stream = elements.stream();
/*stream.filter(new Predicate<Element>() {
@Override
public boolean test(Element t) { // t当前遍历到的Element元素
return "69".equals(t.attribute("id").getText());
}
}) // 将满足条件的Element装到stream流中
.forEach(new Consumer<Element>() {
@Override
public void accept(Element t) {
System.out.println(t.element("name").getText());
}
});
lambda优化
*/
stream.filter(t -> "69".equals(t.attribute("id").getText())) // 将满足条件的Element装到stream流中
.forEach(t -> System.out.println(t.element("name").getText()));
}
}
}
使用xpath查询元素
使用dom4j支持xpath的操作的几种主要形式【提高查询元素的效率的】 xpath表达式: 第一种形式:/a/b/c: 表示一层一层的,a下面 b下面的c 第二种形式://b: 表示和这个名称相同,表示只要名称是b,都得到 第三种形式:/* : 所有元素 第四种形式:a[1]:表示第一个a元素;a[last()]:表示最后一个a元素 第五种形式://a[@id]: 表示只要a元素上面有id属性,都得到 第六种形式: //自定义命名空间:a[@id='b1'] 表示元素名称是a,在a上面有id属性,并且id的属性值是b1【如果使用约束文件,则写自定义命名空间】 //a[@id='b1'] 表示元素名称是a,在a上面有id属性,并且id的属性值是b1【如果不使用约束文件】
如果查询id属性值是b1的a元素下一级元素d,则可以这样写: //自定义命名空间:a[@id='b1']/自定义命名空间:d 表示元素名称是a,在a上面有id属性,并且id的属性值是b1【使用了约束文件,且属性值是:qualified】 //a[@id='b1']/d 表示元素名称是a,在a上面有id属性,并且id的属性值是b1【如果不使用约束文件】
使用dom4j支持xpath具体操作 默认的情况下,dom4j不支持xpath,如果想要在dom4j里面是有xpath, 第一步需要,引入支持xpath的jar包:jaxen-1.1-beta-6.jar 在dom4j里面Xpath解析,提供了两个方法,用来支持xpath selectNodes("xpath表达式"),获取多个节点 selectSingleNode("xpath表达式"),获取一个节点 具体使用哪一个方法,要根据具体的业务需求。 |
代码示例:
public class XmlParseTest2 {
@Test // 需求:查询id="69"的linkman的name值【xml没有约束文件】
public void testGet() throws Exception {
// 1.创建SaxReader对象
SAXReader reader = new SAXReader();
// 2.用自动关流语法,创建字符流对象和缓冲流对象
try (
FileReader fr = new FileReader("src/cn/itsource/parse/contacts.xml");
BufferedReader buff = new BufferedReader(fr);
) {
// 3.SAXReader对象调用read方法,将当前XML文件,转换为Document对象
Document document = reader.read(buff);
// System.out.println(document);
// 4.获取根节点, 通过document对象调用方法:getRootElement();
Element rootElement = document.getRootElement();
// System.out.println(rootElement);
/*
* 5.直接使用xpath解析查询
* 在dom4j里面提供了两个方法,用来支持xpath
selectNodes("xpath表达式"),获取当前名字的多个节点
selectSingleNode("xpath表达式"),获取一个节点
【注意】在多层级的xpath使用的语法://a[@id='b1']/命名空间:元素
*/
Node nameElement = rootElement.selectSingleNode("//linkman[@id='69']/name");
System.out.println(nameElement.getText());
}
}
}
修改元素
需求:修改id="69"的linkman的address值为"九眼桥某会所"【xml没有约束文件】
@Test
public void testUpdate() throws Exception {
// 1.创建SaxReader对象
SAXReader reader = new SAXReader();
// 2.用自动关流语法,创建字符流对象和缓冲流对象
try (
FileReader fr = new FileReader("src/cn/itsource/parse/contacts.xml");
BufferedReader buff = new BufferedReader(fr);
) {
// 3.SAXReader对象调用read方法,将当前XML文件,转换为Document对象
Document document = reader.read(buff);
// 4.获取根节点, 通过document对象调用方法:getRootElement();
Element rootElement = document.getRootElement();
/*
* 5.直接使用xpath解析查询
* 在dom4j里面提供了两个方法,用来支持xpath
selectNodes("xpath表达式"),获取当前名字的多个节点
selectSingleNode("xpath表达式"),获取一个节点
【注意】在多层级的xpath使用的语法://a[@id='b1']/命名空间:元素
*/
Node addressElement = rootElement.selectSingleNode("//linkman[@id='69']/address");
// 6.给addressElement标签修改值,调用方法:setText("标签值");
addressElement.setText("九眼桥某会所"); // 这是修改的内存中文档树的数据,而xml文件并未修改
// 7.创建字符流,参数就是xml文件路径
FileWriter fileWriter = new FileWriter("src/cn/itsource/parse/contacts.xml");
// 8.创建XMLWriter对象,将fileWriter作为参数
XMLWriter xmlWriter = new XMLWriter(fileWriter);
// 9.XMLWriter对象调用write方法,将document写出到硬盘文件
xmlWriter.write(document);
// 10.关闭xmlWriter、字符流也要关闭
xmlWriter.close();
// 11.关闭fileWriter
fileWriter.close();
System.out.println("修改成功!");
}
}
添加元素和删除元素【代码优化】
我们发现增删改查中有大量相同代码,所以将相同代码提取到@Before和@After方法中,优化代码。
public class XmlParseTest3 {
/** 为了让下面测试方法可以访问document和根标签rootElement对象,所以声明为成员变量,在before中赋值 */
private Document document;
private Element rootElement;
@Before
public void before() throws Exception {
// 将重复的增删改查中代码抽取到预执行方法中
// 1.创建SaxReader对象
SAXReader reader = new SAXReader();
// 2.用自动关流语法,创建字符流对象和缓冲流对象
try (
FileReader fr = new FileReader("src/cn/itsource/parse/contacts.xml");
BufferedReader buff = new BufferedReader(fr);
) {
// 2.设置命名空间 (如果XML使用约束且属性elementFormDefault值是:qualified,则必须要设置)【简化自定义命名空间使用过程】
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("myKey", "http://itsource.cn"); // key的名字自己取
reader.getDocumentFactory().setXPathNamespaceURIs(hashMap); // 表示,开启了map简化代码支持,即可以通过key代替自定义命名空间名
// 3.SAXReader对象调用read方法,将当前XML文件,转换为Document对象
document = reader.read(buff);
// System.out.println(document);
// 4.获取根节点, 通过document对象调用方法:getRootElement();
rootElement = document.getRootElement();
// System.out.println(rootElement);
}
}
@After
public void after() throws Exception {
// 将重复的增删改中代码【将内存中document写到硬盘代码】抽取到后执行方法中
// 6.获取一个OutputFormat对象,OutputFormat调用createPrettyPrint方法
OutputFormat format = OutputFormat.createPrettyPrint();
// 7.创建字符流,参数就是xml文件路径
FileWriter fileWriter = new FileWriter("src/cn/itsource/parse/contacts.xml");
// 8.创建XMLWriter对象,将fileWriter作为第一个参数,format作为第二个参数
XMLWriter xmlWriter = new XMLWriter(fileWriter, format);
// 9.XMLWriter对象调用write方法,将document写出到硬盘文件
xmlWriter.write(document);
// 10.关闭xmlWriter、字符流也要关闭
xmlWriter.close();
// 11.关闭fileWriter
fileWriter.close();
System.out.println("修改成功!");
}
@Test // 需求:查询id="69"的linkman的name值【xml没有约束文件】
public void testGet() throws Exception {
Node nameElement = rootElement.selectSingleNode("//linkman[@id='69']/name");
System.out.println(nameElement.getText());
}
@Test // 需求:修改id="69"的linkman的address值为"九眼桥某会所"【xml没有约束文件】
public void testUpdate() throws Exception {
Node addressElement = rootElement.selectSingleNode("//linkman[@id='69']/address");
// 6.给addressElement标签修改值,调用方法:setText("标签值");
addressElement.setText("九眼桥某会所2"); // 这是修改的内存中文档树的数据,而xml文件并未修改
}
@Test // 需求:添加id="5269"的linkman,name="某柏成",email="chengdu@cold.com", address="九眼桥某会所", group="某电影公司"【xml没有约束文件】
public void testAdd() throws Exception {
/*
* 5. 通过父签添加子标签(元素)
Element element = root.addElement("标签名");//返回值就是要添加的元素对象
*/
Element linkman = rootElement.addElement("linkman");
// 用linkman添加属性id,调用方法:addAttribute("属性名", "属性值")
linkman.addAttribute("id", "5269");
// 用linkman添加下一级标签,调用方法:addElement("标签名"),setText("值")
linkman.addElement("name").setText("某柏成");
linkman.addElement("email").setText("chengdu@cold.com");
linkman.addElement("address").setText("九眼桥某会所");
linkman.addElement("group").setText("某电影公司");
}
@Test // 需求:删除id="5269"的linkman【xml没有约束文件】
public void testRemove() throws Exception {
/*
* 10. 删除子元素,必须通过父元素remove(子元素对象)完成
父元素.remove(子元素对象);
*/
// 先查询到id="5269"的Element对象
Node linkman = rootElement.selectSingleNode("//linkman[@id='5269']");
// 通过父标签调用remove方法,传入当前要删除的标签linkman
rootElement.remove(linkman);
}
// ============================================= 华丽的分割线 【使用约束文件】==================================================
@Test // 需求:查询id="69"的linkman的name值【xml使用约束文件】
public void testGet2() throws Exception {
// 使用约束文件后,在使用xpath解析的时候,只需要在每一个标签名前加上: map的key:即可
Node nameElement = rootElement.selectSingleNode("//myKey:linkman[@id='69']//myKey:name");
System.out.println(nameElement.getText());
}
}
6.Mybatis的XML
XML结构
dtd标签
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
根标签
<mapper namespace="userMapper">
字标签
<select id="findAll" resultType="com.study.domain.User">
三、Mybatis
1.功能架构
我们把Mybatis的功能架构分为三层:
(1) API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
(2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
(3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
2.框架架构
框架架构讲解:
(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
3.生命周期和作用域
生命周期、作用域是至关重要的,因为错误的使用会导致非常严重的并发问题
- SqlSessionFactoryBuilder
- 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了
- 局部变量
- SqlSessionFactory
- 可以想象成连接池
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
- SqlSessionFactory的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式
SqlSession
1.连接到的一个请求,可以想象成数据库连接池中的一个请求。
2.SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
3.用完之后需要赶紧关闭,否则资源被占用!(类似于数据库连接池的回收)
4.动态SQL
动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句也就是复杂的SQL语句。
官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
常用语句
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
动态SQL之 <
where>
where元素可以自动识别拼接的SQL语句,像是and、or等,会智能的选择加上或者去除。
例:如下代码,假设不满足第一个if,满足第二个if,则拼接的语句应该select * from blog where and author = #{author}
。很显然多了一个and,此时where元素会自动去掉and,使得sql语句正确。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
动态SQL之 <if>
我们根据实体类的不同取值,使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询,如果username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
<select id="findByCondition" parameterType="user" resultType="user">
select * from User
<where>
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
</where>
</select>
当查询条件id和username都存在时,控制台打印的sql语句如下:
… … …
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
condition.setUsername("lucy");
User user = userMapper.findByCondition(condition);
… … …
当查询条件只有id存在时,控制台打印的sql语句如下:
… … …
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User condition = new User();
condition.setId(1);
User user = userMapper.findByCondition(condition);
… … …
动态SQL之 <foreach>
循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。
<select id="findByIds" parameterType="list" resultType="user">
select * from User
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="array" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
测试代码片段如下:
… … …
//获得MyBatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int[] ids = new int[]{2,5};
List<User> userList = userMapper.findByIds(ids);
System.out.println(userList);
… … …
<foreach>
标签用于遍历集合,它的属性:
- collection:代表要遍历的集合元素,注意编写时不要写#{}
- open:代表语句的开始部分
- close:代表结束部分
- item:代表遍历集合的每个元素,生成的变量名
- sperator:代表分隔符
动态SQL之 <
choose、when、otherwise>
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,这里其实类似于java的Switch语句,用Switch来理解会很容易
例:下面代码只会查询到满足一种条件的sql,等同于三种情况sql
- select * from blog where title = #{title}
- select * from blog where author = #{author}
- select * from blog where views = #{views}
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
动态SQL之 <
set>
如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?也类似于where元素,可以自动识别逗号,动态的保留或者去掉。
例:下面代码若满足第一个if,不满足第二个if,则set元素会自动去掉逗号,sql语句变成update blog title = #{title}
<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id};
</update>
SQL片段抽取
Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。
<!--抽取sql片段简化编写-->
<sql id="selectUser" select * from User</sql>
<select id="findById" parameterType="int" resultType="user">
<include refid="selectUser"></include> where id=#{id}
</select>
<select id="findByIds" parameterType="list" resultType="user">
<include refid="selectUser"></include>
<where>
<foreach collection="array" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
XML配置参数
typeHandlers标签
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器(截取部分)。
类型处理器 | Java类型 | JDBC类型 |
BooleanTypeHandler | java.lang.Boolean,boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte, byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short, short | 数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer, int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long, long | 数据库兼容的 NUMERIC 或 LONG INTEGER |
FloatTypeHandler | java.lang.Float, float | 数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler | java.lang.Double,double | 数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler | java.lang.String | CHAR, VARCHAR |
ClobReaderTypeHandler | java.io.Reader | - |
ClobTypeHandler | java.lang.String | CLOB, LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR, NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | - |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB, LONGVARBINARY |
DateTypeHandler | java.util.Date | TIMESTAMP |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
SqlDateTypeHandler | java.sql.Date | DATE |
SqlTimeTypeHandler | java.sql.Time | TIME |
ObjectTypeHandler | Any | OTHER 或未指定类型 |
EnumTypeHandler | Enumeration Type | VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称) |
开发步骤
- 定义转换类继承类BaseTypeHandler
- 覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时mysql的字符串类型转换成 java的Type类型的方法
- 在MyBatis核心配置文件中进行注册
plugins标签
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
开发步骤:
- 导入通用PageHelper的坐标
- 在mybatis核心配置文件中配置PageHelper插件
- 测试分页数据获取
导入通用PageHelper坐标:
<!-- 分页助手 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
在mybatis核心配置文件中配置PageHelper插件:
<!-- 注意:分页助手的插件 配置在通用馆mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
测试分页代码实现:
@Test
public void testPageHelper(){
//设置分页参数
PageHelper.startPage(1,2);
List<User> select = userMapper2.select(null);
for(User user : select){
System.out.println(user);
}
}
获得分页相关的其他参数
//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(select);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());
System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否最后一页:"+pageInfo.isIsLastPage());
5.Mybatis多表查询
一对一查询
一对一查询的模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
一对一查询的语句
-- 对应的sql语句:
select * from orders o,user u where o.uid=u.id;
查询结果如下:
代码示例
创建Order和User实体
public class Order {
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个客户
private User user;
}
public class User {
private int id;
private String username;
private String password;
private Date birthday;
}
创建OrderMapper接口
public interface OrderMapper {
List<Order> findAll();
}
配置OrderMapper.xml
<mapper namespace="com.itheima.mapper.OrderMapper">
<resultMap id="orderMap" type="com.itheima.domain.Order">
<result column="uid" property="user.id"></result>
<result column="username" property="user.username"></result>
<result column="password" property="user.password"></result>
<result column="birthday" property="user.birthday"></result>
</resultMap>
<select id="findAll" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
</select>
</mapper>
其中<resultMap>还可以配置如下:
<resultMap id="orderMap" type="com.itheima.domain.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.itheima.domain.User">
<result column="uid" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
测试结果
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Order> all = mapper.findAll();
for(Order order : all){
System.out.println(order);
}
一对多查询
一对多查询的模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对多查询的
需求:查询一个用户,与此同时查询出该用户具有的订单
一对多查询的语句
-- 对应的sql语句:
select *,o.id oid from user u left join orders o on u.id=o.uid;
查询的结果如下:
代码示例
修改User实体
public class Order {
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个客户
private User user;
}
public class User {
private int id;
private String username;
private String password;
private Date birthday;
//代表当前用户具备哪些订单
private List<Order> orderList;
}
创建UserMapper接口
public interface UserMapper {
List<User> findAll();
}
配置UserMapper.xml
<mapper namespace="com.itheima.mapper.UserMapper">
<resultMap id="userMap" type="com.itheima.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="orderList" ofType="com.itheima.domain.Order">
<result column="oid" property="id"></result>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
</select>
</mapper>
测试结果
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAll();
for(User user : all){
System.out.println(user.getUsername());
List<Order> orderList = user.getOrderList();
for(Order order : orderList){
System.out.println(order);
}
System.out.println("----------------------------------");
}
多对多查询
多对多查询的模型
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用多对多查询的
需求:查询用户同时查询出该用户的所有角色。
多对多查询的语句
-- 对应的sql语句:
select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;
查询的结果如下:
代码示例
创建Role实体,修改User实体
public class User {
private int id;
private String username;
private String password;
private Date birthday;
//代表当前用户具备哪些订单
private List<Order> orderList;
//代表当前用户具备哪些角色
private List<Role> roleList;
}
public class Role {
private int id;
private String rolename;
}
添加UserMapper接口方法
List<User> findAllUserAndRole();
配置UserMapper.xml
<resultMap id="userRoleMap" type="com.itheima.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<collection property="roleList" ofType="com.itheima.domain.Role">
<result column="rid" property="id"></result>
<result column="rolename" property="rolename"></result>
</collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
select u.*,r.*,r.id rid from user u left join user_role ur on u.id=ur.user_id
inner join role r on ur.role_id=r.id
</select>
测试结果
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAllUserAndRole();
for(User user : all){
System.out.println(user.getUsername());
List<Role> roleList = user.getRoleList();
for(Role role : roleList){
System.out.println(role);
}
System.out.println("----------------------------------");
}
四、Mybatis缓存
1.什么是缓存
缓存通俗的说就是一个临时存储区域,因为每次执行sql语句时,都需要去访问数据库,当访问量成千上万的时候,会出现访问缓慢等问题,但假若设置一个缓存区,执行sql返回的结果集,先暂存到一个临时区,下次再用就不需要去访问数据库,可以直接从临时区获取,这个临时区就是缓存区。
举个例子,你有一个保险箱,书包放在里面,近三天你需要使用书包,每次使用书包的时候,你都需要输入保险箱密码,把书包拿出来,用完打开保险箱把书包放回去。但你可以第一次从保险箱把书包拿出来后,先暂时放到桌上,等三天后,再把书包放回保险箱,这样会方便许多。
2.一级缓存
MybatisUtils.getSqlSession()就是一级缓存,每当使用的时候就默认自动打开
当使用 MybatisUtils.getSqlSession().close(),缓存区关闭,数据清除。
3.二级缓存
二级缓存需要手动开启,在SQL 映射文件中添加一行
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>