Mybatis入门

入门案例

第一个:创建maven工程并导入坐标

第二个:创建实体类和dao接口

第三步:创建Mybatis的主配置文件

​ SqlMapConfig.xml

第四步:创建映射配置文件

​ IUserDao.xml

环境创建的注意事项:

第一个:

​ 创建IUserDao.xml和IUserDao.java时名称是为了和我们之前的知识保持一致,在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper

第二个:

第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同

第四个:映射配置文件mapper标签namespace属性的取值必须是dao接口的全限定类名。

第五个:映射配置文件的操作配置(select),id 属性的取值必须是dao接口的方法名。

当我们遵从了第三,四,五点之后,我门在开发中就无须在写dao的实现类。

image-20200304112119243

MybatisTest
public class MybatisTest {
    @Test
    public static void main(String[] args) throws Exception {
        InputStream in= Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory=builder.build(in);
        SqlSession session=factory.openSession();
        IUserDao userDao=session.getMapper(IUserDao.class);
        List users = userDao.findAll();
        for(User user:users){
            System.out.println(user);
        }
        session.close();
        in.close();
    }
}
IUserDao
package com.itheima.dao;
import com.itheima.domain.User;
import java.util.List;
public interface IUserDao {
    List findAll();
}
User.java
package com.itheima.domain;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}
IUserDao.xml



    
    
    
    

sqlMapConfig.xml

src/main/resources/sqlMapConfig.xml




    
    
        
        
            
            
            
            
                
                
                
                
            
        
    
    
    
        
    

pom.xml


    4.0.0

    org.example
    firstbatis
    1.0-SNAPSHOT
    
        
        
            org.mybatis
            mybatis
            3.3.1
        

        
            mysql
            mysql-connector-java
            8.0.11
        
        
            junit
            junit
            4.12
            test
        
        
            log4j
            log4j
            1.2.12
        
    



    
            
                maven-compiler-plugin
                
                    13
                    13
                    UTF-8
                
            
    



注解实现案例

将IUserDao.xml删除

在IUserDao种使用注解

public interface IUserDao {
    @Select("select * from mybatis")
    List findAll();
}

将sqlMapConfig中

将resource="com/itheima/dao/IUserDao.xml"改成class="com.itheima.dao.IUserDao"



    

自己写DAO实现类

UserDaoImpl.java实现类

package com.itheima.dao.impl;
import com.itheima.dao.IUserDao;
import com.itheima.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;

public class UserDaoImpl implements IUserDao {
    private SqlSessionFactory factory;
    public UserDaoImpl(SqlSessionFactory factory){
        this.factory=factory;
    }

    public List findAll() {
        //使用工厂创建SqlSession对象
        SqlSession session = factory.openSession();
        //使用Sqlsession对象执行查询所有方法
        List list = session.selectList("com.itheima.dao.IUserDao.findAll");
        session.close();
        return list;
    }
}

Test.java

import com.itheima.dao.IUserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
    @Test
    public static void main(String[] args) throws Exception {
        //读取配置文件
        InputStream in= Resources.getResourceAsStream("sqlMapConfig.xml");
        //创建sqlsessionfactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory=builder.build(in);

        IUserDao userDao = new UserDaoImpl(factory);
        /*
        实现类做的工作定义了userDao的方法
        SqlSession session=factory.openSession();
        List users = session.selectList("com.itheima.dao.IUserDao.findAll");
        */
        //使用代理对象执行方法
        List users = userDao.findAll();
        //使用代理对象执行方法
        for(User user:users){
            System.out.println(user);
        }
        //释放资源
        in.close();
    }
}

mybatis允许我们自己来写实现类但是,mybatis的通过映射机制为我们自动创建实现类,项目中完全不用自己创建。

List list = session.selectList("com.itheima.dao.IUserDao.findAll");

实现类中的这句代码,让我们知道IUserDao.xml的namespace+id才能确定唯一的mysql语句,也就是那个dao(namespace)中的哪个方法(id)

案例分析

读配置文件

InputStream in= Resources.getResourceAsStream("sqlMapConfig.xml");

读配置文件使用相对路径或者绝对路径都存在问题一般情况下都不使用

使用的方法主要有两种:

1.使用类加载器,它只能读取类路径的配置文件

//类加载器
Properties pro =new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));

2.使用ServletContext对象的getRealPath(),获取项目部署的路径

//getRealPath()
ServletContext context = this.getServletContext();
String realPath = context.getRealPath("new.txt");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

Mybatis分析



    
    
    
    



    

com/itheima/dao/IUserDao.xml


    
    
    
    

自定义Mybatis分析

CRUD

执行完插入数据操作,不会自动提交事务。

DEBUG - Opening JDBC Connection
DEBUG - Created connection 2108763062.
DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7db12bb6]
DEBUG - ==>  Preparing: insert into mybatis(username,birthday,sex,address) values(?,?,?,?) 
DEBUG - ==> Parameters: newone(String), 2020-03-07 13:44:35.841(Timestamp), male(String), 北京1(String)
DEBUG - <==    Updates: 1
DEBUG - Rolling back JDBC Connection        <--[com.mysql.cj.jdbc.ConnectionImpl@7db12bb6]
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7db12bb6]
DEBUG - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7db12bb6]
DEBUG - Returned connection 2108763062 to pool.

需要添加一行代码

session.commit();

结果为

DEBUG - Opening JDBC Connection
DEBUG - Created connection 760357227.
DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2d52216b]
DEBUG - ==>  Preparing: insert into mybatis(username,birthday,sex,address) values(?,?,?,?) 
DEBUG - ==> Parameters: newone(String), 2020-03-07 13:48:38.379(Timestamp), male(String), 北京1(String)
DEBUG - <==    Updates: 1
DEBUG - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2d52216b]
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2d52216b]
DEBUG - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2d52216b]
DEBUG - Returned connection 760357227 to pool.

补充:

Mybatis的SqlSession运行原理

SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式)

Mybatis:

SqlSession session=factory.openSession();
//使用Mapper接口
IUserDao userDao=session.getMapper(IUserDao.class);
List users = userDao.findAll();

自己写实现类:

SqlSession session = factory.openSession();
//使用Sqlsession对象执行查询所有方法,直接使用Sqlsession
List list = session.selectList("com.itheima.dao.IUserDao.findAll");
session.close();

JDBC:

Class.forName("com.mysql.cj.jdbc.Driver");
//3.获取数据库连接
Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydate?serverTimezone=UTC","root","root");
//4.定义sql语句
String sql="delete from student2 where id =1";
//5.获取执行sql语句的对象statement
Statement stmt=conn.createStatement();
//6.执行sql,接受返回结果
int count=stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();

Mybatis的参数深入

我们在上一章节中已经介绍了SQL语句传参,使用标签的parameterType属性来设定。该属性的取值可以是基本类型,引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。

基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。 实体类类型,目前我们只能使用全限定类名。 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名.

POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。

使用POJO名称是为了避免和EJB混淆起来, 而且简称比较直接. 其中有一些属性及其getter setter方法的类,没有业务逻辑,有时可以作为VO(value -object)或dto(Data Transform Object)来使用.当然,如果你有一个简单的运算属性也是可以的,但不允许有业务方法,也不能携带有connection之类的方法。

OGNL 对象导航图语言 是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。这样可以更好的取得数据。

[站外图片上传中...(image-eff5f6-1589709193346)]

直接使用birthday就获取了domain.User对象的birthday参数相当于调用了user.getBirthday()

OGNL可以让我们用非常简单的表达式访问对象层,例如,当前环境的根对象为user1,则表达式person.address[0].province可以访问到user1的person属性的第一个address的province属性。

Mybatis中的返回值深入-解决实体类属性和数据库列名不对应的两种方式

为了解决查询结果的列名和实体类的属性名对应不上的问题

可以在查询数据库的时候对查询数据起别名。

[站外图片上传中...(image-ccd1e5-1589709193346)]

或者在mybatis中进行配置resultMap

resultMap


    
        
        
        
        
        
        
        
    
    
    

mybatis配置的设置

properties中可以用属性名resource=加载路径



属性值中同样可以使用url来引用配置文件




    
    
        

        
        
    
    
        
            
            
                
                
                
                
                
            
        
    

    
        
        
        
    


别名:typeAliases标签和package属性

    

        

        
    
    
        
        
        
    

Mybatis连接池pool

我们在前面的WEB课程中也学习过类似的连接池技术,而在Mybatis中也有连接池技术,但是它采用的是自己的连接池技术。在Mybatis的SqlMapConfig.xml配置文件中,通过来实现Mybatis中连接池的配置。

容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一个连接,集合还必须实现队列的特性,先进先出。

连接池类型

SqlMapConfig.xml中的dataSource标签type就是连接池的属性

type的取值

POOLED 使用连接池的数据源

UNPOOLED 不使用连接池的数据源 ,采用传统的获取连接的方式,虽然也实现了Javax.sql.DataSource接口,但是并没有使用池的思想。

JNDI采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的是服务器所能拿到的DataSource不一样

MyBatis在初始化时,根据的type属性来创建相应类型的的数据源DataSource,即:

type=”POOLED”:MyBatis会创建PooledDataSource实例

type=”UNPOOLED” : MyBatis会创建UnpooledDataSource实例

type=”JNDI”:MyBatis会从JNDI服务上查找DataSource实例,然后返回使用

注意:如果不是web或者maven的war工程,是不能使用的。tomcat服务器,采用的连接池就是dbcp连接池


    
    
    
    
    

运行结果

DEBUG - ==>  Preparing: select * from mybatis; 
DEBUG - ==> Parameters: 
DEBUG - <==      Total: 10
DEBUG - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@543295b0]
DEBUG - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@543295b0]
DEBUG - Returned connection 1412601264 to pool.《--归还连接到连接池》

Mybatis中DataSource的存取

MyBatis是通过工厂模式来创建数据源DataSource对象的,MyBatis定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory,通过其getDataSource()方法返回数据源DataSource。

下面是DataSourceFactory源码,具体如下:

package org.apache.ibatis.datasource;

import java.util.Properties;import javax.sql.DataSource;

public interface DataSourceFactory {
    void setProperties(Properties props);

    DataSource getDataSource();
} 

MyBatis创建了DataSource实例后,会将其放到Configuration对象内的Environment对象中, 供以后使用。 具体分析过程如下:
1.先进入XMLConfigBuilder类中,可以找到如下代码

[站外图片上传中...(image-df3ccd-1589709193346)]

2.分析configuration对象的environment属性,结果如下:

[站外图片上传中...(image-ab1e0f-1589709193346)]

Mybatis中连接的获取过程分析

当我们需要创建SqlSession对象并需要执行SQL语句时,这时候MyBatis才会去调用dataSource对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。     
@Test
public void testSql() throws Exception {
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession sqlSession = factory.openSession();
    List list = sqlSession.selectList("findUserById", 41);
    System.out.println(list.size());
} 
只有当第4句sqlSession.selectList("findUserById"),才会触发MyBatis在底层执行下面这个方法来创建java.sql.Connection对象。 如何证明它的加载过程呢? 我们可以通过断点调试,在PooledDataSource中找到如下popConnection()方法,如下所示: 分析


分析源代码,得出PooledDataSource工作原理如下:

[站外图片上传中...(image-56e9f4-1589709193346)]

事务提交

为什么CUD过程中必须使用sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了JDBC中的connection.commit()方法实现事务提交。

在创建session对象的时候采用openSession()

明白这一点后,我们现在一起尝试不进行手动提交,一样实现CUD操作。

@Before//在测试方法执行之前执行 
public void init()throws Exception{ 
//1.读取配置文件 
in = Resources.getResourceAsStream("SqlMapConfig.xml"); 
//2.创建构建者对象 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
//3.创建SqlSession工厂对象 
factory = builder.build(in); 
//4.创建SqlSession对象 
session = factory.openSession(true); 
//5.创建Dao的代理对象 
userDao = session.getMapper(IUserDao.class); 
}

@After//在测试方法执行完成之后执行 
public void destroy() throws Exception{ 
    //7.释放资源
    session.close(); 
    in.close(); 
} 
所对应的DefaultSqlSessionFactory类的源代码:

[站外图片上传中...(image-1f55af-1589709193346)]

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(boolean autoCommit) {//这里设置为true即可实现事务的自动提交
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }

Mybatis的动态SQL语句

Mybatis的映射文件中,前面我们的SQL都是比较简单的,有些时候业务逻辑复杂时,我们的SQL是动态变化的,此时在前面的学习中我们的SQL就不能满足要求了。

< if>标签

我们根据实体类的不同取值,使用不同的SQL语句来进行查询。比如在id如果不为空时可以根据id查询,如果username不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。


注意:标签的test属性中写的是对象的属性名,如果是包装类的对象要使用OGNL表达式的写法。 另外要注意where 1=1 的作用如果没有1=1,那么会变成如下错误语句:

 select * from mybatis where and username like #{username}
@Test 
public void testFindByUser() { 
    User u = new User(); u.setUsername("%王%"); 
    u.setAddress("%顺义%"); 
    //6.执行操作 
    List users = userDao.findByUser(u); 
    for(User user : users) { 
        System.out.println(user); 
    } 
}
where标签

< foreach>标签

传入多个id查询用户信息,用下边两个sql实现:

SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16) 
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)

QueryVo1.java

package domain;

import java.io.Serializable;
import java.util.List;

public class QueryVo1 implements Serializable {
    private List ids;

    public List getIds() {
        return ids;
    }

    public void setIds(List ids) {
        this.ids = ids;
    }
}
    

测试:

@Test
public void testfindInIds(){
    QueryVo1 vo1 = new QueryVo1();

    List ids = new ArrayList();
    ids.add(8);
    ids.add(9);
    ids.add(10);
    ids.add(14);
    vo1.setIds(ids);
    List inIds = mapper.findInIds(vo1);
    System.out.println(inIds);

拼接sql语句

DEBUG - ==>  Preparing: select * from mybatis WHERE id in ( ? , ? , ? , ? ) 
DEBUG - ==> Parameters: 8(Integer), 9(Integer), 10(Integer), 14(Integer)
DEBUG - <==      Total: 4

Mybatis中简化编写的SQL片段

定义代码片段

 
select * from mybatis 

引用代码片段

    
    

多表查询

一对一

account表和user表

想要查询account信息且对应的包含user信息

public class Account implements Serializable {

方式一:名称直接对应

方式二:使用resultMap

 
 
 
     
     
         
         
         
             
         
             
             
             
             
             
         
    
     
     

多对多

Role.java

package com.itheima.domain;

import java.io.Serializable;
import java.util.List;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 */
public class Role implements Serializable {

    private Integer roleId;
    private String roleName;
    private String roleDesc;

    //多对多的关系映射:一个角色可以赋予多个用户
  }

IRoleDao.xml




    
    
        
        
        
        
            
            
            
            
            
        
    
    
    

RoleTest.java

package com.itheima.test;

import com.itheima.dao.IRoleDao;
import com.itheima.domain.Role;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 */
public class RoleTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IRoleDao roleDao;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        roleDao = sqlSession.getMapper(IRoleDao.class);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        //提交事务
        // sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试查询所有
     */
    @Test
    public void testFindAll(){
        List roles = roleDao.findAll();
        for(Role role : roles){
            System.out.println("---每个角色的信息----");
            System.out.println(role);
            System.out.println(role.getUsers());
        }
    }
}

延迟加载

通过前面的学习,我们已经掌握了Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载

延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

实现:
我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要是通过association、collection实现一对一及一对多映射。association、collection具备延迟加载功能。

一对一的延迟加载

package domain;
import java.io.Serializable;

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    //单个User不是User的集合
    private User user;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    ...
}

添加配置信息到SqlMapConfig.xml



    
    
    

使用association实现延迟加载





    
        
        
        
        
        
    

    
    


    

测试类

public void test() throws Exception {
    List allAccount = mapper.findAllAccount();
}

结果

DEBUG - ==>  Preparing: select * from account 
DEBUG - ==> Parameters: 
DEBUG - <==      Total: 8

这里没有查询User, 当我们使用打印account时就会自动查询User属性

一对多的延迟加载

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String address;
    private String sex;
    private List accountList;

    public List getAccountList() {
        return accountList;
    }
    ...
    }

使用collection实现延迟加载

    
        
        
        
        
        
        
        
    

    

    

测试类

    @Test
    public void testFindAccountByUserId(){
        List allUser = mapper.findAllUser();
        for(User i:allUser){
            System.out.println("user:"+i.getId()+"账户-----------------------------------");
            List accountList = i.getAccountList();
            for(Account j:accountList){
                System.out.println("账户id"+j.getId());
                System.out.println("账户所属user"+j.getUser().getId());
            }
        }
    }

Mybatis中的缓存

​ 什么是缓存
​ 存在于内存中的临时数据。
​ 为什么使用缓存
​ 减少和数据库的交互次数,提高执行效率。
​ 什么样的数据能使用缓存,什么样的数据不能使用
​ 适用于缓存:
​ 经常查询并且不经常改变的。
​ 数据的正确与否对最终结果影响不大的。
​ 不适用于缓存:
​ 经常改变的数据
​ 数据的正确与否对最终结果影响很大的。
​ 例如:商品的库存,银行的汇率,股市的牌价。
​ Mybatis中的一级缓存和二级缓存

像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。[站外图片上传中...(image-4a3e7a-1589709193346)]

一级缓存:

它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。

SqlSession session1 = factory.openSession();
IUserDao   mapper1=session1.getMapper(IUserDao.class);
List byId = mapper1.findById(14);
System.out.println(byId);
List byId2 = mapper1.findById(14);
System.out.println(byId2);
session1.close();
is.close();

结果:同一个Session查询了2次但查询数据库1次

DEBUG - ==>  Preparing: select * from user where id = ? 
DEBUG - ==> Parameters: 14(Integer)
DEBUG - <==      Total: 1
[User{id=14, username='newone', birthday=Sat Mar 07 08:00:00 CST 2020, address='北京1', sex='male'}]
[User{id=14, username='newone', birthday=Sat Mar 07 08:00:00 CST 2020, address='北京1', sex='male'}]

分析

一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

[站外图片上传中...(image-4135c6-1589709193346)]

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。

得到用户信息,将用户信息存储到一级缓存中。

如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

@Test
public void testSecondLevelCache(){
    SqlSession session1 = factory.openSession();
    IUserDao   mapper1=session1.getMapper(IUserDao.class);

    User user1 = mapper1.findById(14);
    System.out.println(user1);

    User user16 = mapper1.findById(16);

    //session.close()或者session.clearCache()都会请理缓存

    //2.更新用户信息
    user16.setUsername("update user clear cache");
    user16.setAddress("北京市海淀区");
    mapper1.updateUser(user16);

    //3.再次查询id为14的用户
    User user2 = mapper1.findById(14);
    System.out.println(user2);

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

}

二级缓存:

它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)

 
 
     
 
因为cacheEnabled的取值默认就为true,所以这一步可以省略不配置。为true代表开启二级缓存;为false代表不开启二级缓存

​ 第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)

标签表示当前这个mapper映射将使用二级缓存,区分的标准就看mapper的namespace值。 
 
 

     
     

​ 第三步:让当前的操作支持二级缓存(在select标签中配置)

 
 
将UserDao.xml映射文件中的