使用MyBatis的动态SQL注解实现实体的CRUD操作代码
作者:徐州蔡徐坤
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对于此种需求的支持并不友好
/** * 资源量统计-根据专题 * 之所以使用此种方式是因为统计的列是不固定的,所以需要动态拼接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注解可以提高代码的灵活性和可维护性。
以上就是使用MyBatis的动态SQL注解实现实体的CRUD操作代码的详细内容,更多关于MyBatis动态SQLCRUD操作的资料请关注脚本之家其它相关文章!