一:说明
(1)如何传参
在接口的方法中传递参数时可以直接传参,但是当字段过多时,就不那么合适了。因此我们直接传递对象,然后赋值时直接使用对象中的属性
例如后面查询例子
(2)#{}
和${}
在MyBatis中,#{}
和${}
是两种不同的参数注入方式,它们在SQL语句中的使用有一些重要的区别
#{}
(预编译): 使用#{}时,MyBatis会自动将传入的值进行预编译处理,将参数值安全地嵌入SQL语句中。这样可以有效防止SQL注入攻击,因为用户输入的数据不会直接拼接到SQL语句中${}
(直接替换):直接将参数的值拼接到SQL语句中,不进行预编译处理。这种方式更像是简单的字符串替换,可以用于处理一些特殊需求,但是也容易引发SQL注入问题,因为用户输入的数据直接嵌入到SQL语句中
尽管推荐使用#{}
来防止SQL注入,但有一些情况下可能需要使用${}
。主要的情况包括
- 动态表名或列名:如果你需要动态指定表名或列名,而且这个表名或列名不能通过预编译参数来实现,那么可能需要使用
${}
。在这种情况下,确保动态生成的表名或列名是可信的,以避免SQL注入风险 - 排序字段的动态指定:有时候你可能希望动态指定排序的字段,这时候可能需要使用
${}
(3)SQL注入问题
SQL注入:是一种常见的网络安全问题,它发生在应用程序未正确验证用户输入数据的情况下。攻击者可以通过巧妙构造的输入,向数据库发送恶意的SQL查询,从而绕过应用程序的身份验证和访问控制,甚至执行潜在的破坏性操作
我们通过一个简单的案例来说明SQL注入问题。考虑一个具有登录功能的网站,用于验证用户提供的用户名和密码。假设这个网站使用以下SQL查询来检查用户凭据
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码';
现在,假设用户输入了以下作为用户名的内容:
' OR '1'='1' --
当这个输入传递到SQL查询中时,查询变成了:
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = '输入的密码';
出现这个问题的原因在于
OR '1'='1'
总是为真,因为1等于1。这就是一个恶意用户想要达到的目标,绕过身份验证--
是SQL中的注释符号,它表示后续的所有内容都是注释,不会被执行。这对于绕过密码检查非常重要
二:增、改、删
(1)增
- 以在
userinfo
表中添加一条记录为例
控制层
package com.example.mybatisstudy.controller;
import com.example.mybatisstudy.entity.UserInfo;
import com.example.mybatisstudy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 当返回实体对象时,Spring会自动转换为json
@RequestMapping("/getuserbyid")
public UserInfo getUserById(Integer id) {
if (id == null) return null;
return userService.getUserById(id);
}
@ RequestMapping("/adduser")
public String addUser(@RequestBody UserInfo userInfo) {
userInfo.setCreatetime(LocalDateTime.now());
userInfo.setUpdatetime(LocalDateTime.now());
int res = userService.addUser(userInfo);
if (res == 1) {
return "插入成功";
}
return "插入失败";
}
}
服务层
package com.example.mybatisstudy.service;
import com.example.mybatisstudy.entity.UserInfo;
import com.example.mybatisstudy.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// Spring会找这个interface的实现类
// 如果有多个实现类就会报错(可以使用resource解决)
@Autowired
private UserMapper userMapper;
// 这个方法名建议和接口中的方法名一致
public UserInfo getUserById(Integer id) {
return userMapper.getUserById(id);
}
public Integer addUser(UserInfo userInfo) {
return userMapper.addUser(userInfo);
}
}
Mapper
package com.example.mybatisstudy.mapper;
import com.example.mybatisstudy.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 根据用户id查询用户信息
* @param id
* @return
*/
UserInfo getUserById (Integer id);
/**
* 查询全部
* @return
*/
List<UserInfo> getALL();
/**
* 添加用户
*/
int addUser(UserInfo userInfo);
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisstudy.mapper.UserMapper">
<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo where id = #{id}
</select>
<select id="getALL" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo
</select>
<insert id="addUser">
insert into userinfo(username,password,createtime,updatetime)
values(#{username},#{password},#{createtime},#{updatetime})
</insert>
</mapper>
测试
如果需要返回自增id
,则mapper.xml
实现如下
useGeneratedKeys
:取出主键keyColumn
:设置生成键值在表中的列名,如果生成列不止一个,可以用都好分隔多个属性名称keyProperty
:将主键取出之后赋值给类的某个属性
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisstudy.mapper.UserMapper">
<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo where id = #{id}
</select>
<select id="getALL" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo
</select>
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
insert into userinfo(username,password,createtime,updatetime)
values(#{username},#{password},#{createtime},#{updatetime})
</insert>
</mapper>
```java
@ RequestMapping("/adduser")
public String addUser(@RequestBody UserInfo userInfo) {
userInfo.setCreatetime(LocalDateTime.now());
userInfo.setUpdatetime(LocalDateTime.now());
int res = userService.addUser(userInfo);
if (res == 1) {
return "插入成功" + userInfo.getId();
}
return "插入失败";
}
(2)改
- 以在
userinfo
表中将id
为2的username
由“张三”更改为“张奇”为例
控制层
package com.example.mybatisstudy.controller;
import com.example.mybatisstudy.entity.UserInfo;
import com.example.mybatisstudy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 当返回实体对象时,Spring会自动转换为json
@RequestMapping("/getuserbyid")
public UserInfo getUserById(Integer id) {
if (id == null) return null;
return userService.getUserById(id);
}
@RequestMapping("/adduser")
public String addUser(@RequestBody UserInfo userInfo) {
userInfo.setCreatetime(LocalDateTime.now());
userInfo.setUpdatetime(LocalDateTime.now());
int res = userService.addUser(userInfo);
if (res == 1) {
return "插入成功" + userInfo.getId();
}
return "插入失败";
}
@RequestMapping("/update")
public String updateUserNameById(Integer id, @RequestParam("newname") String newUserName) {
int res = userService.updateUserNameById(id, newUserName);
if (res == 1) {
return "更改成功";
}
return "更改失败";
}
}
服务层
package com.example.mybatisstudy.service;
import com.example.mybatisstudy.entity.UserInfo;
import com.example.mybatisstudy.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// Spring会找这个interface的实现类
// 如果有多个实现类就会报错(可以使用resource解决)
@Autowired
private UserMapper userMapper;
// 这个方法名建议和接口中的方法名一致
public UserInfo getUserById(Integer id) {
return userMapper.getUserById(id);
}
public Integer addUser(UserInfo userInfo) {
return userMapper.addUser(userInfo);
}
public Integer updateUserNameById(Integer id, String newUserName) {
return userMapper.updateUserNameById(id, newUserName);
}
}
Mapper
package com.example.mybatisstudy.mapper;
import com.example.mybatisstudy.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
/**
* 根据用户id查询用户信息
* @param id
* @return
*/
UserInfo getUserById (Integer id);
/**
* 查询全部
* @return
*/
List<UserInfo> getALL();
/**
* 添加用户
*/
int addUser(UserInfo userInfo);
/**
* 修改用户名
*/
int updateUserNameById(Integer id, String newUserName);
}
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisstudy.mapper.UserMapper">
<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo where id = #{id}
</select>
<select id="getALL" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo
</select>
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
insert into userinfo(username,password,createtime,updatetime)
values(#{username},#{password},#{createtime},#{updatetime})
</insert>
<update id="updateUserNameById">
update userinfo set username=#{newUserName} where id=#{id}
</update>
</mapper>
测试
(3)删
- 略:删除操作和更新操作非常相似
三:查
(1)单表查询
单表查询非常简单,因此不再阐述。这里主要说明一种非常特殊的情况:like使用#{}
报错
List<UserInfo> getUserByName(String UserName);
```xml
<select id="getUserByName" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo where username like '%#{username}%'
</select>
这个sql语句相当于
select * from userinfo where username like '%'username'%'
在不使用${}
(最好不要使用)的前提下,如果要解决这个问题,可使用MySql内置函数concat()解决
<select id="getUserByName" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo where username like concat('%', #{username}, '%');
</select>
(2)resultType和resultMap
A:resultType
resultType:用于指定查询结果的类型,即数据库查询返回的每一行记录将被映射为指定类型的对象。它通常用于简单的映射场景,其中查询返回的数据可以直接映射到一个简单的 Java 对象或基本数据类型。适用于绝大多数情况
<select id="selectUser" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
但如果数据库字段名和类属性名不同时,就会出现下面的问题
<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">
select * from userinfo where id = #{id}
</select>
B:resultMap
resultMap:提供了更灵活和复杂的结果映射机制。它允许你通过定义映射规则,将查询结果的列映射到 Java 对象的属性或其他复杂结构中。它通常用于处理复杂查询,例如多表连接查询、嵌套查询等情况
<resultMap id="testMap" type="com.example.mybatisstudy.entity.UserInfo">
<id column="id" property="id"></id>
<result column="username" property="UN"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="state" property="state"></result>
</resultMap>
<select id="getUserById" resultMap="testMap">
select * from userinfo where id = #{id}
</select>
C:更为简单的解决方法
仍然使用resultType
,我们可以使用别名解决
<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">
select id, username as UN, password, photo, createtime, updatetime, state from userinfo where id = #{id}
</select>
(3)多表查询
- 以在
articleinfo
表中查询uid=1
的username
为例
由于articleinfo
中没有username
字段,所以要联合userinfo
表查询。我们可以建立类似于视图的实体
ArticleInfo.java
package com.example.mybatisstudy.entity;
import lombok.Data;
@Data
public class ArticleInfo {
private int id;
private String title;
private String content;
private String createtime;
private String updatetime;
private int uid;
private int rcount;
private int state;
}
ArticleInfoView.java
package com.example.mybatisstudy.entity.view;
import com.example.mybatisstudy.entity.ArticleInfo;
import lombok.Data;
@Data
public class ArticleInfoView extends ArticleInfo {
private String username;
}
ArticleMapper.java
package com.example.mybatisstudy.mapper;
import com.example.mybatisstudy.entity.ArticleInfo;
import com.example.mybatisstudy.entity.view.ArticleInfoView;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ArticleMapper {
ArticleInfoView getById (Integer id);
}
ArticleMapper.xml
select article.*, user.username
: 这部分指定了要选择的列。它从名为articleinfo
的表中选择所有列,并从名为userinfo
的表中选择username
列。这意味着查询的结果将包含来自articleinfo
表的所有列,以及来自userinfo
表的username
列from articleinfo article
: 这部分指定了要从哪个表中检索数据。表名为articleinfo
,它用别名article
来简化查询中的引用left join userinfo user on user.id=article.id
: 这是一个左连接操作,它将articleinfo
表与userinfo
表连接起来。连接的条件是两个表中的id
列相等。左连接意味着即使在userinfo
表中找不到匹配的id
,仍然会返回articleinfo
表中的行。如果没有匹配的userinfo
行,相关的username
列将包含NULL值where article.id=${id}
: 这部分是一个筛选条件,它限制了查询的结果,只包含满足条件的行。${id}
是一个占位符,它表示在实际执行查询时将被具体的值替换。这个条件指定了在articleinfo
表中选择具有特定id
值的行
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisstudy.mapper.ArticleMapper">
<select id="getById" resultType="com.example.mybatisstudy.entity.view.ArticleInfoView">
select article.*, user.username from articleinfo article left join userinfo user on user.id=article.id where <article></article>.id=${id}
</select>
</mapper>
四:动态SQL
动态SQL:它允许你根据不同的条件生成不同的SQL语句,从而更灵活地构建和执行SQL查询。动态SQL通常用于构建动态查询条件,避免在代码中硬编码多个不同版本的SQL语句
例如下图,在用户注册时,可能面临【必填字段】和【非必填字段】的问题。当一些不确定字段传入时,应该如何编写呢?MyBatis的动态SQL可以解决这一问题
(1)if标签
if标签:允许你在SQL语句中添加条件判断
如下:<if>
标签用于条件判断。它的test
属性用于指定一个条件表达式,只有当条件表达式为true时,<if>
标签内的内容才会被包含在最终生成的SQL语句中。在这个例子中,如果username
和age
都不为null,那么对应的条件语句会被包含在最终的SQL语句中,从而实现了动态生成SQL语句的效果
<select id="getUsers" parameterType="map" resultType="User">
SELECT * FROM users
WHERE 1 = 1
<if test="username != null">
AND username = #{username}
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
再来一个比较复杂的例子:假设我们有一个查询用户的功能,用户可以选择按照用户名升序或降序排序,也可以选择按照年龄升序或降序排序
<select id="getUsers" parameterType="map" resultType="User">
SELECT * FROM users
WHERE 1 = 1
<if test="orderByUsername != null">
ORDER BY username
<if test="ascending">
ASC
</if>
<if test="!ascending">
DESC
</if>
</if>
<if test="orderByAge != null">
<if test="orderByUsername == null"> <!-- 如果已经有了排序条件,追加一个逗号 -->
ORDER BY
</if>
age
<if test="ascending">
ASC
</if>
<if test="!ascending">
DESC
</if>
</if>
</select>
插入的例子:我们使用了<if>
标签来判断是否需要插入年龄信息。如果用户提供了年龄信息,那么在生成的插入语句中会包含年龄字段。否则,将只插入用户名和邮箱信息
<insert id="insertUser" parameterType="User">
INSERT INTO users (username, email
<if test="age != null">
, age
</if>
) VALUES (#{username}, #{email}
<if test="age != null">
, #{age}
</if>
)
</insert>
(2)trim标签
trim标签:用来处理SQL语句的前缀、后缀、以及包裹元素之间的逗号问题,从而使动态SQL的构建更加灵活。有以下属性
prefix
:表示整个语句块以它作为前缀suffix
:表示整个语句块以它作为后缀prefixOverrides
:表示整个语句块要去除掉的前缀suffixOverrides
:表示整个语句块要去除掉的后缀
如下:假设我们有一个插入用户的功能,用户可以选择是否插入用户的年龄信息
<trim>
标签用于处理插入语句中字段的前缀和后缀- 通过
prefix
属性和suffix
属性,我们可以指定在包裹元素内容之前和之后要添加的字符串 suffixOverrides
属性指定了要从包裹元素内容中移除的后缀字符串,以处理逗号的问题
<insert id="insertUser" parameterType="User">
INSERT INTO users
<trim prefix="(" suffix=")" suffixOverrides=",">
username,
<if test="email != null">
email,
</if>
<if test="age != null">
age,
</if>
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
#{username},
<if test="email != null">
#{email},
</if>
<if test="age != null">
#{age},
</if>
</trim>
</insert>
如果用户提供了邮箱和年龄信息,生成的插入语句将是
INSERT INTO users (username, email, age) VALUES (#{username}, #{email}, #{age})
如果用户只提供了用户名和邮箱信息,生成的插入语句将是:
INSERT INTO users (username, email) VALUES (#{username}, #{email})
(3)where标签
where标签:用于构建SQL语句中的WHERE
子句。与<trim>
标签类似,<where>
标签可以处理SQL语句中的前缀和后缀,同时在内容为空的情况下能够自动省略WHERE
关键字。通过<where>
标签,可以更方便地构建包含动态条件的WHERE
子句,而无需手动处理冗余的AND
关键字。这有助于提高动态SQL语句的可读性和维护性
<where>
标签通常配合<if>
标签使用<where>
标签会删除最前的and关键这,而不会删除最后面的
如下:假设我们有一个查询用户的功能,用户可以根据不同的条件查询,比如用户名、邮箱和年龄
<where>
标签用于包裹<if>
标签,它会自动处理WHERE
关键字和多余的AND
<select id="getUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
下面是where
和trim
处理同一段功能代码的区别,使用哪个完全取决于个人偏好和需求
# <where标签>
<select id="getUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
</select>
# <trim标签>
<select id="getUsers" parameterType="map" resultType="User">
SELECT * FROM users
<trim prefix="WHERE" prefixOverrides="AND">
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
</trim>
</select>
(4)set标签
set标签:当你需要根据不同条件来更新不同字段时,可以使用set
标签。使用它,你可以指定一系列可能得更新,并且只有在相应条件满足时,这些更新才会被包含在最终的SQL语句中
set
标签通常要配合if
标签一起使用set
标签会自动去除最后一个英文
如下:你需要根据用户提供的数据来更新一个用户的记录。用户可能只更新了他们的邮箱地址,或者他们的地址和电话号码,或者所有这些信息。使用动态 SQL 的 SET
标签,你可以构建一个 SQL 语句,它只会更新用户实际提供了新数据的字段
<update id="updateUser">
UPDATE user
<set>
<if test="email != null">email = #{email},</if>
<if test="address != null">address = #{address},</if>
<if test="phone != null">phone = #{phone}</if>
</set>
WHERE id = #{id}
</update>
当然你可以使用trim
标签代替
<update id="updateUser">
UPDATE user
<trim prefix="SET" suffixOverrides=",">
<if test="email != null">email = #{email},</if>
<if test="address != null">address = #{address},</if>
<if test="phone != null">phone = #{phone},</if>
</trim>
WHERE id = #{id}
</update>
(5)foreach标签
foreach标签:该标签允许你遍历一个集合,并对每个元素执行一段 SQL 代码片段。它在处理诸如批量插入、更新或查询时非常有用
collection
: 指定要遍历的集合。这个属性是必需的。在 MyBatis 中,根据传入的对象类型(例如 List、Map 或数组),这个属性的值可能是list
、map
或其他item
: 为集合中的每个元素指定一个别名。在<foreach>
体内,可以使用这个别名来引用当前元素index
: 在遍历 Map 或 List 时,这个变量代表键或索引separator
: 指定在每次迭代后添加的分隔符,常用于 SQL 语句中元素之间的分隔,如逗号open
和close
: 指定<foreach>
生成的 SQL 片段的开始和结束部分,常用于添加括号
例如:假设你有一个产品 ID 列表,并且你想构建一个查询以获取所有这些产品的详细信息。你可以使用 <foreach>
标签来动态生成查询:
<select id="selectProducts" resultType="Product">
SELECT * FROM product
WHERE id IN
<foreach item="item" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
在这个例子中,如果你的产品 ID 列表是 [1, 2, 3]
,那么 MyBatis 将生成以下 SQL 语句:
SELECT * FROM product WHERE id IN (1, 2, 3)
评论区