标签 mybatis 下的文章


MyBatis Dynamic SQL 是一种类型安全的 Java 领域特定语言(DSL),用于通过编程方式构建 SQL 查询,而非编写 SQL 字符串或基于 XML 的动态查询。它在运行时使用流畅的 Java 构建器生成 SQL,同时仍通过标准的 MyBatis 映射器执行。与手动拼接字符串或复杂的 XML 逻辑相比,这使得查询构建更安全、更易于重构,并且更不容易出错。

由于查询是用 Java 编写的,列名和表引用通过强类型的元数据类在编译时进行验证,这提供了更好的 IDE 支持并减少了运行时 SQL 错误。本文将解释 MyBatis Dynamic SQL,并展示如何在 Java 应用程序中使用它。

1. 使用 MyBatis Dynamic SQL 可以做什么?

MyBatis Dynamic SQL 支持大多数常见的 SQL 操作,包括 SELECTINSERTUPDATEDELETE,以及连接、子查询、分页、排序、条件过滤和批量操作。它允许我们逐步构建查询,仅在某些参数存在时添加条件,这使其成为搜索界面和过滤 API 的理想选择。

它直接与 MyBatis 映射器接口集成,意味着我们仍然可以受益于结果映射、事务处理和连接管理。由于它生成标准的 SQL,因此适用于 MyBatis 支持的任何数据库,没有供应商锁定的问题。

1.1 MyBatis Dynamic SQL 的工作原理

Dynamic SQL 基于两个组件:表元数据类和 DSL 构建器。元数据类用 Java 描述表和列。DSL 构建器使用这些类以流畅、类型安全的方式组装 SQL 语句。

DSL 不直接执行 SQL。相反,它生成语句提供者对象,例如 SelectStatementProviderInsertStatementProvider。这些对象被传递给使用 @SelectProvider@InsertProvider 及类似注解标注的映射器方法,然后 MyBatis 使用其正常的执行引擎来执行这些语句。

2. 项目设置与依赖

Maven 依赖

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.15</version>
</dependency>

<dependency>
    <groupId>org.mybatis.dynamic-sql</groupId>
    <artifactId>mybatis-dynamic-sql</artifactId>
    <version>1.5.2</version>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.4.240</version>
    <scope>runtime</scope>
</dependency>

这些依赖项包括 MyBatis 本身、Dynamic SQL DSL 以及一个用于测试的嵌入式数据库。

注意
数据库驱动可以替换为 MySQL、PostgreSQL 或任何其他支持的数据库。MyBatis Dynamic SQL 不依赖于数据库类型,仅依赖于标准 SQL 生成。

数据库模式

schema.sql

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    age INT
);

INSERT INTO users(username, email, age) VALUES
('thomas', 'thomas@jcg.com', 30),
('benjamin', 'benjamin@jcg.com', 22),
('charles', 'charles@jcg.com', 17);

此脚本创建一个简单的表并插入测试数据。内存中的 H2 数据库将在启动时执行此脚本,因此无需外部依赖即可测试查询。

领域模型

public class User {

    private Long id;
    private String username;
    private String email;
    private Integer age;

    // Getter 和 Setter 方法...
}

这个 POJO 代表数据库中的一行。MyBatis 会自动使用匹配的字段名将列映射到字段,因此本例不需要额外的结果映射。

3. 用于 Dynamic SQL 的表元数据

下面的类以类型安全的方式定义数据库表及其列,允许在构建查询时被 Dynamic SQL DSL 引用。它充当 Java 代码与实际数据库结构之间的桥梁,实现了列名和类型的编译时验证。

public final class UserDynamicSqlSupport {

    public static final User user = new User();

    public static final SqlColumn<Long> id = user.id;
    public static final SqlColumn<String> username = user.username;
    public static final SqlColumn<String> email = user.email;
    public static final SqlColumn<Integer> age = user.age;

    public static final class User extends SqlTable {

        public final SqlColumn<Long> id = column("id", JDBCType.BIGINT);
        public final SqlColumn<String> username = column("username", JDBCType.VARCHAR);
        public final SqlColumn<String> email = column("email", JDBCType.VARCHAR);
        public final SqlColumn<Integer> age = column("age", JDBCType.INTEGER);

        public User() {
            super("users");
        }
    }
}

这个类为 users 表定义了类型安全的元数据,使 MyBatis Dynamic SQL 可以在不使用原始 SQL 字符串的情况下构建查询。DSL 使用这些 Java 对象而非按名称引用列,从而提高了安全性和 IDE 支持。

内部类 User 继承 SqlTable,这将其标记为可用于 from(user) 和连接等子句的数据表。构造函数调用 super("users") 来告知 MyBatis 要在 SQL 语句(如 FROM users)中呈现的确切表名。

每个列都使用 SqlTable 中的 column() 方法定义,该方法注册列名及其 JDBC 类型。这会产生强类型的 SqlColumn<T> 对象,确保比较和条件在编译时使用正确的 Java 类型。

外部类公开了对表及其列的静态引用,以便于静态导入,使得查询读起来很自然,例如:select(id, username).from(user),同时保持完全的类型安全和重构友好。

映射器接口

@Mapper
public interface UserMapper {

    @SelectProvider(type = SqlProviderAdapter.class, method = "select")
    List<User> selectMany(SelectStatementProvider selectStatement);
}

@Mapper 注解告诉 MyBatis 此接口应注册为映射器并在运行时进行代理。MyBatis 会自动生成实现,因此不需要具体的类。

selectMany 方法接受一个 SelectStatementProvider,它封装了完全呈现的 SQL 语句及其参数。MyBatis 执行该语句并将每个结果行映射到 User 对象,将它们作为 List<User> 返回。

@SelectProvider 注解指定 SQL 将由 MyBatis Dynamic SQL 的一部分 SqlProviderAdapter 动态提供。实际的 SQL 是在运行时从使用 DSL 构建的 SelectStatementProvider 生成的,而不是在注解或 XML 中编写 SQL。

4. 构建动态查询

在这里,我们使用流畅的 Dynamic SQL DSL 构建 SQL 语句,而不是编写原始 SQL 字符串。

public static void main(String[] args) throws Exception {

    MyBatisUtil.runSchema();

    try (SqlSession session = MyBatisUtil.getSession()) {

        UserMapper mapper = session.getMapper(UserMapper.class);

        SelectStatementProvider select
                = select(id, username, email, age)
                        .from(user)
                        .where(age, isGreaterThan(18))
                        .and(username, isLike("%tho%"))
                        .orderBy(username)
                        .build()
                        .render(RenderingStrategies.MYBATIS3);

        List<User> users = mapper.selectMany(select);

        users.forEach(u
                -> System.out.println(u.getUsername() + " - " + u.getAge()));
    }
}

此代码使用流畅的 Dynamic SQL DSL 动态构建一个 SELECT 查询,并将其渲染为与 MyBatis 兼容的语句提供者。通过以编程方式添加条件,它能够以类型安全且可维护的方式创建复杂的过滤器。在本例中,查询选择 age 大于 18 岁且 username 包含 "tho" 的用户,然后按用户名字母顺序对结果进行排序。

MyBatis 工具类

public class MyBatisUtil {

    private static SqlSessionFactory factory;

    static {
        try {
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            factory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static SqlSession getSession() {
        return factory.openSession(true);
    }

    public static void runSchema() throws IOException, SQLException {
        try (SqlSession session = getSession()) {
            Connection conn = session.getConnection();
            Statement stmt = conn.createStatement();

            try (InputStream is = Resources.getResourceAsStream("schema.sql")) {
                String sql = new String(is.readAllBytes(), StandardCharsets.UTF_8);
                stmt.execute(sql);
            }
        }
    }
}

此工具类加载 MyBatis 配置,构建 SqlSessionFactory,并提供对数据库会话的访问。它还通过执行 SQL 脚本(schema.sql)手动初始化数据库模式。

5. 使用 Dynamic SQL 进行插入、更新和删除

MyBatis 中的 Dynamic SQL 允许我们使用流畅的 DSL 以编程方式构造 INSERT、UPDATE 和 DELETE 语句。在此,我们演示如何执行这些常见的数据操作。

// INSERT
User newUser = new User();
newUser.setUsername("andrew");
newUser.setEmail("andrew@jcg.com");
newUser.setAge(28);

InsertStatementProvider<User> insert
        = insert(newUser)
                .into(user)
                .map(username).toProperty("username")
                .map(email).toProperty("email")
                .map(age).toProperty("age")
                .build()
                .render(RenderingStrategies.MYBATIS3);

int inserted = mapper.insert(insert);
System.out.println("Rows inserted: " + inserted);

// UPDATE
UpdateStatementProvider update
        = update(user)
                .set(age).equalTo(35)
                .where(username, isEqualTo("thomas"))
                .build()
                .render(RenderingStrategies.MYBATIS3);

int updated = mapper.update(update);
System.out.println("Rows updated: " + updated);

// DELETE
DeleteStatementProvider delete
        = deleteFrom(user)
                .where(age, isLessThan(18))
                .build()
                .render(RenderingStrategies.MYBATIS3);

int deleted = mapper.delete(delete);
System.out.println("Rows deleted: " + deleted);

相同的 DSL 风格也用于写操作。语句以流畅的方式构建、渲染,然后由映射器提供者方法执行。

  • INSERT:创建一个新的 User 对象并填充值。使用 Dynamic SQL DSL,我们将其字段映射到表列并生成 InsertStatementProvider。映射器执行插入操作,返回受影响的行数。
  • UPDATE:DSL 构建一个更新语句,将用户名为 "thomas" 的用户的年龄设置为 35。这确保只修改目标行,映射器执行更新。
  • DELETE:删除语句移除所有年龄小于 18 岁的用户。在 DSL 中使用条件保证了类型安全并避免了字符串拼接。

更新后的映射器接口

为了支持这些操作,映射器接口必须包含用于 INSERT、UPDATE 和 DELETE 的方法,使用 MyBatis Dynamic SQL 提供者。

// INSERT
@InsertProvider(type = SqlProviderAdapter.class, method = "insert")
int insert(InsertStatementProvider<User> insertStatement);

// UPDATE
@UpdateProvider(type = SqlProviderAdapter.class, method = "update")
int update(UpdateStatementProvider updateStatement);

// DELETE
@DeleteProvider(type = SqlProviderAdapter.class, method = "delete")
int delete(DeleteStatementProvider deleteStatement);

映射器中的每个方法处理一个特定的 DML 操作(插入、更新或删除),并接受一个封装了生成的 SQL 及其参数的 InsertStatementProviderUpdateStatementProviderDeleteStatementProvider。这种方法允许所有写操作都在 Java 中以编程方式表达,而无需手动组合 SQL 字符串,同时仍能利用 MyBatis 高效地执行语句和映射结果。

6. 结论

在本文中,我们探讨了如何在 Java 应用程序中使用 MyBatis Dynamic SQL 来创建类型安全、可维护且可编程的 SQL 查询。通过将 SQL 构建与执行分离,MyBatis Dynamic SQL 简化了复杂查询逻辑的处理,降低了错误风险,并提高了代码可读性。这种方法非常适合查询需要动态变化或经常修改的应用程序。

7. 下载源代码

本文讨论了 MyBatis Dynamic SQL 及其在 Java 中的使用方法。

下载

您可以通过此处下载此示例的完整源代码:java mybatis dynamic sql


【注】本文译自:Getting Started with MyBatis Dynamic SQL

简介

Frostmourne(霜之哀伤)是汽车之家经销商技术部监控系统的开源版本,用于帮助监控几乎所有数据库数据(包括Elasticsearch, Prometheus, SkyWalking, MySql 等等)。如果你已经建立起了日志系统, 指标体系,却苦恼于没有一个配套监控系统,也许它能帮到你。

支持数据源:Elasticsearch, HTTP, SkyWalking, Prometheus, InfluxDB, MySQL/TiDb, ClickHouse, SqlServer, PING, IotDB, Telnet

支持告警发送方式:钉钉(机器人)、企业微信(机器人)、飞书机器人、OneMessage机器人、Email、短信、HTTP。(text, markdown)

支持LDAP认证和自动创建用户。

Elasticsearch数据查询、分享和下载

报警消息抑制功能,防止消息轰炸;也有报警升级功能,避免故障相关方长时间得不到通知。

Github地址:https://github.com/AutohomeCorp/frostmourne

环境要求:JAVA8-14、Mysql 8数据库。

教程使用Linux版本:Rocky Linux 8 (关闭SELinux)

版本:部署1.0版本

注意:目前不支持ES8 的加密认证,需要等后期支持JAVA 17

使用ZIP包部署(使用Linux环境)

下载JDK(推荐使用JDK8)

dnf install wget tree
## 安装wget tree

cd /opt
mkdir java1.8
cd /opt/java1.8
wget --execute robots=off -nc -nd -r -l1 -A '*.gz' https://mirrors.tuna.tsinghua.edu.cn/Adoptium/8/jdk/x64/linux/
## 下载OpenJDK8二进制包,源为清华大学源,会自动下载最新版。

tar -zxvf OpenJDK8U-jdk_*.tar.gz 
## 解压

tree -L 2 /opt/java1.8/
/opt/java1.8/
├── jdk8u362-b09
│   ├── ASSEMBLY_EXCEPTION
│   ├── bin
│   ├── include
│   ├── jre
│   ├── lib
│   ├── LICENSE
│   ├── man
│   ├── NOTICE
│   ├── release
│   ├── sample
│   ├── src.zip
│   └── THIRD_PARTY_README
└── OpenJDK8U-jdk_x64_linux_hotspot_8u362b09.tar.gz

7 directories, 7 files


## 查看当前文件树

PS:JDK也可以手动去https://mirrors.tuna.tsinghua.edu.cn/Adoptium/8/jdk/x64/linux/ 目录下进行下载最新版。

部署Mysql(建议为8版本)

dnf install mysql-server

##Rocky 8源默认安装mysql8

systemctl enable --now mysqld

systemctl status mysqld.service 

## 设置开机并启动,查看启动状态
mysql_secure_installation 

## 初始化Mysql


## 如下,都选择Y即可,密码等级选择0,然后配置root密码。


Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

Press y|Y for Yes, any other key for No: y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0
Please set the password for root here.

New password: 

Re-enter new password: 

Estimated strength of the password: 50 
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done! 

创建数据库导入SQL文件

wget https://github.com/AutohomeCorp/frostmourne/blob/master/doc/mysql-schema/frostmourne.sql

## 可能需要挂国际代理,或浏览器下载,使用SCP上传即可.

mysql -uroot -p < frostmourne.sql

#输入密码即可导入创建数据库。

mysql -uroot -p
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| frostmourne        |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)


#查看数据库是否正常创建, quit命令退出。

下载frostmourne

cd /opt
mkdir frostmourne
cd frostmourne

wget https://github.com/AutohomeCorp/frostmourne/releases/download/1.0-RELEASE/frostmourne-monitor-1.0-RELEASE.zip

## 可能需要挂国际代理,或浏览器下载,使用SCP上传到opt目录即可.
## Github 下载地址 https://github.com/AutohomeCorp/frostmourne/releases

dnf install unzip 

unzip frostmourne-monitor*.zip 

tree 
.
├── application.properties
├── env
├── frostmourne-monitor-1.0-RELEASE.jar
├── frostmourne-monitor-1.0-RELEASE.zip
└── scripts
    ├── shutdown.sh
    └── startup.sh

1 directory, 6 files
## 文件树如上。

修改ENV 环境文件,配置java路径

vim /opt/frostmourne/env 


JAVA_HOME=/opt/java1.8/jdk8u362-b09
##配置文件上面操作解压的JDK路径即可

JAVA_OPTS="-Xms1200m -Xmx2000m -Xss1024K -XX:PermSize=128m -XX:MaxPermSize=256m -Dfile.encoding=utf-8"
##JAVA参数,可适当改大内存等参数。

修改启动app配置(也可以配置邮件发送服务器和LDAP)

vim /opt/frostmourne/application.properties 

alarmlog_reserve_days=30
#配置监控日志保留天数

datasource_frostmourne_url=jdbc:mysql://127.0.0.1:3306/frostmourne?characterEncoding=utf8
#配置mysql数据库IP地址、数据库名字、编码。

datasource_frostmourne_username=root
datasource_frostmourne_password=Wow@123
#配置mysql数据库账号密码


frostmourne_monitor_address=192.168.124.100

#配置为本机IP地址即可

frostmourne_message_title=Wow
#配置实例名字

启动测试

/opt/frostmourne/scripts/startup.sh 

./startup.sh: line 7: dos2unix: command not found
LOG_DIR: /opt/frostmourne/scripts/../logs
PID_FOLDER: /opt/frostmourne/scripts/../pid
Sun Mar 26 16:42:27 CST 2023 ==== Starting ==== 
Application is running as root (UID 0). This is considered insecure.
Started [5477]
Waiting for server startup..
Sun Mar 26 16:42:37 CST 2023 Server started in 10 seconds

ss -an | grep 10054
tcp   LISTEN 0      100                    *:10054           *:*                  

## 查看10054端口监听即可判断已正常启动。

/opt/frostmourne/scripts/shutdown.sh 

## 停止服务

Web访问: http://192.168.124.100:10054 即可测试,注意替换IP

admin 管理账号默认的账号密码是123456

注意:开启了LDAP认证,admin账号仍然可以用初始密码登录。

配置为Systemd自定义服务并设置开机启动

vim /usr/lib/systemd/system/frostmourne.service 


[Unit]
Description=frost
After=network.target mysqld.service

[Service]
User=root
Type=forking
ExecStart=/opt/frostmourne/scripts/startup.sh
ExecStop=/opt/frostmourne/scripts/shutdown.sh
RemainAfterExit=yes



[Install]
WantedBy=multi-user.target

##写入到文件保存。

systemctl daemon-reload
## 加载自定义服务
systemctl start frostmourne.service 
systemctl status frostmourne.service 

## 尝试启动,并查看状态。
systemctl enable frostmourne.service 

## 设置开启及并启动

注意:需要等待网络服务和mysql启动之后,才会启动。

技术架构

frostmourne-vue:前端项目,使用vue-element-template实现,打包时会把生成的资源文件构建到frostmourne-monitor

  • frostmourne-monitor:监控运行主体服务
    Frostmourne (霜之哀伤)日志监控系统部署安装

主要技术栈

  • springboot 2.x
  • element ui
  • vue-admin-template
  • xxl-job
  • mybatis
  • freemarker
  • elasticsearch
  • InfluxDB
  • jjwt
  • nashorn

    参考

官方文档:https://github.com/AutohomeCorp/frostmourne

注意:推荐配合https://songxwn.com/elk/ ES部署使用。