文章目录
问一:更新传入值为null的属性,数据库不更新.
使用MP提供的更新接口时,我们传入某个属性为空的对象,进行数据库数据更新时,我们发现当前字段并没有更新。
Entity
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("mnt_app")
@ApiModel(value="App对象", description="")
public class App extends BasicModel<App> {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "项目id")
@TableId(value = "app_id", type = IdType.AUTO)
private Long appId;
@ApiModelProperty(value = "项目名称")
private String name;
@ApiModelProperty(value = "完成时间")
private LocalDateTime finishTime;
@Override
protected Serializable pkVal() {
return this.appId;
}
}
controller
“App”:{
“appId”: 6,
“name”:张三,
“finishTime”: null,
}
LambdaQueryWrapper<App> queryWrapper = Wrappers.<App>lambdaQuery()
.eq(App::getAppId, app.getAppId());
int update = appMapper.update(app, queryWrapper);
SQL执行日志
UPDATE mnt_app SET name='张三' WHERE is_deleted=0 AND (app_id = 6) ;
通过sql的执行日志我们可以看出,我们的finishTime属性并没有传入更新。
解决方式
上述问题的原因很简单MP为我们提供了一个非主键注解@TableField,其中有一个属性FiledFill属性,它的默认是FieldFill.DEFAULT,默认不处理表示的是当属性值为null时,进行增删改操作时,就忽略这个字段。因此我们进行更新操作时,出现了并没有set finishTime这个操作。
如果我们不想忽略此字段,只需在实体类中,根据使用场景,在属性上添加 @TableField(fill = FieldFill.UPDATE) 即可。
最后的测试
//添加@TableField(fill = FieldFill.UPDATE),当进行更新操作时,值为null不忽略该字段设置。
@TableField(fill = FieldFill.UPDATE)
@ApiModelProperty(value = "完成时间")
private LocalDateTime finishTime;
实体类
“App”: {
“appId”: 6,
“finishTime”: null,
“name”: “张三”
}
最终效果
UPDATE mnt_app SET name='张三'finish_time=NULL WHERE is_deleted=0 AND (app_id = 6)
此时,我们就完成字段置空的更新了。
问二:@Param如何使用 mybatis版本3.5.6
@Param是mybatis为我们提供的一个mapper参数与sql的映射注解。简单来说,就是将增删改查条件传入到sql语句中。
官方文档解释
如果你的映射方法接受多个参数,就可以使用这个注解自定义每个参数的名字。否则在默认情况下,除 RowBounds 以外的参数会以 “param” 加参数位置被命名。例如 #{param1}, #{param2}。如果使用了 @Param(“person”),参数就会被命名为 #{person}。
我的解读
官方解释的意思就是,如果我在mapper的参数中不使用@Param,那么在sql写条件时,我写入的条件名称,都会被当做 #{param1}, #{param2}处理。
举个例子
学生实体
之后我们的每个例子,都是基于这个student操作的
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="Student对象", description="")
public class Student extends Model<Student> {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "学号")
private String stuId;
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "性别 0 男,1 女")
private Integer sex;
@ApiModelProperty(value = "手机号")
private String phone;
@ApiModelProperty(value = "个人简介")
private String info;
@ApiModelProperty(value = "头像,picture表id")
private Long picture;
@ApiModelProperty(value = "班级")
private String className;
@ApiModelProperty(value = "专业")
private String major;
@ApiModelProperty(value = "所在小组")
private Long gId;
@ApiModelProperty(value = "0 未删除,1 已删除")
private Boolean isDeleted;
@Override
protected Serializable pkVal() {
return this.id;
}
}
通过学生id,查询某一个学生的基本信息举例
Student student = studentMapper.selectStudentById(id);
mapper
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{id}"+
"</script>")
Student selectStudentById(Long id);
- 虽然我们这里使用了#{id},但是实际运行时,mybatis还是把它当做#{param1}处理了。如果我们在前面加一个@Param(“id”),则作为#{id}处理,同时也可以作为#{param1}处理。
那么问题来了,竟然id能被转换成param1,是不是这里随便写一个,即使不和方法中的参数名对照,也会自动转换成param1,而不会报错呢?
我们把#{id}换成#{ids}
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{ids}"+
"</script>")
Student selectStudentById(Long id);
经过测试,顺利通过。
- 旧版本使用${id}时,可能会出现报错(在18年之前的blog中看到的),但是我测试了一下,没有报错,可能官方已经优化了。
不同情况下使用@Param分析
1. 传递一个属性值作为参数
不使用@Param
见上方例子
使用@Param
我们可以自定义sql与mapper的映射,当然,@Param(“id”)也是不成问题的
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{student_id}"+
"</script>")
Student selectStudentById(@Param("student_id") Long id);
2. 传递一个对象作为参数
不使用@Param
service
Student student = new Student();
student.setId(1L);
studentMapper.selectStudentByMessage(student);
mapper
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{id}"+
"</script>")
Student selectStudentByMessage(Student student);
- 如果不使用@Param,我们可以直接在#{}中写 所需要的对象的属性id,就可以自动获取student中的id属性。
- 如果我们这样写#{student.id},则会报错
There is no getter for property named ‘student’ in ‘class
com.marchsoft.group.manager.system.entity.Student’
提示没有getStudent()这个方法,说明当没有@Param时,我们不能在#{对象.属性}来获取对象的值。
使用@Param
service
Student student = new Student();
student.setId(1L);
studentMapper.selectStudentByMessage(student);
mapper
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{student.id}"+
"</script>")
Student selectStudentByMessage(@Param("student") Student student);
- 如果使用@Param,则必须在#{}指明要获取对象的哪一个属性。可以是#{student.id},也可以是#{param1.id}
- 如果我们使用#{id},直接获取属性的值,则会报错
Parameter ‘id’ not found. Available parameters are [student, param1]
提示我们没有id这个参数,只有student和param1这两个参数,同时也可见,student同时也可以用param1代替。
我们使用param1测试,发现可以正常执行。
3. 传递多个属性作为参数
不使用@Param
service
Student student = studentMapper.selectStudentById(id,"张三");
mapper
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{id} AND s.name=#{name}"+
"</script>")
Student selectStudentById(Long id,String name);
大家能看出上述代码的问题吗?额,好像没什么问题,结果运行,纳尼?报错了,我们来看报错信息
Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
参数id不存在。但是却有 [arg1, arg0, param1, param2],看来mybatis自动的把我们这两个参数替换了。
正确使用
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{param1} AND s.name=#{param2}"+
"</script>")
Student selectStudentById(Long id,String name);
或者用arg0和arg1。前者表示id,后者表示name
使用@Param
service
Student student = studentMapper.selectStudentById(id,"张三");
mapper
@Select("<script>" +
"SELECT s.id,name,stu_id,class_name,group_name " +
"FROM student s, group_admin g " +
"WHERE s.`g_id`=g.`id` AND s.id=#{student_id} AND s.name=#{student_name}"+
"</script>")
Student selectStudentById(@Param("student_id")Long id,@Param("student_name") String name);
我们使用student_id和student_name,分别表示id和name的参数映射。运行完全没有问题
我们看一下,还可以怎么设置#{}里面的值。
Parameter ‘student’ not found. Available parameters are [stu_name, student_id, param1, param2]
从错误中我们可以看出,我们还可以使用param1和param2
4. 传递多个对象作为参数
接下来,我们来测试一下多个对象作为参数来测试,可能这个场景实际开发中很少用到,也可能我的业务逻辑比较简单,用不到吧,不过既然来了,我们还有有必要测试一下的。
测试场景
用户表user
学生表student
我们联表查user的id为1,并且student的name 为张三的人
用户表的username和student表的stu_id是相互关联的。
查询语句
SELECT s.student_id,u.username,s.name FROM user u ,student s WHERE
u.id=? AND u.username=s.student_id AND s.name=?;
不使用@Param
service
Student student = new Student();
student.setName("张三");
User user = new User();
user.setId(1L);
Student student1 = studentMapper.selectStudentByMessage(student, user);
mapper
@Select("<script>" +
"SELECT s.stu_id,u.username,s.name " +
"FROM user u ,student s " +
"WHERE u.id=#{id} AND u.username=s.stu_id AND s.name=#{name}"+
"</script>")
Student selectStudentByMessage(Student student, User user);
之前一个对象时,我们可以直接#{属性名称},照样是可以识别的,那两个是否能识别呢?
Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
显然,是不行的。
我们再试一下用student.id 是否可行
@Select("<script>" +
"SELECT s.stu_id,u.username,s.name " +
"FROM user u ,student s " +
"WHERE u.id=#{user.id} AND u.username=s.stu_id AND s.name=#{student.name}"+
"</script>")
Student selectStudentByMessage(Student student, User user);
Parameter ‘user’ not found. Available parameters are [arg1, arg0, param1, param2]
看来,在不使用@Param的情况下,多个对象时,只能使用arg或param了。
使用@Param
service
Student student = new Student();
student.setName("张三");
User user = new User();
user.setId(1L);
Student student1 = studentMapper.selectStudentByMessage(student, user);
mapper
@Select("<script>" +
"SELECT s.stu_id,u.username,s.name " +
"FROM user u ,student s " +
"WHERE u.id=#{user.id} AND u.username=s.stu_id AND s.name=#{student.name}"+
"</script>")
Student selectStudentByMessage(@Param("student") Student student, @Param("user")User user);
同样的,我们还可以使用@Param1.属性,和@Param2.属性代替,但是注意的是,如果我们就写一个#{user}或#{Param1}是会产生错误的。形参未对象时必须要指定到属性名称。
总结
在上面我们分析了四种情况下@Param的使用,不难不发现,传递一个参数时,使不使用@Param的结果都一样,都会把我们方法中的参数映射成#{param}或者#{arg}。但是传递多个参数时,如果使用@Param,则必须使用param,但是多个参数时使用param,会特别的不方便我们读取sql语句,比如id=#{param1.id},我们还得去分析param1到底是什么。
所以,在实际使用中,我们要视情况而定,我通常是这样使用的。
- 单独一个属性参数,直接传参,不使用@Param
- 多个属性参数,使用@Param,不然你的sql中都是@{Param1-N}
- 单独一个对象参数,使用@Param,具体指向对象的某个属性,便于阅读
- 多个对象参数,使用@Param,原因就不解释了。
问三:动态sql如何使用及使用注意
script
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
更新
@Update({
"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
或者
@Update("<script>" +
"UPDATE t_libellee" +
"<trim prefix ='SET' suffixOverrides=','>" +
"<if test='updateLibeller.markRgbStatus!=null'>" +
"mark_rgb_status=#{updateLibeller.markRgbStatus}" +
"</if>" +
"</trim>" +
"WHERE id=#{updateLibeller.id} AND is_deleted=0" +
"</script>")
int updateRiskLibeller(@Param("updateLibeller") UpdateLibellerRiskDTO updateLibeller);
trim可用于替代set。trim是where的升级版。
foreach
@Insert({
"<script>",
"INSERT INTO sys_users_roles_apps(app_id,role_id,user_id) VALUES",
"<foreach collection='userIds' item='item' index ='index' separator=','>",
"(#{appId},#{roleId},#{item})",
"</foreach>",
"</script>"})
void insertUserRoleApp(Long appId, Long roleId, List<Long> userIds);
执行sql:INSERT INTO sys_users_roles_apps(app_id,role_id,user_id) VALUES (74,9,10) , (74,9,11) , (74,9,12)
注意:foreach是针对与一条sql记录插的,并不是生成多条sql语句。
if和where
@Select({
"<script>" +
"SELECT id FROM sys_user us " +
"<where>\n" +
"AND us.id IN (SELECT u.id FROM sys_user u, mnt_app a, sys_users_roles_apps sa\n" +
"WHERE u.`id` IN (" +
"<foreach collection='ids' item ='id' index='index' separator=','>" +
" #{id}" +
"</foreach> " +
") \n" +
"AND a.`app_id` IN (SELECT app_id FROM mnt_app WHERE is_deleted=FALSE AND STATUS IN (0,4))\n" +
"AND u.`id`=sa.`user_id` \n" +
"AND sa.`app_id`=a.`app_id` GROUP BY u.`id`) \n" +
"</where>" +
"</script>"})
Set<Long> selectJoinAppId(IPage<User> user, @Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper, Set<Long> ids);
IPage user:分页条件
@Param(Constants.WRAPPER) LambdaQueryWrapper wrapper:查询条件
Integer status:状态
if和where通常会放在一起使用。
使用where,如果之前没有where,条件成立时,会自动填上where关键字。
如果条件成立,但是where后紧跟着and,则会自动把and给消除掉。