Mybatis一些问题和优化:
问题:
- 进行模糊查询的时候,xml中写"%"#{}"%" 会导致sql注入问题
将%dirname%写死成字符串blurname,直接传入blurname,使用#{value}获得
- 数据库中的字段值和实体类的属性不对应
原因
执行sql操作,将值传给对应实体类时,会查找属性的set方法,由于数据库的属性名和实体类不对应,所以导致set失败;so,会导致取不到值的情况
在sql语句中给数据库的中的属性名起别名为实体类中的属性名
eg:select id , name , pwd as password from user where id = #{id}
pwd为数据库中的名、password为实体类的名
优化:
- mybatis-config.xml配置中数据库参数的冗长问题
properties优化
为了简化配置文件的编写,我们可以引入db.properties配置文件,
在configuration下的第一行加入
这样只需要在对应的value中写${}参数即可
- Mapper.xml中对象限定名冗长
在标签中添加typeAliases 可以对完整的限定名起别名,简化书写
- 进行数据库数据回传时,若属性名过多,导致取值很麻烦
1、可以使用map自动映射参数和其值
2、使用手动设定map映射关系
<!-- resultMap最终还是要将结果映射到pojo上,type就是指定映射到哪一个pojo -->
<!-- id:设置ResultMap的id 在select中ResultMap对应这个值-->
<resultMap type="order" id="orderResultMap">
<!-- 定义主键 ,非常重要。如果是多个字段,则定义多个id -->
<!-- property:主键在pojo中的属性名 -->
<!-- column:主键在数据库中的列名 -->
<id property="id" column="id" />
<!-- 定义普通属性 -->
<result property="userId" column="user_id" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
</resultMap>
- 提升sql排错效率
日志工厂 在mybatis-config.xml下configuration标签下
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
- 分页机制
使得查询出的数据输出一定的范围数据,对数据库压力比较小
语法
select * from user limit startindex, pageSize;
select * from user limit 5, 10;//展示6-15的数据
select * from user limit 5, -1;//输出6-last的数据
使用map将startindex, pageSize参数put进去.
注解开发
注解分为:
- @select()
- @update()
- @insert()
- @delete()
我们只需要在对应的Mapper接口上注解对应的sql语句即可
@Select("select * from user")
public List<User> getAll();
在mybatis-config.xml文件中使用
<mappers>
<mapper class="com.mapper.MapperUser"/>
多对一的处理
eg:多个学生对应一个老师
实体类:
public class Teacher{
private String id;
private String name;
}
public class Student{
private String id;
private String name;
//多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
- 按查询嵌套处理
方式:
查询所有的学生信息,使用resultMap进行接收,做一个结果集映射:StudentTeacher,类型为Student
association:一个复杂类型的关联;使用它来处理关联查询
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!--association关联属性 property属性名 javaType属性类型 column在多
的一方的表中的列名-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
这里传递过来的id,只有一个属性的时候,下面可以写任何值
association中column多参数配置:
column="{key=value,key=value}"
其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的
字段名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
- 按结果嵌套查询处理
<!--
按查询结果嵌套处理
思路:
1. 直接查询出结果,进行结果集的映射
-->
<select id="getStudents2" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
小结:
- 按查询嵌套查询处理:类似sql的子查询
- 按结果嵌套查询处理:类似sql的联表查询
一对多的处理
实体类:
public class Student{
private String id;
private String name;
}
public class Teacher{
private String id;
private String name;
//一个老师对应多个学生
private List<Student> students;
}
- 按结果嵌套查询处理
<mapper namespace="com.kuang.mapper.TeacherMapper">
<!--
思路:
1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
2. 对查询出来的操作做结果集映射
1. 集合的话,使用collection!
JavaType和ofType都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname , t.name tname, t.id tid
from student s,teacher t
where s.tid = t.id and t.id=#{id}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
</mapper>
- 按查询嵌套处理
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!--column是一对多的外键 , 写的是一的主键的列名-->
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
**小结 **
- 关联-association:是用于一对一和多对一
- 集合-collection:用于一对多的关系
- .JavaType和ofType都是用来指定对象类型的
- JavaType是用来指定pojo中属性的类型—>对应pojo中的参数类型 如一对多中的List
- ofType指定的是映射到list集合属性中pojo的类型
动态SQL
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
if语句
**需求:**根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则 根据作者名来查询
<select id="queryByIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title=#{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</select>
存在问题:
当title为null时, SQL语句为select * from user where and author=#{author},这是显然错误的.
So,修改上面的语句
<select id="queryByIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title=#{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</where>
</select>
结果:
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返 回的内容是以AND 或OR 开头的,则它会剔除掉。
Set语句
<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id};
</update>
choose语句
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
<select id="queryByChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title},
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
and view=#{view}
</otherwise>
</choose>
</where>
</select>
注意:
1.test中的key不能使用特殊字符 - 或者 +
2.test中的判断仅对!= null有效,不能使用==对字符串判断
if与choose对比:
-
使用if标签时,只要test中的表达式为 true,就会执行 if 标签中的条件,so,可以选择多个条件
-
choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql.
SQL片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽 取出来,然后使用时直接调用。
提取片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
author = #{author}
</if>
</sql>
引用SQL片段:
<select id="query" id="queryByChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace-->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
注意:
- 最好基于 单表来定义 sql 片段,提高片段的可重用性
- 在 sql 片段中不要包括 where
foreach语句
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
注意:这里的ids是一个集合list
HashMap map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
Mybatis缓存
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二 级缓存
一级缓存
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
一级缓存失效的四种情况:
- 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请 求!
- sqlSession不同
- sqlSession相同,查询参数||条件不同
- sqlSession相同,两次查询之间执行了增删改操作(理解为有事务提交)
- sqlSession相同,手动清除一级缓存
一级缓存就是一个map
二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
-
工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一 级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
-
开启全局缓存
1.在mybatis-config.xml中配置
<setting name="cacheEnabled" value="true"/>
2.去每个mapper.xml中配置使用二级缓存,这个配置非常简单;
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
//这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者 产生冲突。
结论:
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
- 查出的数据都会被默认先放在一级缓存中
- 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
缓存原理
完结撒花!