这一篇文章我将和大家一起回顾整个MyBatis框架技术,由于内容较多,内容共分为三大部分。东西是当时我老师讲的,现在将知识点都整理下来,供大家一起讨论学习,也方便以后自己复习。如果有不对的地方欢迎大家指正。
创建数据库tedu_ums
create database tedu_ums;
创建数据表t_user
,该表中至少包含:id
、username
、password
、age
、phone
、email
,字段约束可自行设计,并保留SQL语句,下同
create table t_user (
id int auto_increment,
username varchar(16) unique not null,
password varchar(16) not null,
age int,
phone varchar(20),
email varchar(50),
primary key(id)
) default charset=utf8;
向t_user
表添加不少于10条数据
insert into t_user (
username, password, age, phone, email
) values (
'root', '12r435r34', 21, '13800138001', '[email protected]'
), (
'admin', '12hghe34', 22, '13800138002', '[email protected]'
), (
'spring', '1234', 23, '13800138003', '[email protected]'
), (
'mybatis', '12cxv34', 24, '13800138004', '[email protected]'
), (
'html', '12876734', 25, '13800138005', '[email protected]'
), (
'filter', '12132134', 26, '13800138006', '[email protected]'
), (
'jdbc', '1221434334', 27, '13800138007', '[email protected]'
), (
'java', '12r54334', 28, '13800138008', '[email protected]'
), (
'mvc', '12ferdaf34', 29, '13800138009', '[email protected]'
), (
'servlet', '12fdsaf34', 30, '13800138010', '[email protected]'
);
查询t_user
表中所有数据
select id, username, password, age, phone, email from t_user;
获取t_user
表中数据的数量
select count(id) from t_user;
获取t_user
表指定username
值为xx的数据
select id, username, password, age, phone, email from t_user where username='mvc';
获取t_user
表中年龄从高到低排列的前5条数据
select id, username, password, age, phone, email from t_user order by age desc limit 0, 5;
删除t_user
表中指定username
值为xx的数据
delete from t_user where username='mvc';
将t_user
表中年龄大于xx的数据的密码修改为xx
update t_user set password='P@ssw0rd' where age>26;
修改t_user
表中指定id
为xx的数据的电子邮箱是xx
update t_user set email='[email protected]' where id=20;
解决持久层数据处理的问题,主要是基于JDBC技术的原生代码比较繁琐,没有经过任何优化,开发甚至执行效率低下!
使用MyBatis框架时,不必关心JDBC技术如何实现,只需要编写需要执行的操作的抽象方法,例如User findById(Integer id)
,然后,为这个方法映射所需执行的SQL语句即可。
前提:在数据库系统中已经存在tedu_ums
数据库,且存在t_user
表,结构可参考昨天的作业。
步骤1:创建项目
创建时,Artifact Id
为MYBATIS
,Group Id
为cn.tedu.mybatis
。
创建过程与前次课程相同。
步骤2:创建实体类
通常,每张数据表都有一个与之对应的实体类,在实体类中,有相同数量的属性,数据类型应该保持一致,属性名称与字段名应该一一对应(在Java中的属性名称应该采用驼峰命名法,而数据库领域中并不区分大小写),所有的属性都应该是私有的,且都存在公有的SET/GET方法,整个实体类应该是实现了Serializable
接口的!
步骤3:添加依赖
org.mybatis
mybatis
3.4.6
org.mybatis
mybatis-spring
1.3.2
org.springframework
spring-jdbc
4.3.9.RELEASE
mysql
mysql-connector-java
8.0.13
commons-dbcp
commons-dbcp
1.4
junit
junit
4.9
注意:下载的新的依赖的jar可能是损坏的文件,如果保证代码正确的前提下,无法得到预期的运行效果,应该删除本地仓库中的jar包并重新更新!
步骤4:配置数据库连接
在src\main\resources
下创建db.properties
文件,用于配置数据库连接的相关信息:
# data-source
url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
driver=com.mysql.cj.jdbc.Driver
username=root
password=root
initialSize=2
maxActive=10
然后,在src\main\resources
下使用spring-dao.xml
配置:
步骤5:编写方法
使用MyBatis时,无需自行编写JDBC相关代码,只需要创建Java接口文件,并将需要执行的数据操作的抽象方法添加在接口中即可!
通常,建议按照“增 > 查 > 删 > 改”的顺序开发相关功能。
目标:向数据表中插入新的用户数据。
则创建cn.tedu.mybatis.mapper.UserMapper
接口,然后,添加“插入新的用户数据”的抽象方法:
Integer insert(User user);
使用MyBatis时,执行的增、删、改操作均返回
Integer
,表示受影响的行数。
步骤6:编写XML映射
使用MyBatis时,还需要与接口的抽象方法对应的SQL语句,该SQL语句是在XML文件中配置的!
从FTP下载somemapper.zip
,得到所需的XML文件,并重命名为UserMapper.xml
。
通常,接口文件的数量与XML映射文件的数量是相同的,是一一对应的!
映射的XML文件应该存放到src\main\resources
下,但是,项目中可能存在多个映射文件,为了便于管理,会在resources
下创建mappers
文件夹,然后把映射的XML文件放在这个文件夹中。
然后,配置该XML映射文件:
INSERT INTO t_user (
username, password,
age, phone, email
) VALUES (
#{username}, #{password},
#{age}, #{phone}, #{email}
)
步骤7:完成MyBatis的配置
步骤8:执行单元测试
public class UserMapperTestCase {
@Test
public void insert() {
AbstractApplicationContext ac
= new ClassPathXmlApplicationContext(
"spring-dao.xml");
UserMapper userMapper
= ac.getBean("userMapper", UserMapper.class);
User user = new User();
user.setUsername("mapper");
user.setPassword("1234");
user.setAge(31);
user.setPhone("13900139001");
user.setEmail("[email protected]");
Integer rows = userMapper.insert(user);
System.out.println("rows=" + rows);
ac.close();
}
}
分析需要执行的SQL语句:
select * from t_user
设计抽象方法:
List findAll();
在查询时,MyBatis会得到
List
集合类型的结果,如果存在匹配的数据,则全部封装在该List
集合中,如果没有匹配的数据,则返回的List
集合是长度为0的集合。
配置映射:
任何查询都必须指定
resultType
属性,当然,也可以替换为resultMap
,关于resultMap
,下周再讲……
分析需要执行的SQL语句:
select * from t_user where username=?
设计抽象方法:
User findByUsername(String username);
查询时,如果返回值声明为某数据类型,却不是
List
集合,MyBatis会尝试从List
集合中取出第1个元素,如果存在,则返回该元素,如果不存在(没有匹配的结果,List
集合的长度为0),则返回null
。
配置映射:
分析需要执行的SQL语句:
select count(id) from t_user
设计抽象方法:
Integer getCount();
在查询时,查询结果与返回值的类型相匹配即可!所以,如果查询的是数据的数量,查询结果是数字,所设计的抽象方法的返回值就可以是Integer。
配置映射:
目前,所设计的抽象方法只允许存在最多1个参数!
MyBatis使用简单,可以简化开发,开发者不必关注数据库编程的细节;
使用MyBatis编程主要做好:(1)设计SQL语句; (2)设计抽象方法; (3)配置映射;
所有的xx.properties
和spring-dao.xml
中的配置,需要理解,需要掌握修改配置值,不需要记住;
关于抽象方法:如果是增删改操作,返回值固定设计为Integer
,如果是查询操作,根据查询结果来决定,例如可能是List
、User
、Integer
……;方法的名称应该尽量对应所执行的数据操作,而不应该是某个业务,例如插入数据的方法名可以是insert
,或者addnew
,但是不应该使用reg
;目前,只允许使用最多1个参数,如果一定要使用多个,请封装为1个参数;
关于映射配置:根据所执行的操作选择
、
、
、节点,无论是哪个节点,必须配置
id
,取值为对应的抽象方法的名称;在SQL语句中的参数使用#{}
框住即可;
关于执行结果:如果是增删改操作,只能获得受影响的行数,可以根据该结果判断操作成功与否;如果是执行查询操作,当结果声明为List
时,无论是否查询到数据,返回有效的List
集合对象,如果没有匹配的数据,则List
的长度为0;当结果声明为某个对象,例如User
时,如果没有匹配的数据,则返回null
。
当需要获取新增的数据的id时,首先,需要在
节点中添加2个属性:
useGeneratedKeys="true"
keyProperty="id"
以上配置中,useGeneratedKeys
表示获取自增长的键(自增长的字段的值),keyProperty
表示键的属性名,即对应的类中的属性名(即该id
是User
类中的id
,并非t_user
表中的id
)!
添加以上配置之后,插入数据操作的返回值依然表示“受影响的行数”,但是,用于执行插入操作的参数对象中就会包含自动生成的id
值,例如,调用时的代码:
System.out.println("增加前:" + user);
Integer rows = userMapper.insert(user);
System.out.println("rows=" + rows);
System.out.println("增加后:" + user);
结果例如:
增加前:User [id=null, username=jsd1808, password=1234, age=31, phone=13900139008, [email protected]]
rows=1
增加后:User [id=24, username=jsd1808, password=1234, age=31, phone=13900139008, [email protected]]
可以记住:在配置时,需要配置4个属性。
功能设定:将t_user
表中年龄大于xx的数据的密码修改为xx
分析所执行的SQL语句:
update t_user set password=? where age>?
抽象方法:
Integer updatePasswordByAge(
Integer age, String password);
SQL映射:
update t_user
set
password=#{password}
where
age>#{age}
直接调用,会报告错误:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'password' not found. Available parameters are [arg1, arg0, param1, param2]
Caused by: org.apache.ibatis.binding.BindingException: Parameter 'password' not found. Available parameters are [arg1, arg0, param1, param2]
因为.java
文件在编译后变成.class
文件就会丢失参数名称,在最终执行的.class
文件中,根本就不存在名为password
或age
的参数,所以,程序会报错,可行的解决方法是把多个参数封装到一个Map
中,这样的话,此前使用的方法名称例如age
或password
就会变成Map
的key
,是一些字符串,并不会因为编译而丢失,最终运行就不会有问题,当然,每次运行时都需要自行将参数封装为Map
,操作麻烦,还存在key
拼写错误的风险,所以,MyBatis提供了@Param
注解以解决这个问题:
解决多个参数的问题:可以将多个参数封装为1个Map,或封装为1个自定义的数据类型,但是,更推荐使用@Param注解。
使用resultMap
可以解决名称不匹配的问题!例如在数据表中存在is_delete
字段,则实体类存在isDelete
属性,则数据表中使用的名称与实体类中的属性名不一致!
前置操作:
alter table t_user add column is_delete int default 0;
update t_user set is_delete=1 where id in (11,13,15);
然后,在User
类中添加private Integer isDelete;
及SET/GET方法,重新生成toString()
。
名称不匹配的问题可以通过自定义别名来解决,例如:
也就是:MyBatis的要求是“查询结果中的列名与返回值类型的属性名必须一致”,通过自定义别名就可以满足这个要求,并不需要通过
来实现!
通常,需要自定义
时,主要用于解决多表数据关联查询的问题。
例如:
create table t_department (
id int auto_increment,
name varchar(20) not null,
primary key(id)
);
insert into t_department (name) values ('UI'), ('RD'), ('TEST');
alter table t_user add column department int;
通常实体类都是与数据表一一对应的,符合设计规范,但不适用于多表的关联查询,例如当需要“查询某个部门信息的同时需要获取该部门的所有员工的信息”,则没有任何数据类型可以匹配这样的信息,为了解决这样的问题,通常会定义VO类,即Value Object类,这种类型是专用于解决实体类不满足使用需求而存在的,类的设计结构与实体类非常相似,但是,属性的设计是完全根据使用需求来决定的,例如:
public class DepartmentVO {
private Integer depId;
private String depName;
private List users;
// SET/GET方法,toString(),序列化接口
}
普通的查询无法得到以上结果,查询语句可能是:
select
t_user.id, username, password, age, phone, email, is_delete,
t_department.id AS dep_id, name
from
t_user, t_department
where
t_user.department=t_department.id
and t_department.id=?
以上查询易于理解,通俗易懂,但是,不推荐使用,更推荐使用JOIN
系列的查询语法:
SELECT
t_user.id, username, password, age, phone, email, is_delete,
t_department.id AS dep_id, name
FROM
t_user
INNER JOIN
t_department
ON
t_user.department=t_department.id
WHERE
t_department.id=?
这样的查询结果可能有好几行,
需要有效的封装到1个对象中,就必须依靠
来设计封装的规则:
配置方式可参考:
最后,应用时,与普通的数据操作相同,先添加接口与抽象方法:
public interface DepartmentMapper {
DepartmentVO findById(Integer id);
}
然后配置映射:
最终,执行查询获取的结果例如:
DepartmentVO [
depId=2,
depName=RD,
users=[
User [id=13, username=spring, password=1234, age=23, phone=13800138003, [email protected], isDelete=1, department=null],
User [id=14, username=mybatis, password=12cxv34, age=24, phone=13800138004, [email protected], isDelete=0, department=null],
User [id=17, username=jdbc, password=88888888, age=27, phone=13800138007, [email protected], isDelete=1, department=null],
User [id=21, username=mapper, password=88888888, age=31, phone=13900139001, [email protected], isDelete=1, department=null],
User [id=23, username=namespace, password=88888888, age=31, phone=13900139002, [email protected], isDelete=1, department=null]
]
]
如果提示错误TooManyResultsException
,则错误多半在于查询结果的列名与
中普通的
或
节点的column
的配置有误!也有可能存在例如2列的名称都是id
,却有多条数据的id
值不同的问题!
练习:存在学生表和班级表,学生表t_student中包括id, name, age, class_id,班级表t_class中包括id, name,最终,查询时,查某班级数据时将显示该班级所有学生的信息。涉及的类为Student、Clazz。
练习步骤1:创建2张数据表,插入一定量的数据,创建对应的实体类,创建班级的VO类:
create table t_class (
id int auto_increment,
name varchar(20),
primary key (id)
);
insert into t_class (name) values ('JSD1806'),('JSD1807'),('JSD1808');
create table t_student (
id int auto_increment,
name varchar(20),
age int,
class_id int,
primary key (id)
);
insert into t_student (name,age,class_id) values ('Mike', 20, 1), ('Tom', 21, 2), ('Terry', 21, 3), ('Jerry', 22, 2), ('Lucy', 22, 1), ('Kitty', 22, 2), ('Lily', 21, 3), ('Lilei', 20, 3), ('HanMM', 23, 3), ('XiaoMing', 21, 2);
练习步骤2:创建cn.tedu.mybatis.mapper.ClazzMapper
接口,复制得到src\main\resources\ClazzMapper.xml
映射文件,这2个文件都是空文件即可。
练习步骤3:设计SQL语句:
SELECT
t_class.id AS cls_id,
t_class.name AS cls_name,
t_student.id AS stu_id,
t_student.name AS stu_name,
age, class_id
FROM
t_class
INNER JOIN
t_student
ON
t_class.id=t_student.class_id
WHERE
t_class.id=?;
练习步骤4:抽象方法
ClazzVO findById(Integer id);
练习步骤5:配置
练习步骤6:配置
课后练习:新添加考试成绩表t_score,包括字段id(int)、stu_id(int), subject(varchar), score(int),要求最终实现:根据学生id查询出该学生的所有成绩,例如:
XX学生成绩单
学号:xx 姓名:xx
序号 | 科目 | 分数
1 Java 80
2 SQL 70
在MyBatis中,常见的占位符格式是#{参数}
,其中,也可能是参数对象中的属性,如果参数是Map
类型,还可以是Map
中的key。
使用#{}
的占位符可用于替换值,例如:
select * from t_user where username=?
即可替换以上语句中的问号(?
),在实际运行时,MyBatis会将以上SQL语句进行预编译,并后续使用#{}
替换问号(?
)。
假设获取用户列表时,排序规则不确定,可能使用的抽象方法是:
List findAllOrderedList(String orderBy);
配置的映射可能是:
调用时:
mapper.findAllOrderedList("id asc");
mapper.findAllOrderedList("id desc");
以上代码的执行效果是失败的!需要将#{}
修改为${}
,且在抽象方法中,这样的参数必须添加@Param
注解,即:
List findAllOrderedList(
@Param("orderBy") String orderBy);
然后,在调用时,就可以根据参数的不同,实现不同的排序效果!
使用${}
格式的占位符并不具备预编译的效果!它是直接拼接形成的SQL语句,例如:"select * from t_user order by" + orderBy
,如果一定使用${}
格式的占位符来表示某个值,还需要考虑单引号类似的问题,例如:select * from t_user where username='${username}'
,由于只是拼接,所以,还存在SQL注入风险!
小结
使用#{}
是预编译的(没有SQL注入风险,无需关注数据类型),使用${}
不是预编译的;
使用#{}
只能替换某个值,使用${}
可以替换SQL语句中的任何部分;
关于SQL注入,不需要太过于紧张,预编译可以从根源上杜绝,或者,在执行SQL指令之前,判断参数中是否包含单引号也可以杜绝!
通过使用${}
,可以使得SQL更加灵活,更加通用!但是,却不推荐太过于通用的SQL!因为,即使查询条件可以自由更改,但是,不同的查询条件对应不同的需求,所需的字段列表很有可能是不一样的,查询时,获取不必要的字段,就会造成不必要的资源浪费,例如,显示列表时,可能需要用户名、密码、年龄、手机、邮箱,但是,登录的查询就只需要用户名、密码即可,年龄、手机、邮箱这几项数据在登录时是不需要的,如果也查询出来,就是浪费资源!如果变量太多,又会导致不可控因素太多,容易出错!
在MyBatis的映射文件中,配置SQL语句时,可以添加例如
此类的标签,实现SQL语句的动态变化,即:参数不同,最终执行的SQL语句可能是不同的!
在使用动态SQL时,最常用的就是
和
这两种,
是用于判断的,例如:
select
*
from
t_user
where
${where}
order by
${orderBy}
关于
,主要用于循环处理SQL语句中的某个部分,例如:批量删除某些数据!它的SQL语句可能是:
delete from t_user where id in (?,?,?)
其中,in
关键字右侧的括号中的内容是不确定的,应该是由用户操作时决定的!则需要
动态的生成这个部分!
针对这个问题,设计的抽象方法可能是:
Integer deleteByIds(Integer[] ids);
配置的映射为:
delete from
t_user
where
id in (
#{id}
)
以上配置的
中,collection
表示被遍历的集合对象,当抽象方法只有1个参数时,取值为list
或array
,取决于集合对象的数据类型,当抽象方法 有多个参数时,使用@Param
注解中的名称,item
表示遍历过程中的变量名,separator
表示分隔符。
以上配置还可以调整为:
id in
#{id}
即:open
表示由
处理的SQL语句的起始部分的字符串,而close
表示结束部分的字符串。