Mybatis完整版详解

一、简介

1. 什么是 MyBatis

  • MyBatis 是一款优秀的持久层框架

  • 它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  • MyBatis 本是 apache 的一个开源项目 iBatis,2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis。

  • 2013 年 11 月迁移到 Github。

(1) 如何获得 MyBatis

2. 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程

  • 内存:断电即失

  • 数据库 (jdbc),io 文件持久化

为什么需要持久化

  • 有一些对象,不能让它丢掉

  • 内存太贵了

3. 持久层

Dao 层,Service 层,Controller 层 什么叫层
  • 完成持久化工作的代码块

  • 层是界限十分明显的

4. 为什么需要 Mybatis

  • 帮助程序员将数据存入到数据库中

  • 方便

  • 传统的 JDBC 代码太复杂了,Mybatis 对其进行了简化,

二、第一个 Mybatis 程序

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

1. 搭建环境

搭建一个数据库


新建一个 maven 项目, 并导入 maven 依赖,要注意导入 mybatis 时需要手动开启 Tomcat 的 bin 目录下 startup.sh(只针对本机,Windows 开启 startup.bat)

 <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
        </dependency>
    </dependencies>

2. 创建一个模块

编写 mybatis 的核心配置文件

mybatis-config.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
<!--                &amp; 在 xml 文件中与符号需要这样来转义 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&amp;useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="root123456"/>
            </dataSource>
        </environment>
    </environments>
<!--    每一个 mapper.xml 都需要在 Mybatis 核心配置文件中注册 -->
    <mappers>
        <mapper resource="com/tang/dao/UserMapper.xml"/>
    </mappers>
</configuration>

注意:这里如果没写加载驱动的话会报以下错误
org.apache.ibatis.exceptions.PersistenceException: Error querying database. Cause: java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "key" is null
但是写了又会说会自动加载,加载多余,不过这并不是错误,因此还是写上安全

编写 mybatis 工具类

//sqlSessionFactory-->sqlSession
public class MybatisUtils {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> SqlSessionFactory sqlSessionFactory;

<span class="hljs-keyword">static</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">//使用Mybatis第一步,获取sqlSessionFactory对象</span>
        <span class="hljs-comment">//这三行代码是从mybatis中文文档中获取到的,规定这么写的</span>
        <span class="hljs-type">String</span> <span class="hljs-variable">resource</span> <span class="hljs-operator">=</span> <span class="hljs-string">"mybatis-config.xml"</span>;<span class="hljs-comment">//这里写上自己的mybatis配置文件的文件名即可</span>
        <span class="hljs-type">InputStream</span> <span class="hljs-variable">inputStream</span> <span class="hljs-operator">=</span> Resources.getResourceAsStream(resource);
        sqlSessionFactory = <span class="hljs-keyword">new</span> <span class="hljs-title class_">SqlSessionFactoryBuilder</span>().build(inputStream);
    } <span class="hljs-keyword">catch</span> (IOException e) {
        e.printStackTrace();
    }
}

<span class="hljs-comment">//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。</span>
<span class="hljs-comment">// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> SqlSession <span class="hljs-title function_">getSqlSession</span><span class="hljs-params">()</span>{
    <span class="hljs-type">SqlSession</span> <span class="hljs-variable">sqlSession</span> <span class="hljs-operator">=</span> sqlSessionFactory.openSession();
    <span class="hljs-keyword">return</span> sqlSession;
}

}

3. 编写代码

实体类

字段名和数据里的字段一一对应
public class User {
    private int id;
    private String name;
    private String pwd;
<span class="hljs-keyword">public</span> <span class="hljs-title function_">User</span><span class="hljs-params">()</span> {
}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">User</span><span class="hljs-params">(<span class="hljs-type">int</span> id, String name, String pwd)</span> {
    <span class="hljs-built_in">this</span>.id = id;
    <span class="hljs-built_in">this</span>.name = name;
    <span class="hljs-built_in">this</span>.pwd = pwd;
}

<span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getId</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> id;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setId</span><span class="hljs-params">(<span class="hljs-type">int</span> id)</span> {
    <span class="hljs-built_in">this</span>.id = id;
}

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> name;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> {
    <span class="hljs-built_in">this</span>.name = name;
}

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getPwd</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> pwd;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setPwd</span><span class="hljs-params">(String pwd)</span> {
    <span class="hljs-built_in">this</span>.pwd = pwd;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"User{"</span> +
            <span class="hljs-string">"id="</span> + id +
            <span class="hljs-string">", name='"</span> + name + <span class="hljs-string">'\''</span> +
            <span class="hljs-string">", pwd='"</span> + pwd + <span class="hljs-string">'\''</span> +
            <span class="hljs-string">'}'</span>;
}

}

Dao 接口

public interface UserDao {
    List<User> getUserList();
}

接口实现类由 Impl 转为一个 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">
<!--namespace= 绑定一个对应的 Dao/Mapper 接口, 等价于以前去实现接口并重写方法 -->
<mapper namespace="com.tang.dao.UserDao">
    <!--  select 查询语句  -->
    <!--id 等价于以前去实现接口并重写方法   resultType:执行 sql 返回的结果集,仅需要返回接口的方法中的泛型类型即可 -->
    <select id="getUserList" resultType="com.tang.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

4. 测试

注意点:
  • 若在接口的配置文件中没有写以下代码则会报下面的错

    <mappers>
          <mapper resource="com/tang/dao/UserMapper.xml"/>
      </mappers>
    

    org.apache.ibatis.binding.BindingException: Type interface com.tang.dao.UserDao is not known to the MapperRegistry.

  • 若在 pom 中没有以下代码则 resources 下的配置文件和 java 目录下的 xml 配置文件就不会被打包,也就是在 target 中并没有相应的 class 文件

    <build>
          <resources>
              <resource>
                  <directory>src/main/resources</directory>
                  <includes>
                      <include>**/*.properties</include>
                      <include>**/*.xml</include>
                  </includes>
                  <filtering>true</filtering>
              </resource>
              <resource>
                  <directory>src/main/java</directory>
                  <includes>
                      <include>**/*.properties</include>
                      <include>**/*.xml</include>
                  </includes>
                  <filtering>true</filtering>
              </resource>
          </resources>
      </build>
    

    * 测试代码:

      public class UserDaoTest {
        @Test
        public void test(){
    
        <span class="hljs-comment">//第一步:获得sqlSession对象</span>
        <span class="hljs-type">SqlSession</span> <span class="hljs-variable">sqlSession</span> <span class="hljs-operator">=</span> MybatisUtils.getSqlSession();
        <span class="hljs-comment">//方式一:getMapper 执行SQL</span>
        <span class="hljs-type">UserDao</span> <span class="hljs-variable">userDao</span> <span class="hljs-operator">=</span> sqlSession.getMapper(UserDao.class);
        List&lt;User&gt; userList = userDao.getUserList();
    
        <span class="hljs-keyword">for</span>(User user: userList){
            System.out.println(user);
        }
        <span class="hljs-comment">//关闭SQLSession</span>
        sqlSession.close();
    }
    

    }

运行结果图

三、CRUD(增删改查)

1.Select

选择,查询语句
  • id: 就是对应 namespace 中的方法名

  • resultType:Sql 语句执行的返回值

  • parameterType: 参数类型

编写接口

// 查询指定 id 的用户
    User getUserById(int id);

编写对应 Dao 中的 sql 语句

<select id="getUserById" parameterType="int" resultType="com.tang.pojo.User">
        select * from mybatis.user where id= #{id}
    </select>

测试

// 查询指定用户
    @Test
    public void getUserByID(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
        sqlSession.close();
    }

2.Insert

编写接口

// 添加一个用户
    int addUser(User user);

编写对应 Dao 中的 sql 语句

 <insert id="addUser" parameterType="com.tang.pojo.User">
        insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd})
    </insert>

测试

// 添加用户
    @Test
    public void addUserTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        mapper.addUser(new User(4,"twq","1233"));
    sqlSession.commit();<span class="hljs-comment">//增删改必须要提交事务,否则在数据库中就无法查看增删改后的结果</span>
    sqlSession.close();
}

3.update

编写接口

// 修改一个用户
    int updateUser(User user);

编写对应 Dao 中的 sql 语句

 <update id="updateUser" parameterType="com.tang.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd}  where id=#{id};
    </update>

测试

// 修改用户
    @Test
    public void updateUserTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        mapper.updateUser(new User(1,"唐","1234"));
    sqlSession.commit();
    sqlSession.close();
}

4.delete

编写接口

// 删除一个用户
    int deleteUser(int id);

编写对应 Dao 中的 sql 语句

<delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id};
    </delete>

测试

@Test
    public  void deleteUserTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        mapper.deleteUser(2);
    sqlSession.commit();
    sqlSession.close();
}

运行前 user 中表的数据

运行增删改查之后结果图

5. 万能的 map

目的:将 user 表中 id=1 的 name 改为“唐三唐昊” 接口代码
// 万能的 map
    int updateUser2(Map<String,Object> map);

对应 Dao 中 sql 代码

    <update id="updateUser2" parameterType="map">
--         这里就没有必要在把user中的所有字段都写进来,用到哪个就可以写哪个字段,且传进去的字段名可以任意写
        update mybatis.user set name=#{username}  where id=#{userid};
    </update>

测试代码

 //map 实现用户修改
    @Test
    public void updateUser2Test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
    HashMap&lt;String, Object&gt; map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;String, Object&gt;();

    map.put(<span class="hljs-string">"userid"</span>,<span class="hljs-number">1</span>);
    map.put(<span class="hljs-string">"username"</span>,<span class="hljs-string">"唐三唐昊"</span>);

    mapper.updateUser2(map);

    sqlSession.commit();<span class="hljs-comment">//增删改必须要提交事务,否则在数据库中就无法查看增删改后的结果</span>
    sqlSession.close();
}

运行结果图


Map 传递参数,直接在 sql 中取出 key 即可

对象传递参数,直接在 sql 中取对象的属性即可

只有一个基本类型参数的情况下,可以直接在 sql 中取到

6. 模糊查询

目的:利用模糊查询   查询所有姓唐的人 接口
 List<User> getUserLike(String value);

sql 代码

 <select id="getUserLike" resultType="com.tang.pojo.User">
        select *from mybatis.user where name like #{value}
    </select>

测试代码
在 Java 代码执行的时候,传递通配符 % %,不会存在 sql 注入的问题

    // 模糊查询
    @Test
    public void getUserLikeTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
    List&lt;User&gt; userLike = mapper.getUserLike(<span class="hljs-string">"%唐%"</span>);

    <span class="hljs-keyword">for</span>(User user: userLike){
        System.out.println(user);
    }

    sqlSession.close();
}

在 sql 拼接中使用通配符,存在 sql 注入问题

<select id="getUserLike" resultType="com.tang.pojo.User">
        select *from mybatis.user where name like "%"#{value}"%"
    </select>
List<User> userLike = mapper.getUserLike("唐");

两种情况的运行结果

四、配置解析

1. 核心配置文件

  • mybatis-config.xml

  • Mybatis 的配置文件包含了会深深影响 Mybatis 行为的设置和属性信息

  • configuration(配置)

    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

2. 环境配置 (environments)

MyBatis 可以配置成适应多种环境

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
学会使用配置多套运行环境,比如如下这种方式就可以选择 id 为 test 的配置环境,虽然有多套配置环境,但是最终运行的只会是其中一种

<configuration>
    <environments default="test">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
<!--                &amp; 在 xml 文件中与符号需要这样来转义 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&amp;useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="root123456"/>
            </dataSource>
        </environment>
    <span class="hljs-tag">&lt;<span class="hljs-name">environment</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"test"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">transactionManager</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"JDBC"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dataSource</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"POOLED"</span>&gt;</span>
            <span class="hljs-comment">&lt;!--                &amp;amp;在xml文件中与符号需要这样来转义--&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"driver"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"com.mysql.jdbc.Driver"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"url"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai<span class="hljs-symbol">&amp;amp;</span>useSSL=true<span class="hljs-symbol">&amp;amp;</span>useUnicode=true<span class="hljs-symbol">&amp;amp;</span>characterEncoding=utf8"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"username"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"root"</span>/&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"root123456"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dataSource</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">environment</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">environments</span>&gt;</span>

<!-- 每一个 mapper.xml 都需要在 Mybatis 核心配置文件中注册 -->
<mappers>
<mapper resource="com/tang/dao/UserMapper.xml"/>
</mappers>
</configuration>

Mybatis 默认的事务管理器就是 JDBC,连接池为 POOLED

3. 属性 (properties)

我们可以通过 properties 属性来实现引用配置文件

这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,也可通过 properties 元素的子元素来传递【db.properties】


编写一个配置文件

db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"
username=root
password=root123456

在核心配置文件中引入

<!-- 引入外部配置文件 -->
    <properties resource="db.properties"/>

然后就可以通过如下的方式去读取 db.properties 文件里的值

<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
  • 可以直接引入外部文件

  • 可以在其中增加一些属性配置

  • 如果两个文件有同一个字段,优先使用外部配置文件中的

4. 类型别名 (typeAliases)

作用
  • 类型别名可为 Java 类型设置一个缩写名字。

  • 它仅用于 XML 配置,意在降低冗余的全限定类名书写

<!-- 可以给实体类起别名 -->
    <typeAliases>
        <typeAlias type="com.tang.pojo.User" alias="User"></typeAlias>
    </typeAliases>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
扫描实体类的包,它的默认别名就为这个类的 类名,首字母小写, 大写也行!
如下代码在调用到 pojo 包下面的类的时候可以直接使用类名的小写字母完成

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

在实体类比较少的时候使用第一种方式

如果实体类比较多,建议使用第二种

第一种可以 DIY 起别名,第二种则不行,如果非要改,需要在实体类上增加注解

在实体类上加注解给类名起别名

@Alias("user")
public class User {

5. 设置 (settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为

6. 映射器 (mappers)

MapperRegistry: 注册绑定我们的 Mapper 文件;
方式一:【推荐使用】

<!--    每一个 mapper.xml 都需要在 Mybatis 核心配置文件中注册 -->
    <mappers>
        <mapper resource="com/tang/dao/UserMapper.xml"/>
    </mappers>

方式二:使用 class 文件绑定注册

<mappers>
    <mapper class="com.tang.dao.UserMapper"/>
</mappers>

注意点

  • 接口和它的 Mapper 配置文件必须同名

  • 接口和它的 Mapper 配置文件必须在同一个包下

7. 作用域 (Scope) 和生命周期

不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了
    SqlSessionFactory

  • 可以想象为:数据库连接池

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例

  • SqlSessionFactory 的最佳作用域是应用作用域

  • 最简单的就是使用单例模式或者静态单例模式
    SqlSession

  • 连接到连接池的一个请求

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

  • 用完之后需要赶紧关闭,否则资源被占用


这里面的每一个 Mapper, 就代表一个具体的业务

五、解决属性名和字段名不一致的问题

1. 问题

数据库中的字段

新建一个项目,拷贝之前的,情况测试实体类字段不一致的情况

public class User {
    private int id;
    private String name;
    private String password;

测试出现问题

select * from mybatis.user where id= #{id}
// 类处理器,以上等价于
select id,name,pwd from mybatis.user where id = #{id}
// 所以并未查找到 pwd 字段所以测试结果 password 为空

解决方法:

  • 起别名

    <select id="getUserById" parameterType="int" resultType="com.tang.pojo.User">
            select id,name,pwd as password from mybatis.user where id = #{id}
        </select>
    

2.resultMap

结果集映射
数据库中的字段为 id    name    pwd
User实体类字段为 id    name    password
<!-- 结果集映射 -->
<resultMap id="UserMap" type="user">
    <!--column 数据库中的字段,property 实体类中的属性 -->
   <!--id 和 name 属性可以不写,只需要写实体类中与数据库不一样的字段的映射即可 -->
    <result column="id" property="id"></result>
    <result column="name" property="name"></result>
    <result column="pwd" property="password"></result>
</resultMap>
 <!--select 中 resultMap 的值必须与上面 resultMap 的 id 的值相同 -->
<select id="getUserById"  resultMap="UserMap">
    select * from mybatis.user where id= #{id}
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素

  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了

六、日志

1. 日志工厂

如果一个数据库操作出现了异常,我们需要排错,日志就是最好的助手

曾经出现异常通常使用:sout、debug 来找到异常

现在:使用日志工厂来实现

  • SLF4J

  • LOG4J(3.5.9 起废弃)【要掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING【要掌握】

  • NO_LOGGING
    在 Mybatis 中具体使用哪个日志实现,在设置中设定

STDOUT_LOGGING 标准日志输出

<settings>
    <!-- 标准的日志工厂实现 -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

2.Log4j

什么是 log4j?

  • Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件

  • 我们也可以控制每一条日志的输出格式

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程

  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

先导入 Log4j 的包

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/tang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

配置 log4j 日志的实现

<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

log4j 的使用,直接测试

简单使用

  • 再要使用 Log4j 的类中,导入包 import org.apache.log4j.Logger;

  • 日志对象,参数为当前类的 class

日志级别

 @Test
   public void testLog4j(){
      logger.info("info: 进入了 testLog4j");
      logger.debug("debug: 进入了 testLog4j");
      logger.error("erro: 进入了 testLog4j");
   }

运行结果

七、分页

1. 使用 Limit 实现分页

接口

// 分页
List<User> getUserByLimit(Map<String,Integer> map);

接口的 xml 文件

<!--    分页实现查询 -->
<!--    这里写 user 是因为我已经起过别名,所以可简写为 user-->
<select id="getUserByLimit" parameterType="map" resultType="user">
    select * from mybatis.user limit #{startIndex},#{pageSize}
</select>

测试

@Test
public void getUserByLimit(){
  SqlSession sqlSession = MybatisUtils.getSqlSession();
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);

HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);

List<User> userList = mapper.getUserByLimit(map);

for(User user : userList){
System.out.println(user);
}
}

运行结果图

八、使用注解开发

1. 面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程

  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
    关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
    三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .

  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .

  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题. 更多的体现就是对系统整体的架构

2. 注解的使用

注解在接口上实现

public interface UserMapper {
   @Select("select * from user")
   List<User> getUsers();
}

在核心配置文件中绑定接口

<!--    绑定接口 -->
<mappers>
    <mapper class="com.tang.dao.UserMapper"></mapper>
</mappers>

本质:反射机制实现

3.Mybatis 详细执行流程

4. 注解实现 CRUD

我们可以再工具类创建的时候实现自动提交事务
 public static SqlSession getSqlSession(){
        // 这里写上 true 之后在进行增删改之后就会自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        return sqlSession;
    }

配置文件中对接口进行注册

<mappers>
    <mapper class="com.tang.dao.UserMapper"></mapper>
</mappers>

接口代码

// 查询所有用户
    @Select("select * from user")
    List<User> getUserList();
<span class="hljs-comment">//方法存在多个参数,所有的参数前面必须加上@Param注解</span>
<span class="hljs-comment">//查询指定id的用户</span>
<span class="hljs-meta">@Select("select * from user where id=#{id}")</span>
User <span class="hljs-title function_">getUserById</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> <span class="hljs-type">int</span> id)</span>;

<span class="hljs-comment">//增加用户</span>
<span class="hljs-meta">@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")</span>
<span class="hljs-type">int</span> <span class="hljs-title function_">addUser</span><span class="hljs-params">(User user)</span>;

<span class="hljs-comment">//修改用户</span>
<span class="hljs-meta">@Update("update user set name =#{name},pwd=#{password} where id=#{id}")</span>
<span class="hljs-type">int</span> <span class="hljs-title function_">updateUser</span><span class="hljs-params">(User user)</span>;

<span class="hljs-comment">//删除用户</span>
<span class="hljs-meta">@Delete("delete from user where id=#{uid}")</span>
<span class="hljs-type">int</span> <span class="hljs-title function_">deleteUser</span><span class="hljs-params">(<span class="hljs-meta">@Param("uid")</span><span class="hljs-type">int</span> id)</span>;

增删改查的测试类

@Test
   public void getUserListTest(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      List<User> userList = mapper.getUserList();
      for (User user : userList) {
         System.out.println(user);
      }
   }
   @Test
    public void getUserBID(){
       SqlSession sqlSession = MybatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       User userById = mapper.getUserById(1);
       System.out.println(userById);
       sqlSession.close();
   }
   @Test
   public void addUserTest(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      mapper.addUser(new User(6,"唐银","123"));
      sqlSession.close();
   }
   @Test
   public void updateUserTest(){
      SqlSession sqlSession = MybatisUtils.getSqlSession();
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      mapper.updateUser(new User(1,"汤昊","678"));
      sqlSession.close();
   }

@Test
public void deleteUserTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(3);
sqlSession.close();
}

对 user 表操作之后的结果图

【关于 @Param() 注解】

  • 基本类型的参数或者 String 类型,需要加上 @Param

  • 引用类型不用加

  • 如果只有一个进本类型的话,可以忽略,但是建议也加上

  • 我们在 SQL 中引用的就是我们这里的 @Param() 中设定的属性名

九、Lombok

1. 使用步骤

  • 在 IDEA 中安装 Lombok 插件

  • 在项目中导入 lombok 的 jar 包

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>
  • 在实体类上加 lombok 注解
    • @Data: 无参构造,get,set,toString,hashcode,equals
    • @NoArgsConstructor: 无参构造
    • @AllArgsConstructor: 有参构造,写了有参的注解和 Data 的注解,虽然 Data 会产生无参,但是在写有参注解的同时无参会消失,因为显示定义有参之后,无参需要手动赋值

十、多对一处理

如学生和老师之间的关系
  • 对于学生这边而言,关联,多个学生,关联一个老师【多对一】

  • 对于老师而言 ,集合,一个老师,有很多学生【一对多】

1. 测试环境搭建

最终的包结构图

步骤

  • 导入 lombok

  • 新建实体类 Teacher,Student
    Student 实体类

    import lombok.Data;
    

    @Data
    public class Student {
    private int id;
    private String name;

    <span class="hljs-comment">//学生需要关联一个老师</span>
    <span class="hljs-keyword">private</span> Teacher teacher;
    

    }

    Teacher 实体类

    import lombok.Data;
    

    @Data
    public class Teacher {
    private int id;
    private String name;
    }

  • 建立 Mapper 接口
    StudentMapper 接口

    public interface StudentMapper {
    

    }

    TeacherMapper 接口

    import com.tang.pojo.Teacher;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    

    public interface TeacherMapper {

    <span class="hljs-meta">@Select("select * from teacher where id = #{tid}")</span>
    Teacher <span class="hljs-title function_">getTeacher</span><span class="hljs-params">(<span class="hljs-meta">@Param("tid")</span><span class="hljs-type">int</span> id)</span>;
    

    }

  • 建立 Mapper.xml 文件
    StudentMapper.xml 代码

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 核心配置文件 -->
    <mapper namespace="com.tang.dao.StudentMapper">
    

    </mapper>

    TeacherMapper.xml 代码

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 核心配置文件 -->
    <mapper namespace="com.tang.dao.TeacherMapper">
    

    </mapper>

  • 在核心配置文件中绑定注册我们的 Mapper 接口或者文件

    <mappers>
      <mapper class="com.tang.dao.TeacherMapper"></mapper>
      <mapper class="com.tang.dao.StudentMapper"></mapper>
    </mappers>
    
  • 测试查询对否成功

        @Test
      public void TeacherMapperTest(){
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          TeacherMapper mapper =     sqlSession.getMapper(TeacherMapper.class);
    
      <span class="hljs-type">Teacher</span> <span class="hljs-variable">teacher</span> <span class="hljs-operator">=</span> mapper.getTeacher(<span class="hljs-number">1</span>);
      System.out.println(teacher);
    
      sqlSession.close();
    

    }

运行结果图

2. 按照查询嵌套处理

目的:查询每个学生及对应老师的信息
<mapper namespace="com.tang.dao.StudentMapper">
<span class="hljs-comment">&lt;!--思路:
    1.查询所有的学生信息
    2.根据查询出来的学生的tid,寻找对应的老师
--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"getStudent"</span> <span class="hljs-attr">resultMap</span>=<span class="hljs-string">"StudentTeacher"</span>&gt;</span>

-- select s.id,s.name,t.name from student s,teacher t where s.tid = t.id;
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!-- 因为 student 和 Teacher 表中 id, 和 name 字段相同,因此这里没必要写映射关系 -->
<!-- 复杂的属性,我们需要单独处理
对象使用 association
集合使用 collection
-->

<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{id}
</select>
</mapper>

运行结果图

3. 按照结果嵌套处理

目的:查询每个学生及对应老师的信息
<!-- 按照结果嵌套处理 -->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name snmae,t.id tid,t.name tname
    from student s,teacher t
    where s.tid = t.id;
</select>

<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<association property="teacher" javaType="Teacher" >
<result property="name" column="tname"></result>
<result property="id" column="tid"></result>
</association>
</resultMap>

十一、一对多处理

比如:一个老师拥有多个学生 对于老师而言,就是一对多的关系

1. 环境搭建

与之前环境搭建差不多 这里实体类变为
import lombok.Data;

@Data
public class Student {
private int id;
private String name;
private int tid;
}

import lombok.Data;

import java.util.List;

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

2. 按照结果查询

目的:查询每个老师及对应学生的信息
<mapper namespace="com.tang.dao.TeacherMapper">
    <!-- 按结果嵌套查询 -->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid,s.name snmae,t.id tid,t.name tname
        from student s,teacher t
        where s.tid = t.id and t.id=#{tid}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"></result>
        <result property="name" column="tname"></result>
        <!-- 复杂的属性,我们需要单独处理
            对象使用 association
            集合使用 collection
            javaType 指定属性的类型
            集合中的泛型信息,使用 ofType 获取
        -->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"></result>
            <result property="name" column="snmae"></result>
            <result property="tid" column="tid"></result>
        </collection>
    </resultMap>
</mapper>

3. 小结

  • 关联 -association【多对一】

  • 集合 -collection 【一对多】

  • javaType & ofType

    • javaType 用来指定实体类中属性的类型
    • ofType 用来指定映射到 List 或者集合中 pojo 类型,泛型中的约束类型

注意点:

  • 保证 SQL 的可读性,尽量保证通俗易懂

  • 注意一对多和多对一中,属性名和字段的问题

  • 如果问题不好排查错误,可以使用日志,建议使用 log4j

【面试高频】

  • Mysql 引擎
    问题描述:一张表, 里面有 ID 自增主键, 当 insert 了 17 条记录之后, 删除了第 15,16,17 条记录, 再把 Mysql 重启, 再 insert 一条记录, 这条记录的 ID 是 18 还是 15 ?(区分两种数据库引擎)

    (1)如果表的类型是 MyISAM,那么是 18。
    因为 MyISAM 表会把自增主键的最大 ID 记录到数据文件里,重启 MySQL 自增主键的最大 ID 也不会丢失。
    (2)如果表的类型是 InnoDB,那么是 15。
    InnoDB 表只是把自增主键的最大 ID 记录到内存中,所以重启数据库或者是对表进行 OPTIMIZE 操作,都会导致最大 ID 丢失。

  • InnoDB 底层原理
    innoDB 是聚集索引方式,因此数据和索引都存储在同一个文件里。首 先 InnoDB 会根据主键 ID 作为 KEY 建立索引 B+ 树,如左下图所示,而 B+ 树的叶子节点存储的是主键 ID 对应的数据,比如在执行 select * from user_info where id=15 这个语句时,InnoDB 就会查询这颗主键 ID 索引 B+ 树,找到对应的 user_name='Bob'。

    这是建表的时候 InnoDB 就会自动建立好主键 ID 索引树,这也是为什么 Mysql 在建表时要求必须指定主键的原因。当我们为表里某个字段加索引时 InnoDB 会怎么建立索引树呢?比如我们要给 user_name 这个字段加索引,那么 InnoDB 就会建立 user_name 索引 B+ 树,节点里存的是 user_name 这个 KEY,叶子节点存储的数据的是主键 KEY。注意,叶子存储的是主键 KEY!拿到主键 KEY 后,InnoDB 才会去主键索引树里根据刚在 user_name 索引树找到的主键 KEY 查找到对应的数据。

  • 索引
    问题描述:简单描述 MySQL 中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响。

    (1)索引是一种特殊的文件(InnoDB 数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
    (2)普通索引(由关键字 KEY 或 INDEX 定义的索引)的唯一任务是加快对数据的访问速度。
    (3)普通索引允许被索引的数据列包含重复的值,如果能确定某个数据列只包含彼此各不相同的值,在为这个数据索引创建索引的时候就应该用关键字 UNIQE 把它定义为一个唯一所以,唯一索引可以保证数据记录的唯一性。
    (4)主键,一种特殊的唯一索引,在一张表中只能定义一个主键索引,逐渐用于唯一标识一条记录,是用关键字 PRIMARY KEY 来创建。
    (5)索引可以覆盖多个数据列,如像 INDEX 索引,这就是联合索引。
    (6)索引可以极大的提高数据的查询速度,但是会降低插入删除更新表的速度,因为在执行这些写操作时,还要操作索引文件。

  • 索引优化
    1. 创建索引
    对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。
    2. 复合索引
    比如有一条语句是这样的:select * from users where area=’beijing’ and age=22;
    如果我们是在 area 和 age 上分别创建单个索引的话,由于 mysql 查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效率,但是如果在 area、age 两列上创建复合索引的话将带来更高的效率。如果我们创建了 (area, age, salary) 的复合索引,那么其实相当于创建了 (area,age,salary)、(area,age)、(area) 三个索引,这被称为最佳左前缀特性。因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。
    3. 索引不会包含有 NULL 值的列
    只要列中包含有 NULL 值都将不会被包含在索引中,复合索引中只要有一列含有 NULL 值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为 NULL。
    4. 使用短索引
    对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个 CHAR(255) 的 列,如果在前 10 个或 20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和 I/O 操作。
    5. 排序的索引问题
    mysql 查询只使用一个索引,因此如果 where 子句中已经使用了索引的话,那么 order by 中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
    6.like 语句操作
    一般情况下不鼓励使用 like 操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而 like “aaa%”可以使用索引。
    7. 不要在列上进行运算
    select * from users where YEAR(adddate)<2007;
    将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成
    select * from users where adddate<‘2007-01-01’;
    8. 不使用 NOT IN 和操作
    NOT IN 和操作都不会使用索引将进行全表扫描。NOT IN 可以 NOT EXISTS 代替,id3 则可使用 id>3 or id<3 来代替。

十二、动态 SQL

== 什么是动态 SQL:动态 SQL 就是指根据不同的条件生成不同 SQL 语句 ==

1. 环境搭建

编写实体类

import lombok.Data;

import java.util.Date;
@Data
public class Blog {

<span class="hljs-keyword">private</span> String id;
<span class="hljs-keyword">private</span> String title;
<span class="hljs-keyword">private</span> String author;
<span class="hljs-keyword">private</span> Date createTime;
<span class="hljs-keyword">private</span> <span class="hljs-type">int</span> views;

}

2.IF

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

测试代码

@Test
public void queryBlogIFTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
<span class="hljs-type">HashMap</span> <span class="hljs-variable">map</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>();
map.put(<span class="hljs-string">"title"</span>,<span class="hljs-string">"java很简单"</span>);
List&lt;Blog&gt; blogs = mapper.queryBlogIF(map);
<span class="hljs-keyword">for</span> (Blog blog : blogs) {

    System.out.println(blog);
}
sqlSession.close();

}

运行结果图

3.choose(when,otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
       <choose>
           <when test="title != null">
               title = #{title}
           </when>
        <span class="hljs-tag">&lt;<span class="hljs-name">when</span> <span class="hljs-attr">test</span>=<span class="hljs-string">"author != null"</span>&gt;</span>
            and author=#{author}
        <span class="hljs-tag">&lt;/<span class="hljs-name">when</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">otherwise</span>&gt;</span>
            and views = #{views}
        <span class="hljs-tag">&lt;/<span class="hljs-name">otherwise</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">choose</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">where</span>&gt;</span>

</select>

测试代码

@Test
    public void queryBlogChoose(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    <span class="hljs-type">HashMap</span> <span class="hljs-variable">map</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>();
    map.put(<span class="hljs-string">"author"</span>,<span class="hljs-string">"唐三"</span>);
    map.put(<span class="hljs-string">"views"</span>,<span class="hljs-string">"9999"</span>);
    List&lt;Blog&gt; blogs = mapper.queryBlogChoose(map);
    <span class="hljs-keyword">for</span> (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

测试结果

4.trim、where、set

<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author =#{author}
        </if>
<span class="hljs-tag">&lt;/<span class="hljs-name">set</span>&gt;</span>
where id =#{id}

</update>

测试代码

@Test
public void queryBlogUpdate(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
<span class="hljs-type">HashMap</span> <span class="hljs-variable">map</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>();
map.put(<span class="hljs-string">"title"</span>,<span class="hljs-string">"2mybatis很简单"</span>);
map.put(<span class="hljs-string">"author"</span>,<span class="hljs-string">"唐四"</span>);
<span class="hljs-comment">//根据第一个博客的id,修改其标题和作者</span>
map.put(<span class="hljs-string">"id"</span>,<span class="hljs-string">"bf618aebd32143648dd982b31a2b8016"</span>);

mapper.updateBlog(map);
sqlSession.close();

}

5.SQL 片段

有的时候,我们可能会将一些功能的部分抽取出来,方便复用 (1) 使用 SQL 标签抽取公共的部分
<sql id="if-title-author">
    <if test="title != null">
        title = #{title},
    </if>
    <if test="author != null">
        author =#{author}
    </if>
</sql>

(2)在需要使用的地方使用 include 标签引用即可

<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <include refid="if-title-author"></include>
    </set>
    where id =#{id}
</update>

注意事项:

  • 最好基于单标来定义 SQL 片段

  • 不要存在 where 标签,一般 SQL 片段里放的最多的就是 if 判断

6.Foreach

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id=#{id}
        </foreach>
    </where>
</select>

测试代码如下

 @Test
    public void queryBlogForeach(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    <span class="hljs-type">HashMap</span> <span class="hljs-variable">map</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>();

    ArrayList&lt;Integer&gt; ids = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;Integer&gt;();

    ids.add(<span class="hljs-number">1</span>);
    ids.add(<span class="hljs-number">2</span>);
    ids.add(<span class="hljs-number">3</span>);

    map.put(<span class="hljs-string">"ids"</span>,ids);
    List&lt;Blog&gt; blogs = mapper.queryBlogForeach(map);

    <span class="hljs-keyword">for</span> (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

运行结果

动态 SQL 就是在拼接 SQL 语句,我们只需要保证 SQL 的正确性,按照 SQL 的格式,去排列组合就可以了

建议:先在 MySQL 中写出完整的 SQL,再对应的去修改成为我们的动态 SQL 实现通用即可!

十三、缓存

1. 简介

平时我们常用的数据库的查询:主要用来连接数据库,这样做比较消耗资源
一次查询的结果,给他暂存在一个可以直接取到的地方 --> 内存:缓存

我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
【什么是缓存】

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

【为什么使用缓存?】
减少和数据库的交互次数,减少系统开销,提高系统效率

【什么样的数据可以使用缓存?】
经常查询并且不经常改变的数据 【可以使用缓存】。简单理解,只有查询才会用到缓存!!!

2.MyBatis 缓存 (目前 Redis 使用最多)

  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大的提高查询效率

  • MyBatis 系统中默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,一级缓存开启(SqlSession 级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存。 举例:namespace="com.yff.dao.BlogMapper" namespace 级别即接口级别
    • 为了提高可扩展性,MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来定义二级缓存。

3. 一级缓存

一级缓存也叫本地缓存:SqlSession

  • 与数据库同义词会话期间查询到的数据会放在本地缓存中

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库

测试步骤:

  • 开启日志

  • 测试在一个 Sesion 中查询两次相同的记录

    public void test(){
          SqlSession sqlSession = MybatisUtils.getSqlSession();
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
      <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> mapper.queryUserById(<span class="hljs-number">1</span>);
      System.out.println(user);
    
      <span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> mapper.queryUserById(<span class="hljs-number">1</span>);
      System.out.println(user1);
    
      sqlSession.close();
    

    }

  • 查看日志输出


    缓存失效的情况

  • 查询不同的东西

  • 增删改操作,可能会改变原来的数据,所以必定会刷新缓存

  • 查询不同的 Mapper.xml

  • 手动清理缓存

    sqlSession.clearCache();
    


    小结:一级缓存默认是开启的,只在一次 Sqlsession 中有效,也就是拿到连接直到连接关闭连接这个区间段类有效
    一级缓存就是一个 map

4. 二级缓存

概念:
  • 二级缓存与一级缓存区别在于二级缓存的范围更大,多个 sqlSession 可以共享一个 mapper 中的二级缓存区域。

  • mybatis 是如何区分不同 mapper 的二级缓存区域呢?它是按照不同 mapper 有不同的 namespace 来区分的,也就是说,如果两个 mapper 的 namespace 相同,即使是两个 mapper,那么这两个 mapper 中执行 sql 查询到的数据也将存在相同的二级缓存区域中。

  • 由于 mybaits 的二级缓存是 mapper 范围级别,所以除了在 SqlMapConfig.xml 设置二级缓存的总开关外,还要在具体的 mapper.xml 中开启二级缓存。

  • 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。​​​​​​

使用步骤:

①开启全局缓存
<!-- 显示的开启全局缓存 -->
 <setting name="cacheEnabled" value="true"/>

②在要使用二级缓存的 Mapper 中开启
<!-- 在当前 Mapper.xml 中使用二级缓存 -->
    <!--
    以下参数的解释
    eviction: 使用 FIFO 这样一个输入输出策略
    flushInterval:每隔 60 秒刷新一次缓存
    size:最多存 512 个缓存
    readOnly:是否只读
    这些参数也可以不写
    -->
    <cache eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
③测试
@Test
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
<span class="hljs-type">UserMapper</span> <span class="hljs-variable">mapper</span> <span class="hljs-operator">=</span> sqlSession.getMapper(UserMapper.class);
<span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> mapper.queryUserById(<span class="hljs-number">1</span>);
System.out.println(user);
sqlSession.close();
<span class="hljs-comment">//mapper.updateUser(new User(2, "aaa", "bbb"));</span>

// sqlSession.clearCache();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

System.out.println(<span class="hljs-string">"================="</span>);
<span class="hljs-type">User</span> <span class="hljs-variable">user1</span> <span class="hljs-operator">=</span> mapper2.queryUserById(<span class="hljs-number">1</span>);
System.out.println(user1);

}

测试结果

  • 问题:如果二级缓存中没有写那些参数,则我们的实体类需要序列化,否则就会报如下错误!
 Cause: java.io.NotSerializableException: com.tang.pojo.User

解决方法:让 User 实体类序列化即可, 也就是如下实现 Serializable 接口即可实现实体类的序列化

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}

小结:

  • 只要开启了二级缓存,在同一个 Mapper 下就有效

  • 所有的数据都会先放在一级缓存中;

  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

5.Mybatis 缓存原理

6. 自定义缓存 Ehcache

Ehcahe 是一种广泛使用的开源 Java 分布式缓存,主要面向通用缓存

要在程序中使用 ehcahe, 先要导包

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

如果运行报 java.lang.NoClassDefFoundError: Could not initialize class net.sf.ehcache.CacheManager 就表名 ehcache 的包导错了,可以跟改为我上面的包即可
在 mapper 中指定使用我们的 ehcache 缓存

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
<span class="hljs-tag">&lt;<span class="hljs-name">diskStore</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"./tmpdir/Tmp_EhCache"</span>/&gt;</span>
<span class="hljs-comment">&lt;!--
   defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
 --&gt;</span>
<span class="hljs-comment">&lt;!--
  name:缓存名称。
  maxElementsInMemory:缓存最大数目
  maxElementsOnDisk:硬盘最大缓存个数。
  eternal:对象是否永久有效,一但设置了,timeout将不起作用。
  overflowToDisk:是否保存到磁盘,当系统当机时
  timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
  timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
  diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
  diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
  diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
  memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
  clearOnFlush:内存数量最大时是否清除。
  memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
  FIFO,first in first out,这个是大家最熟的,先进先出。
  LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
  LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

-->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>

<span class="hljs-tag">&lt;<span class="hljs-name">cache</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">"cloud_user"</span>
        <span class="hljs-attr">eternal</span>=<span class="hljs-string">"false"</span>
        <span class="hljs-attr">maxElementsInMemory</span>=<span class="hljs-string">"5000"</span>
        <span class="hljs-attr">overflowToDisk</span>=<span class="hljs-string">"false"</span>
        <span class="hljs-attr">diskPersistent</span>=<span class="hljs-string">"false"</span>
        <span class="hljs-attr">timeToIdleSeconds</span>=<span class="hljs-string">"1800"</span>
        <span class="hljs-attr">timeToLiveSeconds</span>=<span class="hljs-string">"1800"</span>
        <span class="hljs-attr">memoryStoreEvictionPolicy</span>=<span class="hljs-string">"LRU"</span>/&gt;</span>

</ehcache>