一、设置日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
在Mybatis当中我们可以借助⽇志, 查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果
二、XML 映射 SQL
在 MyBatis 中,你可以选择两种主要方式来编写 SQL 映射:使用 XML 映射文件或者使用 Java 注解。这两种方法各有优缺点,适用于不同的场景。以下是这两种方法的详细比较:
XML 映射是 MyBatis 最传统也是最灵活的配置方式。它将 SQL 语句和 Java 接口分离,使得 SQL 语句更易于管理和优化。
优点:
- 集中管理:XML 文件可以集中管理 SQL 语句,易于维护和查阅。
- 支持动态 SQL:XML 方式支持更为复杂的动态 SQL 语句,如条件语句(
<if>
)、循环语句(<foreach>
)等,这些可以使 SQL 根据不同条件灵活生成。 - 易于版本控制:作为文件的一部分,XML 映射可以轻松地纳入版本控制系统中。
缺点:
- 间接性:代码与 SQL 分离,可能导致开发中来回查找对应 SQL 语句,降低开发效率。
- 冗余:可能需要为每一个 SQL 语句编写大量的 XML 配置代码
三、注解 映射 SQL
注解方式是 MyBatis 在较新版本中引入的,它将 SQL 语句直接写在 Mapper 接口的方法上,减少了配置的复杂性。
优点:
- 直接性:SQL 语句与 Java 方法紧密绑定,易于阅读和修改。
- 简洁:减少了 XML 的冗余配置,使得项目结构更为简洁。
缺点:
- 有限的动态 SQL 支持:虽然注解支持基本的动态 SQL,如
@Select
、@Insert
等,但对于复杂的动态 SQL,如条件判断、循环等支持不如 XML 方式。 - 可扩展性有限:对于非常复杂的 SQL,注解方式可能显得力不从心。
public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User selectUserById(int id); }
四、参数传递
1)一个参数
如果 SQL 语句只需要一个参数,你可以直接在 mapper 接口的方法中传递这个参数。MyBatis 会自动将它识别为 SQL 语句中的参数。
public interface UserMapper {
User selectUserById(int id);
}
Mapper XML
<select id="selectUserById" parameterType="int" resultType="com.example.mybatisdemo.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
在这个例子中,#{id}
是一个占位符,用于在执行时替换成方法 selectUserById(int id)
中的参数。
2)多个参数
当方法中有多个参数时,MyBatis 提供了几种处理方式:
- @Param 注解:最常用的方式,可以给参数命名,然后在 SQL 语句中引用这些名称、Mapper 接口
public interface UserMapper { User findUserByNameAndEmail(@Param("name") String name, @Param("email") String email); }
Mapper XML
<select id="findUserByNameAndEmail" resultType="com.example.mybatisdemo.model.User"> SELECT * FROM users WHERE name = #{name} AND email = #{email} </select>
使用
@Param
注解可以明确指定 SQL 语句中参数的名称,这样即使参数的顺序改变,也不会影响 SQL 语句的执行
3) 返回主键
在 MyBatis 中,将操作结果的自动生成的主键(如数据库自增主键)返回到应用程序中是一个常见需求。这通常在执行插入(INSERT)操作时使用。MyBatis 提供了几种方式来处理自动生成的主键,并将其返回。
a) 使用 XML 映射
当你使用 XML 映射文件定义 SQL 语句时,可以通过 useGeneratedKeys
和 keyProperty
属性来指定如何返回自动生成的主键。
示例:
假设你有一个名为 users
的表,其中 id
字段是自增主键。
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, email) VALUES (#{name}, #{email})
</insert>
在这个例子中:
useGeneratedKeys="true"
告诉 MyBatis 使用 JDBC 的getGeneratedKeys()
方法来检索由数据库自动生成的键。keyProperty="id"
指定 MyBatis 应将获取的键值设置到哪个属性上。在本例中,它将设置到User
对象的id
属性上。
b) 使用注解
当你使用注解来配置 MyBatis 映射时,可以使用 @Options
注解来指定如何处理自动生成的键。
Mapper 接口方法:
public interface UserMapper {
@Insert("INSERT INTO users (name, email) VALUES (#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
}
这里的 @Options
注解具有与 XML 相同的属性:
useGeneratedKeys = true
这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内 部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字 段),默认值:falsekeyProperty = "id"
指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)
五、SELECT 查找
在 MyBatis 中,字段映射的默认行为是按照属性名和数据库字段名进行匹配。如果 Java 对象的属性名与数据库表的字段名不一致,你需要显式地指定这种映射关系,以确保正确的数据赋值
1. 起别名 (Alias)
在 SQL 查询中直接为字段起别名,使其与 Java 对象的属性名匹配。这是一种非常简单且直接的方式,适用于简单的场景。
示例:
假设数据库字段名为 emp_name
,而 Java 对象的属性名为 name
,可以在 SQL 语句中直接为 emp_name
起别名为 name
:
SELECT emp_id as id, emp_name as name, emp_email as email FROM employees
使用这种方法,MyBatis 在映射结果到 Java 对象时会自动将 name
列的值赋给 name
属性。
2. 结果映射 (Result Mapping)
使用 MyBatis 的 resultMap
元素来定义字段映射关系。这种方式提供了高度的灵活性和控制力,适用于复杂的数据结构和关系。
示例:
在 MyBatis 映射文件中使用 resultMap
来定义字段与属性的映射关系:
<mapper namespace="com.example.mapper.EmployeeMapper">
<resultMap id="EmployeeMap" type="Employee">
<result property="id" column="emp_id"/>
<result property="name" column="emp_name"/>
<result property="email" column="emp_email"/>
</resultMap>
<select id="selectEmployee" resultMap="EmployeeMap">
SELECT emp_id, emp_name, emp_email FROM employees
</select>
</mapper>
这个 resultMap
显式地告诉 MyBatis 如何将数据库字段映射到 Java 对象的属性。
3. 开启驼峰命名 (Camel Case Mapping)
通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词), ⽽ Java 属性⼀般遵循驼峰命名法约定. 为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true。
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰⾃动转换
开启这个配置后,MyBatis 会自动将诸如 emp_name
的字段名映射为 empName
的 Java 属性。这样,你不需要在每个 SQL 语句中为字段起别名,也不需要在每个映射中定义 resultMap
。
六、#{} 和 ${}
在 MyBatis 中,#{}
和 ${}
是两种基本的参数占位符,用于在 SQL 语句中插入参数值,但它们的工作方式和用途有明显的区别:
1. #{}
(Prepared Statement 占位符)
#{}
是用来创建预处理语句 (Prepared Statement) 的参数占位符。当你使用 #{}
时,MyBatis 会使用 JDBC 的 PreparedStatement
功能,这意味着参数值在传递给数据库之前会进行适当的转义,从而防止 SQL 注入攻击。这是处理用户输入或不可信数据时推荐的做法。
示例:
SELECT * FROM users WHERE id = #{id}
在这里,#{id}
会被替换为一个参数标记,如 ?
,并且 id
的值会在执行时被安全地绑定到这个问号上。如果 id
是 5
,那么实际执行的 SQL 会是:
SELECT * FROM users WHERE id = ?
并且 5
作为参数传递,这样可以防止 SQL 注入。
2. ${}
(文本替换占位符)
${}
用于直接将参数值插入 SQL 语句中。这种方式可以用于动态更改表名、列名或者是 SQL 语句的其它部分,而不仅仅是值。使用 ${}
时,必须非常小心,因为它不会对参数值进行任何处理或转义,从而有可能导致 SQL 注入风险。
SELECT * FROM ${tableName} WHERE id = ${id}
如果 tableName
是 users
,而 id
是 5
,那么生成的 SQL 将直接是:
SELECT * FROM users WHERE id = 5
这种方式使得你可以灵活地构建 SQL 语句,但如果 tableName
或 id
来自于用户输入或其他不可信的来源,则可能会带来安全风险。
选择使用哪一个
- 当你需要插入安全的、转义后的数据值时,使用
#{}
。 - 当你需要在 SQL 语句中动态插入表名、列名或其他不会改变 SQL 逻辑的元素时,可以使用
${}
,但必须非常小心确保这些值是安全的,避免 SQL 注入。
通常,除非绝对必要,建议尽量避免使用 ${}
来避免潜在的安全问题。使用 #{}
可以利用 PreparedStatement
的优势,有效防止 SQL 注入,是更安全的选择。
${}的必要性
1.排序
@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +
"from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
使⽤ ${sort} 可以实现排序查询, ⽽使⽤ #{sort} 就不能实现排序查询了. 注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 '' 的, 所以此时的 ${sort} 也不加引号
当使⽤ #{sort} 查询时, asc 前后⾃动给加了引号, 导致 sql 错误 #{} 会根据参数类型判断是否拼接引号 '' 如果参数类型为String, 就会加上 引号
除此之外, 还有表名作为参数时, 也只能使⽤ ${}
2.like 查询
在 MyBatis 中使用 #{}
对于大多数情况都是正常工作的,但在使用 LIKE
语句时,可能会遇到问题。这是因为 #{}
在转换过程中会将输入视为字面值,并且会自动在值周围添加单引号。这在 LIKE
查询中通常不是预期的行为,因为通配符(如 %
)需要与字段值直接相连。
当你使用 LIKE
并希望包含通配符时,如下
SELECT * FROM users WHERE name LIKE '%#{name}%'
MyBatis 会处理这个语句并将其转换为:
SELECT * FROM users WHERE name LIKE '%\'someName\'%'
这里的单引号是自动添加的,导致 SQL 语句错误,因为 SQL 不期望在 %
和 someName
之间有额外的单引号。
为了正确使用 LIKE
,你应该让 MyBatis 在不添加额外单引号的情况下处理变量。有两种主要方法可以实现:
使用 CONCAT
函数
你可以使用 SQL 的 CONCAT
函数来确保通配符和变量被正确组合,而不是将它们作为字符串字面值直接插入。这在大多数数据库中都有效。
SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')
安全性考虑
尽管 ${}
在这种情况下是必要的,但它引入了潜在的 SQL 注入风险。为了安全地使用 ${}
,你需要确保:
-
输入验证:对传入的列名和排序方向进行严格验证,确保它们是预定义的有效值。例如,你可以在应用层或服务层检查
orderByColumn
是否确实是数据库中的列名,orderDir
是否仅为 "ASC" 或 "DESC"。 -
使用白名单:仅允许特定的、安全的字段名和排序方向,可以通过在服务层设置一个白名单来实现。
通过这种方式,尽管使用了 ${}
,但通过严格的输入验证和白名单策略,可以最大程度地减少 SQL 注入的风险,使得动态排序既灵活又安全。这种方法适用于需要高度动态性的 SQL 操作,如动态排序、动态查询字段等场景。