MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
对jdbc的封装框架有哪些:Hibernate,dbutils,jdbcTemplate[spring],mybatis
原理:
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
以上是普通jdbc数据库操作,缺点
- 数据库连接频繁开启和关闭,会严重影响数据库的性能。
- 代码中存在硬编码,分别是数据库部分的硬编码和SQL执行部分的硬编码。
- 1、mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的信息。
- 2、mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
- 3、通过SqlSessionFactory,可以创建SqlSession即会话。Mybatis是通过SqlSession来操作数据库的。
- 4、SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
- 5、Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括HashMap集合对象、POJO对象类型。
SqlMapConfig.xml
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/study?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
configuration>
在classpath下,创建sqlmap文件夹,在其下创建User.xml映射文件
<mapper namespace="test">
<select id="findUserById" parameterType="int" resultType="com.shunxu.model.User">
SELECT * FROM USER WHERE id = #{id}
select>
mapper>
在SqlMapConfig.xml中加载User.xml
<mappers>
<mapper resource="sqlmap/User.xml"/>
mappers>
以上测试代码是mybatis单独使用的方法,但实际上会同spring一起使用,对象的操作都会交给bean,比上述代码简单很多。
<select id="findUserByName" parameterType="String" resultType="com.shunxu.model.User">
SELECT * FROM USER WHERE username like '%${value}%'
select>
<insert id="insertUser" parameterType="com.shunxu.model.User">
INSERT INTO USER (username,sex,birthday,address)
VALUES(#{username},#{sex},#{birthday},#{address})
insert>
<delete id="deleteUser" parameterType="int">
DELETE FROM USER WHERE id=#{id}
delete>
<update id="updateUser" parameterType="com.shunxu.model.User">
UPDATE USER SET username=#{username},sex=#{sex} WHERE id=#{id}
update>
通过sql函数获得:SELECT LAST_INSERT_ID()
<insert id="insertUser" parameterType="com.gyf.domain.User">
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
selectKey>
INSERT INTO USER (username,sex,birthday,address)
VALUES(#{username},#{sex},#{birthday},#{address})
insert>
<insert id="insertUser" parameterType="com.gyf.domain.User">
<selectKey keyProperty="id" resultType="String" order="BEFORE">
SELECT UUID()
selectKey>
INSERT INTO USER (username,sex,birthday,address)
VALUES(#{username},#{sex},#{birthday},#{address})
insert>
parameterType和resultType
- parameterType指定输入参数的java类型,可以填写别名或Java类的全限定名。
resultType指定输出结果的java类型,可以填写别名或Java类的全限定名。
#{}和${}
- #{}:相当于预处理中的占位符?。
#{}里面的参数表示接收java输入参数的名称。
#{}可以接受HashMap、POJO类型的参数。
当接受简单类型的参数时,#{}里面可以是value,也可以是其他。
#{}可以防止SQL注入。- ${}:相当于拼接SQL串,对传入的值不做任何解释的原样输出。
${}会引起SQL注入,所以要谨慎使用。
${}可以接受HashMap、POJO类型的参数。
当接受简单类型的参数时,${}里面只能是value。
selectOne和selectList
- selectOne:只能查询0或1条记录,大于1条记录的话,会报错:
- selectList:可以查询0或N条记录
Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。
Mybatis会自动的为mapper接口生成动态代理实现类。
不过要实现mapper代理的开发方式,需要遵循一些开发规范。
别名是使用是为了在映射文件中,更方便的去指定参数和结果集的类型,不再用写很长的一段全限定名。
<mapper resource=’’/>
使用相对于类路径的资源
如:<mapper resource="sqlmap/User.xml" />
-----------------------------------------
<mapper class=’’/>
使用mapper接口的全限定名
如:<mapper class="cn.shunxu.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;
-----------------------------------------
<package name=’’/>(推荐)
注册指定包下的所有映射文件
如:<package name="cn.shunxu.mybatis.mapper"/>
注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;
可以将xml文件删除,所有的sql语句都在mapper接口方法中写
指定输入参数的java类型,可以使用别名或者类的全限定名。它可以接收简单类型,POJO对象、HashMap。
开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
例如:
综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。
输出映射与输出映射的用法基本一致,因此这里针对resultType和resultMap各举一个典型例子
resultType
- 使用resultType进行结果映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功。
- 如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象;
- 如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象。
输出POJO列表
如果查询出来的列名和属性名不一致(即数据库字段与model字段不一致),通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
- 1、定义resultMap
- 2、使用resultMap作为statement的输出映射类型
if标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。(test中的参数是property而不是column,且所有特殊字符都需要转义,逻辑运算符也得转义,因此建议使用英文逻辑运算符)
注意:
将某一段查询语句单独抽出来,然后通过引用的方式实现“到处使用”
xml
还可以加上index属性,当为list指索引,当为map指key。
测试
此外,foreach常用于批量保存
for-each本质上是拼接sql字符串
,因此,凡是可遍历生成的字符串,都可以用foreach,比如连接url设置allowMultiQueries=true,可以通过for-each发起多个sql语句
需求:如果带了id,就用id查询,如果带了username,就用username查;二选其一。choose相当于带了bread的switch-case(因此是按顺序进入when)
<select id="getByIdOrUsername" resultType="xx.User">
select * from user
<where>
<choose>
<when test="id!=null">
id=#{id}
when>
<when test="userName!=null">
username like #{userName}
when>
<otherwise>
1=1
otherwise>
choose>
where>
select>
显然,mybatis是以一个model为基本单位,当查询涉及到多个model时,就需要关联查询
orders和orderdetail:
orderdetail和items:
需求:
根据商品ID查找订单,包括用户名和地址
SQL语句:
#查找某个定单id的信息,包括用户名字和地址
SELECT o.*,u.username,u.address FROM orders o,user u
WHERE o.user_id = u.id AND o.id = 3
复杂查询时,单表对应的po类已不能满足输出结果集的映射。所以要根据需求建立一个扩展类来作为resultType的类型。
掌握association用法
resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果没有查询结果的特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的对象属性中。
resultMap可以实现延迟加载,resultType无法实现延迟加载。
掌握collection用法
需求:根据订单ID查找订单信息、用户信息和订单明细
SQL
Select
orders.id,
orders.user_id,
orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from
orders,user,orderdetail
where
orders.user_id = user.id
and orders.id = orderdetail.orders_id
and orders.id = #{?};
SELECT
o.*,
u.username,
u.address,
od.id detail_id,
od.items_id,
od.items_num
FROM
orders o,
user u,
orderdetail od
WHERE
o.user_id = u.id
AND o.id = od.orders_id
AND o.id = 3
使用resultType实现:
需求:查询用户信息及用户购买的商品信息,要求将关联信息映射到主pojo的pojo属性中
SELECT
u.id,
u.username,
u.address,
o.id order_id,
o.number,
o.createtime,
o.note,
od.id detail_id,
od.items_id,
od.items_num,
it.name,
it.price,
it.detail
FROM
user u,
orders o,
orderdetail od,
items it
WHERE
o.user_id = u.id
AND o.id = od.orders_id
AND od.items_id = it.id;
思路:
- 将用户信息映射到user中。
- 在user类中添加订单列表属性List
orderslist,将用户创建的订单映射到orderslist - 在Orders中添加订单明细列表属性List
detailList,将订单的明细映射到detailList - 在Orderdetail中添加Items属性,将订单明细所对应的商品映射到Items
懒加载又叫延时加载,也叫按需加载。也就是说先加载主信息,需要的时候,再去加载信息。
在mybatis中,resultMap标签 的association标签和collection标签具有懒加载的功能。
Usermapper.xml
OrdersMapper.xml
请记住:缓存只针对“查询”操作,其他操作会清除\更新缓存。
Mybatis的缓存,包括一级缓存和二级缓存,一级缓存是默认使用的。二级缓存需要手动开启。
一级缓存指的就是sqlsession,同一个sqlSession的同一条查询语句结果会被缓存。
在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。
一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。
原理:
由于一级缓存是sqlSession级别的,在spring中,也可理解为是一个事务级别的,只有在一个事务中的相同查询,一级缓存才有效。
不同的namespace缓存放不同的map。
缓存是一种保存操作,对象会被序列化到本地,因此,所有对象都必须实现serializable接口
刷新缓存:
注意,二级缓存实际放的是一级缓存,因此,只有当一级缓存的sqlSession关闭后,二级缓存才有数据。
应用需求:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
局限性
Mybatis二级缓存对细粒度的数据,缓存实现不好。
应用场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。
mybatis本身的缓存实现不太好,因此本节没有详细解释用法,仅仅是罗列概念。
在springboot中将学习更好的缓存框架。
mybatis的缓存只是“意思意思”,实际上不会真的用。一般使用第三方缓存框架
mybatisplus是对mybatis的封装操作,能够实现更加强大的功能
mybatisplus官网
具体的使用可以查看官网(中文),我会在springboot中将会介绍它的入门使用。
推荐一个不错的学习博文
链接
mybatis映射文件:
#和$的区别:
resultMap详解:
property
指定返回类型,select="mapper接口全方法名"
指定调用方法,column
指定为该方法传入参数的参数,该参数是第一步的查询结果的某个字段名,如果需要传入多个则使用column={property=column, property=column}
(视频中的例子,User的Javabean中没有dept_id,但是数据表有)
<resultMap type="xxx.User" id="testMap">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<association property="department" select="xxx.departmentMapper.getById" column="dept_id">
<id column="id" property="id"/>
<result column="name" property="name"/>
association>
resultMap>
<select id="getByStep" resultMap="testMap">
select * from user where id=#{id}
select>
分步查询每步都会发起一次查询语句,因此性能上可能会有问题。lazyLoadingEnabled=true
aggressiveLazyLoading=false
(侵入式懒加载,意思是如果是分步查询,那就将每一步的都加载出来,我们需要关闭这个功能)(有时候默认值就是我们想要的,但是也应该显示的指定我们想要的值,说明我们用到了,也避免版本更新带来的错误)<resultMap type="xx.Department" id="testMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="users" ofType="xx.User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
collection>
resultMap>
<select id="getUsersByDeptId" resultMap="testMap">
select * d.id did,d.name dname,u.id uid,u.user_name userName
from department d
left join user u
on u.dept_id=d.id
where d.id=#{id}
select>
collection也有select,支持分步查询.<resultMap type="xxx.Department" id="testMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<discriminator javaType="string" column="gender">
<case value="0" resultType="xx.User">
<association property="users" select="xxx.UserMapper.getAllByDeptId" column="id">
<id column="id" property="id"/>
<result column="name" property="name"/>
association>
case>
<case value="1" resultType="xx.User">
<result column="username" property="email"/>
case>
<discriminator>
resultMap>
<select id="getByStep" resultMap="testMap">
select * from user where id=#{id}
select>
底层执行步骤:
SqlSessionFactory
,因此框架的第一步就是构建SqlSessionFactorysqlSession
对象,sqlSession能够通过唯一标识
执行已经映射的sql语句(Mapped sql)
,执行完毕需要关闭。唯一标识
:Mapped sql的id,且包含namespace。Mapped sql
:就是包含sql语句的xxMapper.xml,需要将其注册到全局配置文件的Mappers中才能生效。接口式执行步骤:
非线程安全的对象(如sqlSession),不允许将其作为类属性作为类共享属性,而应该在调用处,每次获取新的对象,否则会出现竞争。
Mapper接口没有实现类,但是mybatis为其生成了代理对象。
mybatis通过TypeHandlers实现Java和mysql对象的转换。
mybatis四大对象:
在这步,拿到了四大对象之一的executor(第七步很重要,利用了插件包装executor)
* 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封装结果
* 注意:
* 四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);
如下图所示:
MyBatis在四大对象的创建过程中,都会有插件进行介入。
插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: