Mybatis 3.0
历史及资源
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编写
中部的黑盒是不可自定义的。如果想要,需要学习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自动识别】
其他参数
重要参数
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 && email.trim()!=""">
and email = #{email}
</if>
这里的trim()是OGNL自带函数取值,"
是双引号"(这个双引号就是)转义字符,意思是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 && email.trim()!=""">
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。
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完全实现类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使用
1.导入第三方所需jar包
2.导入Mybatis与第三方的适配(adapter)jar包
3.在需要用的mapper.xml引用<cache type="全类名">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
Mybatis Generate
<?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>
- 随便找个类,或者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);
- 关与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框架构思划分
Configuration重要对象
Configuration两个重要属性
-
mappedstatements
这里存的是所有Mapper的全部CRUD标签SQL语句内容
-
mapperRegistry
这个属性下有子属性,knownMappers
核心步骤
* 1、获取sqlSessionFactory对象:
* 解析全局(xml)+独立mapper(xml文件)的每一个信息保存在Configuration中,也绑定了接口与代理对象
* 返回一个包含Configuration的DefaultSqlSession;
* 注意:【MappedStatement】:代表一个增删改查的详细信息
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
实际源码调用过程
* 2、获取sqlSession对象
* 返回一个DefaultSQlSession对象,包含Executor和Configuration,此处也创建了事务管理;
* 这一步会创建Executor对象;如果有二级缓存则会对封装为CachingExecutor对象/没有就是SimpleExecutor
* 此步也是插件plugin、拦截器工作(包装executor)的地方
SqlSession openSession = sqlSessionFactory.openSession();
实际源码调用过程:
* 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接口);
实际源码调用过程:
* 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();
查询·文字简化总结
查询·全流程总结
查询·boundSql对象
查询·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解析器