使用原生jdbc的问题
- 数据库连接, 使用时就创建,不使用就立即释放,对数据库进行频繁地链接开启和关闭,造成数据库资源浪费,影响数据库性能。
解决方案: 使用数据库连接池管理数据库连接。
2.将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护。
设想:将sql 语句配置到xml文件中。
3.向preparedStatement中设置参数,设置占位符号位置,以及对应的参数值属于硬编码,不利于系统维护。
设想 将sql语句及占位符号和全部参数配置在xml中。
4.从resultset中遍历结果集数据时存在硬编码,将获取表的字段进行成硬编码,不利于系统维护。
设想:将查询结果集,自动映射成java对象。
mybatis和hibernate的区别和应用场景
hibernate:是一个ORM框架(对象关系映射),不需要程序写sql, sql语句自动生成了。
对sql语句进行优化、修改比较困难。
mybatis:专注sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全的ORM框架,虽然程序员自己写sql,mybatis也可以实现映射(输入映射、输出映射)。
应用场景:
适用于需求变化比较多的项目,比如:互联网项目。
mybatis 开发dao的方法
SqlSessionBuilder作为工具类创建SqlSessionFactory
SqlSessionFactory使用单例模式
SqlSession是线程不安全的,在SqlSession实现类中除了有接口中方法还有数据域属性。
总结原始dao开发问题
- statement 的id需要在代码中调用,属于硬编码
- 调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错
- dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来。
mapper 代理方法
程序员只需要实现mapper接口(相当于dao接口)
程序员还需要编写mapper.xml映射文件
需要遵循一个规范
- mapper.xml 中namespace 就是mapper.java 的全限定名
- mapper.xml 中statement 的id 就是 mapper.java 的方法名一致
- mapper.xml 中statement 的parameterType 指定输入参数类型和mapper.java的方法输入,参数类型一致。
- mapper.xml 中statement 的resultType 指定输出结果的类型和mapper.java的返回方法值类型一致。
mapper 接口方法参数只能有一个是否影响系统开发
mapper接口方法参数只能有一个,系统是否不利于扩展维护?
解决
系统框架中,dao层的代码是被业务层公用的。
即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。
注意
持久层方法参数可以包装类型,但是service 方法中建议不要使用包装类型(不利于业务层可扩展性)。
将sql连接信息写入db.properties文件中
mybatis 配置
typeAliases
用于配置单个类的别名或者整个包的别名
typeHandlers
实现java类和jdbc类型的相互转化,由mybatis的一系列类型handler实现。
mapper 配置
单个映射文件加载使用
<mapper class="com.vee.mybatis.mapper.UserMapper"></mapper>
批量加载
<package name="com.vee.mybatis.mapper"></package>
使用条件
使用package进行批量加载,要求与使用class一样需要将mapper接口和xml映射文件名称保持一致,且在一个目录中。
Vo包装pojo类
- 通过创建子类对pojo 类进行扩展
- 创建vo类包装pojo类可以实现多表联合查询
- 使用Vo 类可以利用成员中的pojo类或者pojo扩展类,实现多参数查询。
resultType
使用resultType 进行输出映射,只要查询出来的列名和Pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象。
只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象。
resultMap
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
- 定义resultMap
- 使用resultMap作为statement的输出映射类型
动态sql
mysql核心 对sql语句进行灵活操作,对sql进行灵活拼接组装
例如:
对查询条件进行判断, 如果输入参数不为空才进行查询条件拼接。
使用它可以防止空指针异常
<select id="findUserList4" parameterType="UserQueryVo" resultType="UserCustom">
SELECT * FROM USER
<where>
<if test="userCustom.uid!=0">
AND uid = #{userCustom.uid}
</if>
<if test="userCustom.uname!=null">
AND uname LIKE '%${userCustom.uname}%'
</if>
</where>
</select>
sql 片段
将上边实现的动态sql 判断代码块抽取出来,组成一个sql 片段。其他的statement中就可以引用sql片段。
- sql 片段可以提高sql代码的可重用性
- sql 片段建议基于单表
- sql 片段不用包含where防止使用多个sql片段时where 重复
foreach
向sql传递数组或List,mybatis 使用foreach解析
例如如下的sql语句
select * from user where id=1 or id=2 or id=10
或者
select * from user where id IN(1,2,10)
这就需要在输入参数类型中添加List<Integer> ids
<select id="findUserList6" parameterType="UserQueryVo2" resultType="UserCustom">
SELECT * FROM USER
<where>
<if test="userCustom!=null">
AND uname LIKE '%${userCustom.uname}%'
</if>
<if test="ids!=null">
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
uid=#{id}
</foreach>
</if>
</where>
</select>
一对一查询
做法:
对需要用到的属性较多的类进行扩展,并将另一个类的属性作为字段传入。
使用resultMap
在<resultMap>标签中使用<association>标签映射关联查询单个对象的信息,property:将要关联查询的用户信息映射到Orders中哪个属性。
总结:
resultType:如果没有查询结果的特殊要求建议使用resultType,如果pojo中没有包括查询出来的列名,需要在扩展类中增加列名对应的属性。
resultMap:需要单独定义resultMap,如果对查询结果有特殊要求(列名和pojo中的属性名称不匹配)可以使用resulMap对属性名和列名进行映射。
一对多查询
做法:
- 使用resultType,创建新pojo包含必要属性,然后返回pojo列表
- 使用resultMap,扩展类添加List<>属性,需要使用<resultMap>内部标签中的<collection>标签。
多对多查询
resultMap的可以在pojo中有List的时候在<resultMap>标签中定义collection,并且collection中可以递归定义collection,但是这种场景往往使用resultType ,利用扩展的pojo类也可以很轻松地完成功能。
但是有的需求确实需要一个类有一个list成员,这个时候还是resultMap适合。
resultMap和resultType小结
resultType和resultMap都可以完成高级结果映射,如果没有特殊要求,使用resultType方便,resultType和resultMap 的一个主要区别就是要应用场景不同resultType主要是查询明细使用,resultMap主要是层级查询使用,比如查询用户信息,如何点击查询订单,再去查询订单信息,resultMap可以实现延迟加载,而resultType没有该功能。
延迟加载
resultMap 可以实现高级映射(使用association、实现一对一和一对多映射),association、collection具备延迟加载功能。
延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能。
使用resultMap 中的association 去实现延迟加载。
select 属性可以执行statement的id
- mybatis默认没有开启延迟加载,需要在sqlMapConfig.xml中setting 配置。
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnable | Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. | true/false | false |
aggressiveLazyLoading(不常用) | When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded on demand | true/false | false (true in ≤3.4.1) |
- 不使用延迟加载也是可以完成功能的,延迟加载的本质是延迟sql查询的时间,那么使用不同的mapper也可以完成先单表查询,然后一些属性按需查询的功能
resultType、resultMap、延迟加载使用场景总结
- 延迟加载:
mybatis 提供 的延迟加载功能用于service 层,要在对象消亡之前进行 - resultType
将明细记录映射到对应的pojo中 - resultMap
使用association 和collection 完成一对一和一对多的高级映射 - association
将关联查询信息映射到一个pojo类中。 - collection
将关联查询信息映射到到一个list集合中。
查询缓存
使用缓存可以将热数据放在内存中,用户查询数据就不用从磁盘上查询,这样可以提高查询效率,解决了高并发系统的性能问题。
mybatis中二级缓存生存与sqlSession中,二级缓存是mapper级别的缓存,二级缓存是可以被不同的sqlSession共享的。
一级缓存
第一次查询一个sql,sql 查询结果写入sqlsession 缓存中,同一个sqlsession 再次发出相同的sql ,就从缓存中取。如果两次中间出现了commit曹组,本sqlsession 中的一级缓存区域就全部清空。
mybatis默认支持一级缓存,但是mybatis 和spring整合后进行mapper代理开发,不支持一级缓存。mybatis 和spring整合,spring按照mapper模板去生成mapper代理对象,模板中在最后统一关闭了sqlsession。
二级缓存
二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为单位创建缓存数据结构。
- mybatis开启二级缓存
<setting name="cacheEnabled" value="true">
还需要在mapper.xml中添加<cache />标签。 - 查询结果映射的pojo需要序列化,实现java.io.serializable接口。
二级缓存可以将内存的数据写到磁盘,存在对象的序列化和反序列化,所以要实现java.io.serializable 接口。 - 一个sqlSession 在close的时候会将缓存写入到公有缓存(磁盘上)。
- 单个statement 禁止使用二级缓存。
<select id="findUserList6" parameterType="UserQueryVo" resultType="UserCustom" useCache="false">
对于变化频率较高的select,需要禁用二级缓存,即该sql使用二级缓存。 - 如果sqlSession进行commit操作,刷新缓存(全局清空)。
设置statement 的flushCache 是否刷新缓存,默认值是true,设置为false可能导致脏读。