Mybatis学习笔记


一、什么是MyBatis

特点

  • 优秀持久层框架
  • 支持自定义 SQL、存储过程以及高级映射
  • 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
  • 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

历史发展

  • 本名“iBatis”,iBATIS一词来源于“internet”和“abatis”的组合
  • 2010年由apache基金会迁移到谷歌,改名为MyBatis
  • 2013年11月由谷歌迁移到GitHub

持久化

  • 什么是持久层

    即数据持久化,也就是指数据瞬时状态和持久状态的过程

    也指数据对象持久化,防止对象数据丢失

MyBatis优点

  • 解除sql与程序代码的耦合
  • 提供映射标签
  • 提供对象关系映射标签
  • 提供xml标签,支持编写动态sql

二、程序结构

  • 3个jar包

    • mysql-connector-java.jar
    • junit.jar
    • mybatis.jar
  • 核心配置文件

    
        
        
            
            
            
                
                
                
                
                    
                    
                    
                    
                
            
        
        
        	
        
    
    

    注意:Mapper.xml注册

  • 数据库连接

    public class MybatisUtils{
        private SqlSessionFactory sqlSessionFactory;
        static{
            String resource = "mybatis-config.xml";
            InputStream inputStream = MybatisUtils.class.getClassLoader().getResourceAsStream(resource);
            //InputStream inputStream = Resources.getResourceAsStream(resource);
            sqSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }
        //返出session对应mapper.xml
        public static SqlSession getSession(){
            return SqlSessionFactory.openSession();
        }
    }
    
    
  • SQL语句实现

    <?xml version="1.0" encoding="UTF-8" ?>
    
    
    
    
    
        
    
    
  • 测试

    public class Test{
        SqlSession sqlSession = MybatisUtils.getSession();
        接口名 mapper = sqlSession.getMapper(接口名.class);
        返回类型 对象名 = mapper.方法名();
        //关闭
        sqlSession.close();
    }
    
  • 资源过滤

三、搭建入门Mybatis程序

1.程序部署

  • MySQL5.7/MySQL8.0以上

  • 创建数据库和数据表

    CREATE TABLE `users` (
      `id` int NOT NULL AUTO_INCREMENT,
      `name` varchar(20) CHARACTER SET utf8 COLLATE utf8 NOT NULL,
      `pwd` int NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8
    

    注意写引擎(ENGINE)和字符集(CHARSET

    注意配置Maven的setting.xml和仓库

  • 创建一个MavenModel

    • 将其src删除,以当前Model为父工程,创建子工程,防止多次配置pom.xml

    • 在父工程pom.xml配置以下内容

      <?xml version="1.0" encoding="UTF-8"?>
      
          4.0.0
      
          
          com.dragon
          mybatisStudy
          pom
          1.0-SNAPSHOT
          
              mybatis-01
          
          
          
          
              
                  mysql
                  mysql-connector-java
                  8.0.19
              
              
              
                  org.mybatis
                  mybatis
                  3.5.9
              
              
              
                  junit
                  junit
                  4.12
              
          
          
              UTF-8
          
              
          
              
                  
                      src/main/resources
                      
                          **/*.properties
                          **/*.xml
                      
                      true
                  
                  
                      src/main/java
                      
                          **/*.properties
                          **/*.xml
                      
                      true
                  
              
          
      
      

      注意:一定要配置资源过滤,否则项目执行不成功

    • 该项目中包含三个包

      • pojo实体类

      • dao数据处理层

        1.接口

        2.userMapper.xml

      • utils工具包

        1.MybatisUtils.class

  • 链接数据库在IDEA中

  • 创建mybatis配置文件“mybatis-config.xml

    • 位于src/main/resources/

    • 配置内容如下

      <?xml version="1.0" encoding="UTF-8" ?>
      
      
          
          
              
                  
                  
                      
                      
                      
                      
                  
              
          
          
              
          
      
      

      注意:此处一定要配置mapper,找到userMapper.xml文件,否则执行不成功(userMapper.xml是实现接口的方法)

  • userMapper.xml内容如下

    <?xml version="1.0" encoding="UTF-8" ?>
    
    
    
    
        
    
    

    此处getUserList为接口中的方法名

  • MybatisUtils.class

    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 java.io.IOException;
    import java.io.InputStream;
    
    public class MybatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            //使用Mybatis第一步,获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = null;
            try {
                inputStream = Resources.getResourceAsStream(resource);
            } catch (IOException e) {
                e.printStackTrace();
            }
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
    
  • Test测试类

    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class MyBatisTest {
        @Test
        public void test(){
            //获取sqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List userList = mapper.getUserList();
    
            for (User user : userList) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    }
    
    

2.解决问题

2.1

  • mybatis-config连接驱动中,如果是mysql8版本以下的jar包/依赖,那么可以如下写

    
    
    
  • 相反,在mysql8以及8以上版本

    
    
    

2.2

  • 配置pom.xml一定要配置资源过滤,否则会出现找不到userMapper.xml文件。

     
         
             
                 src/main/resources
                 
                     **/*.properties
                     **/*.xml
                 
                 true
             
             
                 src/main/java
                 
                     **/*.properties
                     **/*.xml
                 
                 true
             
         
     
    
  • 在mybatis-config.xml文件中配置内容中一定要注册mapper,否则会出现“org.apache.ibatis.binding.BindingException: Type interface com.dragon.dao.UserDao is not known to the MapperRegistry.”这种错误。

    
    
     
    
    

四、CRUD

1.选择、查询语句

  • id:就是对应的namespace方法名
  • resultType:sql语句执行的返回值类型
  • parameterType:参数类型
  1. 编写接口

      List getUserList();
    
  2. 配置mapper中SQL语句

    
        
    
    
  3. 测试类

     @Test
        public void test(){
            //获取sqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            users = mapper.getUserList();
    
            for (User user : users) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    

2.增加数据

  1. 编写接口

     int insertUser(User user);
    
  2. 配置Mapper中SQL语句

     
            insert into mybatis.users values (#{id},#{name},#{pwd});
     
    
  3. 测试类

     @Test
        public void testInsert(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int user0 = mapper.insertUser(new User(6, "小红", 888));
            if (user0>0){
                System.out.println("插入成功");
            }
            //提交事务
            sqlSession.commit();
            sqlSession.close();
        }
    

3.删除数据

  1. 编写接口

    void deleteUserById(int id);
    
  2. 配置Mapper中SQL语句

    
        delete from mybatis.users where id = #{id}
    
    
  3. 测试类

        @Test
        public void testDeleteById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.deleteUserById(6);
            users = mapper.getUserList();
            for (User user : users) {
                System.out.println(user);
            }
            sqlSession.commit();
            sqlSession.close();
        }
    

4.更改数据

  1. 编写接口

    void queryUser(int pwd);
    
  2. 配置Mapper中SQL语句

    
        update mybatis.users set pwd=#{pwd} where id = 3
    
    
  3. 测试类

    @Test
        public void testQueryById(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.queryUser(0000);
            users = mapper.getUserList();
            for (User user : users) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    

5.注意

  • 在增、删、改三种情况下必须提交事务!

    sqlSession.commit();
    
  • 关闭sqlSession

    sqlSession.close();
    
  • 接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上!

  • 有时候根据业务的需求,可以考虑使用map传递参数!

    当一个实体类属性过多时,可以使用Map来解决命名传参

    //java---controller层
    Map<> list = new HashMap;
    list.put("a",值);
    
    
    
        insert into table values(#{a});
    
    
    • Map传递参数,直接在SQL中取出即可
    • 对象传递参数,直接在SQL中取对象的属性即可
    • 只有一个基本类型参数的情况下,可以直接在sql中取到
    • 多个参数用Map或者注解
  • 为了规范操作,在SQL的配置文件中,我们尽量将Parameter参数和resultType都写上!

6.SQL模糊查询

  • 在sql.xml中直接%%,容易被SQL注入,不知道为啥
  • 在controller中用%%

五、配置

1.结构

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

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

2.属性(properties)

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


  
  

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:


  
  
  
  

3.属性别名(typeAliases)

  • 为实体类设置别名,意在降低冗余的全限定类名书写

    • DIY自定义
    
    	
    
    
    • 扫描包
    
    	
    
    
    
    • 注解
    @Alias("user")
    public class User{
        
    }
    

4.映射器(mappers)

  • 注册mapper-SQL语句

  • 三种方式

    • 相对路径

      
      	
      
      
    • 使用完全限定资源定位符(URL)

      
        
        
        
      
      
    • 使用映射器接口实现类的完全限定类名

      
        
        
        
      
      
    • 将包内的映射器接口实现全部注册为映射器

      
        
      
      

六、生命周期及作用域

  • SqlSession建造流程

  • 两种方式结束

    1. Sqlmapper获取类,类获取方法
    2. SqlSession直接调用sql操作(不建议使用)
  • 生命周期、作用域是至关重要的,因为错误的使用会导致非常严重的并发问题

  • SqlSessionFactoryBuilder

    • 一旦创建了SqlSessionFactory,就不需要建造者了
    • 局部变量
  • SqlSessionFactory:

    • 可以理解为:数据库连接池
    • 一旦被创建,在运行期间一直存在,没有任何理由丢弃或重新创建另外一个实例(当然也可以创建,但是会出现高并发,导致程序崩溃
    • 最简单就是使用单列模式/静态单列模式
  • SqlSession

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

    • 每个Mapper代表一个业务

七、ResultMap结果映射集

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

  • 起别名

SQL中的字段与bean中的字段不一致

修改sql语句

as 别名

  • resultMap的使用

    当类中的属性和数据库的字段不一致时

    
    	
    
    
        
    
    

八、日志工厂

  • 在配置文件出错时,我们基本无法排查,所以有了日志工厂,以此来监听内部步骤

  • mybatis-config.xml文件中设置以下内容

    
    	
        
    
    
  • 官方文档:

logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

1.Log4j

  • 什么是Log4j

    • Apache的一个开源项目,通过使用Log4j,可以控制日志信息输送的目的地是控制台、文件、GUI组件
    • 也可以控制每一条日志的输出格式
    • 通过配置文件来灵活地进行配置,而不需要修改代码
  • 先导入log4j的包

    
    
        log4j
        log4j
        1.2.17
    
    
  • 配置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/dragon.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为日志实现

    
    	
    
    
  • 简单使用

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

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

      static Logger logger = Logger.getLogger(当前类名.class);
      
    3. 日志级别

      logger.info("info进入testlog4j");
      logger.debug("debug进入testlog4j");
      logger.error("error进入testlog4j");
      

九、分页

9.1LIMIT分页

  • 作用:减少数据处理量

  • SELECT * FROM user LIMIT startIndex,pageSize;
    #或者-----起始位置,每页数量
    SELECT * FROM user LIMIT pageSize;
    
  • 使用MyBatis分页

    1. 接口

      List getUserByLimit(Map map);
      
    2. SQL

      
      
    3. HashMap<> map = new HashMap();
      map.put("startIndex",1);
      map.put("pageSize",2);
      

9.2RowBounds分页

十、注解开发MyBatis

  • 开发中基本不用
  • 简单,不用配置xml

十一、剖面解析原理

十二、复杂查询

1.多对一

  • ResultMap结果映射

    • association:对象
    • collection*:集合
  • 两个实体类

    • Teacher.java
    • Student.java

    其中多个学生对应一个老师

    在Student类中创建Teacher的姓名

  • 
    
        
        
        
        
    
    
    
  • 注意:提前映射Mapper文件,设置typeAlias

十三、动态SQL

  • 动态 SQL 是 MyBatis 的强大特性之一。

  • 动态关键词

    • if

      
      
    • choose (when, otherwise)

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

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

      
      
    • trim (where, set)

      
      
    • foreach

      动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

      
      

十四、缓存

简介

1、什么是缓存 [ Cache ]?

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

2、为什么使用缓存?

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

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

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

Mybatis缓存

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

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

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

1.一级缓存

  • 一级缓存又叫“本地缓存”:SqlSession
    • 与数据库同一次会话期间查询到数据会放在本地缓存中
    • 以后如果需要获取相同数据,直接存缓存中拿,没必要去查询数据库
  • 缓存时间即事务开启到事务结束

一级缓存失效的四种情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

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

1、sqlSession不同

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

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

   session.close();
   session2.close();
}

观察结果:发现发送了两条SQL语句!

结论:每个sqlSession中的缓存相互独立

2、sqlSession相同,查询条件不同

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

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

   session.close();
}

观察结果:发现发送了两条SQL语句!很正常的理解

结论:当前缓存中,不存在这个数据

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

增加方法

//修改用户
int updateUser(Map map);

编写SQL


  update user set name = #{name} where id = #{id}

测试

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

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

   HashMap map = new HashMap();
   map.put("name","kuangshen");
   map.put("id",4);
   mapper.updateUser(map);

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

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

   session.close();
}

观察结果:查询在中间执行了增删改操作后,重新执行了

结论:因为增删改操作可能会对当前数据产生影响

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

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

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

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

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

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

   session.close();
}

一级缓存就是一个map

2、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

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

使用步骤

1、开启全局缓存 【mybatis-config.xml】


2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】



官方示例=====>查看官方文档

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

3、代码测试

  • 所有的实体类先实现序列化接口
  • 测试代码
@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();

   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

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

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

   session2.close();
}

结论

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

3.缓存原理

当一级缓存的session关闭时,利用我们在mapper中设置的来进行二级缓存