Mybatis 笔记

一、mybatis简介

1.1 框架概念

软件的半成品,完成软件开发过程中的通用操作,实现特定的功能,从而简化开发人员在软件开发中的步骤,提升开发效率。

1.2 常用框架

  • MVC框架:简化servlet的开发步骤,与前端交互
    • struct2
    • springMVC
  • 持久层框架:完成数据库操作的框架,与数据库交互
    • mybatis
    • hibernate
  • 胶水框架:Spring --- 负责协同MVC和持久层

1.3 mybatis介绍

mybatis是半自动的ORM框架,提供了实体类和数据表的映射关系,通过映射文件配置,实现对象的持久化。

  • 半自动:封装度不高,SQL语句还需要自己写,比较适合处理复杂的SQL,多表联查等(hibernate是全自动)
  • ORM(Object Relaitional Mapping):对象关系映射,将Java中的一个对象与表中的一行记录一一对应。

1.4 mybatis 的特点

  • 支持自定义SQL,存储过程
  • 对原有的JDBC进行封装,几乎消除了所有的JDBC代码,开发者只需要关注SQL本身
  • 支持XML和注解配置的方式自动完成ORM操作,实现结果映射。

二、mybatis框架部署

  1. 创建一个maven项目
  2. 在pom.xml中添加依赖(mybatis依赖,mysql driver依赖)
  3. 创建mybatis主配置文件
    1. resource目录下新建mybatis-config.xml配置文件
    2. mybatis-config.xml文件中配置数据库连接信息
pom中的依赖
<dependencies>
	 <dependency>
		 <groupId>junit</groupId>
		 <artifactId>junit</artifactId>
		 <version>4.11</version>
		 <scope>test</scope>
	</dependency>
	 <dependency>
		 <groupId>org.mybatis</groupId>
		 <artifactId>mybatis</artifactId>
		 <version>3.5.1</version>
	</dependency>
	 <dependency>
		 <groupId>mysql</groupId>
		 <artifactId>mysql-connector-java</artifactId>
		 <version>5.1.9</version>
	 </dependency>
 </dependencies>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 <!--配置 mybatis 环境-->
 <environments default="mysql">
	 <!--id:数据源的名称-->
	 <environment id="mysql">
	 <!--配置事务类型:使用 JDBC 事务(使用 Connection 的提交和回滚)-->
	 <transactionManager type="JDBC"/>
	 <!--数据源 dataSource:创建数据库 Connection 对象 type: POOLED 使用数据库的连接池 -->
	 <dataSource type="POOLED">
		<!--连接数据库的四个要素-->
		 <property name="driver" value="com.mysql.jdbc.Driver"/>
		 <property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
		 <property name="username" value="root"/>
		 <property name="password" value="123456"/>
	 </dataSource>
	 </environment>
 </environments>
 <mappers>
 <!--告诉 mybatis 要执行的 sql 语句的位置,建议多个映射文件写在一个文件夹下面-->
 <mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
 </mappers>
</configuration>
支持中文的 url
jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8

三、mybatis框架使用

  1. 创建表结构信息
  2. domain包中创建实体类,属性和表属性对应
  3. dao包中创建接口,定义操作数据库的方法
  4. resource包下创建一个mappers文件夹,创建dao包中接口的映射文件
  5. 映射文件添加到主配置文件中,告诉mybatis要执行的SQL的位置。
  6. 测试用例
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
 namespace:必须有值,自定义的唯一字符串 
 推荐使用:dao 接口的全限定名称 
-->
<mapper namespace="com.bjpowernode.dao.StudentDao">
	 <!--
	 <select>: 查询数据, 标签中必须是 select 语句
	 id: sql 语句的自定义名称,推荐使用 dao 接口中方法名称, 
	 使用名称表示要执行的 sql 语句
	 resultType: 查询语句的返回结果数据类型,使用全限定类名 
	 -->
	 <select id="selectStudents" resultType="com.bjpowernode.domain.Student">
	 <!--要执行的 sql 语句-->
	 select id,name,email,age from student
	 </select>
</mapper>

四、mybatis 的动态SQL

动态SQL:根据查询条件动态完成SQL的拼接

4.1 动态SQL <if>

对于该标签的执行,当 test 的值为 true 时,会将其包含的 SQL 片断拼接到其所在的 SQL 语句中。

 语法:
 <if test="条件"> sql 语句的部分 </if>

<select id="selectStudentIf" resultType="com.bjpowernode.domain.Student">
	 select id,name,email,age from student
	 where 1=1
	 <if test="name != null and name !='' ">
		and name = #{name}
	 </if>
	 <if test="age > 0 ">
		and age &gt; #{age}
	 </if>
</select>

# 注意where条件后面的1=1

4.2 动态SQL <where>

if标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1 的子句。因为,若 where 后 的所有条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where,SQL 出错。但当数据量很大时,会 严重影响查询效率。

使用<where/>标签,在有条件查询时,会自动加上where子句,没有条件查询也不会添加where子句,需要注意的是,第一个<if/>标签中的SQL片段,既可以加and,也可以不加and,而其他<if/>标签中的SQL片段必须加上and,但是建议都加上。

 语法:
 <where> 其他动态SQL </where>

<select id="selectStudentWhere" resultType="com.bjpowernode.domain.Student">
 select id,name,email,age from student
	 <where>
		 <if test="name != null and name !='' ">
			and name = #{name}
		 </if>
		 <if test="age > 0 ">
			and age &gt; #{age}
		 </if>
	 </where>
</select>

4.3 动态SQL之 <trim>

<select id="selectStudentWhere" resultType="com.bjpowernode.domain.Student">
 select id,name,email,age from student
	 <trim prefix="where" prefixOverrides="and | or" suffix="order by age">
		 <if test="name != null and name !='' ">
			and name = #{name}
		 </if>
		 <if test="age > 0 ">
			and age &gt; #{age}
		 </if>
	 </trim>
</select>

# prefix 前缀
# prefixOverrides 第一个是and或者or,就自动去掉
# suffix 后缀

4.4 动态SQL之 foreach

语法
 <foreach collection="集合类型" open="开始的字符" close="结束的字符" 
	item="集合中的成员" separator="分隔符">
	#{stuid}
 </foreach>



<select id="selectStudentForList" resultType="com.bjpowernode.domain.Student">
	 select id,name,email,age from student
	 <if test="list !=null and list.size > 0 ">
		 where id in
		 <foreach collection="list" open="(" close=")" 
			item="stuid" separator=",">
			#{stuid}
		 </foreach>
	 </if>
</select>

五、一些常见的问题

5.1 mybatis的优缺点

  • 优点:
    • 半ORM的框架,封装了JDBC代码,开发者只需要关注如何编写SQL,减少了开发难度
    • SQL写在XML里面,保证了SQL和代码的解耦,便于SQL的统一管理。支持编写动态SQL语句,保证SQL的可重用性
    • 很好的与各种数据库兼容
    • 能够与spring很好的集成
  • 缺点
    • SQL编写工作量大,尤其是多表联查的时候,SQL的编写较为复杂
    • SQL语句依赖数据库,移植性差,不能随意更换数据库。

5.2 #{}和${}的区别是什么?

  1. 编译过程
    1. #{} 是占位符,SQL会在执行前进行预编译,将#{}对应的内容替换为 ? 调用prepareSatement的set方法进行赋值
    2. ${}是拼接符,字符的替换,相当于对应的内容append
  2. 是否自动加引号
    1. #{}对应的变量会自动加上引号
    2. ${}对应的变量不会自动加引号,因此编写的时候要注意手动加引号
  3. 安全性
    1. #{} 因为是预编译的占位符,因此能防止SQL注入
    2. ${}则不能防止SQL注入

5.3 mybatis中 dao接口和mapper.xml文件中的SQL是如何建立关联的?

mapper.xml其实是对dao接口的实现,mapper.xml中的namespace对应的是dao接口的全限定名。

1、解析XML: mybatis初始化SqlSessionFactoryBean的时候,会找到mapperLocations路径去解析里面所有的XML文件。

  • 创建SqlSource:把每个SQL封装成SqlSource对象(分为静态SQL--字符串和动态SQL---SQLNode)
  • 创建MappedStatement:每个SQL标签就对应一个MappedStatement对象,包括两个属性。创建完MappedStatement对象,就把他缓存到Configuration对象中,基本所有的配置信息都维护在Configuration 
    • id: 全限定类名加方法名组成的id
    • sqlSource:当前SQL标签对应的SqlSource对象

 2、Dao接口代理

  • dao的初始化
    • SqlSessionFactoryBuilder 的build方法去解析配置文件,得到一个SqlSessionFactory工厂对象,传入的参数是一个inputStream流,其实就是去读取mybatis的主配置文件和mapper.xml  ---- 同时将所有的配置信息都缓存在Configuration对象中
    • factory.openSession() 建立连接,获取事务管理配置,创建事务管理对象
    • sqlSession.getMapper(dao.class) ---- 通过jdk动态代理产生dao代理对象。

 

5.4 Dao接口里面的方法,参数不同的时候可以重载吗?

Mapper接口里的方法,是不能重载的,因为mybatis是使用 全限名+方法名 的保存和寻找策略;当调用接口方法时,通过 “接口全限名+方法名”拼接字符串作为key值,可唯一定位一个MapperStatement,因为在Mybatis中,每一个SQL标签,都会被解析为一个MapperStatement对象。如果重载,方法名一致,就会导致key value的覆盖。

5.5 Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

记住mybatis是使用 全限名+方法名 的保存和寻找策略,如果不同的xml的namespace不同,那么id是可以重复的。

5.6 mybatis中的分页?

  • 原生的limit分页:
    • 需要在SQL中编写limit关键字,然后传入分页参数进行分页
    • 缺点是编写SQL复杂
  • RowBounds分页:
    • 是mybatis中内置的一个处理分页的类,可以不需要在映射SQL中写limit关键字,自动给我们拼接,使用方便
    • 不适用于处理数据量较大的场景。
  • 拦截器插件分页(如pageHelper)

 

5.7 mybatis插件运行原理

mybatis框架下,我们可以基于插件机制实现分页,分表和监控等功能。

mybatis的四大组件:ExecutorStatementHandlerParameterHandlerResultSetHandler,mybatis支持用插件对四大核心对象进行拦截,其实,插件就是拦截器,用来增强核心对象的功能,增强功能的本质是借助动态代理实现的。

Mybatis编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。

插件运行原理:

  1. 每个对象创建出来不是直接返回,而是调用了interceptorChain.pluginAll(parameterHandler);
  2. 拦截器链是mybatis初始化时创建出来的,保存了所有的拦截器
  3. 遍历所有拦截器,调用intercept.plugin(target)返回封装后的target对象
    1. interceptor.plugin(target)中调用了Plugin.warp(target,this)
    2. Plugin类实现了InvocationHandler接口,也就是说调用Plugin类的方法最终会调用Plugin的invoke()方法的实现
    3. invoke()方法最终会回调Interceptor的抽象方法intercept()
    4. intercept()方法具体实现是我们自己
  4. 我们可以使用插件为目标对象创建一个代理对象,AOP四大组件,然后拦截四大对象的每次执行

Mybatis插件原理总结 - 寒天峰 - 博客园

5.8 mybatis的延迟加载?

mybatis的延迟加载也称为懒加载,是指在进行表的管理查询时,只查询需要使用的数据,关联的数据不适用则不查询,也称为按需查询。

如进行一对多查询时,只查询出一方,当程序需要多方的数据时,mybatis在发出SQL语句进行查询,这样就可以减少数据库的压力。

mybatis延迟加载的条件:resultMap实现高级映射,如使用association、collection实现一对一以及一对多映射;关联对象的查询时子查询,多表连接查询则不行。

延迟加载好处:先从单表查询,需要时在从关联表查询,大大提高了数据库的性能。

延迟加载的原理:

本质是通过动态代理的方式,创建目标对象的代理对象,调用目标方法时(getting方法),进入拦截器方法,⽐如调⽤ a.getB().getName() ⽅法,进⼊拦截器的 invoke(...) ⽅法,发现 a.getB() 需要延迟加载时(为null时),那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调⽤ a.setB(b) ⽅法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理。

5.9 mybatis的缓存

mybatis提供了对缓存的支持 ,分为一级缓存和二级缓存。

  • 一级缓存是SqlSession级别的缓存,操作数据库时需要构造SqlSession对象,一级缓存主要是SqlSession内部共享,不同SqlSession之间缓存互不影响
  • 二级缓存是namespace级别的缓存,多个SqlSession去操作同一个mapper中的SQL语句时,可以共用二级缓存。

5.9.1 一级缓存

SqlSession是一个接口,提供了一些CRUD的方法,其默认实现类为DefaultSqlSession,DefaultSqlSession 类持有 Executor 接口对象,而 Executor 的默认实现是 BaseExecutor 对象,每个 BaseExecutor 对象都有一个 PerpetualCache 缓存,是一个hashMap。

用户发起查询时,根据当前执行语句生成MappedStatement,在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户。

 

一级缓存配置:
<setting name="localCacheScope" value="SESSION"/>

value两个选项
SESSION 默认级别 一个 MyBatis 会话中执行的所有语句,都会共享这一个缓存
STATEMENT,可以理解为缓存只对当前执行的这一个 Statement 有效。

 一级缓存总结:

  • 一级缓存生命周期和SqlSession一致
  • 一级缓存结构是一个hashmap
  • 一级缓存缓存的是对象(user1 == user2 会打印为true,代码里对user1属性修改,会影响user2,但是不会影响数据库。)
  • 一个SqlSession内部的更新操作会导致一级缓存失效
  • 一级缓存适用范围为一个SqlSession 内部,分布式环境下或者有多个SqlSession 时,其他SqlSession 的写操作会导致脏数据,建议缓存级别设置为statement

5.9.2 二级缓存

如果多个SqlSession需要共享缓存,则需要使用到二级缓存,二级缓存默认是不开启的,开启之后,会使用CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询

二级缓存数据存储的介质不一定在内存中,也有可能在硬盘中,从硬盘读取的时候需要反序列化,因此相关pojo类都需要实现Serializable 序列化接口

 二级缓存总结:

  • 二级缓存是 namespace 级别,实现了 SqlSession 之间缓存数据的共享
  • 二级缓存缓存的是数据(user1 == user2 会打印为false)
  • 其他SqlSession的更新操作,会导致二级缓存失效,从而当前SqlSession查询会走数据库
  • 多表查询时,极大可能出现脏数据,安全使用条件苛刻(通常我们会为每个单表创建单独的映射文件,由于 MyBatis 的二级缓存是基于 namespace 的,多表查询语句所在的 namspace 无法感应到其他 namespace 中的语句对多表查询中涉及的表进行的修改,引发脏数据问题)

mybatis缓存总结:

实际生产环境一般都会关闭mybatis的缓存,因为这个缓存真的很鸡肋啊。

对于一级缓存:多个SqlSession或者分布式环境,容易产生脏数据,增删改操作多的服务,清除一级缓存也很麻烦

对于二级缓存:虽然解决了SqlSession之间缓存数据共享问题,但是多表查询情况,不同namespace之间无法感应到变化,引发脏数据,基于cache ref 解决的话导致缓存粒度变粗,并且增加了开发成本。

因此,直接使用redis等分布式缓存成本会更低,安全性也会更高。

5.10 mybatis如何将SQL执行结果封装为目标对象并返回的?有哪些映射方式?

  1. 使用resultMap标签,column对应表字段,property对应对象的属性名,并将定义的resultMap标签作为select标签的 resultMap 属性赋值进去
  2. 使用sql列的别名功能,将列的别名书写为对象属性名。
 mapper 文件:
<!-- 创建 resultMapid:自定义的唯一名称,在<select>使用type:期望转为的 java 对象的全限定名称或别名 -->
<resultMap id="studentMap" type="com.bjpowernode.domain.Student">
	 <!-- 主键字段使用 id -->
	 <!-- column对应表字段,property对应对象的属性名 -->
	 <id column="id" property="id" /> 
	 <!--非主键字段使用 result-->
	 <result column="name" property="name"/>
	 <result column="email" property="email" />
	 <result column="age" property="age" />
</resultMap>
<!--resultMap: resultMap 标签中的 id 属性值-->
<select id="selectUseResultMap" resultMap="studentMap">
	 select id,name,email,age from student where name=#{queryName} or age=#{queryAge}
</select>

 5.11 mybatis动态SQL的用处?

根据查询条件动态完成SQL的拼接,实现SQL的复用

执行原理是根据表达式的值,完成逻辑判断,并拼接SQL。

5.12 如何获取自动生成的主键值?主键回填?

<insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
     insert into names (name) values (#{name})
</insert>

5.13  在mapper中如何传递多个参数?

  • 用#{arg0}、#{arg1}...传递参数,但是要注意顺序不能错,一般不用
  • 用param注解
  • 用map
  • 用Javabean

public interface usermapper {
   user selectuser(@param(“username”) string username,
                    @param(“hashedpassword”) string hashedpassword);
}

5.14 一对一,一对多查询

1、一对一关联查询 association

<select id="getClass" parameterType="int" resultMap="ClassesResultMap">  
	select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}  
</select>  

<resultMap type="com.lcb.user.Classes" id="ClassesResultMap">  
	<!-- 实体类的字段名和数据表的字段名映射 -->  
	<id property="id" column="c_id"/>  
	<result property="name" column="c_name"/>  
	<association property="teacher" javaType="com.lcb.user.Teacher">  
		<id property="id" column="t_id"/>  
		<result property="name" column="t_name"/>  
	</association>  
</resultMap> 

2、一对多关联查询 collection

<resultMap type="com.lcb.user.Classes" id="ClassesResultMap2">  
	<id property="id" column="c_id"/>  
	<result property="name" column="c_name"/>  
	<association property="teacher" javaType="com.lcb.user.Teacher">  
		<id property="id" column="t_id"/>  
		<result property="name" column="t_name"/>  
	</association>  

	<collection property="student" ofType="com.lcb.user.Student">  
		<id property="id" column="s_id"/>  
		<result property="name" column="s_name"/>  
	</collection> 
</resultMap> 

5.15  一对一有几种查询方式,怎么操作的 

1、联合查询,见5.14,在resultMap中配置association节点

2、子查询,先查一个表,根据这个表的结果的外键id,在去查另外一个表的数据。也是通过association配置,但是另一个表的查询通过select属性配置,而不是写在SQL中。

<association property="teacher" javaType="com.lcb.user.Teacher" select="子查询id"/>  

 一对多查询,就是把association节点变成collection节点

5.16 什么是mybatis的接口绑定?有哪些实现方式?

接口绑定,就是在mybatis中任意定义接口,然后把接口中的方法和SQL进行绑定,我们直接调用接口方法就可以。

接口绑定有两种实现方式

  • 注解绑定,在接口的方法上添加@select,@update等注解,适合简单的SQL
  • xml绑定,指定xml映射文件的namespace为接口全路径名,适合较为复杂的SQL

5.17 mybatis 和 hibernate有哪些不同?

  • mybatis
    • 半ORM框架,需要自己编写SQL
    • 直接编写原生态SQL,可以严格控制SQL执行,灵活度高
    • 无法做到数据库无关性,实现支持多种数据库软件需要自定义多套SQL映射文件
  • hibernate
    • 全orm框架,无需编写SQL
    • 对象/关系映射能力强
    • 数据无关性好,对关系模型要求高的软件,使用hibernate开发可以节省代码提高效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值