SQL Mappers
================
当我们将数据源信息配置好之后,就可以使用 SQL Mapper 来访问数据库了。首先,我们需要告诉 Juice 去哪里找到我们的 SQL 语句。
mappers 标签
----------------
``mappers`` 是 ``mapper`` 标签的父标签,它是一个集合标签,用来存放 ``mapper`` 标签。
.. code-block:: text
root:qwe123@tcp(localhost:3306)/database
mysql
mapper 标签
----------------
``mapper`` 标签是用来存储 SQL 语句的集合标签。
基本用法示例:
.. code-block:: xml
**属性说明:**
- ``namespace``: 用来指定 mapper 的命名空间,这个命名空间用来区分不同的 mapper,它的值必须是唯一的。
- ``resource``: 用来引用另外一个 mapper 文件。注意:引用的 mapper 文件如果没有再次引用别的文件,那么它的 ``namespace`` 属性是必须的。
- ``url``: 通过 URL 来引用 mapper 文件。目前支持 ``http`` 和 ``file`` 协议。如果引用的 mapper 文件没有再次引用别的文件,那么它的 ``namespace`` 属性是必须的。
通过引用 mapper 文件,我们可以将 SQL 语句分散到不同的文件中,这样可以使得我们的结构更加清晰。
.. attention::
``namespace``、``resource``、``url`` 三个属性是互斥的,一个 ``mapper`` 标签只能使用其中的一个。
SQL 语句标签
-----------------------------------
Juice 支持四种类型的 SQL 语句标签:``select``、``insert``、``update``、``delete``。
.. code-block:: xml
insert into user (name, age) values (#{name}, #{age})
update user set age = #{age} where name = #{name}
delete from user where name = #{name}
上述的 ``select``、``insert``、``update``、``delete`` 标签都有一个 ``id`` 属性,这个属性是用来标识 SQL 语句的,它的值在同一个 mapper 中必须是唯一的。
**常见问题:**
*问:可不可以在 select 标签里面写 delete 语句呢?*
*答:技术上可以,但强烈不推荐,每个标签都应该有自己的语义。*
参数处理
----------------
在 SQL 语句中使用参数
~~~~~~~~~~~~~~~~~~~~~~~~~~
我们可以在 SQL 语句中使用参数,这些参数可以通过外部传递进来,我们只需要通过特定的语法来引用这些参数即可。
**参数定义示例:**
.. code-block:: xml
上述的 SQL 语句中,我们使用了 ``#{name}`` 来引用参数,这个参数的值将会在执行 SQL 语句的时候传递进来。
**参数语法对比:**
- ``#{name}``: 预编译参数,会被替换成占位符(``?``),可以防止 SQL 注入,**推荐使用**
- ``${name}``: 直接字符串替换,不会被替换成占位符,**有 SQL 注入风险,谨慎使用**
.. code-block:: xml
.. warning::
使用 ``${}`` 语法时,必须确保参数值是安全的,因为它不会进行 SQL 注入防护。
参数传递方式
~~~~~~~~~~~~~~~~~~~~
**1. Map 参数传递**
.. code-block:: go
userMap := map[string]interface{}{
"name": "eatmoreapple",
"age": 25,
}
engine.Object("main.CountUserByName").QueryContext(context.TODO(), userMap)
**2. Struct 参数传递**
.. code-block:: go
type User struct {
Name string `param:"name"` // 使用 param tag 自定义参数名
Age int `param:"age"`
}
user := User{
Name: "eatmoreapple",
Age: 25,
}
engine.Object("main.CountUserByName").QueryContext(context.TODO(), user)
**3. 数组/切片参数传递**
既然 map 和 struct 都可以转换成 key-value 结构,那么如果我们传递一个 slice 或者 array 的参数,可以通过索引访问的形式来访问传递的参数:
.. code-block:: xml
.. code-block:: go
engine.Object("main.CountUserByName").QueryContext(context.TODO(), []interface{}{"eatmoreapple", 25})
**4. 单一参数传递**
如果我们传递一个非 struct、非 map、非 slice/array 的参数,那么 Juice 会将这个参数包装成一个 map,这个 map 的 key 是 ``param``,value 是我们传递的参数。
.. code-block:: xml
.. code-block:: go
engine.Object("main.CountUserByName").QueryContext(context.TODO(), "eatmoreapple")
**自定义参数名:**
可以通过 ``paramName`` 属性来自定义单一参数的名称:
.. code-block:: xml
或者通过环境变量 ``JUICE_PARAM_NAME`` 来全局设置。
**便捷类型:**
``juice.H`` 是一个 ``map[string]interface{}`` 的别名,用来方便开发者传递参数。
.. code-block:: go
params := juice.H{
"name": "eatmoreapple",
"age": 25,
}
engine.Object("main.GetUser").QueryContext(context.TODO(), params)
.. attention::
当参数是 map 类型时,这个 map 的 key 必须是 string 类型。
高级功能
--------
语句属性
~~~~~~~~
SQL 语句标签支持多种属性来控制执行行为:
.. code-block:: xml
insert into user (name, email) values (#{name}, #{email})
**常用属性说明:**
- ``timeout``: 设置 SQL 执行超时时间(毫秒)
- ``debug``: 是否启用调试模式
- ``paramName``: 自定义单一参数的名称
- ``useGeneratedKeys``: 是否使用自动生成的主键
- ``keyProperty``: 指定接收自动生成主键的属性名
最佳实践
--------
1. **命名规范**
- 使用有意义的命名空间
- SQL 语句 ID 应该清晰表达其功能
- 参数名应该具有描述性
2. **文件组织**
- 按功能模块划分 mapper 文件
- 每个 mapper 文件不要过大
- 使用合理的目录结构
3. **安全性**
- 优先使用 ``#{}`` 参数语法
- 避免直接拼接 SQL 字符串
- 对输入参数进行验证
4. **性能优化**
- 合理使用索引
- 避免 SELECT *
- 使用合适的数据类型
示例:完整的 Mapper 配置
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: text
insert into users (name, email, age, created_at)
values (#{name}, #{email}, #{age}, now())
update users
set name = #{name},
email = #{email},
age = #{age},
updated_at = now()
where id = #{id}
delete from users where id = #{id}
insert into users (name, email, age, created_at) values
(#{user.name}, #{user.email}, #{user.age}, now())
通过以上的配置和使用方式,您可以充分利用 Juice 的 SQL Mapper 功能来构建高效、安全的数据访问层。