CURD 操作,即指对数据库中实体对象的增Create、改Update、查Read、删Delete操作。
自定义 DAO 接口实现类
搭建测试环境
项目:MyBatis_CURD,在上一个项目上进行修改。
(1)修改 Dao 接口
(2)修改 Dao 实现类
Dao实现类除了已经实现的insertStudent()方法外,其它方法暂时先以空方法体的方式
实现。而具体的实现后面详细来讲。
(3)修改测试类
对 CURD 的测试,使用 JUnit 进行测试。每一个方法在测试前,首先需要创建和获取 Dao 对象。所以,将 Dao 的创建放在了 @Before 注解的方法中。
单纯插入数据
(1)修改映射文件
①id:该 SQL 语句的唯一标识,Java代码中要使用该标识。
②#{}:对指定参数类型属性值的引用。其底层是通过反射机制,调用 Student 类相关属性的 get 方法类获取值的。因为底层使用的是反射,所以这里用的是类的属性名,而不是表的字段名。
(2)修改 Dao 实现类
使用 SqlSession 对象的 insert() 方法。该方法默认返回 DB 中受影响条数。其方法原型为:insert(String id,Object obj)。
需要注意的是,执行完对 DB 的修改操作,必须要做 SqlSession 的提交。否则,修改将无法同步到 DB 中。因为使用无参的 openSession()方法已经将事务的自动提交功能给关闭了。
(3)修改测试类
由于后面要讲解查询,所以这里要先通过插入来创建一些基本的测试数据。
插入后用新的 id 初始化被插入对象
(1)修改映射文件
MySQL 中在插入语句后执行如下语句,则会输出新插入记录的 id:
映射文件的
标签中,有一个子标签 用于获取新插入记录的主键值。以下两种写法均可以完成“使用新插入记录的主键值初始化被插入的对象的功能”。
① resultType:指出获取的主键的类型。
② keyProperty:指出主键在Java 类中对应的属性名。此处会将获取的主键值直接封装到被插入的 Student 对象中,即 dao 中 insert() 方法的第二个参数对象中。
③ order:指出 id 的生成相对于 insert 语句的执行是在前还是在后。MySQL数据库表中的 id ,都是先执行 insert 语句,而后生成 id,所以需要设置为 AFTER;Oracle 数据库表中的 id,则是在 insert 执行之前生成,所以需要设置为BEFORE。当前的 MyBatis 版本,不指定 order 属性,则会根据所用 DBMS ,自动选择其值。
(2)修改 Dao 实现类
(3)修改测试类
(4)运行结果
(5)id 是何时获取到的?
这个新插入数据的 id 是什么时候获取到的呢?是在插入操作完成后由 DB 回传给 Dao 的实现类的么?
在 Dao 的实现类中 insert() 方法后,commit()方法前插入一条输出 student 对象的语句,并在 commit()方法处添加一个断点。
以调试方式运行,发现在插入操作还未提交时 student 对象已经有了 id 。这说明一个问题:无理插入操作是提交还是回滚,DB 都会为 insert 的记录分配 id,即使发生回滚,这个 id 也已经被使用。后面再插入并提交的记录数据,这个 id 已经不能再使用了,被分配的 id 是跳过这个 id 后的 id。
另外,从前面中 order 属性值的设置讲解可以知道,MySQL 在 insert 语句执行后会自动生成该新插入记录的主键值。这样值的生成之与 insert 语句是否执行有关,而与最终是否提交无关。
删除数据
(1)修改映射文件
注意,这里的动态参数 id 所赋值为 #{xxx}。这个#{xxx}表示这就是个占位符,代表 delete()方法的第二个参数。#{}中可以放任意值,不需要与 delete()方法的第二个参数值相同。
(2)修改 Dao 实现类
(3)修改测试类
修改数据
(1)修改映射文件
注意,这里的#{}中,必须要填写 update()方法所传第二个参数 student 对象的属性名称,不能随意填写。
(2)修改 Dao 实现类
(3)修改测试类
查询所有对象-返回 List
(1)修改映射文件
注意,resultType 属性并非指查询结果集最后的类型,而是每查出 DB 中的一条记录,将该记录封装成为的对象的类型。
这里的 resultType 属性使用的是全限定性类名。对于一个映射文件来说,一般情况下是对一个类的操作都放在同一个映射文件。所以,一个映射文件中出现的类一般是相同的。而每一个需要指定类名的地方若均需指定全限定性类名,会比较麻烦。所以,MyBatis 支持为类起别名的方式。
(2)注册类的别名
在主配置文件中标签后,添加 标签,指定类的别名。
① 通过指定
type:全限定性类名
alias:别名
该方式的好处是,可以指定别名为简单类名以外的其他名称。当然,弊端是,必须为每个类逐个指定,比较繁琐。
② 通过
指定
对于实体类的全限定性类名的别名指定方式,一般使用方式。这样做的好处是会将该包中所有实体类的简单类名指定为别名,写法简单方便。
但是弊端是,只能将类的简单类名作为别名。
(3)再修改映射文件
此时映射文件中就可以使用类的别名了。
(4)修改 Dao 实现类
完成 Dao 实现类的 selectAllStudent()方法。使用SqlSession 的 selectList()方法完成查询操作。该方法会将查询出的每条记录封装为指定类型对象后,再将最后的结果集封装为 List 返回。方法的原型为:List selectList(String statement)。
statement:映射文件中配置的 SQL 语句的 id 。
在写查询时,由于不是对 DB 中的数据进行修改,所以无需进行 SqlSession 的提交。但是最终 SqlSession 对象还是需要关闭的。
(4)修改测试类
查询所有对象-返回 Map
(1)修改映射文件
这里映射文件不用修改。
(2)修改 Dao 实现类
完成 Dao 实现类的 selectStudentMap()方法。使用 SqlSession 的 selectMap()方法完成查询操作。该查询方法会将查询出的每一条记录先封装为指定对象,然后再将该对象作为 value,将该对象的指定属性所对应的字段名作为 key 封装一个 Map 对象。方法原型为:Map
(3)修改测试类
(4)说明
若指定的作为 key 的属性值在 DB 中国并不唯一,则后面的记录值会覆盖掉前面的值。即指定 key 的 value 值,一定是 DB中该同名属性的最后一条记录值。
查询单个对象
(1)修改映射文件
(2)修改 Dao 实现类
使用 SqlSession 的 selectOne()方法。其会将查询的结果记录封装为一个指定类型的对象。方法原型为:Object selectOne(String statement,Object parameter)
statement:映射文件中配置的 SQL 语句的 id。
parameter:查询条件中动态参数的值。
(3)修改测试类
模糊查询
(1)修改映射文件
在进行模糊查询时,需要进行字符串的拼接。SQL 中的字符串的拼接使用的是函数concat(arg1,arg2...)。注意不能使用 Java 中的字符串连接符+。
当然,也可以写为如下形式:
以上两种形式是等效的。都是以动态参数的形式出现在 SQL 语句中的。
还可使用如下方式,只是需要注意,只能能使用${}中只能使用 value,不能使用其它。
这种方式是纯粹的字符串拼接,直接将参数拼接到了 SQL 语句中。这种方式可能会发生 SQL注入。
(2)修改 Dao 实现类
(3)修改测试类
(4)$与#的区别
A:理论区别
$与#的区别是很大的。#为占位符,而$为字符串拼接符。
字符串拼接是将参数值以硬编码的方式直接拼接到了SQL 语句中。字符串拼接就会引发两个问题:SQL注入问题与没有使用预编译所导致的执行效率低下问题。
占位符的引入,解决以上两个问题。
B:执行区别
下面的 SQL 使用的是占位符,其底层执行时,为 SQL 动态的进行了赋值。
下面的 SQL 使用的也是占位符,其底层执行时,为 SQL 动态的进行了赋值。
下面的 SQL 使用的是字符串拼接,其底层执行时,根本就没有动态参数,调用者传来的是参数以硬编码的方式直接拼接到了 SQL 语句中。
C:应用场景
一般情况下,动态参数的值是由用户输入的,则不能使用拼接符$,因为会有可能出现 SQL 注入;若动态参数的值是由系统计算生成的,则可以使用拼接符$。但是这样虽然不存在 SQL 注入的风险,但是仍然存在着执行效率的问题。
(5)回顾:SQL 注入问题
例如,有一个用于根据姓名进行查询的表单。
其后台 SQL 语句为拼接字符串:
若用户正常输入,则查出所有“张”姓学生是没有问题的。执行的sql语句为:
但,若用户有意进行SQL注入,则会输入如下:
此时表单获取的表单属性值,即name值为:张%' or1=1or'。与后台的sql语句拼接-块,则形成如下的sql:select * from student where name Jike '%张%' or 1=1 or '%'
此SQL的执行结果为,查询出所有数据记录。
但是,若在进行查询时,使用的为PreparedStatement,而非Statement,则可防止SQL 注入的产生。
在MyBatis中,使用#号占位符,则后台执行SQL使用的为PreparedStatement,将会防止SQL注入。而使用$待,则为字符串拼接,使用的为Statement,将无法防止SQL注入。
所以,此时
查看该写法的后台执行情况可知,该方式使用的为字符串拼接方式,无动态参数。
应写为:
查看该写法的后台执行情况可知,该方式使用的为动态参数赋值方式。
(6)回顾:SQL 的预编译问题
当Java代码通过JDBC的Statement向DB发送一条 SQL 语句时, DBMS会对SQL语句编译后执行。
当 Java 代码通过 JDBC 的 PreparedStatement 向 DB 发送一条 SQL 语句时,DBMS会首先编译 SQL 语句,!然后将编译好的 SQL 放入到DBMS 的数据库缓存池中再执行。当 DBMS 再次接收到对该数据库操作的 SQL 时,先从 DB 缓存池中查找该语句是否被编译过,若被编译过,则直接执行,否则先编译后将编译结果放入 DB 缓存池,再执行。
根据 Map 进行查询
mapper 中的 SQL 语句的动态参数也可以是 Map 的 Key。
(1)修改测试类
下面代码中的 Map 中存放了两个 “键-值”对,即有两个 Key。
(2)修改映射文件
(3)修改 Dao 实现类