开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

admin 2024年7月29日16:52:00评论13 views字数 81912阅读273分2秒阅读模式

MyBatis 基本使用总结 && 手动实现 MyBatis

传统的DB操作图 && 它的问题:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

MyBatis 使用方法

看到上面传统操作DB的问题后, 我们看一下MyBatis带给了什么便利:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

MyBatis 官方文档: https://mybatis.org/mybatis-3/zh_CN/index.html

MyBatis 基本使用 (使用插入做演示)

创建所需库 && 表

首先我们创建MyBatis库以及MyBatis表, 如下:

CREATE DATABASE `mybatis`;USE `mybatis`;CREATE TABLE `monster`(    `id` INT NOT NULL AUTO_INCREMENT,    `age` INT NOT NULL,    `birthday` DATE DEFAULT NULL,    `email` VARCHAR(255) NOT NULL,    `gender` TINYINT NOT NULL,    `name` VARCHAR(255) NOT NULL,    `salary` DOUBLE NOT NULL,    PRIMARY KEY(`id`))CHARSET=utf8;

定义 父 && 子 模块

随后我们创建一个父项目, 例如:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis创建起来父项目后, 配置pom.xml, 那么它的所有子模块就会使用父项目的环境, 定义如下 pom.xml, 让其所有子模块应用该模块.

<dependencies>    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>8.0.25</version>    </dependency>    <dependency>        <groupId>org.mybatis</groupId>        <artifactId>mybatis</artifactId>        <version>3.5.6</version>    </dependency>    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>4.7</version>        <scope>compile</scope>    </dependency></dependencies>

接下来按照如下步骤创建子模块:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis当我们创建成功后, 我们的父模块的pom.xml文件中会增加如下定义:

<modules>    <module>MyBatis01</module></modules>

而子模块中的pom.xml文件中会增加如下配置:

<parent>    <groupId>org.example</groupId>    <artifactId>My</artifactId>    <version>1.0-SNAPSHOT</version></parent>

明显是一个父模块 指向 子模块以及子模块 确认 父模块的关系.

配置 mybatis-config.xml 文件

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "https://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC"/> <!-- 配置事务管理器 -->            <dataSource type="POOLED"> <!-- 配置数据源 -->                <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <!-- 配置数据库驱动 -->                <property name="url"                          value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>                <!-- 配置链接的 URL                    jdbc:mysql: 表明 JDBC 链接 MYSQL                    127.0.0.1:3306 指明 IP 以及 MYSQL 服务器端口号                    mybatis 选择指定数据库                    & 表示 & 的转义符                    useUnicode 使用 UNICODE 编码, characterEncoding 指定字符集放置乱码                -->                <property name="username" value="root"/>                <property name="password" value="toor"/>            </dataSource>        </environment>    </environments><!--    <mappers>--><!--        <mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <!-- 暂时注销 --><!--    </mappers>--></configuration>

这里要注意的是, 要放到类路径下, 如图:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

根据对应表创建 JavaBean

public class Monster {    /*    * mysql> desc monster;    +----------+--------------+------+-----+---------+----------------+    | Field    | Type         | Null | Key | Default | Extra          |    +----------+--------------+------+-----+---------+----------------+    | id       | int(11)      | NO   | PRI | NULL    | auto_increment |    | age      | int(11)      | NO   |     | NULL    |                |    | birthday | date         | YES  |     | NULL    |                |    | email    | varchar(255) | NO   |     | NULL    |                |    | gender   | tinyint(4)   | NO   |     | NULL    |                |    | name     | varchar(255) | NO   |     | NULL    |                |    | salary   | double       | NO   |     | NULL    |                |    +----------+--------------+------+-----+---------+----------------+    7 rows in set (0.00 sec)    * */    private Integer id;    private Integer age;    private LocalDateTime birthday; // 如果Mysql驱动是8.0以下, 那么使用 Date    private String email;    private Integer gender;    private String name;    private Double salary;    // setter && getter && 有参构造 && 无参构造 && toString方法}

创建 MonsterMapper 接口 && 创建 MonsterMapper SQL 文件

定义MonsterMapper接口如下:

public interface MonsterMapper {    public void addMonster(Monster monster); // 定义一个增加 Monster 对象的功能}

定义MonsterMapper.xml文件如下:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.heihu577.Mapper.MonsterMapper"> <!-- namespace 用于指定 接口 -->    <insert id="addMonster" parameterType="com.heihu577.Beans.Monster">        <!--            id 用于指定接口中所声明的方法, 这里对应上了 public void addMonster(Monster monster);            parameterType 用于指定参数中所指定的 Bean        -->        INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`)        VALUES(#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})        <!--            SQL 语句中的 #{age} 是用于指定 传递进来的 Monster 对象的 age 属性的具体值        -->    </insert></mapper>

定义完毕之后, 此时我们需要打开mybatis-config.xml文件中的mappers标签并进行配置如下:

<mappers>    <mapper resource="com/heihu577/Mapper/MonsterMapper.xml"/>  <!-- 指明所定义的XML-SQL文件 --></mappers>

创建 MyBatis 工具类

定义工具类如下:

public class MyBatisUtils {    private static SqlSessionFactory sqlSessionFactory;    // 初始化 SqlSessionFactory    static {        // 指定资源文件, 配置文件        String resource = "mybatis-config.xml";        try {            InputStream resourceAsStream = Resources.getResourceAsStream(resource); // 注意是 org.apache.ibatis.io 包下的 Resources 类            sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);        } catch (IOException e) {            throw new RuntimeException(e);        }    }    public static SqlSession getSqlSession() {        return sqlSessionFactory.openSession();    }}

最终测试结果

public class MonsterMapperTest {    private SqlSession sqlSession;    private MonsterMapper monsterMapper;    @Before // 加入 junit 单元测试中的 Before 注解, 在 Test 方法测试前会进行执行    public void init() {        sqlSession = MyBatisUtils.getSqlSession();        MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);        System.out.println(mapper);    }    @Test    public void t1() {        System.out.println("T1");        /*            最终报错:            ### The error may exist in com/heihu577/Mapper/MonsterMapper.xml            ### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/heihu577/Mapper/MonsterMapper.xml        */    }}
* 报错问题解决

其报错终极原因则是, 我们使用Resources.getResourceAsStream读取的目录是class类目录, 而默认Maven不会将我们.java目录下的文件输出到.class目录中, 所以在这里我们需要增加一个Maven配置项:

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

注意放入到properties标签之后, 声明完毕后, 我们进行Maven-clean-install即可运行成功, 最终运行结果:

org.apache.ibatis.binding.MapperProxy@cd2dae5T1
测试插入方法
public class MonsterMapperTest {    private SqlSession sqlSession;    private MonsterMapper monsterMapper;    @Before // 加入 junit 单元测试中的 Before 注解, 在 Test 方法测试前会进行执行    public void init() {        sqlSession = MyBatisUtils.getSqlSession();        monsterMapper = sqlSession.getMapper(MonsterMapper.class);    }    @Test    public void t1() {        monsterMapper.addMonster(new Monster(1, 1, LocalDateTime.now(), "[email protected]", 1, "张三", 120.));        /*            mysql> SELECT * FROM monster;            Empty set (0.00 sec)            mysql>            mysql> SELECT * FROM monster;            +----+-----+------------+-----------------+--------+--------+--------+            | id | age | birthday   | email           | gender | name   | salary |            +----+-----+------------+-----------------+--------+--------+--------+            |  1 |   1 | 2024-04-13 | [email protected] |      1 | 张三   |    120 |            +----+-----+------------+-----------------+--------+--------+--------+            1 row in set (0.10 sec)        */    }}

MyBatis 使用说明图

开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

Insert, Update, Delete

我们可以根据官网https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html#insert_update_and_delete, 可以看到:

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以根据语句中实际传入的参数计算出应该使用的类型处理器(TypeHandler),默认值为未设置(unset)。
parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

插入操作 - 获得自增长 ID

其中useGeneratedKeys以及keyProperty可以进行设置得到自增长ID, 如下:

<insert id="addMonster" parameterType="com.heihu577.Beans.Monster" useGeneratedKeys="true" keyProperty="id">    INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`)    VALUES(#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})</insert>

增加两个属性之后, 我们可以看到结果如下:

@Testpublic void t3() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster = new Monster();    monster.setAge(99);    monster.setEmail("[email protected]");    monster.setBirthday(LocalDateTime.now());    monster.setGender(20);    monster.setName("gogogo");    monster.setSalary(120.);    mapper.addMonster(monster);    System.out.println("增加的对象: " + monster);    System.out.println("自增长ID: " + monster.getId());    /*    增加的对象: Monster{id=4, age=99, birthday=2024-04-14T14:50:23.474, email='[email protected]', gender=20, name='gogogo', salary=120.0}    自增长ID: 4    */}

删除操作 (事务管理)

MonsterMapper接口中定义如下方法声明:

public void deleteMonster(Integer id); // 根据 ID 号进行删除

随后在MonsterMapper.xml文件中进行实现:

<delete id="deleteMonster" parameterType="java.lang.Integer">    DELETE FROM `monster` WHERE `id` = #{id}</delete>

随后我们将我们的表修改为Innodb, 这样MyBatis需要进行提交事务才可以成功生效, 创建测试代码:

@Testpublic void t4() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    mapper.deleteMonster(1);    sqlSession.commit(); // 提交    sqlSession.close(); // 删除    System.out.println("删除成功!");}

修改操作

MonsterMapper接口中定义如下方法定义:

public void updateMonsterById(Monster monster); // 通过 Monster 修改值

随后我们定义MonsterMapper.xml中, 实现该接口:

<update id="updateMonsterById" parameterType="com.heihu577.Beans.Monster">    UPDATE `monster`    SET `age` = #{age}, `birthday` = #{birthday}, `email` = #{email},    `gender` = #{gender}, `name` = #{name}, `salary` = #{salary}    WHERE `id` = #{id}</update>

定义测试方法:

@Testpublic void t5() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    /*    +----+-----+------------+------------------+--------+-----------+--------+    | id | age | birthday   | email            | gender | name      | salary |    +----+-----+------------+------------------+--------+-----------+--------+    |  2 |   0 | 2024-04-14 | [email protected] |      2 | 张三: 0   |    120 |    +----+-----+------------+------------------+--------+-----------+--------+    * */    Monster monster = new Monster();    monster.setId(2);    monster.setName("李四");    monster.setEmail("[email protected]");    monster.setBirthday(LocalDateTime.now());    monster.setAge(30);    monster.setSalary(200.);    monster.setGender(3);    mapper.updateMonsterById(monster);    sqlSession.commit();    sqlSession.close();    /*    *    *   +----+-----+------------+------------------+--------+-----------+--------+        | id | age | birthday   | email            | gender | name      | salary |        +----+-----+------------+------------------+--------+-----------+--------+        |  2 |  30 | 2024-04-14 | [email protected]      |      3 | 李四      |    200 |        +----+-----+------------+------------------+--------+-----------+--------+    * */}

设置 MyBatis 别名

在我们前面定义mapper中, 属性名称type都是写的类全路径, 这样很麻烦, 这里我们可以在mybatis-config.xml文件中进行配置一个别名, 如下:

<typeAliases>    <typeAlias type="com.heihu577.Beans.Monster" alias="Monster"/></typeAliases>

定义完毕之后, 我们在MonsterMapper.xml文件中就可以进行使用, 如下:

<insert id="addMonster" parameterType="Monster" useGeneratedKeys="true" keyProperty="id">    <!-- parameterType 进行了简写 -->    INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`)    VALUES(#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})</insert>

SELECT 查询操作

根据ID返回Bean

MonsterMapper中定义如下方法签名:

public Monster getMonsterById(Integer id); // 传入 ID, 得到 Monster 对象

随后在MonsterMapper.xml文件中进行实现:

<select id="getMonsterById" resultType="Monster" parameterType="java.lang.Integer">    SELECT * FROM `Monster` WHERE `ID` = #{id}</select>

测试结果:

@Testpublic void t6() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster = mapper.getMonsterById(2);    System.out.println(monster);    // Monster{id=2, age=30, birthday=2024-04-14T00:00, email='[email protected]', gender=3, name='李四', salary=200.0}}

查询出所有Bean

MonsterMapper中定义如下方法声明:

public List<Monster> getAllMonsters();

MonsterMapper.xml文件中进行实现该方法:

<select id="getAllMonsters" resultType="Monster">    SELECT * FROM `Monster`</select>

最终测试结果:

@Testpublic void t7() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    List<Monster> allMonsters = mapper.getAllMonsters();    System.out.println(allMonsters);    /*    * [Monster{id=2, age=30, birthday=2024-04-14T00:00, email='[email protected]', gender=3, name='李四', salary=200.0}, Monster{id=3, age=1, birthday=2024-04-14T00:00, email='[email protected]', gender=2, name='张三: 1', salary=120.0}, Monster{id=4, age=99, birthday=2024-04-14T00:00, email='[email protected]', gender=20, name='gogogo', salary=120.0}, Monster{id=6, age=12, birthday=2024-04-14T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}]    * */}

配置 MyBatis 日志输出

用于查看MyBatis向数据库发送了什么内容信息, 在mybatis-config.xml文件中进行配置:

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

我们可以看到如下结果:

@Testpublic void t8() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    List<Monster> allMonsters = mapper.getAllMonsters();    /*    * ==>  Preparing: SELECT * FROM `Monster`    ==> Parameters:    <==    Columns: id, age, birthday, email, gender, name, salary    <==        Row: 2, 30, 2024-04-14, [email protected], 3, 李四, 200.0    <==        Row: 3, 1, 2024-04-14, [email protected], 2, 张三: 1, 120.0    <==        Row: 4, 99, 2024-04-14, [email protected], 20, gogogo, 120.0    <==        Row: 6, 12, 2024-04-14, [email protected], 1, 张三, 120.0    <==      Total: 4    * */    System.out.println(allMonsters);}

手动实现 MyBatis 机制

MyBatis 核心框架图

开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

具体实现步骤

我们创建一个子项目即可, 随后我们按照步骤依次操作.

搭建基础环境

导入具体依赖项
<dependencies>    <!-- 用于读取 XML 配置 -->    <dependency>        <groupId>dom4j</groupId>        <artifactId>dom4j</artifactId>        <version>1.6.1</version>    </dependency>    <!-- MySQL -->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>8.0.25</version>    </dependency>    <!-- 根据 bean, 自动生成 getter && setter -->    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.20</version>    </dependency></dependencies>
创建对应的库 && 表
CREATE DATABASE `heihu_mybatis`;USE `heihu_mybatis`CREATE TABLE `monster` (    `id` INT NOT NULL AUTO_INCREMENT,    `age` INT NOT NULL,    `birthday` DATE DEFAULT NULL,    `email` VARCHAR(255) NOT NULL,    `gender` TINYINT NOT NULL,    `name` VARCHAR(255) NOT NULL,    `salary` DOUBLE NOT NULL,    PRIMARY KEY (`id`)) CHARSET=utf8; -- 目前默认还是 MyIsAm 引擎INSERT INTO `monster` VALUES(NULL, 200, '2000-11-11', '[email protected]', 1, '牛魔王', 8888.88);
实现思路图
开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

读取配置文件, 建立连接

我们可以看到如下图, 接下来我们要完成读取 XML 文件 -> 建立链接的操作.开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

创建 heihu_mybatis.xml 文件

resources目录下定义heihu_mybatis.xml文件, 并且文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?><database>    <!-- 配置数据库链接信息 --> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>    <!-- 配置链接数据库的 URL --> <property name="url" value="jdbc:mysql://127.0.0.1:3306/heihu_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>    <!-- 用户名 --> <property name="username" value="root"/>    <!-- 密码 --> <property name="password" value="root"/></database>

当然了, 定义该文件是用于被读取的, 所以下面我们需要定义一个HeihuConfiguration类, 专门用于XML文件处理.

创建 com.heihu577.SqlSession.HeihuConfiguration

定义如下代码:

public class HeihuConfiguration {    private static ClassLoader loader = ClassLoader.getSystemClassLoader(); // 定义类加载器, 可以进行读取文件    private SAXReader saxReader = new SAXReader(); // 用于读取 XML 文件的工具    /* 传入 xml 数据库配置文件, 返回一个 Connection */    public Connection build(String xmlFile) {        System.out.println(">> HeihuConfiguration::build 进行读取 " + xmlFile + " 配置文件, 并返回一个链接");        Connection result = null;        InputStream xmlInputStream = loader.getResourceAsStream(xmlFile); // 读取外部传递进来的配置文件        try {            Document document = saxReader.read(xmlInputStream);            Element rootElement = document.getRootElement(); // 读取 <database> 标签, 结果: org.dom4j.tree.DefaultElement@73a8dfcc [Element: <database attributes: []/>]            result = getConnectionByElement(rootElement); // 得到 Connection        } catch (DocumentException e) {            throw new RuntimeException(e);        }        return result;    }}

那么, 接下来我们定义一个getConnectionByElement方法, 专门用于处理<database>标签的解析, 代码变为如下:

public class HeihuConfiguration {    private static ClassLoader loader = ClassLoader.getSystemClassLoader(); // 定义类加载器, 可以进行读取文件    private SAXReader saxReader = new SAXReader(); // 用于读取 XML 文件的工具    /* 传入 xml 数据库配置文件, 返回一个 Connection */    public Connection build(String xmlFile) {        System.out.println(">> HeihuConfiguration::build 进行读取 " + xmlFile + " 配置文件, 并返回一个链接");        Connection result = null;        InputStream xmlInputStream = loader.getResourceAsStream(xmlFile); // 读取外部传递进来的配置文件        try {            Document document = saxReader.read(xmlInputStream);            Element rootElement = document.getRootElement(); // 读取 <database> 标签            result = getConnectionByElement(rootElement); // 得到 Connection        } catch (DocumentException e) {            throw new RuntimeException(e);        }        return result;    }    /* 根据XML根节点, 读取数据库配置并返回 */    public Connection getConnectionByElement(Element rootElement) {        System.out.println(">> HeihuConfiguration::getConnectionByElement 进行读取 " + rootElement.getName() + " 标签, " +                "并提取数据库配置, 返回 Connection");        Connection result = null;        String dirverClass = null, url = null, username = null, password = null;        List<Element> elements = rootElement.elements();        for (Element element : elements) {            if (!"property".equals(element.getName())) {                continue;            }            String nameType = element.attributeValue("name");            switch (nameType) {                case "driverClass":                    dirverClass = element.attributeValue("value");                    break;                case "url":                    url = element.attributeValue("value");                    break;                case "username":                    username = element.attributeValue("value");                    break;                case "password":                    password = element.attributeValue("value");                    break;            }        }        System.out.println("tdirverClass -> " + dirverClass);        System.out.println("turl -> " + url);        System.out.println("tusername -> " + username);        System.out.println("tpassword -> " + password);        try {            Class.forName(dirverClass);            result = DriverManager.getConnection(url, username, password);        } catch (ClassNotFoundException e) {            throw new RuntimeException(e);        } catch (SQLException e) {            throw new RuntimeException(e);        }        return result;    }}

在当前类创建Junit测试方法, 代码如下:

@Testpublic void testBuild() {    HeihuConfiguration heihuConfiguration = new HeihuConfiguration();    Connection connection = heihuConfiguration.build("mybatis_config.xml");    System.out.println(connection);    /*        >> HeihuConfiguration::build 进行读取 mybatis_config.xml 配置文件, 并返回一个链接        >> HeihuConfiguration::getConnectionByElement 进行读取 database 标签, 并提取数据库配置, 返回 Connection            dirverClass -> com.mysql.cj.jdbc.Driver            url -> jdbc:mysql://127.0.0.1:3306/heihu_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8            username -> root            password -> root        com.mysql.cj.jdbc.ConnectionImpl@691a7f8f    */}

可以看到, 通过传入配置文件名称, 可以成功得到Connection链接.

编写执行器, 发送 SQL 语句, 进行测试

当然了, 我们可以得到Connection, 那么我们就可以发送SQL语句, 接下来我们完成如下部分:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

创建 JavaBean (使用 lombok 自动生成 setter && getter && toString && 有参构造 && 无参构造)

这里我们针对 Monster 表进行创建一个 JavaBean, 放置在com.heihu577.Beans包包中, 代码如下:

@Getter // lombok 下的 注解@Setter // 加入这些注解后, 会自动生成 getter && setter && toString && 无参构造 && 有参构造@ToString@NoArgsConstructor@AllArgsConstructorpublic class Monster {    /*    *   +----------+--------------+------+-----+---------+----------------+        | Field    | Type         | Null | Key | Default | Extra          |        +----------+--------------+------+-----+---------+----------------+        | id       | int(11)      | NO   | PRI | NULL    | auto_increment |        | age      | int(11)      | NO   |     | NULL    |                |        | birthday | date         | YES  |     | NULL    |                |        | email    | varchar(255) | NO   |     | NULL    |                |        | gender   | tinyint(4)   | NO   |     | NULL    |                |        | name     | varchar(255) | NO   |     | NULL    |                |        | salary   | double       | NO   |     | NULL    |                |        +----------+--------------+------+-----+---------+----------------+        7 rows in set (0.01 sec)    * */    private Integer id;    private Integer age;    private LocalDate birthday;    private String email;    private Integer gender;    private String name;    private Double salary;}

当然, 如果感觉依次加这些注解比较麻烦, 我们还可以直接声明@Data来进行代替, 例如:

@Data // 代替上面那么多声明, 但是 Data 注解不包含有参构造器, 所以我们需要增加声明@AllArgsConstructor // 如果只填写了 AllArgsConstructor, 那么默认的无参构造器会被覆盖, 所以在这里我们还需要定义一个无参构造器@NoArgsConstructorpublic class Monster {    /*    *   +----------+--------------+------+-----+---------+----------------+        | Field    | Type         | Null | Key | Default | Extra          |        +----------+--------------+------+-----+---------+----------------+        | id       | int(11)      | NO   | PRI | NULL    | auto_increment |        | age      | int(11)      | NO   |     | NULL    |                |        | birthday | date         | YES  |     | NULL    |                |        | email    | varchar(255) | NO   |     | NULL    |                |        | gender   | tinyint(4)   | NO   |     | NULL    |                |        | name     | varchar(255) | NO   |     | NULL    |                |        | salary   | double       | NO   |     | NULL    |                |        +----------+--------------+------+-----+---------+----------------+        7 rows in set (0.01 sec)    * */    private Integer id;    private Integer age;    private LocalDate birthday;    private String email;    private Integer gender;    private String name;    private Double salary;}
编写 HeihuExecutor

定义Executor接口如下:

public interface Executor {    public String XML_FILE = "mybatis_config.xml"; // 定义配置文件路径    public <T> T query(String sql, Object parameter, Class<T> clazz); // SQL 语句, 所需参数, 注入的 BEAN 类型}

对其进行实现:

public class HeihuExecutor implements Executor {    private HeihuConfiguration heihuConfiguration = new HeihuConfiguration();    @Override    public <T> T query(String sql, Object parameter, Class<T> clazz) { // 传入 ABC.class, 编译器就会知道返回的是 ABC 类型        System.out.println(">> HeihuExecutor::query 发送 SQL 语句");        System.out.println("tSQL: " + sql);        System.out.println("tParameter: " + parameter);        Connection connection = heihuConfiguration.build(XML_FILE); // 读取 XML 文件, 并得到 Connection 链接        Object result = null;        try {            PreparedStatement preparedStatement = connection.prepareStatement(sql);            preparedStatement.setString(1, String.valueOf(parameter));            ResultSet resultSet = preparedStatement.executeQuery();            ResultSetMetaData metaData = resultSet.getMetaData(); // 获得列名称            int columnCount = metaData.getColumnCount(); // 得到列的数量            result = clazz.newInstance();            resultSet.next();            for (int i = 1; i <= columnCount; i++) {                String columnName = metaData.getColumnName(i); // 获得每一列名称                Field[] declaredFields = clazz.getDeclaredFields();                for (Field declaredField : declaredFields) { // 使用列名称与JavaBean属性名称进行匹配                    if (columnName.equals(declaredField.getName())) { // 匹配成功后进行封装 BEAN                        declaredField.setAccessible(true);                        Object value = resultSet.getObject(declaredField.getName()); // 拿出查询出的结果                        if(value instanceof Date){                            value = ((Date) value).toLocalDate(); // Date 需要进行转换                        }                        declaredField.set(result, value); // 放置到 JavaBean 中                    }                }            }        } catch (SQLException e) {            throw new RuntimeException(e);        } catch (InstantiationException e) {            throw new RuntimeException(e);        } catch (IllegalAccessException e) {            throw new RuntimeException(e);        }        return (T) result; // 将封装好的 BEAN 进行返回    }}
测试结果

在本类增加如下测试方法:

@Testpublic void test() {    HeihuExecutor heihuExecutor = new HeihuExecutor();    Monster monster = heihuExecutor.query("SELECT * FROM `monster` WHERE `id` = ?", 1, Monster.class);    System.out.println(monster);    /*        >> HeihuExecutor::query 发送 SQL 语句            SQL: SELECT * FROM `monster` WHERE `id` = ?            Parameter: 1        >> HeihuConfiguration::build 进行读取 mybatis_config.xml 配置文件, 并返回一个链接        >> HeihuConfiguration::getConnectionByElement 进行读取 database 标签, 并提取数据库配置, 返回 Connection            dirverClass -> com.mysql.cj.jdbc.Driver            url -> jdbc:mysql://127.0.0.1:3306/heihu_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8            username -> root            password -> root        Monster(id=1, age=200, birthday=2000-11-11, [email protected], gender=1, name=牛魔王, salary=8888.88)    */}

编写 SqlSession

public class HeihuSqlSession {    private HeihuExecutor executor = new HeihuExecutor(); // SqlSession 具有发送 SQL 语句的功能    public <T> T selectOne(String sql, Object parameter, Class<T> clazz) {        System.out.println(">> HeihuSqlSession::selectOne 准备发送 SQL 语句, 调用 HeihuExecutor::query 进行发送");        return executor.query(sql, parameter, clazz);    }}

当然, 测试也及其简单, 如下:

@Testpublic void test() {    Monster monster = selectOne("SELECT * FROM `monster` WHERE `id` = ?", 1, Monster.class);    System.out.println(monster);    /*        >> HeihuSqlSession::selectOne 准备发送 SQL 语句, 调用 HeihuExecutor::query 进行发送        >> HeihuExecutor::query 发送 SQL 语句            SQL: SELECT * FROM `monster` WHERE `id` = ?            Parameter: 1        >> HeihuConfiguration::build 进行读取 mybatis_config.xml 配置文件, 并返回一个链接        >> HeihuConfiguration::getConnectionByElement 进行读取 database 标签, 并提取数据库配置, 返回 Connection            dirverClass -> com.mysql.cj.jdbc.Driver            url -> jdbc:mysql://127.0.0.1:3306/heihu_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8            username -> root            password -> root        Monster(id=1, age=200, birthday=2000-11-11, [email protected], gender=1, name=牛魔王, salary=8888.88)    */}

当然, 目前我们的SqlSession只完成了调用 Executor 发送 SQL 语句的功能.

编写 Mapper

创建MonsterMapper接口如下:

package com.heihu577.Mapper; // 注意新开一个 Mapper 包import com.heihu577.Beans.Monster;public interface MonsterMapper {    public Monster queryMonsterById(Integer id);}

随后我们在resources目录下定义一个MonsterMapper.xml文件, 文件内容如下:

<?xml version="1.0"?><mapper namespace="com.heihu577.Mapper.MonsterMapper"> <!-- 指定对 MonsterMapper 进行编写 SQL 语句 -->    <select id="queryMonsterById" parameterType="java.lang.Integer" resultType="com.heihu577.Beans.Monster">        SELECT * FROM `monster` WHERE `id` = ?    </select>    <!-- ID="方法名称" parameterType="接收的参数类型" resultType="返回的BEAN类型" --></mapper>
开发 MapperBean

根据上面XML文件的定义, 接下来我们对其进行封装成一个MapperBean其中关系如下:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

定义 MapperBean 保存 Mapper 名称 以及 XML 中所配置的方法等

接下来我们将它们之间的关系进行保存到一个BEAN对象中, 例如, 定义如下BEAN:

@Data@NoArgsConstructor@AllArgsConstructorpublic class MapperBean {    private String MapperName; // 对应具体某个 XML 文件    private List<MapperInfo> MapperInfos = new Vector<>(); // 对应某个 XML 文件中所定义的每一条标签的信息    public void MapperInfoAdd(MapperInfo mapperInfo) {        MapperInfos.add(mapperInfo); // 增加一条 Mapper 信息    }}

定义完毕之后, 我们继续定义MapperInfo

定义 MapperInfo
@Data@NoArgsConstructor@AllArgsConstructorpublic class MapperInfo {    private String sqlType; // sql类型: <select> <update> <delete> <insert>    // <select id="getMonsterById" resultType="com.heihu577.Beans.Monster">    private String funcName; // 上述 id 部分 (方法名称)    private Object resultType; // 上述 resultType 部分 (返回类型)    private String parameterType; // 参数类型    private String sql; // 需要执行的 SQL 语句 (<select> 这一部分 </select>)}

这样定义之后, 我们就可以将XML文件接口进行绑定在一起了.

解析 MonsterMapper

因为我们之前的HeihuConfiguration是用于处理XML文件信息的, 所以在这里我们需要读取Mapper文件, 读取到接口信息, 并生成MapperBean, 定义HeihuConfiguration::readMapper方法, 具体实现如下:

/* 根据传递过来的 xml 路径名称, 生成其 MapperBean 并返回 */public MapperBean readMapper(String path) {    MapperBean result = new MapperBean();    InputStream datasource = loader.getResourceAsStream(path); // 根据给过来的 XML 路径, 得到 InputStream 流    try {        Document document = saxReader.read(datasource);        Element element = document.getRootElement(); // 读取根节点        String clazzString = element.attributeValue("namespace");        /*         * 读取 <mapper namespace="com.heihu577.Mapper.MonsterMapper">         * */        result.setMapperName(clazzString);        List<Element> elements = element.elements();        if (!elements.isEmpty()) {            /*            * 开始遍历                <select id="queryMonsterById" parameterType="java.lang.Integer" resultType="com.heihu577.Beans                * .Monster">                    SELECT * FROM `monster` WHERE `id` = ?                </select>              等标签, 并封装, 为了方便理解, 下面的变量名称可能不合理            * */            for (Element el : elements) {                MapperInfo mapperInfo = new MapperInfo(); // 准备存放                String select = el.getName(); // 也就是SQL类型                String id = el.attributeValue("id"); // 也就是对应接口方法名称                String parameterType = el.attributeValue("parameterType"); // 也就是参数类型                String resultType = el.attributeValue("resultType"); // 返回结果类型                String sql = el.getStringValue(); // 具体 SQL 语句                mapperInfo.setSql(sql);                mapperInfo.setFuncName(id);                mapperInfo.setSqlType(select);                mapperInfo.setResultType(Class.forName(resultType).newInstance());                mapperInfo.setParameterType(parameterType);                result.MapperInfoAdd(mapperInfo); // 加入进去            }        }    } catch (DocumentException e) {        throw new RuntimeException(e);    } catch (ClassNotFoundException e) {        throw new RuntimeException(e);    } catch (InstantiationException e) {        throw new RuntimeException(e);    } catch (IllegalAccessException e) {        throw new RuntimeException(e);    }    return result;}
测试结果
@Testpublic void testReadMapper() {    MapperBean mapperBean = readMapper("MonsterMapper.xml");    System.out.println(mapperBean);    /*    MapperBean(MapperName=com.heihu577.Mapper.MonsterMapper, MapperInfos=[MapperInfo(sqlType=select, funcName=queryMonsterById, resultType=Monster(id=null, age=null, birthday=null, email=null, gender=null, name=null, salary=null), parameterType=java.lang.Integer, sql=        SELECT * FROM `monster` WHERE `id` = ?    )])    */}

此时已经可以根据对应的Mapper.xml文件进行生成对应的MapperBean了, 接下来就是通过动态代理, 进行返回具体接口的对象.

动态代理 Mapper 的方法

接下来完成该部分功能:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

定义代理类
/* 代理对象类, 链接 XML 文件 以及 接口类, 让 HeihuSqlSession.getMapper 用 */public class HeihuMapperProxy implements InvocationHandler {    private HeihuSqlSession sqlSession = new HeihuSqlSession(); // 调用 mapperObj.方法名 时 发送 SQL 语句用    private Class<?> mapper; // 传入 Mapper 文件名称    private HeihuConfiguration heihuConfiguration = new HeihuConfiguration(); // 因为要读取 XML 信息, 所以一定要有 HeihuConfiguration    public HeihuMapperProxy(Class<?> mapperFile) {        this.mapper = mapperFile;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if (method.getDeclaringClass() != mapper) {            return null;        }        MapperBean mapperBean = heihuConfiguration.readMapper(mapper.getSimpleName() + ".xml"); // 通过 XML 文件, 得到        // MapperBean        List<MapperInfo> mapperInfos = mapperBean.getMapperInfos();        for (MapperInfo mapperInfo : mapperInfos) {            String funcName = mapperInfo.getFuncName();            if (funcName.equals(method.getName())) { // 调用的方法在XML中有定义                String sql = mapperInfo.getSql();                return sqlSession.selectOne(sql, String.valueOf(args[0]), mapperInfo.getResultType().getClass());            }        }        return null;    }}

该代理类的本质则是, 比较方法名称是否有对应上的MapperInfo中所包含的方法名称, 如果存在, 那么直接调用heihuSqlSession对象的selectOne方法进行查询即可.

定义 getMapper 方法, 并且生成代理对象

此时我们在HeihuSqlSession中增加一个getMapper方法, 专门用于返回代理对象用.

public class HeihuSqlSession {    private HeihuExecutor executor = new HeihuExecutor(); // SqlSession 具有发送 SQL 语句的功能    public <T> T selectOne(String sql, Object parameter, Class<T> clazz) {        System.out.println(">> HeihuSqlSession::selectOne 准备发送 SQL 语句, 调用 HeihuExecutor::query 进行发送");        return executor.query(sql, parameter, clazz);    }    public <T> T getMapper(Class<T> clazz) { // 传入进来的是 接口名, 返回代理类        System.out.println(">> HeihuSqlSession::getMapper 进行得到 Mapper 类");        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new HeihuMapperProxy(clazz));    }}
测试结果

在本类进行定义如下方法:

@Testpublic void getMapperTest() {    MonsterMapper mapper = getMapper(MonsterMapper.class);    Monster monster = mapper.queryMonsterById(1);    System.out.println(monster);    /*        >> HeihuSqlSession::getMapper 进行得到 Mapper 类        >> HeihuSqlSession::selectOne 准备发送 SQL 语句, 调用 HeihuExecutor::query 进行发送        >> HeihuExecutor::query 发送 SQL 语句            SQL:                 SELECT * FROM `monster` WHERE `id` = ?            Parameter: 1        >> HeihuConfiguration::build 进行读取 mybatis_config.xml 配置文件, 并返回一个链接        >> HeihuConfiguration::getConnectionByElement 进行读取 database 标签, 并提取数据库配置, 返回 Connection            dirverClass -> com.mysql.cj.jdbc.Driver            url -> jdbc:mysql://127.0.0.1:3306/heihu_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8            username -> root            password -> root        Monster(id=1, age=200, birthday=2000-11-11, [email protected], gender=1, name=牛魔王, salary=8888.88)    */}

可以看到, 已经可以通过一个getMapper方法来得到一个Mapper类, 并且通过动态代理的方式, 调用了sqlSession中的selectOne方法, 就实现了. 没有任何类实现该接口, 但是可以进行调用的效果.

总结图

开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

MyBatis 两种方式

原生 API 调用

我们可以通过如下测试代码, 来查看SqlSession的具体类型:

public class MonsterMapperTest3 {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void t1() {        System.out.println(sqlSession.getClass()); // class org.apache.ibatis.session.defaults.DefaultSqlSession    }}

DefaultSqlSession是由如下方法进行定义的:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis而这里的String statement参数, 并不是SQL语句, 而是接口方法的指定

增加一条记录 API 调用

使用方法如下:

public class MonsterMapperTest3 {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void testInsert() {        /*        * 当前数据库定义如下:        *   mysql> truncate table monster;            Query OK, 0 rows affected (0.49 sec)            mysql> SELECT * FROM monster;            Empty set (0.00 sec)        * */        Monster monster = new Monster();        monster.setId(2);        monster.setGender(6);        monster.setEmail("[email protected]");        monster.setSalary(123.3);        monster.setName("heihu888");        monster.setBirthday(LocalDateTime.now());        monster.setAge(199);        int affectedRow = sqlSession.insert("com.heihu577.Mapper.MonsterMapper.addMonster", monster);        // sqlSession.insert 方法对应如下        /*        * public interface MonsterMapper {        *   public void addMonster(Monster monster); // 定义一个增加 Monster 对象的功能        * }        * 因为 mybatis-config.xml 文件中进行绑定了 Mapper 文件目录, 所以在这里 Mybatis 知道执行的 SQL 语句是什么模板        * */        System.out.println("影响的行数: " + affectedRow);        sqlSession.commit();        /*        *   mysql> SELECT * FROM monster;            +----+-----+------------+---------------+--------+----------+--------+            | id | age | birthday   | email         | gender | name     | salary |            +----+-----+------------+---------------+--------+----------+--------+            |  1 | 199 | 2024-04-19 | [email protected] |      6 | heihu888 |  123.3 |            +----+-----+------------+---------------+--------+----------+--------+            1 row in set (0.00 sec)        * */        sqlSession.close();    }}

查询功能 API 调用

public void testSelect() {    Object o = sqlSession.selectOne("com.heihu577.Mapper.MonsterMapper.getMonsterById", 1);    System.out.println(o);    /*        ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3        <==      Total: 1        Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}    */}

当然, 还有修改记录的update方法, 以及删除记录的delete方法, 需要注意的是, 如果是增删改操作, 必须使用sqlSession.commit()方法进行提交, 否则遇到事务无法更新数据库.

使用注解方式操作 MyBatis

接下来我们使用注解的方式进行配置接口方法以及SQL语句, 定义如下接口:

public interface MonsterAnnotation {    @Insert("INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`) VALUES(#{age}, #{birthday}, " +            "#{email}, #{gender}, #{name}, #{salary})")    public void addMonster(Monster monster); // 定义一个增加 Monster 对象的功能    @Delete("DELETE FROM `monster` WHERE `id` = #{id}")    public void deleteMonster(Integer id); // 根据 ID 号进行删除    @Update("UPDATE `monster` " +            "SET `age` = #{age}, `birthday` = #{birthday}, `email` = #{email}, " +            "`gender` = #{gender}, `name` = #{name}, `salary` = #{salary} " +            "WHERE `id` = #{id}")    public void updateMonsterById(Monster monster); // 通过 Monster 修改值    @Select("SELECT * FROM `Monster` WHERE `ID` = #{id}")    public Monster getMonsterById(Integer id); // 传入 ID, 得到 Monster 对象    @Select("SELECT * FROM `Monster`")    public List<Monster> getAllMonsters();}

当然了, 我们仍需要在mybatis-config.xml文件中进行指明该接口:

<mappers>    <mapper resource="com/heihu577/Mapper/MonsterMapper.xml"/>      <mapper resource="com/heihu577/Mapper/MonkMapper.xml"/>    <!-- 上面是之前指明的 XML 文件路径, 下面是刚刚定义的接口全路径 -->    <mapper class="com.heihu577.Mapper.MonsterAnnotation"/></mappers>

测试结果:

public class MonsterAnnotationTest {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    private MonsterAnnotation monsterAnnotation;    @Test    public void testMonster() {        monsterAnnotation = sqlSession.getMapper(MonsterAnnotation.class);        List<Monster> allMonsters = monsterAnnotation.getAllMonsters();        System.out.println(allMonsters); // [Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}]    }}

返回自增长 ID

当然了, 我们在使用XML方式进行配置Mapper时, 可以通过属性名称进行指定, 而如果我们使用注解方式进行指明时, 可以使用@Options注解, 如下:

public interface MonsterAnnotation {    @Insert("INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`) VALUES(#{age}, #{birthday}, " +            "#{email}, #{gender}, #{name}, #{salary})")    @Options(useGeneratedKeys = true, keyProperty = "id")    /*    * 对应上    *   <insert id="addMonster" parameterType="Monster" useGeneratedKeys="true" keyProperty="id">            INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`)            VALUES(#{age}, #{birthday}, #{email}, #{gender}, #{name}, #{salary})        </insert>    * */    public void addMonster(Monster monster); // 定义一个增加 Monster 对象的功能}

最终运行结果如下:

@Testpublic void testMonster2() {    Monster monster = new Monster(999, 188, LocalDateTime.now(), "[email protected]", 8, "heihu666", 123.3);    monsterAnnotation = sqlSession.getMapper(MonsterAnnotation.class);    monsterAnnotation.addMonster(monster);    System.out.println("增长的 ID: " + monster.getId());    /*    最终运行结果:    ==>  Preparing: INSERT INTO `monster`(`age`,`birthday`,`email`,`gender`,`name`,`salary`) VALUES(?, ?, ?, ?, ?, ?)    ==> Parameters: 188(Integer), 2024-04-19T17:59:03.238(LocalDateTime), [email protected](String), 8(Integer), heihu666(String), 123.3(Double)    <==    Updates: 1    增长的 ID: 3    Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@66d18979]    Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@66d18979]    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@66d18979]    Returned connection 1725008249 to pool.    */    sqlSession.commit();    sqlSession.close();}

配置文件详解

properties 标签引入 properties 文件

这里类似于Spring中的<context:property-placeholder>标签, 用于引入properties文件, 然后进行引入其中的属性, 那么我们在resources目录下创建jdbc.properties文件, 配置信息如下:

jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8jdbc.username=rootjdbc.password=root

然后我们这样进行引用:

<configuration>    <properties resource="jdbc.properties"/> <!-- 可以配置 properties 信息, 也可以引入 properties 文件 -->    <!-- 其他配置项 ... -->    <environments default="development">        <environment id="development">            <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>    </environments>    <!-- 其他配置项 ... --></configuration>

具体可以参考: https://mybatis.net.cn/configuration.html#properties

settings 标签, 配置日志等设置

具体参考: https://mybatis.net.cn/configuration.html#settings

typeAlias 标签, 配置 Bean 类型 (一把梭哈)

在之前我们使用过该标签, 上面的案例为:

<typeAliases>    <typeAlias type="com.heihu577.Beans.Monster" alias="Monster"/>    <typeAlias type="com.heihu577.Beans.Monk" alias="Monk"/></typeAliases>

而因为我们的这些Bean都在同一个包下, 我们可以直接这样配置, 就可以直接生成效果:

<typeAliases>    <package name="com.heihu577.Beans"/></typeAliases>

具体参考: https://mybatis.net.cn/configuration.html#typeAliases

mapper 标签一把梭哈
<mappers><!--        <mapper resource="com/heihu577/Mapper/MonsterMapper.xml"/>  <!– 指明所定义的XML-SQL文件 –>--><!--        <mapper resource="com/heihu577/Mapper/MonkMapper.xml"/>--><!--        <mapper class="com.heihu577.Mapper.MonsterAnnotation"/>-->    <package name="com.heihu577.Mapper"/></mappers>

当然了, 这样我们就不用一条一条指定了.

类型自动转换

我们可以看一下https://mybatis.net.cn/configuration.html#typeHandlers给出的表格:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

XML 映射器

ParameterType

  1. 传入简单类型, 按照 ID 查 Monster
  2. 传入JAVABEAN, 查询时需要有多个筛选条件
  3. 当有多个条件时, 传入的参数就是POJO类型和JAVA对象, 比如 Monster 对象
  4. 当传入参数类是 String 时, 也可以使用 ${} 方法来接收参数.
通过 ID 或 NAME 查询 (多条件查询)

在这里我们可以这样定义方法声明:

public interface MonsterMapper {    public List<Monster> findMonsterByIdOrName(Monster monster); // 通过 ID 或 NAME 查询}

随后定义MonsterMapper.xml文件内容如下:

<mapper namespace="com.heihu577.Mapper.MonsterMapper">    <select id="findMonsterByIdOrName" parameterType="com.heihu577.Beans.Monster" resultType="com.heihu577.Beans.Monster"> <!-- 方法名称, 参数类型, 返回结果类型 -->        SELECT * FROM `monster` WHERE `id` = #{id} OR `name` = #{name}    </select></mapper>

创建测试代码:

public class TestMonsterMapper {    private SqlSession sqlSession;    private MonsterMapper monsterMapper;    @Before    public void beforeFunction() {        sqlSession = MyBatisUtils.getSqlSession();        monsterMapper = sqlSession.getMapper(MonsterMapper.class);    }    @Test    public void testFindMonsterByIdOrName() {        Monster monster = new Monster();        monster.setId(1);        monster.setName("张三");        // 这里的目的是查询出 ID 为 1, name 为 张三 的记录.        System.out.println(monsterMapper.findMonsterByIdOrName(monster));        /*            [Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}, Monster{id=4, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}, Monster{id=5, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}]        */        sqlSession.close();    }}
查询 NAME 包含 heihu (模糊查询)

我们可以这样定义如下方法定义:

public interface MonsterMapper {    public List<Monster> findMonsterByName(String name); // 模糊查询}

其中XML文件的配置如下:

<select id="findMonsterByName" parameterType="String" resultType="com.heihu577.Beans.Monster">    SELECT * FROM `monster` WHERE `name` LIKE '%${name}%'</select>

注意, 包含的 SQL 语句中使用 ${} 进行包围

创建测试代码:

@Testpublic void testFindMonsterByName2() {    List<Monster> monsters = monsterMapper.findMonsterByName("heihu");    System.out.println(monsters);    /*    * [Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}, Monster{id=3, age=188, birthday=2024-04-19T00:00, email='[email protected]', gender=8, name='heihu666', salary=123.3}]    * */}
SQL 注入问题

${} 的使用方式会引起 SQL 注入, 例如:

@Testpublic void testFindMonsterByName1() {    List<Monster> monsters = monsterMapper.findMonsterByName("heihu' AND 1=updatexml(1,concat(1,user(),1),1)-'");    System.out.println(monsters);    /*        org.apache.ibatis.exceptions.PersistenceException:         ### Error querying database.  Cause: java.sql.SQLException: XPATH syntax error: 'root@localhost1'        成功进行报错注入.    */}
传入 HashMap 类型

声明一个方法, 按照传入参数是HashMap的方式, 查询name=heihu888并且salary=123.3的所有Monster.开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

返回 Map 类型

定义如下方法声明:

public List<Map<String, Object>> findMonsterByIdAndSalary_ParameterHashMap_ReturningMap(HashMap<String, Object> hashMap); // 传入 HashMap, 返回 Map

MonsterMapper.xml文件中定义如下SQL语句:

<select id="findMonsterByIdAndSalary_ParameterHashMap_ReturningMap" parameterType="hashmap" resultType="map">    SELECT * FROM `monster` WHERE `id` = #{id} OR `id` = #{idx}</select>

其中parameterType=hashmap以及resultType=map的简写原因, 是因为官网中https://mybatis.net.cn/configuration.html#typeAliases中指明了类型关系, 如下:

别名 映射的类型
_byte byte
... ...
map Map
hashmap HashMap

最终运行结果:

@Testpublic void testReturnMap() {    HashMap<String, Object> objectObjectHashMap = new HashMap<>();    objectObjectHashMap.put("id", 1); // 与 SQL 中给出的 #{id} 对应上    objectObjectHashMap.put("idx", 3); // 与 SQL 中给出的 #{idx} 对应上    List<Map<String, Object>> resultMap =    monsterMapper.findMonsterByIdAndSalary_ParameterHashMap_ReturningMap(objectObjectHashMap);    for (Map<String, Object> stringObjectMap : resultMap) {        System.out.println(stringObjectMap);    }    /*    *   ==>  Preparing: SELECT * FROM `monster` WHERE `id` = ? OR `id` = ?        ==> Parameters: 1(Integer), 3(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3        <==        Row: 3, 188, 2024-04-19, [email protected], 8, heihu666, 123.3        <==      Total: 2        {birthday=2024-04-19, gender=6, name=heihu888, id=1, salary=123.3, age=199, [email protected]}        {birthday=2024-04-19, gender=8, name=heihu666, id=3, salary=123.3, age=188, [email protected]}    * */}

ResultType

取出 JAVA BEAN 中字段

创建一张User表:

CREATE TABLE `user`(    `user_id` int unsigned primary key auto_increment comment '用户ID',    `user_name` varchar(32) unique comment '用户名',    `user_pass` varchar(32) not null)engine=Innodb;INSERT INTO `user` VALUES(NULL, 'admin', 'admin888');

创建完毕后, 我们创建对应的JavaBean:

@Data // 引入 lombok@NoArgsConstructor@AllArgsConstructorpublic class User {    private Integer id;    private String username;    private String password;}

注意这里并没有与数据库中的字段编写一致.

随后我们定义UserMapper接口, 如下:

public interface UserMapper {    public void addUser(User user); // 使用 bean 进行插入}

定义UserMapper.xml文件, 内容如下:

<mapper namespace="com.heihu577.Mapper.UserMapper">    <insert id="addUser" parameterType="com.heihu577.Beans.User">        INSERT INTO `user`(`user_name`, `user_pass`) VALUES(#{username}, #{password})        <!-- 注意 SQL 中 #{username} 与 #{password} 是取出的 `java bean` 属性 -->    </insert></mapper>

定义完毕之后, 我们在mybatis-config.xml文件中, 进行加入该mapper, 如下:

<mappers>    <mapper resource="com/heihu577/Mapper/MonsterMapper.xml"/>    <mapper class="com.heihu577.Mapper.UserMapper"/> <!-- 加入进来 UserMapper --></mappers>

测试结果:

public class TestUserMapper {    @Test    public void t1() {        SqlSession sqlSession = MyBatisUtils.getSqlSession();        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);        User user = new User(null, "guest", "guest888");        userMapper.addUser(user);        /*        控制台:        * Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@55fe41ea]            ==>  Preparing: INSERT INTO `user`(`user_name`, `user_pass`) VALUES(?, ?)            ==> Parameters: guest(String), guest888(String)            <==    Updates: 1        数据库:            +---------+-----------+-----------+            | user_id | user_name | user_pass |            +---------+-----------+-----------+            |       1 | admin     | admin888  |            |       3 | guest     | guest888  |  增加了这一条记录            +---------+-----------+-----------+        * */        sqlSession.commit();        sqlSession.close();    }}
resultType (查询结果别名演示)

接下来演示正常的resultType如何使用, 定义findUserById方法定义, 如下:

public List<User> findUserById(Integer id); // 指定ID的字段

随后在UserMapper.xml文件中进行实现该方法:

<select id="findUserById" resultType="com.heihu577.Beans.User" parameterType="integer">    <!--    数据库中的字段: user_id, user_name, user_pass    BEAN中的字段: id, username, password    将查询出来的结果, 对应BEAN中的字段即可 (使用 AS 别名)    -->    SELECT `user_id` AS `id`, `user_name` AS `username`, `user_pass` AS `password` FROM `user` WHERE `user_id` = #{id}</select>

最终运行结果:

@Testpublic void t2() {    SqlSession sqlSession = MyBatisUtils.getSqlSession();    UserMapper mapper = sqlSession.getMapper(UserMapper.class);    List<User> allUser = mapper.findUserById(1);    System.out.println(allUser);    /*        ==>  Preparing: SELECT `user_id` AS `id`, `user_name` AS `username`, `user_pass` AS `password` FROM `user` WHERE `user_id` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, username, password        <==        Row: 1, admin, admin888        <==      Total: 1        [User(id=1, username=admin, password=admin888)]    */}
resultMap (不使用别名的方式)

因为上面的形式, 我们需要一点一点AS进行指定别名, 才能查询到正确的值, 那么接下来我们可以使用Mybatis提供的resultMap进行指定数据库字段BEAN的关系, 等同于一个映射... 具体实现方法如下: 定义findAllUser方法如下:

public List<User> findAllUser();

随后在XML文件中定义resultMap, 并使用, 如下:

<resultMap id="userResultMap" type="com.heihu577.Beans.User">    <result column="user_id" property="id"/> <!-- 数据库中是 user_id, BEAN 中是 id -->    <result column="user_name" property="username"/> <!-- 数据库中是 user_name, BEAN 中是 username -->    <result column="user_pass" property="password"/> <!-- 数据库中是 user_pass, BEAN 中是 password --></resultMap><select id="findAllUser" resultMap="userResultMap">    SELECT * FROM `user`</select>

测试代码:

@Testpublic void t3() {    SqlSession sqlSession = MyBatisUtils.getSqlSession();    UserMapper mapper = sqlSession.getMapper(UserMapper.class);    List<User> allUser = mapper.findAllUser();    System.out.println(allUser);    /*    运行结果:    ==>  Preparing: SELECT * FROM `user`    ==> Parameters:    <==    Columns: user_id, user_name, user_pass    <==        Row: 1, admin, admin888    <==        Row: 3, guest, guest888    <==      Total: 2    [User(id=1, username=admin, password=admin888), User(id=3, username=guest, password=guest888)]    * */}

动态 SQL 语句

具体功能为: 如果外部传递进来的值是1, 那么执行A-SQL, 如果外部传递进来的值是2, 那么执行B-SQL.

if 标签

下面我们使用一个案例来进行演示, 查询出age > 程序员输入进来的AGE的所有妖怪. 如果程序员输入的age不大于0, 那么输出所有妖怪.

public interface MonsterMapper {    public List<Monster> findMonsterByAge(@Param("age") Integer age); // 使用 @Param 给 if 标签使用}

那么我们在MonsterMapper.xml文件中这样实现:

<select id="findMonsterByAge" resultType="Monster" parameterType="Integer">    <!-- resultMap 已在 mybatis-config.xml 文件中进行配置了别名, parameterType 官网已给出了简写形式 -->    SELECT * FROM `monster` WHERE 1=1    <if test="age > 0">        AND age > #{age}    </if></select>

实现完毕后进行测试:

public class TestMonster {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void t1() {        MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);        List<Monster> monsters1 = monsterMapper.findMonsterByAge(2); // Preparing: SELECT * FROM `monster` WHERE 1=1 AND age > ?        List<Monster> monsters2 = monsterMapper.findMonsterByAge(-1); // Preparing: SELECT * FROM `monster`        System.out.println(monsters1);        System.out.println(monsters2);    }}

可以看到, 通过外部参数的不同, MyBatis发送了不同的SQL语句.

where 标签

需求: 查询 id > 2, 并且 name = 张三的所有记录, 如果name | id为空, 则不拼接WHERE条件. 在MonsterMapper中定义方法声明:

public List<Monster> findMonsterByIdAndName(Monster monster);

MonsterMapper.xml文件中进行实现:

<select id="findMonsterByIdAndName" resultType="Monster" parameterType="Monster">        SELECT * FROM `monster`        <where>            <if test="name != null and age != null"> <!-- 因为是 age 是 Integer 类型, 所以判断 null 即可 -->                `age` > #{age} AND `name` = #{name}            </if>            <if test="name == null and age != null">                `age` > #{age}            </if>            <if test="age == null and name != null">                `name` = #{name}            </if>        </where>    <!--        这里有四种情况:            当 name&&age不为空, 则查询一个 AND 条件            当 name 为空, age 不为空, 则增加查询 age 的 WHERE 条件            当 age 为空, name 不为空, 则增加查询 name 的 WHERE 条件            当 age 和 name 都为空, 则不增加条件    --></select>

运行结果:

@Testpublic void t2() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster = new Monster();    monster.setAge(2);    monster.setName("张三");    List<Monster> monsters =  mapper.findMonsterByIdAndName(monster);    System.out.println(monsters);    // 当 monster.setAge && monster.setName 不为空时, 会有 WHERE 条件的限制, 否则, 查询所有.}
WHERE 标签特性

在WHERE标签中, 假设存在这样的SQL:

<where>    <if test="条件为真">        AND 逻辑判断1    </if>    <if test="条件也为真">        AND 逻辑判断2    </if></where>

在两个条件判断都成立的情况下, 看似拼接了WHERE AND 逻辑判断1 AND 逻辑判断2, 其实MyBatis会将最开头的AND拿掉, 解析为WHERE 逻辑判断1 AND 逻辑判断2, 所以在这里SQL依然成立.

choose/when/otherwise

-> 如果 name 不为空, 就按照名字查询妖怪 -> 如果指定的 id > 0, 就按照 id 来查询妖怪 -> 如果前面两个条件都不满足, 就默认查询 salary > 100的. -> 要求 choose/when/otherwise 标签实现, 传入参数要求使用 Map

定义如下接口中的方法:

public List<Monster> findMonsterByNameAndId_Choose(Map<String, Object> map);

定义MonsterMapper.xml文件, 进行实现它:

<select id="findMonsterByNameAndId_Choose" parameterType="map" resultType="Monster">    SELECT * FROM `monster`    <where>        <choose>            <when test="name != null and name != ''">                `name` = #{name}            </when>            <when test="id > 0">                `id` = #{id}            </when>            <otherwise>                `salary` > 100            </otherwise>        </choose>    </where></select>

测试结果:

@Testpublic void t3() {    MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);    Map<String, Object> map = new HashMap<>();    map.put("name", "张三");    /*    * 存在 name 的情况:    *   ==> Parameters: 张三(String)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 4, 12, 2024-04-22, [email protected], 1, 张三, 120.0        <==        Row: 5, 12, 2024-04-22, [email protected], 1, 张三, 120.0        <==      Total: 2        [Monster{id=4, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}, Monster{id=5, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}]    * */    map.put("id", 4);    /* 没有 name, 但有 id 的情况    *   ==>  Preparing: SELECT * FROM `monster` WHERE `id` = ?        ==> Parameters: 4(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 4, 12, 2024-04-22, [email protected], 1, 张三, 120.0        <==      Total: 1        [Monster{id=4, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}]    * */    List<Monster> monsters = monsterMapper.findMonsterByNameAndId_Choose(map);    /*    * map 是一个空的 map, 则会报错    * */    System.out.println(monsters);}

foreach 标签

查询 ID 为 1, 3, 5的妖怪. 准备如下接口:

public List<Monster> findMonsterByID_ForEach(Map<String, Object> map);

MonsterMapper.xml中进行实现:

<select id="findMonsterByID_ForEach" resultType="Monster" parameterType="map">    SELECT * FROM `monster` <!-- 正常情况应该使用 IN(1,3,5) 进行得到集合 -->    <!-- 在这里我们使用 K-V 为这种情况: ids - [1,3,5], 来进行遍历出 IN 的结果 -->    <if test="ids != null and ids != ''">        <!-- 说明 ids 不为空 -->        <where>            `id` in <!-- WHERE `id` in -->            <foreach collection="ids" separator="," item="id" open="(" close=")">                <!--                collection: 被遍历的集合                separator: 用什么分割                item: 生成出的名称                open: 最左边是什么字符包围                close: 最右边是什么字符包围                -->                #{id}            </foreach>        </where>    </if></select>

最终运行结果:

@Testpublic void t4() {    MonsterMapper mapper = sqlSession.getMapper(MonsterMapper.class);    Map<String, Object> map = new HashMap<>();    map.put("ids", new Integer[]{1, 3, 5}); // ids 是被遍历的集合    List<Monster> monsters = mapper.findMonsterByID_ForEach(map);    System.out.println(monsters);    /*    *   ==>  Preparing: SELECT * FROM `monster` WHERE `id` in ( ? , ? , ? )        ==> Parameters: 1(Integer), 3(Integer), 5(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3        <==        Row: 3, 188, 2024-04-19, [email protected], 8, heihu666, 123.3        <==        Row: 5, 12, 2024-04-22, [email protected], 1, 张三, 120.0        <==      Total: 3        [Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}, Monster{id=3, age=188, birthday=2024-04-19T00:00, email='[email protected]', gender=8, name='heihu666', salary=123.3}, Monster{id=5, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}]    * */}

trim 标签 (应用较少)

定义如下接口中的方法:

public List<Monster> findMonsterByIdAndName_Trim(Monster monster);

定义如下XML:

<select id="findMonsterByIdAndName_Trim" resultType="Monster" parameterType="Monster">    SELECT * FROM `monster`    <trim prefix="WHERE" prefixOverrides="AND|OR"> <!-- 如果发现开头是 AND, 那么将开头的 AND 替换为 WHERE -->        <!-- 等同于一个 where 标签 -->        <if test="age != null">            AND `age` > #{age}        </if>        <if test="name != null and name != ''">            AND `name` = #{name}        </if>    </trim></select>

测试结果:

@Testpublic void t5() {    MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster = new Monster();    monster.setName("张三");    monster.setAge(5);    List<Monster> monsters = monsterMapper.findMonsterByIdAndName_Trim(monster);    System.out.println(monsters);    /*        ==>  Preparing: SELECT * FROM `monster` WHERE `age` > ? AND `name` = ?        ==> Parameters: 5(Integer), 张三(String)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 4, 12, 2024-04-22, [email protected], 1, 张三, 120.0        <==        Row: 5, 12, 2024-04-22, [email protected], 1, 张三, 120.0        <==      Total: 2        [Monster{id=4, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}, Monster{id=5, age=12, birthday=2024-04-22T00:00, email='[email protected]', gender=1, name='张三', salary=120.0}]    */}

相当于扩展的 WHERE 标签.

set 标签

对指定 ID 的妖怪进行修改, 如果没有设置新的属性, 则保持原来的值.

这里 set 标签是用来应用于 update 语句的, 我们可以通过判断外部传递过来的 map 属性中, 是否含有某一项, 如果含有, 才增加条件, 定义如下接口方法.

public void updateMonster(Monster monster);

随后在MonsterMapper.xml文件中进行定义它:

<update id="updateMonster" parameterType="map">    UPDATE `monster`    <set> <!-- set 标签可以智能的清除多余的 逗号 -->        <if test="age != null">            `age` = #{age},        </if>        <if test="email != null and email != ''">            `email` = #{email},        </if>        <if test="name != null and name != ''">            `name` = #{name},        </if>        <if test="birthday != null">            `birthday` = #{birthday},        </if>        <if test="gender != null and gender != ''">            `gender` = #{gender},        </if>        <if test="salary != null">            `salary` = #{salary},        </if>    </set>    WHERE `id` = #{id}</update>

运行结果:

@Testpublic void t6() {    MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);    Map<String, Object> map = new HashMap<>();    map.put("id", 5);    map.put("birthday", "2021-04-22");    map.put("gender", 7);    /*    * |  5 |  12 | 2024-04-22 | [email protected] |      1 | 张三     |    120 |    * */    monsterMapper.updateMonster(map); // Preparing: UPDATE `monster` SET `birthday` = ?, `gender` = ? WHERE `id` = ?    sqlSession.commit();    /*    *|  5 |  12 | 2021-04-22 | [email protected] |      7 | 张三     |    120 |    * */}

综合练习

要求hero表: id号属性, nickname(外号)属性, skill(本领)属性, rank(排行)属性, salary(薪水)属性, join_datetime(入伙时期)

完成功能 (创建新项目完成):

  1. 创建 hero 表
  2. 编写方法, 添加 hero 记录
  3. 编写方法, 查询 rank 大于 10 的所有 hero, 如果输入的 rank 不大于 0, 则输出所有 hero
  4. 编写方法, 查询 rank 为 3, 6, 10 [rank 可变] 的 hero
  5. 编写方法, 修改 hero 信息, 如果没有设置新的属性值, 则保持原来的值
  6. 编写方法, 可以根据 id 查询 hero, 如果没有传入 id, 就返回所有 hero
第一题 (并且搭建基础环境)

配置本地pom.xml文件, 允许com.heihu577.Mappers进行输出到target目录中, 并且引入lombok:

<build>    <resources>        <resource>            <directory>src/main/java</directory>            <includes>                <include>**/*.xml</include>            </includes>        </resource>        <resource>            <directory>src/main/resources</directory>            <includes>                <include>**/*.xml</include>                <include>**/*.properties</include>            </includes>        </resource>    </resources></build><!-- 父项目还包含了 mysql-driver 等 --><dependencies>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>1.18.20</version>    </dependency></dependencies>

定义完毕之后, 我们进行定义jdbc.properties文件内容如下:

jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis_homework?useSSL=true&useUnicode=true&characterEncoding=UTF-8jdbc.username=rootjdbc.password=root

随后我们创建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>    <properties resource="jdbc.properties"/> <!-- 引入刚才的 jdbc.properties 文件 -->    <typeAliases>        <package name="com.heihu577.Beans"/> <!-- 批量添加别名 -->    </typeAliases>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <property name="driver" value="${jdbc.driver}"/> <!-- 引入进来properties信息 -->                <property name="url" value="${jdbc.url}"/>                <property name="username" value="${jdbc.username}"/>                <property name="password" value="${jdbc.password}"/>            </dataSource>        </environment>    </environments><!--    <mappers>--><!--        <mapper resource="org/mybatis/example/BlogMapper.xml"/>--><!--    </mappers>--></configuration>

定义完毕之后, 我们开始创建数据库部分:

create database mybatis_homework;use mybatis_homework;create table `hero`(    `id` int unsigned primary key auto_increment comment 'ID号',    `nickname` varchar(64) not null comment '外号',    `skill` varchar(64) not null comment '本领',    `rank` tinyint unsigned default 0 comment '排行',    `salary` decimal(10, 2) not null default 0.0 comment '薪水',    `join_datetime` datetime not null comment '入伙日期' -- MYSQL 中用下划线)engine=innodb;

并且配置对应的JavaBean:

@Data@NoArgsConstructor@AllArgsConstructorpublic class Hero {    /*    *   +---------------+---------------------+------+-----+---------+----------------+        | Field         | Type                | Null | Key | Default | Extra          |        +---------------+---------------------+------+-----+---------+----------------+        | id            | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |        | nickname      | varchar(64)         | NO   |     | NULL    |                |        | skill         | varchar(64)         | NO   |     | NULL    |                |        | rank          | tinyint(3) unsigned | YES  |     | 0       |                |        | salary        | decimal(10,2)       | NO   |     | 0.00    |                |        | join_datetime | datetime            | NO   |     | NULL    |                |        +---------------+---------------------+------+-----+---------+----------------+        6 rows in set (0.00 sec)    * */    private Integer id;    private String nickname;    private String skill;    private Integer rank;    private BigDecimal salary;    private LocalDateTime joinDatetime; // JavaBean 中用驼峰}

那么随后我们创建对应的HeroMapper接口内容如下:

public interface HeroMapper {}

并且创建对应的HeroMapper.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.heihu577.Mappers.HeroMapper"> <!-- namespace 用于指定 接口 --></mapper>

架子已搭好, 在mybatis-config.xml中进行注册该XML文件:

<mappers>    <mapper resource="com/heihu577/Mappers/HeroMapper.xml"/></mappers>

随后创建MyBatisUtils类, 用于获取SqlSession:

public class MyBatisUtils {    private static SqlSessionFactory sqlSessionFactory;    static {        InputStream is = MyBatisUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml"); // 当然, 也可以使用        // Mybatis 提供的 Resources 类        sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);    }    public static SqlSession getSqlSession() {        return sqlSessionFactory.openSession(); // 返回 SqlSession    }}
编写方法, 添加 hero 记录

HeroMapper接口中创建如下方法声明:

public void insertHeroByJavaBean(Hero hero);

HeroMapper.xml文件中进行实现该接口:

<insert id="insertHeroByJavaBean" parameterType="Hero" useGeneratedKeys="true" databaseId="id"> <!-- 返回添加索引 -->    <!-- 注意这里属性不要出错, 否则可能爆出 BindingException 错误 -->    INSERT INTO `hero` VALUES(NULL, #{nickname}, #{skill}, #{rank}, #{salary}, #{joinDatetime});</insert>

创建测试文件, 进行测试该SQL语句:

@Testpublic void t1() {    HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);    System.out.println(heroMapper.getClass()); // class com.sun.proxy.$Proxy5 代理类型    System.out.println(heroMapper); // org.apache.ibatis.binding.MapperProxy@23faf8f2    Hero hero = new Hero();    hero.setNickname("电击小子");    hero.setSkill("电人");    hero.setRank(1);    hero.setSalary(new BigDecimal("1999.10"));    hero.setJoinDatetime(LocalDateTime.now());    heroMapper.insertHeroByJavaBean(hero);    sqlSession.commit(); // 提交事务    sqlSession.close();    System.out.println("自增的ID: " + hero.getId()); // 自增的ID: 1}
编写后续测试环境代码

在这里我们需要编写后续测试环境, 执行 truncate table hero后, t1方法修改为如下情况:

@Testpublic void t1() {    HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);    System.out.println(heroMapper.getClass()); // class com.sun.proxy.$Proxy5 代理类型    System.out.println(heroMapper);    for (int i = 0; i < 100; i++) { // 添加 100 条记录, 方便测试        Hero hero = new Hero();        hero.setNickname("电击小子-" + i);        hero.setSkill("电人-" + i);        hero.setRank(i);        hero.setSalary(new BigDecimal("2000.10"));        hero.setJoinDatetime(LocalDateTime.now());        heroMapper.insertHeroByJavaBean(hero);        sqlSession.commit();        System.out.println("自增的ID: " + hero.getId());    }    sqlSession.close();}
查询 rank 大于 10 的所有 hero, 如果输入的 rank 不大于 0, 则输出所有 hero

HeroMapper接口中定义如下方法:

public List<Hero> findHeroByRand(@Param("rank") Integer rank); // 因为要当条件, 所以使用 @Param 注解

HeroMapper.xml中进行实现:

<select id="findHeroByRand" resultType="Hero" parameterType="Integer">    SELECT * FROM `hero`    <where>        <if test="rank > 0">            AND `rank` > 10        </if>    </where></select>

最终运行结果:

@Testpublic void t2() {    HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);    List<Hero> heroByRand = heroMapper.findHeroByRand(2); // SELECT * FROM `hero` WHERE `rank` > 10    System.out.println(heroByRand);    List<Hero> heroByRand1 = heroMapper.findHeroByRand(-1); // SELECT * FROM `hero`    System.out.println(heroByRand1);}
编写方法, 查询 rank 为 3, 6, 10 [rank 可变] 的 hero

HeroMapper接口中定义如下方法:

public List<Hero> findHeroByRank(Map<String, Object> map);

HeroMapper.xml文件中定义如下接口实现:

<select id="findHeroByRank" resultType="Hero" parameterType="map">    SELECT * FROM `hero`    <where>        `id` in        <foreach collection="ids" open="(" close=")" separator="," item="id">            #{id}        </foreach>    </where></select>

最终测试结果:

@Testpublic void t3() {    HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);    HashMap<String, Object> map = new HashMap<>();    map.put("ids", Arrays.asList(3, 6, 10));    List<Hero> heroByRank = heroMapper.findHeroByRank(map);    System.out.println(heroByRank);    /*    ==>  Preparing: SELECT * FROM `hero` WHERE `id` in ( ? , ? , ? )    ==> Parameters: 3(Integer), 6(Integer), 10(Integer)    <==    Columns: id, nickname, skill, rank, salary, join_datetime    <==        Row: 3, 电击小子-2, 电人-2, 2, 2000.10, 2024-04-23 12:02:22    <==        Row: 6, 电击小子-5, 电人-5, 5, 2000.10, 2024-04-23 12:02:22    <==        Row: 10, 电击小子-9, 电人-9, 9, 2000.10, 2024-04-23 12:02:22    <==      Total: 3    [Hero(id=3, nickname=电击小子-2, skill=电人-2, rank=2, salary=2000.10, joinDatetime=null), Hero(id=6, nickname=电击小子-5, skill=电人-5, rank=5, salary=2000.10, joinDatetime=null), Hero(id=10, nickname=电击小子-9, skill=电人-9, rank=9, salary=2000.10, joinDatetime=null)]    */}
编写方法, 修改 hero 信息, 如果没有设置新的属性值, 则保持原来的值

HeroMapper接口中定义如下方法声明:

public void updateHero(Hero hero);

HeroMapper.xml文件中定义如下XML配置:

<update id="updateHero" parameterType="Hero">    UPDATE `hero`    <set>        <if test="nickname != null and nickname != ''">            `nickname` = #{nickname},        </if>        <if test="skill != null and skill != ''">            `skill` = #{skill},        </if>        <if test="rank != null and rank != ''">            `rank` = #{rank},        </if>        <if test="salary != null and salary != ''">            `salary` = #{salary},        </if>        <if test="joinDatetime != null"> <!-- 注意, 日期不能判断 != '', 否则会报错 -->            `join_datetime` = #{joinDatetime},        </if>    </set>    WHERE `id` = #{id}</update>

测试结果:

@Testpublic void t4() {    HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);    Hero hero = new Hero();    hero.setId(98); // 要修改的 ID    hero.setNickname("电击大王");    hero.setSkill("控制小弟");    hero.setSalary(new BigDecimal("999.99"));    hero.setJoinDatetime(LocalDateTime.now());    hero.setRank(100);    /*    *   +----+--------------+--------------+------+--------+---------------------+        | id | nickname     | skill        | rank | salary | join_datetime       |        +----+--------------+--------------+------+--------+---------------------+        |  98 | 电击小子-97 | 电人-97      |   97 | 2000.10 | 2024-04-23 12:02:28 |        +----+--------------+--------------+------+--------+---------------------+    * */    heroMapper.updateHero(hero);    sqlSession.commit();    /*    *   +----+--------------+--------------+------+--------+---------------------+        | id | nickname     | skill        | rank | salary | join_datetime       |        +----+--------------+--------------+------+--------+---------------------+        | 98 | 电击大王      | 控制小弟       | 100 | 999.99   | 2024-04-23 15:58:32 |        +----+--------------+--------------+------+--------+---------------------+    * */    sqlSession.close();}
编写方法, 可以根据 id 查询 hero, 如果没有传入 id, 就返回所有 hero

HeroMapper接口中定义如下方法声明:

public List<Hero> findHeroById(@Param("id") Integer id);

HeroMapper.xml文件中定义如下方法实现:

<select id="findHeroById" parameterType="Integer" resultType="Hero">    SELECT * FROM `hero`    <where>        <if test="id != null and id != 0">            AND `id` = #{id}        </if>    </where></select>

运行结果:

@Testpublic void t5() {    HeroMapper heroMapper = sqlSession.getMapper(HeroMapper.class);    heroMapper.findHeroById(0); // SELECT * FROM `hero`    heroMapper.findHeroById(8); // SELECT * FROM `hero` WHERE `id` = ?}

映射关系

在这里我们看一下MyBatis中处理一对一, 一对多, 多对一的关系, 是如何实现的.

一对一映射关系

一个经典的案例, 则是Person(人) <-> IdCard(身份证), 一个人只有一个身份证, 一个身份证对应一个人.

Idencard 编写

在这里我们创建一个子项目, 并引入lombok, 使其Maven运行时, XML文件会输出到target目录中:

    <build>        <resources>            <resource>                <directory>src/main/java</directory>                <includes>                    <include>**/*.xml</include>                </includes>            </resource>            <resource>                <directory>src/main/resources</directory>                <includes>                    <include>**/*.xml</include>                    <include>**/*.properties</include>                </includes>            </resource>        </resources>    </build>    <dependencies>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.20</version>        </dependency>    </dependencies>

创建表格:

CREATE TABLE `idencard`(    id int primary key auto_increment comment '身份证ID号',    card_sn varchar(32) not null default '' comment '具体身份证号')engine=innodb charset=utf8;CREATE TABLE `person`(    id int unsigned primary key auto_increment comment 'ID号',    name varchar(32) not null default '' comment '姓名',    card_id int comment '身份证ID号',    foreign key(card_id) references idencard(id) -- 创建外键)engine=innodb charset=utf8;INSERT INTO `idencard` VALUES(1, '111111111110');INSERT INTO `person` VALUES(1, '张三', 1);

创建完毕后, 我们创建对应的JavaBean:

@Data@NoArgsConstructor@AllArgsConstructorpublic class Idencard {    private Integer id;    private String card_sn;}

重点关注Person类如何定义:

@Data@NoArgsConstructor@AllArgsConstructorpublic class Person {    private Integer id;    private String name;    private Idencard card; // 这里 card 名字可以任意起名    // 在数据表中的关系则是 Person 对应上了 Idencard    // 虽然数据库中的保存的是 card_id, 但是在我们实体的 JavaBean 中, 一定要用类关系}

因为IdencardMapper的定义来说比较简单, 所以在这里我们先定义IdencardMapper, 定义接口如下:

public interface IdencardMapper {    public Idencard getIdencardById(Integer id); // 根据 id 查询出对应的 Idencard 信息}

定义如下IdencardMapper.xml文件:

<mapper namespace="com.heihu577.Mappers.IdencardMapper">    <!--    特别注意, 在 mybatis-config.xml 文件中需要定义如下标签:        <mappers>            <package name="com.heihu577.Mappers"/> 将我们的 xml 文件扫描进去        </mappers>    -->    <select id="getIdencardById" parameterType="Integer" resultType="Idencard">        <!--         因为配置了别名:            <typeAliases>                <package name="com.heihu577.Beans"/>            </typeAliases>         所以在这里可以直接指定 resultType = Idencard         -->        SELECT * FROM `idencard` WHERE `id` = #{id}    </select></mapper>

测试结果:

@Testpublic void t1() {    IdencardMapper idencardMapper = sqlSession.getMapper(IdencardMapper.class);    Idencard idencardById = idencardMapper.getIdencardById(1);    System.out.println(idencardById);    /*        ==>  Preparing: SELECT * FROM `idencard` WHERE `id` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, card_sn        <==        Row: 1, 111111111110        <==      Total: 1        Idencard(id=1, card_sn=111111111110)    */}
Person 编写
按照惯例方式编写存在的问题

创建如下JavaBean:

@Data@NoArgsConstructor@AllArgsConstructorpublic class Person {    private Integer id;    private String name;    private Idencard card; // 在数据表中的关系则是 Person 对应上了 Idencard    // 虽然数据库中的保存的是 card_id, 但是在我们实体的 JavaBean 中, 一定要用类关系}

并且创建PersonMapper接口如下:

public interface PersonMapper {    public Person getPersonById(Integer id);    // 通过 Person 的 id 获得对应的 Person, 并且 Idencard 是存在关系的 [级联查询]}

随后我们在PersonMapper.xml文件中进行实现:

<mapper namespace="com.heihu577.Mappers.PersonMapper">    <select id="getPersonById" parameterType="Integer" resultType="Person">        SELECT * FROM `person` WHERE `id` = #{id}    </select></mapper>

测试一下我们可以发现给我们带来的问题:

@Testpublic void t2() {    PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);    Person personById = personMapper.getPersonById(1);    /*    *   ==>  Preparing: SELECT * FROM `person` WHERE `id` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, name, card_id        <==        Row: 1, 张三, 1        <==      Total: 1        Person(id=1, name=张三, card=null) 这里 card 并没有生成记录, 所以是存在问题的, 因为结果集中并没有找到叫 card 的结果    * */    System.out.println(personById);}
第一种解决方式

在这里我们可以使用resultMap进行解决该问题.

<resultMap id="getPersonByIdMap" type="Person"> <!-- id: resultMap名称 type: getPersonById 方法返回类型 -->    <result property="id" column="id"/> <!-- BEAN 中叫 id, 数据库中也叫 id -->    <result property="name" column="name"/> <!-- BEAN 中叫 name, 数据库中也叫 name -->    <!--    参考:        public class Person {            private Integer id;            private String name;            private Idencard card;        }    -->    <association property="card" javaType="Idencard"> <!-- association 用于处理复杂类型, BEAN 中叫 card, 并且参数类型是 Idencard -->        <result property="id" column="id"/> <!-- 因为 MyBatis 映射时, 是从结果集按照顺序映射的, 所以这里不会报错 -->        <result property="card_sn" column="card_sn"/>        <!--        参考:            public class Idencard {                private Integer id;                private String card_sn;            }        -->    </association></resultMap><select id="getPersonById" parameterType="Integer" resultMap="getPersonByIdMap"> <!-- 在这里我们使用 resultMap -->    SELECT * FROM `person`, `idencard` WHERE `person`.`card_id` = `idencard`.`id` AND `person`.`id` = #{id}</select>

最终运行结果:

==>  Preparing: SELECT * FROM `person`, `idencard` WHERE `person`.`card_id` = `idencard`.`id` AND `person`.`id` = ?==> Parameters: 1(Integer)<==    Columns: id, name, card_id, id, card_sn<==        Row: 1, 张三, 1, 1, 111111111110<==      Total: 1Person(id=1, name=张三, card=Idencard(id=1, card_sn=111111111110))

可以从中看到, 成功映射到card属性.

当然了, 我们平时都使用的result标签进行操作的, 在这里我们可以对主键使用id标签, 以优化查询速度, 如下:

<resultMap id="getPersonByIdMap" type="Person">    <id property="id" column="id"/> <!-- 在这里可以使用 id 标签, 因为是 primary key -->    <result property="name" column="name"/>    <association property="card" javaType="Idencard">        <id property="id" column="id"/> <!-- 这里也可以使用 id 标签, 因为是 primary key -->        <result property="card_sn" column="card_sn"/>    </association></resultMap>
第二种解决方式 (表多使用)

PersonMapper接口中定义如下方法定义:

public Person getPersonById2(Integer id);

PersonMapper.xml文件中进行实现:

<resultMap id="getPersonByIdMap2" type="Person">    <id property="id" column="id"/>    <result property="name" column="name"/>    <association property="card" column="card_id" select="com.heihu577.Mappers.IdencardMapper.getIdencardById"/>    <!-- 将查过来的 card_id, 进行调用 getIdencardById 方法 -->    <!--        这个时候又执行了 SELECT * FROM `Idencard` WHERE `id` = 刚刚查询过来的 ID, 随后封装成了 Idencard 对象    --></resultMap><select id="getPersonById2" parameterType="Integer" resultMap="getPersonByIdMap2">    SELECT * FROM `person` WHERE `id` = #{id}    <!--    目前查找出单个信息, 例如:        mysql> SELECT * FROM `person` WHERE `id` = 1;        |  1 | 张三   |       1 |        1 row in set (0.00 sec)    --></select>

运行结果:

@Testpublic void t3() {    PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);    Person personById2 = personMapper.getPersonById2(1);    System.out.println(personById2);    /*        ==>  Preparing: SELECT * FROM `person` WHERE `id` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, name, card_id        <==        Row: 1, 张三, 1        核心思想: 查询出 card_id, 然后取出 card_id 去 Idencard 表中找, 等于是将单条 SQL 语句, 使用了多条进行解决了        ====>  Preparing: SELECT * FROM `idencard` WHERE `id` = ?        ====> Parameters: 1(Integer)        <====    Columns: id, card_sn        <====        Row: 1, 111111111110        <====      Total: 1        <==      Total: 1        Person(id=1, name=张三, card=Idencard(id=1, card_sn=111111111110))    */}
注解方式实现 (不推荐)

定义IdencardMapperAnnotation文件内容如下:

public interface IdenCardMapperAnnotation {    @Select("SELECT * FROM `idencard` WHERE `id` = #{id}")    public Idencard getIdenCardById(Integer id);}

随后我们定义PersonMapperAnnotation, 内容如下:

public interface PersonMapperAnnotation {    @Select("SELECT * FROM `person` WHERE `id` = #{id}")    @Results({            @Result(id = true, property = "id", column = "id"), // 相当于 <id property="id" column="id"/>            @Result(property = "name", column = "name"), // 相当于 <result property="name" column="name"/>            @Result(property = "card", column = "card_id",one = @One(                    select = "com.heihu577.Mappers.IdenCardMapperAnnotation.getIdenCardById")            )            /*            * 相当于 <association property="card" column="card_id" select="com.heihu577.Mappers.IdencardMapper.getIdencardById"/>            * 只不过需要指定 one, 来表明是 一对一的关系, 而 one 中 select 则是上面 xml 中的 select            * */    })    public Person getPersonById(Integer id);}

最终运行结果:

@Testpublic void t2() {    PersonMapperAnnotation mapper = sqlSession.getMapper(PersonMapperAnnotation.class);    Person personById = mapper.getPersonById(1);    System.out.println(personById);    /*        ==>  Preparing: SELECT * FROM `person` WHERE `id` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, name, card_id        <==        Row: 1, 张三, 1        ====>  Preparing: SELECT * FROM `idencard` WHERE `id` = ?        ====> Parameters: 1(Integer)        <====    Columns: id, card_sn        <====        Row: 1, 111111111110        <====      Total: 1        <==      Total: 1        Person(id=1, name=张三, card=Idencard(id=1, card_sn=111111111110))    */}

一对多映射关系

一对多的案例, 我们就使用User(用户) - Pet(宠物)来进行说明关系吧. 需求说明: 实现级联查询, 通过 user 的 id 可以查询到用户信息, 并且可以查询到关联的 pet 信息, 反过来, 通过Petid可以查询到Pet的信息, 并且可以级联查询到它主人User对象的信息.

表创建
CREATE TABLE `user`(    id int primary key auto_increment,    name varchar(32) not null default '')charset=utf8 engine=innodb;CREATE TABLE `pet`(    id int primary key auto_increment,    nickname varchar(32) not null default '',    user_id int,    foreign key(user_id) references `user`(id));INSERT INTO `user` VALUES(NULL, '宋江'),(NULL,'张飞'); -- 两个主人INSERT INTO `pet` VALUES(1, '黑背', 1),(2, '小哈', 1); -- 宋江的两个宠物INSERT INTO `pet` VALUES(3, '波斯猫', 2),(4, '贵妃猫', 2); -- 张飞的两个宠物
JavaBean 创建

创建User类如下:

@Data@NoArgsConstructor@AllArgsConstructorpublic class User {    /*    * +-------+-------------+------+-----+---------+----------------+    | Field | Type        | Null | Key | Default | Extra          |    +-------+-------------+------+-----+---------+----------------+    | id    | int(11)     | NO   | PRI | NULL    | auto_increment |    | name  | varchar(32) | NO   |     |         |                |    +-------+-------------+------+-----+---------+----------------+    * */    private Integer id;    private String name;    private List<Pet> pets; // 一个 User(主人) 可以有多个 Pet(宠物)}

并且创建Pet类, 如下:

@Data@NoArgsConstructor@AllArgsConstructorpublic class Pet {    /*    * +----------+-------------+------+-----+---------+----------------+    | Field    | Type        | Null | Key | Default | Extra          |    +----------+-------------+------+-----+---------+----------------+    | id       | int(11)     | NO   | PRI | NULL    | auto_increment |    | nickname | varchar(32) | NO   |     |         |                |    | user_id  | int(11)     | YES  | MUL | NULL    |                |    +----------+-------------+------+-----+---------+----------------+    * */    private Integer id;    private String nickname;    private User user; // 一个 Pet(宠物) 只能有一个 User(主人)}
Mapper 创建

创建PetMapper接口, 内容如下:

public interface PetMapper {    // 通过 User 的 id 来获取 pet 对象, 可能有多个, 因此用 List 进行接收    public List<Pet> getPetByUserId(Integer userId);    // 通过 Pet 的 id 获取 Pet 对象    public Pet getPetById(Integer id);}

并且定义UserMapper接口, 内容如下:

public interface UserMapper {    // 通过 id 获取 User 对象    public User getUserById(Integer id);}
创建 Mapper 对应的 xml 文件

创建UserMapper.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.heihu577.Mappers.UserMapper">    <resultMap id="getUserByIdMap" type="User">        <id property="id" column="id"/>        <result property="name" column="name"/>        <!-- List 集合使用 collection 标签, 如果是 普通 Bean, 使用 association -->        <collection property="pets" column="id" ofType="Pet"                    select="com.heihu577.Mappers.PetMapper.getPetByUserId"/>        <!-- collection标签, association标签, column + select, column 是 select 语句的参数 -->        <!--            property: bean属性名称            column: 作为方法参数            ofType: 指定方法返回的类型            select: 调用目标 Mapper 方法        -->    </resultMap>    <!-- 因为 User Bean 中, 存在 pets 属性, 而该属性在数据库中是没有定义的, 所以需要 resultMap 集合 -->    <select id="getUserById" resultMap="getUserByIdMap" parameterType="Integer">        SELECT * FROM `user` WHERE `id` = #{id}    </select></mapper>

随后我们实现PetMapper.xml文件, 文件内容如下:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"        "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.heihu577.Mappers.PetMapper">    <resultMap id="PetResultMap" type="Pet">        <id property="id" column="id"/>        <result property="nickname" column="nickname"/>        <association property="user" column="user_id" select="com.heihu577.Mappers.UserMapper.getUserById"/>        <!-- association 用于指定单个 JavaBean -->    </resultMap>    <select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap"> <!-- 定义 resultMap -->        SELECT * FROM `pet` WHERE `user_id` = #{userId}    </select></mapper>
测试问题 (堆栈溢出问题)

在这里我们定义如下测试类:

public class T4 {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void t1() {        PetMapper petMapper = sqlSession.getMapper(PetMapper.class);        List<Pet> petByUserId = petMapper.getPetByUserId(1);        System.out.println(petByUserId);  // java.lang.StackOverflowError        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);        User userById = userMapper.getUserById(1);        System.out.println(userById); // java.lang.StackOverflowError    }}

System.out.println会抛出异常, 原因是, pet中有了user, 而user中也有了pet, 会爆出一个经典的堆栈溢出错误.

原因则是两者的 toString 方法的定义, 输出问题. 我们只需要在Pet && User中不定义 toString 方法即可.

我们只需要将两个Bean中的注解声明改为如下即可:

@Getter@Setter@NoArgsConstructor@AllArgsConstructor

最终运行结果:

@Testpublic void t1() {    PetMapper petMapper = sqlSession.getMapper(PetMapper.class);    List<Pet> petByUserId = petMapper.getPetByUserId(1);    for (Pet pet : petByUserId) {        System.out.println("宠物: " + pet.getNickname() + " 主人: " + pet.getUser().getName());        /*        宠物: 黑背 主人: 宋江        宠物: 小哈 主人: 宋江        */    }    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);    User userById = userMapper.getUserById(1);    System.out.println("主人: " + userById.getName() + " 宠物: " + userById.getPets());    // 主人: 宋江 宠物: [com.heihu577.Beans.Pet@2cdd0d4b, com.heihu577.Beans.Pet@7e9131d5]}
最后一个方法实现 (resultMap 复用)

在我们的PetMapper接口中, 我们还剩下一个方法没有实现, 如下:

public Pet getPetById(Integer id); // 通过 Pet 的 id 获取 Pet 对象

接下来我们进行实现, 如下:

<resultMap id="PetResultMap" type="Pet"> <!-- 因为之前定义过该 resultMap, 所以在这里发现可以进行复用 -->    <id property="id" column="id"/>    <result property="nickname" column="nickname"/>    <association property="user" column="user_id" select="com.heihu577.Mappers.UserMapper.getUserById"/></resultMap>

我们可以发现, 这里的映射关系, 我们也可以应用, 具体实现如下:

<select id="getPetById" parameterType="Integer" resultMap="PetResultMap">    SELECT * FROM `pet` WHERE `id` = #{id}</select>

最终运行结果:

@Testpublic void t1() {    PetMapper petMapper = sqlSession.getMapper(PetMapper.class);    Pet petById = petMapper.getPetById(1);    System.out.println(petById);    /*        ==>  Preparing: SELECT * FROM `pet` WHERE `id` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, nickname, user_id        <==        Row: 1, 黑背, 1        ====>  Preparing: SELECT * FROM `user` WHERE `id` = ?        ====> Parameters: 1(Integer)        <====    Columns: id, name        <====        Row: 1, 宋江        ======>  Preparing: SELECT * FROM `pet` WHERE `user_id` = ?        ======> Parameters: 1(Integer)        <======    Columns: id, nickname, user_id        <======        Row: 1, 黑背, 1        <======        Row: 2, 小哈, 1        <======      Total: 2        <====      Total: 1        <==      Total: 1        com.heihu577.Beans.Pet@2cdd0d4b    */}
注解实现多对一 (不推荐)

定义UserMapperAnnotation接口如下:

public interface UserMapperAnnotation {    @Select("SELECT * FROM `user` WHERE `id` = #{id}")    @Results({            @Result(id = true, property = "id", column = "id"),            @Result(property = "name", column = "name"),            @Result(property = "pets", column = "id", many = @Many(select = "com.heihu577.Mappers.PetMapperAnnotation" +                    ".getPetByUserId"))    })    public User getUserById(Integer id);}

该定义完全参考了我们上面的UserMapper.xml文件中的resultMap标签的定义, 对比一下即可找到规律, 下面我们来定义PetMapperAnnotation接口, 如下:

public interface PetMapperAnnotation {    @Select("SELECT * FROM `pet` WHERE `user_id` = #{userId}")    @Results(id = "myResultMap", value = { // 这里 id 则是指定 resultMap 的名称            @Result(id = true, property = "id", column = "id"),            @Result(property = "nickname", column = "nickname"),            @Result(property = "user", column = "user_id", one = @One(select = "com.heihu577.Mappers.UserMapperAnnotation.getUserById"))    })    public List<Pet> getPetByUserId(Integer userId);    @Select("SELECT * FROM `pet` WHERE `id` = #{id}")    @ResultMap("myResultMap") // 应用上面定义的 resultMap    public Pet getPetById(Integer id);}

使用完毕之后我们进行测试:

@Testpublic void t1() {    Integer userId = 1;    Integer petId = 2;    UserMapperAnnotation userMapperAnnotation = sqlSession.getMapper(UserMapperAnnotation.class);    User user = userMapperAnnotation.getUserById(userId);    System.out.println("查询 userId = " + user.getId() + " 的用户信息");    List<Pet> pets = user.getPets();    for (Pet pet : pets) {        System.out.println(user.getName() + " -> 宠物ID: " + pet.getId() + " 宠物名称: " + pet.getNickname());        /*        *   查询 userId = 1 的用户信息            宋江 -> 宠物ID: 1 宠物名称: 黑背            宋江 -> 宠物ID: 2 宠物名称: 小哈        * */    }    PetMapperAnnotation petMapperAnnotation = sqlSession.getMapper(PetMapperAnnotation.class);    Pet pet = petMapperAnnotation.getPetById(petId);    System.out.println("宠物名称: " + pet.getNickname() + " 主人: " + pet.getUser().getName());    // 宠物名称: 小哈 主人: 宋江    List<Pet> Pets = petMapperAnnotation.getPetByUserId(userId);    for (Pet pet1 : Pets) {        System.out.println(userId + " -> 宠物名称: " + pet1.getNickname());        /*        *   1 -> 宠物名称: 黑背            1 -> 宠物名称: 小哈        * */    }}

缓存

开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

一级缓存 (一个 sqlSession 一个缓存)

当我们第一次查询id = 1Monster后, 再次查询id = 1Monster对象, 就会直接从一级缓存获取, 不会再次发出SQL. 接下来我们创建mybatis_cache模块, 并引用常用依赖即可, 配置方面就不多说了, 并且我们将前面所用到的Monster类, MonsterMapper, MonsterMapper.xml文件, MyBatisUtils文件拷贝过来. 当一切运行正常时, 我们开始测试:

public class T1 {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void t1() {        MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);        Monster monster = monsterMapper.getMonsterById(1);        System.out.println(monster);        // Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}    }}
快速入门

当我们测试查询SQL语句时, 我们可以通过控制台看到, MyBatis默认开启一级缓存, 如下:

    @Test    public void t1() {        MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);        Monster monster1 = monsterMapper.getMonsterById(1);        System.out.println("=============================");        Monster monster2 = monsterMapper.getMonsterById(1);        /*        *   ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?            ==> Parameters: 1(Integer)            <==    Columns: id, age, birthday, email, gender, name, salary            <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3            <==      Total: 1            =============================        * */    }

可以看到, 总共发送了一次SQL语句. 具体debug过程, 放在第四阶段 主流框架主流框架【5】- MyBatis108.一级缓存执行流程Debug.mp4. 其中存储结构如下:开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis在我们第二次发送相同的SQL语句时, 会从缓存中取出.

一级缓存失效问题

当我们两次发送相同的SQL语句时, 除非将sqlSession关闭掉, 并且将其重新获取, 即可发送两次SQL语句.

public class T1 {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void t1() {        MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);        Monster monster1 = monsterMapper.getMonsterById(1);        sqlSession.close();        System.out.println("=============================");        sqlSession = MyBatisUtils.getSqlSession(); // 重新获取        monsterMapper = sqlSession.getMapper(MonsterMapper.class);        Monster monster2 = monsterMapper.getMonsterById(1);        /*        *   ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?            ==> Parameters: 1(Integer)            <==    Columns: id, age, birthday, email, gender, name, salary            <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3            <==      Total: 1            Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8297b3a]            Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8297b3a]            Returned connection 136936250 to pool.            =============================            Opening JDBC Connection            Checked out connection 136936250 from pool.            Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8297b3a]            ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?            ==> Parameters: 1(Integer)            <==    Columns: id, age, birthday, email, gender, name, salary            <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3            <==      Total: 1        * */    }}

或者我们使用sqlSession.clearCache()即可导致一级缓存失效.

准备如下代码:

@Testpublic void t1() {    MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster1 = monsterMapper.getMonsterById(1);    sqlSession.clearCache();    System.out.println("=============================");    monsterMapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster2 = monsterMapper.getMonsterById(1);    /*    *   ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3        <==      Total: 1        Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8297b3a]        Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8297b3a]        Returned connection 136936250 to pool.        =============================        Opening JDBC Connection        Checked out connection 136936250 from pool.        Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8297b3a]        ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3        <==      Total: 1    * */}

还有一种情况, 就是当我们修改数据库内容后, 一级缓存也会失效.

@Testpublic void t1() {    MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);    Monster monster = monsterMapper.getMonsterById(1);    System.out.println(monster);    monsterMapper.updateMonsterById(            new Monster(1, 0, LocalDateTime.now(), "[email protected]", 9, "黑客2", 123d)    ); // 修改 ID 为 1 的 Monster 表    monster = monsterMapper.getMonsterById(1);    System.out.println(monster);    /*    *   ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3        <==      Total: 1        Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}        ==>  Preparing: UPDATE `monster` SET `age` = ?, `birthday` = ?, `email` = ?, `gender` = ?, `name` = ?, `salary` = ? WHERE `id` = ?        ==> Parameters: 0(Integer), 2024-04-27T14:20:27.812(LocalDateTime), [email protected](String), 9(Integer), 黑客2(String), 123.0(Double), 1(Integer)        <==    Updates: 1        ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?        ==> Parameters: 1(Integer)        <==    Columns: id, age, birthday, email, gender, name, salary        <==        Row: 1, 0, 2024-04-27, [email protected], 9, 黑客2, 123.0        <==      Total: 1        Monster{id=1, age=0, birthday=2024-04-27T00:00, email='[email protected]', gender=9, name='黑客2', salary=123.0}    * */    // 按道理这里需要使用 sqlSession.commit(), 这里不 commit() 也会清空缓存.}

二级缓存 (全局缓存)

开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis二级缓存可以参考, mybatis官方文档:https://mybatis.net.cn/sqlmap-xml.html#cache

快速入门

我们需要在mybatis-config.xml文件中定义如下配置:

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

随后我们需要将我们的JavaBean类, 进行实现Serializable接口, 因为二级缓存有可能会用到序列化技术. 如下:

public class Monster implements Serializable {    private static final long serialVersionUID = 1L;    // ... 其他内容}

随后我们将配置标签放入到MonsterMapper.xml文件中, 如下:

<mapper namespace="com.heihu577.Mapper.MonsterMapper">    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <!-- 参数含义官网有说明 -->    <!-- 其他内容... --></mapper>

那么我们可以进行测试:

public class T2 {    private SqlSession sqlSession = MyBatisUtils.getSqlSession();    @Test    public void t1() {        MonsterMapper monsterMapper = sqlSession.getMapper(MonsterMapper.class);        Monster monster01 = monsterMapper.getMonsterById(1);        System.out.println(monster01);        System.out.println("===================================");        sqlSession.close();        // 关闭后第二次获取 sqlSession        sqlSession = MyBatisUtils.getSqlSession();        monsterMapper = sqlSession.getMapper(MonsterMapper.class);        monster01 = monsterMapper.getMonsterById(1);        System.out.println(monster01);        /*        *   ==>  Preparing: SELECT * FROM `Monster` WHERE `ID` = ?            ==> Parameters: 1(Integer)            <==    Columns: id, age, birthday, email, gender, name, salary            <==        Row: 1, 199, 2024-04-19, [email protected], 6, heihu888, 123.3            <==      Total: 1            Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}            ===================================            Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@38afe297]            Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@38afe297]            Returned connection 951050903 to pool.            Cache Hit Ratio [com.heihu577.Mapper.MonsterMapper]: 0.5            Monster{id=1, age=199, birthday=2024-04-19T00:00, email='[email protected]', gender=6, name='heihu888', salary=123.3}        * */    }}
注意事项 && 使用陷阱
关闭二级缓存总开关

若在mybatis-config.xml文件中定义如下标签:

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

则关闭二级缓存总开关.

二级缓存开启前提

因为二级缓存是基于Mapper的, 如果对应Mapper并没有定义<cache>标签, 则该Mapper并没有开启二级缓存.

对应方法不开启二级缓存

例如:

<select id="getAllMonsters" resultType="Monster">    SELECT * FROM `Monster`</select><select id="getMonsterById" resultType="Monster" parameterType="java.lang.Integer" useCache="false">    SELECT * FROM `Monster` WHERE `ID` = #{id}</select>

在配置好二级缓存的前提下, getMonsterById因为配置了useCache属性, 会导致二级缓存失效. 而getAllMonsters则不会.

默认情况下不需要使用该属性, 除非有特殊的业务需求.

增删改语句刷新缓存设置

因为增删改语句会刷新缓存, 如果不刷新缓存, 可能会造成数据的脏读(数据不一致), 所以在这里有一个flushCache属性, 默认为true.

<delete id="deleteMonster" parameterType="java.lang.Integer" flushCache="true">    DELETE FROM `monster` WHERE `id` = #{id}</delete>
一级缓存 二级缓存执行顺序

其中执行顺序为: 二级缓存 > 一级缓存 > 数据库

当然, 当我们定义了二级缓存, 当一级缓存sqlSession被关闭, MyBatis会把一级缓存的数据信息, 放入到二级缓存中.

ehCache

开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis
pom.xml 文件引入依赖

我们需要引入如下依赖:

<dependency>    <groupId>net.sf.ehcache</groupId>    <artifactId>ehcache-core</artifactId>    <version>2.6.11</version></dependency><dependency>    <groupId>org.slf4j</groupId>    <artifactId>slf4j-api</artifactId>    <version>1.7.25</version></dependency><dependency>    <groupId>org.mybatis.caches</groupId>    <artifactId>mybatis-ehcache</artifactId>    <version>1.2.1</version></dependency>
mybatis-config.xml 设置 cacheEnabled

虽然cacheEnabled默认为true, 但是这里根据习惯还是显示的声明一下:

<setting name="cacheEnabled" value="true"/>
ehcache 配置文件

将如下配置文件内容放入到resources/目录下, 命名为: ehcache.xml

<?xml version="1.0" encoding="UTF-8"?><ehcache>    <!--       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:       user.home – 用户主目录       user.dir  – 用户当前工作目录       java.io.tmpdir – 默认临时文件路径     -->    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>    <!--       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。     -->    <!--      name:缓存名称。      maxElementsInMemory:缓存最大数目      maxElementsOnDisk:硬盘最大缓存个数。      eternal:对象是否永久有效,一但设置了,timeout将不起作用。      overflowToDisk:是否保存到磁盘,当系统当机时      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。      clearOnFlush:内存数量最大时是否清除。      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。      FIFO,first in first out,这个是大家最熟的,先进先出。      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。   -->    <defaultCache            eternal="false"            maxElementsInMemory="10000"            overflowToDisk="false"            diskPersistent="false"            timeToIdleSeconds="1800"            timeToLiveSeconds="259200"            memoryStoreEvictionPolicy="LRU"/></ehcache>
在 Mapper 中进行定义 ehcache

在的MonsterMapper.xml文件中, 定义如下标签:

<!--    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <!– 这是 MyBatis 自带的二级缓存策略 –>--><cache type="org.mybatis.caches.ehcache.EhcacheCache"/> <!-- 引入 ehCache -->

最终debug结果 (使用正常的查询语句即可调用出来):开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

理解 EhCache 和 MyBatis 缓存的关系

MyBatis 提供了一个接口 Cache 只要实现了 Cache 接口, 就可以作为二级缓存产品和 MyBatis 整合使用, EhCache 就是实现了该接口. MyBatis 默认情况(一级缓存), 是使用的 PerpetualCache 类实现 Cache 接口的, 是核心类.

原文始发于微信公众号(Heihu Share):开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatis

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月29日16:52:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   开发基础 | MyBatis 基本使用总结 && 手动实现 MyBatishttps://cn-sec.com/archives/3007471.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息