1.MyBatis简介
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
回顾
对jdbc的封装框架有哪些:
Hibernate,dbutils,jdbcTemplate[spring],mybatis
2.MyBatis的框架核心(重要)
2.1两个配置
- Mybatis全局配置文件SqlMapConfig.xml
作用:配置数据源(与spring结合后就交给spring了),加载映射文件,给模型取别名
<!--取别名-->
<typeAliases>
<!--方式1:单个的取别名,不实用-->
<!--<typeAlias type="com.gyf.model.User" alias="user"></typeAlias>-->
<!--方式2:给包取别名,批量实用,默认别名为类名(首字母大小写都可以)-->
<package name="com.gyf.model"></package>
<package name="com.gyf.vo"></package>
</typeAliases>
- Mybatis映射文件 mapper.xml
作用:构造出SqlSessionFactory,即会话工厂
2.2两个对象
- SqlSessionFactory
作用:开启会话
sqlSession = sqlSessionFactory.openSession();
- sqlSession
作用: 操作数据库
//selectOne查询一条结果
User user = sqlSession.selectOne("findUserById",25);
3.映射文件内的普通标签
- #{}:占位符 (可以防止sql注入问题)
- ${}:拼接sql(常用于模糊查询拼接字符串,例如:拼接模型参数:LIKE ‘%${user.username}%’ 拼接简单类型参数:LIKE ‘%${value}%’ )
- parameterType:接口中传入参数的类型(如果参数是简单类型,则#{}内任意命名,如果是模型,名称必须是模型的属性字段名)
- resultType:接口方法的返回值类型(用于sql查询的结果表列名和model的属性名一样)
- resultMap:接口方法的返回值类型(用于sql查询的结果表列名和model的属性名不一样,使使用前提,需要自己配置resultMap映射关系,用于复杂的关联查询,模型里有模型、集合等)
4. 动态SQL标签(if、where、foreach、sql片段)
- if和where( 注意:用if进行判断是否为空时,1.要判断null,2.要判断空字符串‘’《集合和数组就要判断size>0》;)
用法:
<select id="findUserListDSQL" parameterType="userQueryVo" resultType="user">
select * from user
<where>
<if test="user != null">
<if test="user.sex != null and user.sex != ''">
sex = #{user.sex}
</if>
<if test="user.username != null and user.username != ''">
and username like '%${user.username}%'
</if>
</if>
</where>
</select>
- sql片段(提高SQL的可重用性)
用法:
<sql id="select_user_where">
<if test="user != null">
<if test="user.sex != null and user.sex != ''">
sex = #{user.sex}
</if>
<if test="user.username != null and user.username != ''">
and username like '%${user.username}%'
</if>
</if>
</sql>
<select id="findUserListDSQL" parameterType="userQueryVo" resultType="user">
select * from user
<where>
<!--refid 是sql片段的id -->
<include refid="select_user_where"></include>
</where>
</select>
- foreach
用法1:参数是集合
<!--SELECT * from user where id IN(1,10,16)-->
<select id="findUserByIds" parameterType="userQueryVo" resultType="user">
select * from user
<where>
<if test="ids != null and ids.size > 0">
<!--collection:集合,写集合属性
item:遍历接收变量
open:遍历开始
close:遍历结束
separator:拼接格式
类似于java里的增强for循环
for(Integer id : ids){
}
-->
<!--userQueryVo模型里面的集合属性ids -->
<foreach collection="ids" item="id" open="id in (" close=")" separator=",">
${id}
</foreach>
</if>
</where>
</select>
用法2:参数是数组
<!-- 参数是数组
如果参数是数组的话,parameterType可以写全名【java.util.List】,也可以写别名list
遍历或者判断的时候,都用list变量
-->
<!--SELECT * from user where id IN(1,10,16)-->
<select id="findUserByIds2" parameterType="list" resultType="user">
select * from user
<where>
<if test="list != null and list.size > 0">
<foreach collection="list" item="id" open="id in (" close=")" separator=",">
${id}
</foreach>
</if>
</where>
</select>
5.映射文件内的关联查询用到的标签(在 resultMap里用)
- association
作用于模型里有模型,类型用javaType
- collection
作用于模型里有集合或者集合里有集合,类型用ofType
6. mybatis与hibernate的区别【面试题】
- Mybatis技术特点
好处:
1、 通过直接编写SQL语句,可以直接对SQL进行性能的优化;
2、 学习门槛低,学习成本低。只要有SQL基础,就可以学习mybatis,而且很容易上手;
3、 由于直接编写SQL语句,所以灵活多变,代码维护性更好。
缺点:
4、 不能支持数据库无关性,即数据库发生变更,要写多套代码进行支持,移植性不好。
a) Mysql:limit
b) Oracle:rownum
5、 需要编写结果映射。
应用场景:需求多变的互联网项目,例如电商项目。
- Hibernate技术特点
好处:
1、 标准的orm框架,程序员不需要编写SQL语句。
2、 具有良好的数据库无关性,即数据库发生变化的话,代码无需再次编写。
a) 以后,mysql数据迁移到oracle,只需要改方言配置
缺点:
3、 学习门槛高,需要对数据关系模型有良好的基础,而且在设置OR映射的时候,需要考虑好性能和对象模型的权衡。
4、 程序员不能自主的去进行SQL性能优化。
应用场景:需求明确、业务固定的项目,例如OA项目、ERP项目等。
7.关联查询类型(一对一、一对多、多对多)
- 一对一 resultType实现
复杂查询时,单表对应的po类已不能满足输出结果集的映射。所以要根据需求建立一个扩展类(继承原模型)来作为resultType的类型。
<!--1.一对一 定单的扩展类(接收返回值,用resultType)-->
<select id="findOrdersById" parameterType="int" resultType="ordersExt">
SELECT o.*,u.username,u.address
FROM orders o,`user` u
WHERE o.user_id = u.id AND o.id = #{id}
</select>
- 一对一 resultMap实现(掌握association的使用)
根据sql查询出来的表字段名,涉及到多张表的字段,结合已有的模型(一表一模型),无法接收表的返回值,这时让模型里再装缺少的模型,给set、get方法。在resultMap,用association给模型里的模型配置映射。
<!--2.一对一 模型里有模型(接收返回值,用resultMap )-->
<resultMap id="orderResultMap" type="orders">
<!--往orders的模型匹配数据-->
<id property="id" column="id"></id>
<result property="number" column="number"></result>
<result property="createtime" column="createtime"></result>
<result property="note" column="note"></result>
<!--往orders的user匹配数据
模型里模型,使用association来配置
-->
<association property="user" javaType="user">
<id property="id" column="user_id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
</association>
</resultMap>
<select id="findOrdersById2" parameterType="int" resultMap="orderResultMap">
SELECT o.*,u.username,u.address
FROM orders o,`user` u
WHERE o.user_id = u.id AND o.id = #{id}
</select>
- 一对多 resultMap实现(掌握collection的使用)
作用于模型里有集合属性,给这个属性配置映射关系
<resultMap id="orderResultMap3" type="orders">
<!--往orders的模型匹配数据-->
<id property="id" column="id"></id>
<result property="number" column="number"></result>
<result property="createtime" column="createtime"></result>
<result property="note" column="note"></result>
<!--往orders的user匹配数据
模型里有模型,使用association来配置
-->
<association property="user" javaType="user">
<id property="id" column="user_id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
</association>
<!--往orders的orderDetails匹配数据
模型里有集合,使用collection来配置,模型类型用ofType
-->
<collection property="orderDetails" ofType="orderDetail">
<id property="detailId" column="detail_id"></id>
<result property="itemsId" column="items_id"></result>
<result property="itemsNum" column="items_num"></result>
</collection>
</resultMap>
<select id="findOrdersById3" parameterType="int" resultMap="orderResultMap3">
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 = #{id}
</select>
- 多对多 resultMap实现(一对一和一对多的综合运用)
适用于模型1里有集合模型1,集合模型1里有集合模型2,集合模型2里有模型2等等,复杂的关联查询
<resultMap id="duoduiduo" type="user">
<!--往user模型匹配数据-->
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<!--往user的orders匹配数据,orders是集合,模型里有集合,用collection配置,类型用ofType-->
<collection property="orders" ofType="orders">
<id property="id" column="orders_id"/>
<result property="number" column="number"/>
<result property="createtime" column="createtime"/>
<result property="note" column="note"/>
<!--往orders的orderDetails匹配数据,orderDetails也是集合,集合里有集合,还是用collection配置,类型用ofType-->
<collection property="orderDetails" ofType="orderDetail">
<id property="detailId" column="detail_id"/>
<result property="itemsId" column="items_id"/>
<result property="itemsNum" column="items_num"/>
<!--往orderDetails的item匹配数据,item是模型,用association,类型用javaType-->
<association property="item" javaType="items">
<result property="name" column="name"/>
<result property="detail" column="detail"/>
</association>
</collection>
</collection>
</resultMap>
<select id="findUserAndItemInfo" resultMap="duoduiduo">
SELECT u.id,u.username,u.address,
o.id orders_id,o.number,o.createtime,o.note,
od.id detail_id,od.items_id,od.items_num,it.name,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
</select>
8.延时加载(懒加载)
先加载主信息,在需要的时候,再去加载从信息。节省资源。
<!--============================懒加载(延迟加载),!!!起作用需要在全局配置文件里设置setting 设为true===============================-->
<resultMap id="lazyLoading" type="orders">
<id property="id" column="id"/>
<result property="user_id" column="user_id"/>
<result property="number" column="number"/>
<result property="createtime" column="createtime"/>
<result property="note" column="note"/>
<!--配置orders里的模型user属性使用懒加载,模型里有模型,使用association,!!!这种方式的前提是有public User findUserById(int id);这个方法-->
<association property="user" select="com.gyf.mapper.UserMapper.findUserById" column="user_id"></association>
</resultMap>
<select id="findOrdersAndUserByLazyLoading" resultMap="lazyLoading">
select * from orders
</select>
sqlMapConfig.xml内配置
<settings>
<!--懒加载开关-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
9.查询缓存(一级、二级)
一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。
二级缓存指的就是同一个namespace下的mapper,即SqlSessionFactory
9.1一级缓存使用(默认开启)
- 解释
mybatis默认使用开启了以及缓存,一级缓存就是指sqlSession,通过它分别先后获取同一个user对象,默认只执行一次sql,第二次同样的查询,会从一级缓存里直接获取;如果在两次获取对象中间加入了更新操作(删除,修改,删除),则一级缓存会被清空,前面的user2,将会自己执行sql语句,查询,此过程有3次sql查询。
- 原理图
- 代码演示
/**
* 一级缓存SqlSession
* @throws IOException
*/
@Test
public void test1() throws IOException {
//1.读取配置文件;
String resouce = "SqlMapConfig.xml";
InputStream is = Resources.getResourceAsStream(resouce);
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂。
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory ssf = sqlSessionFactoryBuilder.build(is);
//3.获取sqlSession对象
SqlSession sqlSession = ssf.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findUserById(1);
System.out.println("user1:"+user1);
//保存操作,加入此操作,一级缓存将被清空,后面同样的查询需要再执行sql语句(若此处加入修改,删除操作,一级缓存也会被清空)
userMapper.save(new User("广东雨神",null,null,"广东"));//不加入,则查询两次id=1,就只用执行一次sql
User user2 = userMapper.findUserById(1);
System.out.println("user2:"+user2);
//提交并关闭sqlSession
sqlSession.commit();
sqlSession.close();
}
9.2二级缓存使用(手动开启)
- 解释
二级缓存就是指SqlSessionFactory,清空情况跟以及缓存一样
- 使用前提条件
- 1.在全局配置文件setting里加入二级缓存允许配置
* 2.在指定的mapper.xml命名空间下配置缓存cache标签
* 3.调用的模型需要实现序列化
* 4.sqlSession关闭了才会写入二级缓存
- 原理图
- 代码演示
public void test2() throws IOException {
//1.读取配置文件;
String resouce = "SqlMapConfig.xml";
InputStream is = Resources.getResourceAsStream(resouce);
//2.通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂。
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory ssf = sqlSessionFactoryBuilder.build(is);
//3.创建多个sqlSession
SqlSession sqlSession1 = ssf.openSession();
SqlSession sqlSession2 = ssf.openSession();
SqlSession sqlSession3 = ssf.openSession();
//4.创建多个mapper
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
//5.二级缓存作用验证
//第一次查询
User user1 = mapper1.findUserById(1);
System.out.println("user1:"+user1);
sqlSession1.close();
//保存操作或其他修改、删除,提交后,会清除二级缓存.类似于一级缓存
/* mapper3.save(new User("斗神",null,null,"广东"));
sqlSession3.commit();
*/
//第二次查询
User user2 = mapper2.findUserById(1);
System.out.println("user2:"+user2);
sqlSession2.close();
}
sqlMapConfig.xml内配置
<settings>
<!--二级缓存开关-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 应用场景
对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。(建议使用时设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存) - 单独禁用某方法使用缓存(了解)
- 设置更新操作时,该方法不清空二级缓存(了解)
9.3 二级缓存,使用第三方框架ehcache(推荐)
思路:
Cache是一个接口,它的默认实现是mybatis的PerpetualCache。如果想整合mybatis的二级缓存,那么实现Cache接口即可。
- ehcache与mybatis的缓存对比
Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式。Ehcache是一个分布式的缓存框架。
- 分布式
系统为了提高性能,通常会对系统采用分布式部署(集群部署方式)
3.使用前提
1.导包
* 2.在指定的mapper.xml命名空间下配置缓存cache标签,type里类的路径
* 3.添加ehcache.xml配置文件
mapper.xml配置
ehcache配置
10. SqlMapConfig.xml加载映射文件(推荐方式)
<!--告诉mybatis加载映射文件-->
<mappers>
<!--加载映射文件的方式三 注册指定包下的所有映射文件(推荐,可以批量,简单高效,一劳永逸)-->
<package name="com.gyf.mapper"></package>
</mappers>
11.SqlMapConfig.xml内取别名(推荐方式)
<typeAliases>
<!--给包取别名,批量实用,默认别名为类名(首字母大小写都可以)-->
<package name="com.gyf.model"></package>
<package name="com.gyf.vo"></package>
</typeAliases>
12.Spring与Mybatis整合(推荐使用mapper扫描方式)
整合详情见项目ssm
12.1ApplicationContext.xml内批量创建mapper的bean对象(推荐使用)
<!-- 批量创建mapper的bean对象,内部会扫描指定包下的mapper,创建代理对象,名字就是类名,头字母改小写
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- mapper代理开发方式之批量mapper配置 ,bean的名字默认为mapper接口类名的首字母小写
注意:
1.jdk1.8 用这种方式,bean不能创建成功 ,改成jdk1.7的即可
2.或者spring我换成spring3.2.9或以上就OK了
-->
<property name="basePackage" value="com.gyf.backoffice.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
</bean>
13.逆向工程
可以生成指定表的模型,mapper接口以及mapper.xml,默认有简单的CRUD(增删改查)功能。开发中常用,极大地提高了开发效率。
项目见:AutoGenerator
14.补充:主键返回之MySQL自增主键
思路:
MySQL自增主键,是指在insert之前MySQL会自动生成一个自增的主键。
我们可以通过MySQL的函数获取到刚插入的自增主键:
LAST_INSERT_ID()
这个函数是在insert语句之后去调用。
mapper.xml
<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>
总结
花了两天时间学习mybatis持久层框架,首先对框架的全局配置文件sqlMapConfig.xml有了了解,里面用来配置数据源以及加载映射mapper的方式。接着搭建了mybatis的项目,由model,mapper,test组成,mapper取代了以前的dao的方式,这种方式不用写dao的实现类,换成mapper.xml。接着对mapper.xml里标签属性进行了联系,包括if,where,foreach等,即动态SQL,另外做了一个关联查询的操作,涉及一对一,一对多,多对多;模型里有模型,模型里有集合,集合里有集合,集合里模型等复杂的关联查询。这个是根据业务需求得到的结果表而定的。掌握了association、collection的使用,然后就是掌握三种推荐配置文件的使用方式,会提高效率;除此之外,还学习了mybatis的一级缓存和二级缓存(推荐使用支持分布式的第三方缓存框架ehcache)最后,会用mybatis的逆向工程。学习框架的意义在于简化代码,提高开发效率。