Mybatis

Mybatis

官网 https://mybatis.org/mybatis-3/zh/index.html

什么是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。

如何获得Mybatis?

  • Mybatis文档:https://mybatis.org/mybatis-3/zh/getting-started.html

  • Github:https://github.com/mybatis/mybatis-3/releases/tag/mybatis-3.5.6

  • maven仓库

     <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
    

持久化

数据持久化

  • 持久化是将程序数据在持久状态和瞬时状态间转换的机制。

  • 通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。

  • 持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。

  • 持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等

  • JDBC就是一种持久化机制。文件IO也是一种持久化机制。

持久层

持久层就是能够长期保存数据的一层

Dao层、Service层、Controller层

  • Dao层:完成持久化工作的代码块

  • Service层:业务层操作

  • Controller层:负责接收用户的请求

  • 层界限十分明显

为什么需要Mybatis

  • 帮助程序员将数据存入到数据库中
  • 方便
  • 传统的JDBC代码太复杂了。简化,自动化
  • 优点:
    • 简单易学
    • 灵活
    • 解除 sql 与程序代码的耦合,解除 sql 与程序代码的耦合
    • 提供映射标签,支持对象与数据库的 orm 字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供 xml 标签,支持编写动态 sql。

第一个MyBatis程序

自我观察的流程

1、编写util工具类。2、编写核心配置文件。3、编写pojo实体类。4、编写Dao接口。5、编写DaoMapper实现类

搭建环境

  • 搭建数据库

    CREATE DATABASE mybatis;
    
    USE mybatis;
    
    CREATE TABLE `user`(
      `id` INT NOT NULL PRIMARY KEY,
      `name` VARCHAR(20) NOT NULL,
      `pwd` VARCHAR(30) NOT NULL
    ) ENGINE=INNODB DEFAULT CHARSET=utf8;
    
    INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
    (1,'张三','123456'),
    (2,'李四','123456'),
    (3,'王五','123456');
    

新建项目

  1. 创建maven项目

    1. 删除src

    2. 导入依赖

      <!-- 导入依赖-->
      <dependencies>
      <!--        mysql驱动-->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>8.0.19</version>
              </dependency>
      <!--        mybatis-->
              <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
              <dependency>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis</artifactId>
                  <version>3.5.5</version>
              </dependency>
      <!--        junit-->
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.12</version>
                  <scope>test</scope>
              </dependency>
          </dependencies>
      
          <!--在build中配置resources,来防止我们资源导出失败的问题-->
          <build>
              <resources>
                  <resource>
                      <directory>src/main/resources</directory>
                      <excludes>
                          <exclude>**/*.properties</exclude>
                          <exclude>**/*.xml</exclude>
                      </excludes>
                      <filtering>false</filtering>
                  </resource>
                  <resource>
                      <!-- 由于maven配置文件默认是在resources目录里面
                      但是现在我们把配置文件放到了java目录下,故而导出不来,
                      所以现在我们要手动配置资源过滤,让他把src/main/java目录下的
                      properties.xml文件能够被导出 -->
                      <directory>src/main/java</directory>
                      <includes>
                          <include>**/*.properties</include>
                          <include>**/*.xml</include>
                      </includes>
                      <filtering>false</filtering>
                  </resource>
              </resources>
          </build>
      
  2. 在项目下面创建一个子模块

    ​ 创建使用子模块就可以了,父项目pom文件里面有jar包了,子项目就不需要引入了

    1. 编写mybatis的核心配置文件

      <?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核心配置文件-->
      <configuration>
          <!--    environments 环境,s 可放多套环境-->
          <environments default="development">
              <!--     environment环境-->
              <environment id="development">
                  <!-- transactionManager 事务管理-->
                  <transactionManager type="JDBC"/>
                  <!-- dataSource数据源-->
                  <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=UTF-8"/>
                      <property name="username" value="root"/>
                      <property name="password" value="123456"/>
                  </dataSource>
              </environment>
          </environments>
          
          <!--    每一个Mapper.xlm都需要在Mybatis核心配置文件中注册-->
      <!--    mappers表示父数,里面可以配置多个-->
          <mappers>
              <!--  每一个路径都以/-->
              <mapper resource="org/mybatis/example/BlogMapper.xml"/>
          </mappers>
      </configuration>
      
      
    2. 编写mybatis工具类

      //SqlSessionFactory --->SqlSession
      public class MybatisUtils {
          private static SqlSessionFactory sqlSessionFactory;
          static {
              //把资源加载进来
              try{
                  //使用Mybatis第一步:获取sqlSessionFactory对象
                  String resource = "mybatis-config.xml";
                  InputStream inputStream = Resources.getResourceAsStream(resource);
                  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
              }catch( IOException e){
                  e.printStackTrace();
              }
          }
          //创建一个能执行sql的对象
          //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
          // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
          public static SqlSession getSqlSession(){
              return sqlSessionFactory.openSession();
          }
      

    }

    
    
    
    3. 编写代码
    
    - 实体类
    
      ```java
      package com.kuang.pojo;
      //实体类
      public class User {
          private int id;
          private String name;
          private String pwd;
      
          public User() {
      
          }
      
          public User(int id, String name, String pwd){
              this.id = id;
              this.name = name;
              this.pwd = pwd;
          }
      
          public int getId() {
              return id;
          }
      
          public void setId(int id) {
              this.id = id;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getPwd() {
              return pwd;
          }
      
          public void setPwd(String pwd) {
              this.pwd = pwd;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "id=" + id +
                      ", name='" + name + '\'' +
                      ", pwd='" + pwd + '\'' +
                      '}';
          }
      }
     
      ```
    
      
    
    - Dao接口
    
      ```java
      public interface UserDao {
         //查询用户列表
          List<User> getUserList();
       }
      ```
    
    
    
  • 接口实现类

     由原来的Impl实现类转换为现在的Mapper配置文件
    
     ```xml
     <?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.kuang.dao.UserDao">
     <!--    select查询  id对应方法名字  就相当于重写方法名字  resultType返回一个结果类型-->
         <select id="getUserList" resultType="com.kuang.pojo.User">
             select * from mybatis.user
       </select>
     </mapper>
    
     ```
    
  1. junit测试

    public class UserDaoTest {
        @Test
        public void test(){
            //获取工具类的执行sql对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            //方式1:getMapper
            //获取Dao对象
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            //拿到Dao对象就可以调用他接口里面的方法了
            List<User> userList = userDao.getUserList();
    
    /*
            //方式2:返回一个的时候就用One  返回多个的时候就用List   不建议使用第二种
            List<User> list = sqlSession.selectList("com.xun.dao.UserDao.getUserList");
            User o = sqlSession.selectOne("com.xun.dao.UserDao.getUserList");
     */
            for (User user : userList) {
                System.out.println(user);
            }
            //关闭资源
            sqlSession.close();
        }
    }  
    

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

解决方法:在核心配置文件中注册 mappers

找不到资源异常


    <!--
    解决Mybatis,io找不到资源异常问题:
    
    mybatis-config.xml放在了resources目录下 运行还报io找不到资源
    
    java.io.IOException: Could not find resource mybatis-config.xml
    
    解决方法:
    
    设置资源过滤后,可以把mybatis-config.xml放在java里面随便一个目录下都行
    
    当然,放在resources里面也是可以的,由于resources下没有其他目录,所以把 资源过滤里面的 **/*.xml 改为 /*.xml就可以运行了 -->
    
    <!--在build中配置resources,来防止我们资源导出失败的问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>**/*.properties</exclude>
                    <exclude>/*.xml</exclude>
                </excludes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

CRUD

1、 namespace

namespace中的包名要和Dao/Mapper接口的包名一致

<mapper namespace="com.xun.dao.UserDao"> 
	<select id="" resultType="" parameterType="">
        
    </select>
</mapper>

2、select

  • id:就是对应namespace中的方法名

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

  • parameterType:参数类型

    1.编写接口

        //根据ID查询用户  
        User getUserById(int id);
    

    2.编写对应mapper中的sql语句

    <!--   根据ID查询用户-->
        <select id="getUserById" parameterType="int" resultType="com.xun.pojo.User">
            select * from mybatis.user where id = #{id}
        </select>
    

    3.测试

        //查询测试
        @Test
        public void testUserById(){
            //通过工具类获取执行 SQL对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            //通过对象拿到 要执行的接口
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            //拿到接口,然后调用里面要执行的方法,并传递参数
            User a = mapper.getUserById(1);
    
            System.out.println(a);
    		//关闭资源
            sqlSession.close();
        }
    

3、insert

  • id:就是对应namespace中的方法名

  • resultType:Sql语句执行的返回值,但是只有查询才有

  • parameterType:参数类型

    1.编写接口

    //添加用户
    int addUserdata(User user);
    

    2.编写对应mapper中的sql语句

    <!--    添加用户  除了查询有结果集 其他没有-->
        <insert id="addUserdata" parameterType="com.xun.pojo.User" >
            insert into mybatis.user(id, name, pwd) values (#{id},#{name},#{pwd});
        </insert>
    

    3.测试

    //添加测试   增删改需要提交事务
    @Test
    public void testAddUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        int seven = mapper.addUserdata(new User(5,"小七","123456"));
    
        if (seven>0){
            System.out.println("插入成功");
        }
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }
    

4、update

  • id:就是对应namespace中的方法名

  • resultType:Sql语句执行的返回值,但是只有查询才有

  • parameterType:参数类型

    1.编写接口

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

    2.编写对应mapper中的sql语句

    <!--    修改用户  除了查询有结果集 其他没有-->
        <update id="updateUser" parameterType="com.xun.pojo.User">
            update mybatis.user set pwd=#{pwd},name=#{name} where id=#{id};
        </update>
    

    3.测试

    //修改测试   增删改需要提交事务
    @Test
    public void testUpdateUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        int five = mapper.updateUser(new User(5, "五", "123321"));
    
        if (five>0){
            System.out.println("修改成功");
        }
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }
    

5、delete

  • id:就是对应namespace中的方法名

  • resultType:Sql语句执行的返回值,但是只有查询才有

  • parameterType:参数类型

    1.编写接口

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

    2.编写对应mapper中的sql语句

    <!--    删除用户  除了查询有结果集 其他没有-->
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id=#{id};
        </delete>
    

    3.测试

    //删除测试  增删改需要提交事务
    @Test
    public void testDeleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        int i = mapper.deleteUser(4);
    
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }
    

注意点:

增删改需要提交事务

6、Map

如果我们实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用map。

例子:

  • 不使用map添加数据

    1.编写接口

  //添加用户
    int addUserdata(User user);

​ 2.编写对应mapper中的sql语句

<!--    添加用户  除了查询有结果集 其他没有   里面的值不可以随意定制,要对照字段来写
		比如只想添加个 id										-->
    <insert id="addUserdata" parameterType="com.xun.pojo.User" >
        insert into mybatis.user(id) values (#{id});
    </insert>

​ 3.测试(下面重点注释)

//添加测试   增删改需要提交事务
@Test
public void testAddUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    
/*由于传递的是User对象,在new传参数进去的时候,需要把User对象里面 所有 参数写进来,否则报错。比如有百来个参数,
    就需要写百来个参数进来,很繁琐,且麻烦	*/
    
	mapper.addUserdata(new User(4,"",""));
    //提交事务
    sqlSession.commit();
    sqlSession.close();
}
  • 使用map添加数据

    1.编写接口

    //用map添加用户
    int addUserMap(Map<String,Object> map);
    

    2.编写对应mapper中的sql语句

    <!-- 用map添加用户   只需要传递map的key, key可以随意定制
    			比如只想添加个 id		-->
        <insert id="addUserMap" parameterType="map" >
            insert into mybatis.user(id) values (#{userid});
        </insert>
    

    3.测试(下面重点注释)

    //    用map添加用户
        @Test
        public void testMap(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
    
            Map<String, Object> map = new HashMap<String, Object>();
            //需要跟key对应
            map.put("userid",5);
    
            //如果传Map对象的话,只需要定义好你想传递的参数即可,不需要写一堆User对象里面的参数
            mapper.addUserMap(map);
    
            sqlSession.commit();
            sqlSession.close();
        }
    

如果使用User传参的话 里面的字段可以为空的话,操作数据的时候都需要把User里面的参数给定义好。想只定义一个都不行,

但是传Map的话 里面的参数字段可以为空的话,操作数据的时候只需要定义好你想传的参数就好,不需要把User对象里面的参数都定义

在xml中:

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

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

7、模糊查询

//模糊查询
List<User> likeSelect(String name);
<!--    模糊查询-->
    <select id="likeSelect" resultType="com.xun.pojo.User">
        select * from mybatis.user where name like "%" #{value} "%";
    </select>
//模糊查询
@Test
public  void  testLikeSelect(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    List<User> userList = mapper.likeSelect("李");

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

配置解析

1、核心配置文件

  • mybatis-config.xml
  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

2、环境配置(environments)

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

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

学会使用配置多套运行环境

Mybatis使用的默认事务管理器是:JDBC,连接池是:POOLED

3、属性(properties)

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

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置

编写一个配置文件

db.peoperties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username=root
password=123456

在核心配置文件中映入

注意:在xml中所有的标签都需要按照顺序来放

<!--    引入外部文件-->
    <properties resource="db.properties">
        <!-- 也可以在内部设置属性 -->
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的!

4、类型别名(typeAliases)

它仅用于 XML 配置

  • 类型别名可为 Java 类型设置一个缩写名字。
  • 意在降低冗余的全限定类名书写
<!--    可以给实体类起别名-->
    <typeAliases>
        <typeAlias type="com.xun.pojo.User" alias="User"/>
    </typeAliases>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 类

使用的时候,写上类名的小写字母即可

<!--    指定一个包名-->
    <typeAliases>
        <package name="com.xun.pojo"/>
    </typeAliases>

在实体类较少的情况下,使用第一种方式。

如果实体类十分多,建议使用第二种。

区别:

​ 第一种可以自定义别名

​ 第二种则不行( 如果非要起第二种别名的话,就需要在实体类上加注解 @Alias(“user”)

5、设置(settings)

cacheEnabled :

​ 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。

lazyLoadingEnabled:

​ 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的 开关状态。

logImpl:

​ 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

6、映射器(mappers)

定义 SQL 映射语句,我们需要告诉 MyBatis 到哪里去找到这些语句

MapperRegistry:注册绑定我们的mapper文件;

第一种:

    <!--    每一个Mapper.xlm都需要在Mybatis核心配置文件中注册-->
<!--    mappers表示父数,里面可以配置多个-->
    <mappers>
        <!--  每一个路径都以/-->
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>

第二种: 使用class文件绑定注册

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

注意点:

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

第三种:使用扫描包进行注册绑定

    <mappers>
        <package name="com.xun.dao"/>
    </mappers>

注意点:

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

7、生命周期和作用域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cM0qx6uq-1609049759776)(Mybatis.assets/image-20201216174204960.png)]

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

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory:

  • 说白了就是可以想象为数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • SqlSessionFactory 的最佳作用域是应用作用域 (全局作用域)
  • 最简单的就是使用单例模式或者静态单例模式

SqlSession

  • 连接到连接池的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完之后需要赶紧关闭,否则资源被占用!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQrLFvuE-1609049759782)(Mybatis.assets/image-20201216174658034.png)]

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

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

1、问题

数据库中的字段

  `id` INT NOT NULL PRIMARY KEY,
  `name` VARCHAR(20) NOT NULL,
  `pwd` VARCHAR(30) NOT NULL

建立实体类跟字段不一致的属性

    private int id;
    private String username;
    private String password;

根据id查询进行测试

<!-- 得出的结果 -->
User{id=1, username='null', password='null'}

为什么会变为null?

//本来的sql语句
select * from mybatis.user where id=#{id};
//结果
//经过类型处理器
select id,name,pwd from mybatis.user where id=#{id};

解决方法:

  • 起别名

        <select id="selectId" parameterType="_int" resultType="User">
            select id,name as username,pwd as password from mybatis.user where id=#{id};
        </select>
    

2、resultMap

结果集映射

id   name      pwd
id   username  password
<!--    结果集映射   type对应的是要映射的实体类  -->
    <resultMap id="UserMap" type="User">
<!--        column对应数据库中的字段,property对应实体类中的属性-->
        <result column="id" property="id"/>
        <result column="name" property="username"/>
        <result column="pwd" property="password"/>
    </resultMap>
<!--    resultMap要对应映射里面的id-->
    <select id="selectId" resultMap="UserMap">
        select * from mybatis.user where id=#{id};
    </select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了

日志

1、日志工厂

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

曾经:sout、debug

现在:日志工厂

logImpl : 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

  • SLF4J

  • LOG4J 【掌握】

  • LOG4J2

  • JDK_LOGGING java自带的日志输出

  • COMMONS_LOGGING 工具包

  • STDOUT_LOGGING 控制台输出 【掌握】

  • NO_LOGGING 没有日志输出

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

STDOUT_LOGGING 标准日志输出

在Mybatis核心配置文件中,配置我们的日志

    <settings>
<!--   标准的日志工厂实现      注意:大小写要区分,并且不能有空格-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

日志输出内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4I2Bx5Uo-1609049759785)(Mybatis.assets/image-20201217144714941.png)]

2、LOG4J

什么是log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

1.先导入log4j的包

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2.log4j.properties

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

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
#日志通过System.out输出就可以了
log4j.appender.console.Target = System.out
#DEBUG级别的日志输出
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/kuang.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

3.配置log4j的实现

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

简单使用

1.在要使用log4j的类中,导入包 import org.apache.log4j.Logger;

2.日志对象参数为当前类的class

static Logger logger = Logger.getLogger(UserMapperTest.class);

3.日志级别

logger.info("info");
logger.debug("debug");
logger.error("error");

3、解决不输出文件

配置文件把.log改成.text就可以了

分页

为什么要分页?

  • 减少数据处理量

使用Limit分页

select * from user limit 0,3;    #起始下标,显示多少条数据
select * from user limit 3;       #  默认 [0,n]

使用Mybatis实现分页

1.接口

//limit分页
List<User> getLimit(Map<String,Object> map);

2.Mapper.xml

<!--    limit分页-->
    <select id="getLimit" parameterType="map" resultMap="User">
        select * from mybatis.user limit #{initial},#{page}
    </select>

3.测试

@Test
public void testLimit(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("initial",0);
    map.put("page",3);
    List<User> limit = mapper.getLimit(map);

    for (User user : limit) {
        System.out.println(user);
    }
    sqlSession.close();
}

使用RowBounds分页

不使用Sql实现分页

1.接口

//    通过RowBounds实现分页
    List<User> getLimitRowBounds();

2.Mapper.xml

<!--    通过RowBounds实现分页-->
    <select id="getLimitRowBounds" resultMap="UserMap">
       select * from mybatis.user;
    </select>

3.测试

//通过RowBounds实现分页
@Test
public void testRowBounds(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //RowBounds实现分页
    RowBounds rowBounds = new RowBounds(1, 3);
    //通过java代码层面实现分页
    List<Object> list = sqlSession.selectList("com.xun.dao.UserMapper.getLimitRowBounds",null,rowBounds);
    for (Object o : list) {
        System.out.println(o);
    }
}

使用注解开发

面向接口编程

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

  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好
    在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
  • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
  • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法 .
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现 .
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

使用注解开发

1.注解在接口上实现

//用注解查询所有用户
@Select("select * from user")
List<User> getUser();

2.需要在核心配置文件中绑定接口

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

3.测试

@Test
public void Test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> user = mapper.getUser();
    for (User user1 : user) {
        System.out.println(user1);
    }
    sqlSession.close();
}

CRUD

注意:要将接口绑定到我们的核心配置文件中

我们可以在工具类创建的时候实现自动提交事务

public static SqlSession getSqlSession(){
    //设置commit自动提交
    return sqlSessionFactory.openSession(true);
}
查询

编写接口,增加注解

//用注解 通过id查询
//方法存在多个参数,所有的参数前面必须加上@Param("")注解,里面的参数就是要获取的参数,把名字给限定住了,引用类型不用加(map...)
@Select("select * from user where id = #{id}")
List<User> getUserId(@Param("id") int id2);

测试

@Test
public void TestId(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userId = mapper.getUserId(1);
    System.out.println(userId);
    sqlSession.close();
}
添加

编写接口,增加注解

//添加
@Insert("insert into user(id,name,pwd) values(#{id},#{username},#{password})")
int addUser(User user);

测试

//添加
    @Test
    public void TestAddUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(new User(8, "得到", "123654"));
        sqlSession.close();
    }
修改

编写接口,增加注解

//修改
@Update("update user set name=#{username} where id=#{id}")
int updateUser(User user);

测试

//修改
    @Test
    public void TestUpdateUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.updateUser(new User(8, "孤胆特工", ""));
        sqlSession.close();
    }
删除

编写接口,增加注解

//删除
//所有的参数前面必须加上@Param("")注解,里面的参数就是要获取的参数,把名字给限定住了,引用类型不用加
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid") int id);

测试

//删除
    @Test
    public void TestDeleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.deleteUser(8);
        sqlSession.close();
    }
关于@Param()注解
  • 基本类型的参数,或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但还是建议要加上
  • 在SQL中引用的就是@Param()中设定的属性名!
#{ },${ } 区别

#{}
  是预编译的方式,相当于jdbc的占位符PrepareStatement,

一个#{}就是一个占位符

mybatis在为#{}设置值时,会加引号

模 糊 查 询 时 不 用     直 接 拼 接 的 方 式 , 不 对 数 值 做 预 编 译     m y b a t i s 在 为 {} 模糊查询时不用   直接拼接的方式,不对数值做预编译   mybatis在为     mybatis{}设置值时,不加引号
  存在sql注入的现象

只有在不支持占位符的时候才使用,比如需要列名的地方,同时获取传来的数据时要做校验

Lombok

使用Lombok

Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 hashCode()equals() 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。
再也不用写get、set、toString、有参无参构造方法了,只需要注解就能实现

使用步驟:

  1. 在idea中安裝Lombok插件!

  2. 在meven中导入Lombok依赖

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
        <scope>provided</scope>
    </dependency>
    
  3. 在实体类上加注解即可

@Getter and @Setter  //生成get、set   在属性上加就生成当前的get、set
@FieldNameConstants
@ToString  //生成 toString
@EqualsAndHashCode // 生成 equals、hashCod、canEqual
@AllArgsConstructor  /*生成有参构造*/, @RequiredArgsConstructor and @NoArgsConstructor //生成无参构造
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data  //【常用】 生成 get、set、toString、无参构造、equals、hashCod、canEqual
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)

Lombok的优缺点

Lombok的好处:

  1. 减少模板代码:lombok处理get,set,toString,hash,equal等方法,大量的模板代码进行封装,减少重复代码,当增加新属性的时候,以上方法都不需要再重新编写;
  2. 让代码变得简洁,不用过多的去关注相应的方法
  3. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
  4. 增强代码可读性:专注于类的属性定义,不需要再去为排版浪费时间;

Lombok的坏处:

  1. 不支持多种参数构造器的重载

  2. 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

  3. 强迫队友:Lombok插件的使用,要求开发者一定要在IDE中安装对应的插件。不仅自己要安装,任何和你协同开发的人都要安装。 如果有谁未安装插件的话,使用IDE打开一个基于Lombok的项目的话会提示找不到方法等错误,导致项目编译失败。

    更重要的是,如果我们定义的一个jar包中使用了Lombok,那么就要求所有依赖这个jar包的所有应用都必须安装插件,这种侵入性是很高的。

  4. 代码可调试性降低:Lombok确实可以帮忙减少很多代码,因为Lombok会帮忙自动生成很多代码。
    但是,这些代码是要在编译阶段才会生成的,所以在开发的过程中,很多代码其实是缺失的。
    这就给代码调试带来一定的问题,我们想要知道某个类中的某个属性的getter方法都被哪些类引用的话,就没那么简单了。

多对一处理

多对一:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7R6mrR0-1609049759789)(Mybatis.assets/image-20201218143532037.png)]

  • 多个学生,对应一个老师
  • 对于学生这边而言,关联… 多个学生,关联一个老师 【多对一】
  • 对于老师而言, 集合… 一个老师,有很多学生 【一对多】

SQL

CREATE TABLE `teacher`(
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`,`name`) VALUES(1,'老师');

CREATE TABLE `student`(
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO student(`id`,`name`,`tid`)
VALUES('1','小明','1'),
('2','小红','1'),
('3','小张','1'),
('4','小李','1'),`student`
('5','小王','1');

测试环境搭建

  1. 导入lombok

  2. 新建实体类

    @Data
    public class Student {
        private int id;
        private String name;
    
        //学生需要关联一个老师
        private Teacher teacher;
    }
    
    @Data
    public class Teacher {
        private int id;
        private String name;
    }
    
  3. 建立Mapper接口

  4. 建立Mapper.xml文件

  5. 在核心配置文件中绑定注册Mapper文件或者接口

  6. 测试查询是否成功

按照查询嵌套处理(子查询)

    <!--    select * from student where tid in ,teacher t where s.tid = t.id;-->
    <!--
        1.查询所有的学生信息
        2.根据查询出来的学生tid,寻找对应的老师
            先查了学生,然后查老师,由于学生的结果里面存在一个特殊的对象,而不是一个普通的字段,就把它拿出来单独处理,用association 然后再通过tid查到
                teacher,在sql里面 这种叫做子查询
    -->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student ;
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!--        复杂的属性需要单独处理  对象:association   集合:collection-->
<!--        因为teacher是复杂属性是一个对象,所有要给他设置一个类型javaType="Teacher" 后面再追加一段sql 通过id查出来teacher  就相当于,查完前面的,在这里面又查一次(子查询) -->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
    </resultMap>

    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id=#{tid}
    </select>

按照结果嵌套处理(联表查询)

<!--    按照结果嵌套处理-->
    <select id="getStudent2" 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"/>
        <!--        复杂的属性需要单独处理  对象:association   集合:collection-->
        <!--        因为teacher是复杂属性是一个对象,所有要给他设置一个类型javaType="Teacher"  -->
        <association property="teacher"  javaType="Teacher">
<!--            需要处理Teacher的属性,映射Teacher里面的结果-->
            <result property="name" column="tname"/>
        </association>
    </resultMap>

一对多处理

比如:一个老师拥有多个学生!

对于老师而言就是一对多的关系!

环境搭建跟上面一样

实体类

@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="TeacherStudent">
    select s.id sid,s.name sname ,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="name" column="tname"/>
        <result property="id" column="tid"/>
<!--        复杂的属性需要单独处理  对象:association   集合:collection
            javaType=""  指定属性的类型!
            ofType=""  集合中的泛型信息 使用ofType
-->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

按照查询嵌套处理(子查询)

<select id="getTeacher3" resultMap="TeacherStudent1">
    select * from teacher where id=#{tid};
</select>
<resultMap id="TeacherStudent1" type="Teacher">

    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
</resultMap>

<select id="getStudent" resultType="Student">
    select id,name from student where tid = #{tid}
</select>

小结:

  1. 关联-association 【多对一】
  2. 集合-collection 【一对多】
  3. javaType & ofType
    1. javaType :用来指定实体类中属性的类型
    2. ofType :用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

面试高频

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mGtN10zN-1609049759792)(Mybatis.assets/image-20201219135821501.png)]

动态SQL

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

在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

if
choose (when, otherwise)
trim (where, set)
foreach

搭建环境

CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT '博客标题',
  `author` varchar(30) NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

自动生成随机id的工具类

public static String getId(){
    return UUID.randomUUID().toString().replaceAll("-","");
}

创建一个基础工程

  1. 导包

  2. 编写配置文件

  3. 编写实体类

    @Data
    public class Blog {
        private int id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
    }
    
  4. 编写实体类对应Mapper接口和Mapper.xml文件

IF:

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

choose、when、otherwise

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

传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG

    <select id="getSelectOtherwise" parameterType="map" resultType="Blog">
        select * from blog
        <where>
<!--     选择  -->
            <choose>
<!--   看哪个条件满足就执行哪个,如果都不满足就执行otherwise里面的。如果都满足了,只会执行第一个 (里面不可以有注释 否则会报错)  -->
                <when test="title1 !=null">
                    title=#{title1}
                </when>
                <!-- 由于加了where标签,当第一个不满足的时候,第二个满足了。就会执行第二个跟最后一个-->
                <when test="author !=null">
                    and author=#{author}
                </when>
                <!-- 如果都不满足 默认执行  -->
                <otherwise>
                    and views=#{views}
                </otherwise>
            </choose>
        </where>
    </select>

trim(where、set)

where:

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

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

set:

动态更新语句set, set 元素可以用于动态包含需要更新的列,忽略其它不更新的列

set 元素会动态地在行首插入 SET 关键字并会删掉额外的逗号(比如最后一个条件也加逗号会报错,但是set自动去除) 但是set不自动帮加逗号,所以要自己加好逗号

<update id="updateSet" parameterType="map">
    update blog
    <set>
        <if test="views !=null"> views=#{views},</if>
        <if test="author !=null"> author=#{author} ,</if>
        <if test="id !=null"> id=#{id} </if>
    </set>
    where title=#{title}
</update>

SQL片段

将一些复用的语句进行封装,提高复用性

有的时候,我们可能会将一些功能的部分抽取出来,方便复用!

  1. 使用SQL标签抽取公共的部分

    <sql id="suoyou">
        <if test="views !=null"> views=#{views},</if>
        <if test="author !=null"> author=#{author} ,</if>
        <if test="id !=null"> id=#{id} </if>
    </sql>
    
  2. 在需要使用的地方引用 include 标签

        <update id="updateSet" parameterType="map">
            update blog
            <set>
    <!--          引用sql片段 refid 填的是sql片段里面的id -->   
               <include refid="suoyou"> </include>
            </set>
            where title=#{title}
        </update>
    

foreach

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

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

1.编写接口:

//查询 第 1,2,3 号记录
List<Blog> selectBlogForeach(Map map);

2.编写对应mapper中的sql语句

<!--    查询 第 1,2,3 号记录-->
<!--    collection:传入的集合 item:集合项  index:索引,可以指定索引取指定的值   open:开始是什么 close:结束是什么  separator:分隔符是什么
    我们传递的是一个万能的map,这个map中可以存在一个集合
    就相当于从哪个 集合 里面 遍历 出来什么, 这个东西的 开头 是什么,结尾 是什么,分隔符 是什么
    然后 下面的 id = #{item遍历的值}  -->
    <select id="selectBlogForeach" parameterType="map" resultType="Blog">
        select * from blog
        <where>
            <foreach collection="list" item="aaa" index="index"  open="and ("  separator="or" close=")">
                    id=#{aaa}
            </foreach>
        </where>
    </select>

3.测试

@Test
public void TestSelectBlogForeach() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map map = new HashMap();

    ArrayList<Integer> l = new ArrayList<Integer>();
    l.add(1);
    l.add(2);
    l.add(3);

    map.put("list",l);

    List<Blog> blogs = mapper.selectBlogForeach(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
}

缓存

简介

/* 每次 查询 都需要连接 数据库, 就很耗资源
	怎么样来优化呢?
		就是在一次查询中,给他暂存在一个可以直接取到的地方! --> 内存:缓存
	我们再次查询相同数据的时候,就可以直接走缓存,不用走数据库了
  1. 什么是缓存[Cache]?
    • 存在内存中的临时数据。
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  2. 为什么使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么用的数据能使用缓存?
    • 经常查询并且不经常改变的数据。【可以使用缓存】
    • 不经常查询并且经常改变的数据。【不可以使用缓存】

Mybatis缓存

  • Mybatis包含一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • Mybatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace(就是代表一个接口,Mapper.xml里面最上面的指定哪个接口,相当于级别最高,里面的东西都使用它) 级别的缓存。
    • 为了提高扩展性,Mybatis定义了缓存接口Cache。可以通过实现Cache接口来自定义二级缓存
一级缓存:
  • 一级缓存也叫本地缓存:sqlSession

    • 与数据库同一次会话期间,查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。

    测试步骤:

    1. 开启日志,这样就可以观察到日志输出的东西了

    2. 测试在一个session中查询两次相同的记录

          @Test
          public void Test(){
              SqlSession sqlSession = MybatisUtils.getSqlSession();
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      
              User user = mapper.selectUser(1);
              System.out.println(user);
      
              System.out.println("===============");
              //传入的值要相同,就相当于你又重新查询了这条数据
              User user2 = mapper.selectUser(1);
              System.out.println(user2);
      
              sqlSession.close();
          }
      
    3. 查看日志输出

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWzEd7fY-1609049759793)(Mybatis.assets/image-20201224211349677.png)]
    

    缓存失效的情况:

    1. 查询不同的东西

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

    3. 查询不同的Mapper.xml

    4. 手动清理缓存

      sqlSession.clearCache();  //手动清理缓存
      

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

    一级缓存就相当于是一个Map,用的时候就从这Map里面拿东西,不用的时候就崩了

二级缓存:
  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

​ 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

​ 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

​ 这些属性可以通过 cache 元素的属性来修改。比如:

<cache
  eviction="FIFO"   	//输入输出策略
  flushInterval="60000"   //设置每隔60秒自动刷新缓存
  size="512"			//设置最多存储多少个缓存 
  readOnly="true"/>		//是否只读
  • 这个更高级的配置创建了一个 FIFO(输入输出) 缓存,每隔 60 秒刷新最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
    • 可用的清除策略有:
      • LRU – 最近最少使用:移除最长时间不被使用的对象。
      • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
      • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
        • 默认的清除策略是 LRU
  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  • readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

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

步骤:

  1. 开启全局缓存

        <settings>
    <!--        显示的开启全局缓存-->
            <setting name="cacheEnabled" value="true"/>
        </settings>
    
  2. 在要使用二级缓存的Mapper.xml中开启

    <!--    在当前Mapper.xml中使用二级缓存-->	
    <!-- 没有加 readOnly="true" 时, 需要去实体类序列化 ,implements Serializable -->
    <cache />	
    

    也可以自定义一些参数

    <cache
      eviction="FIFO"   	//输入输出策略
      flushInterval="60000"   //设置每隔60秒自动刷新缓存
      size="512"			//设置最多存储多少个缓存 
      readOnly="true"		//是否只读
           />	
    
  3. 测试

    @Test
    public void Test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    //默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectUser(1);
        System.out.println(user);
        sqlSession.close();   //此时一级缓存已经结束
    
        System.out.println("==================================");
    
        //传入的值要相同,就相当于你又重新查询了这条数据
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.selectUser(1);
        System.out.println(user2);
        sqlSession2.close();
        //由于是开启了二级缓存,所以一级缓存结束了,就直接启用二级缓存。
    }
    

    问题,需要将实体类序列化!否则就会报错

    Caused by: java.io.NotSerializableException: com.xun.pojo.User
    
    //序列化只需要在实体类 实现 序列化就可以了
    implements Serializable
    

    小结:

    • 只要开启了二级缓存,在同一个Mapper下就有效
    • 所有的数据都会先放在一级缓存中;
    • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

缓存原理:

​ 缓存顺序:

  1. 先看二级缓存中有没有
  2. 在看一级缓存中有没有
  3. 查询数据库

自定义缓存:-ehcache

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

要在程序中使用ehcache,先要导包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现

<!--    在当前Mapper.xml中使用二级缓存-->
    <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">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>
    
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
 
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      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,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->

</ehcache>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值