Java_持久层_MyBatis

不定期补充、修正、更新;欢迎大家讨论和指正

JDBC

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:

  • JDBC API:提供应用程序到JDBC管理器连接。
  • JDBC驱动程序API:支持JDBC管理器到驱动程序连接。

如以下架构图,它显示了驱动程序管理器相对于JDBC驱动程序和Java应用程序的位置
sun公司提出JDBC接口规范,各个厂商对自己的产品实现相关符合规范的驱动,
只要符合JDBC接口规范,我们都可以通过JDBC统一的API操控不同的数据库产品。
在这里插入图片描述

这里只是简单回顾JDBC,如果没学过可以参考以下视频
尚硅谷JDBC核心技术视频教程

使用JDBC来访问数据库大致可以分为以下步骤

  1. 配置账号、密码、Driver、url成功连接数据库
  2. 通过链接Connection获取PreparedStatement类
  3. 编写SQL语句,将SQL语句放入PreparedStatement中预编译
  4. 填充占位符
  5. 执行SQL语句,如果是查询SQL,还需要用ResultSet类来接收结果集
  6. 关闭资源等收尾工作

配置连接数据库的信息,获取连接和关闭资源都是重复的代码,所以这里写一个工具类来进行封装

public class JdbcUtils {

    private static Connection conn;
    static {
        try {
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc_conn.properties");//读取JDBC配置文件,自己定义
            Properties p = new Properties();
            p.load(is);

			
            String url = p.getProperty("url");//数据库URL
            String user = p.getProperty("user");//用户名
            String password = p.getProperty("password");//密码
            String driverClass =p.getProperty("driverClass");//数据库驱动

            Class.forName(driverClass);
            conn = DriverManager.getConnection(url, user, password);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throwables.printStackTrace();
        }
    }

    public static Connection getConnection() {//获取连接
        return conn;
    }
    public static void closeResource(Connection conn, PreparedStatement ps){//关闭资源
        try {
            if(ps !=null)
                ps.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if(conn !=null)
                conn.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
    public static void closeResource(Connection conn, PreparedStatement ps, ResultSet rs){//关闭资源
        try {
            if(ps !=null)
                ps.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if(conn !=null)
                conn.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if(rs !=null)
                rs.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

接下来是SQL相关的代码,这里以下表为例
在这里插入图片描述

插入

@Override
    public void insert(Jobs jobs) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = JdbcUtils.getConnection();
                        								
            String sql = "INSERT INTO `jobs`(`job_id`,`job_title`,`min_salary`,`max_salary`)VALUE(?,?,?,?);";
			
            ps = conn.prepareStatement(sql);//预编译

            ps.setString(1,jobs.getJob_id());//填充占位符
            ps.setString(2,jobs.getJob_title());
            ps.setInt(3,jobs.getMin_salary());
            ps.setInt(4,jobs.getMax_salary());

            ps.execute();//执行

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closeResource(conn,ps);//关闭连接
        }

    }

删除

@Override
    public void deleteByID(String ID) {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "DELETE FROM `jobs` WHERE `job_id`=?;";
            ps = conn.prepareStatement(sql);
            ps.setString(1,ID);

            ps.execute();

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closeResource(conn,ps);
        }
    }

查询

@Override
    public Jobs getByID(String ID) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;//查询就复杂些,需要接收结果集

        try {
            conn = JdbcUtils.getConnection();
            String sql = "SELECT * FROM `jobs` WHERE `job_id` = ?;";

            ps = conn.prepareStatement(sql);

            ps.setString(1,ID);

            rs = ps.executeQuery();
            if (rs.next()) {
                Jobs jobs = new Jobs();
                jobs.setJob_id(rs.getString(1));
                jobs.setJob_title(rs.getString(2));
                jobs.setMin_salary(rs.getInt(3));
                jobs.setMax_salary(rs.getInt(4));
                return jobs;
            }


        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtils.closeResource(conn,ps,rs);
        }
        return null;

在使用传统的JDBC中,我们可以发现诸多不便

  • CURD很多步骤相同,连接数据库,获取PreparedStatement等。
    解决方法:提出公共部分作于模板
  • 将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。
    解决方法:利用反射从外部文件获取SQL语句。
  • 对于查询语句,接收结果集的过程十分繁琐
  • 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能
    解决方法:利用数据库连接池

尽管有一些简化的工具比如Dbutils和JDBCTemplate等,但能做的也很有限。
随后市面上就出现了许多持久层框架来简化开发和操作,如MyBatis、Hibernate、TopLink、Guzz、jOOQ、Spring Data。如今MyBatis占主流,同时也是主流MVC框架——SSM(Spring + SpringMVC + MyBatis)重要的一环。

MyBatis

在这里插入图片描述

什么是 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。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
–摘自百度百科

MyBatis官方帮助文档

基本使用

导入Mybatis
jar包:mybatis
Maven依赖

<!-- Maven -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.5</version>
  <!-- 之前用的3.4.6执行后会抛出一堆Warning,查了之后说要降级jdk或者升级mybatis的版本 3.5.5亲测没有这些警告-->
</dependency>

mysql驱动

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

根据官方所说,SqlSessionFactory是整个MyBatis的核心,SqlSessionFactory通过SqlSessionFactoryBuilder获取,而SqlSessionFactoryBuilder可以通过XML配置文件来构造,以下为官方给出的XML配置文件模板(配置文件名根据规范一般为mybatis-config.xml)。

<?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="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED"><!--是否使用连接池-->
       <!--连接数据库的基本四个信息-->
        <property name="driver" value="${driver}"/> 
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
  <!--将mapper注册到mybatis中,后面会讲,很重要的一步,先注释掉-->
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

在这里插入图片描述
接下来就可以获取SqlSessionFactory实例了,通过SqlSessionFactory可以获取SqlSession实例,而SqlSession才是真正与数据库交互的东西。

在这里插入图片描述

为了后续方便使用,可以写成工具类

public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;
    
    static {
    
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}

在测试前先把主配置文件中的mapper映射注释掉(写SQL的地方),因为这是官方给的例子,我们并没有该mapper需要映射,否则会报错
在这里插入图片描述

在这里插入图片描述
成功获取SqlSession,但不代表能成功连接到数据库,就算连接数据库的账号密码是错误的,在这时也不会有任何提示
在这里插入图片描述
利用SqlSession向数据库做CURD操作,仍以下表为例
在这里插入图片描述
ORM(对象关系映射),创建实体类,用于后面接收结果集
在这里插入图片描述
按照原生JDBC,接下来就是写SQL语句了,MyBatis提供在XML中编写SQL语句的形式,这些文件我们称为mapper

<?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="org.mybatis.example.BlogMapper">
	
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
  
</mapper>
  • namespece:命名空间。在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的,即使在不同映射文件中的语句ID相同,也不会再产生冲突了。命名空间在之前版本的MyBatis中是可选的,这样容易引起混淆因此毫无益处。现在命名空间则是必须的,且易于简单地用更长的完完全限定名来隔离语句。
  • id:全局唯一不解释
  • resultType:接收结果集的类
  • #{id}:占位符以该形式编写

命名空间是为了防止id冲突,这里简单使用完全不怕,但既然是必须的,就先创建一个接口
在这里插入图片描述
在这里插入图片描述
接下来很重要的一步,将我们的mapper在主配置文件中注册
在这里插入图片描述
有些人习惯把mapper放在src目录下,会很容易遇到找不到该mapper路径的异常,因为Maven一般只读取resources下的xml文件,所以解决方法就是放在resources目录下
在这里插入图片描述
如果偏要放在其他目录下比如这里的src,需要在pom.xml添加以下配置

<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

接下来就可以通过sqlSession向数据库进行CURD操作了
我们先用用selectOne()方法,该方法提供两个参数,一个是mapper接口中的方法,第二个是传入的查询参数
在这里插入图片描述
在这里插入图片描述
sqlSession的方法过于局限,同时为了避免标签冲突,更好的做法是获取mapper所绑定的接口,通过接口调用这些方法,这样不仅更安全,可读性好、传参也方便
在这里插入图片描述
经过简单的使用,可以发现MyBatis很好地解决了原生JDBC的弊端:SQL语句在外部文件中编写,降低耦合性、结果集的接收MyBatis自动完成,只需要指定接收结果集的类即可,使得我们可以专注于SQL语句的构造。

接下来把剩下的CRUD写完,不过在实现之前导入log4j日志功能,我们只能通过SqlSession是否操作数据库成功,但是如果出错,仅仅通过SqlSession很难进行排错。

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

导入依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.12</version>
</dependency>

创建log4j的配置文件(log4j.properties,最好放在resource下)
这里是从Log4j的配置与使用详解直接扒下来的

# Global logging configuration
# 设置日志输出级别以及输出目的地,可以设置多个输出目的地,开发环境下,日志级别要设置成DEBUG或者ERROR
# 前面写日志级别,逗号后面写输出目的地:我自己下面设置的目的地相对应,以逗号分开
# log4j.rootLogger = [level],appenderName1,appenderName2,…
log4j.rootLogger=DEBUG,CONSOLE,LOGFILE

#### 控制台输出 ####
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 输出到控制台
log4j.appender.CONSOLE.Target = System.out
# 指定控制台输出日志级别
log4j.appender.CONSOLE.Threshold = DEBUG
# 默认值是 true, 表示是否立即输出
log4j.appender.CONSOLE.ImmediateFlush = true
# 设置编码方式
log4j.appender.CONSOLE.Encoding = UTF-8
# 日志输出布局
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p (%c:%L) - %m%n



#### 输出错误信息到文件 ####
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
# 指定输出文件路径
#log4j.appender.LOGFILE.File =F://Intellij idea/logs/error.log 
log4j.appender.LOGFILE.File =./logs/error.log 

#日志输出到文件,默认为true
log4j.appender.LOGFILE.Append = true
# 指定输出日志级别
log4j.appender.LOGFILE.Threshold = ERROR
# 是否立即输出,默认值是 true,
log4j.appender.LOGFILE.ImmediateFlush = true
# 设置编码方式
log4j.appender.LOGFILE.Encoding = UTF-8
# 日志输出布局
log4j.appender.LOGFILE.layout = org.apache.log4j.PatternLayout
# 如果日志输出布局为PatternLayout 自定义级别,需要使用ConversionPattern指定输出格式
log4j.appender.LOGFILE.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

有了日志功能,可以了解到SQL完整的执行过程,方便调试
在这里插入图片描述
在接口制定CRUD方法
在这里插入图片描述

插入
在这里插入图片描述
对于增删改操作,SqlSession默认是不会自动提交事务的,需要手动提交
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
更改
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

删除
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
查询全部
查询多个结果,虽然返回值是集合,但在SQL语句返回值设置为集合中元素的类型就行
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

传参问题

细心的朋友可以发现在以上CRUD传参的形式都有所不同
在MyBatis中,当传参只有一个时,在SQL语句写的参数名可以随意
比如删除操作,因为不会产生歧义,名字怎么取都没关系
在这里插入图片描述
当传的参数有多个时,可以传map,SQL语句的#{}中的变量名需要和map中的key相同
在这里插入图片描述
在这里插入图片描述
也可以通过Java Bean传递多个参数,SQL语句的#{}中的变量名和实体类的属性相同
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
还有是使用@param注解的形式,这里不进行演示
MyBatis(四)SQL语句中参数传递的五种方法

还有需要注意的是 ${},#{}的区别

在很多地方,${} 的作用是直接取值,这种方式容易产生SQL注入的危险(以预编译的方式将参数设置到sql中)
而#{} 运行结果会是一个?占位符 ,和使用PreparedStatement目的一致

当然${} 并不是一无是处,可以用于分库分表排序等原生jdbc不支持占位符的地方,例如
select * from ${month}_salary order by ${name}

全局配置文件

全局配置文件的属性设置,完全可以看官方文档
mybatis-配置在这里插入图片描述
这里学习几个常用的
属性(properties)
主要用来获取外部文件的内容,比如连接数据库的信息可以单独放到另一个文件,然后通过properties引入
在这里插入图片描述

在这里插入图片描述
也可以直接在标签内设置,但是优先级还是文件中的高
在这里插入图片描述
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
比如之前mapper用到类时写的是全路径比较长
在这里插入图片描述
取别名
在这里插入图片描述
在这里插入图片描述

当需要某个包下有很多类需要取别名时,可以以包为单位为其下的所有类取别名
在这里插入图片描述
类的别名为类名(无视大小写)
在这里插入图片描述

环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境,所以要在< environments default=“development” >标签内选择具体的环境
在这里插入图片描述
每套环境内都要设置transactionManager和dataSource,具体内容自己看
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。自行了解,后续使用时会提到。
在这里插入图片描述

结果映射

在前面select语句中,对于复杂的结果集我们常用JavaBean 或 POJO来接收,也就是resultType=‘JavaBean’,虽然看似是ResultType完成了结果集的映射,但实际上是ResultMap完成了这些事。MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。

什么时候用ResultMap呢,当数据库的字段和javaBean属性字段不一样时就可以使用(数据库字段命名一般为job_id,而Java是驼峰式命名)

之前属性名和数据库字段是一一对应的,所以直接可以用resultType接收
在这里插入图片描述
假如改成驼峰式命名,我们来看看还能接收到结果集吗
在这里插入图片描述
可以看到能查询到结果,但是没接收到结果集
在这里插入图片描述

在这里插入图片描述
这种情况下容易解决,我们可以在全局配置文件中的setting标签内开启驼峰命名自动映射
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
也可以利用SQL语法中的as取别名(别忘了关掉驼峰命名自动映射,不然不知道有没有成功)
在这里插入图片描述
还有一种方法就是用resultMap作映射了
resultMap标签有三个属性
在这里插入图片描述
resultMap下有几个子标签,现在先使用这两个(id和result的作用一样)
在这里插入图片描述

如果数据库字段和属性名一一对应或者驼峰转换后对应,直接用resultType就行,用resultMap就多次一举了
在这里插入图片描述
但是对于多表查询或者一些复杂的查询就不得不用resultMap来封装结果了,在此之前先了解resultMap标签内所有的属性
在这里插入图片描述

多表查询

在先前我们已经有了一张job表
在这里插入图片描述
现在根据job_id与另一张employee表进行多表查询(为了方便点,这里只考虑以下四个属性,以及job表的job_title属性)
在这里插入图片描述

而这两张表结合呈现一对多的关系(以job表为主表,一个job可以有多个employee),而反过来就是多对一的关系(以employee为主表),因此使用resultMap时要对两种关系分别进行实现。

构造实体类
在这里插入图片描述
因为job表可以查出多个employees,所以要为其添加一个列表类型的属性(这种其实方式是很不好的,当初刚学还不懂这些,因为我们要避免修改实体类,在web开发中会经常遇到实体类接收不了封装结果,这时可以另外创建一个类来包括实体类的所有属性和多出来的属性,比如vo)
在这里插入图片描述
重写下Employees的toString方法,方便后面查看
在这里插入图片描述
多对一
接下来先以简单的多对一关系进行实现,以下面查询结果为例
在这里插入图片描述
在这里插入图片描述
映射文件,在主配置文件注册这些常规流程就不说了
在这里插入图片描述
先构造select语句
在这里插入图片描述
再构造resultMap,因为查询的主表是Employees,所以type为Employees类
在这里插入图片描述
这段没什么好说的,都是Employees的属性,按照普通的列名-属性名进行映射就行
在这里插入图片描述
重点是association标签,关联(association)元素处理“有一个”类型的关系

association标签下又包含四个属性,不过只用关心property和javaType
在这里插入图片描述
简单来说
property:你想将另一张表查询的结果集放到哪个属性下,还记得在Employees表下的Jobs jobs属性吗,所以这里property=’jobs’
javaType:接收另一张表结果集的JavaBean
接下来只要完成另一张表自己的列名-属性名的映射就行了
在这里插入图片描述
test
在这里插入图片描述
查询成功
在这里插入图片描述
在该例中,我们完全可以将多表查询等价为嵌套子查询,即先查询jobs表得到结果,在此结果上再查询employees表
因此我们可以修改成另一种形式

实现查询jobs表和employees的SQL语句,jobs直接返回结果,employees表还需要进行映射处理
在这里插入图片描述
在这里插入图片描述
同样重点在于association标签
property和javaType和上面一样
在这里插入图片描述
column和select的功能如下
column:连接键的列名,这里column是必选项,不像前面可以略去,因为要知道通过哪个共同的键进行查询
select:也是必选,不然怎么获得嵌套查询的结果
在这里插入图片描述
同样成功查询
在这里插入图片描述
一对多
当jobs作为主表时,通过job_id查询employees,显然构成了一对多的关系,但是大部分操作都相似,连SQL语句都是一样的
在这里插入图片描述

在这里插入图片描述
接下来是关键点resultMap,association标签用于处理“有一个”类型的关联,而处理“有多个”类型的关联就需要用到collection集合标签了,collection标签的属性和association标签大部分相同,需要注意的是collection返回的类型,因为返回的是List,所以javaType得是ArrayList类型(可忽略),而ofType填才是List中元素的类型

在这里插入图片描述
在jobs表中实现打印方法(本来不用这么麻烦,但是只取了几个字段,其他字段就不打印了)
在这里插入图片描述
在这里插入图片描述
成功获取
在这里插入图片描述
关于多表查询的结果映射还是比较令人头大,可以参考以下视频,以及强烈建议参考官方文档
【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂

动态SQL

​ 动态sql是指在进行sql操作的时候,传入的参数对象或者参数值,根据匹配的条件,有可能需要动态的去判断是否为空,循环,拼接等情况,是比较常用的功能。

比如在一个页面中的表单有几个传输参数(学到MyBatis应该都学过JaveWeb了)
在这里插入图片描述
根据传输的参数动态地构造SQL语句
在这里插入图片描述

在这里插入图片描述
执行SQL语句
在这里插入图片描述
原文:JDBC实现动态查询

可以发现使用原生的JDBC十分的繁琐,而且还容易漏掉空格逗号造成错误,因此MyBatis提供了强大的动态SQL,让我们摆脱这种麻烦。

在这里插入图片描述

if标签

if标签是比较常用的功能,最常见情景是根据条件包含 where 子句的一部分
比如想查询工资大于minSalary,小于maxSalary范围的字段
在这里插入图片描述

在这里插入图片描述
仅传入minSalary
在这里插入图片描述
根据日志信息可以看到成功拼接了SQL语句(maxSalary为空)
在这里插入图片描述

在这里插入图片描述
在此基础上查询工资低于20000的
在这里插入图片描述

在这里插入图片描述
其实这里写的拼接语句有很大的漏洞,假如minSalary为空但maxSalary不为空
在这里插入图片描述
就会拼接成错误的SQL语句导致报错

在这里插入图片描述
一种解决方法是在主SQL中添加where 1=1(保证后续的语句肯定执行),拼接SQL统一用逻辑连接符开头

在这里插入图片描述
这样SQL语句就不会出错了
在这里插入图片描述
另一种方法是使用trim标签,这个就自己去官网看。

choose标签

choose标签和swith-case语句类似,只会进入一个分支
比如我们可以根据job_id或job_title查询字段,如果一个属性存在,另一个就属性就不使用
在这里插入图片描述
为了规范点,以后查询条件都放在where标签内
在这里插入图片描述
只用job_title查询(when标签)
在这里插入图片描述
在这里插入图片描述
同时用两个属性查询,可以看到当排在前面的分支符合后,就算后面有其他分支符合条件也不会进入
在这里插入图片描述
默认情况(otherwise标签)
在这里插入图片描述

foreach标签

foreach常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
foreach使用难点比if和choose复杂些,主要在于参数的选择,foreach标签内有以下几个参数

  • item:表示集合中每一个元素进行迭代时的别名。(必选)
  • index:指定一个名字,表示在迭代过程中每次迭代到的位置。(可选)
  • open:表示该语句以什么开始(如果是 in 条件语句,以’('开始 )(可选)
  • separator:表示在每次进行迭代之间以什么符号作为分隔符(如果是 in 条件语句,以’,'作为分隔符)(可选)
  • close:表示该语句以什么结束(如果 in 条件语句,以’)'开始 )(可选)

使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:

  • 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
  • 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
  • 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
  • 摘自MyBatis foreach标签

比如传入List,通过多个job_id查询多个字段
在这里插入图片描述
另外提一下,我们知道形参不同就可以函数重载,但在mybatis这是不允许的,因为标签id必须得唯一
Mybatis的XxxMapper.xml中能否配置重载方法
Mybatis的mapper接口函数重载问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率
MyBatis默认定义了两级缓存:一级缓存和二级缓存

一级缓存

一级缓存是默认开启的(SqlSession级别的缓存,也称为本地缓存)
如下,当我们先后分别查询同一字段,因为第一次查询的结构仍在缓存中,所以并不需要再次从数据库查询,大大增加了高并发的效率,因为我们知道数据从内存中读取远比从磁盘IO读取来的快
在这里插入图片描述

演示需要用到日志功能,这里不使用log4j,使用MyBatis自带的日志功能换换口味,在主配置文件的setting标签开启就行

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

在这里插入图片描述

一级缓存在几种情况下会失效,官方的给的文档说到select语句的结果会被缓存,但是insert、update、delete语句会刷新缓存

在这里插入图片描述

如下,在两次查询同一字段中,我们还更新了字段,尽管更新的字段和要查询的字段无关,MyBatis为了保证ACID原则也会再次从数据库查询
在这里插入图片描述

在这里插入图片描述
对此我们可以看各个标签内的属性,对于select语句,默认使用缓存
在这里插入图片描述
对于增删改操作,没有useCache属性,并且flushCache默认为true,所以一执行就会刷新缓存
在这里插入图片描述

二级缓存

二级缓存也叫全局缓存,是基于namespace级别的缓存,一个命名空间对应一个缓存(二级缓存并不是来解决一级缓存失效的问题的)
一个会话查询一个缓存,查询的数据会存放在当前会话的一级缓存中,如果该会话关闭,其缓存也会消除。
但我们可以使其保存在二级缓存中,新的会话查询就可以从二级缓存读取,不同的mapper查出的数据会放在自己对应的缓存中
要启用全局的二级缓存,需要现在主配置文件开启。

<setting name="cacheEnabled" value="true"/>

因为二级缓存的作用域是namespace,也就是mapper映射文件,所以哪个映射文件需要二级缓存,在该mapper映射文件中添加< cache >即可

在这里插入图片描述
该标签下有五个属性

  • eviction(清除策略):缓存清空策略,默认为LRU
    1. LRU – 最近最少使用:移除最长时间不被使用的对象。
    2. FIFO – 先进先出:即队列,按对象进入缓存的顺序来移除它们。
    3. SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    4. WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  • readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
  • type(自定义缓存)除了上述自定义缓存的方式,也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

创建两个会话,在第一次会话关闭后,仍然查询相同的字段,很明显可以看到两次查询的对象是相同的。
在这里插入图片描述

在这里插入图片描述

关闭二级缓存后
在这里插入图片描述

自定义缓存

MyBatis毕竟不是专门做缓存的,所以在一些复杂的情况下就需要用其他更强大的缓存,比如EhCache、Redis。自行了解
MyBatis自定义缓存——Redis实现
MyBatis自定义缓存——EhCache实现

源码分析

MyBatis底层源码解析 (详细)

整合Spring

目前来说,Mybatis常与Spring、SpringMVC一起组成SSM框架,也是目前主流的MVC框架来完成一个JavaWeb项目的开发,因此学习整合Mybatis和Spring(SpringMVC是Spring下的子框架,整合Spring就行)是十分必要的。

使用 Spring IoC 可以有效的管理各类的 Java 资源,达到即插即拔的功能;通过 Spring AOP 框架,数据库事务可以委托给 Spring 管理,消除很大一部分的事务代码,配合 MyBatis 的高灵活、可配置、可优化 SQL 等特性,完全可以构建高性能的大型网站。
毫无疑问,MyBatis 和 Spring 两大框架已经成了 Java 互联网技术主流框架组合,它们经受住了大数据量和大批量请求的考验,在互联网系统中得到了广泛的应用。使用 MyBatis-Spring 使得业务层和模型层得到了更好的分离,与此同时,在 Spring 环境中使用 MyBatis 也更加简单,节省了不少代码,甚至可以不用 SqlSessionFactory、 SqlSession 等对象,因为 MyBatis-Spring 为我们封装了它们。
–《Java EE 互联网轻量级框架整合开发》

篇幅原因这里不进行实现
Spring:
Java学习_Spring_IoC
Java学习_Spring_AOP

MyBatis-Spring
MyBatis 与 Spring 整合

MBG

MBG(MyBatis-generator),是MyBatis框架中代码生成器。在此之前,我们先了解逆向工程的概念。

逆向工程(又称逆向技术),是一种产品设计技术再现过程,即对一项目标产品进行逆向分析及研究,从而演绎并得出该产品的处理流程、组织结构、功能特性及技术规格等设计要素,以制作出功能相近,但又不完全一样的产品。逆向工程源于商业及军事领域中的硬件分析。其主要目的是在不能轻易获得必要的生产信息的情况下,直接从成品分析,推导出产品的设计原理。

比如制造一个零件,正向的流程是先用绘图工具比如AutoCAD、UD进行绘制图纸和建模,然后进入生产车间通过车床之类的加工出来。而逆向工程是先有实物,通过扫描实物获取实物的各个参数,在计算机中分析数据,再经过修正处理就可以获得接近实物的图纸,再通过图纸加工出实物,大大减少了设计流程。

在前面使用MyBatis中,我们要根据数据库表创建实体类、DAO层的各个接口以及mapper映射文件,通过逆向工程,我们就可以根据数据库表自动生成这些东西。它只需要很少量的简单配置,就可以完成大量的表到Java对象的生成工作,拥有零出错和速度快的优点,让开发人员解放出来更专注于业务逻辑的开发。

MBG的官方文档如下,目前只有英文,估计以后也不会更新了
MyBatis Generator

导入相关依赖(数据库,MyBaits这些依赖就不说了)

<!-- mybatis-generator -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.7</version>
</dependency>

MGB只负责帮我们生成三类文件,上面也说过了

  • 实体类,根据ORM规则生成与数据库表对应的实体类
  • DAO层的接口
  • Mapper映射文件

根据官方文档快速开始,简单来说就是MBG生成的代码可以有几种风格,取决于在其配置文件下< context >如何配置targetRuntime这个属性。
MBG快速运行只需要三步

  1. 创建和配置配置文件
  2. 将该文件保存到合适的位置
  3. 在命令行界面运行MBG(有多种执行方式)
    在这里插入图片描述

targetRuntime这个属性提供了四种代码生成策略
在这里插入图片描述
这里以MyBatis3生成策略为例(MyBatis3和MyBatis3Simple的targetRuntime属性都是MyBatis3Simple,MyBatis3Simple相较于MyBatis3的配置文件只是少了< sqlMapGenerator >这个用于生成mapper映射文件的标签)
创建配置文件,将文档给的配置复制上去先

<!DOCTYPE generatorConfiguration PUBLIC
 "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

  <context id="simple" targetRuntime="MyBatis3Simple">
  <!-- 连接数据库,这里只给出了驱动和URL,用户名(userId)和密码(password)自己补上-->
    <jdbcConnection driverClass="org.hsqldb.jdbcDriver"
        connectionURL="jdbc:hsqldb:mem:aname" />
        
  <!-- java模型生成器 也就是生成实体类的 targetPackage:生成到目标包下 targetProject:生成到目标工程下 -->
    <javaModelGenerator targetPackage="example.model" targetProject="src/main/java"/>
 <!-- 生成Mapper映射文件 同上 -->
    <sqlMapGenerator targetPackage="example.mapper" targetProject="src/main/resources"/>
 <!-- 生成Mapper接口  -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="example.mapper" targetProject="src/main/java"/>
<!-- 对数据库哪张表进行生成,可以有多张表,至少有一个; domainObjectName是比较常用的属性,用于指定该表生成的实体类的名字-->
    <table tableName="FooTable" />
  </context>
</generatorConfiguration>

以这四张表为例
在这里插入图片描述
在这里插入图片描述

接下来就可以运行MBG了,执行的方法有以下几种,因为我们用XML来配置,所以用下面这种方式执行
在这里插入图片描述
把文档给的代码改改(主要是配置文件的路径)执行

  List<String> warnings = new ArrayList<String>();
   boolean overwrite = true;
   File configFile = new File("generatorConfig.xml");
   ConfigurationParser cp = new ConfigurationParser(warnings);
   Configuration config = cp.parseConfiguration(configFile);
   DefaultShellCallback callback = new DefaultShellCallback(overwrite);
   MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
   myBatisGenerator.generate(null);

在这里插入图片描述
到目标包查看生成的代码
在这里插入图片描述
在这里插入图片描述
对于实体类,MBG可以自动将数据库字段变成驼峰式命名
在这里插入图片描述
在这里插入图片描述
对于DAO层接口和Mapper映射文件,MBG会自动生成最基础的SQL语句
在这里插入图片描述
在这里插入图片描述
MBG的配置文件中还有很多配置没说,可以自行阅读官方文档和下篇文章
MyBatis——MyBatis Generator插件使用(配置详解)

通用Mapper(tk Mapper)

尽管MBG已经很大程度提高我们的效率,但在使用 MBG 过程中,如果数据库字段变化很频繁,就需要反复重新生成代码,并且由于 MBG 覆盖生成代码和追加方式生成 XML,导致每次重新生成都需要大量的比对修改。除了这个问题外,还有一个问题,仅仅基础的增删改查等方法,就已经产生了大量的 XML 内容,还没有添加一个自己手写的方法,代码可能就已经几百行了。

由于MBG 中定义的大多是常用的单表方法,为了解决前面提到的问题,也为了兼容 MBG 的方法避免项目重构太多,在 MBG 的基础上结合了部分 JPA 注解产生了通用 Mapper。通用 Mapper 可以很简单的让你获取基础的单表方法,也很方便扩展通用方法。使用通用 Mapper 可以极大的提高工作效率。
-摘自MyBatis 为什么需要通用 Mapper ?

环境搭建

官方文档:通用 MAPPER 3

通用mapper也叫tk mapper,通用Mapper需要在Mybatis和Spring的整合上进行集成

<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>4.1.5</version>
</dependency>

导入相关依赖后,需要在Spring的配置文件上进行一些修改,即Mapper扫描配置,正常扫描包的路径是mapper映射文件所在的目录
在这里插入图片描述
这里需要修改为通用Mapper的类,其作者为了方便大家,这里只需要将类最前面的org改为tk即可,同时扫描的路径不再是mapper映射文件,而是DAO层接口所在的包
在这里插入图片描述
通用Mapper帮助我们省略mapper.xml的编写,想当然必须要花费别的代价,它要求我们的DAO接口继承它提供的BaseMapper接口
这里仍以jobs表为例
在这里插入图片描述
实体类直接使用MBG生成的(注意实体类是Job,表是jobs,挖下坑)
在这里插入图片描述
继承接口,泛型传入相应的实体类
在这里插入图片描述
这里看不出什么,查看Mapper接口就会发现大有文章,Mapper接口又继承了以下接口,看接口的名称也能猜个大概
在这里插入图片描述

先看BaseMapper,很显然该接口提供了基本的CRUD功能,一层一层往下看
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

每个接口看太麻烦,直接看BaseMapper的结构,看它都继承到了些什么方法
在这里插入图片描述
为了方便测试这些方法,使用JUnit测试工具来测试
引入依赖

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>test</scope>
</dependency>

我们的接口需要把继承的方法实现,IDEA中Ctrl+I 快捷键就可以批量实现
在这里插入图片描述
在这里插入图片描述
随后在当前接口快捷键 Ctrl + Shift + t 就可以生成测试文件
在这里插入图片描述

在这里插入图片描述
最终会在test包下生成测试文件
在这里插入图片描述
然后取出相关的bean就可以测试
在这里插入图片描述

BaseMapper

以简单的selectAll()为例,取出表中所有记录
在这里插入图片描述
报错了,因为数据库中没有job这张表,前面故意留的坑,可以看出通用mapper自动找的表应该是与实体类名称一致的表,对于这种情况可以修改数据库表名或实体类名字,但显然不合理,因为这些基本是不动的。
在这里插入图片描述
只需要在实体类上加个注解就行
在这里插入图片描述

测试成功
在这里插入图片描述
同样的如果属性名和列名不一致也要添加相应的注解(下划线会自动转换为相应的驼峰式,所以这里没出错)
假如数据库字段变成驼峰式也对应不上,那肯定会报错

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样的,这种情况加上一个注解就可以解决

在这里插入图片描述
如果有些属性是数据库没有的字段,可以加上@Transient,不参与SQL语句
在这里插入图片描述

接下来测试其他方法

selectOne(),查询一个结果,这里需要传入相应实体类,并返回查询结果,传入的参数主要用于封装查询条件的(只能判断’='的条件,对于大于小于这些其他判断条件暂时判断不了)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
该方法限制了只返回一个结果,如果查询结果有多个结果会报错
在这里插入图片描述
这时得调用select()来接收
在这里插入图片描述

selectByPrimaryKey(),根据主键来查询
在这里插入图片描述
在这里插入图片描述
没查询到结果, 我们检查下SQL语句,发现这一幕,所有字段都根据给的主键值来查询,显然是查询不到的
在这里插入图片描述
这是因为通用mapper不知道哪个属性是主键,只能全部注入了,解决方法在主键属性上加上注解
在这里插入图片描述
在这里插入图片描述

insert(),插入实体类,返回生效的行数,没什么好说的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

insertSelective(),作用跟insert一样,区别在于它不会插入非空的字段
在这里插入图片描述
在这里插入图片描述

其他方法大同小异,自己去官方文档了解
Mapper 接口大全

ExampleMapper

BaseMapper接口的功能了解后,现在来看ExampleMapper
在这里插入图片描述
ExampleMapper用于QBC查询

QBC(Query By Criteria)是一种面向对象的查询方式,这种查询方式以函数API的方式动态地设置查询条件,组成查询语句。

在前面在用select方法时是直接用实体类封装查询条件,这种方法只能简单地来根据字段是否等于封装条件来查询。如果想查询更复杂些的,比如要查询薪资在某个范围内(minSalary<x<maxSalary),就要使用QBC查询了。QBC查询条件通过Criteria对象进行模块化封装。

在通用Mapper中,使用QBC查询先创建Example实例,可以从Example类的结构看出Criteria和Criterion是其内部类,需要通过Example实例获取
在这里插入图片描述
我们看到Criteria提供了大量封装好的查询条件
在这里插入图片描述
绝大多数方法需要以下这两种参数类型,显然就是属性名和属性值(仔细看看就会发现该类构造查询条件其实就是拼接字符串来实现的)
在这里插入图片描述

在这里插入图片描述

大概结构了解后,开始测试。比如想要查询薪资在(4000-5000)或(10000-20000)间的job
SQL语句应为

(minSalary >=4000 AND maxSalary<=9000) OR (minSalary >=10000 AND maxSalary<=20000) 

该SQL语句分为两部分,所以需要创建两个Criteria
在这里插入图片描述
为Criteria封装查询条件(GreaterThanOrEqualTo:大于或等于 LessThanOrEqualTo:小于或等于,如果是或逻辑,把and改成or就行)
在这里插入图片描述
然后合并两个Criteria需要调用example的or或and

在这里插入图片描述
调用ExampleMapper的方法传入example参数
在这里插入图片描述

在这里插入图片描述
当我看到生成的SQL语句我陷入了沉思,怎么又多出了一个查询条件!

在这里插入图片描述
经过几次试验,我猜测example会自动将第一个Criteria的查询条件封装到自己内部,所以example进行逻辑链接时就不用链接第一个了

如下,不对Criteria进行链接
在这里插入图片描述
可以发现封装的条件和参数和第一个Criteria一致
在这里插入图片描述
在这里插入图片描述
这样的话只用链接第二个Criteria就行了
在这里插入图片描述
在这里插入图片描述
还有一个坑是example自动链接的第一个Criteria是根据什么来决定的,是根据Criteria创建还是Criteria封装条件后?
我把2放到1前,让2先进行创建
在这里插入图片描述
根据参数的参数可以知道是根据创建的顺序来链接了
在这里插入图片描述
感觉该作者设计得不太好,不管怎么样还是查询到了结果(个人宁愿自己写SQL语句)
剩下的方法不测试了,只要英语和SQL语句有点基础都能根据方法名了解怎么使用
在这里插入图片描述

RowBoundsMapper

在这里插入图片描述

该接口的功能用于分页操作
需要两个参数,第一个封装查询条件的类,第二个RowBounds用于封装分页的参数
在这里插入图片描述
在这里插入图片描述
该类的结构十分简单,除了构造器就是get方法
在这里插入图片描述
这里就不封装查询条件了
在这里插入图片描述
可以看到该方法并不是在SQL语句后面拼接分页逻辑,而是查询所有数据到内存,再到内存中进行分页
在这里插入图片描述

MyBatis-Plus

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

官方文档:MyBatis-Plus
在这里插入图片描述
实际上中MP会用的更多,MP是比MBG和通用mapper更强大的工具,其核心功能和上两者相同,所以这里不对MP进行了解,相信大家在学会MBG和通用mapper后,加上文档能很容易上手MP
在这里插入图片描述
这些工具固然好用,但还是建议大家先把基本的SQL语句用熟练了再使用工具简化开发

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值