【JavaEE】MyBatis框架要点总结(2)
【JavaEE】MyBatis框架要点总结(2)
在上一篇文章中,我们主要讲解的内容就是MyBatis是什么,MyBatis环境的搭建,简单地体验了MyBatis的写法去查询数据库
而本文的内容就是,MyBatis更深入的讲解,以及更多的数据库操作,如增删改查…
- 值得注意的是,写法就是写法,记住它就好了,要使用框架就要遵守写法的规定,熟悉什么地方应该写什么…
- 没有 “为什么这么写” 等问题,就是规定
- 开发者不需要过多注意底层原理,“坐享其成”即可~
- 知道代码意味着什么,这么写框架会帮我们做什么就行
**知道怎么进行开发,这才是重点!**之后再去了解一下xml文件去实现接口的一些原理~
1. 单表查看操作
1.1 (条件查询)通过id查找用户
1.1.1 接口上声明方法
参数最好不要写基本数据类型,这是因为前端不传参数过来的话,可以传默认值
- 但是基本数据类型的默认值是有含义的,例如0、0.0、‘/u0000’、false
- 并且,如果前端没有传相应的参数,是会赋值为null的,基本数据类型无法赋值为null!
- 服务器报错,服务器并不会关闭,只是捕捉到了异常打印了日志
对象中的成员是基本数据类型除外:
只有null表示真正意义的空
这个@Param注解的作用就是,确定这个参数替换到sql语句的哪~
- 这里爆红是因为我们【还没在xml中实现】/或者【没有正确实现】这个方法(这个提示是MyBatisX这个插件提供的)
1.1.2 xml文件中去实现方法
- resultType,顾名思义,也就是返回的类型,设置为UserInfo,其实不加也行,因为可以通过方法的返回值类型来判断,不过一些版本不支持~
- 这里的返回值UserInfo类型的
返回规则:根据返回的信息,也就是一张结果表,列名对应属性名
- 结果的列名有对应的属性,赋值给属性的值
- 列有的却没有对应的属性,赋值给了空气
- 属性有的却没有对应的列,无能为力,属性只能是默认值
- 如果有多个结果对象,则返回的是个集合List
接口内就不报错了:
- 参数的处理可以使用这种方式:
${id}
,这个花括号里的id对应的就是那个参数的注解括号里面的值${}
的方式去写,原理就是简单的字符串拼接,所以如果传递的是字符串的话,还得自己传引号过去,比较麻烦- 并且这种写法,MyBatis并不是运行[一整条传参过来的自己拼接的sql字符串],而是提前定好[sql语句的模板]
- 这里不是java代码,是xml,当然不能用+号拼接
- 你也发现它不需要引号包裹,因为MyBatis规定这标签之间的值就是要执行的sql代码,不应该将写代码看成写字符串~
- 包装类对象也会自动转化到sql字符串的,也就是对应的值~
不加注解,那么它对应的就是参数的名字,但是不是所有的电脑都适合这种写法,而注解则是所有人都适合
1.1.3 测试
- 如果id为null的话,sql语句就为
select * from userinfo where id = null
,显然是错误的,那么后端就会传500的响应,代表服务器出现异常,在前端就会被ajax的error属性接受,没被success接受,那么这次请求就相当于失效了
Postman中发送请求:
前端不传id过去,Integer就为空:
1.2 传递参数的重点问题:sql注入问题
sql注入是什么?
${} 和 #{} 有什么区别?
这是MyBatis中很常见的面试题!
1.2.1 ${}方式
可见这是一种简单的替换,可以理解为字符串拼接,也就是说,字符串类型的数据我们要手动加引号:
以用户名查找用户为例:
效果:
但是如果不加双引号:
就会报错,这个是sql语句执行失败的意思~
- (我们之前的一个配置让sql执行就打印相关日志)~
并且,字符串拼接的话,就会出现影响原sql语句结构的问题,即sql注入问题:
例如传的字符串是这样的:
sql语句变为:
那么,就可能会出现危险的一种情况,就是改变原sql语句的结构,然后多加一条sql语句,对数据库进行攻击!
轻则sql语句因为非法而失效,服务器报500
重则:
- 查询你的表的其他信息!
- 加个or 1 = 1,根据其他条件查询信息,甚至暴露其他库其他表的所有信息…
- 例如,破解密码:即使输入密码是错的,加上or 1 = 1也能查询得到
- 或者 1 = ‘1’,sql语法中数字是可以跟字符串进行比较的~
- 删除/修改你的数据!
- 修改用户信息,删库删表…
总之,可以 巧妙地设计 出很多==危险的sql语句==
也就是说,${}
是**即使执行的,比较危险!**
例子:
- 用户登录密码错误仍能登录成功:
正常情况下:
恶意传参:
- 不需要密码也能登录
服务器的日志:
1.2.2 #{}方式
而这个方式对应就不会出现这个问题:
查看日志:
这也就是 之前JDBC的 ? 占位符,然后用参数【value替换】的思想,也就是username的值就是这个String类型的值,id就是这个Integer类型的值
- mybatis这一点就省去了原本的繁琐步骤,因为传过来的参数就能决定是setInt还是setString了…
底层帮我们去正确地赋值(避免了手动对字符串类型加引号的过程,也保证了不会出现添加字符串是危险sql语句而导致数据库被恶意影响的情况出生,因为只会被看成一个整体,一个普通的字符串的内容而已)~
- 所以#{}也可以完成一些奇葩字符串的保存进表里,例如
"''"'""'
,这种字符串如果拼接的话,很有可能会错(因为外部引号包裹的规定)- 但是#{}会把这个看成整体,就跟普通字符串除了值不同外没啥区别,就是一坨二进制数据,并不会有所影响
- 被视为了整体字符串~
所以,#{}
是 预编译的,比较安全!
- 可以防止sql注入!
1.2.3 必须用到${}的情景
那么${}
这么危险,为什么还存在呢?
- 这是因为一些情况下,是
#{}
无法实现的 - 例如, 传过去的字符串,是sql语句的关键字/本体的一部分!
一个典型的例子就是:
select * from userinfo order by id ${option};
在sql后面如果是desc,就是逆序;如果是asc或者啥也没有,就是顺序~
- 当然,这个也能用动态sql来解决的,但现在的情景是,传参过来的静态sql
效果:
- desc
日志:
- asc
日志:
空字符串也可以:
1.2.4 ${}和#{}的区别和注意事项
${}
可以实现的,#{}
基本都能实现,所以能用#{}
就用#{}
- 因为
${}
是及时执行的,mysql 编译完整的sql并执行,而#{}
是预编译的,可以防止sql注入的问题 - sql注入就是通过字符串替换语句中的
${}
巧妙地构造出危险的sql语句
- 因为
- 但是
${}
也是有使用场景的,在传参的值是sql语句的本体的一部分/关键字的时候,则必须使用${}
- 因为
#{}
,被字符串替换,代表的含义一定是“值”而不是关键字,跟不能被认定为sql语句本体的一部分(可以理解为带了引号) ${}
是不安全的,所以应该为${option}
提供有限个选项(可枚举的),不能有用户自主输入传递的字符串,而是选择,并且这些选项不能让sql语句最终是危险的!- 这样,最终的sql语句的执行的各种情况都是意料之中的,是可控的
- 例如刚才的例子,就只有三个选项
- “desc”
- “asc”
- “”
- 因为
2. 简单的增删改操作
对于这三个操作,语句的返回值一定是int型的“受影响行数”
我想,你现在有很多想法,试着根据刚才这些规则去实现吧!
2.1 修改操作
测试:
将username为ad的用户的state置为0:
查看数据库:
2.2 删除操作
测试:
数据库:
2.3 增加操作
目前我们的知识只能运行我们固定的去传值:
insert into userinfo (username, password) values('张三', '123456')
我的意思就是,这一段是我们写死了的:
不能根据实体类值的属性而决定,如属性值为空则添加的时候不设置,不为空则这里就会设置,因为我们现在是sql结构写死了的静态sql,所以无法实现这种情况,等待下一篇文章“动态sql”的学习!
测试:
效果:
数据库中:
补充:
- 有时候我们增加完成之后,是需要用到自增id的~
- 因为这个自增id我们不需要自己去设,自然不知道它的值(如果不查表的话)
- 也就是说我们传递的对象id属性是null值(一般情况下我们传递的是实体类对象,而不是一个个参数,即使如此,也应该有id参数)
例如:提交博客后,跳转到刚才的博客的详情页里;或者注册用户信息后,跳转到主页(省略一次登录)
- 这些功能就需要这个自增id~
写法:
- 设置useGeneratedKeys
- 意思就是“使用 自主生成的 键值对”,为true,否则框架不会在sql执行后专门返回这个键值对,我们目前知道的自增键值对就是我们的自增主键!
- 设置映射关系,keyColumn和keyProperty
- 也就是让自增主键返回到哪里
- keyColumn为列名
- keyProperty为赋值给对象的哪个属性的属性名
xml实现的方法的返回值,依旧是影响的行数,而id返回给对象
测试:
这个UserInfo对象参数,身兼数职
- 信息输入型参数
- 输出型参数
3. 模糊匹配like
根据我们目前的理解,应该写成这样:
- 即,找到用户名包含likeString的用户并返回~
测试:
命令行报错:
原因可想而知,就是因为预编译的问题,导致的语法错误
这么说的话那么“拼接sql”,及时执行的${}
可以解决咯?
- 确实可以解决
- 但是会出现sql注入的问题!
我们可以用mysql中的concat(字符串拼接)方法来有效解决这个问题!
- 可以用
#{}
了~
效果:
查看日志:
4. 常见疑问
4.1 返回对象的属性名与字段名对应不上
疑问:为什么有时候一些字段是存在值的,但是属性却是null值?
- 大概率是因为你字段名和返回对象的属性名对应不上~
这里提供几个解决方案:
其实这种对应不上的问题,在一开始写代码的时候规定下来就完事了,统一一下命名就行了,也就是写代码的时候规范一点,一般就不会出现
但是,有时候重复利用一段代码作用于另一个是实体类的时候,确实可能会出现这种情况~
- 当然也有其他的原因~
4.1.1 修改成一致
这是个简单直接的代码,就是改变定义咯, name变为username
如果用了@Data 注解,修改定义就比较简单,改属性名后方法会自动重新定义
- 但是,比较麻烦的就是,你的代码可能已经用到了Getter和Setter方法,一个个去修改有点麻烦
- 修改定义不可能修改数据库表定义的表名的!
- 貌似IDEA有个功能或者插件可以解决,可以去了解一下~
4.1.2 重命名列名
这也是比较有效的方法,利用的原理就是:
- 返回值的赋值的依据,根据的是 最终呈现出来的表
- 并不是将表转化为一个独立的对象,然后刚好是UserInfo对象,而是 可以赋值就赋值
也就是说,属性名和列名对应的上,也是可能出现属性名没有被赋值的情况的:
所以就可以通过重命名的方式来展现:
4.1.3 返回字典
通过定义resultMap的,将属性名和字段名进行手动规定的映射
resultMap的定义:
- 含义就是,返回UserInfo对象,对这个对象进行手动赋值,或者叫做手动建立映射关系
select标签定义:
效果:
- 值得一提的是,这个映射表只是起到一个 “补充” 的作用,其优先级会比**“名字对应得上就赋值,对应不上就不负责”**要高~
- 也就是说,如果你在映射表中不写其他属性和列的对应关系,也没有关系,会根据属性名和列名的对应关系进行赋值~
- 当然建议做戏做全套~
如果啥也没有,就跟之前的写法一致:
4.1.4 注解TableField
不过这个注解来自MyBatisPlus框架的,使用方法如下:
@TableField("username");
private String name;
这样就可以建立对应关系了~
不过MyBatis plus 我暂不讲解,感兴趣的同学可以去了解一下~
4.2 sql关键字作为属性名
疑问:会不会因为需要反引号去包起来属性名,才能正确的返回值?
如果是在sql语句中写字段名,那么就需要 反引号` 去包起来;
如果是作为返回值赋值给对象的属性的话,因为显示出来的列名本来就没有反引号
- 所以就不需要担心对应不上的问题~
测试表:
测试:(代码省略)
4.3 传入的参数是多个对象
写代码是根据实际情况的,传入多个自定义对象作为参数很不合理!
- 传入多个对象,可以是List对象,后面复杂sql语句可以用到
- 传入多个对象,是自定义对象,甚至是多个不一样的自定义对象,是不合理的,即使框架有解决方法,也没啥意义,因为代码不是这么写的,不考虑实际情况的!
不要制造没有意义的冲突!
- 如果要传递自定义对象,就只能单参传递,不然会导致“not found”!
我们要学的是正常的开发所用到的,而不是去研究一些奇葩的特殊情况的现象是咋样的,咋处理的,这样写会咋样,这样的不常规写法会咋样,研究这些作死一点的写法,返回的结果是咋样,这些都没意义!写正常的代码就行了!
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆!