Mybatis学习笔记

233 阅读16分钟

1. 什么是Mybatis

  MyBatis是一个半自动化的ORM框架 (Object Relationship Mapping-->对象关系映射),避免了几乎所有的JDBC代码和手动配置参数以及获取结果集的过程,比如 : 数据取出时的封装 , 数据库的建立连接等等,通过MyBatis框架可以减少重复代码,提高了开发效率。

  MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。

  其实所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之分,只有使用这个技术的人有高低之别

MyBatis的优点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供xml标签,支持编写动态sql。

持久化   由于内存本身存在的缺陷,内存断电后数据会丢失,但是一些重要的数据无论如何都不能丢失的,比如:银行卡密码等,但是我们也无法保证永不断电。   数据持久化就能很好的解决该问题,持久化即把数据保存到可永久保存的存储设备中。持久化主要的应用是将内存中的数据保存到数据库中,或者存储在磁盘文件、XML数据文件中等等。JDBC也是一种持久化机制。

2. HelloWorld

步骤:搭建环境-->导入Mybatis--->编写代码--->测试

1、创建数据库

CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert  into `user`(`id`,`name`,`pwd`) values (1,'王五','123456'),(2,'张三','abcdef'),(3,'李四','987654');

2、导入相关jar包

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.2</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.47</version>
</dependency>

3、核心配置文件

<?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>
   <!-- 通过properties标签,读取java配置文件的内容实现外部配置且可动态替换 -->
   <!--<properties resource="db.properties" />-->
   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>
   <mappers>
       <mapper resource="com/dao/userMapper.xml"/>
   </mappers>
</configuration>

4、Mybatis工具类

public class MybatisUtils {
   private static SqlSessionFactory sqlSessionFactory;
   static {
       try {
           String resource = "mybatis-config.xml";
           InputStream inputStream = Resources.getResourceAsStream(resource);
           sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
   //获取SqlSession连接
   public static SqlSession getSession(){
       return sqlSessionFactory.openSession();
  }
}

5、创建实体类

public class User {
   private int id;  //id
   private String name;   //姓名
   private String pwd;   //密码
   //构造,有参,无参
   //set/get
   //toString()
}

6、编写Mapper接口类

public interface UserMapper {
   List<User> selectUser();
}

7、编写Mapper.xml配置文件

  • namespace对应着Mapper接口或者Dao接口的完整包名,必须一致!
  • id对应方法名
<?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">
<mapper namespace="com.dao.UserMapper">
 <select id="selectUser" resultType="com.pojo.User">
  select * from user
 </select>
</mapper>

8、测试类

public class MyTest {
   @Test
   public void selectUser() {
       SqlSession session = MybatisUtils.getSession();
       //方法一:
       //List<User> users = session.selectList("com.mapper.UserMapper.selectUser");
       //方法二:
       UserMapper mapper = session.getMapper(UserMapper.class);
       List<User> users = mapper.selectUser();

       for (User user: users){
           System.out.println(user);
      }
       session.close();
  }
}

3. 配置文件解析

核心配置

<configuration>
   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>
   <mappers>
       <mapper resource="com/kuang/dao/userMapper.xml"/>
   </mappers>
</configuration>

environments

  • 通过default可以配置多套运行环境 mappers
  • 定义映射SQL语句文件
  • mappers元素引入方式
<!-- 1. 使用相对于类路径的资源引用 -->
<mappers>
 <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- 2. 使用完全限定资源定位符(URL) -->
<mappers>
 <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>

<!--
3. 使用映射器接口实现类的完全限定类名
需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
 <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

<!--
4. 将包内的映射器接口实现全部注册为映射器
但是需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
 <package name="org.mybatis.builder"/>
</mappers>
  • typeAliases   类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

指定类

<!--配置别名,注意顺序-->
<typeAliases>
   <typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>

指定包名

<typeAliases>
   <package name="com.kuang.pojo"/>
</typeAliases>

Mapper文件

<?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">
<mapper namespace="com.dao.UserMapper">
 <select id="selectUser" resultType="com.pojo.User">
  select * from user
 </select>
</mapper>
  • namespace:必须与某接口同名,接口中的方法与映射文件中的select标签中的id对应。

4. CRUD操作

步骤:

  1. 创建Mapper接口方法
  2. 在Mapper.xml中编写sql
  3. 调用方法

select

  如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可

直接在方法中传参:

//通过密码和名字查询用户
User selectUserByNP(@Param("username") String username,@Param("pwd") String pwd);

/*
   <select id="selectUserByNP" resultType="com.kuang.pojo.User">
     select * from user where name = #{username} and pwd = #{pwd}
   </select>
*/

使用Map作参数

User selectUserByNP2(Map<String,Object> map);
<select id="selectUserByNP2" parameterType="map" resultType="com.kuang.pojo.User">
select * from user where name = #{username} and pwd = #{pwd}
</select>
Map<String, Object> map = new HashMap<String, Object>();
map.put("username","小明");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);

insert

int addUser(User user);
<insert id="addUser" parameterType="com.kuang.pojo.User">
    insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
public void testAddUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = new User(5,"王五","zxcvbn");
   int i = mapper.addUser(user);
   System.out.println(i);
   session.commit(); //提交事务,重点!不写的话不会提交到数据库
   session.close();
}

注意点:增、删、改操作需要提交事务!

update

  与上同

delete

  与上同

总结

  • 所有的增删改操作都需要提交事务
  • 接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上!
  • 有时候根据业务的需求,可以考虑使用map传递参数!
  • 为了规范操作,在SQL的配置文件中,我们尽量将Parameter参数和resultType都写上!

5. 生命周期和作用域

5.1 生命周期

image.png

  1. 首先通过SqlSessionFactoryBuilder的builde()方法去加载xml文件或者配置文件,会生成一个SqlSessionFactory工厂对象。

  2. 得到工厂对象后,就可以去工厂里面去拿我们需要的SqlSession对象了,调用openSession()方法即可。

  3. 拿到SqlSession对象后,可以调用getMapper(Class mapperClass)方法,可以得到一个映射器接口的实例对象。SqlSession可以理解为一个请求,向连接池连接的请求,使用完毕之后立马关闭它,否则会造成资源浪费。

  4. 通过映射器实例对象去调用相应方法。

  5. 最后关闭SqlSession即可。

5.2 作用域

  1. SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

  2. SqlSessionFactory可以被认为是一个数据库连接池,所以它占据着数据库的连接资源,它的作用是创建SqlSession接口对象。因为Mybatis的本质就是Java对数据库的操作,所以SqlSessionFactory的生命周期存在整个Mybatis应用中,一旦创建SqlSessionFactory,就要长期保存它,直至不再使用Mybatis应用,所以可以认为SqlSessionFactory的生命周期等同于Mybatis的应用周期。因此一般的应用中,我们往往希望SqlSessionFactory作为一个单例,让它在应用中的被共享,最佳作用域是应用作用域

  3. SqlSessionFactory是一个数据库连接池,那么SqlSession就是一个数据库连接(Connection对象),可以在一个事务中执行多条SQL,然后通过它的commit、rollback等方法,提交或回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后应该关闭连接,让它归还给SqlSessionFactory,否则数据库资源就很快会耗光,系统就会瘫痪,所以应该用try...catch...finally...语句来保证其正确关闭。所以SqlSession最佳的作用域是请求或方法作用域

6. ResultMap及分页

  数据库中的字段名和实体类的属性名不同,就会出现查询结果为null的问题。因为mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到对应的setXXX()方法 , 所以返回null ;

解决方案:

  1. as指定别名
<select id="selectUserById" resultType="User">
  select id , name , pwd as password from user where id = #{id}
</select>
  1. 使用结果集映射-><ResultMap> 【推荐】
<resultMap id="UserMap" type="User">
   <!-- id为主键 -->
   <id column="id" property="id"/>
   <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
   <result column="name" property="name"/>
   <result column="pwd" property="password"/>
</resultMap>

<select id="selectUserById" resultMap="UserMap">
  select id , name , pwd from user where id = #{id}
</select>

自动映射:

<select id="selectUserById" resultType="map">
select id , name , pwd
  from user
  where id = #{id}
</select>

  以上是自动映射的例子,但是并没有显式指定resultMap,只是简单的将所有的列映射到HashMap的键上,这由resultType属性指定虽然大部分时候都够用,但是你的程序更有可能会使用JavaBean或者POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。

ResultMap

  • resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。
  • 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。
  • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。
<resultMap id="UserMap" type="User">
   <!-- id为主键 -->
   <id column="id" property="id"/>
   <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
   <result column="name" property="name"/>
   <result column="pwd" property="password"/>
</resultMap>

<select id="selectUserById" resultMap="UserMap">
  select id , name , pwd from user where id = #{id}
</select>

7. 分页的几种方式

  增上改查操作中,使用最多的是查操作。如果查询的数据量大的时候就需要进行分页查询,也就是每次处理小部分的数据,这样的对数据库压力就在可控范围内。

7.1 Limit分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize

SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15  

#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:   
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.  

#如果只给定一个参数,它表示返回最大的记录行数目:   
SELECT * FROM table LIMIT 5; //检索前 5 个记录行  

#换句话说,LIMIT n 等价于 LIMIT 0,n。

7.2 RowBounds分页

public void testUserByRowBounds() {
   SqlSession session = MybatisUtils.getSession();

   int currentPage = 2;  //第几页
   int pageSize = 2;  //每页显示几个
   RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);

   //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
   List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds"null, rowBounds);

   for (User user: users){
       System.out.println(user);
  }
   session.close();
}

7. 使用注解开发

  Mybatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的,这样Java 注解的的表达力和灵活性十分有限,MyBatis 3提供了新的基于注解的配置,利用注解开发就不需要mapper.xml映射文件了。不过使用注解只适用于简单的查询语句,如果是复杂的就会出现上面所提到的查询结果为null的问题。

  • sql 类型主要分成 :
    • @select ()
    • @update ()
    • @Insert ()
    • @delete ()
  1. 我们在我们的接口中添加注解
//根据id查询用户
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);
  1. 在mybatis的核心配置文件中注入
<!--使用class绑定接口-->
<mappers>
   <mapper class="com.mapper.UserMapper"/>
</mappers>
  1. 测试
public void testGetAllUser() {
   SqlSession session = MybatisUtils.getSession();
   //底层主要应用反射得到UserMapper的方法,通过解析方法和注解实现查询
   UserMapper mapper = session.getMapper(UserMapper.class);
   List<User> users = mapper.selectUserById(1);
   System.out.println(user);
   session.close();
}

@Param:

  • 在方法只接受一个参数的情况下,可以不使用@Param。
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

#与$的区别:

  • #{} 的作用是对传入的参数视为字符串,也就是它会预编译,相当于对数据 加上 双引号。 【推荐使用】

    select * from user where name = #{name}
    select * from user where name = 'csdn'
    
  • ${} 的作用是直接进行字符串替换,相当于直接显示数据。

    select * from user where name=${name}
    select * from user where name=csdn
    
  • MyBatis排序时使用order by 动态参数时需要注意,用$而不是#。

总结:

  默认情况下,使用#{}格式的语法会导致MyBatis创建预处理语句属性并以它为背景设置安全的值(比如?)。这样做很安全,很迅速也是首选做法,有时你只是想直接在SQL语句中插入一个不改变的字符串。比如,像ORDER BY,你可以这样来使用: ORDER BY ${columnName} 这里MyBatis不会修改或转义字符串。

重要:接受从用户输出的内容并提供给语句中不变的字符串,这样做是不安全的。这会导致潜在的SQL注入攻击,因此你不应该允许用户输入这些字段,或者通常自行转义并检查。

8. 多对一和一对多的处理

8.1 多对一的处理

  举一个学生和老师的例子,如果对于学生这边,就是多对一的关系,即从学生这边关联一个老师;如果对于老师这边,就是一个一对多的关系,即从一个老师下面拥有一群学生(集合)

image.png

实体类的编写:

@Data //GET,SET,ToString,有参,无参构造
public class Teacher {
   private int id;
   private String name;
}
@Data
public class Student {
   private int id;
   private String name;
   //多个学生可以是同一个老师,即多对一
   private Teacher teacher;
}

以下是对应Mapper文件的编写:

按查询嵌套处理: association中嵌套了一个select

   <select id="getStudents" resultMap="StudentTeacher">
    select * from student
   </select>
   
   <resultMap id="StudentTeacher" type="Student">
   <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
       <association property="teacher"  column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
   </resultMap>
       <!--
       select="getTeacher"指定了要传递的目标,传递的参数是tid。
       如果需要传递的对象只有一个时下面的#{}可以是任何值。
       association中column多参数配置:
       column="{key=value,key=value}"
       其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
       -->
   <select id="getTeacher" resultType="teacher">
      select * from teacher where id = #{id} and name = #{name}
   </select>

按结果嵌套处理:

<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中的联表查询

8.2 一对多的处理

实体类编写:

@Data
public class Student {
   private int id;
   private String name;
   private int tid;
}
@Data
public class Teacher {
   private int id;
   private String name;
   //一个老师多个学生
   private List<Student> students;
}

按查询嵌套处理:

<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
   <!-- 
   集合的话,使用collection!
           JavaType和ofType都是用来指定对象类型的
           JavaType是用来指定pojo中属性的类型
           ofType指定的是映射到list集合属性中pojo的类型。
   -->
   <!--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>

按结果嵌套处理:

<mapper namespace="com.mapper.TeacherMapper">
   <!--
   思路:
       1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
       2. 对查询出来的操作做结果集映射
   -->
   <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>

总结:

  1. 关联-association
  2. 集合-collection
  3. 所以association是用于一对一和多对一,而collection是用于一对多的关系
  4. JavaType和ofType都是用来指定对象类型的
  • JavaType是用来指定pojo中属性的类型
  • ofType指定的是映射到list集合属性中pojo的类型。

9. 动态SQL

  当面临复杂的业务的时候,需要写复杂的Sql语句时往往需要拼接,但是拼接Sql稍不注意就可能导致错误。这时候就需要用到Mybatis的动态Sql,组合成非常灵活的Sql语句,提高Sql语句准确性的同时,也大大提高了开发人员的开发效率。

if语句:

需求:根据作者名字博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog where
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</select>

  这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

Where:

<select id="queryBlogIf" 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,用法同理。

<!--注意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="queryBlogChoose" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <choose>
           <when test="title != null">
                title = #{title}
           </when>
           <when test="author != null">
              and author = #{author}
           </when>
           <otherwise>
              and views = #{views}
           </otherwise>
       </choose>
   </where>
</select>

SQL片段:

  有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

<!-- 提取SQL片段: -->
<sql id="if-title-author">
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</sql>
<!-- 引用SQL片段: -->
<select id="queryBlogIf" 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>

测试:

public void testQueryBlogForeach(){
   SqlSession session = MybatisUtils.getSession();
   BlogMapper mapper = session.getMapper(BlogMapper.class);

   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);

   System.out.println(blogs);

   session.close();
}

10. 缓存

10.1 简介

1. 什么是缓存(Cache)?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2. 为什么使用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

3. 什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。

4. Mybatis的缓存

  Mybatis系统中定义了两级缓存:一级缓存、二级缓存。为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

10.2 一级缓存

  默认情况下只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存),与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。   一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效的四种情况:

  一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

  1. sqlSession不同。

  2. sqlSession相同,查询条件不同。

  3. sqlSession相同,两次查询之间执行了增删改操作!

  4. sqlSession相同,手动清除一级缓存

public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   session.clearCache();//手动清除缓存

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}

10.3 二级缓存

  二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存,需要手动开启和配置。他是基于namespace级别的缓存,

工作机制:

  一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中。如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中。新的会话查询信息,就可以从二级缓存中获取内容,不同的mapper查出的数据会放在自己对应的缓存(map)中。

使用步骤:

  1. 开启全局缓存
<setting name="cacheEnabled" value="true"/>
  1. 在需要开启二级缓存的mapper.xml(xxxMapper.xml)中配置标签。 例如:
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

结论:

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。
  • 查出的数据都会被默认先放在一级缓存中。
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。