Mybatis知识点全总结

2022-11-22 08:23:35 浏览数 (1)

Mybatis知识点全总结

  • 一.什么是 MyBatis?
    • 1.框架是什么?
    • 2.MyBatis的开发流程
    • 3.JDBC的优缺点
      • 优点
      • 缺点
  • 二.MyBatis的基础知识
    • 1.引入MyBatis的Maven依赖
    • 2.创建并编写核心配置文件
      • 配置文件的详细讲解
        • 环境配置(environments)
          • 事务管理器(transactionManager)
          • 数据源(dataSource)
    • 3.创建实体类
    • 4.创建Mapper XML映射器
      • Mybatis的数据查询
    • 5.执行SQL语句 - (1)构建 SqlSessionFactory - (2)从 SqlSessionFactory 中获取 SqlSession
    • 6.封装初始化工具类MybatisUtils
    • 7.设置(settings)
    • 8.SQL传参
      • 传入一个参数时的写法
      • 要传入多个参数时的写法
    • 9.多表关联查询
      • 获取多表关联查询结果
    • 10.ResultMap结果映射
    • 11.Mybatis的数据插入、修改与删除的操作
      • MySQL相关知识拓展----事务
        • (1)事务简介
        • (2)事务的ACID属性(事务的特点)
        • (3)提交事务
        • (4)回滚事务
        • (5)事务之间的隔离级别
      • Mybatis数据插入操作
        • selectKey 元素的属性
        • useGeneratedKeys属性的用法
      • 数据的更新与删除操作
      • 12预防SQL注入攻击
        • Mybatis的两种传值方式
    • 三.Mybatis进阶-----高级特性
      • 1.Mybatis日志管理
      • 2.Mybatis动态SQL
      • 3.Mybatis二级缓存
        • 二级缓存概述
        • 二级缓存的运行规则
        • 案例演示
      • 4.Mybatis多表级联查询
      • 5.分页插件----PageHelper - PageHelper的使用流程
      • 6.Mybatis配置C3P0连接池
      • 7.Mybatis批处理
        • 批量插入
        • 批量删除
      • 8.Mybatis注解开发
    • 四.完结感言

一.什么是 MyBatis?

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

1.框架是什么?

框架就是一个软件,完成了部分功能。框架是一种规则,保证开发者遵循相同的方式开发程序。框架提倡“不要重复造轮子”,对基础功能进行封装。框架是可以升级和改造的,框架是安全的。但是框架只对某一个方面有用,不是全能的。

框架的优点:

  1. 极大地提高了开发效率
  2. 统一的编码规则,利于团队管理
  3. 灵活的配置,拥有更好的维护性

Mybatis是持久层框架,使用XML将SQL与程序解耦,便于维护。底层基于JDBC。

2.MyBatis的开发流程

  1. 引入MyBatis的Maven依赖
  2. 创建核心配置文件
  3. 创建实体(entity)类
  4. 创建Mapper映射文件
  5. 初始化sqlSessionFactory
  6. 利用Sqlsession对象操作数据

3.JDBC的优缺点

优点

  1. 直观,好理解

缺点

  1. 创建很多的对象(比如:Connection,Preparedstatement,ResultSet等)。
  2. 注册驱动非常麻烦。
  3. SQL语句和业务逻辑代码混在了一起,不好管理。

二.MyBatis的基础知识

后面的学习是通过案例实践来总结知识点的,这些案例要用到的资源有数据库的建库脚本,我把它上传上了我CSDN的资源文件了。链接: 数据建库脚本,我用的是MySQL数据库,关于脚本怎么使用,点开idea专业版右侧的database添加MySQL数据库后,右键选择run script后选择此文件来创建数据库。如果你不会使用,评论区或者私信联系我,我24小时在线答疑。

1.引入MyBatis的Maven依赖

使用 Maven 来构建项目,则需将下面的依赖代码配置于 pom.xml 文件中,还要导入jdbc驱动和其他的依赖,这里不列举:

代码语言:javascript复制
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

2.创建并编写核心配置文件

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)等。MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

配置文件的详细讲解

在项目的Resource文件夹下,创建一个mybatis-config.xml的核心配置文件。配置文件的名字其实也可以去其他的名字。

下面就是这个配置文档的代码的一个案例,通过后面的学习,会有更多其他配置项的详细讲解。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<!--这是xml的约束,根标签为configuration-->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>  
    <!--驼峰命名转换-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  <!--environments里面可以创建很多environment的环境,default指定默认的环境配置(id)-->
    <environments default="dev">
        <!--environment环境配置,不同的环境用不 同的id-->
        <environment id="dev">
            <!--采用jdbc的方式对数据库的事务进行commit/rollback-->
            <transactionManager type="JDBC"/>
            <!--采用连接池的方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun"/>
                <property name="username" value="root"/>
                <property name="password" value="zc20020106"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
</configuration>

这里我们就创建好了一个最基本的mybatis配置文件了,当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。下面我来对里面的配置项依次解释:

首先,就是每个XML文件都有的声明:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>

然后定义了该配置文件的约束条件,约束了根标签为configuration和其他的一些配置项约束:

代码语言:javascript复制
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
环境配置(environments)

MyBatis 可以配置多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。如果忽略了环境参数,那么将会加载默认环境

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推.

  • 默认使用的环境 ID(比如:default=“dev”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“dev”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="JDBC|MANAGED")

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 J2EE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
代码语言:javascript复制
<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>

提示:如果你正在使用 Spring MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

数据源(dataSource)

有三种内建的数据源类型(也就是 type="UNPOOLED|POOLED|JNDI"):

UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。

UNPOOLED 类型的数据源仅仅需要配置以下 5 种property属性:

property

解释

driver

这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。

url

这是数据库的 JDBC URL 地址。

username

登录数据库的用户名。

password

登录数据库的密码。

defaultTransactionIsolationLevel

默认的连接事务隔离级别。

defaultNetworkTimeout

等待数据库操作完成的默认网络超时时间(单位:毫秒)。

POOLED: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源。可以根据自己以后的需求来进行配置:

property

解释

poolMaximumActiveConnections

在任意时间可存在的活动(正在使用)连接数量,默认值:10

poolMaximumIdleConnections

任意时间可能存在的空闲连接数。

poolMaximumCheckoutTime

在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)

poolTimeToWait

这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。

poolMaximumLocalBadConnectionTolerance

这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)

poolPingQuery

发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。

poolPingEnabled

是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。

poolPingConnectionsNotUsedFor

配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

对于配置文件的mappers标签,我们后面再解释。

3.创建实体类

上一步,创建并编写好了配置文件。现在要创建数据表所对应的实体类。我们在Java目录下建一个org.haiexijun.entity的一个包,用来放置所有与数据表对应的实体类。这里就以数据库中的t_goods这张表为例,创建一个实体类。我们给这个实体类取名为Goods.然后按照JavaBean的相关的规范来创建字段和get、set方法。

代码语言:javascript复制
package org.haiexijun.entity;

public class Goods {
    private Integer goodId;//商品编码
    private String title;//标题
    private String subTitle;//子标题
    private Float  originalCost;//原始的价格
    private Float currentPrice;//当前的价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮。1包邮,0不包邮
    private Integer categoryId;//分类编号

    public Integer getGoodId() {
        return goodId;
    }

    public void setGoodId(Integer goodId) {
        this.goodId = goodId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public Float getOriginalCost() {
        return originalCost;
    }

    public void setOriginalCost(Float originalCost) {
        this.originalCost = originalCost;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }

    public Float getDiscount() {
        return discount;
    }

    public void setDiscount(Float discount) {
        this.discount = discount;
    }

    public Integer getIsFreeDelivery() {
        return isFreeDelivery;
    }

    public void setIsFreeDelivery(Integer isFreeDelivery) {
        this.isFreeDelivery = isFreeDelivery;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }
}

4.创建Mapper XML映射器

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。我们的SQL语句就是再在里面编写的。

Resource包下创建mappers文件夹,在里面创建一个名为goods.xml的文件,然后对goods.xml进行如下的配置:

代码语言:javascript复制
<?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="goods">
    <!--select语句,id表示select语句的名称,resultType表示返回查询结果为entity对象的结果-->
    <select id="selectAll" resultType="org.haiexijun.entity.Goods">
        select * from t_goods order by goods_id desc limit 10
    </select>
</mapper>

我们首先引入了xml约束,规定了mapper为根标签。mapper根标签有一个属性叫做namespace,值我取为goods。在mapper里面,可以定义select语句等其他语句,我们用<select>标签进行定义查询语句,每个select标签都有一个id值,用来标识各语句。在select标签中要定义resultType属性为实体类的全类名。表示查询结果返回为指定的那一个实体entity。配置完后要在核心配置文件里面引用到这个mapper。在mybatis-config.xml根节点下面添加一个mappers节点:

代码语言:javascript复制
    <mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>

这个节点里面,可以设置我们在goods.xml中定义好的mapper。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出)

元素

解释

cache

该命名空间的缓存配置。

cache-ref

引用其它命名空间的缓存配置。

resultMap

描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。

sql

可被其它语句引用的可重用语句块。

insert

映射插入语句。

update

映射更新语句。

delete

映射删除语句。

select

映射查询语句。

Mybatis的数据查询

查询语句是 MyBatis 中最常用的元素之一——光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。 MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。

Select 元素的属性

属性

描述

id

在命名空间中唯一的标识符,可以被用来引用这条语句。

parameterType

将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。

resultType

期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。

resultMap

对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。

flushCache

将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。

useCache

将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。

timeout

这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。

fetchSize

这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。

statementType

可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

resultSetType

FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。

databaseId

如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

resultOrdered

这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。

resultSets

这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

5.执行SQL语句

前面我们对mybatis已经配置得差不多了,现在差不多就可以使用mybatis来执行SQL语句了。在项目的Test测试文件夹中创建一个MybatisTest的一个Java测试类,我们同时要到导入junit的maven依赖:

代码语言:javascript复制
         <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

然后创建一个名为testSelectAll的测试方法,编写测试select语句的相关代码,代码如下:

代码语言:javascript复制
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.haiexijun.entity.Goods;
import org.junit.Test;

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

public class MybatisTest {
    @Test
    public void testtestSelectAll() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml环形配置文件
        Reader reader=Resources.getResourceAsReader("mybatis-config.xml");
        //初始化SqlSessionFactory
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession=null;
        try {
            sqlSession=sqlSessionFactory.openSession();
            sqlSession.selectList("goods.selectAll");
            List<Good> goods= sqlSession.selectList("goods.selectAll");
            for (Good g:goods){
                System.out.println(g.getSubTitle());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //如果type=”POOLED",代表连接池,close则是将连接回收到连接池中
            //如果type=“UNPOOLED",代表将连接关闭
            if (sqlSession!=null){
                sqlSession.close();
            }
        }
    }
}

运后能成功查询到相关的数据。

下面对代码进行一些步骤的解释:

(1)构建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory是Mybatis的核心对象。用于初始化Mybatis,创建SqlSession对象。要保证SqlSessionFactory在全局中唯一。

SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 可以使用任意的输入流(Reader、InputStream)实例。

如:

代码语言:javascript复制
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
(2)从 SqlSessionFactory 中获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

SqlSession是Mybatis操作数据库的核心对象,底层使用JDBC与数据库进行交互。 SqlSession对象提供了数据表CRUD对应的方式。

看到这里,就算完成了对Mybatis的最基本的操作了。恭喜你!入门了。但是这只是入门而已,我们仍然能发现代码的很多问题。比如,我们的代码无法保证SqlSessionFactory在全局中唯一。所以我们可以对代码做进一步的优化改善。所以下面要封装一个MybatisUtils初始化工具类来对我们的代码进一步优化。

6.封装初始化工具类MybatisUtils

在项目的main下Java下创建一个org.haiexijun.utils的工具包,用于存放我们封装的工具类。

然后再utils包下面创建一个叫做MybatisUtils的java类。代码如下:

代码语言:javascript复制
package org.haiexijun.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.Reader;

//工具类的方法和属性一般都用static关键字修饰
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory=null;
    //用于初始化静态对象
    static {
        Reader reader= null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
            //把异常抛出去,让使用到这个工具类的程序也知道我们这段代码出错了
            //ExceptionInInitializerError是初始化异常
            throw new ExceptionInInitializerError(e);
        }
    }
    //创建一个方法获取SqlSession对象
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }
    //关闭(回收)SqlSession的连接的方法
    public static void closeSession(SqlSession session){
        if (session !=null){
            session.close();
        }
    }
}

我们编写好MybatisUtils初始化工具类后,我们再到之前编写的MybatisTest测试类中对它进行测试,代码如下:

代码语言:javascript复制
    @Test
    public void testMybatisUtils(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            List<Good> goods= sqlSession.selectList("goods.selectAll");
            for (Good g:goods){
                System.out.println(g.getSubTitle());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

结果显示,使用MybatisUtils工具类后,既保证了SqlSessionFactory在全局中唯一,也使我们的代码更加的直观和整洁了。

你以为到这里就结束了吗?并没有哦。我们的代码还有一个很严重的bug没有解决。我们先来对比一下数据库中的t_goods数据表的字段名和我们实体类定义的字段名。如下图:

数据库表的字段名:

Good的entity实体类的字段名:

我们会发现,有些字段的名字是相互对应的关系,如title和discount这两个字段。但如果字段的名字是由两个以上单词组成时,就不是相互对应的关系了。如good_id这个字段我们在实体里面写为goodId这种大驼峰的写法,但这种定义Mybatis无法对其识别。如果我们这时查询goodId,则查询结果全为null。解决办法就是对mybatis的核心配置文件中增加一个设置项<settings>,之后mybatis就会自动对数据表进行驼峰命名转换了。

在mybatis-config.xml中新增加如下代码:

代码语言:javascript复制
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

7.设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。我们就是在里面设置对数据表的驼峰命名转化的。

下面我们列出一些setting的选项:

设置名

描述

有效值

默认值

cacheEnabled

全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。

true 或 false

true

lazyLoadingEnabled

延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。

true 或false

false

aggressiveLazyLoading

开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。

true 或 false

false (在 3.4.1 及之前的版本中默认为 true)

multipleResultSetsEnabled

是否允许单个语句返回多结果集(需要数据库驱动支持)。

true 或 false

true

defaultStatementTimeout

设置超时时间,它决定数据库驱动等待数据库响应的秒数。

任意正整数

未设置 (null)

aultFetchSize

为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。

任意正整数

未设置 (null)

defaultResultSetType

指定语句默认的滚动策略。(新增于 3.5.2)

FORWARD_ONLY

或SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 或DEFAULT(等同于未设置)

safeRowBoundsEnabled

是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。

true 或false

False

mapUnderscoreToCamelCase

是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。

true

或false False

。。。

、、、

、、、

、、、

一个配置完整的 settings 元素的示例如下:

代码语言:javascript复制
<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

8.SQL传参

前面案例里面,有用到select,查询t_goods表的所有的数据。但是,很多时候,我们要查询的数据可能并不是全部数据,而是我们指定要查询的数据。这时候,我们就不能再像以前那样把select语句给写死了。而是根据用户的输入,动态传入参数来查询。

传入一个参数时的写法

要实现SQL传参,就要用到select元素的一个属性:parameterType,来指定参数的类型。

比如我要通过Id值来查询数据:

代码语言:javascript复制
    <select id="selectById" parameterType="Integer" resultType="org.haiexijun.entity.Goods">
           select * from t_goods where goods_id = #{value}
    </select>

parameterType指定传入的参数为Integer类型,然后在select语句里面用#{ }来表示传入的参数的占位。有点像JDBC里面preparedstatement的 ?。

在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testSelectById(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Good good= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

注意: 因为是通过id来查询数据,返回的数据只有一条。所以要用selectOne()方法 ,这个方法传入两个参数,第二个参数是sql语句要传入的参数值,要与之前select元素里面编写的parameterType要一致。比如传入1603,就指查找id为1603的good的信息。

要传入多个参数时的写法

如果要查询某一价格范围内的商品的信息,传入的参数为最大值和最小值这两个值,此时一个参数就不能完成查询了。但是Mybatis只支持设置一个parameterType。

遇到多参数的情况下,我们可以通过设置parameterType的值为java.util.Map,键值对的形式来传入多个参数。

新建一个select查询:

代码语言:javascript复制
    <select id="selectByPriceRange" parameterType="java.util.Map" resultType="org.haiexijun.entity.Goods">
        select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}
    </select>

可以看到,我们把parameterType的值定义为java.util.Map,select语句要传入三个参数:min,max和limit。

在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testSelectByPriceRange(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Map param=new HashMap();
            param.put("min",100);
            param.put("max",500);
            param.put("limit",10);
            List<Good> goods= sqlSession.selectList("goods.selectByPriceRange",param);
            for (Good g:goods){
                System.out.println(g.getTitle() ":" g.getCurrentPrice());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

我们创建了HashMap,并传入3个键值对,这三个键值对与select语句中要传入的参数一 一对应。键名也要对应。

9.多表关联查询

在实际开发中,大量的企业级应用,都是多表联合查询所产生的一个复杂的结果集。查询的结果字段会横跨很多张表。

获取多表关联查询结果

如果要进行多表关联查询,那么查询结果的类型就不是某个具体的实体(entity)了。此时我们要把resultType设置为java.util.Map。也就是说,多表关联查询后返回的结果为map类型。

编写一条select语句,查询t_goods表和t_category这两个表的中category_id相同的结果。

代码语言:javascript复制
    <select id="selectGoodsMap" resultType="java.util.Map">
        select g.* , c.category_name from t_goods g,t_category c
        where g.category_id = c.category_id
    </select>

在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testSelectGoodsMap(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            List<Map> list= sqlSession.selectList("goods.selectGoodsMap");
            for (Map m:list){
                System.out.println(m);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

运行这个测试方法后,如下:

我们看到所有的键名都是原数据表的字段名。而且会发现虽然Map可以存储查询结果,但是存储的结果并不是按顺序来排列的。这是因为HashMap是按哈希值来排序的。

resultType设置为java.util.LinkedHashMap,因为LinkedHashMap是链表形式的HashMap,会按照顺序来存取。

代码语言:javascript复制
    <select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
        select g.* , c.category_name from t_goods g,t_category c
        where g.category_id = c.category_id
    </select>

更改后运行代码,就有序了:

利用LinkedHashMap,保存多表关联查询结果,Mybatis会将每条记录都包装为LinkedHashMap对象。Key是字段名,value是字段对应的属性值。字段类型会根据表结构自动判断。

优点:易于拓展和使用

缺点:太过于灵活了,无法进行编译时检查。

10.ResultMap结果映射

  • ResultMap可以将查询结果映射为复杂类型的java对象
  • ResultMap适用于Java对象保存多表关联的结果
  • ResultMap支持对象关联查询等高级特性。
  • resultMap 元素是 MyBatis 中最重要最强大的元素。

写一个案例来体验一下结果映射:

上面的案例我们用LinkedHashMap来保存了多表联合查询的结果。

如果此次我想要把多表联合查询的结果保存为Java对象的话,就要用到ResultMap结果映射。

  1. 先创建一个新的包:org.haiexijun.dto。 DTO(data transfer object)数据传输对象,是一个特殊的javaBean,对原始的对象进行扩展,用于数据保存和传递。
  2. 在org.haiexijun.dto包下面创建一个叫GoodsDTO的Java类。下面是代码:
代码语言:javascript复制
package org.haiexijun.dto;

import org.haiexijun.entity.Goods;

public class GoodsDTO {
    //Goods实体的引用
    private Goods goods=new Goods();
    //其他字段
    private String categoryName;

    public Good getGoods() {
        return goods;
    }

    public void setGoods(Good goods) {
        this.goods = goods;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }
}

那么针对于这个DTO对象,如何让Mybatis自动对其进行对应的赋值呢?在这,我们就要使用ResultMap了。

  1. 配置ResultMap

(1)在goods.xml映射文件中创建新建一个resultMap节点,并对其进行配置:

代码语言:javascript复制
    <!--结果映射-->
    <resultMap id="rmGoods" type="org.haiexijun.dto.GoodsDTO">
        <!--设置主键字段和属性映射-->
        <id property="goods.goodsID" column="good_id"/>
        <!--设置非主键字段与属性映射-->
        <result property="goods.title" column="title"/>
        <result property="goods.subTitle" column="sub_title"/>
        <result property="goods.originalCost" column="original_cost"/>
        <result property="goods.currentPrice" column="current_price"/>
        <result property="goods.discount" column="discount"/>
        <result property="goods.isFreeDelivery" column="is_free_delivery"/>
        <result property="goods.categoryId" column="category_id"/>
        <!--特殊的DTO的字段,property直接传入字段名-->
        <result property="categoryName" column="category_name"/>
    </resultMap>

resultMap的id属性是为了被下面的select元素引用而定义的。type表示把结果存为哪个DTO。resultMap还要顶义一些子标签,id标签是设置主键字段和属性映射,result标签是设置非主键字段与属性映射。每个子标签都有两个属性:property和column。column表示查询的字段名,property这指向了GoodsDTO里面的每个属性的名称,如果一个属性是额外的属性,则直接传入属性名。

(2)在goods.xml映射文件中新建一个select元素,并为其指定resultMap属性,属性值为上面定义的resultMap的id属性值rmGoods:

代码语言:javascript复制
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.category_name from t_goods g,t_category c
        where g.category_id = c.category_id
    </select>
  1. 在MybatisTest测试类中编写测试方法:
代码语言:javascript复制
    @Test
    public void testSelectGoodsDTO(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            List<GoodsDTO> list= sqlSession.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g:list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

11.Mybatis的数据插入、修改与删除的操作

在学习mybatis的写操作之前,想要学习一下数据库的事务。

MySQL相关知识拓展----事务

(1)事务简介

一个或一组SQL语句组成一个执行单元,这个执行单元要么全部执行,要么全部不执行。一个事务其实就是一个完整的业务逻辑。很多情况下,完成一个业务需要多条sql语句,每条sql语句是相互依赖的,如果单元中的某条sql语句受到影响或产生错误,整个业务单元将会回滚到以前的状态。只有DML语句(增、删、改)才有事务一说,其他语句没有事务。 只要有涉及到数据的增删改,那么就一定要考虑到安全问题。数据库事务是保证数据操作完整性的基础。

比如:

银行转账,A用户向B用户转账10000元。

业务逻辑:

将A用户的钱减去10000元(update语句) ; 将B用户的钱增加10000元(update语句)。

这就是一个完整的业务逻辑(即事务)。

以上的操作是一个最小的工作单元,不可再分,要么同时成功,要么同时失败。两个update语句必须同时成功,同时失败。

(2)事务的ACID属性(事务的特点)
  1. 原子性:原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性:事务必须使数据库中从一个状态变为另一个一致状态。
  3. 隔离性:一个事务不能被其他的事务干扰
  4. 持久性:事务被最终结束的一个保障。
(3)提交事务

在事务的执行过程中,每一条DML语句的操作都会记录到“事务性活动的日志文件”中。提交事务就是清空事务性活动的日志文件,将数据全部彻底地持久化到数据库中。提交事务标志着事务的结束。并且是一种全部成功的结束。MySQL在默认情况下自动提交事务,每执行一条DML语句,提交一次。

(4)回滚事务

将之前所有的DML语句的操作全部撤销,清空事务性活动的日志文件。回滚事务也表示事务的结束,并且是一种全部失败的结束。回滚只能回滚到上一次的提交点。

(5)事务之间的隔离级别

1. 读未提交 read uncommitted (最低的隔离级别)

事务A可以读取到事务B未提交的数据。会读到脏数据。这种隔离级别非常少用。(事务B没有提交就读到了)

2. 读已提交 read committed

事务A只能读取到事务B提交之后的数据。这种隔离级别解决了脏读的现象。但存在一些问题:不能重复读取数据。比如在事务开启后,第一次读取到的数据是3条,但是如果当前事务还没有,可能第二次再读取的时候,读到的数据是4条。(提交之后才能读到)

3. 可重复度 repeatable read

事务A开启之后,不管过多久,每一次在事务A中读取到的数据都是一致的。即使事务B将数据已经修改,并且提交了,事务A读取到的数据还是没有发生改变。(提交提交之后也读不到)

4. 序列化/串行化 serializable(最高隔离级别)

这是最高隔离级别,但效率最低。虽然解决了所有的问题。但事务排队,不能并发。

MySQL的默认隔离级别是repeatable read

好的,了解完MySQL的事务的相关知识点后,就可以学习mybatis的写操作了。mybatis的写操作包含3种,分别是插入<insert>,更新<update>和删除<delete>。

Mybatis数据插入操作

编写goods.xml文件,添加如下的代码:

代码语言:javascript复制
    <insert id="insert" parameterType="org.haiexijun.entity.Goods">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery,category_id)
        values(#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery},#{categoryId})
    <selectKey resultType="Integer" keyProperty="goodId" order="AFTER">
        select last_insert_id()
    </selectKey>
    </insert>

insert标签有id和parameterType两个属性。然后里面添加insert语句,用 #{ } 来传入参数。

注意:insert into 后面的键名有下划线,而values()里面的#{ } 里面的字段名没有下划线,是驼峰的,不要写错了,不然会报错哦。

会发现,我们并没有在insert语句中传入主键goods_id ,要通过<selectKey>来插入主键goods_id.

我们一般用<selectKey>来设置主键。它里面会有一条select语句,意思是查询插入数据后的最后的id值。<selectKey>会将里面查询的id值赋值给goods_id.

selectKey 元素的属性

属性

描述

keyProperty selectKey

语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。

keyColumn

返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。

resultType

结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。

order

可以设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。

statementType

和前面一样,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement, PreparedStatement 和 CallableStatement 类型。

在goods.xml中编写好insert后,在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testInsert(){
        SqlSession session=null;
        try {
            session=MybatisUtils.openSession();
            Goods goods=new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试商品的子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            System.out.println(goods.getSubTitle());
            //insert()方法的返回值代表本次成功插入的记录总数
            int n=session.insert("goods.insert",goods);
            System.out.println(n);
            //提交事务
            session.commit();
        }catch (Exception e){
            //回滚事务
            if (session!=null){
                session.rollback();
            }
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

案例使用的是sqlSession的insert方法来才对数据进行插入操作。里面传入2个参数,第一个参数是insert语句的映射,第二个参数是要插入的数据,传入一个Goods实例。 insert()方法的返回值代表本次成功插入的记录总数。

执行完insert语句后,如果成功要对事务进行提交(session.commit()),失败要在catch里面把事务回滚(session.rollback())。

useGeneratedKeys属性的用法

上面的案例用selectKey来给插入数据的主键值取值。其实还可以用useGenetatedKeys属性来完成。下面来介绍这个属性的用法。

代码语言:javascript复制
    <insert id="insert" parameterType="org.haiexijun.entity.Goods" useGeneratedKeys="true" keyProperty="goodId" keyColumn="goodId">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery,category_id)
        values(#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery},#{categoryId})
    </insert>

useGeneratedKeys属性仅适用于 insert 和 update,这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键,默认值:false。

keyProperty属性仅适用于 insert 和 update,指定能够唯一识别对象的属性(主键),如果生成列不止一个,可以用逗号分隔多个属性名称。

keyColumn属性仅适用于 insert 和 update,设置生成键值在表中的列名,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。

selectKey标签与useGeneratedKeys标签的区别:

selectKey标签需要明确编写获取最新主键的SQL语句,useGeneratedKeys属性会自动根据驱动生成对应的SQL语句。二者的应用场景也不同:selectKey适用于所有的关系型数据库而useGeneratedKeys只支持“自增主键”的数据库。

数据的更新与删除操作

数据的更新与删除操作与前面的插入数据的操作非常的相似,只不过用的标签不一样罢了。其实用法非常简单啦。

更新操作(update)

先在goods.xml中配置update语句:

代码语言:javascript复制
    <update id="update" parameterType="org.haiexijun.entity.Goods">
        update t_goods 
        set 
        title = #{title},
        sub_title = #{subTitle},
        original_cost = #{originalCost},
        current_price = #{current_price},
        discount = #{discount},
        is_free_delivery = #{isFreeDelivery},
        category_id = #{categoryId}
        where
        goods_id=#{goodId}
    </update>

然后在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testUpdate(){
        SqlSession session=null;
        try {
            session=MybatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById",2679);
            goods.setTitle("更新的测试商品");
            goods.setSubTitle("更新的测试商品的子标题");
            goods.setOriginalCost(300f);
            goods.setCurrentPrice(150f);
            int n=session.update("goods.update",goods);
            //提交事务
            session.commit();
        }catch (Exception e){
            //回滚事务
            if (session!=null){
                session.rollback();
            }
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

删除操作(delete)

删除操作非常的简单,下面来演示一下具体操作:

先在goods.xml中配置delete语句:

代码语言:javascript复制
    <delete id="delete" parameterType="Integer">
        delete
        from t_goods
        where goods_id=#{value};
    </delete>

然后在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testDelete(){
        SqlSession session=null;
        try {
            session=MybatisUtils.openSession();
            session.delete("goods.delete",2683);
            //提交事务
            session.commit();
        }catch (Exception e){
            //回滚事务
            if (session!=null){
                session.rollback();
            }
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

12预防SQL注入攻击

SQL注入攻击是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式。

Mybatis的两种传值方式

1 . ${ }替换文本替换,未经任何处理对SQL文本进行替换

2 . #{ }预编译传值,使用预编译传值可以预防SQL注入(我们一直在使用的方式)

三.Mybatis进阶-----高级特性

1.Mybatis日志管理

日志:日志文件是用于记录系统操作事件的记录文件或文件集合。日志保存历史数据,是诊断问题以及理解系统活动的只要依据。

说起Java中的日志,我们就必须要说到上面这个特殊的结构了。

在java中,日志其实分成2部分,一个叫做日志门面,一个叫作日志实现。门面和具体的实现是什么意思呢?

举一个生活中的例子,家家户户都有插排,插排会有2个空的和3个孔的,在中国,所有插排的规格都是统一的,那么统一的这个插排的插孔的设计就是门面,但是对于不同的插排里面的电路结构的是不同的,比方说公牛和小米等等等。有的设计了安全保护电路,有的使用了其他材料。不同的品牌都有不同的设计。但是呢,如果作为最终的产品体现出来的话,他们的规格都是相同的。但是里面具体的实现是不同的。真是因为门面和实现进行了区分,通过统一的门面,屏蔽了底层复杂的实现,所以我们的插排在中国任何地方都可以通用。

诸如此类的设计在我们Java中也是存在的,作为日志来说也是一样。上面图中提到了两个不同的日志门面,SLF4JCommons-Logging。他们的作用就像是我们插排的面板一样,为java提供了统一的日志调用接口。

SLF4JCommons-Logging 是两个不同的厂商开发的,而且这两个门面组件在我们日常的开发中都使用得很广。所以呢,有的公司使用的是SLF4J的日志门面,有的公司的使用的是Commons-Logging 这个日志门面。

日志门面的下面,有各种日志门面的实现,我们都知道,在java中,有各种各样的组织开发了各种各样的产品,那对于日志来说也是这样的。诸如有log4j、logback、java.util.logging(jul)等等。这些组件提供了日志的打印、输出与管理。

正是因为在在日志中,基于门面和实现进行了彼此分开,所以给我们程序迁移提供了极大的便利。

举个例子,比方说,我现在开发了一个Java程序,底层使用了SLF4J作为日志门面,而在后面我使用了log4j这个日志组件。但是呢,随着技术的不断发展和延伸,logback从性能和设计上都比log4j要好,那我要做的就是把log4j这个jar包从系统中剔除,加入logback这个jar包就可以了。最为我们程序访问的门面不用做任何的调整,SLF4J自动的会完成从log4j到logback得迁移工作。也就是说,我们的程序当面向了统一的门面的时候,底层具体的实现对于程序的调用者来说已经不重要了。这个切换的工作是由日志门面自动帮我们完成的。

那对于实现来说,目前主流的日志组件有log4jlogback等,但是目前的主流是logback,log4j在早期非常有名,log4j的作者对log4j的改进是logback。

Mybatis底层就是通过SLF4J支持logback。

下面通过一个案例来演示如何让mybatis的logback的使用,来输出日志:

1.在项目的pom.xml中导入logback的maven依赖:

代码语言:javascript复制
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>test</scope>
        </dependency>

我们在没有导入logback依赖以前,在运行MybatisTest测试类中运行测试方法不会打印出日志,当我们到入logback的依赖后,就会看到运行测试方法后控制台会打印出很多日志信息。这些信息可以方便我们程序的调试。

下面呢,我们针对于日志的展现和日志的控制细则来进行讲解。

作为logback,它呢是允许对日志进行自定义的。具体的做法呢,是需要在项目的resources目录下新增加一个xml文件,这个xml文件强制要求叫作logback.xml 。这个xml文件用来对logback进行配置。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="trace" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <patten>%d{HH:mm:ss:SSS} [%thread] %-5level %logger{36} - %msg%n</patten>
        </encoder>
    </appender>
    <root level="trace">
        <appender-ref ref="console"/>
    </root>
</configuration>

对于logback.xml不做过多的解释,更多配置参考logback的官网学习。

2.Mybatis动态SQL

动态SQL是指根据参数数据动态组织SQL的技术。

动态SQL听起来似乎非常的高大上,但是在日常项目中,动态SQL的使用场景是非常普遍和广泛的。比如我们在淘宝上搜索笔记本电脑,搜索后,会显示所有的笔记本电脑,在此时我们还可以对电脑进行品牌的筛选等,这就会涉及到SQL语句的拼接了,此时就要用到动态SQL了。

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

下面写一个来体验一下动态SQL:

1.打开goods.xml,编写sql语句:

代码语言:javascript复制
    <select id="dynamicSQL" parameterType="java.util.Map" resultType="org.haiexijun.entity.Goods">
        select  * from t_goods
        where
        //添加1=1
        //或包裹where标签
        <where>
        <if test="categoryId != null">
            and category_id =#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分,用到了if 标签。如果有>和 <等一些特殊的符号,就要对符号进行转移。如:<要写成< 。1=1起占位作用,防止语法报错。或包裹一个where标签。where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

然后我们在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testDynamicSQL(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Map param=new HashMap();
            param.put("categoryId",44);
            param.put("currenPrice",500);
            List<Goods> list=sqlSession.selectList("goods.dynamicSQL",param);
            for (Goods g:list){
                System.out.println(g.getTitle());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

当然,实际项目中可能不会这样用了,这里只是了解了解它的基本用法。

具体怎么用,我在以后的项目里再慢慢体会。动态SQL除了if标签外还有其他的标签。下面来简单介绍一下其他标签。

choose、when、otherwise

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

代码语言:javascript复制
    <select id="choose" parameterType="java.util.Map" resultType="org.haiexijun.entity.Goods">
        select * from t_goods
        where
        discount=1
        <choose>
            <when test="categoryId != null">
                and category_id =#{categoryId}
            </when>
        <when test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </when>
        <otherwise>
            and category_id =30
        </otherwise>
        </choose>
    </select>

传入categoryId就加入查询category_id =#{categoryId}的条件,传入currentPrice,就加入查询current_price < #{currentPrice}的条件,如果都没有传入,就插入otherwise里面程序定义的条件。

3.Mybatis二级缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行<cache/>

二级缓存概述

所谓缓存就是缓冲存储的意思,在mybatis中用于数据优化,提高程序执行效率的有效方式。这里说的有点晦涩。打个比方,比如我现在用SQL语句第一次查询时获取到了婴幼儿奶粉这个商品,紧接着,因为程序的需要,可能我还需要重新再获取一次婴幼儿奶粉。那这个时候,如果按照原始设计,它需要再次从数据库中,把婴幼儿奶粉的数据提取出来。可试想一下,MySQL是把数据存储在硬盘上的,硬盘提取数据的速度并不是很快。同时,我们第一次和第二次获取数据的时候,他们都返回了相同的数据。如果要对这种情况进行优化的话,可以把第一次查询的数据放在内存中的某个区域中,当在次获取这个数据的时候,不去读取数据库,而是直接从内存中读取数据就可以了。因为内存的速度是非常快的,所以可以极大地提高我们程序的执行效率。我们这种把数据存储在内存中的方式其实就是缓存的最基础和最底层的实现。那么,在mybatis中,缓存到底是如何设计的呢?

mybatis中存在二级缓存的设计:

一级缓存 :默认开启,缓存范围为sqlSession这个级别。也就是说,在一次sqlSession的处理的过程中,对同一个数据进行反复读取时,就会利用到这个缓存来提高我们程序的处理速度。

二级缓存 :要手动开始,缓存范围为mapper映射器的namespace命名空间内

下面用一个图来表述缓存的存储范围:

二级缓存的运行规则
  • 二级缓存开启后,默认所有的查询操作均使用缓存。
  • 写操作commit提交时,对该namespace命名空间下所有的缓存进行强制清空。

这么做是为了保证数据的一致性。假设第一个用户得到了一个商品的数据为婴幼儿奶粉,第二个用户把婴幼儿奶粉这个商品的名称改成了其他什么什么奶粉的名称。如果在进行写操作后不进行清空缓存,那么第一个用户再次读取那个婴幼儿奶粉的时候,读取到的数据还是婴幼儿奶粉,而不是更改后的奶粉。这样,我们得到的数据就和数据库存储的数据就不一致了。所以在用户写操作以后都会对该namespace命名空间下所有的缓存进行强制清空。

  • 通过配置useCache=false可以不用缓存。
  • 配置flushCache=true代表强制清空缓存。
案例演示

下面通过案例来体验:

1.我们先来体验一下一级缓存,以之前编写的selectById为例,在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testLv1Cache(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Goods good= sqlSession.selectOne("goods.selectById",1603);
            Goods good2= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
            System.out.println(good2.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

案例的测试方法里面有两条相同的select语句。运行后,查看日志我们会发现select语句只执行了一次。

还没完,这里只是验证了在同一个SqlSession中的情况,我们把这个方法里面的try-catch在下面复制一份。

代码语言:javascript复制
    @Test
    public void testLv1Cache(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Goods good= sqlSession.selectOne("goods.selectById",1603);
            Goods good2= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
            System.out.println(good2.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }

        try {
            sqlSession=MybatisUtils.openSession();
            Goods good= sqlSession.selectOne("goods.selectById",1603);
            Goods good2= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
            System.out.println(good2.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }

    }

这里相当于创建了2个SqlSession对象,我们运行一下,查看日志后会发现select语句执行了两次,这也说明了一级缓存范围为sqlSession这个级别:

你认为这样就完了?还没有,我们还要验证一下写操作commit提交时,对该namespace命名空间下所有的缓存进行强制清空。我们在第一个select语句后添加一行代码sqlSession.commit(),对事务进行提交。

代码语言:javascript复制
    @Test
    public void testLv1Cache(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Goods good= sqlSession.selectOne("goods.selectById",1603);
            sqlSession.commit();
            Goods good2= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
            System.out.println(good2.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

运行一下,发现缓存的确被清空了,日志显示执行了2个select语句:

2.接下来体验一下二级缓存,同样是以之前编写的selectById为例。

我们回到项目,打开goods.xml,对二级缓存进行配置。

我先写案例,后面再详细地进行解释:

在<mapper>标签内进行如下cache的配置

代码语言:javascript复制
<mapper namespace="goods">
    <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
    ·························
</mapper>

然后对上面一级缓存的测试方法进行更改:

代码语言:javascript复制
    @Test
    public void testLv1Cache(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Goods good= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }

        try {
            sqlSession=MybatisUtils.openSession();
            Goods good= sqlSession.selectOne("goods.selectById",1603);
            System.out.println(good.getTitle());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }

    }

这个案例我们创建了两个sqlsession来执行相同的查询,由于我们开启了二级缓存,所以只执行一次select语句。

下面来对二级缓存进行详细解释:

上面的案例中,我们使用<cache eviction=“LRU” flushInterval=“600000” size=“512” readOnly=“true”/>来开启二级缓存。

eviction:是缓存的清除策略,当缓存对象的数量达到上限后,自动触发对应算法对缓存进行清除。

清除策略:LUR 指的是清除最近最久未使用的数据对象。FIFO 先进先出,按对象的进入缓存的顺序来清除他们。SOFT 软引用,基于垃圾回收器状态和软引用规则移除对象。WEAK 弱引用,更积极地基于垃圾收集器状态和弱引用规则移除对象。Mybatis默认的清除策略是 LRU。

flushInterva 刷新间隔,代表相隔多长时间自动清空缓存,单位毫秒。

size当前的二级缓存保存的数据对象的上限,最多缓存多少个对象。默认值是 1024

readOnly 可以设置true或false。当设置为true时,表示返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率最高。当设置为false时,代表每次取出的是缓存对象的副本,每一次取出的对象都是不同的,这种安全性比较高。

我们可以对不同的select语句设置是否要使用缓存,只要在select语句中添加一个useCache属性为false。代表查询结果不会放到缓存。

代码语言:javascript复制
    <select id="selectById" parameterType="Integer" resultType="org.haiexijun.entity.Goods" useCache="false">
    select * from t_goods where goods_id = #{x}
    </select>

有时候,在使用完insert等更新操作的语句后,只有当我们提交了事务后,才会清除缓存。如果你需要程序在执行完insert语句后就立马清除缓存而不是等commit后才清除缓存的话,就要在insert标签中设置flushCache属性的值为true。

代码语言:javascript复制
    <insert id="insert" parameterType="org.haiexijun.entity.Goods" useGeneratedKeys="true" keyProperty="goodId" keyColumn="goodId" flushCache="true">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery,category_id)
        values(#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery},#{categoryId})
    </insert>

4.Mybatis多表级联查询

多表级联查询和多表关联查询不一样,之前说的关联查询是指两个表通过主外键在一条SQL中完成所有数据的提取,而多表级联查询通常是指通过一个对象来获取与它关联的另外一个对象,执行的SQL语句分为多条。我们都知道,在MySQL等关系型数据库中,他们都有主键和外键,数据的关系也有一对一、一对多和多对多。

例如商品与商品详情对象有一对多的关系,商品是一的一方,详情是多的一方。详情要持有商品的主键。理清楚这个关系之后,我们就可以利用Mybatis的对象关联查询来快捷的获取商品下的所有详情,或者根据某一个详情对象得到与它对应的商品信息。

我们通过案例来体验:

打开t_goods_detail表,这张表是商品的详情信息的表,里面gd_id是自动生成的id,good_id是外键,gd_pic_ur是商品的图片,gd_order代表显示的时候他的排序前后顺序。

然后在entity包下创建t_goods_detail表对应的实体类GoodsDetail:

代码语言:javascript复制
package org.haiexijun.entity;

public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;

    public Integer getGdId() {
        return gdId;
    }

    public void setGdId(Integer gdId) {
        this.gdId = gdId;
    }

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getGdPicUrl() {
        return gdPicUrl;
    }

    public void setGdPicUrl(String gdPicUrl) {
        this.gdPicUrl = gdPicUrl;
    }

    public Integer getGdOrder() {
        return gdOrder;
    }

    public void setGdOrder(Integer gdOrder) {
        this.gdOrder = gdOrder;
    }
}

之后,在resources下的mappers文件夹下创建一个goods_detail的xml文件。

代码语言:javascript复制
<?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="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer" resultType="org.haiexijun.entity.GoodsDetail">
        select * from t_goods_detail where goods_id= #{value}
    </select>
</mapper>

我们的goods表是一的一方,t_goods_detail是多的一方。所以可以对Goods实体类进行一些更改,加入一个List<GoodsDetail>集合,并赋予get和set方法,表示一个Goods商品对应多个GoodsDetail商品详情。

代码语言:javascript复制
package org.haiexijun.entity;

import java.util.List;

public class Goods {
    private Integer goodId;//商品编码
    private String title;//标题
    private String subTitle;//子标题
    private Float  originalCost;//原始的价格
    private Float currentPrice;//当前的价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮。1包邮,0不包邮
    private Integer categoryId;//分类编号
    private List<GoodsDetail> goodsDetails; //商品详情

    public List<GoodsDetail> getGoodsDetails() {
        return goodsDetails;
    }

    public void setGoodsDetails(List<GoodsDetail> goodsDetails) {
        this.goodsDetails = goodsDetails;
    }

    public Integer getGoodId() {
        return goodId;
    }

    public void setGoodId(Integer goodId) {
        this.goodId = goodId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public Float getOriginalCost() {
        return originalCost;
    }

    public void setOriginalCost(Float originalCost) {
        this.originalCost = originalCost;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }

    public Float getDiscount() {
        return discount;
    }

    public void setDiscount(Float discount) {
        this.discount = discount;
    }

    public Integer getIsFreeDelivery() {
        return isFreeDelivery;
    }

    public void setIsFreeDelivery(Integer isFreeDelivery) {
        this.isFreeDelivery = isFreeDelivery;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }
}

现在我们已经完成了对象的关联,但是呢,作为里面这个List<GoodsDetail>的数据我们并没有进行获取,它里面还是空的。未来解决这个问题,我们还要在goods.xml中对这个List<GoodsDetail>进行说明。

代码语言:javascript复制
    <resultMap id="rmGoods1" type="org.haiexijun.entity.Goods">
        <id column="goods_id" property="goodId"></id>
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
    </resultMap>

    <select id="selectOneToMany" resultMap="rmGoods1">
    select * from t_goods limit 0,1
    </select>

上面编写了一个<select> ,并指定了resultMap为上面定义的rmGoods1。在对象关联的情况下,我们是通过resultMap来实现的。resultMap里面的collection标签的作用是在 select * from t_goods where goods_id=1 得到结果后,,然后对所有的Goods对象遍历得到goods_id字段值,并带入到goodsDetails命名空间的findByGoodsId的sql中查询,最后将得到的”商品详情“集合赋值给List<GoodsDetail>。

我们不要忘记在mybatis-config.xml这个总配置文件中注册 goods_detail.xml

代码语言:javascript复制
    <mappers>
        <mapper resource="mappers/goods.xml"/>
        <mapper resource="mappers/goods_detail.xml"/>
    </mappers>

在MybatisTest测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testSelectOneToMany(){
        SqlSession session=null;
        try {
            session=MybatisUtils.openSession();
             Goods goods= session.selectOne("goods.selectOneToMany");
			 List<GoodsDetail> gd=goods.getGoodsDetails();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

上面这个案例我们实现了一对多的查询。

下面我要来体验一下多对一的查询。如果我得到了一个GoodsDetail描述对象,想得到相对应的商品信息。就要使用Mybatis的多对一对象关联查询了。多的一方想要得到一的一方,只要在多的一方增加一的一方的实体就可以了。

在GoodsDetail类里面加入Goods属性,同时设置get和set方法。

代码语言:javascript复制
package org.haiexijun.entity;

public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;
    private Goods goods;

    public Goods getGoods() {
        return goods;
    }

    public void setGoods(Goods goods) {
        this.goods = goods;
    }

    public Integer getGdId() {
        return gdId;
    }

    public void setGdId(Integer gdId) {
        this.gdId = gdId;
    }

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getGdPicUrl() {
        return gdPicUrl;
    }

    public void setGdPicUrl(String gdPicUrl) {
        this.gdPicUrl = gdPicUrl;
    }

    public Integer getGdOrder() {
        return gdOrder;
    }

    public void setGdOrder(Integer gdOrder) {
        this.gdOrder = gdOrder;
    }
}

上一步增加了Goods属性,但是Mybatis并不直到它是从哪来的。我们需要打开goods_detail.xml进行关联的描述:

代码语言:javascript复制
    <resultMap id="rmGoodsDetail" type="org.haiexijun.entity.GoodsDetail">
        <id column="gd_id" property="gdId"></id>
        <association column="good_id" property="goods" select="goods.selectById" ></association>
    </resultMap>
    <select id="selectManyToOne" resultMap="rmGoodsDetail">
        select * from t_goods_detail limit 0,1
    </select>

编写测试方法:

代码语言:javascript复制
   @Test
    public void testSelectManyTOne(){
        SqlSession session=null;
        try {
            session=MybatisUtils.openSession();
            GoodsDetail gd= session.selectOne("goodsDetail.selectManyToOne");
            System.out.println(gd);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

5.分页插件----PageHelper

PageHelper是一款由中国人开发的Mybatis分页插件,该分页插件支持任何复杂的单表、多表分页。PageHelper官网:https://pagehelper.github.io/

因为这个插件的官方文档非常的详细和清晰,所以下面只做一个简单的案例来体验一下这个插件,以后如果有其他的需求,就去参考官方文档。

PageHelper的使用流程

1.引入maven依赖PageHelp和jsqlparser

代码语言:javascript复制
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.0</version>
</dependency>

2.mybatis-config.xml增加Plugin配置

代码语言:javascript复制
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

3.代码中使用PageHelper.startPage()来完成自动分页

先在goods.xml中编写一条查询语句:

代码语言:javascript复制
    <select id="selectPage" resultType="org.haiexijun.entity.Goods">
        select * from t_goods where current_price &lt; 1000
    </select>

然后再测试类中编写测试方法使用:

代码语言:javascript复制
    @Test
    public void testSelectPage(){
        SqlSession session=null;
        try {
            session=MybatisUtils.openSession();
            //开启分页,startPage方法会自动将下一次查询进行分页,第一个参数为第几页的数据,第二个参数为每一页有多少行
            //下面查询第10-20条数据
            PageHelper.startPage(2,10);
            //下面这个查询要注意,以前返回的是一个List集合,但这里返回的是一个配置对象,包含分页数据和分页信息
           Page<Goods> page =(Page) session.selectList("goods.selectPage");
            System.out.println("总页数:" page.getPages());
            System.out.println("总记录数:" page.getTotal());
            System.out.println("开始行号:" page.getStartRow());
            System.out.println("结束行号:" page.getEndRow());
            System.out.println("当前页码:" page.getPageNum());
            //当前页数据
            List<Goods> data=page.getResult();
            for (Goods g:data){
                System.out.println(g.getTitle());
            }
            System.out.println("");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

6.Mybatis配置C3P0连接池

关于C3P0连接池的基本使用,我之前有写过一篇博客:C3P0连接池的基本配置与使用 , 里面有比较详细的基本使用步骤。

不过这里还是会教大家具体的使用步骤的。

1.再pom.xml中导入C3P0连接池需要的依赖(c3p0-0.9.1.2.jar和mchange-commons-java.jar)

代码语言:javascript复制
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

2.在org.haiexijun包下创建一个dataSource文件夹,里面创建一个C3P0DataSourceFactory类,这个类是我们自定义C3P0的数据源工厂。创建好了以后,为了让Mybatis对他有良好的支持,这个类需要继承自一个父类UnpooledDataSourceFactory,虽然这个父类名叫UnpooledDataSourceFactory,但请不要被这个父类名给迷惑误解了,通过继承这个类可以完成C3P0的嵌入工作。

代码语言:javascript复制
package org.haiexijun.dataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

/**
 * C3P0与Mybatis兼容使用的数据源工厂类
 */
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
    public C3P0DataSourceFactory(){
        //让数据源指向对应的连接池
        this.dataSource=new ComboPooledDataSource();
    }

}

3.更改mybatis-config.xml中的dataSources的type,由之前的POOLEDED改为我们C3P0连接池。这里还要注意一个点,dataSource里面的属性property的有些配置和C3P0的有些不一样,我们需要对其进行更改。

代码语言:javascript复制
            <!--采用连接池的方式管理数据库连接-->
            <dataSource type="org.haiexijun.dataSource.C3P0DataSourceFactory">
                <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun"/>
                <property name="user" value="root"/>
                <property name="password" value="zc20020106"/>
                <property name="initialPoolSize" value="5"/>
                <property name="maxPoolSize" value="20"/>
            </dataSource>

3.最后,就可以使用C3P0了

7.Mybatis批处理

批处理其实就是批量处理的意思,之前的案例我们的增删改查都是一条一条地被处理的。但实际工作中,很多情况下,一条一条去处理,效率是很慢的。比方说,假设我有10万条数据,按照之前的语句一条一条来处理,效率可想而知。这一节,给大家带来一个实用的技巧,利用集合保存批处理的数据,再利用批处理SQL一次性完成,这样可以很大程度地提高我们程序地执行效率。

下面我们直接使用代码来说明:

批量插入

先在goods.xml中新增加一个insert标签

代码语言:javascript复制
    <insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyColumn="goodId" keyProperty="goodId">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        values
        <!--foreach子标签专门对parameter传入参数中地list集合进行遍历-->
        <!--item是循环中集合的一项数据,index索引为第几次循环。separator为循环分割符-->
        <foreach collection="list" item="item" index="index" separator=",">
            (#{item.title},#{item.subTitle},#{item.originalCost},#{item.currentPrice},#{item.discount},#{item.isFreeDelivery},#{item.categoryId})
        </foreach>
    </insert>

大多数数据库都有这样的插入语法:

INSERT INTO table(字段,字段,字段,…)

VALUES

(a1,b2,c2,…) ,

(a2,b2,c2,…) ,

····

上面编写批处理时,用的就是这个语法。

然后在测试类中编写测试方法:

代码语言:javascript复制
    @Test
    public void testBatchInsert(){
        SqlSession session=null;
        try {
            long startTime=new Date().getTime();
            session=MybatisUtils.openSession();
            List list=new ArrayList();
            for (int i = 0; i < 10000; i  ) {
                Goods goods=new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试商品子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                list.add(goods);
            }
            session.insert("goods.batchInsert",list);
            session.commit();
            long endTime=new Date().getTime();
            System.out.println("执行时间:" (endTime-startTime) "毫秒");

        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

上面的批量插入数据生成的SQL太长了,可能会被服务器拒绝。假设公司有一千万条数据需要导入,那上面的写法肯定是不行的。我们可以采用分段的方式,把一千万条数据,拆分成一千组一万条数据来执行。数据分段,可以同过for循环二次嵌套来完成。 很简单,这里不做演示了。

批量删除

思路:

我们之前写的删除语句是delete from t_goods where goods_id = #{value}; 删除一个具体的数据。

现在要执行批量删除的话,需要对语句进行更改,然后同样要使用foreach:

代码语言:javascript复制
    <delete id="batchDelete" parameterType="java.util.List">
        delete from t_goods where goods_id in
        <!--open设置以什么开始,close设置以什么结束-->
        <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </delete>

编写一个测试方法进行测试:

代码语言:javascript复制
    @Test
    public void testBatchDelete(){
        SqlSession session=null;
        try {
            long startTime=new Date().getTime();
            session=MybatisUtils.openSession();
            List list=new ArrayList();
            for (int i = 1929; i < 11929; i  ) {
                list.add(i);
            }
            session.delete("goods.batchDelete",list);
            session.commit();
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(session);
        }
    }

8.Mybatis注解开发

设计初期的 MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,映射语句也是定义在 XML 中的。而在 MyBatis 3 中,提供了其它的配置方式。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。它是 XML 和注解配置的基础。注解提供了一种简单且低成本的方式来实现简单的映射语句。虽然注解方便,但是Java 注解的表达能力和灵活性十分有限,很多强大的映射并不能用注解来构建。而且对于复杂的逻辑,注解就无法完成了。注解也不太利于线上维护。所以注解缺点其实是挺多的。

下面列举了Mybatis常用的注解:

注解

对应的xml

说明

@Insert

<insert>

插入SQL

@Update

<update>

更新SQL

@Delete

<delete>

删除SQL

@Select

<select>

查询SQL

@Param

参数映射

@Results

<resultMap>

结果映射

@Result

<id><result>

字段映射

这里只是部分注解,如果想了解更多,请参考:https://mybatis.org/mybatis-3/zh/java-api.html

下面来写一个程序,来简单地演示:

实现我们要说明,如果使用注解,我们是不需要使用Mapper的xml文件的,取而代之,我们要创建名为Dao的包,在包中创建一系列的接口。比如GoodsDao,我们利用这些接口加上注解来替代原本的xml文件。

代码语言:javascript复制
package org.haiexijun.dao;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.haiexijun.entity.Goods;

import java.util.List;

public interface GoodsDAO {
    @Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}")
    public List<Goods> selectByPriceRange(@Param("min") Float min,@Param("max") Float max, @Param("limit") Integer limit);
}

然后在mybatis-config.xml中把dao到入mappers中:

有两种写法

代码语言:javascript复制
	<mappers>
        <mapper class="org.haiexijun.dao.GoodsDAO"/>
    </mappers>

或者:

代码语言:javascript复制
    <mappers>
       <package name="org.haiexijun.dao"/>
   </mappers>

这两种写法都行,二选一即可。package更实用,因为当dao包下有非常多的dao的时候,如果使用第一种方式导入的话,要写非常多的代码,太麻烦了。直接导入整个dao包不香吗?

到这里,就可以利用这个接口来完成数据查询的操作了。

下面编写测试方法:

代码语言:javascript复制
    @Test
    public void testAnnotationSelect(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            GoodsDAO goodsDAO= sqlSession.getMapper(GoodsDAO.class);
             List<Goods> list=goodsDAO.selectByPriceRange(100f,500f,20);
            System.out.println(list.size());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

上面是用注解来实现查询,下面再举一个例子来实现一下新增数据

和之前一样,在GoodsDao接口里面定义方法

代码语言:javascript复制
package org.haiexijun.dao;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import org.haiexijun.entity.Goods;

import java.util.List;

public interface GoodsDAO {

    @Insert("insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery,category_id)values(#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery},#{categoryId})")
    @SelectKey(statement = "select last_insert_id()",before = false,keyProperty = "goodId",resultType =Integer.class)
    public int insert(Goods goods);

}

然后编写测试类来对他进行测试:

代码语言:javascript复制
    @Test
    public void testAnnotationInsert(){
        SqlSession sqlSession=null;
        try {
            sqlSession=MybatisUtils.openSession();
            Goods goods=new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试商品的子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            GoodsDAO goodsDAO=sqlSession.getMapper(GoodsDAO.class);
            goodsDAO.insert(goods);
            sqlSession.commit();
        }catch (Exception e){
            if (sqlSession!=null){
                sqlSession.rollback();
            }
            e.printStackTrace();
        }finally {
            MybatisUtils.closeSession(sqlSession);
        }
    }

对于其他的用法,需要用时,再去学习就好。

四.完结感言

敲了一个多礼拜,终于把这篇博客给敲出来了。真的很不容易呀!这篇博客到这里已经将近5万字了,真的佩服自己的毅力。但是我知道,学无止境,我肯定还有很多知识点没有概括到。路过的大佬,如果我有什么重要的知识点遗漏了,希望能在评论区告诉我,感谢你。如果发现有错别字,也欢迎指出。这篇博客的案例代码我放在GitHub上了案例代码。

感谢大家的点赞和关注!真心希望这篇博客对大家的Mybatis学习能有所帮助。

再见!

0 人点赞