前言:本文基于若依前后端分离版本(Spring Boot 3.3.0 + Vue 3 + Activiti 8.1.0)进行改造,相关教程可以在网上找到。在撰写此博客期间,笔者刚刚开始接触 Java Web,本系列下的文章内容包含大量“个人初期”视角,注意鉴别。

概述

暂不考虑MySQL的一些底层实现,仅从基本的增删查改功能来看,我们似乎要使用大量的sql语句去实现这些功能,使用JDBC(Java Database connect)?这个实在是太麻烦了,要手动建立连接,写sql语句传值,小项目凑活一下应该问题不大,但是复杂项目就比较麻烦了,笔者没有试过跑JDBC,在这里贴一段GPT给出的插入数据的代码,当属性变多的时候,这代码就显得有些麻烦了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public void insertUser(String username, String email) {
String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
try (Connection connection = DatabaseUtil.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {

preparedStatement.setString(1, username);
preparedStatement.setString(2, email);
preparedStatement.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
}
}

所以有没有一种方法,让我们把对数据库的操作变得更简单?函数形参直接传入实体类,函数自己去匹配字段插入数据库。这似乎就是MyBatis的作用:通过XML描述符把对象与存储过程用SQL语句关联起来。听着有些抽象,我第一次看见也觉得抽象。

举个例子

XML文件

现阶段我倾向于将MySQL中的每一张表等同于domain层的一个类,比如档案管理中:档案类&&档案表,进行增删查改时的对象应该是:一个对象&&一行记录,先不管怎么写入数据库的事,类和表之间应该存在一些预定义的关系,比如:private Long ID 可以对应 `id` bigint(20) NOT NULL COMMENT '档案ID'

基于这个前提,我们就需要准备实体类与对应表的详细映射关系,这些关系就保存在XML文件中。 比如(挑了个稍微短一些的):

type="FormTemplates"是我们定义的实体类,property是对应的类成员变量,column是表中列的名字,这个对应关系叫他:FormTemplatesResult

1
2
3
4
5
6
<resultMap type="FormTemplates" id="FormTemplatesResult">
<result property="id" column="id" />
<result property="formKey" column="form_key" />
<result property="templatePath" column="template_path" />
<result property="createdTime" column="created_time" />
</resultMap>

一般情况下,查找操作似乎比较频繁,比如打开MySQL Workbench 中任意一张表的时候,默认执行了SELECT * FROM tb_form_templates;,那么一个自然的想法,我们在xml中也可以设定一个可以复用的组件,专门保存上面这一句sql的结果?一些条件查询的语句可以在此基础上添加。同时给这个复用片段加id,下次可调用它。

1
2
3
<sql id="selectFormTemplatesVo">
select id, form_key, template_path, created_time from tb_form_templates
</sql>

我们现在有了:类成员与表属性之间的映射关系、可复用的片段(从表中查全部数据)。现在尝试使用上面两个条件,实现:根据ID查找数据:selectFormTemplatesById。

我们可以给这个方法起个名字:selectFormTemplatesById,我们还需要指定要查询的条件的类型private Long IDLong

我们提前准备了一段可复用的sql片段,它已经把我们要查找的全部数据拿了出来,我们在它的基础上根据id进行筛选,使用它的方式就是把他include进来,后一段继续增加条件,#{id}是一个占位符,外部调用这个方法时,形参列表需要有一个叫id的变量。

最终的查找结果,将会通过 最开始的代码块中我们指定的映射关系绑定到(所以是resultMapFormTemplates对象(也可以是对象列表)。

1
2
3
4
<select id="selectFormTemplatesById" parameterType="Long" resultMap="FormTemplatesResult">
<include refid="selectFormTemplatesVo"/>
where id = #{id}
</select>

到这为止,我们基本指定了xml文件中的核心部件:

  • 对象属性与表属性之间的对应关系
  • 整体查找且可复用的sql片段
  • 利用可复用片段 + ID查找对应数据的方法

但是,我们如何让Java代码去访问这些方法呢?我们应该在最开就给他定义好,跟外面的某个Java接口绑定在一起,让:接口中的方法名 == xml中方法的id,具体来说就是接口中有selectFormTemplatesById,xml文件中就得实现selectFormTemplatesById方法。

那么到底怎么对应关系呢?xml文件最开始就要声明:

1
<mapper namespace="com.iams.activiti8.mapper.FormTemplatesMapper">

就是我们外部定义的接口,外部的其他服务如果要操作数据库,也要通过这个接口。
FormTemplatesMapper

外部接口:mapper

感觉得多啰嗦两句(现阶段的理解):domain层与数据表对应,mapper层使用domain中的类,通过XML文件,去对应数据表

FormTemplatesMapper.java 使用 domain/FormTemplates.java && FormTemplatesMapper.xml 增删查改 tb_form_templates

domain/FormTemplates.java,作为数据传输整体贯穿其中(暂不考虑DTO)

现在来看接口,接口与实现对应,只不过这个实现是在xml中实现的。所以只要添加新的方法,对应也要在xml文件中添加新实现。

1
2
3
4
5
6
7
8
9
public interface FormTemplatesMapper 
{
/**
* 查询表单模板
* @param id 表单模板主键
* @return 表单模板
*/
public FormTemplates selectFormTemplatesById(Long id);
}

看这里的Long id,是不是解释了上面按照id查找数据的#{id}占位符。

具体来怎么用?(我猜的,毕竟没怎么深入了解bean:)mapper中的每一个接口似乎被看作一个bean,通过类型自动注入,调用的方法嘛,非常的朴实无华,注意返回值就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Autowired
private FormTemplatesMapper formTemplatesMapper;

/**
* 查询表单模板
*
* @param id 表单模板主键
* @return 表单模板
*/
@Override
public FormTemplates selectFormTemplatesById(Long id)
{
return formTemplatesMapper.selectFormTemplatesById(id);
}

MySQL语法

上面只是根据ID进行查找,那么如果进行一些其他的sql操作呢?所以这一个part主要记录一下sql语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
<insert id="insertFormTemplates" parameterType="FormTemplates" useGeneratedKeys="true" keyProperty="id">
insert into tb_form_templates
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="formKey != null and formKey != ''">form_key,</if>
<if test="templatePath != null and templatePath != ''">template_path,</if>
<if test="createdTime != null">created_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="formKey != null and formKey != ''">#{formKey},</if>
<if test="templatePath != null and templatePath != ''">#{templatePath},</if>
<if test="createdTime != null">#{createdTime},</if>
</trim>
</insert>
  • useGeneratedKeys="true":表示使用自动生成的主键。
  • keyProperty="id":指定将自动生成的主键值设置到 FormTemplates 对象的 id 属性中。
  • insert into tb_form_templates指定要插入的数据表
  • <trim prefix="(" suffix=")" suffixOverrides=",">构建列名
  • <trim prefix="values (" suffix=")" suffixOverrides=",">构建列名对应的值
  • if test 语句用于条件判断,仅在属性不为空时才将对应的列名添加到插入语句中。

也就是通过上述元素,构建了一个sql语句,

1
2
INSERT INTO tb_form_templates (form_key, template_path, created_time) 
VALUES (#{formKey}, #{templatePath}, #{createdTime});

1
2
3
<delete id="deleteFormTemplatesById" parameterType="Long">
delete from tb_form_templates where id = #{id}
</delete>

1
2
3
4
5
6
7
8
<select id="selectFormTemplatesList" parameterType="FormTemplates" resultMap="FormTemplatesResult">
<include refid="selectFormTemplatesVo"/>
<where>
<if test="formKey != null and formKey != ''"> and form_key = #{formKey}</if>
<if test="templatePath != null and templatePath != ''"> and template_path = #{templatePath}</if>
<if test="createdTime != null "> and created_time = #{createdTime}</if>
</where>
</select>

这里的目的是查找一个数据列表,似乎我们直接返回<include refid="selectFormTemplatesVo"/>的结果就可以,但是这里又添加了一些其他的if test的语句,而且注意这个方法是需要参数传入的:parameterType="FormTemplates"

  • 如果传入的对象属性全是null,那么返回的就是全部数据。
  • 如果存在某个值不为null,就会根据where构建条件查询,返回的也就是筛选过后的数据。

这样也就不需要按照每个属性单独写查询条件了。下面的修改操作也是如此。

1
2
3
4
5
6
7
8
9
10
<update id="updateFormTemplates" parameterType="FormTemplates">
update tb_form_templates
<trim prefix="SET" suffixOverrides=",">
<if test="formKey != null and formKey != ''">form_key = #{formKey},</if>
<if test="templatePath != null and templatePath != ''">template_path = #{templatePath},</if>
<if test="createdTime != null">created_time = #{createdTime},</if>
</trim>
where id = #{id}
</update>