MyBatis

第1章 框架概述

三层架构

  1. image-20210910213003041

  2. 三层对应的包:

    界面层:controller包(servlet)

    业务逻辑层:service包(XXXService类)

    数据访问层:dao包(XXXDao类)

    我认为MVC中的V在spring这三层架构下,V已经被分离出去,C代表的业务逻辑层,就是controller+service

  3. MVC三层架构,M代表数据,V代表视图,C代表控制,也就是业务逻辑处理。

    MVC中的C就代表了springboot中的controller层和service层。

    V就是界面层,M就是数据访问层,C就是业务逻辑层。

    用户--->界面层---->业务逻辑层---->数据访问层---->数据库

  4. 在springBoot中,业务逻辑层又分为了controller层和service层,视图层被拿了出去,实现了前后端的分离,

    controller层用于接受请求,调用service层处理请求,返回请求,以JSON或XML格式返回给前端页面进行处理。这涉及到REST

    service层 专心处理业务逻辑,并调用DAO层

    总结:

    • controller层----web层,有的也叫界面层,但实际和html、jsp不是一类,**这一层做的就是servlet做的工作,接收、处理请求,返回请求的结果。**在springboot中,不和界面挂钩,返回xml或json格式的结果给前端。------对应框架:springMVC
    • service层---业务逻辑层,专心处理业务逻辑------对应框架:spring
    • DAO层---持久层,和数据库打交道------对应框架:mybatis
  5. 根据第二点:

    controller层 对应 spring MVC : 接收请求,响应处理结果

    service层 对应 spring : 处理业务,返回处理结果给controller层

    DAO层 对应 mybatis

框架定义

  1. 框架中定义好了一些功能、组件,这些功能、组件是可用的,

    自己写的项目中的功能,可以利用框架中已有的功能

    框架是一个半成品的软件,定义好了一些基础功能,需要加入自己的功能才是完整的。

    这些基础功能是可重复使用的,可升级的。

    框架是一个提供好了基础功能的平台

  2. 框架是整个或部分系统的可重用设计

    是安全的、可复用的、不断升级的软件。是写好一部分功能、组件的软件,这些功能、组件供我们开发出完整的产品。

  3. 框架特点:

    • 框架一般不是全能的,不能做所有事情
    • 框架是针对某一个领域有效,特长在某一个方面,比如mybatis,做数据库操作强。

使用JDBC的缺陷

  1. 代码比较多,开发效率低
  2. 需要关注Connection、Statement、ResultSet对象创建和销毁
  3. 对ResultSet查询的结果,需要自己封装为List
  4. 重复的代码比较多些。
  5. 业务代码和数据库的操作混杂在一起。

Mybatis框架概述

  1. Mybatis是一个框架,用于DAO层,提供了访问数据库的基本功能,改进了JDBC的缺点

  2. 解决的主要问题:

    减轻JDBC的复杂性,不用重复的创建connection,关闭connection(即使我们已经把创建connection和关闭connection封装到了专门的工具类里),不用重复编写statement,不用考虑资源代码的关闭

    目的:让开发者专心SQL的处理,其他这些关于数据库的操作,由Mybatis代劳

    在JDBC中,虽然我们创建了工具类,但是还是要不断调用创建connection的方法,在BaseDAO抽象类提供的方法里,虽然connection是传入的,但是每次都要关闭资源

  3. Mybatis 是SQL Mapper Framework for Java 即 sql映射框架

    • SQL mapper : SQL映射:

      是说我们可以把数据库中表中的一行数据映射为一个Java类的实例化对象(一行数据是具体的,所以是实例化对象)

      数据库中的一个表对应于一个Java类

      这是ORM思想,对象关系映射思想。

    • Data Access Objects:

      数据访问,即对数据库执行增删改查操作。

  4. mybatis提供了哪些功能

    • 提供了创建connection、statement、ResultSet的能力,不用开发人员创建了
    • 提供了执行SQL语句的能力(之前JDBC通过QueryRunner执行),不用开发人员执行sql
    • 提供了循环SQL,把SQL的结果转为Java对象,List集合的能力
    • 提供了关闭资源的能力,不用开发人员关闭Connection、Statement等。

    开发人员做的是:提供SQL语句

    开发人员提供sql语句---mybatis处理sql----开发人员得到List集合或Java对象(表中的数据映射到Java层面)

    也就是对数据库的操作,交给Mybatis执行,开发人员只提供SQL,专注于业务的处理.和数据库打交道的一些流水线工作,交给mybatis处理

  5. mybatis是一个SQL映射框架,提供的数据库的操作能力,是增强的JDBC

第2章 MyBatis快速入门

MyBatis入门例子

实现步骤

  1. 新建数据库

  2. 新建表

  3. 创建maven项目

  4. 添加mybatis依赖,添加MYSQL驱动connector依赖

  5. 创建实体类(Java Bean),对应于mysql中一个表,表中一行数据对应于Java层面的一个类的对象,这是ORM,对象关系映射模型

  6. 创建持久层的DAO接口,定义操作数据库的方法,此方法名要作为SQL语句标签的ID,标识所要执行的SQL语句。而SQL语句的ID和接口里的方法名是相同的

    nameSpace + “.”+此ID = sqlId

    nameSpace为接口的全限定名。

  7. 创建sql映射文件(也叫mapper文件)

    sql映射文件:写sql语句的,一般一个表(对应一个类)对应一个sql映射文件

    这个文件是xml文件,写在接口所在的目录中,这个文件的名称和接口保持一致

  8. 创建mybatis主配置文件:

    一个项目就一个主配置文件

    主配置文件提供了

    • 数据库的连接信息(环境信息,即主配置文件中的environment),除了连接信息,还有事务信息,是否使用连接池等。

    • SQL映射文件的位置信息

      可以写多个SQL映射文件

  9. 创建使用mybatis类

    通过mybatis访问数据库

    使用mybatis的对象SqlSession,通过它的方法执行sql语句

sql映射文件

<?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">
<mapper namespace="com.example.dao.StudentDao">
    <!--
        select:表示查询操作
        id:你要执行的sql语句的唯一标识,mybatis会使用这个id的值来找到要执行的sql语句,这个id和数据库中表的属性那个id不一样!!!
        可以自定义,但是要求使用接口中的方法名称
        resultType: 表示结果类型的,是sql语句执行后得到的ResultSet,遍历这个ResultSet得到的Java对象的类型,就不需要像JDBC一样,我们得到ResultSet,然后自己做赋值操作,将ResultSet里的值赋值给对象。
        值写的是类型的全限定名称
    -->
    <select id="selectStudent" resultType="com.example.domain.Student">
        select id, name, email, age from student order by id;
    </select>
</mapper>
<!--
    sql映射文件:写sql语句的,mybatis会执行这些sql
    1. 指定约束文件
    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    mybatis-3-mapper.dtd是约束文件的名称,扩展名是dtd的
    2. 约束文件作用:限制,检查在当前文件中出现的标签,属性必须符合mybatis的要求

    3. mapper是当前文件的根标签,必须的。
       namespace:叫做命名空间,唯一值的,可以是自定义的字符串
                  虽然是自定义字符串,但是我们要求使用dao接口的全限定名称
    4. 在当前文件中,可以使用特定的标签,表示数据库的特定操作
       <select> 表示执行查询
       <update>  表示更新数据库的操作,就是在<update>标签中,写的update sql语句
       <delete>  表示删除  写的是delete语句
       <insert>  表示插入,写的是insert语句
 -->

mybatis主配置文件

mybatis 的主配置文件:主要定义了数据库的配置信息,sql映射文件的位置
    1. 约束文件
    <!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
    mybatis-3-config.dtd 是约束文件的名称,固定值
    2. configuration:各种配置信息
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 环境配置,是数据库的连接信息 -->
    <!-- 可以配置多个环境-->
    <!-- 这个标签是environments,内部配置了多个环境,
	default属性的值必须和其中一个环境的id是一样的,告诉mybatis使用哪个数据库的连接信息-->
    <environments default="mydev">
        <!--
            environment:一个数据库信息的配置,环境
            id: 一个唯一值,自定义,表示环境的名称,给这段配置起个名字
        -->
        <environment id="mydev">
            <!--
                transactionManager:mybatis的事务类型
                type:
					1.JDBC(表示使用jdbc中的Connection对象的commit、rollback做事务处理)
					2.MANAGED:把mybatis的事务处理委托给其他的容器(一个服务器软件,一个框架(spring))
             -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:表示数据源,连接数据库的,数据源表示connection对象
							Java体系中,规定实现了javax.sql.DataSource接口的都是数据源
                type:表示数据源的类型,
					1. POOLED表示使用连接池
					2. UNPOOLED 不使用连接池,在每次执行sql语句时,先创建连接,执行sql,再关闭连接
						mybatis会创建一个UnpooledDataSource对象,来管理connection对象的使用
					3. JDNI:JAVA命名和目录服务(windows注册表)
             -->
            <dataSource type="POOLED">
                <!-- property是标签,name是属性,name属性的值不能改!!!是固定的。-->
                <!-- 数据库的驱动类名-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- sql mapper文件的位置 -->
    <mappers>
        <!--
			一个mapper标签指定一个sql mapper文件的位置
			从类路径开始的路径信息。target/classes(类路径)
		-->
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>

<!--
    mybatis 的主配置文件:主要定义了数据库的配置信息,sql映射文件的位置
    1. 约束文件
    <!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
    mybatis-3-config.dtd 是约束文件的名称,固定值
    2. configuration:各种配置信息

 -->
<!-- <configuration>下的<settings>是控制mybatis的全局行为 -->
<settings>
		<!-- 设置mybatis输出日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

主配置文件做两个事情:

  1. 配置好环境信息,包括连接信息、是否使用连接池、默认环境名、事务类型。

  2. 指定sql映射文件的位置,即sql mapper文件。注意这个文件要从类路径写全名。

    mapper标签可以出现多次,即可以配置多个SQL mapper文件。

    可以通过编译,看到sql mapper文件在类路径下的地方,

    如果看不到,在pom文件中添加以下内容在build标签里,意思是如果不添加,那么包下的xml或properties等配置文件,在编译的过程中会自动忽略掉,但是加上以下配置之后,意思就是目录下的这些配置文件在编译时仍然会被扫描到。

      <build>
        <resources>
          <resource>
            <directory>src/main/java</directory>
            <includes><!-- 包括目录下的.properties,.xml文件都会扫描到 -->
              <include>**/*.properties</include>
              <include>**/*.xml</include>
            </includes>
          </resource>
        </resources>
      </build>
    

通过mybatis访问数据库

 // 访问mybatis读取数据库中数据
        //1. 定义mybatis主配置文件的名称
        String config = "mybatis.xml";
        // 2. 读取这个config表示的文件
        InputStream inputStream = Resources.getResourceAsStream(config);
        //3. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //4. 创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        //5. 获取SqlSession对象,从SqlSessionFactory中获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //6. 指定要执行的SQL语句的标识即sql语句id。sql映射文件中的namespace + "." + 标签的id值
        String sqlId = "com.example.dao.StudentDao" + "." + "selectStudent";
        //7. 执行sql语句,通过sqlId找到语句
        List<Student> studentList = sqlSession.selectList(sqlId);
        //8. 输出结果
        studentList.forEach(System.out::println);
        //9. 关闭SqlSession
        sqlSession.close();

注意:mybatis不是默认提交事务的(不过还是要看创建SqlSession所调用的构造方法),如果执行的是插入或者更新、删除操作而不是查询(DQL)操作,那么就要在insert、update、delete后手动提交事务。事务的处理方式,是在主配置文件中的环境配置中声明的,采用的是JDBC的事务处理方式,即commit 和 rollback

如果不想手动提交事务,那么通过SqlSessionFactory的实例化对象获取SqlSession的时候,调用openSession方法,传入参数true,即为创建具有自动提交的SqlSession

Mybatis用到的类和对象的介绍

创建SqlSession所需要的类

  1. Resources类,工具类,负责读主配置文件

  2. SqlSessionFactoryBuilder 调用无参构造方法得到sqlSessionFactoryBuilder对象

    目的是通过sqlSessionFactoryBuilder对象创建SqlSessionFactory对象

    //4. 创建SqlSessionFactory对象
    // 左边是接口,右边是接口的实现类的实例化对象。
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    
  3. SqlSessionFactory重量级对象,就是程序创建这个对象,耗时比较长,使用资源比较多,在整个项目中有一个就够用了,整个项目只需创建一个这个对象

    作用:获取SqlSession对象

    SqlSession sqlSession = sqlSessionFactory.openSession();
    

    方法说明:

    openSession():无参数的,获取的是非自动提交事务的SqlSession对象

    openSession(boolean):

    • openSession(true):获取自动提交事务的SqlSession
    • openSession(false):获取非自动提交事务的SqlSession
  4. SqlSession这是个接口(我们创建的是接口的实现类的实例化对象,也就是左边是接口,右边是实例化对象),执行方法,执行sql语句,类似于JDBC里的queryRunner

    总之执行的是操作数据库用到的各种方法

    selectOne() selectList() insert() update() delete() commit() rollback()

    SqlSession的实现类---DefaultSqlSession

    使用要求:SqlSession的实例化对象不是线程安全的,需要在方法内部使用,在执行SQL语句之前,要使用openSession()获取SqlSession对象,在执行完SQL语句后,需要关闭它,执行SqlSession.close(),这样能保证在只用SqlSession过程中是线程安全的。

MyBatis工具类写法

package org.example.utils;

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 {
        String config = "mybatis.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(config);
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 获取SqlSession的方法,定义成静态的
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = null;
        if (sqlSessionFactory != null) {
            sqlSession = sqlSessionFactory.openSession();
        }
        return sqlSession;
    }
}

第3章 MyBatis框架的Dao代理

DAO代理实现数据库操作

实现原理

  1.   public static void main(String[] args) {
            StudentDao studentDao = new StudentDaoImpl();
            List<Student> students = studentDao.selectStudents();
            students.forEach(System.out::println);
    //        Student student = new Student(1111, "富富", "fufu@qq.com", 18);
    //        int count = studentDao.insertStudent(student);
    //        System.out.println(count);
        }
    
    • 通过StudentDao接口,就可以拿到 StudentDao的全限定名称

    • 通过studentDao实例化对象调用(或者说接口定义)的方法名就可以拿到SQL映射文件中Sql语句的语句id

      通过拿到的全限定名和id就可以拿到SqlSession执行方法所需要的sqlId

    • SqlSession的对象应该执行什么方法怎么拿到呢?

      通过StudentDao接口中的方法的返回值可以确定Mybatis要调用的SqlSession的方法

      • 如果返回值是List,调用的是SqlSession.selectList()方法
      • 如果返回值是int,或是非List的,看mapper文件中的标签是<insert>还是<update>来判断调用的是SqlSession的insert还是update方法

    在Dao接口的实现类Impl里,写的方法,内部有很多是相同的,唯一不同的两点就是sqlId,和SqlSession所调用的方法,而这两点可以分别通过上面的两点拿到,所以Mybatis可以通过动态代理来实现这个过程

    SqlId可以通过接口的全限定名成和接口实例化对象调用方法名组合起来拿到,也就是可以通过前面两点拿到

    SqlSession调用方法可以通过mapper文件里的标签和接口实例化对象调用方法的返回值拿到。

实现步骤

  1. mybatis的动态代理

    mybatis根据StudentDao接口的方法调用,获取执行sql语句的信息,比如sqlId

    mybatis根据dao接口,创建出一个dao接口的实现类,并创建这个类的对象

    完成SqlSession调用方法,访问数据库

    这个过程叫动态代理

    也就是我们的程序中不需要创建Dao接口的实现类Impl了!!!!

    mybatis帮我们完成这个过程!!帮我们创建Dao接口的实现类,并调用SqlSession访问数据库的方法

    SqlSession的getMapper方法来自动获取DAO接口的实现类的实例化对象!!!!!!

    @Test
        public void selectStudents() {
            /**
             * 使用mybatis的动态代理机制,使用SqlSession.getMapper(dao 接口)
             * getMapper能获取dao接口对应的实现类对象
             */
            SqlSession sqlSession = MyBatisUtils.getSqlSession();
            // 这行代码获得的studentDao实际是jdk的动态代理:com.sun.proxy.$Proxy2
            StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
            List<Student> students = studentDao.selectStudents();
            students.stream().forEach(System.out::println);
        }
    
  2. 现在只需要写主配置文件,SQL映射文件mapper和dao接口!!

    动态代理机制,是项目中主要开发方式

  3. 使用动态代理步骤

    • 获取SqlSession对象,SqlSessionFactory.openSession()
    • 使用getMapper()方法获取某个接口的对象,sqlSession.getMapper(接口.class)
    • 使用dao接口的方法,调用方法就执行了mapper文件中的sql语句
  4. 使用动态代理方式的要求:

    • mapper文件中的namespace的值是dao接口的全限定名称
    • mapper文件中的<select><insert><update><delete>等sql语句的语句ID是接口中方法名称
    • dao接口和mapper文件放在同一个目录
    • dao接口的名称和mapper文件名称相同。

深入理解参数

parameterType

  1. mybatis的接口的方法的形参是怎么通过Java代码对应到sql映射文件的sql语句的参数的?

    传入参数:从Java代码中把数据传入到mapper文件中的sql语句中

    parameterType:写在mapper映射文件中的一个属性,表示dao接口中方法的形参的数据类型

    例如StudentDao接口中有一个方法

    public Student selectStudentById(Integer id)

    可知,接口中定义的方法的形参类型是Integer

    那么parameterType就应该写在mapper文件中,值是Java数据类型的全限定名称或者mybatis定义的别名,表示dao接口中的这个形参的数据类型Integer

    parameterType不是强制的,mybatis通过反射机制能够发现接口参数的数据类型

    所以可以没有,一般我们也不写

sql mapper 映射文件,一个简单类型的参数传值方式

  1. 简单类型:mybatis把Java的基本数据类型和String都叫做简单类型

    在mapper文件获取简单类型的一个参数的值:使用**#{任意字符}**

  2. 一个简单类型的参数,sql mapper文件中sql语句使用#{}这种方式,对应于JDBC中什么形式呢?

    使用#{}之后,mybatis执行sql是使用的jdbc中的PreparedStatement对象

    由mybatis执行下面的代码

    //1. mybatis创建connection,PreparedStatement对象
    String sql = "select id, name, email, age from student where id = ?";
    PreparedStatement pst = conn.preparedStatement(sql);
    pst.setInt(1, 1001);
    ...
    // 或者相当于这样写,用PreparedStatement对象来执行
    			//1.获取数据库的连接
    			conn = JDBCUtils.getConnection();
    			
    			//2.获取PreparedStatement的实例 (或:预编译sql语句)
    			ps = conn.prepareStatement(sql);
    			//3.填充占位符
    			for(int i = 0;i < args.length;i++){
    				ps.setObject(i + 1, args[i]);
    			}
    			
    			//4.执行sql语句
    			ps.execute();
    			// 或者 ResultSet rs = ps.executeQuery();
    			Student student = null;
    			while (rs.next()) {
                    student = new Student();
              		student.setId(rs.getInt("id"));
                    student.setName(rs.getString("name"));
                    student.setEmail(rs.getString("email"));
                    student.setAge(rs.getInt("age"));
                }
    			return student;
    			// 这个student,就是对应于下面这一句mybatis的代码
    			// 这个studentDao,是mybatis根据动态代理自动创建出的dao接口的实现类的实例化对象
    			// selectStudentById这个方法的执行过程,mybatis帮我们完成了,实际执行的就是上述代码的过程,直到返回student对象!!!
    			// 相当于上面这些操作全都在mybatis内部来完成
    			// Student student = studentDao.selectStudentById(1002);
    

    要注意,上面的while循环体内,是通过拿到的结果ResultSet的对象,来设置Student的属性值,一个表对应一个类,一个表的一行对应一个类的一个实例对象,那么拿到一行结果,就要实例化Student类,然后给这个实例化对象赋值,但是在实际开发中,有很多表,我们怎么确定查的是哪个表,对应于Java中的哪个类呢?while循环体内就是实例化对象,然后赋值的操作,并且实际开发中有太多的JavaBean类,根本无法确定在while循环体内实例化哪个类的对象,这个时候就是通过反射来创建对象并且赋值,反射可以通过构造函数创建对象,也可以通过newInstance()创建对象,也可以拿到属性值,也可以拿到方法名,甚至是私有的属性值都可以通过设置setAccessible来访问到,也就是说私有的属性并不是完全不能被外界访问到的,只是在形式上告诉你不要来访问,实际上反射可以访问到一个类的内部并且能访问到私有属性。反射是动态的,通过反射,这一份代码我们就只有写一次,查到什么,就给对应的类实例化并且赋值。要查什么,传入类.class这个大Class对象,就可以通过反射来操作了。while循环体内的代码,就高度抽象化,降低耦合度。

    反射是动态的,Java本身是静态语言,和JavaScript、python不一样,Java是静态语言,很多对象在编译期间就创建好了,要是编译期间不能确定创建哪个对象怎么办?通过反射!!!

  3. 介绍PreparedStatement

    • 预编译,提高性能

      PreparedStatement接口是Statement接口的子接口,它表示一条预编译过的sql语句,预编译语句有可能重复调用,被DBServer编译过后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句那么就不需要编译,只要将参数直接传入已经编译的预编译sql语句就可以执行。

      statement没有预编译,因为没有预编译的意义,在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。

      PreparedStatement能够预编译,有预编译的意义正是因为它是通过占位符的方式来设置参数

    • 解决sql注入问题

      PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,这是占位符,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数即占位符. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

      正是因为是通过占位符的方式设置参数,所以可以解决sql注入问题

sql mapper 映射文件,多个参数传值方式

  1. 多个参数,使用@Param命名参数

    接口public List<Student> selectMultiParam(@Param("MyName") String name, @Param("MyAge") Integer age)

    使用@Param("参数名") String name 这种方式

    mapper文件中:

    <select>
        select * from student where name = #{MyName} or age = #{MyAge};
    </select>
    
  2. 多个参数,使用对象

    接口中的方法的形参是一个Java对象,一般来说,一个Java对象就没问题

    这个对象里面有多个属性,这些属性值就是参数值!!!

    用对象来传递SQL语句的占位参数

    <!--
            多个参数,使用Java对象的属性值,作为实际sql语句参数值
            使用对象语法,#{属性名,javaType=类型名称,jdbcType=数据库中数据类型} 很少用
            javaType 指在Java代码中的属性数据类型
            jdbcType 指在数据库中的数据类型
            例如 #{paramName,javaType=java.lang.String,jdbcType=VARCHAR}
    
            简化方式:
            #{属性名} javaType,jdbcType的值, mybatis反射可以获取,不用提供
        -->
        <select id="selectMultiObject" resultType="org.example.domain.Student">
            select id, name, email, age from student where name = #{paramName} or age = #{paramAge};
        </select>
    
  3. 多个参数,按位置传值

    image-20210912204827485

    image-20210912204838670

  4. 多个参数,通过Map传值

    image-20210912204940961

    image-20210912204900434

  5. 总结:

    • 一个简单类型的参数:#{任意字符}
    • 多个简单类型的参数:使用@Param("自定义名称")
    • 使用一个java对象,对象的属性值作为mapper文件的参数,#{Java对象的属性名称}
    • 使用参数的位置,语法#{arg0},#{arg1},mybatis3.4之前的版本使用#{0},#{1}
    • 使用Map作为参数,#{map的key}

## 和 $

  1. #是占位符

    image-20210912205159195

    因为PreparedStatement接口就是采用的占位符的方式,然后setXxx的方式来设置占位符的参数,而不是像Statement接口采用的拼接的方式,所以避免了sql注入的问题

    在前面说过,mybatis执行原理,能够通过动态代理自动生成dao接口的实现类的实例化对象,并调用SqlSession的方法来执行数据库操作,然后返回结果,正式通过PreparedStatement的方式,所以也是采用的占位符,避免了sql注入的问题

    #放在等号的后面,代表的是列值,在Java层面就是属性值。

    能用#号的地方,都可以替换成$

  2. $用于字符串连接和替换

    image-20210912210543125

    可以看见使用$是使用的statement对象来进行sql语句的执行,可能产生sql注入的问题,也没有PreparedStatement接口的预编译功能。DBServer能对sql语句预编译,后面重复调用的时候,就不用再次编译,直接根据占位符传入参数就可以执行了。

    因为这种方式,相当于不是占位符了,而是字符串替换,或者说拼接。

    PreparedStatement能避免sql注入,正是因为采用了占位符的方式。

    statement无法预编译,因为它是采用的字符串拼接的方式来构成sql,相当于高度耦合,每一条插入语句即使只有参数不同,他们也是不同的sql语句,预编译没有意义。

    总结:

    statement对比PreparedStatement:

    • 无法预编译
    • 会产生sql注入的问题

    #是占位符,而$不是

    使用$,要保证传入的参数符合sql语法,更麻烦

  3. 使用$一般替换表名或列名,如果能确定数据是安全的,可以用$

  4. #$的区别

    • #,是使用?在sql语句中做占位的,使用的是PreparedStatement执行sql,效率高(预编译)
    • #能够避免sql注入,更安全
    • $不使用占位符,是字符串连接方式,使用的是Statement执行sql,效率低
    • $有sql注入的风险,缺乏安全性
    • $可用于替换列名和表名

Mybatis的返回结果

resultType

  1. mybatis执行了sql语句,得到Java对象

  2. resultType结果类型,指sql语句执行完毕后,数据转为的Java对象

    只要是查询语句,也就是标签是<select>的,都有resultType属性

    处理方式:

    1. mybatis执行sql语句,然后mybatis调用类的无参构造方法,创建对象

    2. mybatis把ResultSet指定列值付给同名的属性

      对应的jdbc的操作

    image-20210912214202756

    image-20210912214209011

    同名的列名的值赋给Java代码中同名的属性,那么不同名的列的值,便不会赋值

  3. resultType里面的值是一个任意的Java类型,而不一定是一个实体类

    也可以是Integer的全限定名,或者是Integer的别名,比如说查询表中有多少数据,查询行数,需要返回整型值,那么resultType可以这样写。

    java.lang.Integer的别名是int

  4. resultType结果类型,它的值可以是两种

    • 类型的全限定名称
    • 类型的别名---如果写别名,一定要配置,除非是基本数据类型,mybatis有自动配置别名。

    自定义类也可以用别名,需要配置。

  5. 定义自定义类型的别名,需要在主配置文件中去定义,使用<typeAlias>定义别名

    定义完后,可以在resultType后,使用自定义类的别名

    定义别名:

    <typeAliases>
            <!--
                第一种方式
                type是自定义类型的全限定名称,alias是别名,短小并容易记忆的
    			可以指定一个类型一个自定义别名
            -->
    <!--        <typeAlias type="org.example.domain.Student" alias="stu" />-->
            <!--
                第二种方式
                <package> name属性的值是全限定包名,那么这个包中的所有类,类名就是别名,不区分大小写
            -->
            <package name="org.example.domain"/>
    </typeAliases>
    

    用第二种方式居多,不用自己定义别名,用类名作为别名,而不使用全限定名

    并且第二种方式通过包来定义,一行代码就可以定义多个类的别名,第一种方式一行代码只能定义一个类的别名

    定义了别名,也可以使用全限定名,为了不出错,全部使用全限定名也可以

    而且如果使用别名,可能出现歧义性,因为在不同包下面,可以有同样名称的类名

  6. 还可以使用Map来接收resultType

    返回Map,数据库的表的列名是map的key列值是map的value

    使用Map来接收结果只能返回一行记录,返回多行记录会报错,因为Map里记录的是一个对象的属性名和属性值,属性名作为key,属性值作为value,也就是列名和列值,但是只是一行的。一行数据对应一个Java Bean对象,对应一个map对象,key就是对象的属性,value就是对象的属性值。

    image-20211231170347832

    接口里的方法的返回值也要是Map

resultMap

  1. 这个resultMap和上面的resultType里写HashMap不一样

    上面的resultType里写HashMap,是为了用Map来接收返回结果,Map作为返回结果的接收对象!!

    这里的resultMap,起一个映射的关系,将数据库中表的列名映射到Java对象的属性名!!

    而返回结果,仍然是用自定义Java对象接收,这个Map不是作为接收返回结果的对象,是起映射作用,返回结果仍是自定义Java对象。

    结果映射,指定列名和Java对象的属性对应关系

    • 自定义列值赋值给哪个属性
    • 当列名和属性名不一样时,一种方式是使用resultMap,另一种方式是使用resultType结合列的别名as来写sql语句
  2. 使用resultMap

    • 先定义resultMap
    • 在select标签,使用resultMap来引用1定义的
  3. 定义resultMap

    • id:自定义名称,表示定义的这个resultMap
    • type:Java类型的全限定名称

    image-20210913111244558

  4. resultType的原则是同名列名的列值赋给Java对象中同名的属性。

    如果列名和属性名不同,对应不上,那么就要采用resultMap映射或者使用别名!!

    使用Mybatis的时候,实体类的属性名原则上要和数据库中表的列名一样!!!

  5. resultType和resultMap不要一起用,二选一。

    resultMap和resultType的值写HashMap是不同的,前者是映射,返回对象仍是Java自定义对象。后者返回对象则是HashMap

Mybatis模糊查询的两种方式

  1. 把模糊查询的内容写在Java中,通过占位符的方式传入

    <!-- 第一种like,Java代码指定like的内容 -->
        <select id="selectLikeOne" resultType="org.example.domain.Student">
            select id, name, email, age from student where name like #{name};
        </select>
    
  2. <!-- 第二种like,在mapper文件中拼接like的内容 -->
        <select id="selectLikeTwo" resultType="org.example.domain.Student">
            select id, name, email, age from student where name like "%" #{name} "%";
        </select>
    

第4章 动态SQL

概述

  1. 动态sql:sql的内容是变化的,可以根据条件获取到不同的sql语句

    主要是where部分发生变化

  2. 动态sql的实现,使用的是mybatis提供的标签

    • <if>

      是判断条件的

      语法:

      <if test="判断Java对象的属性值">
          部分sql语句
      </if>
      
    • <where>

    • <foreach>

介绍三种标签

  1. sql-if

    <!-- 
    	<if:test="Java对象的属性 运算符 xxx值">
    -->
    

    image-20210913113956465

  2. sql-where

    <where> 标签用来包含多个<if >的,当多个if有一个成立的,<where>会自动增加一个where关键字,并去掉if中多余的and、or等。

    image-20210913114945145

  3. sql-foreach

    主要用在sql的in语句中,比如学生id是1001,1002,1003的三个学生

    sql语句如下

    select * from student where id in (1001,1002,1003)

    sql映射文件sql语句如下:

    方式一

    <select id="selectForeachOne" resultType="org.example.domain.Student">
            select id, name, email, age from student where id in
            <foreach collection="list" item="myid" open="(" close=")" separator=",">
                #{myid}
            </foreach>
    </select>
    
    • collection: 用来表示接口中方法参数的类型,

      • 如果是数组,使用array
      • 如果是list集合,使用list
    • item:自定义的,表示数组和集合成员的变量

      item的属性值是list或数组里的元素,给其起个名字!!

    • open:循环开始时的字符

    • close:循环结束时的字符

    • separator:集合成员之间的分隔符

    image-20210913142035391

    方式二:

    <select id="selectForeachTwo" resultType="org.example.domain.Student">
            select id, name, email, age from student where id in
            <foreach collection="list" item="student" open="(" close=")" separator=",">
                #{student.id}
            </foreach>
    </select>
    

代码片段

  1. <sql/>标签用于定义sql语句片段,以便其他sql标签复用,而其他标签使用该sql片段,需要使用<include/>子标签,该<sql/>标签可以定义sql语句中的任何部分,所以<include/>子标签可以放在动态sql的任何位置

    image-20210913145213669

    image-20210913145239671

第5章 MyBatis配置文件

主配置文件

  1. <settings>

  2. <typeAliases>

    定义自定义类型的别名,需要在主配置文件中去定义,使用<typeAlias>定义别名

    定义完后,可以在resultType后,使用自定义类的别名

    定义别名:

    <typeAliases>
            <!--
                第一种方式
                type是自定义类型的全限定名称,alias是别名,短小并容易记忆的
    			可以指定一个类型一个自定义别名
            -->
    <!--        <typeAlias type="org.example.domain.Student" alias="stu" />-->
            <!--
                第二种方式
                <package> name属性的值是全限定包名,那么这个包中的所有类,类名就是别名,不区分大小写
            -->
            <package name="org.example.domain"/>
    </typeAliases>
    

    用第二种方式居多,不用自己定义别名,用类名作为别名,而不使用全限定名

    并且第二种方式通过包来定义,一行代码就可以定义多个类的别名,第一种方式一行代码只能定义一个类的别名

    定义了别名,也可以使用全限定名,为了不出错,全部使用全限定名也可以

    而且如果使用别名,可能出现歧义性,因为在不同包下面,可以有同样名称的类名

  3. <environments>环境信息

    • 数据库连接信息

    • 数据源:dataSource(在实际项目中,必然使用连接池)

      <dataSource type="POOLED">
          <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=Asia/Shanghai"/>
          <property name="username" value="root"/>
          <property name="password" value="123456"/>
      </dataSource>
      

      dataSource:表示数据源,Java体系中,规定实现了javax.sql.DataSource接口的都是数据源

      数据源表示Connection对象的。

      type:指定数据源的类型

      • POOLED:使用连接池,mybatis会创建PooledDataSource类的对象

      • UNPOOLED:不使用连接池,在每一次执行SQL语句的时候,先创建Connection,执行SQL,再关闭Connection

        这种情况Mybatis会创建UnpooledDataSource类的对象,管理Connection对象的使用(创建、关闭)

      • JNDI:Java命名和目录服务(windows注册表)

    • 提交事务、回滚事务的方式:transactionManager

      <transactionManager type="JDBC"/>
      
      • JDBC,表示mybatis底层是调用JDBC中的Connection对象的,commit、rollback

      • MANAGED,表示mybatis的事务处理委托给其他的容器(一个服务器软件或者是一个框架(spring))

  4. <mappers>

    sql mapper(sql映射文件)的位置

    • 第一种方式:通过<mapper>标签,指定多个mapper文件位置

    • 第二种方式:通过xml文件(mapper文件)所在的包名,这个包中所有xml文件一次都能加载给mybatis

      image-20210913152221165

      使用package的要求:

      1. mapper文件名称需要和接口名称一样,区分大小写
      2. mapper文件和dao接口需要在同一目录

    这两种方式,指定多个mapper文件,和typeAliases一样的,都是可以写多个,每一对标签指定单独的,也可以通过写包名,来批量指定。

数据库属性配置文件

  1. 数据库的属性配置文件:

    把数据库连接信息放到一个单独的文件中,和mybatis主配置文件分开

    目的是便于修改和保存,处理多个数据库的信息

  2. 使用步骤:

    1. 在resources目录中,定义一个属性配置文件,xxx.properties,例如jdbc.properties

      在属性配置文件中,定义数据,格式是key=value

      key一般使用.做多级目录。

      例如 jdbc.mysql.driver

      jdbc.driver=com.mysq.jdbc.Driver
      jdbc.url=jdbc:mysql//....
      jdbc.username=root
      jdbc.password=123456
      
    2. 在mybatis的主配置文件,使用<property>标签指定文件的位置,在需要使用值的地方,${key}

      <properties resource="jdbc.properties"/>    
      <environments default="mydev">
         <environment id="mydev">
             <transactionManager type="JDBC"/>
             <dataSource type="POOLED">
                 <property name="driver" value="${jdbc.driver}"/>
                 <property name="url" value="${jdbc.url}"/>
                 <property name="username" value="${jdbc.username}"/>
                 <property name="password" value="${jdbc.password}"/>
             </dataSource>
      </environment>
      

补充

使用PageHelper在mybatis来自动帮助分页

  1. 添加maven依赖

    image-20210913153350204

  2. 在主配置文件中文件中加入plugin配置

    image-20210913153406267

  3. 调用PageHelper对象的startPage方法,这是静态方法。

    image-20210913153140997

Last Updated:
Contributors: 陈杨