【SSM Mybatis3.0 Eclipse】Mybatis3學習記錄 2019_6_30

历史及资源

Apache ibatis1.0-2.0 开发团队由Google购买——Mybatis3.0,2010.6
2013.11 Github上开源

官网资源

Github源码主页:https://github.com/mybatis/mybatis-3
中文官网:http://www.mybatis.org/mybatis-3/zh/index.html

Mybatis Generate(MBG)文档:http://www.mybatis.org/generator/quickstart.html
MBG官方下载:http://www.mybatis.org/generator/quickstart.html

Mybatis+Ehcache整合文档:http://www.mybatis.org/ehcache-cache/
Mybatis+redis整合文档:http://www.mybatis.org/redis-cache/

Mybatis-Spring整合包下载:https://github.com/mybatis/spring/releases
Mybatis-Spring整合中文文档:http://www.mybatis.org/spring/zh/getting-started.html
Mybatis官方提供的Spring整合Dome:https://github.com/mybatis/jpetstore-6

原生持久层操作

JDBC:持久层工具

操作过程:
1.编写SQL
2.预编译
3.设置JDBC驱动及参数
4.执行SQL
5.封装结果

实际情景:实际问题
当DBC优化代码,将会导致整个项目的修改、编译、打包、部署、运行等全套流程。
将会是十分浪费时间的过程。

缺点:高耦合,硬编码

Hibernate 已有相似框架

Hibernate:全自动全映射ORM(Object Relation Mapping)框架,旨在消除SQL编写
Hibernate
中部的黑盒是不可自定义的。如果想要,需要学习HQL以及其查询编译原理
缺点:
1.Hibernate自动生成的SQL,不易做特殊优化。
2.基于全映射的全自动框架,在大量字段的情况下取部分映射比较困难。降低性能。
3.依赖项很多,需要较多Jar包
SQL是整个持久层最重要,不重复的部分。其余的操作都是可以根据SQL来有规律重复。

Mybatis与Hibernate区别

半自动框架全自动框架
将黑盒取消,并且将编写SQL这一过程,单独建立一个配置文件,为程序猿自定义做准备。其余部分也是可见。

Mybatis框架内容

SQL Maps,Data Access Object

一般操作过程

1.创建xml文件【mybatis-config.xml】需要挂载在Source folder下的,作为全局配置。
内容:

  • environment标签:<数据库事务管理器 type=jdbc><数据库信息 等>
  • mappers标签:单独SQL查询xml注册管理

2.定义数据库表类 xxx.java。需要写全get,set,toString函数。

3.创建单独SQL查询xml文件。可以放在普通文件?建议扔到Source folder与mybatis-config同级。
内容:

  • mapper标签 定义namespace=XXX
  • select标签 定义唯一标识id,SQL及返回值类型【数据库表类,第二步的xxx.java】

4.Java代码中使用

  • 通过全局配置文件获取已注册的单独SQL查询xml文件,<名称空间.SQL唯一标识>
  • 1.Resources.getResourceAsStream(全局配置文件路径)获取输入流inputStream对象
  • 2.SqlSessionFactoryBuilder().build(inpuStream)生成SQL会话工厂。
  • 3.通过SQL会话工厂的openSession函数获取SqlSession对象
  • 4.SqlSession拥有增删改查(单个/批量)子函数功能
    分两种方式,旧版=【<名称空间.SQL唯一标识>,Object】。新版接口
  • 5.使用完毕后SqlSession对象.close()

5.关于新版接口的使用

  • 基础常识
接口定义后,接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

网址:类与接口的区别

  • 新版Mybatis的单独SQL查询xml文件如何与接口文件进行动态绑定
namespace=接口文件全包名路径
select标签(存储SQL)的唯一标识符与接口内实现函数的函数名相同。
即是,这个单独SQL查询xml文件的select标签实现了接口文件内的函数内容。
  • 具体操作内容
1.与旧版的1.2.3步骤相同,最终获取一个SqlSession对象
2.声明定义一个接口实现类对象【开发人员不用写接口的实现类对象,Mybatis帮你写了,只需要绑定接口的对应xml文件及select标签SQL内容即可】【Mybatis为接口自动创建代理对象,代理对象去增删改查】
3.定义声明数据库表对象作为返回值承载实例,接口类对象的定义的函数方法(实现SQL在同名xml)的返回值。
  • 好处
1.增加传入参数类型检查,object随便都可传入
2.输出的返回值类型检查
3.不光Mybatis可用,Hibernate也可使用该接口,明确规范DAO层规则,单独抽出解耦合,方便各个框架使用。
  • 小总结:
1.接口式编程
Java原生接口: dao =====》DamImpl.java
Mybatis接口:  Mapper(dao) =====》xxxMapper.xml
2.SqlSession = connection 用完需要关闭,同样为非线程安全,每次都需要新New对象,不可列为成员变量。
3.Mapper(dao)并没有实现类,但Mybatis会自动为绑定在xml文件的接口生成代理类。
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
4.两个重要文件
Mybatis-config.xml:全局配置,包含不变的数据库管理
单独独立的SQL映射.xml:给接口进行查询的核心SQL,映射指的是接口类及定义函数与SQL查询的关系。
注:可以不用全局配置文件,直接new出数据库配置对象即可

Mybatis-Config.xml标签详解

1.properties标签:用于外部数据库配置引入【Spring更优,所以基本不用】
配置dbconfig.properties文件路径,用于读取其中数据库定义值【不需要重启服务器】
2.settings标签:一堆重要配置【有时间详勘】
例子:<setting name=mapUnderscoreToCamelCase,value=true></setting>
数据列命名,A_Count,开启驼峰命名映射法 = aCount可被Mybatis识别封装。,就不用写别名了。驼峰写法即可。
3.typeAliase标签:数据表类引用别名
三种方法

  • <typeAlisa type="" alias=""/>标签,alias可写可不写,默认是类名,大小写通吃
  • <package name="">标签,name填写包名,用于批量起别名,默认是类名
  • @Alias注解,可配合package在具体包内类文件中使用,定义别名【批量的时候不同路径下有相同类名的数据库表类】
    注意:不能与Mybatis预留给Java内部的别名重复,pdf里有。
    建议:写全类名,因为方便查找类,点击路径即可到达。

4.typeHandlers标签:类型处理器
数据库变量类型与MybatisString变量的转换规则
例如:低版本Mybatis与当时的JDK均没有对日期类型处理有较好的规范库,所以在1.8JDK后,出现JSR时间处理类库,低版本Mybatis为适配JAVA提供了对应内容的类型处理器,即可去官网下载后使用typeHandlers引入。
在Mybatis3.4版本后,不需要手动引入。自动注册。
其最重要内容是——自定义类型处理器,重写、创建自己的类型处理器来处理不支持的、非标准的类型。
引入自定义类型处理器步骤
5.plugins标签:对四个重要对象进行拦截,动态代理其内容。
插件功能
6.environments标签:环境配置。defualt属性可指定默认运行的数据库环境。可快速切换运行环境。
必含内容:
<environment id="">
<transactionManager type="">事务管理器,jdbc、managed(JEE应用服务器)两种类型,还可以自定义,实现TransactionFactory接口,type=实现类名即可。但综合方案中的事务管理最后都是由Spring解决
<dataSource type="">type=UNPOOLED|POOLED|JNDI,不使用连接池,连接池,JNDI。也可自定义数据源,实现DataSourceFactory接口,返回自己所配置的数据源C3P0?,type=实现类名
</environment>
7.databaseIdProvider标签:为不同数据库起别名,支持多数据库配置。然后可在单独独立的SQL查询xml文件中写多个在不同数据库环境下的select查询语句。为动态切换测试。
说白了就是,单独独立的SQL查询xml文件中的select标签可写默认SQL,MySQL_SQL,Oracle_SQL…
8.mappers标签:注册SQL映射xml文件
mapper子标签属性:
resource:引入类路径下xml文件
url:引入磁盘路径、网络路径下xml文件
class:第二种注册SQL映射文件的方法,注册SQL映射接口,由接口–>同名xml且绑定了此接口的xml内指定实现方法。但一般来说,好像没人用???
第三种注册方法是用来注册基于注解的接口(无xml配置文件)@select(“SQL”)但是不建议,因为Mybatis本来目的就是为了抽出SQL到XML中,结果又写回Java了太蠢了。
总结注册方法:5类
1.resource(仅仅xml)注册方法
2.resource(xml+不同包接口,但xml绑定了接口)注册方法
3.class(interface有同名同包xml)
4.class(interface无xml有注解)4种。不推荐第4种
5.package子标签:批量注册,以包为单位,但是需要以第3种class要求,才可以即同名的interface+xml才可批量注册成功。

小技巧,关于同名的interface+xml,怎么看起来更好看,可以在conf Source folder文件中建立与src全同名DAO包,最后工程合并的bin目录下都是在一起的,只是在Eclipse上看的是xml一个文件夹,interface一个文件夹。
注意:Mybatis-config.xml书写标签是有顺序的,不可乱序。可鼠标移动在configuration标签上查看(当然前提是绑了约束文件)。

mapper.xml标签详解

mapper.xml
增删改查四大操作标签
<select id=></select>
<insert id=></insert>
<update id=></update>
<delete id=></delete>

额外需求:获取插入对象后的自增主键值
insert useGeneratedKeys=“true” keyProperty=“id”
使用自增主键获取策略,主键值存储为对象id属性
然而,Oracle不支持自增,只允许通过序列来模拟自增
那么如何获取插入对象的主键值?
<insert id="addEmp" databaseId="oracle">
<selectKey keyProperty="id" order="BEFORE" resultType="Interger">
select EMPLOYEES_SEQ.nextval from dual
/selectKey>
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(#{id},#{lastName},#{email})
</insert>
使用selectKey子标签来获取下一个next序列值,然后存储在id内,通过order顺序属性将查询next值的sql排序在insert sql前面,再将具有ID的employee对象传值到insert sql。
关于before、after的第二种方法,是直接将sql写成values(EMPLOYEES_SEQ.nextval,#{lastName},#{email})
将order=改为after,查询,select EMPLOYEES_SEQ.currval from dual序列当前值。但是如果插入多条,after后查询currentval会导致只存储了最后的插入序列值,丢失前面几条的序列值。建议before

interface.java
在接口文件中对应的函数返回值类型Mybatis允许为4类
integer、long、boolean、void
mapper.xml中的insert、delete、update标签没有resultTpye属性

test.java
SqlSessionFactory.openSession()无参调用所以,需要手动调用commit()才能生效
SqlSessionFactory.openSession(true)即可自动提交

1.select标签
resultType:如果返回的是一个集合(list),写集合内元素的类型
resultType:如果返回的是一个Map,写map即可,Mybatis已经写了别名map
id:唯一标识符
parameterType:参数类型【可不传,typehandler自动识别】

其他参数
select标签属性
重要参数
resultMap,结果集映射
Mybatis有:
1.autoMappingBehavior,唯一要求【列名与JavaBean属性名一致】
2.双方均符合驼峰命名法,POJO=lastName,MySQL=LAST_NAME=lastName.可开启mapUnderscoreToCamelCase=true。
上面两种规则综合产物就是resultType=JavaBean/POJO也就是employee类
3.自定义结果集映射resultMap,实现高级映射。
例子:对应关系Map<列名,employee.属性>
resultType与resultMap是二选一。
<resultMap type="employee" id="MyEmp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
</resultMap>
写在单独独立的SQL查询xml文件的mapper标签内。位于select子标签上。resultType内的id子标签是指定主键的映射关系,Mybatis底层对id有特殊优化。也可以用result子标签来书写id,只不过没有特殊优化。result子标签针对普通列与列值映射。
如果对于所有列未写全的情况,会继续依据1,2两类规则在开启的情况下自动映射。
type属性是与哪个数据类型匹配封装【以什么规则匹配封装】

resultMap内的association联合(嵌套)查询
依旧是使用resultMap自定义结果集,因为许许多多原因,例如联合查询的结果是一个平面表,是无结构的,而类的复合成员变量是有上下级结构,即级联属性。
故第一种方法便是手动适应——级联属性封装。
<result column="d_id" property="depName.id"/>
<result column="dep_name" property="depName.depName"/>
第二种方法使用resultMap里的association子标签定义变量上下级结构。
<association property="depName" javaType="com.mybatis.bean.DepartmentName">
<id column="d_id" property="id"/>
<result column="dep_name" property="depName"/>
</association>
javaType属性是定义第一级对象property="depName"属性的数据类型(某个类)
剩下的都是一个意思。
关于association标签的一点使用提示:如果在association标签上的result标签未全部写全,直接接上下面的association级联数据设置,会导致第一级属性与数据库自动匹配规则失效。所以如果想写association则必须将所有第一级属性写全。

resultMap内的association子标签还可以进行分步查询。

    <!-- 使用association分步查询
			1.先按照Employee.id查询全部的员工信息,但缺少Employee.depName.depName值
				并且需要注意,d_id此时并没有封装进Employee.depName(DepartmentName类)成员变量中的id属性中
			2.根据全部的员工信息中的d_id,查询数据库中depName值,然后将这个值放入Employee.depName.depName
	 -->
	<!-- public Employee getEmpAndDeptByIdStep(Integer id); -->
	<resultMap type="employee" id="MyEmpDepStep">
		<id column="id" property="id"/>
		<result column="last_name" property="lastName"/>
		<result column="gender" property="gender"/>
		<result column="email" property="email"/>
		<!-- column:为已获取到的用于二级SQL的数据列
				property:此association的select方法查询出的结果存放属性为depName
				select:为关联其他Mapper查询SQL的接口
				javaType:好像可写可不写 -->
		<association column="d_id"  property="depName" javaType="DepartmentName"
			select="com.mybatis.dao.DepartmentMapper.getDepById" >
		</association>
	</resultMap>
	<select id="getEmpAndDeptByIdStep" resultMap="MyEmpDepStep">
		select * from tbl_employee where id=#{id}
	</select>

在分布查询的基础上,还可以延迟查询,当需要二级查询结果的时候再执行二级查询。
设置2全局配置

<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>

注意:建议将所有需求配置setting为显示值。以免当Mybatis版本更新后导致默认值发生改变。

分布查询的问题——造成分步之后,对于相互关联的数据成员无法填满【Employee有一个叫做department的对象,想通过Department查询所有同部门Employee全部属性(包含department对象属性(部门id,部门名)就无法从一级查询传递到二级查询的封装内)】

collection標簽定义关联集合list封装规则
如果department類下屬的是一個list<employee>集合不能使用級聯屬性直接賦值。所以,出现一个collection定义关联集合封装规则
也就是:association用於定義對象數據成員,而collection用於定義list集合數據成員。
也可分步,延遲查詢。與association一樣。關於上面的分步查詢造成的相互關聯數據項無法傳遞封裝,其實有點庸人自擾,因爲沒有這樣的需求。蛇頭吃蛇尾,有點蠢。

關於分步查詢中的傳值多列進行查詢設置(多列封裝為map一起通過column屬性傳遞過去)
column{二級查詢參數的javabean屬性名=一級查詢所得列名,…}

關於延遲加載,全局可配置延遲加載,但具體到某個select也可自己私設fetchType=lazy/eager屬性,以局部覆蓋全局配置。

discriminator標簽

Mybatis参数处理

单个参数:不做特殊处理
#{参数名}:取出参数
参数名,瞎几把写都行,不管写什么都是只取唯一指定的属性值。
NOOOOOO还有特殊类型的单参数会进行处理,例如List,Set,POJO类型变量
例子:
POJO
public Employee getEmp(Integer id,@Param(“e”)Employee emp);
取值:id ===> #{param1} lastName ===> #{param2.lastName / e.lastName}
LIST/SET
public Employee getEmp(List ids);
取第一个id值:id ===> #{list[0]}
list、set属于collection类型,以及数组类型的key会被Mybatis封装成
collection,或者专属list,以及array。

多个参数:做特殊处理
多个参数或会被封装成一个Map
key:param1,…,paramN,或者参数的索引0,1,2,3,…
value:传入的参数值
#{}就是从map中获取指定Key的Value
也就是#{param1},#{param2},……

如何将这些不能令人易懂的参数改为人可识别呢?
@Param("")
在接口文件中,在函数参数前添加@Param("")指定key值

POJO作为参数:
如果多个参数正好是我们为数据库表所写【业务逻辑的数据模型】Employee实体类(POJO),我们可以直接传入POJO。
#{属性名}:取出传入的POJO的属性值
Map作为参数:
如果多个参数不是业务模型中的数据,没有对应的POJO,如果不经常使用,为了方便也可以采用map参数的形式
#{key}:取出map对应的值

TO:
如果多个数据不是业务模型的数据,但还要经常使用,推荐专门再编写一个TO(Transfer Object)数据传输对象(pojo类)

注意:关于自动获取接口的参数名为Mybatis可用#{}引用值,则需要在jdk1.8版本以上,开启Mybatis-config.xml全局配置中的useActualParamName。

Mybatis处理参数的过程
例子 names:{0=id,1=lastName}key=0.1.2… val=name
1.获取每个标注了param注释的@Param值,赋值给names的name(val)
2.解析其他参数然后将信息存储到names(map),(key=索引顺序,val=name)
name的值有两种
第一种:注解值
第二种:name=param+map.size(),该参数当前索引值
即是,names是一个map存储参数次序和参数名称
names{0=id(有注解),1=lastName(有注解),2=param2(无注解)}
下一层Map封装
判断
参数集若为null,直接返回null
参数集若为1个,并且没有注解,直接返回namesMap的第一个key对应的val值
参数集若为多个或者有注解,再封装namesMap成新的paramMap。遍历names集合,将names.val取出作为paramMap的key值,将names.key作为args[索引值]【这里对0,1,2,3…进行了+"param"字串处理】,而args[names.key]作为paramMap的value值。即是paramMap<注解值/names.val(参数名),args[names.key]>(参数值数组的对应该参数名的值)
names[参数的存储顺序,存储的参数名],存储顺序用于获取具体的参数值
param[参数名,参数值]

参数获取:
#{}
${}
井号与美元区别
根本原因是,SQL只支持对参数部位的预编译(占位符),不支持对非参数部分的预编译。
原生JDBC也不支持预编译的时候,使用${}来进行SQL拼接。
一个意外:如果在预编译参数位置使用$符号,会造成
Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'id' in 'class java.lang.Integer'

#{}有更加丰富的功能
对参数的处理
javaType=?
jdbcType=?
mode(存储过程)=?
numericScale(保留小数)=?
resultMap=?
typeHandler(类型处理器)=?
jdbcTypeName=?
expression(OGNI表达式)=?
常用:
传值为空的部分列名,指定其jdbcType数据库类型
当数据为null时,Oracle不能识别Mybatis对null的默认处理会报错。
Mybatis对Null映射的是原生jdbc other类型,mysql识别,但Oracle不识别,
正确做法1:在单独独立的SQL xml中<insert databaseid=Oracle>的SQL中的#{}中指定jdbcType=NULL
value(#{email,jdbcType=NULL})
正确做法2:修改全局配置xml中的<settings></settings>,将jdbcTpyeForNull指定值为NULL、VARCHAR、OTHER,默认是OTHER
<setting name="jdbcTpyeForNull" value="NULL"/>

Mybatis动态SQL标签

if标签(条件判断)

传入的参数,有什么查什么。没有的不拼接SQL
if标签有test属性,
where
<if test="条件(id!=null)">id = #{id}</if>
<if test="gender==0 or gender==1">and gender = #{gender}</if>
test标签使用OGNL表达式语言,可以在Apache官网手册学习,OGNL自动识别数字String转换。上面如果满足条件,即可拼接到该条Select标签SQL中
没有则拼接。

<if test="email!=null &amp;&amp; email.trim()!=&quot;&quot;">
and email = #{email}
</if>
这里的trim()是OGNL自带函数取值,&quot;是双引号"(这个双引号就是)转义字符,意思是email拦截的值不能是空串,空的也不给查询。

where标签(封装SQL的where条件)

使用<where></where>可以自动识别避免首个条件缺失导致第二个if SQL前的多余and、or SQL拼写错误。注意:使用<where/>一定要将and写在每个条件的前面 例如第二行的gender。

select * from tbl_employee
<where>
	<if test="id!=null">
		id = #{id}
	</if>
	<if test="lastName!=null and lastName!=''">
		and last_name like #{lastName}
	</if>
</where>
set标签(封装SQL的update可选择更新)

更新数据如果传入对象,那么一般就全都更新,不管是否对象的属性是否有值
set可以实现哪一个属性有值就更新哪一个属性
条件性更新,有什么条件传过来,更新什么条件
set标签解决了","多余逗号的问题
注意:执行完update函数,需要令session.commit()才能更新到数据库

<update id="updateEmpByIdSet" >
	update tbl_employee
	<set>
		<if test="lastName!=null">
			last_name=#{lastName},
		</if>
		<if test="gender!=null">
			last_name=#{gender},
		</if>
		<if test="email!=null">
			last_name=#{email},
		</if>
	</set>
	where id = #{id}
</update>
trim标签(截取SQL语句,可替换where及set)

与trim()是一个意思,可以自定义前后缀及多余的符号
prefix:前缀定义(where,
prefixOverrides:前缀覆盖(and为""。
suffix:后缀定义,
suffixOverrides:后缀覆盖(and为""。
这个是替代where的

<trim prefix="where" suffixOverrides="and" prefixOverrides="and">
	<if test="id!=null">
		id = #{id}
	</if>
	<if test="lastName!=null and lastName!=''">
		and last_name like #{lastName}
	</if>
	<if test="email!=null &amp;&amp; email.trim()!=&quot;&quot;">
		and email = #{email}
	</if>
	<!--ognl会自动转换字符串和数字-->
	<if test="gender==0 or gender==1">
		and gender = #{gender} and
	</if>
</trim>

这个是替代set的

<update id="updateEmpByIdSet" >
	update tbl_employee
	<trim prefix="set" suffixOverrides=",">
		<if test="lastName!=null">
			last_name=#{lastName},
		</if>
		<if test="gender!=null">
			gender=#{gender},
		</if>
		<if test="email!=null">
			email=#{email},
		</if>
	</trim>
	where id = #{id}
</update>
choose(when,otherwise,if else功能)

只选择众多分支的一个分支
select》choose》when | … | otherwise 结束
当然也可以在choose外面包个where

<select id="getEmpsByConditionChoose" resultType="Employee">
	select * from tbl_employee
	<where>
		<choose>
			<when test="id!=null">
				id=#{id}
			</when>
			<when test="lastName!=null">
				last_name like #{lastName}
			</when>
			<when test="email!=null">
				email=#{email}
			</when>
			<otherwise>
				1=1
			</otherwise>
		</choose>
	</where>
</select>
foreach标签(批量功能)

这个标签主要是实现 批量功能
批量保存,批量查询
例如有一个List ids,一列员工编号
查询出这一列的员工所有信息

foreach将遍历出的元素赋值给变量
1.collection: 指定遍历的集合【传入的List变量】,list类型的参数会特殊处理封装在map中,map的key就叫list
2.item: 将当前遍历出的元素赋值给指定的变量【中间变量】
3.separator: 遍历出的元素的分割符【拼接在SQL: in(1,2,3)】
4.open: 拼接开始的字符【一般是"("】
5.close: 拼接结束的字符【一般是")"】
6.index: 遍历list时 index是索引,item是值
遍历map时 index是key, item是值
#{变量名} 中间变量的值也就是当前遍历出的元素

        collection的取值:
            1.若传入的是list: 可以直接写list
            2.若传入的是array: 可以直接写array
            3.传入对象,对象中有list: 可以直接写oredCriteria,mybatis会找到非集合对象中的集合对象

批量查询

<select id="getEmpsByConditionForeach" resultType="Employee">
	select * from tbl_employee where id in
	<foreach collection="ids" item="item_id" separator="," open="(" close=")">
		#{item_id}
	</foreach>
</select>

注意:open属性把select * from tbl_employee where id in全扔进去都没问题。

Foreach批量保存
mysql1.支持 value(…,…,…),(…,…,…),(…,…,…),…
所以可以用foreach遍历集合,使用一次insert即可批量插入。

mysql2.但也可以通过";"间隔多个insert整体【该操作可以用于批量查询、修改、删除等等】,循环insert整体SQL,也可以达到批量插入的效果。这个需要MySQL开启【allowMultiQueries】
开启方法:在MySQL链接配置中的URL加入
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8?allowMultiQueries=true

<!-- foreach MySQL 批量保存 -->
<insert id="addEmps" >
	insert into tbl_employee(last_name, email, gender, d_id) values
	<foreach collection="emps" item="emp" separator=",">
		(#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.depName.id})
	</foreach>
</insert>
<!-- 可以通过发多个SQL实现批量操作(增删改查) -->
<!-- <insert id="addEmps" >
	<foreach collection="emps" item="emp" separator=";">
		insert into tbl_employee(last_name, email, gender, d_id) 
		values(#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.depName.id})
	</foreach>
</insert> -->

Oracle批量
Oracle不支持value(),(),(),…
Oracle1.使用mysql2.方法,foreach批量生成insert整体SQL.再使用begin insert1;insert2; end;包裹。
Oracle2.使用中间表,虚表的方法,嵌套联合union批量。

<insert id="addEmps" databaseId="Oracle">
	<!-- 第一种方式
		begin
		insert into tbl_employee(last_name, email, gender) values (employees_seq.nextval,"test01","test01@163.com","0");
		insert into tbl_employee(last_name, email, gender) values (employees_seq.nextval,"test02","test02@163.com","0");
		end
	-->
	<foreach collection="emps" item="emp" open="begin" close="end;">
		insert into tbl_employee(employee_id,last_name, email, gender) 
		values (employees_seq.nextval,#{emp.lastName},#{emp.email},#{emp.gender});
	</foreach>
	
	<!--第二种方式
	第一行表示插入到employee表
	第二行是从一个中间表中取值,接下来定义中间表
	中间表是直接从虚拟的表dual中取值,再组合到一起,取得的值应该要取个别名才能被第二行获取
	insert into employees(employee_id,last_name,email)
			select employees_seq.nextval,lastName,email from (
					select 'test01' lastName ,'test01@163.com' email from dual
					union
					select 'test02' lastName ,'test02@163.com' email from dual
					union
					select 'test02' lastName ,'test02@163.com' email from dual)
	-->
	<insert>
		insert into employees(employee_id,last_name,email)
		<foreach collection="emps" item="emp" separator="union" open="select employees_seq.nextval,lastName,email from (" close=")">
			select #{emp.lastName} lastName,#{emp.email} email,#{emp.gender} gender from dual
		</foreach>
    </insert>
</insert>
两个内置参数(常用)

_databaseId,_parameter

_parameter: 代表整个参数
            传入单个参数,那么_parameter就是这个参数
            传入多个参数,参数会被封装为一个map,_parameter就是这个map
_databaseId: 如果配置了DatabaseIdProvider标签,那么_databaseId就是当前数据库的别名

_databaseId作为判断条件与if标签同时使用(Oracle/MySQL)这样两个数据库查询不许要多写一遍select等标签及其属性。
当查询的对象为null就是查询所有,如果不为空则追加where条件,前提条件是根据不同数据库查询不同的数据库表。
Mapper:List<Employee> getEmpsInnerPatameter(Employee e);

<select id="getEmpsInnerPatameter" resultType="me.rsnomis.bean.Employee">
	<!--select * from tbl_employee where id=#{_parameter.id}-->
	<if test="_databaseId=='mysql'">
		select * from tbl_employee
		<if test="_parameter!=null">
			where id=#{_parameter.id}
		</if>
	</if>
	<if test="_databaseId=='Oracle'">
		select * from employees
	</if>
</select>
增删改查内部bind子标签

问题场景:模糊查询的%e%,双%%是不是可以作为固定的模式,以后只写e即可?
bind子标签作用:对无法再进行拼接额外字符串的变量进行拼接。
<bind name="_lastName" value="'%'+name+'%'"/>
name是定义新变量名,value进行原变量值拼接

<select id="getEmpsByNameBind" resultType="me.rsnomis.bean.Employee">
	select * from tbl_employee
	<bind name="_lastName" value="'%'+name+'%'"/>
	where last_name like #{_lastName}
</select>
sql标签(重复sql引用功能)

抽取常用SQL语句作为SQL标签(内含ID),在别的select标签等通过include子标签来重复引用,以refid属性指定引用。
SQL标签内部也可写if子标签等
常用:
1.SQL标签存储经常查询的列名,或者插入用的列名来方便引用。
2.include标签出了引用配置好的SQL标签内容,还可以自定义属性及值,配合${}符号取到自定义属性值。
过程

<sql id="insertColumn">
	<if test="_databaseId=='oracle'">
		employee_id,last_name,${zidingyi}
	</if>
	<if test="_databaseId=='mysql'">
		last_name,${zidingyi}
	</if>
</sql>

<insert>
	<!-- 1.If选择存储的value变量值
		 2.If是include引用的sql内部选择存储的数据库列名
		 3.include内部的property是为数据库列名自定义 -->
	<!-- insert Oracle列名 -->
	<if test="_databaseId=='oracle'">
		insert into employees(
		<include refid="insertColumn">
			<property name="zidingyi" value="abcd">
		</include>
		)
		<!-- value值Oracle foreach批量 -->
		<foreach collection="emps" item="emp" separator="union" open="select employees_seq.nextval,lastName,email from (" close=")">
        			select #{emp.lastName} lastName,#{emp.email} email,#{emp.gender} gender from dual
        </foreach>
	</if>
	<!-- insert MySQL列名 -->
	<if test="_databaseId=='mysql'">
		insert into tbl_employee(
		<include refid="insertColumn">
			<property name="zidingyi" value="abcd">
		</include>
		)
		<!-- value值MySQL foreach批量 -->
		<foreach collection="emps" item="emp" separator=",">
            (#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.depName.id})
        </foreach>
	</if>
</insert>

Mybatis緩存機制

由來:对于常用数据库表数据,如果没有对其进行更改,那只需查询一次即可。例如:菜单数据【导航栏栏目】只需要查一次,然后缓存起来即可。
共两级缓存。

一级缓存是在同一个SQLSession未关闭的情况下,且未手动刷新/删除cache的条件下,增删改查可以直接查询此SQLSession已有的cache。
二级缓存是在两个SQLSession,上一个SQLSession关闭的情况下,已存储的数据从一级cache转移至二级cache进行复用查询。

一级缓存:(局部缓存)

SQLSession局部缓存(无法手动关闭局部缓存,只能是手动清除,或关闭会话)
与数据库同一次会话期间查询到的数据会放到本地缓存中
以后获取相同的数据,直接从本地缓存中拿。
一级的本地缓存是自动Mybatis自带的。连续两次的Employee emp01,emo02为相同的mapper.getEmpId(),对象都是一个(emp01==emp02)返回true。

一级缓存失效情况

1.两个SQLSession查询同一个数据表查询条件相同,封装同一个类型对象(原因:每个SQLSession都有自己的局部缓存。不互通)
2.同一个SQLSession查询同一个数据表但查询条件更改,封装同一个类型对象。(数据内容都不一样,肯定需要重新查询。)
3.同一个SQLSession查询同一个数据表查询条件相同,封装同一个类型对象,但在两次完全相同的查询过程中执行了对该数据库表的增删改。(也同样需要重新查询,因为增删改可能会改变数据库表内容)
4.同一个SQLSession查询同一个数据表查询条件相同,封装同一个类型对象,但手动清空了一级缓存。
opSession.clearCache();

二级缓存:(全局缓存)

基于Mapper的namespace级别的缓存
使用步骤:
1.开启全局二级缓存Mybatis-config.xml(官方文档也有提示)
cacheEnabled=true默认开启【显示配置】

2.在各个Mapper.xml中开启cache变量。
<cache eviction="FIFO" size="1024"></cache>
有各种属性:blocking,eviction,flushInterval,readOnly,size,type。
全局cache
e=FIFO,f=毫秒,默认不清空(也可60000=1分钟)r=只读(只读为速度快,直接将引用地址交给user,可能造成user修改缓存内容,导致不安全)非只读,Mybatis会使用序列化/反序列技术克隆一份,安全。默认false。
size=多少个对象。(type=“”指定全局缓存的全类名路径,实现Cache接口即可。)
3.我们的POJO(javabean)需要实现序列化、反序列化接口。implements Serializable
4.每次Session会话提交或关闭才会将查询内容从一级缓存提升至二级缓存

分布式框架中使用本地缓存会造成脏数据,最好不要使用本地缓存 统一使用外部缓存 比如redis

cache相关设置

1.cacheEnabled=true只能影响二级缓存的使用。
2.每个select标签均有useCache属性,显式写为false也不影响一级缓存。影响二级缓存。
3.每个增删改标签insert、delete、update均有flushCache属性默认是true,删除一级缓存,二级缓存也会被清空,也就是增加了一条id为10的记录,再select查询后,仍然需要发送SQL。。在select标签中flushCache是默认为false,不清空缓存。
4.SQLSession.clearCache();哪个SQLSession调用,清空哪个的一级缓存。
5.全局配置Mybatis-config.xml中的localCacheScope设置=Session(当前会话的局部缓存)|statement(没有一级缓存)。默认是Session
cache缓存设置

cache接口

cache完全实现类s:
支持的缓存类型
public PerpetualCache implemenets Cache{}就是封装成Map
内部实现为:private Map<Object,Object> cache = new HashMap<Object,Object>()
关于Cache接口——由于Mybatis是专注于数据库连接的框架,对于缓存并不在行,故留下cache接口为redis提供入口。直接在继承cache接口的完全实现类中调用redis或者其他Ehcache等命令即可。

SQLSession对Cache使用的顺序

1.任何新会话进行增删改查,先查询二级Cache
2.再查询一级Cache
3.最后再去访问数据库
cache多级缓存使用顺序

第三方cache使用

1.导入第三方所需jar包
2.导入Mybatis与第三方的适配(adapter)jar包
3.在需要用的mapper.xml引用<cache type="全类名">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
第三方缓存使用

Mybatis Generate
  1. 新建一个Project,引入mbg的jar包。可以用6种方法创建在官网上都有
    6种方法

  2. 创建一个XML文件,用于逆向生成mapper接口+mapperXml动态SQL+bean类
    案例XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

	<!-- 
		targetRuntime="MyBatis3Simple":生成简单版的CRUD
		MyBatis3:豪华版
	
	 -->
  <context id="DB2Tables" targetRuntime="MyBatis3">
  	<!-- jdbcConnection:指定如何连接到目标数据库 -->
    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
        connectionURL="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"
        userId="root"
        password="Root123456">
    </jdbcConnection>

	<!--  -->
    <javaTypeResolver >
      <property name="forceBigDecimals" value="false" />
    </javaTypeResolver>

	<!-- javaModelGenerator:指定javaBean的生成策略 
	targetPackage="test.model":目标包名
	targetProject="\MBGTestProject\src":目标工程
	-->
    <javaModelGenerator targetPackage="com.atguigu.mybatis.bean" 
    		targetProject=".\src">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>

	<!-- sqlMapGenerator:sql映射xml生成策略: -->
    <sqlMapGenerator targetPackage="com.atguigu.mybatis.dao"  
    	targetProject=".\conf">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>

	<!-- javaClientGenerator:指定mapper接口所在的位置 -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.dao"  
    	targetProject=".\src">
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

	<!-- 指定要逆向分析哪些表:根据表要创建javaBean -->
    <table tableName="tbl_dept" domainObjectName="Department"></table>
    <table tableName="tbl_employee" domainObjectName="Employee"></table>
  </context>
</generatorConfiguration>

  1. 随便找个类,或者Junit测试类运行一下,自动生成Mapper接口+xml以及Bean类
	List<String> warnings = new ArrayList<String>();
	boolean overwrite = true;
	File configFile = new File("generatorConfig.xml");
	ConfigurationParser cp = new ConfigurationParser(warnings);
	Configuration config = cp.parseConfiguration(configFile);
	DefaultShellCallback callback = new DefaultShellCallback(overwrite);
	MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
	myBatisGenerator.generate(null);
  1. 关与MBG生成的Mapper使用
    生成简单Mapper使用
	@Test
	public void testMyBatis3Simple() throws IOException{
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try{
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			List<Employee> list = mapper.selectByExample(null);
			for (Employee employee : list) {
				System.out.println(employee.getId());
			}
		}finally{
			openSession.close();
		}
	}

复杂mapper使用【QBC风格–Hibernate】

	@Test
	public void testMyBatis3() throws IOException {
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			// xxxExample就是封装查询条件的
			// 1、查询所有,用null即可
			// List<Employee> emps = mapper.selectByExample(null);
			// 2、查询员工名字中有e字母的,和员工性别是1的
			// 封装员工查询条件的example
			EmployeeExample example = new EmployeeExample();
			// 创建一个EmployeeExample类的Criteria,这个Criteria就是拼装查询条件
			// select id, last_name, email, gender, d_id from tbl_employee
			// WHERE ( last_name like ? and gender = ? ) or email like "%e%"
			Criteria criteria = example.createCriteria();
			criteria.andLastNameLike("%e%");
			criteria.andGenderEqualTo("1");

			
			Criteria criteria2 = example.createCriteria(); criteria2.andEmailLike("%e%");
			example.or(criteria2);
			 

			List<Employee> list = mapper.selectByExample(example);
			for (Employee employee : list) {
				System.out.println(employee.getId());
			}
		} finally {
			openSession.close();
		}
	}

注意要点:
Mybatis Generate是一个单独的程序,只是将mapper接口mapperXML以及bean类装入指定的类路径下
Mybatis框架运行的时候仍需要自己的Mybatis-config.xml,数据源等等配置

Mybatis运行原理

Mybatis框架构思划分

layer

Configuration重要对象

Configuration重要对象

Configuration两个重要属性
  1. mappedstatements
    这里存的是所有Mapper的全部CRUD标签SQL语句内容
    mappedstatements重要对象

  2. mapperRegistry
    这个属性下有子属性,knownMappers存的是MapperxyFactory代理对象工厂
    mapperRegistry重要对象

核心步骤
	 * 1、获取sqlSessionFactory对象:
	 * 		解析全局(xml)+独立mapper(xml文件)的每一个信息保存在Configuration中,也绑定了接口与代理对象
	 *		返回一个包含Configuration的DefaultSqlSession;
	 * 		注意:【MappedStatement】:代表一个增删改查的详细信息

	 SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

实际源码调用过程
SQLSessionFactory

	 * 2、获取sqlSession对象
	 * 		返回一个DefaultSQlSession对象,包含Executor和Configuration,此处也创建了事务管理;
	 * 		这一步会创建Executor对象;如果有二级缓存则会对封装为CachingExecutor对象/没有就是SimpleExecutor
	 *		此步也是插件plugin、拦截器工作(包装executor)的地方

	 SqlSession openSession = sqlSessionFactory.openSession();

实际源码调用过程:
defaultSQLSession

	 * 3、获取接口的代理对象(MapperProxy)
	 *		getMapper,先通过Configuration对象的MapperRegistry的knownMappers
	 *		【存储的是所有接口,每个接口对应一个MPF,knownMappers是一个Map集合】存的是每个接口的代理对象创建工厂
	 * 		根据getMapper传入的接口类型,使用对应的MapperProxyFactory创建这个接口的MapperProxy的代理对象
	 *		将第2步产生的sqlSession对象传入MapperProxyFactory创建一个mapperProxy对象
	 *		(实现了jdk动态代理的接口InvocationHandler)再带入java.lang.reflect.Proxy的new Instance方法
	 *		生成并返回真正的Java Proxy实例——动态代理对象mapper。
	 * 		代理对象里面包含了,DefaultSqlSession(Executor)及mapperInterface,methodCache

	 EmployMapper接口 mapper代理对象 = SQLSession.getMapper(EmployMapper.class接口);

实际源码调用过程:
getMapper

	 * 4、执行增删改查方法
	 *		由于mapper实现了jdk动态代理的接口InvocationHandler,所以先执行jdk的invoke
	 *		这个invoke会先判断执行的方法类型,如果是Object原生类的方法,直接执行,如果是Mapper方法的则封装为MapperMethod对象
	 *		然后再使用mapperMethod.execute(sqlSession,参数列表),execute会先判断是CRUD的哪个类型
	 *		再判断返回值类型,Map,void,多个,Cursor游标,然后再对参数进行封装,如果是一个直接返回,如果是多个则封装为Map
	 *		使用Mybatis封装好的,查询一个DefaultSQLSession.selectone(executeForMany\Map\Cursor)
	 *		即便是查询单个,但是调用的仍是selectList<T>,只不过返回的是第一个list.get(0);
	 *		selectList调用的是CachingExecutor.query()【包装传入的参数,如果是list则包装在map中】
	 *		从MappedStatement对象中获取boundSQL对象,这个包含了SQL语句,参数的映射关系,#{}占位符内容,拼装为完整的SQL
	 *		CachingExecutor是局部缓存,之后会ms.getCache(),判断是否开启二级缓存(全局/其他缓存)如果有则获取,没获取到
	 *		或没有二级缓存,就会调用SimpleExecutor/其他的。查询后,立即存入localcache中。
	 *		simpleExecutor也是调用baseExecutor进行查询,使用原生的jdbc,从MappedStatement对象获取Configuration
	 *		以获取到的Configuration创建statementHandler,默认构造的是prepareStatement参数预编译(连接,事务属性)
	 *		预编译参数产生SQL的方法是调用ParameterHandler,同时创建ResultSetHandler处理结果集
	 *		ParameterHandler内部调用DefaultParameterHandler创建,在DPH中调用typeHandler进行数据库类型和javaBean类型的映射
	 		typeHandler.setParameter(ps, i + 1, value, jdbcType);
	 *		同样的ResultSetHandler也是内部调用DefaultResultSetHandler创建,在DRS中调用typeHandler进行数据库类型和Bean类型映射
	 		typeHandler.getResult(rs, column);
	 *		最后环境、参数、结果都预先设置完后,使用JDBC的数据库访问(PreparedStatement)
	 *		将查询的结果,使用resultSetHandler.handleResultSet()【获取参数映射等】封装处理。

	 mapper.getEmpById();

查询·文字简化总结
查询过程图总结

查询·全流程总结
全流程query总结

查询·boundSql对象
boundSql对象

查询·cachekey属性
cachekey

全流程简化总结
	 * 1、获取sqlSessionFactory对象:
	 * 		解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
	 * 		注意:【MappedStatement】:代表一个增删改查的详细信息
	 * 
	 * 2、获取sqlSession对象
	 * 		返回一个DefaultSQlSession对象,包含Executor和Configuration;
	 * 		这一步会创建Executor对象;
	 * 
	 * 3、获取接口的代理对象(MapperProxy)
	 * 		getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
	 * 		代理对象里面包含了,DefaultSqlSession(Executor)
	 * 4、执行增删改查方法
	 * 
	 * 总结:
	 * 	1、根据配置文件(全局,sql映射)初始化出Configuration对象
	 * 	2、创建一个DefaultSqlSession对象,
	 * 		他里面包含Configuration以及
	 * 		Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
	 *  3、DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
	 *  4、MapperProxy里面有(DefaultSqlSession);
	 *  5、执行增删改查方法:
	 *  		1)、调用DefaultSqlSession的增删改查(Executor);
	 *  		2)、会创建一个StatementHandler对象。
	 *  			(同时也会创建出ParameterHandler和ResultSetHandler)
	 *  		3)、调用StatementHandler预编译参数以及设置参数值;
	 *  			使用ParameterHandler来给sql设置参数
	 *  		4)、调用StatementHandler的增删改查方法;
	 *  		5)、ResultSetHandler封装结果

	 *	四大对象,Executor,StatementHandler,ParameterHandler,ResultSetHandler,
	 *  注:
	 *  	四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);

Mybatis-config.xml全局配置文件
作用1:通过全局xml令SQLSessionFactory获取到独立mapper.xml文件
独立mapper.xml
namespace作用:绑定接口java文件

对xml文件,都是用专用的XML解析器【XMLConfigBuilder】来解析,对于独立mapper.xml中的(selec/insert/update/delete)
CRUD标签使用专用的statementParser【StatementBuilder】SQL解析器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值