目录
MyBatis是一个持久层框架,是一个ORM(对象关系映射)框架,用于连接数据库和程序
支持自定义SQL,存储过程,高级映射
1.1、什么是持久化?
持久化,可以理解成数据保存在数据库或者硬盘一类可以保存很长时间的设备里面,不像放在内存中那样断电就消失了。
1.2、什么是持久层?
持久层,就是在系统逻辑层面上,专注于实现数据持久化的一个相对独立的领域,是把数据保存到数据库等存储设备中。在Java领域,最常见的持久层框架有两个:Hibernate和MyBatis。
1.3、什么是ORM?
ORM,即Object-Relationl Mapping,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了 。
1.4、为什么要做持久化和ORM设计?
在目前的企业应用系统设计中,一般采用MVC的分层开发模式,即 Model(模型)、 View(视图)、Control(控制)为主要的系统架构模式。
MVC 中的 Model 包含了复杂的业务逻辑和数据逻辑,以及数据存取机制,例如 JDBC的连接、SQL生成和Statement创建、还有ResultSet结果集的读取等等。将这些复杂的业务逻辑和数据逻辑分离,可以使系统的紧耦合关系转化为松耦合关系,从而降低系统耦合度,这是持久化要做的工作。
简单来说,按通常的系统设计,使用 JDBC 操作数据库,业务处理逻辑和数据存取逻辑是混杂在一起的。一般基本都是如下几个步骤:
- 1、建立数据库连接,获得 Connection 对象。
- 2、根据用户的输入组装查询 SQL 语句。
- 3、根据 SQL 语句建立 Statement 对象 或者 PreparedStatement 对象。
- 4、用 Connection 对象执行 SQL语句,获得结果集 ResultSet 对象。
- 5、然后一条一条读取结果集 ResultSet 对象中的数据。
- 6、根据读取到的数据,按特定的业务逻辑进行计算。
- 7、根据计算得到的结果再组装更新 SQL 语句。
- 8、再使用 Connection 对象执行更新 SQL 语句,以更新数据库中的数据。
- 7、最后依次关闭各个 Statement 对象和 Connection 对象。
由上可看出,代码逻辑非常复杂,这还不包括某条语句执行失败的处理逻辑。其中的业务处理逻辑和数据存取逻辑完全混杂在一块。而一个完整的系统要包含成千上万个这样重复的而又混杂的处理过程,假如要对其中某些业务逻辑或者一些相关联的业务流程做修改,要改动的代码量将不可想象。
另一方面,假如要换数据库产品或者运行环境也可能是个不可能完成的任务。而用户的运行环境和要求却千差万别,我们不可能为每一个用户每一种运行环境设计一套一样的系统。
所以就要将一样的处理代码即业务逻辑和可能不一样的处理即数据存取逻辑分离开来,另一方面,关系型数据库中的数据基本都是以一行行的数据进行存取的,而程序运行却是一个个对象进行处理,而目前大部分数据库驱动技术(如ADO.NET、JDBC、ODBC等等)均是以行集的结果集一条条进行处理的。所以为解决这一困难,就出现 ORM 这一个对象和数据之间映射技术。
MyBatis的使用
创建MyBatis项目
1、引入依赖
MyBatis是一个框架,创建时要导入如下依赖:
MyBatis Framework构成了MyBatis框架,而安装MySQL Driver驱动是因为我现在让这个框架连接的数据库是mysql数据库
2、配置数据库连接
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/stu_teacher?charsetEncoding=utf-8&&useSSL=false
username: root
password: 123
dirver-class-name: com.mysql.cj.jdbc.Driver
#如果是MYSQL5.0之前,使用 com.mysql.jdbc.Driver
3、配置XML路径
MyBatis的SQL语句,依靠接口和实现接口方法的xml实现
XML用于保存操作数据库的具体SQL语句
多个SQL语句对应多个xml文件,项目会比较多,一般会在resources下创建包,然后在配置文件内写出文件路径
#配置文件保存路径
mybatis:
mapper-locations: classpath:mybatis/**Mapper.xml
这时,所有SQL的xml文件,都要存储在resources的mybatis目录下,同时命名为xxx.Mapple.xml
使用MyBatis项目
MyBatis操作数据库,使用接口+xml文件实现
接口里面定义方法的声明,xml文件实现接口中的方法;这两者配合形成可执行的SQL语句并且执行,结果会映射到对象中
1、查看表结构
2、根据表结构,创建对应类
@Data
public class Student {
private int id;
private String name;
private int class_id;
}
3、创建接口
@Mapper//让这个接口被MyBatis框架接收、
public interface SelectByIdMapper {
public Student getStudentById(@Param("id") Integer id);//根据id获取Student对象
//Param 表示在xml中对应获取的参数名称也是id
}
4、xml文件实现接口的方法(这个文件的命名,和配置文件保持一致)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.SelectByIdMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<select id="getStudentById" resultType="com.example.demo.Model.Student">
<!-- 这个id,表示实现StudentMapper接口的哪一个方法-->
<!-- resultType 表示方法的返回类型-->
select * from student where id=${id}
<!-- 这里写具体的执行语句-->
</select>
</mapper>
5、测试代码
1.Model层,存放我们的实体类,与数据库中的属性值保持一致。
2.Dao层,Mapper层也有称,对数据库进行数据持久化操作,直接针对数据库操作的。
3.Service层。存放业务逻辑处理,可以调用Dao层
4.Controller层,接受前端的传来的参数,通过调用service层,来实现数据的业务逻辑,然后再返回。
5.View层。就是前端界面
Controll层,接收前端数据,调用Service层处理业务逻辑
@Controller
public class StudentControl {
@Autowired//属性注入
private StudentService studentService;
@RequestMapping("/getstudentbyid")
@ResponseBody
public Student getStudentById(Integer id) {
if (id == null)
return null;
return studentService.getStudentById(id);
}
}
Service层:处理业务逻辑,调用数据库
@Service
public class StudentService {
//使用StudentMapper对象 属性注入
@Autowired
private SelectByIdMapper selectByIdMapper;
public Student getStudentById(Integer id) {
return selectByIdMapper.getStudentById(id);
}
}
Mapper层:执行数据库操作
@Mapper//让这个接口被MyBatis框架接收、
public interface SelectByIdMapper {
public Student getStudentById(@Param("id") Integer id);//根据id获取Student对象
//Param 表示在xml中对应获取的参数名称也是id
}
单元测试
在执行检查时,我们要通过tomcat访问,可以通过单元测试来实现项目检验
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证,SpringBoot项目基于方法进行单元检测
单元检测的优点:
1、不用启动tomcat
2、如果代码中途改变,项目打包时就会报错
3、检验数据库操作时,可以不污染数据库
SpringBoot单元测试
SpringBoot项目创建时,就会自动添加Spring-boot-test单元测试框架,而这个框架主要依靠于另一个框架JUnit
1、生成单元测试类
鼠标右键-》generate-》Test-》选择方法
就会基于Test框架生成类,类里面是单元测试的方法
class SelectByIdMapperTest {
@Test
void getStudentById() {
}
}
2、为了让这个 单元测试类可以在SpringBoot项目运行,需要将这个类在Spring框架启动时,注册入框架,就要给类增加@SpringBootTest注解
3、实现具体方法
@SpringBootTest
class SelectByIdMapperTest {
//根据属性注入 获取最终执行的方法
@Autowired
private StudentControl studentControl;
@Test
void getStudentById() {
Student student = studentControl.getStudentById(1);//进行数据库查找
Assertions.assertNotNull(student);
//断言 这个表示认定查询结果不为空 如果断言为假 测试不通过 后序代码也不会执行
}
}
直接运行测试类,可以看到运行结果
如果不想污染数据库,给方法加@Transactional注解,可以开启一个事务,会在最后进行事务回滚,撤回修改操作,不污染数据库
实现增删改查
增加记录(默认返回影响条数)
1、Mapper层的接口
public interface AddStudentMapper {
@Mapper
public int addStudent(@Param("name") String student_name, @Param("class_id") Integer class_id);
}
2、xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.AddStudentMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<insert id="addStudent">
<!-- id绑定方法名-->
insert student values (null,#{name},#{class_id})
</insert>
</mapper>
3、Service层
@Service
public class AddStudentService {
@Autowired
private AddStudentMapper addStudentMapper;
public int addStudent(String name, Integer class_id){
return addStudentMapper.addStudent(name,class_id);
}
}
4、Control层
@Controller
public class AddStudentControl {
@Autowired
private AddStudentService addStudentService;
@RequestMapping("/addstudent")
@ResponseBody
public int addStudent(String name, Integer class_id) {
if (name != null && class_id != null)
return addStudentService.addStudent(name, class_id);
return -1;
}
}
5、单元测试
@SpringBootTest
class AddStudentMapperTest {
@Autowired
private AddStudentControl addStudentControl;
@Test
void addStudent() {
int ret= addStudentControl.addStudent("张三",1);
Assertions.assertEquals(1,ret);
}
}
默认情况下,数据库返回的是修改记录的条数
增加记录(返回自增主键)
1、Mapper层
@Mapper
public interface GetPrimaryKeyMapper {
public int getPrimaryKeyMapper( @Param("student") Student student);
}
2、xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.GetPrimaryKeyMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<insert id="getPrimaryKeyMapper" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
<!-- useGeneratedKeys表示开启主键,从主键获取值-->
<!-- keyColumn表示主键在数据库中的列名-->
<!-- keyProperty表示框架将获取到的主键值 填充到对象的哪一个属性上-->
<!-- id绑定方法名-->
insert student (name,class_id) values (#{student.name},#{student.class_id})
<!-- 虽然参数的是对象,但是mybatis会优化,使直接通过类的参数获取对象值-->
<!-- 也就是在接口不使用parma注解时,可以直接传入name,class_id-->
<!-- 如果使用了parma注解,就要通过对象名称获取参数了-->
</insert>
</mapper>
3、单元测试
@SpringBootTest
class GetPrimaryKeyMapperTest {
@Autowired
private GetPrimaryKeyMapper getPrimaryKeyMapper;
@Test
void getPrimaryKeyMapper() {
Student student = new Student();
student.setId(null);//这里将Student的id给为Integer类型
student.setName("张无忌");
student.setClass_id(2);
int ret = getPrimaryKeyMapper.getPrimaryKeyMapper(student);
System.out.println("id= "+student.getId());
System.out.println("ret= "+ret);
}
}
默认返回受影响的行数,而注解会填充到对象内
删除记录
1、Mapper层
@Mapper
public interface DeleteStudentMapper {
public int delete(@Param("id") Integer id);
}
2、xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.DeleteStudentMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<delete id="delete" >
<!-- id绑定方法名-->
delete from student where id=#{id}
</delete>
</mapper>
3、单元测试
@SpringBootTest
class UpdateStudentMapperTest {
@Autowired
private UpdateStudentMapper updateStudentMapper;
@Test
//@Transactional
void updateStudent() {
updateStudentMapper.updateStudent(1, "张三", 3);
}
}
修改记录
1、Mapper层
@Mapper
public interface UpdateStudentMapper {
public void updateStudent(@Param("id") Integer id,@Param("name") String name,@Param("class_id") Integer class_id);
}
2、xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.UpdateStudentMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<update id="updateStudent" >
<!-- 这个id,表示实现接口的哪一个方法-->
update student set name=#{name},class_id=#{class_id} where id=#{id}
</update>
</mapper>
3、单元测试
@SpringBootTest
class UpdateStudentMapperTest {
@Autowired
private UpdateStudentMapper updateStudentMapper;
@Test
//@Transactional
void updateStudent() {
updateStudentMapper.updateStudent(1, "张三", 3);
}
}
xml文件获取参数的两种符号
在xml文件中,可以使用#{}和${}来获取参数
Mybatis在对#{}进行预处理时,会把#{}处理成占位符?,实际执行的时候才会把参数替换进去,相当于jdbc的PreparedStatement。
#{}将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如果传入的值是1111,那么解析成sql时的值为 “1111”, 如果传入的值是id,则传入 “id”.
${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会直接将变量值替换进去,这样就有可能会发生sql注入的风险。
${}将传入的数据直接替换,如果传入的值是1111,那么解析成sql时的值为 1111, 如果传入的值是id,则传入 id.
1、${}直接替换,不会给字符串增加'',导致数据插入异常
2、那么如果是传入了关键字,比如desc,这时#{}会给字符串加单引号'desc',${}不会加单引号,这时#{}就会将关键字当作普通字符串
order by "desc"->SQL语句报错
所以,在传入关键字时,不可以使用#{},在传入普通字符串时,不可以使用${}
3、SQL注入
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
正常来说,${}不会给参数增加"",那么我们可以在xml文件内手动写入''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.LoginMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<select id="login" resultType="com.example.demo.Model.Student">
<!-- 这个id,表示实现StudentMapper接口的哪一个方法-->
<!-- resultType 表示方法的返回类型-->
select * from student where id='${id}' and name='${name}'
<!-- 这里写具体的执行语句-->
</select>
</mapper>
在数据库中,select 1="1" 的结果是1
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.LoginMapper">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<select id="login" resultType="com.example.demo.Model.Student">
<!-- 这个id,表示实现StudentMapper接口的哪一个方法-->
<!-- resultType 表示方法的返回类型-->
select * from student where id=${id} and name='${name}'
<!-- 这里写具体的执行语句-->
</select>
</mapper>
@SpringBootTest
class LoginMapperTest {
@Autowired
private LoginMapper loginMapper;
@Test
void login() {
Student student=loginMapper.login(1,"' or 1='1");
}
}
也即是说:
根据id拿到单个信息
这样非常不可以的,如果这个是账号和密码,可以不需要用户密码,拿到用户在数据库的所有信息,也就产生了SQL注入
like模糊匹配
要想传的参数是‘%三’,那么#{}就可以,因为会额外加引号
@Mapper
public interface LoginMapper {
public Student login(@Param("id") Integer id, @Param("name") String name);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.LikeSearch">
<!-->namespace 绑定xml文件实现的接口 包名+类名 <-->
<select id="likeSearch" resultType="com.example.demo.Model.Student">
<!-- 这个id,表示实现StudentMapper接口的哪一个方法-->
<!-- resultType 表示方法的返回类型-->
select * from student where name like #{name};
<!-- 这里写具体的执行语句-->
</select>
</mapper>
@SpringBootTest
class LikeSearchTest {
@Autowired
private LikeSearch likeSearch;
@Test
void likeSearch() {
List<Student>list=likeSearch.likeSearch("张%");
}
}
可以使用内置函数concat来拼接,这样只传入参数“张”即可
<select id="likeSearch" resultType="com.example.demo.Model.Student">
<!-- 这个id,表示实现StudentMapper接口的哪一个方法-->
<!-- resultType 表示方法的返回类型-->
select * from student where name like concat('%',#{name},'%');
<!-- 这里写具体的执行语句-->
</select>