mybatis学习


目录
  • MyBatis
    • 什么是MyBatis
      • 持久化
      • ORM
    • 功能架构
    • 第一个Mybatis程序
      • 1、创建数据库表
      • 2、使用idea搭建一个maven项目
      • 3、编写mybatis的核心配置文件
      • 4、编写dao接口和mapper配置文件
      • 5、编写工具类获得SqlSession类
      • 6、编写实体类
      • 7.编写测试类
        • 执行过程简单分析
    • 日志配置Log4j
      • 1、引入pom依赖
      • 2、创建日志配置文件
    • #和$的区别
    • 多个参数问题
    • 分页查询
      • 使用PageHelper分页
    • 动态sql
    • 多表查询
      • 多对一
      • 一对多
      • 总结
    • 缓存
      • 一级缓存
      • 二级缓存
      • 注意
    • 作用域和生命周期
      • SqlSessionFactoryBuilder
      • SqlSessionFactory
      • SqlSession

MyBatis

什么是MyBatis

myBatis是一款优秀的持久层框架,它支持自定义sql、存储过程以及高级映射。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录

myBatis是一个轻量级的ORM框架。相对重量级的ORM框架Hibernate而言,mybatis是一个半自动框架,而Hibernate是一个全自动框架。而为什么相对与功能强大的Hibernate全自动化不使用而使用相对较多的是mybatis

原因

  1. Hibernate
  2. 配置繁琐
  3. Hibernate对功能进行全方面的封装,将用户操作,转化为sql语句,然后进行数据库操作,整个转换过程是Hibernate自动完成的,开发无法控制,如果要进行sql语句的优化是没法实现的。所以在数据库压力逐渐增大时,Hibernate框架性能问题就出现了。
  4. 而 mybatis将sql语句的定义控制权完全交给了开发者,并且暴露一套API,对JDBC中:事务,参数,查询结果等进行了配置处理。

持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据持久化的方式:数据库jdbc,io文件

为什么需要持久化?有些对象,不能让他丢失。内存太贵,并且有限

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

ORM

ORM:Object relation mapping 对象关系映射

将数据库中的信息和java实体类进行映射

功能架构

我们把mybatis功能架构分为三层:

  1. API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库,接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理
  2. 数据处理层:负责具体的sql查找、sql解析、sql执行和执行结果映射处理。它主要的目的是根据调用的请求完成一次数据库操作
  3. 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

第一个Mybatis程序

mybatis官网:https://mybatis.org/mybatis-3/

1、创建数据库表

CREATE TABLE `stu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `weight` int(11) DEFAULT NULL,
  `address` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

2、使用idea搭建一个maven项目

在pom.xml中导入依赖

 //mysql依赖

            mysql
            mysql-connector-java
            8.0.22
  
  //mybatis依赖
  
            org.mybatis
            mybatis
            3.4.6
    

3、编写mybatis的核心配置文件

在resources资源目录中创建mybatis.xml

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



    

     
        
    
    
        
        
        
        
    

    
    
        
        
    

    
    
    
        
        
            
            
            
                
                
                
                
            
        
    

    
    
    
    
    
    
        
    

configuration中的标签(必须顺序编写不然会报错)

  1. properties:属性配置文件,一般用于数据库链接配置 因为xml中有些符号需要转义
  2. settings:全局设置,如日志配置
  3. typeAliases:类型别名 ,有package和typeAlias
  4. typeHandlers:类型处理器mybatis内置存在很多进行类型适配的处理器一般不配置,mybatis会进行自动适配
  5. objectFactory:对象工厂
  6. objectWrapperFactory:对象包装类工厂
  7. reflectorFactory:反射工厂
  8. plugins:插件mybatis支持插件式扩展 拔插式扩展 一般使用分页插件
  9. envirnoments:运行环境
  10. databaseIdProvider:
  11. mappers:引入映射文件

4、编写dao接口和mapper配置文件

以前操作jdbcDao层时,会编写一个Dao接口在编写接口的实现类在实现类中进行对数据的jdbc操作编写sql语句,而接口的实现类现在可以不写了可以通过使用Mapper配置文件完成,而如何将dao接口与Mapper配置文件进行绑定,以前是进行实现这个接口,现在使用mapper标签中的namespace属性设置全类名来进行绑定,可以看做绑定后那么这个mapper配置文件就是Dao接口的实现类了,而这个mapper中的sql标签的如:select等,也可以看做是Dao接口中的方法的实现重写方法,而方法名要与标签通过标签的id属性进行绑定,命名要相同。

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



    
    
    
        
        
        
    

    
    
        id,name,age,weight,address
    

    
    
    
    
    
    
        insert into student value (0,#{name},#{age},#{sex},#{address},#{birth})
    
    
        update student set name = #{name}, age = #{age}, sex = #{sex},address = #{address},birth = #{birth} where id = #{id}
    
    
    

5、编写工具类获得SqlSession类

/**
 * SqlSession 工具类
 */
public class SqlSessionUtil {

    final static SqlSessionFactory sqlSessionFactory;


    static {
        String config = "mybatis.xml";
        //找到配置文件
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(config);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 加载  解析
        // 通过工厂构建模式 加载解析 配置文件
        // 创建Sql Session 工厂  : SQL 会话工厂  产生SQL 会话对象
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }


    public static SqlSession getSqlSession(){
         // 创建一个具体的SQL 会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
}

6、编写实体类

//使用了lombok插件
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String studentName;
    private Integer age;
    private Integer weight;
    private String address;
}

7.编写测试类

    public static void main(String[] args) {
        /*
        *  1. 加载所有的配置文件
        *  2. 解析配置文件
        *  3. xml文件 解析成一个对象  Configuration 包含了所有的配置信息  数据源信息  sql指令
        *  4. 使用Map 容器,将每个SQL指令 存储在Map 容器中, namespace+指令ID值作为key,将每个指令标签封装成  MappedStatement 对象
            
        *           SqlCommandType : 指令类型
        *           SqlSource  : 解析后的sql语句
        *
        *   5. 创建了SqlSession 会话对象
        *       sqlSessionFactory.openSession(); : Configuration
        *       事务管理对象
        *   6. sqlSession.selectOne :
        *       1. 先查询所有
        *       2. 所有中取1个  如果不是一个则报错
        *   7. 从全局的map容器中获取MappedStatement对象
        *       7.1. 从MappedStatement获取指令类型
        *       7.2. 从MappedStatement获取SQL指令 sql语句
        *   8. 创建PrepareStatement  SQL 预处理对象
        *   9. 执行SQL
        *   10. 将查询结果集封装成指令中预定义的结果类型
        * */
        SqlSession sqlSession = FactorySession.getSqlSession();
        
          //两种方式 执行sql
        //方式一:指令ID的方式
               List students = sqlSession.selectList("com.zll.pojo.StudentDao.listStudent");
 //====================================================================================   
        //方式二:使用接口代理的方式,使用Mapper接口的代理方式,根据方法名和映射文件中指令ID进行绑定,动态调用相关指令信息
                /*
        * StudentDao 是一个接口
        *   StudentDao dao 是StudentDao的一个实例、对象  必然存在一个实现了StudentDao接口的类
        *   该类是程序自动创建的一个实现类。这种方式就是动态代理
        *   // 实现了接口 :则存在接口中的方法,可以获取到方法的声明   方法体则根据
        *   接口全路径(包名+接口名)+方法名从Configuration的mappedStatements的Map中去根据它查找对应的
        *   MappedStatement ,然后根据指令类型,调用相应的执行器。执行相关代码
        * */
       
         //可以看到生成的动态代理类 将代理生成的文件  进行保存
       //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        List students = dao.listStudent();
        
//=====================================================================================        
        System.out.println(students);
        sqlSession.close();
    }

执行过程简单分析

  1. 解析xml配置文件通过sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);得到----》configuration里面封装了configuration中的所有配置信息,其中configuration封装了一个Map类型的protected final Map mappedStatements;mappedStatements变量
  2. 根据configuration中将mapper映射文件信息进行了解析,将mapper文件中的指令通过key和value进行存储到mappedStatements的变量Map容器中,key是由mapper中的namespace加指令id组成然后作为map容器的key,将每个指令标签封装成MappedStatement对象,MappedStatement中有sqlCommandType:指令类型和sqlSoure:解析后的sql语句,两个变量,将MappedStatement作为value。
  3. 创建sqlSession会话对象 ---》sqlSessionFactory.openSession();
  4. 不使用动态代理的话,就调用sqlSession会话对象的执行sql方法,如执行selectListList selectList(String statement)就需要传入statement,传入map容器中的key,获取对应map容器的value得到MappedStatement对象,MappedStatement中已经定义好了参数类型、返回结果类型和原生sql
  5. 获取原生sql参数类型 返回结果的类型,根据参数类型,原生sql可以内部预处理sql将sql发送给数据库
  6. 接收sql执行结果,将其封装成为对应的返回类型。

日志配置Log4j

日志,就是将程序运行时,一些关键信息进行保存文件中的操作.当问题发生时,是无法观察控制台,将关键的信息保存在文件,便于后期检查。

日志分为一下级别:

  • debug调试模式时进行记录的调试信息 debug级别最低
  • info具体信息 info级别高于DEBUG
  • error 发生异常时可以进行记录的方法 error级别最高

1、引入pom依赖

 
            log4j
            log4j
            1.2.17
 

2、创建日志配置文件

注意:文件名一定要是log4j.properties,不然无法找到

# 全局日志配置
log4j.rootLogger=DEBUG, stdout,D,E
# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG 
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log 
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR 
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

在mybatis.xml中添加配置信息(在没有其他框架整合时也可以不进行配置,会自动扫描jar包识别日志)


    

#和$的区别

mybatis中会将sql进行解析,解析分为两种:

  1. 静态sql 直接将参数进行拼接
  2. 动态sql 使用?占位符,替代参数

这两种解析方式是根据${}和#{}进行区分的

${}是静态sql,#{}是动态sql它们都能传递参数,静态sql它不能防止sql注入问题,而动态sql使用的是PreparedStatement进行参数预处理,可以防止sql注入。${}静态sql能做的#{}动态sql都能做。

那为什么还有${}静态sql呢?

因为PreparedStatement预处理的本质是将参数转换为字符串,当做参数字符串处理。所以如果参数是一个特殊的关键字。例如:数据库名,表名,函数名,内置关键字,使用预处理转为字符串就无效了,所以只能使用${}静态sql进行字符串拼接处理。

多个参数问题

当mybatis传递参数存在多个时,mybatis支持三种方案:

  1. argx 形式:arg 表示参数,x表示参数索引
  2. paramX形式: param表示参数,x表示第几个参数
  3. 使用注解为参数取别名 @Param

分页查询

三种方式:

  1. limit拼接sql方式
  2. mybatis中,提供了一个RowBounds这个类,用于进行分页查询数据,但这个类是在上述通过指令ID第一种方式执行sql时才能使用(已经不推荐使用这种方式执行sql),所以也不用这种方式分页查询
  3. 我们使用插件进行分页查询PageHelper

使用PageHelper分页

  1. 导入pom依赖

    com.github.pagehelper
    pagehelper
    5.2.0

  1. 在mybatis核心配置文件中开启PageHelper插件

    
    
    
    

  1. 在代码显示声明后面紧邻(一次)查询语句需要使用分页

    在PageHelper,每次开启分页,只对最近的一次查询有效。如果要多次分页查询,需要多次开启。

    PageHelper是利用,拦截器原理,拦截最近的一次查询,然后在其基础拼接分页信息。PageHelper只对最近一次查询有效,这种模式,对本身的程序没有侵入,所以,本身程序该怎么写还是怎么写。

       SqlSession session = SqlSessionUtil.getSession();
        //获取UserMapper接口的具体代理对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        //开启分页   2  页码  5  每页数据条数
        Page userPage = PageHelper.startPage(2, 5);
        //进行查询  PageHelper 会对查询进行拦截处理  将将要进行的查询语句进行分页处理
        mapper.selectAll();
        System.out.println("总页数:"+userPage.getPages());
        System.out.println("页码:"+userPage.getPageNum());
        System.out.println("每页条数:"+userPage.getPageSize());
        System.out.println("总条数:"+userPage.getTotal());
        List users = userPage.getResult();
        users.forEach((u) -> System.out.println(u));
        session.commit();
        session.close();
    

动态sql

  • if:进行条件判断
  • foreach:循环
  • choose:if else
  • set:取代set标签
  • trim:前后缀处理标签
  • where:当有多个sql条件时,需要处理and关键字问题
   
        
        
        
    
    
    

    

    
        
            insert into  student values
            
                (0,#{st.name},#{st.age},#{st.sex},#{st.address},#{st.birth})
            
        

    
    
    
        delete  from student where id 
        
            #{id}
        
    

    
        update student set name = #{name},age= #{age},sex=#{sex},address=#{address},birth = #{birth} where id = #{id}
    

    
        update student
        
            
                name = #{student.name} ,
            
            
                age= #{student.age} ,
            
            
                sex=#{student.sex} ,
            
            
                address=#{student.address} ,
            
            
                birth=#{student.birth} ,
            
        
        where id = #{student.id}
    
    

    
        update student
        
            
                name = #{student.name} ,
            
            
                age= #{student.age} ,
            
            
                sex=#{student.sex} ,
            
            
                address=#{student.address} ,
            
            
                birth=#{student.birth} ,
            
        
        where id = #{student.id}
    

多表查询

多对一

需求:查询每个学生所对应的老师

  1. 定义两个表:stu学生表和teacher老师表
CREATE TABLE `stu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(11) NOT NULL,
  `age` int(11) NOT NULL,
  `weight` int(11) NOT NULL,
  `address` varchar(20) NOT NULL,
  `tid` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

学生表中的tid关联的是老师表中的id,使用id和tid将每个学生对应的老师信息进行关联

  1. 定义实体类

    学生表(下面会进行扩展)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Stu {
    private Integer id;

    private String name;

    private Integer age;

    private Integer weight;

    private String address;

    private Integer tid;
}

老师表

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private Integer id;
    private String name;
}

  1. 定义接口和mapper

    因为我们要根据学生表中的tid数据对应查询老师表中的姓名,所以要对学生表进行扩展,添加tname则一个字段,直接修改类是不严谨的所以我们通常在创建一个学生类的扩展类StuExt类,继承Stu类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class StuExt extends Stu{
    private String tname;
}

定义接口查询学生类信息

List selectByAll();

编写mapperxml(重点)

  • 下面是针对字段是普通的类型,而不是对象类型

    
    
    
    
    
    
    
  
  
    
  
//首先使用我们sql的连表查询sql语句编写
  

在有些场景下我们需要的有可能不是一个表中的某一两个字段信息,需要大量的信息,以上这样写就很麻烦了,我们可以直接在Stu类中定义一个teacher对象。

  • 重新定义stu类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Stu {
        private Integer id;
    
        private String name;
    
        private Integer age;
    
        private Integer weight;
    
        private String address;
    
        private Integer tid;
        
        private Teacher teacher;//多了一个teacher对象
    }
    
    
  • 编写接口方法

    List selectByAll2();
    
    
  • 编写mapperxml

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

一对多

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

  1. 实体类修改

    //老师类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Teacher {
        private Integer id;
        private String name;
        private List stus;
    }
    //学生类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Stu {
        private Integer id;
    
        private String name;
    
        private Integer age;
    
        private Integer weight;
    
        private String address;
    
        private Integer tid;
    }
    
    
  2. 编写接口和mapperxml

    需求:查询一个或所有的老师所对应的学生信息,

    接口

     List listByTeacher(@Param("tid") Integer id);
     Student selectStudent(@Param("tid") Integer id);
    
    
  3. mapperxml

    • 使用结果嵌套处理
    
      
      
      
        
      
    
      
    
    
    • 查询嵌套处理
    
    
      
        
      
    
      
    
    
    

总结

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

缓存

mybatis有一级缓存和二级缓存

一级缓存

一级缓存是指sqlSession级别的缓存,在同一个SqlSession,同样的SQL语句只会执行一次,不是第一次执行的SQL会从缓存中获取数据。

二级缓存

二级缓存: 是指SqlSessionFactory级别的缓存,在同一个SqlSessionFactory中,同样的SQL语句,只会执行一次,不是第一次执行的SQL会从缓存中获取数据。

二级缓存需要在映射文件中,开启缓存并且被缓存的数据需要支持,序列化

注意

不论一级缓存还是二级缓存,否是JVM中缓存。当服务器有多台时,缓存容易发生无效,数据发生了更新。缓存没有更新。在实际使用,如果数据可能发生比较频繁的更新不建议使用mybatis。

作用域和生命周期

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中