MyBatis
三层架构回顾

MyBatis简介
历史层面:
Ø MyBatis的前身是iBatis。
Ø iBatis是Apache在2001年发起的开放源代码项目。
Ø 2010年这个项目由Apache迁移到了Google,并改名为MyBatis。
Ø 2013年迁移到Github。
技术层面:
Ø 简单易学、功能强大。
Ø MyBatis对JDBC进行了封装,简化了原生JDBC繁杂的操作。
Ø MyBatis是基于ORM实现的持久层框架。
Ø 支持动态SQL:根据不同的查询条件,执行不同的SQL。
Ø SQL语句可以写在xml文件中,和源码分离,提高了可维护性。
MyBatis帮助文档:
https://mybatis.org/mybatis-3/zh/index.html
ORM简介
什么是ORM:
ORM: Object Relation Mapping,对象关系映射
Object: 实体类、数据库表
Relation:关系
Mapping:映射

为什么要使用ORM:
在类中的属性名和表中的字段名一致的情况下,可以使用反射技术来统一对JDBC进行操作,简化繁杂的JDBC操作
但是,当属性名和字段名不一致时,就需要使用ORM来指定他们的对应关系,以便反射能够更好的操作JDBC
MyBatis快速入门
搭建开发环境
maven环境配置
VM options for importer:-Xms1024m -Xmx2048m
VM Potions:-DarchetypeCatalog=local



导入xml代码提示模板


idea编码设置

新建项目



新建子模块






导入依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
新建实体类
建库、建表
编码步骤
1、 在resources下创建MyBatis的主配置文件mybatis-config.xml

2、 在主配置文件中配置日志输出和环境(数据源)
<?xml version="1.0" encoding="UTF-8"?>
<!--配置环境(数据源)--> <environments default="development"> <environment id="development"> <!--事务管理器--> <transactionManager type="JDBC"/> <!--带连接池的数据源--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/ssm"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> </configuration>
3、 配置MyBatis的映射文件
实体类 <===> 数据库表
SQL语句都写在映射文件中
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="product">
<insert id="insert"> insert into product values(null, #{name}, #{tid}, #{price}, #{num}, #{imgurl}, #{description}) </insert> <update id="update"> update product set name=#{name}, tid=#{tid},price=#{price},num=#{num}, imgurl=#{imgurl}, description=#{description} where id=#{id} </update> <delete id="delete"> delete from product where id=#{id} </delete> </mapper>
4、 在主配置文件中注册映射文件(注意:如果放在包下,则路径中不是点,而是**/**)

<?xml version="1.0" encoding="UTF-8"?>
5、 编写java代码,使用单元测试
1) 使用MyBatis给我们提供的工具类读取MYBatis的主配置文件
2) 创建SqlSessionFactory,可以理解为创建连接池
3) 获取sqlSession,可以理解为获取连接对象
4) 通过sqlSession对象查询映射文件中的SQL来执行,并获取结果
5) MyBatis对事务默认是手动提交,因此对于增删改操作,需要提交事务
public class TestProduct { @Test public void testGet() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 链接的工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取连接 SqlSession sqlSession = sqlSessionFactory.openSession(); Product product = (Product)sqlSession.selectOne("product.get", 50); System.out.println(product); }
@Test public void testInsert() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 链接的工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取连接 SqlSession sqlSession = sqlSessionFactory.openSession();
Product product = new Product(); product.setName("测试MyBatis插入操作");
sqlSession.update("product.insert", product); sqlSession.commit(); // 提交事务 } }
MyBatis结合DAO
方式一:接口和实现类
\1. 编写dao接口

\2. 编写接口的实现类

\3. 在Service层调用时跟之前一样

方式二:接口和代理类(重点)

使用MyBatis创建接口的代理类,并通过接口来调用方法

MyBatis的工具类抽取
public class MyBatisUtils { private static String resource = "mybatis-config.xml"; private static SqlSessionFactory sqlSessionFactory; // 将连接对象SqlSession和当前线程进行绑定 private static final ThreadLocal<SqlSession> TL = new ThreadLocal<>(); // 初始化 sqlSessionFactory static { try { InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } }
// 链接的工厂对象 public static SqlSession openSession() { // 从线程对象中获取链接 SqlSession sqlSession = TL.get(); if (sqlSession == null) { // 说明当前线程是第一次调用该方法 sqlSession = sqlSessionFactory.openSession(); // 将连接对象和当前线程绑定 TL.set(sqlSession); } return sqlSession; }
public static void commit() { openSession().commit(); }
// 释放资源 public static void release() { // 不是真正的关闭,而是将链接返还给连接池 openSession().close(); TL.remove(); } }
MyBatis的简单查询
查询所有
映射文件


resultMap★★★★★
当类中的属性名和表中的字段名不一致时,需要手动映射,如下所示
方式1:给字段起别名,如果有很多处查询,维护起来不方便,不可取
<select id=**"getAll"** resultType=**"com.bjpowernode.domain.User"> select id,name username,** #给字段起别名 password, nickname, telephone, photo from user </select>
方式2:使用resultMap
resultMap可以映射属性和字段的对应关系

<!-- resultMap: 将类中的属性名和表中的字段建立映射关系
属性名和字段名一致时,可以不指定映射关系,但最好所有字段全部建立映射 --> <resultMap id="userMap" type="com.bjpowernode.domain.User"> <!-- property: 类中的属性名 column:表中的字段名 --> <id property="id" column="id" /> <result property="username" column="name" /> <result property="password" column="password" /> <result property="nickname" column="nickname" /> <result property="telephone" column="telephone" /> <result property="photo" column="photo" /> </resultMap>
方法只有一个参数
参数为简单类型时
当参数有且只有一个简单类型时,xml中的#{}中的内容可以随便写,但是最好还是写成参数名(见名知意)!

参数为复杂类型时
#{}中直接写对象中的属性名!

方法中有多个参数


模糊查询
方式1:(不推荐,原因是业务层写SQL了,不满足低耦合、高内聚)


方式2(推荐)


方式3:(不推荐,${}是字符串的拼接,存在SQL注入的风险)


${}和#{}的区别(面试题)
#{} 是占位符 可以防止SQL注入,会判断值的类型:如果是字符串,两端自动加上引号
${} 字符串的拼接符,存在SQL注入的风险,不会判断值的类型,原样输出
获取插入操作时生成的主键
需求:在插入操作之后,获取到生成的ID值。
方式1:


注意!该方式仅适用于支持主键自增的数据库,像Oracle数据库,它的主键是通过序列来维护的,因此这种方式不适用Oracle数据库
方式2:
以下方式适用于所有数据库

动态SQL
什么是动态SQL
动态SQL一般是针对查询而言的,所谓动态SQL,就是条件不同,执行的SQL语句不同
如何产生动态SQL
在映射文件中,提供了以下标签支持动态SQL:
• if标签:用于判断
• foreach标签:用于循环
格式化标签:
• where标签
• set标签(用于修改操作)
if标签★★★★★★
不固定参数查询(重点)★★★★★

还可以通过where标签来完成SQL的格式化

不固定参数更新(了解)

使用set标签进行格式化,注意,当没有任何满足的条件时,sql中没有set关键字,报SQL语法错误!

foreach标签★★★★
通常用于批量操作。
使用参数的默认名称


通过@Param指定参数名称



多表链接查询★★★★★★★★★★★
常见的表关系:
一对多:
订单和用户,一个用户可以下多个订单,但是一个订单只能属于一个
用户和收货地址:一个用户可以添加多个收货地址
商品类型和商品:一个类型,包括多种商品,一个商品属于一个类型
多对多:
用户和商品:一个用户可以购买多个商品,一个商品可以被多个用户购买。
用户和角色:一个用户可以拥有多个角色,一个角色可以被多个用户拥有。
角色和权限:一个角色可以拥有多个权限,一个权限可以被多个角色拥有。
通常,多对多的关系,都需要第三张表来维护,这个第三张表中会包含两种表中的主键
用户表:
id 名称
1 张三
2 李四
角色表:
Id 名称
1 普通员工
2 经理
3 组长
用户角色表:
Id 用户id 角色id
1 1 1
2 1 2
3 2 1
4 2 2
5 2 3
…
一对一(特殊的一对多关系)
用户和身份证
在真是的开发种,一对一的关系不常见,80%以上的情况都是一对多!
学生表:

成绩表

需求:查询学生成绩,显示格式:姓名 成绩
常见查询方式回顾:
连接查询:
交叉(自然)连接查询:select * from a,b 笛卡尔积,即结果的条数为a表中的记录数乘以b表中的记录数
select a.name,b.score from student a,score b where a.id=b.sid

内连接:select s1.name,s2.score from student [as] s1 [inner] join score s2 on s1.id=s2.sid
特点:取两张表种的交集

问题:未参加考试的学生没有出现在结果中!
需求:未参加考试的学生,也应该出现在结果中,成绩显示为0或缺考
外连接
左外连接:以左表为主,左表中的数据会被全部查询处来,右表中不存在关联数据也会被查询出来,
只不过,没有被关联的数据显示null
select s1.name,s2.score from student s1 left join score s2 on s1.id=s2.sid

右外连接:以右表为主关联查询,即便左表中没有关联的数据,也会被查询出来,只不过,没有被关联的数据显示null
左外连接的查询,右外连接也可以完成,左外连接比较常用,更符合惯用思维
一对多查询(以少查多)
添加类型实体类



多对一查询(以多查少)
在查询出商品的时候,同时关联查询出对应的类型



多表嵌套查询**★★★**
什么叫嵌套查询
嵌套查询(nesting query)又叫做分步**(分多次)**查询。
比如查询所有商品,同时查询商品对应的类别。
连接查询:select p.*,t.id t_id,t.name t_name from product p, type t where p.tid=t.id
商品和类型同时查询出来了
嵌套查询:存在n+1条查询语句问题。即商品表中有50条记录(n),sql语句要执行51条。
select * from product;// 一次查询,查询出50条 1条
如果在查询商品的时候,同时希望把商品的类型也查询出来,则需要再次执行50次SQL语句
select * from type where id=1
select * from type where id=2
...
select * from type where id=50
50次
一对多查询(以少查多)


多对一查询(以多查少)


嵌套查询的加载策略
什么是加载策略
假设Product中有类型对象,在获取该对象的时候,就去查询,不获取就不查询,而不需要单独再写SQL
根据需要决定查还是不查对象中的多的一方或一的一方!
延迟加载只能应用于嵌套查询!
全局配置
在mybatis-config.xml中添加如下配置



当没有调用type.getPList()时,没有进行查询类型下的商品列表!

当调用getpList方法时,执行商品列表的查询操作


局部配置
如果单独想对某一条查询配置加载策略,则需要在collection或association中通过fetchType属性来指定加载策略
可选值:
eager表示立即加载
lazy 表示延迟加载

局部配置会覆盖全局配置
注意
当调用对象中的toString等方法时,也会自动触发懒加载,如果不希望这样做,在配置文件中覆盖配置即可


MyBatis的高级配置
引入外部的数据库配置文件
引入jdbc的配置信息

使用方式:${配置文件中的key}

批量起别名

批量注册映射文件


在pom.xml中添加如下配置:
对于src/main/java目录下的文件,Maven在进行编译时,只会编译.java文件!
如果希望xml、properties等配置也一起编译,需要添加如下配置
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>/*.properties</include> <include>/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
驼峰命名自动映射(了解)



查询标签并没有使用resultMap来进行手动映射

同样可以查询到结果

MyBatis的缓存(了解)
缓存是什么?
将数据从服务器中获取,并保存到本机(磁盘或内存中),保存的这个数据就成为缓存数据。
下次获取的时候,不再从服务器重新获取,而是从本地缓存中取数据,提高效率。
一级缓存(内存)
默认开启,并且程序员无法手动干预!
在一次会话(SqlSession)中,当第一次查询出结果之后,会将这个结果保存到内存中,作为缓存数据,当再次进行相同的查询时,直接从缓存中获取数据,提高程序的效率!

实际上只查询一次数据库
二级缓存(磁盘)


开启二级缓存相关的实体类,必须实现序列化接口

对于多表(关联、嵌套)查询,都应该避免使用二级缓存,有可能造成脏数据等问题。
MyBatis的注解开发(了解!)
使用@Insert@Update@Delete@Select@Results@Result@One@Many等注解,将SQL语句写在java代码中
简单注解的使用

手动映射

嵌套查询映射
一对多嵌套:配置文件形式与注解形式的对比


多对一嵌套

结论:简单SQL用注解,复杂SQL用XML

浙公网安备 33010602011771号