采坑记录
1. IService.updateById()方法无法更新空值
可以换用UpdateWrapper进行更新
2. listByIdS()查询条件为空会报错
queryWrapper.in(idList) idList为空会报错。
queryWrapper.in(dormitoryIdList);
List<DormitoryVO> list = list(queryWrapper);上述情况下,一定要对作为查询条件的dormitoryIdList进行判空处理,否则list查询哪里会报错。
3. <if>标签的条件没有生效(字符串比较)
https://blog.csdn.net/qq_41865739/article/details/134991713
statusCode!='1',
这样的一个条件放在<if>标签里面是不会生效的,
解决方法:
方法一:statusCode!='1'.toString()
方法二:statusCode!=“1”,然后把test的双引号换成单引号。
4. xml里面"<="爆红
解决方法:
使用<![CDATA[<=]]>
5. in语法
在xml里面使用in list,必须把list使用foreach包起来,不能直接in list
6. Sql 里面判断空
判空要写is not null , 而不是 !=null,
7. Having 里面只能放聚合函数,如果放进去普通的字段会报错找不到这个字段
8. Group by 分组操作是在where筛选之后执行的,
Group by 分组操作是在where筛选之后执行的,所以如果在where中使用聚合函数进行筛选,会报错。
解决方法:
可以把用到聚合函数的条件筛选查询先不做,把其他的整个查询作为一个子查询,外面嵌套一个查询,把聚合函数的条件筛选防盗外层筛选。
9. 连表造成数据重复
背景:task表连subtask表,subtask表连worklogcontent表,按照task_id分组,然后计算subtask表的workhours的和:sum(pps.work_hours)
错误结果:计算出来的workhour和会比真实结果大很多。
坑:从task连到worklogcontent,由于subtask和worklogcontent是一对多,会导致最终连出来的表subtask会重复很多次(为了展示出来每一个worklogcontent,一个worklogcontent一行,导致subtask重复了)。
解决方法:
在select里面再次select查询subtask表
10. Order by #{sidx} #{order} 失效
使用 ${} 就可以了
11. 在<if>标签里面,比较long类型失效 “==”
需要使用符号 == 进行比较
12. 映射到xml文件的sql的参数失效
在dao文件里面,sql函数参数需要使用@Param进行标记,否则sql函数无法识别参数
13. 表A对应表B多条数据,每次只取表B最新的数据
pdpa(B)
join psps(A) on psps.id = pdpa.product_id
where pdpa.id = (select id from pd_digital_product_apply
where salable_product_id = psps.id
order by create_time desc
limit 1)#{}和${}的区别?
1. 本质区别
#{}**:预编译、参数占位符(推荐)**
${}**:字符串拼接、直接替换(不推荐,不安全)**
2. 会不会 SQL 注入?
#{} ✅ 不会
底层用 PreparedStatement,参数会被转义、防注入。
${} ❌ 会
直接把参数拼进 SQL 字符串,完全不防 SQL 注入。
3. 字符串是否自动加引号?
#{} 会自动加引号
例:
name = #{name}
name = '张三'${} 不会加引号
name = ${name}
name = 张三 -- 报错!少了单引号
4. 使用场景
✅ 用 #{} 的场景(99% 情况)
查询条件
插入/更新字段值
所有用户输入参数
WHERE id = #{id} AND name = #{name}✅ 用 ${} 的场景(极少、必须可控)
只能用在 不能用占位符的地方,比如:
表名动态
排序字段动态
列名动态
这些地方 JDBC 不允许预编译,必须拼接。
例:
ORDER BY ${orderColumn} ${orderType}
FROM ${tableName}注意:这些值必须后端自己控制,绝对不能让用户传!
5. 一句话总结
#{}**:安全、预编译、自动加引号 → 传值用**
${}**:不安全、字符串拼接、不加引号 → 表名/列名/排序字段用**
如果你要面试,我可以给你一句满分背诵版:
#{}是预编译参数占位符,使用 PreparedStatement,能防止 SQL 注入,会自动给字符串加引号;
${}是直接字符串拼接,无法防止 SQL 注入,不会加引号,仅用于动态表名、列名、排序字段等不可预编译场景。
Dao.xml文件里的标签
1.<choose>
它的语法和用法类似于 Java 中的 switch 或 if-else 语句。
<choose> 标签通常与 <when> 和 <otherwise> 标签一起使用:
<choose>: 这是一个容器标签,表示条件选择。<when>: 表示一个条件,当满足条件时,执行其中的 SQL 语句。<otherwise>: 表示默认的 SQL 语句,当没有任何<when>条件满足时执行。如果有多个<when>子标签的条件都满足,只会执行第一个满足条件的<when>子标签内的 SQL 语句块,其他<when>子标签块会被忽略。
举例:
<select id="selectUsers" resultType="com.example.User">
SELECT * FROM user
<where>
<choose>
<when test="username != null">
AND username = #{username}
</when>
<when test="email != null">
AND email = #{email}
</when>
<otherwise>
AND status = 'active' <!-- 默认条件 -->
</otherwise>
</choose>
</where>
</select>
2.<resultmap>嵌套查询,<collection>
博客园1,
博客园2
查询的单个对象中的一个属性包含多个其他对象,使用collection标签嵌套:
按照
查询嵌套时,即查询出来后再嵌套,查询语句一般是由两个查询语句组成,类似子查询按照
结果嵌套时,即查询时整体嵌套,查询语句一般为联合查询
举例:查询老师的信息,老师的信息包含id、name、List<Student>
2.1 查询嵌套: 嵌套的内容还需要额外加一个查询查出来,collection里面是一个子查询
<!--一对多 按查询嵌套处理 子查询-->
<!--首先根据id查询指定老师的信息-->
<select id="selectTeacher2" resultMap="TeacherStudent2">
select * from teacher where id=#{tid};
</select>
<!--编写结果集映射-->
<resultMap id="TeacherStudent2" type="teacher">
<!--此处由于属性和字段名一致所以下面两行可以省略-->
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--由于查询老师中有一个属性为所有学生对象集合,所一使用collection标签进行嵌套,
方法的返回值是List,所以javaType使用ArrayList来接收,而list中泛型约束类型放的
是student对象所以使用ofType为student来进行映射,使用select进行子查询,并用column
将查询出的id字段传给子查询需要的id-->
<!--此处javaType可以省略-->
<collection property="student" javaType="ArrayList" ofType="student" select="selectStu" column="id"/>
</resultMap>
<!--子查询-->
<select id="selectStu" resultType="student">
select * from student where tid=#{tid};
</select>特别注意:如果嵌套的查询需要多个传参,可以使用如下写法:
column="{id=id,code=code}"2.2 结果嵌套 : 嵌套的查询内容先查出来,collection里面是结果映射
<!--一对多 按结果嵌套查询 多表查询-->
<!--联合查询所有需要的信息-->
<select id="selectTeacher" resultMap="TeacherStudent">
select t.id tid,t.name tname, s.id sid,s.name sname
from student s,teacher t
where t.id=s.tid and t.id=#{tid};
</select>
<!--编写结果集映射-->
<resultMap id="TeacherStudent" type="teacher">
<result column="tid" property="id"/>
<result column="tname" property="name"/>
<!--因为查询的老师实体类字段为studnet,所以属性名为student,查询出的映射对象、
是student类,所以使用ofType指定为student类-->
<!--此处加上javaType也能正常运行,进行了省略应该-->
<collection property="student" ofType="student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
</collection>
</resultMap>3.<include refid=""/>
在 MyBatis Plus 中,<include refid=""/> 这个标签主要用于引用其他 SQL 片段。
具体来说,<include refid=""/> 标签中的 refid 属性用于指定要引用的 SQL 片段在当前 XML 文件中的 id。当 MyBatis Plus 解析到 <include> 标签时,会动态地将指定的 SQL 片段插入到当前位置。
这个其他片段可以使用<sql id="xxx">标签独立进行定义。
示例:
假设在 MyBatis Plus 的 XML 文件中有如下定义的 SQL 片段:
<sql id="commonColumns">
id, name, age
</sql>然后可以在另一个地方使用 <include refid="commonColumns"/> 来引用这个 SQL 片段,例如:
<select id="selectUser" resultType="User">
SELECT
<include refid="commonColumns"/>
FROM user_table
</select>4.<result property="tid" column="id"/>
在 MyBatis Plus 中,<result property="tid" column="id"/> 的含义与之前的解释类似,主要是用来进行数据库查询结果与 Java 对象属性之间的映射。具体来说:
property="tid":表示将查询结果集中的某一列的值映射到 Java 对象的tid属性上。column="id":表示要从查询结果集中取出的数据库列名为id。
结合起来,这个配置的意思是:当执行查询操作时,从结果集中取出的 id 列的值将被赋值给 Java 对象中名为 tid 的属性。这通常用于在数据传输对象(DTO)中将数据库中某个列的值映射到不同的 Java 属性名,以便于数据处理和业务逻辑实现。
这种映射常见于需要将结果集从一张表映射到一个与之不完全相同的数据模型中,例如在进行数据转换或聚合时。
分页查询
1.参考博客
2.举例子
1. 引入分页插件
首先,需要在 MyBatis-Plus 的配置类中引入分页插件:
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
2. 使用分页查询
在 Service 层或 Mapper 层使用分页查询,通常是通过 Page 类来实现的。示例如下:
Mapper层分页查询方法 selectPage:
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper后无需额外定义分页查询方法
}
使用示例:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public IPage<User> getUsersPage(int currentPage, int pageSize) {
// 创建分页对象
Page<User> page = new Page<>(currentPage, pageSize);
// 执行分页查询,第二个参数为queryWrapper
Page<User> page = userMapper.selectPage(page, null);
return page;
}
}
官方文档service层的page()方法:
https://baomidou.com/guides/data-interface/#page
page()方法有以下:
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
使用示例:
// 假设要进行无条件的分页查询,每页显示10条记录,查询第1页
IPage<User> page = new Page<>(1, 10);
IPage<User> userPage = userService.page(page); // 调用 page 方法
List<User> userList = userPage.getRecords();
long total = userPage.getTotal();
使用warapper:
// 假设有一个 QueryWrapper 对象,设置查询条件为 age > 25,进行有条件的分页查询
IPage<User> page = new Page<>(1, 10);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 25);
IPage<User> userPage = userService.page(page, queryWrapper); // 调用 page 方法
List<User> userList = userPage.getRecords();
long total = userPage.getTotal();
3.自定义分页
有时候你可能需要自定义分页类,以便在分页查询结果中添加更多信息或进行其他特殊处理。下面是一个自定义分页类的示例。
1. 自定义分页类
public class CustomPage<T> extends Page<T> {
// 额外的字段,比如总页数
private long totalPages;
// 构造函数
public CustomPage(long current, long size) {
super(current, size);
}
@Override
public CustomPage<T> setRecords(List<T> records) {
super.setRecords(records);
// 计算总页数
this.totalPages = this.getTotal() / this.getSize();
if (this.getTotal() % this.getSize() != 0) {
this.totalPages++;
}
return this;
}
// Getter 和 Setter
public long getTotalPages() {
return totalPages;
}
public void setTotalPages(long totalPages) {
this.totalPages = totalPages;
}
}
2. 使用自定义分页类
在 Service 层使用自定义分页类:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public CustomPage<User> getUsersCustomPage(int currentPage, int pageSize) {
// 创建自定义分页对象
CustomPage<User> customPage = new CustomPage<>(currentPage, pageSize);
// 执行分页查询
userMapper.selectPage(customPage, null);
// 返回自定义分页对象
return customPage;
}
}
自定义SQL
1.参考博客
当数据库字段名和VO对象的属性字段名不一样
总结
有以下几种方法:
@Results 注解:直接在Mapper接口中定义字段映射,简单明了。
XML 中定义ResultMap:在XML中定义ResultMap,可以进行复杂的映射配置。
Mapper中调用XML里的@ResultMap:结合注解和XML使用,清晰分离SQL和映射配置。
MyBatis Plus 别名功能
@TableField:使用注解@TableField,简单直接地进行字段映射。TypeHandler:用于复杂类型处理,自定义字段映射逻辑。
在Spring Boot项目中使用MyBatis-Plus进行数据库查询时,当数据库字段名与返回的结果VO对象的属性字段名不一致时,可以通过多种方法解决映射问题。以下是详细解释的几种方法:
1. 使用 @Results 注解进行结果映射
@Results 注解用于在Mapper接口的方法上直接定义结果集的映射关系。
示例:
假设数据库表字段为user_name,而VO对象属性为userName。
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Result;
@Mapper
public interface UserMapper {
@Select("SELECT user_name FROM user WHERE id = #{id}")
@Results({
@Result(property = "userName", column = "user_name")
})
UserVO selectUserVOById(Long id);
}
2. XML 中定义ResultMap
在Mapper XML文件中定义结果集映射关系。
示例:
假设数据库表字段为user_name,而VO对象属性为userName。
UserMapper.java:
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
UserVO selectUserVOById(Long id);
}
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserVOResultMap" type="com.example.vo.UserVO">
<result property="userName" column="user_name"/>
</resultMap>
<select id="selectUserVOById" resultMap="UserVOResultMap">
SELECT user_name
FROM user
WHERE id = #{id}
</select>
</mapper>
3. Mapper中调用XML里的@ResultMap
结合@MapKey和@ResultMap注解使用。
示例:
假设数据库表字段为user_name,而VO对象属性为userName。
UserMapper.java:
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.ResultMap;
@Mapper
public interface UserMapper {
@Select("SELECT user_name FROM user WHERE id = #{id}")
@ResultMap("UserVOResultMap")
UserVO selectUserVOById(Long id);
}
UserMapper.xml:
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserVOResultMap" type="com.example.vo.UserVO">
<result property="userName" column="user_name"/>
</resultMap>
</mapper>
4. 使用 MyBatis Plus 的别名功能@TableField
MyBatis Plus提供了别名功能,通过注解@TableField指定字段映射关系。
示例:
假设数据库表字段为user_name,而VO对象属性为userName。
UserEntity.java:
import com.baomidou.mybatisplus.annotation.TableField;
public class UserEntity {
@TableField("user_name")
private String userName;
// 其他字段和getter、setter方法
}
5. 通过 MyBatis 的 TypeHandler 自定义映射
TypeHandler用于自定义类型处理,可以在字段映射时处理复杂的转换。
示例:
假设数据库表字段为user_name,而VO对象属性为userName。
UserEntity.java:
public class UserEntity {
private String userName;
// 其他字段和getter、setter方法
}
UserTypeHandler.java:
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
public class UserTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
UserMapper.java:
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.type.TypeHandler;
@Mapper
public interface UserMapper {
@Select("SELECT user_name FROM user WHERE id = #{id}")
@TypeHandler(UserTypeHandler.class)
UserVO selectUserVOById(Long id);
}
选择合适的方法取决于项目的具体需求和复杂度。以上方法可以单独使用,也可以结合使用以达到最佳效果。
防止全表更新与删除
防止全表更新与删除,是指在执行更新或删除操作时,防止因条件拼写错误或遗漏条件而导致更新或删除整个数据库表的记录。这种错误在实际开发中很容易发生,尤其是在条件语句写错的情况下。如果不加以防范,可能会导致严重的数据丢失或数据混乱问题。
MyBatis-Plus 通过在执行更新或删除操作时,自动检查 SQL 语句中的条件部分,如果检测到没有条件或条件为空,则会阻止这条操作的执行,从而保护数据的安全。
Service层接口方法
listMaps()
List<Map<String, Object>> userMaps = userService.listMaps(); // 调用 listMaps 方法
它返回一个 List<Map<String, Object>>,每个 Map 对应数据库表中的一行数据,Map 的键为列名,值为对应的列值。
getObj()
listObjs()
Mapper层接口方法
selectPage()
查询指定列
两种方法:
queryWrapper
queryWrapper.select("id");
lambdaQueryWrapper
lambdaQueryWrapper.select(User::getId);
如上:queryWrapper和lambdaQueryWrapper在完成一些相同的功能时参数看起来不太一样,类似的还有添加eq条件:
queryWrapper.eq("id", id);
lambdaQueryWrapper.eq(User::getId, id);
Wrapper的相关API
1.OR() 和 AND()
or(). 后面接别的语句,代表or()后面的和or()前面的使用or连接。
or(xxxx) 括号里面放入的语句,代表括号里面的sql要放在一个括号里面,作为一个整体。并且使用or进行连接。
2.queryWrapper.last()方法,添加自定义sql语句
queryWrapper.last 是 MyBatis-Plus 中 QueryWrapper 类的一个方法,用于直接在 SQL 查询的末尾添加自定义的 SQL 片段。
例如:
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id"); // 按 ID 降序排序
queryWrapper.last("LIMIT 1"); // 限制只返回一条记录
3.queryWrapper, 添加condition参数到条件函数
只有当condition判断为true时才会将本条查询条件添加到queryWrapper
例如:
queryWrapper.in(condition, "id", ids);