跳至主要內容

MyBatis开发实战

wangdx大约 62 分钟

MyBatis 简介

JPA 与 MyBatis

  • 数据库是现代应用开发的核心存储介质,虽然 Java 提供了 JDBC 服务标准,,但是其开发处理都过于繁琐,并且采用 SQL 命令的形式也不符合面向对象的设计思想。在这样的背景下,大量的开发工程师采用了 ORMapping 的设计思想,这样可以基于数据对象的方式实现数据表中的数据处理操作。而随着 Java 技术的发展,ORMapping 组件也在逐步增多,例如:JDO、EntityBean、Hibernate、iBatis、JPA、MvBatis,这之中如果要考虑数据库的可移植性,那么会优先选择 Hibernate,而如果要考虑到数据层的处理性能,则会优先选择 MyBatis。

MyBatis 下载页

  • MyBatis 是在 2010 年 06 月 16 日由 Apache 交由 Google 进行管理的,在这之前该框架的名称为“iBatis”,而这个名称主要由“internet”与“abatis”两个单词所组成,开发者可以通过“mybatis.org”获取该框架有关的信息

MyBatis 开发架构

  • MyBatis 框架实现了传统 JDBC 操作的封装,同时考虑到不同项目开发中的数据操作需求,所以其只是实现了 JDBC 处理逻辑的封装,而在项目中所使用的 SQL 操作则可以由开发人员自行定义。考虑到返回结果的处理,MvBatis 也可以直接使用最原始的 Java 类结构进行定义,使得整个的开发过程非常的直观,同时也避免了 Hibernate 这种重度封装所带来的性能问题,下图展示了 MyBatis 开发中的核心结构。

开发 MyBatis 应用

MyBatis 代码开发结构

  • MyBatis 是基于 JDBC 实现的封装操作,所以要进行 MyBatis 开发一定就需要使用到 SQL 数据库,同时为了对项目单元进行有效的管理,也需要提供有数据库连接配置文件 SQL 定义文件等

MyBatis 核心类关联结构

  • 在使用 MyBatis 开发时,所有的配置文件的名称均可以由开发者自行定义,数据操作方法定义在 SqlSession 接口之中,而要想获取到该接口的实例,则需要通过 SqlSessionFactory 接口提供的操作方法。要想 SqlSessionFactory 接口实例,可以通过 SqlSessionFactoryBuilder 类提供的 build()方法完成,该方法可以根据指定的输入流实现 MyBatis 核心配置文件的加载

1、
// https://mvnrepository.com/artifact/org.mybatis/mybatis
implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.11'

2、
project(":mybatis") {
    dependencies{
        implementation(libraries.'mybatis')
        implementation(libraries.'mysql-connector-java')
    }
}

3、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE book (
   bid 		BIGINT AUTO_INCREMENT 	comment '图书ID',
   name 		VARCHAR(50) 		comment '图书名称',
   author 	VARCHAR(50) 		comment '图书作者',
   price 		DOUBLE 			comment '图书价格',
   CONSTRAINT pk_bid PRIMARY KEY(bid)
) engine=innodb;


4、

package com.yootk.vo; // 这个包名称你可以随意起

public class Book { // 图书结构类,类名称随意
    private Long bid; // 图书编号
    private String name; // 图书名称
    private String author; // 图书作者
    private Double price; // 图书价格
    public Long getBid() {
        return bid;
    }
    public void setBid(Long bid) {
        this.bid = bid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public Double getPrice() {
        return price;
    }
    public void setPrice(Double price) {
        this.price = price;
    }
}



5、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.BookNS">
    <!-- 定义增加数据的操作配置,同时指定参数类型 -->
    <!-- 此处的id表示的是用户操作时指定标记,SQL操作时:“BookNS.doCreate” -->
    <!-- parameterType指的是参数的类型,此时应该操作的是简单Java类 -->
    <insert id="doCreate" parameterType="com.yootk.vo.Book">
        INSERT INTO book(name, author, price) VALUES (#{name}, #{author}, #{price});
    </insert>
</mapper>


6、
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development"> 		<!-- 定义数据库连接池 -->
        <environment id="development"> 		<!-- 数据库资源配置 -->
            <transactionManager type="jdbc" /> 	<!-- JDBC事务管理 -->
            <!-- 定义数据源,MyBatis内部提供有三种数据源的支持,分别为: -->
            <!-- POOLED:采用MyBatis内置的连接池进行数据库连接管理 -->
            <!-- UNPOOLED:不使用连接池管理数据库连接 -->
            <!-- JNDI:引入外部的数据库连接池配置 -->
            <dataSource type="POOLED">		<!-- 配置数据源 -->
                <!-- 数据库的驱动程序路径,配置的mysql驱动包中的类名称 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <!-- 数据库的连接地址 -->
                <property name="url" value="jdbc:mysql://localhost:3306/yootk" />
                <!-- 数据库连接的用户名 -->
                <property name="username" value="root" />
                <!-- 数据库的连接密码 -->
                <property name="password" value="mysqladmin" />
            </dataSource>
        </environment>
    </environments>
    <mappers>  					<!-- 配置SQL映射文件路径 -->
        <mapper resource="mybatis/mapper/Book.xml" />  <!-- 映射文件路径 -->
    </mappers>
</configuration>


5、
package com.yootk.test;

import com.yootk.vo.Book;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;

public class TestBook {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestBook.class);
    @Test
    public void testDoCreate() throws Exception {
        // 1、需要加载mybatis.cfg.xml的资源项,这个资源项其实就是一个标准的输入流的读取
        InputStream inputStream =
                Resources.getResourceAsStream("mybatis/mybatis.cfg.xml"); // 数据流
        // 2、根据mybatis.cfg.xml配置的定义,获取到SQL连接工厂对象实例
        SqlSessionFactory sessionFactory =
            new SqlSessionFactoryBuilder().build(inputStream); // 构建SQL会话工厂
        // 3、通过SQL连接工厂获取到SqlSession接口实例,以实现最终的数据操作
        SqlSession session = sessionFactory.openSession(); // 获取连接
        // 4、将所需要保存的数据保存在Book对象实例之中
        Book book = new Book();  // 实例化VO对象
        book.setName("SpringBoot开发实战");
        book.setAuthor("李兴华");
        book.setPrice(69.8);
        // 5、在执行具体的SQL操作时需要提供有准确的命令定义,而命令都在映射文件里面
        LOGGER.info("【数据增加】更新数据行数:{}", session.insert(
                "com.yootk.mapper.BookNS.doCreate", book)); // 数据增加
        // 6、所有的操作都受到事务的保护,那么需要手工进行事务提交
        session.commit(); // 提交事务
        session.close(); // 关闭操作
        inputStream.close(); // 关闭IO流
    }
}

MyBatis 连接工厂

MyBatisSessionFactory 工厂类

  • 在 MyBatis 中所有的数据处理操作全部都是由 SqlSession 接口提供的方法完成的要想获取该接口实例则需要通过 SqlSessionFactoryBuilder 类提供的 build()方法完成,但是如果每一次的数据库操作都使用这样的操作过程,那么对于代码的开发而言就会显得非常的繁琐,且不便于代码的维护。此时最佳的做法就是创建一个 MyBatisSessionFactory 工厂类
1、
package com.yootk.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

/**
 * 创建连接工厂类
 * @author 爆可爱的小李老师
 */
public class MyBatisSessionFactory {
    private static final String CONFIG_FILE = "mybatis/mybatis.cfg.xml"; // 配置文件路径
    // 所有的MyBatis操作的连接对象都需要通过SqlSessionFactory接口实例获取
    private static SqlSessionFactory sessionFactory; // 连接工厂实例
    private static final ThreadLocal<SqlSession> SESSION_THREAD_LOCAL =
            new ThreadLocal<>(); // 保存每一个线程的连接对象
    static { // 静态代码块
        buildSqlSessionFactory(); // 类加载的时候就需要获取到工厂实例
    }
    /**
     * 构建SqlSession工厂类
     * @return SqlSessionFactory接口实例
     */
    private static SqlSessionFactory buildSqlSessionFactory() {
        try {
            InputStream input = Resources.getResourceAsStream(CONFIG_FILE); // 读取配置文件
            sessionFactory = new SqlSessionFactoryBuilder().build(input); // 配置解析
        } catch (Exception e) {}
        return sessionFactory;
    }

    /**
     * 获取SqlSession接口对象实例
     * @return SqlSession接口实例
     */
    public static SqlSession getSession() {
        SqlSession session = SESSION_THREAD_LOCAL.get(); // 获取当前线程的对象
        if (session == null) { // 没有连接对象
            session = sessionFactory.openSession(); // 创建新的连接
            SESSION_THREAD_LOCAL.set(session); // 保存在集合之中
        }
        return session;
    }

    /**
     * 直接返回当前的连接工厂实例
     * @return SqlSessionFactory接口实例
     */
    public static SqlSessionFactory getSessionFactory() {
        return sessionFactory;
    }
    /**
     * 关闭当前的SqlSession接口实例
     */
    public static void close() {
        SqlSession session = SESSION_THREAD_LOCAL.get(); // 获取当前线程实例
        if (session != null) {
            session.close(); // 关闭会话
            SESSION_THREAD_LOCAL.remove(); // 清除线程对象
        }
    }
}


2、
package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Book;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;

public class TestBook {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestBook.class);
    @Test
    public void testDoCreate() throws Exception {
        // 1、将所需要保存的数据保存在Book对象实例之中
        Book book = new Book();  // 实例化VO对象
        book.setName("SpringBoot开发实战");
        book.setAuthor("李兴华");
        book.setPrice(69.8);
        // 2、在执行具体的SQL操作时需要提供有准确的命令定义,而命令都在映射文件里面
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doCreate", book)); // 数据增加
        // 3、所有的操作都受到事务的保护,那么需要手工进行事务提交
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }
}

别名配置

别名配置

  • MyBatis 实现数据操作主要依靠的是 Mapper 配置文件,而所有定义在 Mapper 配置文件中的 SQL 命令,如果需要进行数据的接收,则要通过 parameterType 配置参数类型这样才可以在 SQL 命令执行时,利用反射获取对应占位符数据,在默认情况下,开发老直接编写类的完整名称即可,但是由于大部分的 MyBatis 应用都会基于特定的表结构映射类的方式进行参数传递,所以也可以通过别名的方式进行配置。
  • MyBatis 中的别名配置主要是在 MyBatis 核心配置文件(mybatis.cfa.xml)中配置的,使用“<typeAliases>”定义别名配置,而在该元素中提供有两个配置的子元素,分别表示两种不同的配置模式:
    • <typeAlias>:由用户自定义别名:
    • '<package>”:包扫描配置,使用类名称作为别名定义
1、
    <typeAliases>
        <typeAlias type="com.yootk.vo.Book" alias="Book"/>
    </typeAliases>

2、
    <typeAliases>
        <package name="com.yootk.vo"/>
    </typeAliases>

获取生成主键

获取 Oracle 序列内容

  • MYSQL 中的自动增长列可以直接利用 JDBC 的主键返回机制获取,但是如果现在开发者使用的是 Oracle 数据库,并且基于序列对象(Sequence)的形式实现主键生成,那么这时只能够通过查询序列的方式来获取生成后的主键
1、
package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Book;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;

public class TestBook {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestBook.class);
    @Test
    public void testDoCreate() throws Exception {
        // 1、将所需要保存的数据保存在Book对象实例之中
        Book book = new Book();  // 实例化VO对象
        book.setName("SpringBoot开发实战");
        book.setAuthor("李兴华");
        book.setPrice(69.8);
        // 2、在执行具体的SQL操作时需要提供有准确的命令定义,而命令都在映射文件里面
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doCreate", book)); // 数据增加
        LOGGER.info("【获取主键】当前新增的图书主键内容为:{}", book.getBid()); // 日志输出
        // 3、所有的操作都受到事务的保护,那么需要手工进行事务提交
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }
}


2、
    <insert id="doCreate" parameterType="Book" keyProperty="bid"
            keyColumn="bid" useGeneratedKeys="true">
        INSERT INTO book(name, author, price) VALUES (#{name}, #{author}, #{price});
    </insert>

3、
    <insert id="doCreate" parameterType="Book">
        INSERT INTO book(name, author, price) VALUES (#{name}, #{author}, #{price});
        <selectKey keyProperty="bid" keyColumn="bid"
                   order="AFTER" resultType="java.lang.Long">
            SELECT LAST_INSERT_ID()
        </selectKey>
    </insert>

MyBatis 数据更新操作

1、

    <!--  MyBatis映射文件配置中insert、update、delete三个配置项的作用是相同的,随意使用  -->
    <update id="doEdit" parameterType="Book">
        UPDATE book SET name=#{name}, author=#{author}, price=#{price} WHERE bid=#{bid};
    </update>
    <delete id="doRemove" parameterType="java.lang.Long">
        DELETE FROM book WHERE bid=#{bid};
    </delete>
2、
    @Test
    public void testDoEdit() {
        Book book = new Book();  // 实例化VO对象
        book.setBid(3L); // 设置要修改的ID内容
        book.setName("SpringCloud开发实战");
        book.setAuthor("李兴华");
        book.setPrice(99.8);
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doEdit", book)); // 数据修改
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }

3、
    @Test
    public void testDoRemove() {
        LOGGER.info("【数据删除】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .delete("com.yootk.mapper.BookNS.doRemove", 5L)); // 数据修改
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }

MyBatis 数据查询操作

1、

    <!--  定义数据查询的操作,根据ID查询需要传递数据的主键,返回结果为VO对象实例  -->
    <select id="findById" parameterType="java.lang.Long" resultType="Book">
        SELECT bid, name, author, price FROM book WHERE bid=#{bid}
    </select>
    <!--  查询全部数据肯定返回List集合,但是此处定义的是List保存项的类型  -->
    <select id="findAll" resultType="Book">
        SELECT bid, name, author, price FROM book;
    </select>
    <!--  分页数据查询的时候,需要传递若干数据项,只能够通过Map集合进行传递  -->
    <!--  要传递的内容:模糊查询列、查询关键字、开始行(计算的结果)、每次加载的数量 -->
    <!--  MyBatis内部使用的是PreparedStatement,所以“#{}”转为“?”,“${}”转为具体内容  -->
    <select id="findSplit" resultType="Book" parameterType="java.util.Map">
        SELECT bid, name, author, price FROM book
        WHERE ${column} LIKE #{keyword} LIMIT #{start}, #{lineSize};
    </select>
    <select id="getAllCount" resultType="java.lang.Long" parameterType="java.util.Map">
        SELECT COUNT(*) FROM book WHERE ${column} LIKE #{keyword};
    </select>
2、
    @Test
    public void testFindById() {
        // 此时的查询直接返回一个VO对象,所以符合ORM的设计要求
        Book book = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", 1L); // 数据查询
        LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        MyBatisSessionFactory.close();
    }

3、
    @Test
    public void testFindAll() {
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findAll"); // 查询全部
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
        MyBatisSessionFactory.close();
    }

4、
    @Test
    public void testFindSpit() {
        // MyBatis的调用的时候只允许传递一个参数项,所以需要使用Map封装多个参数
        Map<String, Object> splitParams = new HashMap<>();
        splitParams.put("column", "name"); // 模糊查询列
        splitParams.put("keyword", "%Spring%"); // 查询关键字
        splitParams.put("start", 1); // 开始行数
        splitParams.put("lineSize", 3); // 加载个数
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findSplit", splitParams); // 分页查询
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
        MyBatisSessionFactory.close();
    }

5、
    @Test
    public void testGetAllCount() {
        // MyBatis的调用的时候只允许传递一个参数项,所以需要使用Map封装多个参数
        Map<String, Object> splitParams = new HashMap<>();
        splitParams.put("column", "name"); // 模糊查询列
        splitParams.put("keyword", "%Spring%"); // 查询关键字
        Long count = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.getAllCount", splitParams); // 分页查询
        LOGGER.info("数据个数统计:{}", count);
        MyBatisSessionFactory.close();
    }

ResultHandler

ResultHandler 转换处理

  • ResultHandler 接口需要与 SqlSession 接口中的 select()方法结合在一起使用,当结果集返回之后,会自动将每一个查询的结果交由 ResultHandler 接口中的 handleResult()方法进行处理,并通过 ResultContext 接口获取当前的对象实例以进行后续的数据处理
1、
    @Test
    public void testResultHandler() {
        Map<Long, String> bookMap = new HashMap<>();
        MyBatisSessionFactory.getSession().select(
                "com.yootk.mapper.BookNS.findAll", new ResultHandler<Book>() { // 需要对结果进行处理
                    @Override
                    public void handleResult(ResultContext resultContext) {
                        Book book = (Book) resultContext.getResultObject(); // 获取返回的结果
                        bookMap.put(book.getBid(), "【" + book.getAuthor() + "】" + book.getName());
                    }
                });
        LOGGER.info("{}", bookMap);
    }

if 语句

静态 SQL 查询

  • 在实际的项目开发中,一张数据表中往往会存在有若干个字段,而后为了便于数据查询的需要,可能需要开发者提供不同字段的查询处理,传统的做法是分别定义有多个不同的 SQL 映射,而后在调用时传入所需的数据
1、
    <!--  此时的操作接收的参数类型为Book,因为这个类可以保存有四种属性  -->
    <select id="findByColumn" parameterType="Book" resultType="Book">
        SELECT bid, name, author, price FROM book
        <if test="bid != null and bid != &quot;&quot;">
            WHERE bid=#{bid}
        </if>
        <if test="name != null and name != &quot;&quot;">
            WHERE name=#{name}
        </if>
        <if test="author != null and author != &quot;&quot;">
            WHERE author=#{author}
        </if>
        <if test="price != null and price != 0.0">
            WHERE price=#{price}
        </if>
    </select>

2、
    @Test
    public void testFindByColumn() {
        Book param = new Book(); // 通过实体类传递属性内容
        param.setBid(1L); // 【四选一】根据ID进行数据查询
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findByColumn", param);
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
    }

3、
    @Test
    public void testFindByColumn() {
        Book param = new Book(); // 通过实体类传递属性内容
        // param.setBid(1L); // 【四选一】根据ID进行数据查询
        param.setName("SpringBoot开发实战"); // 【四选一】根据图书的名称进行数据查询
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findByColumn", param);
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
    }

4、
    @Test
    public void testFindByColumn() {
        Book param = new Book(); // 通过实体类传递属性内容
        // param.setBid(1L); // 【四选一】根据ID进行数据查询
        // param.setName("SpringBoot开发实战"); // 【四选一】根据图书的名称进行数据查询
        param.setAuthor("李兴华"); // 【四选一】根据图书作者进行查询
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findByColumn", param);
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
    }

5、
    @Test
    public void testFindByColumn() {
        Book param = new Book(); // 通过实体类传递属性内容
        // param.setBid(1L); // 【四选一】根据ID进行数据查询
        // param.setName("SpringBoot开发实战"); // 【四选一】根据图书的名称进行数据查询
        // param.setAuthor("李兴华"); // 【四选一】根据图书作者进行查询
        param.setPrice(69.8); // 【四选一】根据图书价格进行查询
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findByColumn", param);
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
    }

6、

    <insert id="doCreate" parameterType="Book" keyProperty="bid"
            keyColumn="bid" useGeneratedKeys="true">
        INSERT INTO book (name, author, price) VALUES
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="name == null or name == &quot;&quot;">
                'NoName',
            </if>
            <if test="name != null and name == &quot;&quot;">
                #{name},
            </if>
            <if test="author == null or author == &quot;&quot;">
                'YootkAuthor',
            </if>
            <if test="author != null and author == &quot;&quot;">
                #{author},
            </if>
            <if test="price == null or price == 0.0">
                -1,
            </if>
            <if test="price != null and price != 0.0">
                #{price},
            </if>
        </trim>
    </insert>
7、
    @Test
    public void testDoCreate() throws Exception {
        // 1、将所需要保存的数据保存在Book对象实例之中
        Book book = new Book();  // 实例化VO对象
        // book.setName("SpringBoot开发实战");
        // book.setAuthor("李兴华");
        // book.setPrice(69.8);
        // 2、在执行具体的SQL操作时需要提供有准确的命令定义,而命令都在映射文件里面
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doCreate", book)); // 数据增加
        LOGGER.info("【获取主键】当前新增的图书主键内容为:{}", book.getBid()); // 日志输出
        // 3、所有的操作都受到事务的保护,那么需要手工进行事务提交
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }

choose 语句

1、
   <!--  之所以使用Book,是因为Book可以同时传递多个不同的数据项  -->
    <select id="findByCondition" parameterType="Book" resultType="Book">
        SELECT bid, name, author, price FROM book
        <where>
            <choose>
                <when test="name != null and author != null">
                    name=#{name} AND author=#{author}
                </when>
                <when test="name != null and author == null">
                    name=#{name}
                </when>
                <when test="name == null and author != null">
                    author=#{author}
                </when>
                <otherwise>
                    1=1
                </otherwise>
            </choose>
        </where>
    </select>

2、

    @Test
    public void testFindByCondtion() {
        // 你如果觉得Book不方便,Book会影响你发挥,也可以使用Map进行存储
        Book param = new Book(); // 通过Book传递的查询数据
        param.setAuthor("李兴华"); // 查询条件
        param.setName("SSM开发实战");
        List<Book> books = MyBatisSessionFactory.getSession().selectList(
                "com.yootk.mapper.BookNS.findByCondition", param); // 动态查询
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
    }
3、
    <!--  之所以使用Book,是因为Book可以同时传递多个不同的数据项  -->
    <select id="findByCondition" parameterType="Book" resultType="Book">
        SELECT bid, name, author, price FROM book
        <where>
            <choose>
                <when test="name != null and author != null">
                    name=#{name} AND author=#{author}
                </when>
                <when test="name != null and author == null">
                    name=#{name}
                </when>
                <when test="name == null and author != null">
                    author=#{author}
                </when>
            </choose>
        </where>
    </select>

set 语句

1、
    <!--  MyBatis映射文件配置中insert、update、delete三个配置项的作用是相同的,随意使用  -->
    <update id="doEdit" parameterType="Book">
        UPDATE book
        <set>
            <if test="name != null and name != &quot;&quot;">
                name = #{name},
            </if>
            <if test="author != null and author != &quot;&quot;">
                author = #{author},
            </if>
            <if test="price != null">
                price=#{price},
            </if>
        </set>
        <where>
            <if test="bid != null and bid != 0">
                bid=#{bid}
            </if>
            <if test="bid == null">
                bid=-1
            </if>
        </where>
    </update>

2、

    @Test
    public void testDoEdit() {
        Book book = new Book();  // 实例化VO对象
        book.setBid(3L); // 设置要修改的ID内容
        book.setName("Spring开发实战");
        book.setAuthor("李兴华");
        book.setPrice(89.8);
        LOGGER.info("【数据修改】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doEdit", book)); // 数据修改
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }
3、
    @Test
    public void testDoEdit() {
        Book book = new Book();  // 实例化VO对象
        book.setBid(3L); // 设置要修改的ID内容
        book.setName("SSM开发实战");
        // book.setAuthor("李兴华");
        // book.setPrice(89.8);
        LOGGER.info("【数据修改】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doEdit", book)); // 数据修改
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }

foreach 语句

1、
    <delete id="doRemove" parameterType="java.lang.Long">
        DELETE FROM book
        <where>
            bid IN
            <foreach collection="array" open="(" close=")" separator="," item="bid">
                #{bid}
            </foreach>
        </where>
    </delete>

2、
    @Test
    public void testDoRemove() {
        // 按照正常数据的传递来讲,此时的操作应该使用的就是Set接收要删除数据的编号集合,没有重复
        Set<Long> bids = Set.of(7L, 8L, 9L); // 模拟要删除的数据项
        LOGGER.info("【数据删除】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .delete("com.yootk.mapper.BookNS.doRemove", bids.toArray())); // 数据删除
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }

3、
    <!--  定义公共的部分SQL语句  -->
    <sql id="selectBook">
        SELECT bid, name, author, price FROM book
    </sql>
    <select id="findByIds" parameterType="java.lang.Long" resultType="Book">
        <include refid="selectBook"/>
        <where>
            bid IN
            <foreach collection="array" open="(" close=")" separator="," item="bid">
                #{bid}
            </foreach>
        </where>
    </select>

4、
    @Test
    public void testFindByIds() {
        long ids [] = new long[] {1L, 3L, 5L};
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.findByIds", ids); // 查询全部
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
        MyBatisSessionFactory.close();
    }

5、
    <insert id="doCreateBatch" parameterType="Book">
        INSERT INTO book (name, author, price) VALUES
        <foreach collection="list" separator="," item="book">
            (#{book.name}, #{book.author}, #{book.price})
        </foreach>
    </insert>

6、
    @Test
    public void testDoCreateBatch() {
        List<Book> all = new ArrayList<>();
        for (int x = 0; x < 5; x++) {
            Book book = new Book();
            book.setName("SpringBoot开发实战 - " + x);
            book.setAuthor("李兴华");
            book.setPrice(69.8 + x);
            all.add(book);
        }
        int line = MyBatisSessionFactory.getSession().insert(
                "com.yootk.mapper.BookNS.doCreateBatch", all);
        LOGGER.info("【数据更新】更新影响的数据行数:{}", line);
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }

一级缓存

SalSession 一级缓存

  • 一级缓存是 MyBatis 默认开启的缓存支持,当使用 SqlSession 进行了指定 ID 数据查询后对应的查询结果会自动保存在一级缓存之中,这样当再次进行同样 ID 的数据查询时将不会再重复发出查询指令,利用这样的处理机制可以有效的提升同一 ID 数据的查询性能。

缓存数据不一致

  • 由于一级缓存始终存在,所以在当前的 SalSession 接口实例未关闭的情况下,该数据将始终被缓存,即便此时缓存中的数据已经和实际数据表中的数据不一致,那么再次查询时也只是会进行缓存数据的加载。为了解决这样的问题,可以使用 SqlSession 接口提供的 clearCache()方法进行缓存清除操作
1、
package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Book;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class TestMyBatisCache {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestMyBatisCache.class);

    @Test
    public void testFindById() {
        long bid = 1L; // 查询数据编号
        Book bookA = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询一】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookA.getBid(), bookA.getName(), bookA.getAuthor(), bookA.getPrice());
        Book bookB = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询二】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookB.getBid(), bookB.getName(), bookB.getAuthor(), bookB.getPrice());
        MyBatisSessionFactory.close();
    }
}


2、

    @Test
    public void testFindById() {
        long bid = 1L; // 查询数据编号
        Book bookA = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询一】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookA.getBid(), bookA.getName(), bookA.getAuthor(), bookA.getPrice());
        bookA.setName("Redis开发实战"); // 修改缓存项
        bookA.setAuthor("爆可爱的小李老师"); // 修改缓存项
        Book bookB = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询二】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookB.getBid(), bookB.getName(), bookB.getAuthor(), bookB.getPrice());
        MyBatisSessionFactory.close();
    }
3、
    @Test
    public void testFindById() {
        long bid = 1L; // 查询数据编号
        Book bookA = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询一】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookA.getBid(), bookA.getName(), bookA.getAuthor(), bookA.getPrice());
        bookA.setName("Redis开发实战"); // 修改缓存项
        bookA.setAuthor("爆可爱的小李老师"); // 修改缓存项
        LOGGER.info("【数据修改一】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookA.getBid(), bookA.getName(), bookA.getAuthor(), bookA.getPrice());
        MyBatisSessionFactory.getSession().clearCache(); // 清除缓存数据
        Book bookB = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询二】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookB.getBid(), bookB.getName(), bookB.getAuthor(), bookB.getPrice());
        MyBatisSessionFactory.close();
    }

二级缓存

二级缓存

  • 一级缓存只允许当前的 Session 实现缓存数据的处理,而二级缓存可以在多个 Session 之间进行缓存的处理。考虑到在实际开发中会同时有多个线程并行实现数据的访问处理所以二级缓存的设计更符合于实际项目的使用需要,但是二级缓存并不是默认开启的需要开发者手工开启下面来看一下具体的实现步骤

1、
    <settings>
        <setting name="cacheEnabled" value="true"/> <!-- 开启应用中的二级缓存 -->
    </settings>

2、
  <cache/>

3、
    @Test
    public void testSecondCache() {
        long bid = 1L; // 查询数据编号
        // 【Session-A】通过SqlSessionFactory创建一个新的SqlSession接口实例
        SqlSession sessionA = MyBatisSessionFactory.getSessionFactory().openSession();
        Book bookA = sessionA.selectOne(
                "com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询一】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookA.getBid(), bookA.getName(), bookA.getAuthor(), bookA.getPrice());
        sessionA.close(); // 关闭Session
        // 【Session-B】通过SqlSessionFactory创建一个新的SqlSession接口实例
        SqlSession sessionB = MyBatisSessionFactory.getSessionFactory().openSession();
        Book bookB = sessionB.selectOne(
                "com.yootk.mapper.BookNS.findById", bid); // 数据查询
        LOGGER.info("【数据查询二】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                bookB.getBid(), bookB.getName(), bookB.getAuthor(), bookB.getPrice());
        sessionB.close(); // 关闭Session
    }

4、

    <!--  定义数据查询的操作,根据ID查询需要传递数据的主键,返回结果为VO对象实例  -->
    <select id="findById" parameterType="java.lang.Long" resultType="Book" useCache="false">
        SELECT bid, name, author, price FROM book WHERE bid=#{bid}
    </select>

Redis 分布式缓存

Redis 分布式缓存管理

  • 不管使用的是一级缓存还是二级缓存,所有的数据都是保存在当前应用的内存空间之中的,而随着项目应用的不断扩大,对于数据的缓存项也会越来越多这样一来所占用的内存空间会持续增加,最终导致整个服务的性能下降,所以为了进一步改善二级缓存的设计,在 MyBatis 中提供了分布式缓存的支持

序列化处理

  • MyBatis 在进行数据缓存时,会传入要缓存数据的 KEY 和 VALUE 内容,所以在进行缓存数据写入时需要进行有效的序列化处理,!,将数据转为二进制内容进行存储。而在获取数据时,则需要把读取进来的二进制数据进行反序列化操作,以获取所保存的对象实例

MyBatis 分布式缓存处理架构

  • 考虑到处理性能的问题,在本次的开发中将基于 Lettuce 组件实现 Redis 客户端的应用编写,同时为了便于所有数据库连接的管理,将创建 RedisConnectionUtil 工具类,实现有状态连接(StatefulRedisConnection)的管理
1、
implementation group: 'io.lettuce', name: 'lettuce-core', version: '6.2.1.RELEASE'
// https://mvnrepository.com/artifact/io.lettuce/lettuce-core
implementation group: 'io.lettuce', name: 'lettuce-core', version: '6.3.1.RELEASE'

implementation group: 'org.apache.commons', name: 'commons-pool2', version: '2.11.1'
// https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
implementation group: 'org.apache.commons', name: 'commons-pool2', version: '2.12.0'



2、
package com.yootk.util.redis;

import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.codec.ByteArrayCodec;
import io.lettuce.core.support.ConnectionPoolSupport;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class RedisConnectionUtil {
    private static final String REDIS_ADDRESS = "redis://hello@192.168.190.130:6379/0";
    private static final int MAX_IDLE = 10; // 最大维持连接数
    private static final int MIN_IDLE = 1; // 最小维持连接数
    private static final int MAX_TOTAL = 10; // 最大可用连接数
    private static final boolean TEST_ON_BORROW = true; // 测试后返回
    // 此时是定义了一个Apache提供的对象池,而后这个对象池要保存有若干个连接
    private static GenericObjectPool<StatefulRedisConnection<byte[], byte[]>> pool;
    private static final RedisURI REDIS_URI = RedisURI.create(REDIS_ADDRESS); // 创建Redis连接地址
    private static final RedisClient REDIS_CLIENT = RedisClient.create(REDIS_URI);
    private static final ThreadLocal<StatefulRedisConnection<byte[], byte[]>>
        REDIS_CONNECTION_THREAD_LOCAL = new ThreadLocal<>(); // 连接管理
    static { // 类加载时初始化
        GenericObjectPoolConfig config = new GenericObjectPoolConfig(); // 对象池配置类
        config.setMaxIdle(MAX_IDLE);
        config.setMinIdle(MIN_IDLE);
        config.setMaxTotal(MAX_TOTAL);
        config.setTestOnBorrow(TEST_ON_BORROW);
        pool = ConnectionPoolSupport.createGenericObjectPool(
                () -> REDIS_CLIENT.connect(new ByteArrayCodec()), config);
    }
    public static StatefulRedisConnection getConnection() { // 获取连接
        StatefulRedisConnection<byte[], byte[]> connection =
                REDIS_CONNECTION_THREAD_LOCAL.get(); // 获取连接
        if (connection == null) { // 没有连接
            connection = build(); // 创建一个新的Redis连接
            REDIS_CONNECTION_THREAD_LOCAL.set(connection); // 留给后续使用
        }
        return connection;
    }
    public static void close() {
        StatefulRedisConnection<byte[], byte[]> connection =
                REDIS_CONNECTION_THREAD_LOCAL.get(); // 获取连接
        if (connection != null) {
            connection.close();
            REDIS_CONNECTION_THREAD_LOCAL.remove();
        }
    }
    private static StatefulRedisConnection build() {
        try {
            return pool.borrowObject(); // 获取连接池的连接
        } catch (Exception e) {
            return null;
        }
    }
}


3、
package com.yootk.util.cache;

import com.yootk.util.redis.RedisConnectionUtil;
import io.lettuce.core.api.StatefulRedisConnection;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;

public class MyBatisRedisCache implements Cache {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(MyBatisRedisCache.class); // 获取日志对象
    private StatefulRedisConnection<byte[],byte[]> connection =
            RedisConnectionUtil.getConnection(); // 获取Redis连接
    private String id; // 定义缓存操作ID
    public MyBatisRedisCache(String id) {
        LOGGER.debug("【设置缓存ID】id = {}", id);
        this.id = id;
    }
    @Override
    public String getId() {
        LOGGER.debug("【获取缓存ID】id = {}", this.id);
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        LOGGER.debug("【缓存数据写入】key = {}、value = {}", key, value);
        this.connection.sync().set(SerializationUtils.serialize(key),
                SerializationUtils.serialize(value));
    }

    @Override
    public Object getObject(Object key) {
        byte data[] = this.connection.sync().get(SerializationUtils.serialize(key));
        if (data == null) {
            return null;
        }
        Object value = SerializationUtils.deserialize(data);
        LOGGER.debug("【缓存数据读取】key = {}、value = {}", key, value);
        return value;
    }

    @Override
    public Object removeObject(Object key) {
        LOGGER.debug("【删除缓存数据】key = {}", key);
        return this.connection.sync().del(SerializationUtils.serialize(key));
    }

    @Override
    public void clear() {
        LOGGER.debug("【清除缓存数据】");
        this.connection.sync().flushdb();
    }

    @Override
    public int getSize() {
        int size = this.connection.sync().dbsize().intValue();
        LOGGER.debug("【获取缓存个数】size = {}", size);
        return size;
    }
}


4、
    <cache type="com.yootk.util.cache.MyBatisRedisCache"/>

5、
package com.yootk.util.cache;

import com.yootk.util.redis.RedisConnectionUtil;
import io.lettuce.core.api.StatefulRedisConnection;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;

public class MyBatisRedisCache implements Cache {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(MyBatisRedisCache.class); // 获取日志对象
    private StatefulRedisConnection<byte[], byte[]> connection =
            RedisConnectionUtil.getConnection(); // 获取Redis连接
    private String id; // 定义缓存操作ID

    public MyBatisRedisCache(String id) {
        LOGGER.debug("【设置缓存ID】id = {}", id);
        this.id = id;
    }

    @Override
    public String getId() {
        LOGGER.debug("【获取缓存ID】id = {}", this.id);
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        LOGGER.debug("【缓存数据写入】key = {}、value = {}", key, value);
        this.connection.sync().set(SerializationUtils.serialize(key),
                SerializationUtils.serialize(value));
    }

    @Override
    public Object getObject(Object key) {
        byte data[] = this.connection.sync().get(SerializationUtils.serialize(key));
        if (data == null) {
            return null;
        }
        Object value = SerializationUtils.deserialize(data);
        LOGGER.debug("【缓存数据读取】key = {}、value = {}", key, value);
        return value;
    }

    @Override
    public Object removeObject(Object key) {
        LOGGER.debug("【删除缓存数据】key = {}", key);
        return this.connection.sync().del(SerializationUtils.serialize(key));
    }

    @Override
    public void clear() {
        LOGGER.debug("【清除缓存数据】");
        this.connection.sync().flushdb();
    }

    @Override
    public int getSize() {
        int size = this.connection.sync().dbsize().intValue();
        LOGGER.debug("【获取缓存个数】size = {}", size);
        return size;
    }

    private static class SerializationUtils {
        private SerializationUtils() {
        } // 构造方法私有化

        public static byte[] serialize(Object object) {
            byte result[] = null;
            ObjectOutputStream oos = null;
            ByteArrayOutputStream bos = null;
            try {
                bos = new ByteArrayOutputStream(); // 内存输出流
                oos = new ObjectOutputStream(bos); // 对象输出流
                oos.writeObject(object); // 输出对象
                result = bos.toByteArray(); // 获取二进制对象
            } catch (Exception e) {
            } finally {
                if (bos != null) {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                if (oos != null) {
                    try {
                        oos.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            return result;
        }

        public static Object deserialize(byte[] data) {
            Object result = null;
            ObjectInputStream ois = null;
            ByteArrayInputStream bis = null;
            try {
                bis = new ByteArrayInputStream(data);
                ois = new ObjectInputStream(bis);
                result = ois.readObject();
            } catch (Exception e) {
            } finally {
                if (bis != null) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            return result;
        }
    }
}

拦截器简介

拦截器与方法调用

  • MyBatis 作为数据层的开发框架,在每次进行数据的更新与查询处理时,也提供了拦截器的实现支持。为了便于拦截器的开发的标准化,提供了 Interceptor 接口,开发者只需要让拦截器处理类实现该接口,并且使用该接口提供的 plugins()方法,即可实现目标操作对象实例的包装,这样在调用方法时就可以自动进行拦截操作。

拦截器实现架构

  • 在新版本的 MyBatis 中,已经不要求使用者强制性的覆写 Interceptor 类的 plugins()方法了,而打开该方法方法源代码,可以发现该方法内部封装了一个“Plugin.wrap()的调用,通过源代码的解读也可以看到 wrap()方法的内部实际上就是 JDK 动态代理的实现,所以 MyBatis 中的拦截器本质上就属于动态代理机制的一种应用,为便于读者理解下面将通过一个基本的拦截操作,为读者分析 Intercetor 接口及相关注解的使用。
1、
package com.yootk.test;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class TestInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestInterceptor.class);
    public static void main(String[] args) {
        // 本次的操作其实可以通过任意的对象进行包装处理,那么本次使用一个Map集合
        // 此时由于需要采用JDK的代理设计模式进行操作,所以要将真实对象实现进行包装
        Map<String, String> map = (Map<String, String>) new DefaultInterceptor()
                .plugin(new HashMap<String, String>());
        LOGGER.info("【代理对象】类型:{}", map.getClass());
        // 此时的Map集合没有任何所设置的数据内容,所以按照常规的做法此时返回的是null
        System.out.println("【获取数据】yootk = " + map.get("yootk"));
    }
    // 现在的拦截器是由开发人员根据自己的需要来进行定义的
    // 在MyBatis之中进行拦截器配置的时候,需要明确的定义拦截的具体方法的信息
    @Intercepts(value = {
            @Signature(args = {Object.class}, method = "get", type = Map.class)
    })
    private static class DefaultInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            LOGGER.info("【拦截操作前】对象:{}、方法:{}、参数:{}",
                    invocation.getTarget(), invocation.getMethod(),
                    Arrays.toString(invocation.getArgs()));
            Object result = invocation.proceed(); // 调用最终的真实业务主题
            if (result == null) {
                result = "沐言科技 ~ 爆可爱的小李老师"; // 自定义的结果
            }
            return result;
        }
    }
}

Executor 执行拦截

数据操作拦截器

  • MyBatis 中的拦截器工作时,需要通过“@Signature”注解绑定要拦截的处理方法这样才能够对数据的更新与查询操作进行拦截控制。在 MyBatis 中所有的数据操作的方法都是由 Executor 接口定义的,该接口提供了 query()查询方法以及 update()更新方法那么此时就可以将这两个方法进行拦截绑定
1、
package com.yootk.interceptor;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Properties;

@Intercepts(value = {
        @Signature(args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class},
                method = "query", type = Executor.class), // 查询方法拦截
        @Signature(args = {MappedStatement.class, Object.class}, method = "update",
                type = Executor.class) // 更新方法拦截
})
public class YootkInterceptor implements Interceptor { // 创建数据操作的拦截器
    private static final Logger LOGGER = LoggerFactory.getLogger(YootkInterceptor.class);
    private String prefix; // 配置前缀
    private String suffix; // 配置后缀
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        LOGGER.debug("【对象实例】{}", invocation.getTarget().getClass().getName());
        LOGGER.debug("【执行方法】{}", invocation.getMethod().getName());
        LOGGER.debug("【参数接收】{}", Arrays.toString(invocation.getArgs()));
        Object result = invocation.proceed(); // 调用真实的处理方法
        LOGGER.debug("【执行结果】{}", result);
        return result;
    }
    @Override
    public void setProperties(Properties properties) { // 通过配置文件获取
        this.prefix = properties.getProperty("prefix"); // 获取配置属性
        this.suffix = properties.getProperty("suffix"); // 获取配置属性
        LOGGER.info("【配置参数】prefix = {}、suffix = {}",
                this.prefix, this.suffix);
    }
}


2、
    <plugins> <!--  在此处配置项目之中要使用的拦截器  -->
        <plugin interceptor="com.yootk.interceptor.YootkInterceptor">
            <!--  通过Interceptor接口之中的setProperty()方法进行属性的配置  -->
            <property name="prefix" value="Muyan"/> <!--  属性的配置  -->
            <property name="suffix" value="Yootk"/> <!--  属性的配置  -->
        </plugin>
    </plugins>

3、
    @Test
    public void testFindById() {
        // 此时的查询直接返回一个VO对象,所以符合ORM的设计要求
        Book book = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findById", 1L); // 数据查询
        LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        MyBatisSessionFactory.close();
    }

4、
    @Test
    public void testDoCreate() throws Exception {
        // 1、将所需要保存的数据保存在Book对象实例之中
        Book book = new Book();  // 实例化VO对象
        // book.setName("SpringBoot开发实战");
        book.setAuthor("李兴华");
        // book.setPrice(69.8);
        // 2、在执行具体的SQL操作时需要提供有准确的命令定义,而命令都在映射文件里面
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession()
                .insert("com.yootk.mapper.BookNS.doCreate", book)); // 数据增加
        LOGGER.info("【获取主键】当前新增的图书主键内容为:{}", book.getBid()); // 日志输出
        // 3、所有的操作都受到事务的保护,那么需要手工进行事务提交
        MyBatisSessionFactory.getSession().commit(); // 提交事务
        MyBatisSessionFactory.close(); // 关闭
    }

StatementHandler 执行拦截

StatementHandler 核心结构

  • 在 MyBatis 中虽然 Executor 接口定义了数据操作的执行方法,但是最终的 JDBC 执行却是由 StatementHandler 接口实现的,在 StatementHandler 接口实现设计中,提供有三类 JDBC 操作支持,分别为:SimpleStatementHandler(Statement 处理实现)PreparedStatementHandler(PreparedStatement 处理实现)以及 CallableStatementHandler(CallableStatement 处理实现)为便于管理,这三个不同的实现类都同时继承了 BaseStatementHandler 父抽象类。而在具体执行 JDBC 操作时,会由 RoutingStatementHandler 类根据当前的 SQL 命令情况选择具体的执行子类 MyBatis 为了便于不同 JDBC 操作的分类的管理,提供了 StatementType 枚举类,里面的枚举项对应着 BaseStatementHandler 三个子类

RoutingStatementHandler 类关联结构

  • 在 StatementHandler 接口中提供了一个 prepare()处理方法,所有 MyBatis 中的数据操作,在执行前都要默认调用此方法以进行 Statement 或其相关子接口的对象实例化处理如果此时要对该方法进行拦截操作,那么此时就可以基于反射机制获取到 RoutingStatementHandler 类中的 delegate 属性,并依据此属性获取执行的 SQL(BoundSaq 类实例)、执行数据(ParameterHandler 接口实例)以及 JDBC 中的 Connection 接口
1、
package com.yootk.interceptor;

import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.Arrays;

@Intercepts(value = {
        @Signature(args = {java.sql.Connection.class, Integer.class},
                method = "prepare", type = StatementHandler.class)
})
public class YootkInterceptor implements Interceptor { // 创建数据操作的拦截器
    private static final Logger LOGGER = LoggerFactory.getLogger(YootkInterceptor.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 所有拦截到的方法调用参数内部都可以直接获取到Connection接口实例
        LOGGER.debug("【JDBC连接对象】{}", invocation.getArgs()[0]);
        if(invocation.getTarget() instanceof RoutingStatementHandler) { // 类型判断
            RoutingStatementHandler handler = (RoutingStatementHandler)
                    invocation.getTarget(); // 进行强制性的向下转型
            Field delegateField = handler.getClass().getDeclaredField("delegate"); // 获取指定属性
            // RoutingStatementHandler内部的delegate属性没有提供Getter方法,所以只能通过反射获取
            delegateField.setAccessible(true); // 取消封装
            // 此时使用的是 PreparedStatement接口获得的
            PreparedStatementHandler delegate = (PreparedStatementHandler)
                delegateField.get(handler); // 获取指定的属性
            BoundSql boundSql = delegate.getBoundSql(); // 获取绑定的SQL对象
            LOGGER.debug("【SQL命令】{}", boundSql.getSql());
            LOGGER.debug("【参数映射】{}", boundSql.getParameterMappings());
            LOGGER.debug("【参数内容】{}", boundSql.getParameterObject());
        }
        Object result = invocation.proceed(); // 调用真实的处理方法
        return result;
    }
}

拦截器实现数据分页

  • MyBatis 拦截器只要定义好了拦截类型以及拦截方法,那么在该方法调用时都可以自动进行处理,那么开发者就可以基于此机制实现更加丰富的处理逻辑。
  • 以数据分页查询为例,传统的做法是在 MyBatis 数据层编写时,进行两次查询的调用,第一次是查询数据,第二次是统计结果。如果使用拦截器操作,在查询时用户将所有的分页参数封装在 PageSplitUtil 类中,而后在拦截器中除了执行目标查询之外,还需要使其可以实现统计查询,并且基于引用的方式将统计结果保存对传入的 SQL 进行解析这样外部虽然只发出了一次查询,但是实际上却可以自动完在 PageSplitUtil 实现中成其他的相关查询处理,而此类的功能可以在 MyBatis-Plus 简化了代码的调用逻辑,插件讲解时学习到。

ResultMap

1、
USE yootk;
CREATE TABLE yootk_book (
   yootk_bid 	BIGINT 	AUTO_INCREMENT	comment '图书ID',
   yootk_name 	VARCHAR(50) 		comment '图书名称',
   yootk_author 	VARCHAR(50) 		comment '图书作者',
   yootk_price 	DOUBLE 			comment '图书价格',
   CONSTRAINT pk_yootk_bid PRIMARY KEY(yootk_bid)
) engine=innodb;
INSERT INTO yootk_book(yootk_name, yootk_author, yootk_price)
    VALUES ('Java进阶编程实战', '李兴华', 89.80);


2、
    <select id="findOne" parameterType="java.lang.Long" resultType="Book">
        SELECT yootk_bid AS bid, yootk_name AS name, yootk_author AS author, yootk_price AS price
        FROM yootk_book WHERE yootk_bid=#{bid}
    </select>

3、
    @Test
    public void testFindOne() {
        // 此时的查询直接返回一个VO对象,所以符合ORM的设计要求
        Book book = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findOne", 1L); // 数据查询
        LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        MyBatisSessionFactory.close();
    }

4、
    <!--  定义数据列与查询属性之间的映射关联,就可以减少别名的出现  -->
    <resultMap id="BookMap" type="Book"> <!--  此处编写了一个完整的别名  -->
        <id column="yootk_bid" property="bid"/> <!--  定义字段名称与属性名称的关联  -->
        <result column="yootk_name" property="name"/>  <!--  定义字段名称与属性名称的关联  -->
        <result column="yootk_author" property="author"/>  <!--  定义字段名称与属性名称的关联  -->
        <result column="yootk_price" property="price"/>  <!--  定义字段名称与属性名称的关联  -->
    </resultMap>

5、
    <!--  在进行最终数据查询返回时,不再直接设置具体的类型,而是直接配置映射类型  -->
    <select id="findResultMap" parameterType="java.lang.Long" resultMap="BookMap">
        SELECT yootk_bid, yootk_name, yootk_author, yootk_price
        FROM yootk_book WHERE yootk_bid=#{bid}
    </select>

6、
    @Test
    public void testFindResultMap() {
        // 此时的查询直接返回一个VO对象,所以符合ORM的设计要求
        Book book = MyBatisSessionFactory.getSession()
                .selectOne("com.yootk.mapper.BookNS.findResultMap", 1L); // 数据查询
        LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        MyBatisSessionFactory.close();
    }

调用存储过程

MyBatis 与存储过程

  • 在关系型数据库的系统中,为了更好的实现数据操作的统一管理,提供有存储过程的结构支持,利用存储过程可以有效的实现业务逻辑的处理封装,同时也便于多张数据表的数据处理控制
1、
DROP PROCEDURE IF EXISTS book_select_proc;
CREATE PROCEDURE book_select_proc(IN p_name VARCHAR(50))
BEGIN
	IF p_name IS NOT NULL AND p_name != '' THEN
		SELECT bid, name, author, price FROM book WHERE name LIKE CONCAT('%', p_name, '%');
	ELSE
		SELECT bid, name, author, price FROM book;
	END IF;
END;
DELIMITER;

2、
CALL book_select_proc('Spring')

3、
INSERT INTO book(name, author, price) VALUES ('Java进阶开发实战', '李兴华', 89.80);
INSERT INTO book(name, author, price) VALUES ('Spring开发实战', '李兴华', 89.80);
INSERT INTO book(name, author, price) VALUES ('SSM开发实战', '李兴华', 89.80);

4、
DROP PROCEDURE IF EXISTS book_multi_select_proc;
CREATE PROCEDURE book_multi_select_proc(IN p_start INT, IN p_linesize INT)
BEGIN
	SELECT bid, name, author, price FROM book LIMIT p_start, p_linesize;
	SELECT COUNT(*) AS count FROM book;
END;
DELIMITER;

5、
CALL book_multi_select_proc(1, 2);

6、
    <select id="producerSingle" parameterType="string"
            resultType="Book" statementType="CALLABLE">
        { CALL book_select_proc(#{param, jdbcType=VARCHAR, mode=IN}) }
    </select>

7、
    @Test
    public void testProducerSingle() {
        long ids [] = new long[] {1L, 3L, 5L};
        List<Book> books = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.producerSingle", "Spring"); // 查询全部
        for(Book book : books) {
            LOGGER.info("【数据查询】图书编号:{}、图书名称:{}、图书作者:{}、图书价格:{}",
                    book.getBid(), book.getName(), book.getAuthor(), book.getPrice());
        }
        MyBatisSessionFactory.close();
    }

8、
    <!--  定义数据列与查询属性之间的映射关联,就可以减少别名的出现  -->
    <resultMap id="BookMap" type="Book"> <!--  此处编写了一个完整的别名  -->
        <id column="bid" property="bid"/> <!--  定义字段名称与属性名称的关联  -->
        <result column="name" property="name"/>  <!--  定义字段名称与属性名称的关联  -->
        <result column="author" property="author"/>  <!--  定义字段名称与属性名称的关联  -->
        <result column="price" property="price"/>  <!--  定义字段名称与属性名称的关联  -->
    </resultMap>
    <resultMap id="CountMap" type="java.lang.Integer">
        <result column="count" property="value"/> <!--  Integer类中的属性名称  -->
    </resultMap>
    <select id="producerMulti" parameterType="map" resultMap="BookMap, CountMap"
            resultSets="books, count" statementType="CALLABLE">
        { CALL book_multi_select_proc(
                    #{start, jdbcType=INTEGER, mode=IN} ,
                    #{lineSize, jdbcType=INTEGER, mode=IN} ) }
    </select>

9、

    @Test
    public void testProducerMulti() {
        Map<String, Integer> param = Map.of("start", 0, "lineSize", 3); // 封装参数
        List<List<?>> result = MyBatisSessionFactory.getSession()
                .selectList("com.yootk.mapper.BookNS.producerMulti", param); // 查询全部
        LOGGER.info("【数据查询】图书数据:{}、记录行数:{}", result.get(0), result.get(1));
        MyBatisSessionFactory.close();
    }

鉴别器

MyBatis 鉴别器

  • 在标准化的设计之中,每一张数据表都会对应一个完整的实体数据集但是在一些特殊环境下,有可能一张数据表中会同时保存有若干种不同的子实体数据,而为了在操作中便于不同子实体的区分,就可以基于鉴别器进行处理。
1、
USE yootk;
CREATE TABLE member (
   mid 		VARCHAR(50) 	comment '用户ID',
   name 		VARCHAR(50) 	comment '用户姓名',
   age 		INT 		comment '用户年龄',
   sex 		VARCHAR(10) 	comment '用户性别',
   score 		DOUBLE 		comment '学生成绩',
   major 		VARCHAR(50) 	comment '学生专业',
   salary 	DOUBLE 		comment '雇员收入',
   dept 		VARCHAR(50) 	comment '所属部门',
   type 		VARCHAR(50) 	comment '类型区分',
   CONSTRAINT pk_mid PRIMARY KEY(mid)
) engine=innodb;


2、
package com.yootk.vo;

import java.io.Serializable;

public class Member implements Serializable {
    private String mid;
    private String name;
    private Integer age;
    private String sex;
    public String getMid() {
        return mid;
    }
    public void setMid(String mid) {
        this.mid = mid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}


3、
package com.yootk.vo;

import java.io.Serializable;

public class Student extends Member implements Serializable {
    private Double score;
    private String major;
    public Double getScore() {
        return score;
    }
    public void setScore(Double score) {
        this.score = score;
    }
    public String getMajor() {
        return major;
    }
    public void setMajor(String major) {
        this.major = major;
    }
}


4、
package com.yootk.vo;

import java.io.Serializable;

public class Employee extends Member implements Serializable {
    private Double salary;
    private String dept;
    public Double getSalary() {
        return salary;
    }
    public void setSalary(Double salary) {
        this.salary = salary;
    }
    public String getDept() {
        return dept;
    }
    public void setDept(String dept) {
        this.dept = dept;
    }
}


5、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yootk.mapper.MemberNS">
    <resultMap id="MemberResultMap" type="Member">
        <id property="mid" column="mid"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <!-- 此处进行鉴别器定义,其实就是针对于type字段进行的处理 -->
        <discriminator javaType="java.lang.String" column="type">
            <case value="stu" resultType="Student">
                <result property="score" column="score"/>
                <result property="major" column="major"/>
            </case>
            <case value="emp" resultType="Employee">
                <result property="salary" column="salary"/>
                <result property="dept" column="dept"/>
            </case>
        </discriminator>
    </resultMap>
    <insert id="doCreateStudent" parameterType="Student">
        INSERT INTO member(mid, name, age, sex, score, major, type)
        VALUES (#{mid}, #{name}, #{age}, #{sex}, #{score}, #{major}, 'stu')
    </insert>
    <insert id="doCreateEmployee" parameterType="Employee">
        INSERT INTO member(mid, name, age, sex, salary, dept, type)
        VALUES (#{mid}, #{name}, #{age}, #{sex}, #{salary}, #{dept}, 'emp')
    </insert>
    <select id="findAllStudent" resultMap="MemberResultMap">
        SELECT mid, name, age, sex, score, major, type FROM member WHERE type='stu'
    </select>
    <select id="findAllEmployee" resultMap="MemberResultMap">
        SELECT mid, name, age, sex, salary, dept, type FROM member WHERE type='emp'
    </select>
</mapper>

6、
    <mappers>  					<!-- 配置SQL映射文件路径 -->
        <mapper resource="mybatis/mapper/Book.xml" />  <!-- 映射文件路径 -->
        <mapper resource="mybatis/mapper/Member.xml" />  <!-- 映射文件路径 -->
    </mappers>

7、

package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Book;
import com.yootk.vo.Employee;
import com.yootk.vo.Student;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class TestMember {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestMember.class);

    @Test
    public void testDoCreateStudent() {
        Student stu = new Student();
        stu.setMid("muyan");
        stu.setName("沐言");
        stu.setAge(18);
        stu.setSex("男");
        stu.setMajor("计算机科学与技术");
        stu.setScore(96.8);
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession().insert(
                "com.yootk.mapper.MemberNS.doCreateStudent", stu));
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }
    @Test
    public void testDoCreateEmployee() {
        Employee emp = new Employee();
        emp.setMid("yootk");
        emp.setName("沐言");
        emp.setAge(18);
        emp.setSex("男");
        emp.setDept("教学研发部");
        emp.setSalary(2500.00);
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession().insert(
                "com.yootk.mapper.MemberNS.doCreateEmployee", emp));
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }
    @Test
    public void testFindAllStudent() {
        List<Student> result = MyBatisSessionFactory.getSession().selectList(
                "com.yootk.mapper.MemberNS.findAllStudent");
        for (Student stu : result) {
            LOGGER.info("【学生信息】编号:{}、姓名:{}、成绩:{}、专业:{}",
                    stu.getMid(), stu.getName(), stu.getScore(), stu.getMajor());
        }
        MyBatisSessionFactory.close();
    }
    @Test
    public void testFindAllEmployee() {
        List<Employee> result = MyBatisSessionFactory.getSession().selectList(
                "com.yootk.mapper.MemberNS.findAllEmployee");
        for (Employee emp : result) {
            LOGGER.info("【雇员信息】编号:{}、姓名:{}、部门:{}、工资:{}",
                    emp.getMid(), emp.getName(), emp.getDept(), emp.getSalary());
        }
        MyBatisSessionFactory.close();
    }
}

类型转换器

类型转换器

  • 在关系型数据库的发展历史中,随着各个数据库版本的不断提高,其所支持的数据类型也越多,考虑到不同数据库的使用环境,所以开发人员在进行数据表结构设计时,也往往会采用一些基础的数据类型,例如:字符串、数字、日期时间等。现在假设有一个 Account 账户数据表,为了便于账户的控制,为其设置了-个 status 字段,利用该字段描述当前账户的锁定状态,此时较为传统的做法是将其定义为整型,而后当"status0”的时候表示该账户未锁定,而“status=1”的时候表示该账户已锁定

1、
USE yootk;
CREATE TABLE account (
   aid 		VARCHAR(50) 	comment '账户ID',
   name 		VARCHAR(50) 	comment '账户名称',
   status 	INT 		comment '账户锁定状态,0表示活跃,1表示锁定',
   CONSTRAINT pk_aid PRIMARY KEY(aid)
) engine=innodb;
INSERT INTO account(aid, name, status) VALUES ('muyan', '沐言', 0);
INSERT INTO account(aid, name, status) VALUES ('yootk', '优拓', 0);
INSERT INTO account(aid, name, status) VALUES ('lixinghua', '李兴华', 1);


2、
package com.yootk.vo;

import java.io.Serializable;

public class Account implements Serializable {
    private String aid;
    private String name;
    private boolean status; // 数据表字段类型为整型
    public String getAid() {
        return aid;
    }
    public void setAid(String aid) {
        this.aid = aid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public boolean isStatus() {
        return status;
    }
    public void setStatus(boolean status) {
        this.status = status;
    }
}


3、
package com.yootk.handler;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.sql.*;

public class BooleanAndIntegerTypeHandler implements TypeHandler<Boolean> {
    @Override
    public void setParameter(PreparedStatement ps, int i,
                             Boolean parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) { // 对应的属性没有内容
            ps.setNull(i, Types.NULL);
        } else {
            if (parameter == true) { // if(parameter)
                ps.setInt(i, 1); // status = 1
            } else {
                ps.setInt(i, 0); // status = 0
            }
        }
    }
    @Override
    public Boolean getResult(ResultSet rs, String columnName) throws SQLException {
        Integer flag = rs.getInt(columnName); // 获取指定列的信息
        return flag != 0;
    }
    @Override
    public Boolean getResult(ResultSet rs, int columnIndex) throws SQLException {
        Integer flag = rs.getInt(columnIndex); // 获取指定列的信息
        return flag != 0;
    }
    @Override
    public Boolean getResult(CallableStatement cs, int columnIndex) throws SQLException {
        Integer flag = cs.getInt(columnIndex); // 获取指定列的信息
        return flag != 0;
    }
}


4、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.AccountNS">
    <resultMap id="AccountResultMap" type="Account">
        <id property="aid" column="aid"/>
        <id property="name" column="name"/>
        <id property="status" column="status"
            javaType="java.lang.Boolean" jdbcType="INTEGER"/>
    </resultMap>
    <insert id="doCreate" parameterType="Account">
        INSERT INTO account(aid, name, status) VALUES
        (#{aid}, #{name}, #{status, javaType=java.lang.Boolean, jdbcType=INTEGER})
    </insert>
    <select id="findAll" resultMap="AccountResultMap">
        SELECT aid, name, status FROM account
    </select>
</mapper>

5、
    <typeHandlers>
        <typeHandler handler="com.yootk.handler.BooleanAndIntegerTypeHandler"
                     javaType="java.lang.Boolean" jdbcType="INTEGER"/>
    </typeHandlers>

6、
    @Test
    public void testFindAllEmployee() {
        List<Account> result = MyBatisSessionFactory.getSession().selectList(
                "com.yootk.mapper.AccountNS.findAll");
        for (Account account : result) {
            LOGGER.info("【账户信息】编号:{}、姓名:{}、状态:{}",
                    account.getAid(), account.getName(), account.isStatus());
        }
        MyBatisSessionFactory.close();
    }

7、
package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Account;
import com.yootk.vo.Employee;
import com.yootk.vo.Student;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class TestAccount {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestAccount.class);

    @Test
    public void testDoCreate() {
        Account account = new Account();
        account.setAid("muyan-yootk");
        account.setName("沐言优拓");
        account.setStatus(false); // 活跃账户信息
        LOGGER.info("【数据增加】更新数据行数:{}", MyBatisSessionFactory.getSession().insert(
                "com.yootk.mapper.AccountNS.doCreate", account));
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }
    @Test
    public void testFindAllEmployee() {
        List<Account> result = MyBatisSessionFactory.getSession().selectList(
                "com.yootk.mapper.AccountNS.findAll");
        for (Account account : result) {
            LOGGER.info("【账户信息】编号:{}、姓名:{}、状态:{}",
                    account.getAid(), account.getName(), account.isStatus());
        }
        MyBatisSessionFactory.close();
    }
}

一对一数据关联

一对一表关联

  • 在数据库设计中,为了简化数据表的结构以及数据存储的体积,可以将一张完整的数据表拆分为若干张不同的表,这些表之间可以根据同一个主键进行关联。例如:在进行银行账户表设计时,由于其所需要填写的数据项较多所以可以将银行账户表拆分为账户信息表(account)与账户详情(details)两张表

1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE account (
   aid		VARCHAR(50) 		comment '账户ID',
   name		VARCHAR(50) 		comment '开户人',
   id		VARCHAR(18) 		comment '身份证编号',
   CONSTRAINT pk_aid1 PRIMARY KEY(aid)
) engine=innodb;
CREATE TABLE details (
    aid 		VARCHAR(50) 		comment '账户ID',
    rmb 		DOUBLE 			comment '人民币存款总额',
    dollar 	DOUBLE 			comment '美元存款总额',
    euro 		DOUBLE 			comment '欧元存款总额',
    CONSTRAINT pk_aid2 PRIMARY KEY(aid)
) engine=innodb;
INSERT INTO account(aid, name, id) VALUES ('yootk-25813919373', '李兴华', 111111111111111);
INSERT INTO account(aid, name, id) VALUES ('yootk-22351096212', '李沐言', 222222222222222);
INSERT INTO details(aid, rmb, dollar, euro) VALUES
         ('yootk-25813919373', 8000.22, 123.32, 6717.22);
INSERT INTO details(aid, rmb, dollar, euro) VALUES
         ('yootk-22351096212', 5700.31, 516.91, 1258.65);


2、
package com.yootk.vo;

import java.io.Serializable;

public class Account implements Serializable {
    private String aid;
    private String name;
    private String id; // 数据表字段类型为整型
    private Details details;
    public String getAid() {
        return aid;
    }
    public void setAid(String aid) {
        this.aid = aid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}


3、
package com.yootk.vo;

public class Details {
    private String aid;
    private Double rmb;
    private Double dollar;
    private Double euro;
    private Account account;
    public String getAid() {
        return aid;
    }
    public void setAid(String aid) {
        this.aid = aid;
    }
    public Double getRmb() {
        return rmb;
    }
    public void setRmb(Double rmb) {
        this.rmb = rmb;
    }
    public Double getDollar() {
        return dollar;
    }
    public void setDollar(Double dollar) {
        this.dollar = dollar;
    }
    public Double getEuro() {
        return euro;
    }
    public void setEuro(Double euro) {
        this.euro = euro;
    }
    public Account getAccount() {
        return account;
    }
    public void setAccount(Account account) {
        this.account = account;
    }
}


4、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.AccountNS">
    <resultMap id="AccountResultMap" type="Account">
        <id property="aid" column="aid"/>
        <result property="name" column="name" />
        <result property="id" column="id" />
        <!-- 关联配置,此时表示的是Account中的结构,获取details的时候调用其他映射文件查询 -->
        <association property="details" column="aid"
                     javaType="com.yootk.vo.Details"
                     select="com.yootk.mapper.DetailsNS.findById"/>
    </resultMap>
    <select id="findById" parameterType="java.lang.String" resultMap="AccountResultMap">
        SELECT aid, name, id FROM account WHERE aid=#{aid}
    </select>
</mapper>

5、

        <mapper resource="mybatis/mapper/Account.xml" />  <!-- 映射文件路径 -->
        <mapper resource="mybatis/mapper/Details.xml" />  <!-- 映射文件路径 -->
6、

package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Account;
import com.yootk.vo.Employee;
import com.yootk.vo.Student;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class TestAccount {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestAccount.class);

    @Test
    public void testFindById() {
        Account account = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.AccountNS.findById", "yootk-25813919373");
        LOGGER.info("【账户信息】账户编号:{}、开户人:{}、身份证号码:{}",
                account.getAid(), account.getName(), account.getId());
        LOGGER.info("【账户详情】人民币储蓄:{}、美元储蓄:{}、欧元储蓄:{}",
                account.getDetails().getRmb(), account.getDetails().getDollar(),
                account.getDetails().getEuro());
        MyBatisSessionFactory.close();
    }
}

7、
    @Test
    public void testFindDetails() {
        Details details = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.DetailsNS.findById", "yootk-25813919373");
        LOGGER.info("【账户详情】人民币储蓄:{}、美元储蓄:{}、欧元储蓄:{}",
                details.getRmb(), details.getDollar(), details.getEuro());
        LOGGER.info("【账户信息】账户编号:{}、开户人:{}、身份证号码:{}",
                details.getAccount().getAid(), details.getAccount().getName(),
                details.getAccount().getId());
        MyBatisSessionFactory.close();
    }

一对多数据关联

对多关联

  • 在项目的设计中经常需要对一些数据进行归类管理,例如:在图书系统设计时,每一本图书都应该对应其所属的学科范畴。在进行商城系统设计时,不同的商品也应该进行归类管理。那么此时就应该采用数据库的第三设计范式,采用一对多的结构进行数据存储
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE role (
   rid  		VARCHAR(50) 		comment '角色ID',
   name        	VARCHAR(50) 		comment '角色名称',
   CONSTRAINT pk_rid PRIMARY KEY(rid)
) engine=innodb;
CREATE TABLE action (
    aid        	VARCHAR(50)  		comment '权限ID',
    name   	VARCHAR(50)  		comment '权限名称',
    rid     	VARCHAR(50) 		comment '角色ID',
    CONSTRAINT pk_aid PRIMARY KEY(aid)
) engine=innodb;
INSERT INTO role(rid, name) VALUES ('member', '用户管理');
INSERT INTO role(rid, name) VALUES ('system', '系统管理');
INSERT INTO action(aid, name, rid) VALUES ('member:lock', '用户锁定', 'member');
INSERT INTO action(aid, name, rid) VALUES ('member:verify', '用户验证', 'member');
INSERT INTO action(aid, name, rid) VALUES ('member:delete', '用户删除', 'member');
INSERT INTO action(aid, name, rid) VALUES ('system:init', '系统初始化', 'system');
INSERT INTO action(aid, name, rid) VALUES ('system:backup', '系统备份', 'system');


2、
package com.yootk.vo;

import java.io.Serializable;
import java.util.List;

public class Role implements Serializable {
    private String rid;
    private String name;
    private List<Action> actions;

    public String getRid() {
        return rid;
    }

    public void setRid(String rid) {
        this.rid = rid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Action> getActions() {
        return actions;
    }

    public void setActions(List<Action> actions) {
        this.actions = actions;
    }
}


3、
package com.yootk.vo;

import java.io.Serializable;

public class Action implements Serializable {
    private String aid;
    private String name;
    private Role role;
    public String getAid() {
        return aid;
    }
    public void setAid(String aid) {
        this.aid = aid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Role getRole() {
        return role;
    }
    public void setRole(Role role) {
        this.role = role;
    }
}


4、

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.RoleNS">
    <resultMap id="RoleResultMap" type="Role">
        <id property="rid" column="rid"/>
        <result property="name" column="name"/>
        <!-- 实现一对多的数据关联结构,默认情况下加载一方的时候同时加载多方 -->
        <collection property="actions" column="rid" javaType="java.util.List"
                    select="com.yootk.mapper.ActionNS.findAllByRole"
                    ofType="com.yootk.vo.Action" fetchType="lazy"/>
    </resultMap>
    <select id="findById" parameterType="java.lang.String" resultMap="RoleResultMap">
        SELECT rid, name FROM role WHERE rid=#{rid}
    </select>
</mapper>
5、
package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Account;
import com.yootk.vo.Details;
import com.yootk.vo.Role;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestRole {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestRole.class);

    @Test
    public void testFindRole() {
        Role role = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.RoleNS.findById", "member");
        LOGGER.info("【角色信息】角色编号:{}、角色名称:{}", role.getRid(), role.getName());
        MyBatisSessionFactory.close();
    }
}


6、
    @Test
    public void testFindRole() {
        Role role = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.RoleNS.findById", "member");
        LOGGER.info("【角色信息】角色编号:{}、角色名称:{}", role.getRid(), role.getName());
        for (Action action : role.getActions()) { // 一方加载多方
            LOGGER.info("【权限信息】权限编号:{}、权限名称:{}", action.getAid(), action.getName());
        }
        MyBatisSessionFactory.close();
    }

7、
    @Test
    public void testFindAction() {
        Action action = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.ActionNS.findById", "system:init");
        LOGGER.info("【权限信息】权限编号:{}、权限名称:{}", action.getAid(), action.getName());
        LOGGER.info("【角色信息】角色编号:{}、角色名称:{}",
                action.getRole().getRid(), action.getRole().getName());
    }

多对多数据关联

多对多关联

  • 多对多是一种双向的一对多结构实现,在实际的项目开发中一个学生可能对应有不同的课程,而一门课程也会有多名学生参加,这样的结构就属于多对多关联
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE student (
   sid		VARCHAR(50) 		comment '学生ID',
   name		VARCHAR(50) 		comment '角色名称',
   CONSTRAINT pk_rid PRIMARY KEY(sid)
) engine=innodb;
CREATE TABLE course (
    cid		VARCHAR(50)  		comment '课程ID',
    name		VARCHAR(50)  		comment '课程名称',
    credit	INT 			comment '课程学分',
    CONSTRAINT pk_cid PRIMARY KEY(cid)
) engine=innodb;
CREATE TABLE student_course (
    sid 		VARCHAR(50) 		comment '学生ID',
    cid 		VARCHAR(50) 		comment '课程ID'
) engine=innodb;
INSERT INTO student(sid, name) VALUES ('lee', '李兴华');
INSERT INTO student(sid, name) VALUES ('muyan', '李沐言');
INSERT INTO course(cid, name, credit) VALUES ('yootk-java', 'java编程', 5);
INSERT INTO course(cid, name, credit) VALUES ('yootk-golang', 'go编程', 3);
INSERT INTO course(cid, name, credit) VALUES ('yootk-python', 'python编程', 1);
INSERT INTO student_course(sid, cid) VALUES ('lee', 'yootk-java');
INSERT INTO student_course(sid, cid) VALUES ('lee', 'yootk-golang');
INSERT INTO student_course(sid, cid) VALUES ('muyan', 'yootk-java');
INSERT INTO student_course(sid, cid) VALUES ('muyan', 'yootk-python');


2、
package com.yootk.vo;

import java.io.Serializable;
import java.util.List;

public class Student implements Serializable {
    private String sid;
    private String name;
    private List<Course> courses; // 配置学生所选择的课程信息
    public String getSid() {
        return sid;
    }
    public void setSid(String sid) {
        this.sid = sid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Course> getCourses() {
        return courses;
    }
    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }
}


3、
package com.yootk.vo;

import java.io.Serializable;
import java.util.List;

public class Course implements Serializable {
    private String cid;
    private String name;
    private Integer credit;
    private List<Student> students;

    public String getCid() {
        return cid;
    }

    public void setCid(String cid) {
        this.cid = cid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getCredit() {
        return credit;
    }

    public void setCredit(Integer credit) {
        this.credit = credit;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }
}


4、
package com.yootk.vo;

import java.io.Serializable;

public class StudentCourseLink implements Serializable {
    private Student student;
    private Course course;
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    public Course getCourse() {
        return course;
    }
    public void setCourse(Course course) {
        this.course = course;
    }
}


5、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.StudentNS">
    <resultMap id="StudentBaseResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="name" column="name"/>
    </resultMap>
    <!--  关联的结构可以写在一起,也可以依据继承的结构编写  -->
    <resultMap id="StudentResultMap" type="Student" extends="StudentBaseResultMap">
        <collection property="courses" column="sid" javaType="java.util.List"
                    ofType="Course" fetchType="lazy"
                    select="com.yootk.mapper.CourseNS.findAllByStudent"/>
    </resultMap>
    <select id="findById" parameterType="java.lang.String" resultMap="StudentResultMap">
        SELECT sid, name FROM student WHERE sid=#{sid}
    </select>
    <!--  查询课程信息的同时,还需要查询出参加了此课程的学生信息  -->
    <select id="findAllByCourse" parameterType="java.lang.String" resultMap="StudentResultMap">
        SELECT sid, name FROM student WHERE sid IN (
            SELECT sid FROM student_course WHERE cid=#{cid})
    </select>
</mapper>

6、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.CourseNS">
    <resultMap id="CourseBaseResultMap" type="Course">
        <id property="cid" column="cid"/>
        <result property="name" column="name" />
        <result property="credit" column="credit" />
    </resultMap>
    <resultMap id="CourseResultMap" type="Course" extends="CourseBaseResultMap">
        <collection property="students" column="cid" javaType="java.util.List"
                    ofType="Student" fetchType="lazy"
                    select="com.yootk.mapper.StudentNS.findAllByCourse"/>
    </resultMap>
    <select id="findById" parameterType="java.lang.String" resultMap="CourseResultMap">
        SELECT cid, name, credit FROM course WHERE cid=#{cid}
    </select>
    <!--  查询学生信息的时候,应该可以根据学生的编号查询出其对应的课程信息  -->
    <select id="findAllByStudent" parameterType="java.lang.String" resultMap="CourseResultMap">
        SELECT cid, name, credit FROM course WHERE cid IN (
            SELECT cid FROM student_course WHERE sid=#{sid})
    </select>
</mapper>

7、
        <mapper resource="mybatis/mapper/Student.xml" />  <!-- 映射文件路径 -->
        <mapper resource="mybatis/mapper/Course.xml" />  <!-- 映射文件路径 -->

8、
package com.yootk.test;

import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Account;
import com.yootk.vo.Course;
import com.yootk.vo.Details;
import com.yootk.vo.Student;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestStudent {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestStudent.class);

    @Test
    public void testStudentFindById() {
        Student student = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.StudentNS.findById", "muyan");
        LOGGER.info("【学生信息】编号:{}、姓名:{}", student.getSid(), student.getName());
        for (Course course : student.getCourses()) {
            LOGGER.info("【课程信息】编号:{}、名称:{}", course.getCid(), course.getName());
        }
        MyBatisSessionFactory.close();
    }
}


9、
    @Test
    public void testCourseFindById() {
        Course course = MyBatisSessionFactory.getSession().selectOne(
                "com.yootk.mapper.CourseNS.findById", "yootk-java");
        LOGGER.info("【课程信息】编号:{}、名称:{}", course.getCid(), course.getName());
        for (Student student : course.getStudents()) {
            LOGGER.info("【学生信息】编号:{}、姓名:{}", student.getSid(), student.getName());
        }
    }

10、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.mapper.StudentCourseLinkNS">
    <insert id="doCreate" parameterType="StudentCourseLink">
        INSERT INTO student_course(sid, cid) VALUES
        <foreach collection="list" separator="," item="link">
            (#{ link.student.sid }, #{ link.course.cid })
        </foreach>
    </insert>
</mapper>

11、
    <insert id="doCreate" parameterType="Student">
        INSERT INTO student(sid, name) VALUES (#{sid}, #{name})
    </insert>

12、
    @Test
    public void testStudentDoCreate() {
        Student student = new Student();
        student.setSid("yootk");
        student.setName("沐言优拓");
        List<StudentCourseLink> links = new ArrayList<>(); // 描述多对多的配置关系
        for (String cid : new String[] {"yootk-java", "yootk-python", "java-golang"}) {
            StudentCourseLink link = new StudentCourseLink();
            link.setStudent(student); // 保存学生信息
            // 性能思考:仅仅为了保存一个课程编号,有意义如此吗?
            Course course = new Course(); // 创建课程信息
            course.setCid(cid);
            link.setCourse(course);
            links.add(link); // 配置了学生和课程之间的关联
        }
        int count = MyBatisSessionFactory.getSession().insert(
                "com.yootk.mapper.StudentNS.doCreate", student);
        if (count > 0) { // 学生信息创建成功
            MyBatisSessionFactory.getSession().insert(
                    "com.yootk.mapper.StudentCourseNS.doCreate", links);
        }
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }

13、

    <delete id="doRemove" parameterType="java.lang.String">
        DELETE FROM student
        <where>
            sid IN
            <foreach collection="array" open="(" close=")" separator="," item="sid">
                #{sid}
            </foreach>
        </where>
    </delete>

14、
    <delete id="doRemove" parameterType="java.lang.String">
        DELETE FROM course
        <where>
            cid IN
            <foreach collection="array" open="(" close=")" separator="," item="cid">
                #{cid}
            </foreach>
        </where>
    </delete>

15、
    <delete id="doRemoveByStudent" parameterType="java.lang.String">
        DELETE FROM student_course
        <where>
            sid IN
            <foreach collection="array" open="(" close=")" separator="," item="sid">
                #{sid}
            </foreach>
        </where>
    </delete>
    <delete id="doRemoveByCourse" parameterType="java.lang.String">
        DELETE FROM student_course
        <where>
            cid IN
            <foreach collection="array" open="(" close=")" separator="," item="cid">
                #{cid}
            </foreach>
        </where>
    </delete>

16、
    @Test
    public void testStudentDoRemove() {
        String sids[] = new String[]{"muyan", "yootk"};
        int count = MyBatisSessionFactory.getSession().delete(
                "com.yootk.mapper.StudentNS.doRemove", sids);
        if (count > 0) {
            MyBatisSessionFactory.getSession().delete(
                    "com.yootk.mapper.StudentCourseLinkNS.doRemoveByStudent", sids);
        }
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }

17、
    @Test
    public void testCourseDoRemove() {
        String cids[] = new String[]{"yootk-java", "yootk-golang"};
        int count = MyBatisSessionFactory.getSession().delete(
                "com.yootk.mapper.CourseNS.doRemove", cids);
        if (count > 0) {
            MyBatisSessionFactory.getSession().delete(
                    "com.yootk.mapper.StudentCourseLinkNS.doRemoveByCourse", cids);
        }
        MyBatisSessionFactory.getSession().commit();
        MyBatisSessionFactory.close();
    }

Spring 整合 MyBatis

Spring 整合 MyBatis

  • MyBatis 是工作在数据层中的服务组件,同时也是基于 JDBC 实现的 ORM 开发组件,而为了满足数据层的开发要求,在默认情况下,MyBatis 需要自己进行数据库连接池的管理事务更新管理以及缓存等辅助功能的处理,所以为了进一步简化 MyBatis 组件的使用,同时也为了便于与更多的服务组件整合,实际的项目中往往会基于 Spring 整合 MyBatis 开发框架,以实现良好的组件分工
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE message (
   mid 		BIGINT 	AUTO_INCREMENT 	comment '消息ID',
   sender 	VARCHAR(50) 		comment '消息发送者',
   title 		VARCHAR(50) 		comment '消息标题',
   content 	TEXT 			comment '消息内容',
   CONSTRAINT pk_mid PRIMARY KEY(mid)
) engine=innodb;


2、
// https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.7'
// https://mvnrepository.com/artifact/org.springframework/spring-jdbc
implementation group: 'org.springframework', name: 'spring-jdbc', version: '6.0.0'
// https://mvnrepository.com/artifact/org.springframework/spring-tx
implementation group: 'org.springframework', name: 'spring-tx', version: '6.0.0'
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation group: 'com.zaxxer', name: 'HikariCP', version: '5.0.1'


3、
yootk.database.driverClassName=com.mysql.cj.jdbc.Driver
yootk.database.jdbcUrl=jdbc:mysql://localhost:3306/yootk
yootk.database.username=root
yootk.database.password=mysqladmin
yootk.database.connectionTimeOut=3000
yootk.database.readOnly=false
yootk.database.pool.idleTimeOut=3000
yootk.database.pool.maxLifetime=60000
yootk.database.pool.maximumPoolSize=60
yootk.database.pool.minimumIdle=20

4、
package com.yootk.config;
@Configuration 						// 配置类
@PropertySource("classpath:config/database.properties")  	// 配置加载
public class DataSourceConfig { 				// 数据源配置Bean
    @Value("${yootk.database.driverClassName}") 		// 资源文件读取配置项
    private String driverClassName; 			// 数据库驱动程序
    @Value("${yootk.database.jdbcUrl}") 			// 资源文件读取配置项
    private String jdbcUrl; 				// 数据库连接地址
    @Value("${yootk.database.username}") 			// 资源文件读取配置项
    private String username; 				// 用户名
    @Value("${yootk.database.password}") 			// 资源文件读取配置项
    private String password; 				// 密码
    @Value("${yootk.database.connectionTimeOut}") 		// 资源文件读取配置项
    private long connectionTimeout; 			// 连接超时
    @Value("${yootk.database.readOnly}") 			// 资源文件读取配置项
    private boolean readOnly; 				// 只读配置
    @Value("${yootk.database.pool.idleTimeOut}") 		// 资源文件读取配置项
    private long idleTimeout; 				// 连接最小维持时长
    @Value("${yootk.database.pool.maxLifetime}") 		// 资源文件读取配置项
    private long maxLifetime; 				// 连接最大存活时长
    @Value("${yootk.database.pool.maximumPoolSize}") 	// 资源文件读取配置项
    private int maximumPoolSize; 				// 连接池最大维持数量
    @Value("${yootk.database.pool.minimumIdle}") 		// 资源文件读取配置项
    private int minimumIdle; 				// 连接池最小维持数量
    @Bean("dataSource") 					// Bean注册
    public DataSource dataSource() { 			// 配置数据源
        HikariDataSource dataSource = new HikariDataSource(); // DataSource子类实例化
        dataSource.setDriverClassName(this.driverClassName); // 驱动程序
        dataSource.setJdbcUrl(this.jdbcUrl); 		// JDBC连接地址
        dataSource.setUsername(this.username); 		// 用户名
        dataSource.setPassword(this.password); 		// 密码
        dataSource.setConnectionTimeout(this.connectionTimeout); // 连接超时
        dataSource.setReadOnly(this.readOnly); 		// 是否为只读数据库
        dataSource.setIdleTimeout(this.idleTimeout); 	// 最小维持时间
        dataSource.setMaxLifetime(this.maxLifetime); 	// 连接的最大时长
        dataSource.setMaximumPoolSize(this.maximumPoolSize); // 连接池最大容量
        dataSource.setMinimumIdle(this.minimumIdle); 	// 最小维持连接量
        return dataSource; 				// 返回Bean实例
    }
}


5、
package com.yootk.config;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration                        // 配置类
@Aspect                            // 切面事务管理
public class TransactionConfig {                // AOP事务配置类
    @Bean("transactionManager")                // Bean注册
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager =
                new DataSourceTransactionManager();        // 事务管理对象实例化
        transactionManager.setDataSource(dataSource);    // 配置数据源
        return transactionManager;
    }

    @Bean("txAdvice")                    // 事务拦截器
    public TransactionInterceptor transactionConfig(
            TransactionManager transactionManager) {        // 定义事务控制切面
        RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
        readOnlyRule.setReadOnly(true);            // 只读事务
        readOnlyRule.setPropagationBehavior(
                TransactionDefinition.PROPAGATION_NOT_SUPPORTED);    // 非事务运行
        readOnlyRule.setTimeout(5);            // 事务超时
        RuleBasedTransactionAttribute requiredRule = new RuleBasedTransactionAttribute();
        requiredRule.setPropagationBehavior(
                TransactionDefinition.PROPAGATION_REQUIRED);    // 事务开启
        requiredRule.setTimeout(5);            // 事务超时
        Map<String, TransactionAttribute> transactionMap = new HashMap<>();
        transactionMap.put("add*", requiredRule);        // 事务方法前缀
        transactionMap.put("edit*", requiredRule);    // 事务方法前缀
        transactionMap.put("delete*", requiredRule);    // 事务方法前缀
        transactionMap.put("get*", readOnlyRule);        // 事务方法前缀
        NameMatchTransactionAttributeSource source =
                new NameMatchTransactionAttributeSource();    // 命名匹配事务
        source.setNameMap(transactionMap);            // 设置事务方法
        TransactionInterceptor transactionInterceptor = new
                TransactionInterceptor(transactionManager, source);  // 事务拦截器
        return transactionInterceptor;
    }

    @Bean
    public Advisor transactionAdviceAdvisor(TransactionInterceptor interceptor) {
        String express = "execution (* com.yootk..service.*.*(..))"; // 定义切面表达式
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(express);            // 定义切面
        return new DefaultPointcutAdvisor(pointcut, interceptor);
    }
}


6、
package com.yootk;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@ComponentScan({"com.yootk"}) // 定义扫描包
// 现在是MyBatis交由Spring进行事务管理,所以一定要编写如下的注解,如果不编写,那么事务无法生效
@EnableAspectJAutoProxy // 启用AOP代理
public class StartMyBatisApplication { // Spring项目启动类
}


7、
package com.yootk.config;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
public class MyBatisConfig { // MyBatis配置类
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(
            @Autowired DataSource dataSource) throws IOException { // mybatis.cfg.xml代替
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        // 整体的配置现在全部交由Spring负责,所以此处一定要将数据源设置进来
        factoryBean.setDataSource(dataSource); // 配置数据源
        factoryBean.setTypeAliasesPackage("com.yootk.vo"); // 别名扫描包
        // 虽然现在提倡的是“零配置”,可是MyBatis不管如何开发都建议保留Mapper映射文件
        // mybatis.cfg.xml配置文件是需要分别定义Mapper文件名称的,但是现在可以扫描实现
        PathMatchingResourcePatternResolver resolver =
                new PathMatchingResourcePatternResolver(); // 路径匹配资源
        String mapperPath = PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                "mybatis/mapper/*.xml"; // 所有Mapper文件的路径批磅亿毫
        factoryBean.setMapperLocations(resolver.getResources(mapperPath));
        return factoryBean;
    }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        // 传统的数据层开发之中,是通过了DAO调用Mybatis组件,这个操作很重复
        // Spring针对于MyBatis的实现可以自动生成一个代理类,这个代理类会自动调用映射配置SQL
        MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
        scannerConfigurer.setBasePackage("com.yootk.dao"); // dao程序包路径
        // 一般良好的做法都需要在需要映射的DAO上配置有一个映射注解(可以没有)
        scannerConfigurer.setAnnotationClass(Mapper.class); // MyBatis提供的注解
        return scannerConfigurer;
    }
}


8、
package com.yootk.vo;

import java.io.Serializable;
public class Message implements Serializable {
    private Long mid;
    private String title;
    private String sender;
    private String content;
    public Long getMid() {
        return mid;
    }
    public void setMid(Long mid) {
        this.mid = mid;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getSender() {
        return sender;
    }
    public void setSender(String sender) {
        this.sender = sender;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}


9、
package com.yootk.dao;

import com.yootk.vo.Message;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
import java.util.Map;
@Mapper // MyBatis自动生成的标志注解
public interface IMessageDAO { // 消息表的数据层接口
    public boolean doCreate(Message message); // 实现数据增加
    /**
     * 实现消息数据的分页查询
     * @param params 表示分页操作的相关参数,本次的参数包含有如下两组内容:
     *               1. key = start、value = 分页开始的数据行数
     *               2. key = line、value = 每页抓取的数据量
     * @return 获取到的全部数据集合
     */
    public List<Message> findAll(Map<String, Object> params);
}


10、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 设置命名空间,可以与不同表的同类型操作进行区分,使用时以“空间名称.id”的方式调用 -->
<mapper namespace="com.yootk.dao.IMessageDAO"> <!-- 与数据层接口名称一致 -->
    <!--  此时编写的ID为数据层方法的名称,而且这个操作将自动被Spring所调用  -->
    <insert id="doCreate" parameterType="Message">
        INSERT INTO message(sender, title, content) VALUES (#{sender}, #{title}, #{content})
    </insert>
    <select id="findAll" resultType="Message">
        SELECT mid, sender, title, content FROM message LIMIT #{start}, #{line}
    </select>
</mapper>

11、
package com.yootk.service;

import com.yootk.vo.Message;

import java.util.List;

public interface IMessageService {
    public boolean add(Message message);
    public List<Message> list(int current, int line);
}

12、
package com.yootk.service.impl;

import com.yootk.dao.IMessageDAO;
import com.yootk.service.IMessageService;
import com.yootk.vo.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class MessageServiceImpl implements IMessageService {
    @Autowired
    private IMessageDAO messageDAO; // 注入DAO代理实例
    @Override
    public boolean add(Message message) {
        return this.messageDAO.doCreate(message);
    }

    @Override
    public List<Message> list(int current, int line) {
        Map<String, Object> params = new HashMap<>();
        params.put("start", (current - 1) * line); // 计算开始行
        params.put("line", line); // 抓取的行数
        return this.messageDAO.findAll(params);
    }
}

13、
package com.yootk.test;

import com.yootk.StartMyBatisApplication;
import com.yootk.service.IMessageService;
import com.yootk.util.MyBatisSessionFactory;
import com.yootk.vo.Account;
import com.yootk.vo.Details;
import com.yootk.vo.Message;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.List;

@ContextConfiguration(classes = StartMyBatisApplication.class) // 定义启动类
@ExtendWith(SpringExtension.class)
public class TestMessageService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestMessageService.class);
    @Autowired
    private IMessageService messageService;
    @Test
    public void testAdd() {
        Message message = new Message();
        message.setSender("李兴华");
        message.setTitle("李兴华原创编程图书");
        message.setContent("《Spring开发实战》+《SSM开发实战》+《SpringBoot开发实战》");
        LOGGER.info("增加消息:{}", this.messageService.add(message));
    }
    @Test
    public void testList() {
        List<Message> messages = this.messageService.list(1, 2);
        for (Message msg : messages) {
            LOGGER.info("【消息】ID:{}、发送者:{}、标题:{}、内容:{}",
                    msg.getMid(), msg.getSender(), msg.getTitle(), msg.getContent());
        }
    }
}



使用注解配置 SQL 命令

SQL 注解配置

  • MyBatis 基于映射文件的方式实现了所有 SQL 命令的定义,这样数据层就可以基于映射文件的方式生成所需要的对象实例,但是随着数据层功能的逐步增加,最终也必然会带来映射文件配置项的增加,这样在进行代码维护时,就需要花费大量的时间进行配置定位。为了解决这样的设计问题,在 MyBatis 中提供了注解的配置支持,在数据层上通过注解配置要使用的 SQL 语句,以代替映射文件的使用
1、
package com.yootk.config;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
public class MyBatisConfig { // MyBatis配置类
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(
            @Autowired DataSource dataSource) throws IOException { // mybatis.cfg.xml代替
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        // 整体的配置现在全部交由Spring负责,所以此处一定要将数据源设置进来
        factoryBean.setDataSource(dataSource); // 配置数据源
        factoryBean.setTypeAliasesPackage("com.yootk.vo"); // 别名扫描包
        // 虽然现在提倡的是“零配置”,可是MyBatis不管如何开发都建议保留Mapper映射文件
        // mybatis.cfg.xml配置文件是需要分别定义Mapper文件名称的,但是现在可以扫描实现
//        PathMatchingResourcePatternResolver resolver =
//                new PathMatchingResourcePatternResolver(); // 路径匹配资源
//        String mapperPath = PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
//                "mybatis/mapper/*.xml"; // 所有Mapper文件的路径批磅亿毫
//        factoryBean.setMapperLocations(resolver.getResources(mapperPath));
        return factoryBean;
    }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        // 传统的数据层开发之中,是通过了DAO调用Mybatis组件,这个操作很重复
        // Spring针对于MyBatis的实现可以自动生成一个代理类,这个代理类会自动调用映射配置SQL
        MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
        scannerConfigurer.setBasePackage("com.yootk.dao"); // dao程序包路径
        // 一般良好的做法都需要在需要映射的DAO上配置有一个映射注解(可以没有)
        scannerConfigurer.setAnnotationClass(Mapper.class); // MyBatis提供的注解
        return scannerConfigurer;
    }
}


2、
package com.yootk.dao;

import com.yootk.vo.Message;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;

import java.util.List;
import java.util.Map;
@Mapper // MyBatis自动生成的标志注解
public interface IMessageDAO { // 消息表的数据层接口
    @Insert("INSERT INTO message(sender, title, content) VALUES " +
            " (#{sender}, #{title}, #{content})") // 直接定义SQL
    @SelectKey(before = false, keyProperty = "mid", keyColumn = "mid",
        resultType = java.lang.Long.class, statement = "SELECT LAST_INSERT_ID()")
    public boolean doCreate(Message message); // 实现数据增加
    /**
     * 实现消息数据的分页查询
     * @param params 表示分页操作的相关参数,本次的参数包含有如下两组内容:
     *               1. key = start、value = 分页开始的数据行数
     *               2. key = line、value = 每页抓取的数据量
     * @return 获取到的全部数据集合
     */
    @Select("SELECT mid, sender, title, content FROM message LIMIT #{start}, #{line}")
    public List<Message> findAll(Map<String, Object> params);
}


3、
    @Test
    public void testAdd() {
        Message message = new Message();
        message.setSender("李兴华");
        message.setTitle("李兴华原创编程图书");
        message.setContent("《Spring开发实战》+《SSM开发实战》+《SpringBoot开发实战》");
        LOGGER.info("增加消息:{}", this.messageService.add(message));
        LOGGER.info("增长后的ID:{}", message.getMid());
    }

SQL 语句构建器

SQL 构建器

  • 在项目开发中,应用程序主要依靠 SQL 命令实现各类数据操作,考虑到实际项目中的 SQL 命令定义较为繁琐,所以在 MyBatis 中提供了 SQL 语句构建器的支持,开发者只需要定义一个专属的 SQL 命令管理类,而后就可以通过里面定义的方法实现 SQL 命令的生成操作

1、
package com.yootk.sql;

import org.apache.ibatis.jdbc.SQL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MessageSQL {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageSQL.class);
    public String insertMessageSQL() { // 生成增加SQL命令
        String sql = new SQL()
                .INSERT_INTO("message")
                .INTO_COLUMNS("sender", "title", "content")
                .INTO_VALUES("#{sender}", "#{title}", "#{content}")
                .toString();
        LOGGER.debug("【生成增加SQL命令】{}", sql);
        return sql;
    }
    public String selectMessageSplitSQL() {
        String sql = new SQL()
                .SELECT("mid", "sender", "title", "content")
                .FROM("message")
                .LIMIT("#{start}, #{line}")
                .toString();
        LOGGER.debug("【生成查询SQL命令】{}", sql);
        return sql;
    }
}


2、
package com.yootk.dao;

import com.yootk.sql.MessageSQL;
import com.yootk.vo.Message;
import org.apache.ibatis.annotations.*;

import java.util.List;
import java.util.Map;
@Mapper // MyBatis自动生成的标志注解
public interface IMessageDAO { // 消息表的数据层接口
    @InsertProvider(type = MessageSQL.class, method = "insertMessageSQL")
    @SelectKey(before = false, keyProperty = "mid", keyColumn = "mid",
        resultType = java.lang.Long.class, statement = "SELECT LAST_INSERT_ID()")
    public boolean doCreate(Message message); // 实现数据增加
    /**
     * 实现消息数据的分页查询
     * @param params 表示分页操作的相关参数,本次的参数包含有如下两组内容:
     *               1. key = start、value = 分页开始的数据行数
     *               2. key = line、value = 每页抓取的数据量
     * @return 获取到的全部数据集合
     */
    @SelectProvider(type = MessageSQL.class, method = "selectMessageSplitSQL")
    public List<Message> findAll(Map<String, Object> params);
}

MyBatis 代码生成器

重复的 CRUD 功能

  • 数据层的代码是整个项目业务实现的核心结构,但是其又存在有大量重复的编码实现例如:现在存在有 20 张实体表,那么在开发中就需要为这 20 张数据表分别定义功能相同,但是操作类型不同的 CRUD 方法,这样就会出现大量的重复性且必要的程序编码。

MyBatis 代码生成

  • 为了进一步提升项目开发的效率,最佳的做法是由一些专属的工具帮助用户自动的生成指定数据表的 VO 类、DAO 接口以及 SQL 映射文件,这些生成的代码中可以包含有基础的 CRUD 功能。这样开发者就可以基于当前已生成的代码进行扩展,从而避免了大量重复功能的编写,

MyBatis 代码生成器

  • 如果要想实现这样的代码生成处理,一般都需要结合 Gradle 或 Maven 构建工具的插件来实现,本次将基于 Gradle 工具讲解代码生成的处理实现
1、
project(":generator") {
    dependencies{}
}

2、
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
    <!-- 数据库驱动程序路径:选择本地硬盘上面的数据库驱动包,路径不要出现中文-->
    <classPathEntry location="H:\lib\mysql-connector-j-8.0.31.jar"/>
    <context id="YootkContext" targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressDate" value="true"/> 	<!-- 去除时间戳 -->
            <property name="suppressAllComments" value="true"/>  <!-- 去除注释 -->
        </commentGenerator>
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/yootk"
                        userId="root" password="mysqladmin"> 	<!-- JDBC连接信息 -->
        </jdbcConnection>
        <javaTypeResolver>  <!-- 在数据库类型和Java类型之间进行转换 -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- 生成VO类所在的包名,以及程序类的保存位置-->
        <javaModelGenerator targetPackage="com.yootk.vo" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>	<!-- 允许生成子包 -->
            <property name="constructorBased" value="true"/>	<!-- 添加构造方法 -->
            <property name="trimStrings" value="true"/> 	<!-- 去除空格 -->
            <!-- 建立的VO对象是否不可改变  即生成的VO对象不会有setter方法,只提供构造方法 -->
            <property name="immutable" value="false"/>
        </javaModelGenerator>
        <!-- 保存Mapper映射文件所在的目录,每一张数据表都会生成对应的Sql映射文件 -->
        <sqlMapGenerator targetPackage="mybatis/mapper"
                         targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>	<!-- 允许生成子包 -->
        </sqlMapGenerator>
        <!-- 数据层代码生成配置,有三种生成方案,分别是映射配置、注解配置、SQL生成器配置-->
        <!-- type="ANNOTATEDMAPPER",基于注解的生成DAO接口,同时会自动生成SQL构建器工具类 -->
        <!-- type="MIXEDMAPPER",基于注解的方式生成DAO接口,同时生成SQL映射文件 -->
        <!-- type="XMLMAPPER",创建原生DAO接口,并生成与之对应的SQL映射文件 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.yootk.dao"
                             targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>	<!-- 允许生成子包 -->
        </javaClientGenerator>
        <!-- 配置数据表信息,每一张实体表都对应一个VO类,根据需要选择生成不同的样例代码 -->
        <table tableName="message" domainObjectName="Message"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false"/>
    </context>
</generatorConfiguration>


3、
def file = 'src/main/resources/mybatis/mybatis-generator-config.xml'
mybatisGenerator { 			// MyBatis生成任务
    verbose = true 			// 显式详细信息
    configFile = file 			// 配置文件路径
}

demo


上次编辑于: