SQL Mappers
===========
After configuring your datasource information, you can use SQL Mappers to access the database. The first step is to tell Juice where to find your SQL statements.
mappers Tag
-----------
``mappers`` is the parent tag of ``mapper``. It is a collection tag used to hold multiple ``mapper`` definitions.
.. code-block:: xml
root:qwe123@tcp(localhost:3306)/databasemysql
mapper Tag
----------
The ``mapper`` tag is a container for SQL statements.
Basic usage examples:
.. code-block:: xml
**Attribute descriptions**
- ``namespace``: specifies the mapper namespace. It must be unique.
- ``resource``: references another mapper file. If the referenced file does not reference another file, its ``namespace`` attribute is required.
- ``url``: references a mapper file through a URL. ``http`` and ``file`` are currently supported. If the referenced file does not reference another file, its ``namespace`` attribute is required.
By referencing mapper files, you can distribute SQL statements across multiple files and keep the structure clearer.
.. attention::
``namespace``, ``resource``, and ``url`` are mutually exclusive. A single ``mapper`` tag can use only one of them.
SQL Statement Tags
------------------
Juice supports four kinds of SQL statement tags: ``select``, ``insert``, ``update``, and ``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}
Each of the ``select``, ``insert``, ``update``, and ``delete`` tags has an ``id`` attribute. It identifies the SQL statement and must be unique within the same mapper.
**Common question**
*Question: Can I put a delete statement inside a select tag?*
*Answer: Technically yes, but it is strongly discouraged. Each tag should preserve its own semantics.*
Parameter Handling
------------------
Using Parameters in SQL Statements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can use parameters in SQL statements and pass values from the outside. You only need to reference them with the proper syntax.
**Parameter definition example**
.. code-block:: xml
In the example above, ``#{name}`` references a parameter whose value is passed in when the SQL is executed.
**Parameter syntax comparison**
- ``#{name}``: a prepared parameter that is replaced with a placeholder such as ``?``. It helps prevent SQL injection and is recommended.
- ``${name}``: direct string interpolation. It is not replaced with a placeholder and carries SQL injection risk, so use it carefully.
.. code-block:: xml
.. warning::
When using ``${}``, make sure the parameter value is safe because Juice will not protect it from SQL injection automatically.
Parameter Passing Methods
~~~~~~~~~~~~~~~~~~~~~~~~~
**1. Passing a map**
.. code-block:: go
userMap := map[string]any{
"name": "eatmoreapple",
}
engine.Object("main.CountUserByName").QueryContext(context.TODO(), userMap)
**2. Passing a struct**
.. code-block:: go
type User struct {
Name string `param:"name"`
Age int `param:"age"`
}
user := User{
Name: "eatmoreapple",
Age: 25,
}
engine.Object("main.CountUserByName").QueryContext(context.TODO(), user)
**3. Passing an array or slice**
Since map and struct values can both be converted into key-value form, a slice or array can be accessed by index:
.. code-block:: xml
.. code-block:: go
engine.Object("main.CountUserByName").QueryContext(context.TODO(), []any{"eatmoreapple", 25})
**4. Passing a single parameter**
If the parameter is not a struct, map, slice, or array, Juice wraps it into a map where the key is ``param`` and the value is the argument you passed.
.. code-block:: xml
.. code-block:: go
engine.Object("main.CountUserByName").QueryContext(context.TODO(), "eatmoreapple")
**Custom parameter names**
You can customize the name of a single parameter with the ``paramName`` attribute:
.. code-block:: xml
You can also set it globally through the ``JUICE_PARAM_NAME`` environment variable.
**Convenience type**
``juice.H`` is an alias for ``map[string]any`` and is provided to make parameter passing more convenient.
.. code-block:: go
params := juice.H{
"name": "eatmoreapple",
"age": 25,
}
engine.Object("main.CountUserByName").QueryContext(context.TODO(), params)
.. attention::
If the parameter is a map, its key type must be ``string``.
Advanced Features
-----------------
Statement Attributes
~~~~~~~~~~~~~~~~~~~~
SQL statement tags support a range of attributes to control execution behavior:
.. code-block:: xml
insert into user (name, age) values (#{name}, #{age})
**Common attributes**
- ``timeout``: SQL execution timeout in milliseconds
- ``debug``: whether to enable debug mode
- ``paramName``: custom name for a single parameter
- ``useGeneratedKeys``: whether to use auto-generated keys
- ``keyProperty``: the property that receives the generated key
Best Practices
--------------
1. **Naming conventions**
- Use meaningful namespaces.
- SQL statement IDs should clearly describe what they do.
- Parameter names should be descriptive.
2. **File organization**
- Split mapper files by feature or module.
- Avoid letting a single mapper file grow too large.
- Use a clear and consistent directory structure.
3. **Security**
- Prefer ``#{}`` over ``${}``.
- Avoid direct SQL string concatenation.
- Validate user input before sending it to SQL.
4. **Performance optimization**
- Use indexes appropriately.
- Avoid ``SELECT *`` where possible.
- Choose appropriate data types.
Example: Complete Mapper Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. 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())
With the configuration and usage patterns above, you can use Juice SQL Mappers to build an efficient and secure data access layer.