使用MyBatis的动态SQL注解实现实体的CRUD操作

news2024/11/26 11:31:24

使用MyBatis的动态SQL注解实现实体的CRUD操作

  • 1. 引言
  • 2. 准备工作
    • 2.1 创建数据表
    • 2.2 创建实体类 `Book`
    • 2.3 修改Mapper接口 `BookMapper`
  • 3. 测试CRUD操作
    • 3.1 配置日志
    • 3.2 查询操作
    • 3.3 新增操作
    • 3.4 修改操作
    • 3.5 删除操作
  • 4. 与MyBatis Plus的对比
  • 5. 动态SQL注解的适用场景
    • 5.1 动态查询条件
    • 5.2 动态插入和更新
    • 5.3 复杂的业务逻辑
    • 5.4 查询结果动态列的聚合
  • 6. 小结

1. 引言

在使用MyBatis进行数据库操作时,动态SQL注解提供了一种优雅的方式来编写动态SQL语句。MyBatis 3.x 版本提供了以下四个CRUD的高级注解:

  • @SelectProvider:用于构建动态查询SQL。
  • @InsertProvider:用于构建动态新增SQL。
  • @UpdateProvider:用于构建动态更新SQL。
  • @DeleteProvider:用于构建动态删除SQL。

这些注解可以帮助开发者在Mapper接口中动态地构建SQL语句,避免了在XML配置文件中编写大量的SQL代码,使代码更加简洁和易于维护。本文将详细介绍如何使用这些动态SQL注解来实现书籍信息的查询、新增、修改和删除操作。

此外,我们将与MyBatis Plus中的@Select注解进行对比,展示MyBatis动态SQL注解的优势和灵活性。

2. 准备工作

2.1 创建数据表

首先,需要创建一个代表书籍信息的表 tb_book

-- 判断数据表是否存在,存在则删除
DROP TABLE IF EXISTS tb_book;

-- 创建“书籍信息”数据表
CREATE TABLE IF NOT EXISTS tb_book
(
    book_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '书籍编号',
    book_name VARCHAR(50) NOT NULL COMMENT '书名',
    author VARCHAR(50) NOT NULL COMMENT '作者',
    publisher VARCHAR(50) COMMENT '出版社',
    publish_date DATE COMMENT '出版日期'
) COMMENT = '书籍信息表';

-- 添加数据
INSERT INTO tb_book(book_name, author, publisher, publish_date) VALUES ('书籍1', '作者1', '出版社1', '2023-01-01');

2.2 创建实体类 Book

com.zhouquan.entity 包中创建 Book 类,使用 @Data 注解来自动生成getter和setter方法。

package com.zhouquan.entity;
import lombok.Data;
import java.util.Date;

/**
 * 书籍信息实体类
 */
@Data
public class Book {
    /**
     * 书籍编号
     */
    private int bookId;
    
    /**
     * 书名
     */
    private String bookName;
    
    /**
     * 作者
     */
    private String author;
    
    /**
     * 出版社
     */
    private String publisher;
    
    /**
     * 出版日期
     */
    private Date publishDate;
}

2.3 修改Mapper接口 BookMapper

com.zhouquan.mapper 包中创建 BookMapper 接口,并使用动态SQL注解来实现CRUD操作。

package com.zhouquan.mapper;

import com.zhouquan.entity.Book;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.jdbc.SQL;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface BookMapper {

    @SelectProvider(type = BookSqlBuilder.class, method = "buildGetBookByIdSql")
    public Book getBookById(@Param("bookId") int bookId);

    @InsertProvider(type = BookSqlBuilder.class, method = "buildInsertBookSql")
    @Options(useGeneratedKeys = true, keyColumn = "book_id", keyProperty = "bookId")
    public int insertBook(Book book);

    @UpdateProvider(type = BookSqlBuilder.class, method = "buildUpdateBookSql")
    public int updateBook(Book book);

    @DeleteProvider(type = BookSqlBuilder.class, method = "buildDeleteBookSql")
    public int deleteBook(@Param("bookId") int bookId);

    class BookSqlBuilder {
        public String buildGetBookByIdSql(@Param("bookId") int bookId) {
            return new SQL() {{
                SELECT("*");
                FROM("tb_book");
                WHERE("book_id = #{bookId}");
            }}.toString();
        }

        public String buildInsertBookSql(Book book) {
            return new SQL() {{
                INSERT_INTO("tb_book");
                VALUES("book_name", "#{bookName}");
                VALUES("author", "#{author}");
                VALUES("publisher", "#{publisher}");
                VALUES("publish_date", "#{publishDate}");
            }}.toString();
        }

        public String buildUpdateBookSql(Book book) {
            return new SQL() {{
                UPDATE("tb_book");
                SET("book_name = #{bookName}", "author = #{author}", "publisher = #{publisher}", "publish_date = #{publishDate}");
                WHERE("book_id = #{bookId}");
            }}.toString();
        }

        public String buildDeleteBookSql(@Param("bookId") int bookId) {
            return new SQL() {{
                DELETE_FROM("tb_book");
                WHERE("book_id = #{bookId}");
            }}.toString();
        }
    }
}

3. 测试CRUD操作

为了测试CRUD操作,我们将使用JUnit进行单元测试,并通过日志记录操作的结果。

3.1 配置日志

在Maven的pom.xml文件中引入SLF4J和Logback的依赖:

<dependencies>
    <!-- MyBatis 依赖 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>
    
    <!-- SLF4J 依赖 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
  
</dependencies>

3.2 查询操作

import com.zhouquan.entity.Book;
import com.zhouquan.mapper.BookMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BookMapperTest {

    private static final Logger logger = LoggerFactory.getLogger(BookMapperTest.class);

    @Autowired
    private BookMapper bookMapper;

    /**
     * 根据书籍ID,获取书籍信息
     */
    @Test
    public void getBookById() {
        Book book = bookMapper.getBookById(1);
        logger.info("书籍编号:{}", book.getBookId());
        logger.info("书名:{}", book.getBookName());
        logger.info("作者:{}", book.getAuthor());
        logger.info("出版社:{}", book.getPublisher());
        logger.info("出版日期:{}", book.getPublishDate());
    }
}

3.3 新增操作

@Autowired
private BookMapper bookMapper;

/**
 * 新增书籍,并获取自增主键
 */
@Test
public void insertBook() {
    Book book = new Book();
    book.setBookName("新书");
    book.setAuthor("新作者");
    book.setPublisher("新出版社");
    book.setPublishDate(new Date());
    bookMapper.insertBook(book);
    logger.info("新增书籍ID:{}", book.getBookId());
}

3.4 修改操作

@Autowired
private BookMapper bookMapper;

/**
 * 更新书籍信息
 */
@Test
public void updateBook() {
    Book book = bookMapper.getBookById(1);
    book.setBookName("更新后的书名");
    bookMapper.updateBook(book);
    logger.info("更新后的书名:{}", book.getBookName());
}

3.5 删除操作

@Autowired
private BookMapper bookMapper;

/**
 * 删除书籍
 */
@Test
public void deleteBook() {
    bookMapper.deleteBook(1);
    logger.info("删除书籍ID为1的记录");
}

4. 与MyBatis Plus的对比

MyBatis Plus提供了一种更加简洁的方式来编写SQL语句,例如,使用@Select注解可以直接在Mapper接口中编写SQL语句,而无需单独定义SQL构建器类。

例如,使用MyBatis Plus可以这样定义BookMapper接口:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhouquan.entity.Book;
import org.apache.ibatis.annotations.Select;

public interface BookMapper extends BaseMapper<Book> {

    @Select("SELECT * FROM tb_book WHERE book_id = #{bookId}")
    Book getBookById(int bookId);
}

这种方式相对于MyBatis动态SQL注解而言,更加直接和易读。但是,MyBatis动态SQL注解在处理复杂SQL语句时具有更高的灵活性和可维护性,特别是当需要根据不同条件动态生成SQL时,MyBatis动态SQL注解显得更为强大和适用

5. 动态SQL注解的适用场景

5.1 动态查询条件

在一些应用中,查询条件是动态的,可能根据不同的用户输入或业务逻辑生成不同的查询条件。使用动态SQL注解可以在运行时根据这些条件动态构建查询语句,避免了硬编码查询条件的问题。

public String buildDynamicQuerySql(Map<String, Object> params) {
    return new SQL() {{
        SELECT("*");
        FROM("tb_book");
        if (params.get("author") != null) {
            WHERE("author = #{author}");
        }
        if (params.get("publisher") != null) {
            WHERE("publisher = #{publisher}");
        }
        if (params.get("publishDate") != null) {
            WHERE("publish_date = #{publishDate}");
        }
    }}.toString();
}

5.2 动态插入和更新

在一些情况下,插入或更新的数据字段可能不是固定的,而是根据业务需求动态变化的。使用动态SQL注解可以根据传入的实体对象构建动态插入或更新语句。

public String buildDynamicInsertSql(Book book) {
    return new SQL() {{
        INSERT_INTO("tb_book");
        if (book.getBookName() != null) {
            VALUES("book_name", "#{bookName}");
        }
        if (book.getAuthor() != null) {
            VALUES("author", "#{author}");
        }
        if (book.getPublisher() != null) {
            VALUES("publisher", "#{publisher}");
        }
        if (book.getPublishDate() != null) {
            VALUES("publish_date", "#{publishDate}");
        }
    }}.toString();
}

public String buildDynamicUpdateSql(Book book) {
    return new SQL() {{
        UPDATE("tb_book");
        if (book.getBookName() != null) {
            SET("book_name = #{bookName}");
        }
        if (book.getAuthor() != null) {
            SET("author = #{author}");
        }
        if (book.getPublisher() != null) {
            SET("publisher = #{publisher}");
        }
        if (book.getPublishDate() != null) {
            SET("publish_date = #{publishDate}");
        }
        WHERE("book_id = #{bookId}");
    }}.toString();
}

5.3 复杂的业务逻辑

在一些复杂的业务场景中,SQL语句的构建逻辑可能非常复杂,涉及多个表的联接、多种条件的判断等。使用动态SQL注解可以在Java代码中使用条件逻辑来构建复杂的SQL语句,使代码更加清晰和易于维护。

public String buildComplexQuerySql(Book book) {
    return new SQL() {{
        SELECT("b.*, a.author_name, p.publisher_name");
        FROM("tb_book b");
        JOIN("tb_author a ON b.author_id = a.author_id");
        JOIN("tb_publisher p ON b.publisher_id = p.publisher_id");
        if (book.getBookName() != null) {
            WHERE("b.book_name LIKE CONCAT('%', #{bookName}, '%')");
        }
        if (book.getAuthor() != null) {
            WHERE("a.author_name LIKE CONCAT('%', #{author}, '%')");
        }
        if (book.getPublisher() != null) {
            WHERE("p.publisher_name LIKE CONCAT('%', #{publisher}, '%')");
        }
    }}.toString();
}

5.4 查询结果动态列的聚合

需求类似于下,根据专题聚合出不同的资料类型,select中的列需要动态的拼接,显然MP的mapper对于此种需求的支持并不友好

image-20240621171513659

 /**
     * 资源量统计-根据专题
     * 之所以使用此种方式是因为统计的列是不固定的,所以需要动态拼接select
     *
     * @param resourceTypeNameList 资源名称列表
     * @param resourceTypeSidList  资源sid列表
     * @param start                加工开始时间
     * @param end                  加工结束时间
     * @return
     */
    public String groupTopicDynamicSQL(final List<String> resourceTypeNameList, final List<String> resourceTypeSidList,
                                       final LocalDate start, final LocalDate end) {
        // 构建外部查询
        SQL outerQuery = new SQL();
        outerQuery.SELECT("COALESCE(topic_name, '总计') AS '专题'");

        if (resourceTypeNameList != null && !resourceTypeNameList.isEmpty()) {
            for (String rt : resourceTypeNameList) {
                outerQuery.SELECT(String.format("SUM(CASE WHEN resource_type_name = '%s' THEN count ELSE 0 END) AS " +
                        "'%s'", rt, rt));
            }
        }

        // 构建子查询
        SQL subQuery = new SQL();
        subQuery.SELECT("rt.name AS resource_type_name, t.name AS topic_name, COUNT(*) AS count")
                .FROM("works w")
                .JOIN("works_topic wt ON w.sid = wt.work_sid")
                .JOIN("topic t ON wt.topic_sid = t.sid")
                .JOIN("resource_type rt ON w.type_leaf_sid = rt.sid")
                .WHERE(" w.deleted=0 ");

        if (resourceTypeNameList != null && !resourceTypeNameList.isEmpty()) {
            String joinedResourceTypes = resourceTypeSidList.stream()
                    .map(rt -> "'" + rt + "'")
                    .collect(Collectors.joining(", "));
            subQuery.WHERE("w.type_leaf_sid IN (" + joinedResourceTypes + ")");
        }
        if (start != null) {
            subQuery.WHERE("w.update_time >= #{start}");
        }
        if (end != null) {
            subQuery.WHERE("w.update_time <= #{end}");
        }
        subQuery.GROUP_BY("rt.name, t.name");

        outerQuery.SELECT("SUM(count) AS '总计'")
                .FROM("(" + subQuery + ") AS subQuery")
                .GROUP_BY("topic_name WITH ROLLUP");

        return outerQuery.toString();
    }
/**
 * 统计分析-资源量统计
 *
 * @param resourceTypeNameList
 * @param resourceTypeSidList
 * @param start
 * @param end
 * @return
 */
@SelectProvider(type = WorksSqlProvider.class, method = "groupTopicDynamicSQL")
List<Map<String, Object>> staticByTopic(@Param("resourceTypeNameList") List<String> resourceTypeNameList,
                                        @Param("resourceTypeSidList") List<String> resourceTypeSidList,
                                        @Param("start") LocalDate start,
                                        @Param("end") LocalDate end);

6. 小结

MyBatis动态SQL注解通过提供更加灵活和动态的方式来构建SQL语句,使得代码更加简洁和易于维护。而MyBatis Plus通过简化SQL编写过程,提供了一种更加便捷的方式来进行数据库操作。根据具体项目的需求和复杂度,可以选择适合的工具来实现数据库操作。

通过上述介绍,可以看出动态SQL注解在处理动态查询条件、动态插入和更新以及复杂的业务逻辑时具有明显的优势和适用性。在实际开发中,合理使用动态SQL注解可以提高代码的灵活性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1873223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python中相关软件安装

1. python安装 1.下载地址 https://www.python.org/downloads/2.选择安装版本 1. Anaconda安装 安装地址 -- 清华大学镜像站点 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/查看anaconda是否安装成功 2.conda安装好后&#xff0c;将镜像源修改为清华大学的镜像…

Python项目-文本语音转换器【附源码】

文本–>语音转换器 本项目是一个简单的从文本到语音这样一个转换器的图形用户界面应用&#xff0c;使用了Python的tkinter库来构建界面&#xff0c;以及pyttsx3库来执行转换。以下是对该项目代码的详细解释&#xff0c;后面会附上本项目的完整代码&#xff1a; 实现效果&am…

文本情绪指数与上证指数的VAR模型构建

大家好&#xff0c;我是带我去滑雪&#xff01; 在前一篇文章中&#xff0c;笔者爬取了东方财富网上证指数股吧的495775条评论数据&#xff0c;并对文本进行了情感分析&#xff0c;基于自制的股票情感词典&#xff0c;使用了深度学习模型对爬取的文本数据进行分类标注&#xff…

24V 350W开关电源电路原理图+PCB工程文件 UC3843AD lm193芯片

资料下载地址&#xff1a;24V 350W开关电源电路原理图PCB工程文件 UC3843AD lm193芯片 1、原理图 2、PCB

算法题 — 接雨水

给定 n 给非负整数&#xff0c;表示每个宽度为 1 的柱子的高度图&#xff0c;计算按照此排列的柱子&#xff0c;下雨之后能能接到多少雨水。 输入&#xff1a;height [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0, 1, 0, 2, 1,…

有了这5款在线去除图片水印工具,妈妈再也不用担心图片水印问题了

Inpaint Inpaint是一款功能强大的图像处理软件&#xff0c;主要用于去除图片中的水印、杂物等不需要的元素。它的主要功能包括&#xff1a; 去除水印&#xff1a;用户可以通过Inpaint轻松去除图片中的水印。只需在软件界面中打开带有水印的图片&#xff0c;然后使用标记工具选…

深入探索Java开发世界:Redis~类型分析大揭秘

文章目录 深入探索Java开发世界&#xff1a;Redis~类型分析大揭秘一、数据结构类型二、分布式锁类型三、事物命令类型四、事物三大特性类型 深入探索Java开发世界&#xff1a;Redis~类型分析大揭秘 Redis数据库基础知识&#xff0c;类型知识点梳理~ 一、数据结构类型 Redis是一…

Pycharm导入内置库或者第三方库时标红,no module named ‘xxx‘

各版本的Pycharm都有可能会出现这样的问题&#xff1a;有些时候内置库和第三方库被标红为“No module named xxx”&#xff0c;而自己的库却能被正常导入。 本人是在使用远程ssh解释器时遇到的。实际运行该代码文件时&#xff0c;能够正常运行&#xff08;若不能正常运行则可能…

Stop Motion Studio Pro for Mac:Mac上的动画大师,让你的创意无限流动!

Stop Motion Studio Pro for Mac为创作者们提供了一个直观且易于使用的平台&#xff0c;让他们能够将静态的物体和场景转化为生动有趣的定格动画。&#x1f3a5; 无论是制作简单的玩具动画&#xff0c;还是复杂的电影级场景&#xff0c;这款软件都能轻松应对&#xff0c;让你的…

浅谈Mysql Innodb存储引擎

一、Mysql整体架构 二、MySQL 5.7 支持的存储引擎 类型 描述 MyISAM 拥有较高的插入、查询速度&#xff0c;但不支持事务 InnoDB 5.5版本后Mysql的默认数据库&#xff0c;5.6版本后支持全文索引&#xff0c;事务型数据库的首选引擎&#xff0c;支持ACID事务&#xff0c;支…

真正的智慧——诺:九九归一,以简驭繁

一、九九归一 国学道家中有物极必反的理念&#xff0c;所以&#xff0c;中国人有九九归一的说法&#xff0c;在基本数字中&#xff0c;九是大数&#xff0c;九九之意&#xff0c;相当于后天八卦一样&#xff0c;相当于一个系统完成了一次大的循环&#xff0c;九九归一&#xf…

数据资产赋能企业决策:通过精准的数据分析和洞察,构建高效的数据资产解决方案,为企业提供决策支持,助力企业实现精准营销、风险管理、产品创新等目标,提升企业竞争力

一、引言 在信息化和数字化飞速发展的今天&#xff0c;数据已成为企业最宝贵的资产之一。数据资产不仅包含了企业的基本信息&#xff0c;还蕴含了丰富的市场趋势、消费者行为和潜在商机。如何通过精准的数据分析和洞察&#xff0c;构建高效的数据资产解决方案&#xff0c;为企…

【Confluence】markdown格式转换为Confluence

简单的文本可以使用网站来快速转换&#xff0c;但是发现很多格式不能正确转换&#xff0c;所以研究了一个Py的方法来实现&#xff0c;如下&#xff1a; 安装Py插件 本方法主要借用markdown2 来实现&#xff0c;开始之前需要先安装一些库。 pip install markdown2 beautiful…

葡萄串目标检测YoloV8——从Pytorch模型训练到C++部署

文章目录 软硬件准备数据准备数据处理脚本模型训练模型部署数据分享软硬件准备 训练端 PytorchultralyticsNvidia 3080Ti部署端 fastdeployonnxruntime数据准备 用labelimg进行数据标注 数据处理脚本 xml2yolo import os import glob import xml.etree.ElementTree as ETxm…

如何在写代码中找到乐趣

平时我们写代码呢&#xff0c;多数情况都是流水线式写代码&#xff0c;基本就可以实现业务逻辑了。 如何在写代码中找到乐趣呢&#xff0c;我觉得&#xff0c;最好的方式就是&#xff1a;使用设计模式优化自己的业务代码。 参考资料&#xff1a; 实战&#xff01;工作中常用到…

【C++进阶9】异常

一、C语言传统的处理错误的方式 终止程序&#xff0c;如assert 如发生内存错误&#xff0c;除0错误时就会终止程序返回错误码 需要程序员自己去查找对应的错误 z如系统的很多库的接口函数都是通 过把错误码放到errno中&#xff0c;表示错误 二、C异常概念 异常&#xff1a;函…

企业出海的浪潮下,如何利用亚马逊云(AWS)更好地应对?

在全球化的浪潮下&#xff0c;越来越多的企业开始将目光投向国际市场。在这个数字化时代&#xff0c;云计算技术成为企业出海的必备利器之一。AWS云作为全球领先的云服务提供商&#xff0c;凭借其卓越的性能和完善的服务体系&#xff0c;成为众多企业出海的首选。 一、出海为什…

【Mybatis 与 Spring】事务相关汇总

之前分享的几篇文章可以一起看&#xff0c;形成一个体系 【Mybatis】一级缓存与二级缓存源码分析与自定义二级缓存 【Spring】Spring事务相关源码分析 【Mybatis】Mybatis数据源与事务源码分析 Spring与Mybaitis融合 SpringManagedTransaction&#xff1a; org.mybatis.spri…

小马搬运物品-第13届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第89讲。 小马搬运物品&…

Redis 哨兵主备切换的数据丢失问题应该怎么解决?

引言&#xff1a;Redis作为一种高性能的内存数据库&#xff0c;广泛应用于分布式系统中。为了保证服务的高可用性&#xff0c;Redis提供了哨兵&#xff08;Sentinel&#xff09;机制&#xff0c;用于监控和管理Redis实例的自动故障恢复。然而&#xff0c;即使在哨兵的保护下&am…