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数据库操作,缺点
SqlMapConfig.xml
<?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="development">
<environment id="development">
<!-- 配置JDBC事务控制,由mybatis进行管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,采用dbcp连接池 -->
<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映射文件
<?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:命名空间,它的作用就是对SQL进行分类化管理,可以理解为SQL隔离
注意:使用mapper代理开发时,namespace有特殊且重要的作用
-->
<mapper namespace="test">
<!--
[id]:statement的id,要求在命名空间内唯一
[parameterType]:入参的java类型
[resultType]:查询出的单条结果集对应的java类型
[#{}]: 表示一个占位符?
[#{id}]:表示该占位符待接收参数的名称为id。注意:如果参数为简单类型时,#{}里面的参数名称可以是任意定义
-->
<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,比上述代码简单很多。
<!--
[${}]:表示拼接SQL字符串
[${value}]:表示要拼接的是简单类型参数。
注意:
1、如果参数为简单类型时,${}里面的参数名称必须为value
2、${}会引起SQL注入,一般情况下不推荐使用。但是有些场景必须使用${},比如order by ${colname}
-->
<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>
如果主键的值是mysql自增,则此处不需要给ID赋值
<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标签]:通过select查询来生成主键
[keyProperty]:指定存放生成主键的属性
[resultType]:生成主键所对应的Java类型
[order]:指定该查询主键SQL语句的执行顺序,相对于insert语句
[last_insert_id]:MySQL的函数,要配合insert语句一起使用 -->
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
<!-- 如果主键的值是通过MySQL自增机制生成的,那么我们此处不需要再显示的给ID赋值 -->
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
#{}和${}
selectOne和selectList
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对象
开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
例如: 综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。
输出映射与输出映射的用法基本一致,因此这里针对resultType和resultMap各举一个典型例子
resultType
输出POJO列表
如果查询出来的列名和属性名不一致(即数据库字段与model字段不一致),通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
if标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。(test中的参数是property而不是column,且所有特殊字符都需要转义,逻辑运算符也得转义,因此建议使用英文逻辑运算符)
注意:
将某一段查询语句单独抽出来,然后通过引用的方式实现“到处使用”
例子:查询指定id用户 java
xml
还可以加上index属性,当为list指索引,当为map指key。 测试
或者直接传入id集合
此外,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时,就需要关联查询
数据库信息如下:
user和orders:
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用法
掌握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属性中
SQL
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;
思路:
懒加载又叫延时加载,也叫按需加载。也就是说先加载主信息,需要的时候,再去加载信息。 在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详解:
基本用法:如果查询出来的列名和Javabean属性名不一致,可以通过resultMap将列名将Javabean属性名之间作一个映射关系。
高级用法():实现关联查询
底层执行步骤:
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 允许使用插件来拦截的方法调用包括: