文章目录
- 一、SpringMVC
- - 1.SpringMVC(Spring Model View Controller) 框架介绍
- - - 1.概述
- - - 2.MVC模型
- - - 3.工作原理
- - 2.创建Module
- - 3.入门案例:展示汽车数据
- 需求
- 创建Maven module
- 创建RunApp.java
- Car.java
- CarController.java
- 测试
- - 4.处理请求参数
- - - 1.概述
- - - 2.get 方式
- - - 3.restful 方式(推荐)
- - - 4.post 方式
- - - - 1.准备form表单
- - - - 2.准备Student类
- - - - 2.准备StudentController类
- - - - 3.利用jdbc把接收到的参数入库
- - - 5.浏览器常见报错的问题
- 二、Spring
- - 1.概述
- - - 1.时代变迁
- - - 2.Spring的野心
- - - 3.官网
- - - 4.框架组成
- - - 5.核心概念
- - - 6.三大核心组件的关系
- - - 7.主要jar组成
- - 2.Spring框架两大核心:IOC和DI
- - - 1.概念
- - - 2.IOC
- - - 3.DI
- - - 4.IOC实现的两种方式
- - - - 1.IOC的XML方式
- - - - - 1.创建Maven Module
- - - - - 2.导入jar包(不需要了,被Spring Boot整合了)
- - - - - 3.创建Hello类
- - - - - 4.创建配置文件(spring-config.xml)
- - - - - 5.测试
- - - - 2.IOC的注解方式(@Component)
- - - - - 1.创建User类
- - - - - 2.修改配置文件
- - - - - 3.测试
- - - - - 4.总结
- - - 5.DI依赖注入
- - - - 1.普通测试
- - - - - 1.创建Dept类
- - - - - 2.创建Emp类
- - - - - 3.测试
- - - - 2.使用spring实现DI
- - - - - 1.创建Teacher类
- - - - - 2.创建Student类
- - - - - 3.创建配置文件(spring-config.xml)
- - - - - 4.测试
- - - 小结
- 面试:IOC和DI
- 自动装配
- - - 6.AOP面向切面编程
- - - - 1.概念
- - - - 2.通知的执行顺序
- - - - 3.多切面执行顺序
- - - - 4.步骤
- - 3.Spring的扩展
- - - 1.模拟SpringIOC的实现
- - - - 1.概念
- - - - 2.开发步骤
- - - - 3.Bean.java
- - - - 4.SpringContext.java
- - - - 5.Hello.java
- - - - 6.TestMyIoC.java
- - - 2.模拟SpringDI的底层实现
- - - - 1.Student.java
- - - - 2.Teacher.java
- - - - 3.自定义注解
- - - - 4.TestMyDI.java
- - - 3.Spring整合SpringMVC的项目实战
- - - - 1.创建网页
- - - - 1.创建RunApp类,启动服务器
- - - - 2.创建Car类
- - - - 3.创建CarController类
- - - - 5.创建CarService接口
- - - - 6.创建CarServiceImpl实现类
- - - - 7.测试
- - - - 8.总结
- - 4.Spring自动装配过程
- - - 1.Spring"容器"
- - - 2.依赖注入的原理
- 三、MyBatis
- - 1.MyBatis持久层框架
- - - 1.概述
- - - 2.内部组件结构图
- - 2.MyBatis:XML映射方式(入门案例)
- - - 1.准备数据库、表
- - - 2.修改pom.xml,添加mybatis和jdbc的jar包依赖
- - - 3.封装pojo类
- - - 4.创建核心配置文件mybatis-config.xml
- - - 5.创建映射文件UserMapper.xml,写SQL
- - - 6.在核心配置文件mybatis-config.xml中引入映射文件UserMapper.xml
- - - 7.创建测试类Test1测试
- - - 8.总结
- - 3.参数分析
- - - 1.别名:alias
- - - 2.参数值:paramterType
- - - 3.返回值:resultType
- - - 4.返回值:resultMap
- - - 5.#和$的区别
- - - 6.SQL中有特殊字符
- - 4.动态SQL
- - - 1.概述
- - - 2.sql和include
- - - 3.if
- - - 4.where
- - - 5.set
- - - 6.foreach
- - 5.MyBatis:接口映射方式
- - - 1.概述
- - - 2.修改pom.xml,添加mybatis和jdbc的jar包依赖
- - - 3.封装pojo类
- - - 4.创建接口DeptMapper.java
- - - 5.创建核心配置文件mybatis-config.xml
- - - 6.创建DeptMapper.xml,写SQL
- - - 7.在核心配置文件mybatis-config.xml中引入映射文件DeptMapper.xml
- - - 8.创建测试类Test1测试
- - - 9.总结
- - 6.ResultMap简单使用
- - - 1.概述
- - - 2.测试
- - - - 1.创建表,添加了记录
- - - - 2.修改pom.xml,添加mybatis的jar包
- - - - 3.创建核心配置文件
- - - - 4.创建映射文件
- - - - 5.创建接口类
- - - - 6.创建pojo类
- - - - 7.测试
- - - 3.自动匹配规范驼峰规则
- - - - 1.第一步:在核心配置文件中开启驼峰规则:
- - - - 2.第二步:在映射文件中的resultMap标签中添加新属性 autoMapping=“true”
- - 7.缓存机制
- - - 1.概述
- - 8.扩展
- - - 1.JDBC和MyBatis的区别?
- - - 2.XML和接口方式的区别?
- - - 3.接口方式怎么找到xml执行的?
一、SpringMVC
- 1.SpringMVC(Spring Model View Controller) 框架介绍
- - 1.概述
MVC思想说明:
SpringMVC主要作用:
1.接受请求(解析请求参数)
2.做出响应
框架: 是一个结构,框架提供了很多的类,由框架控制每个类调用的过程流程
SSM框架里,第一个S就是指SpringMVC,是一个框架.
是Spring框架的一个后续产品,遵循了MVC的设计模式,保证了程序间的松耦合。MVC的设计模式:M是Model模型,用来封装数据V是View视图,用来展示数据C是Controller控制器,用来控制浏览器如何请求,做出数据响应
好处:提高代码的复用性,松耦合
Spring MVC属于Spring框架(SpringFrameWork)的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1(现在一 般不用),Struts2(一般老项目使用)等。
SpringMVC就是基于MVC设计模式来实现的。
我们的POJO就是Model层,我们的JSP就是视图层,我们的Controller就是控制层。
现在主流基于SSM三大框架开发都是在MVC上继续演化,又分为持久层DAO,业务层Service,控制层Controller。持久层用来和数据库读写ORM,业务层用来处理复杂的业务逻辑,控制层用来处理MVC的控制。
- - 2.MVC模型
用来进行分层的结构,这样代码分离结构清晰,各层代码,各司其职,易于开发大型项目。
MVC(Model模型、View视图、Controller控制层),将软件进行分层达到松耦合的效果。
通用的软件编程思想, 在MVC设计模式中认为, 任何软件都可以分三层:控制层(Controller)、数据处理模型(Model)、负责展示数据的视图(View)。
在MVC设计思想中要求一个符合MVC设计思想的软件应该保证上面这三部分相互独立,互不干扰,每一个部分只负责自己擅长的部分。如果某一个模块发生变化,应该尽量做到不影响其他两个模块。提高代码的可读性,实现程序间的松耦合、提高代码复用性。
- - 3.工作原理
1.前端控制器DispatcherServlet:当浏览器发送请求成功后,充当着调度者的角色,负责调度每个组件。
2.处理器映射器HandlerMapping:根据请求的URL路径,找到能处理的类名和方法名url:http://localhost:8080/hi,找到HelloController类里的hi()
3.处理器适配器:HandlerAdaptor:正式开始处理业务,并把返回结果的结果集交给DispatcherServlet
4.视图解析器ViewResolver:找到正确的,能展示数据的视图,准备展示数据
5.视图渲染View:展示数据
过程简单描述: 😗
客户端发送请求 - > 前端控制器 DispatcherServlet 接受客户端请求 - > 找到处理器映射 HandlerMapping 解析请求对应的 Handler - > HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑 - > 处理器返回一个模型视图 ModelAndView - > 视图解析器进行解析 - > 返回一个视图对象 - > 前端控制器 DispatcherServlet 渲染数据(Moder) - > 将得到视图对象返回给用户
更具体一些的描述: 😗
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
- 2.创建Module
选中Project - 右键 - New - Module - 选择Maven - next - 输入Module的名字 - next - finish
- 3.入门案例:展示汽车数据
POJO(Plain Ordinary Java Object)译为:简单的java对象,也就是普通JavaBeans,可以看做是作为支持业务逻辑的协助类。POJO有一些private的参数作为对象的属性。然后针对每个参数定义了get和set方法作为访问的接口。
1.导入jar包(被Spring Boot整合好了)
2.准备一个启动类RunApp,用来启动服务器
3.准备一个类,补充方法
访问链接: http://localhost:8080/car/get
得到JSON数据:123
4.准备一个网页
需求
访问链接: http://localhost:8080/car/
得到JSON数据: {“id”:718,“name”:“保时捷”,“type”:“Cayman T”,“color”:“红色”,“price”:641000.0}
创建Maven module
详情见上
创建RunApp.java
package cn.tedu.mvc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;//标记着这是一个springboot的启动类
@SpringBootApplication
public class RunApp{public static void main(String[] args) {SpringApplication.run(RunApp.class,args);//运行当前类}
}
Car.java
package cn.tedu.mvc;// Model对象,也称为POJO
public class Car {private int id;private String name;private String type;private String color;private double price;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public Car() { }public Car(int id, String name, String type, String color, double price) {this.id = id;this.name = name;this.type = type;this.color = color;this.price = price;} @Overridepublic String toString() {return "Car{" +"id=" + id +", name='" + name + '\'' +", type='" + type + '\'' +", color='" + color + '\'' +", price=" + price +'}';}
}
CarController.java
package cn.tedu.mvc;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;//完成springmvc的角色,接受请求和给出响应
//是MVC设计模式里的C控制器,接受请求和给出响应@RestController //标记这个类是controller是一个控制器+接受请求+响应JSON串
@RequestMapping("car") //规定了url怎么访问这个类
public class CarController {//访问链接: http://localhost:8080/car/show@RequestMapping("show") //规定了url怎么访问这个方法public String show(){return "123";}//访问链接: http://localhost:8080/car/show2@RequestMapping("show2")public int show2(){return 1010101;}//访问数组@RequestMapping("arr")public int[] arr(){int[] a = {1,2,3,4};return a;}/*** 访问链接: http://localhost:8080/car/get* SpringMVC框架除了能返回字符串、整数以外,还能返回对象信息* {* "id":718,"name":"保时捷","type":"Cayman T","color":"红色","price":641000.0* }* 创建Car类,封装属性 -- 准备new Car*/@RequestMapping("get")public Object get(){
// Car c = new Car();
// // 给客户准备数据
// c.setId(718);
// c.setName("保时捷");
// c.setType("Cayman T");
// c.setColor("红色");
// c.setPrice(641000.0);Car c = new Car(718,"保时捷","Cayman T","红色",641000.0);return c;// 把对象信息 变成JSON字符串在浏览器展示}
}
测试
浏览器访问:http://localhost:8080/car/get
执行结果:
- 4.处理请求参数
SpringMVC参数请求处理
- - 1.概述
当客户端打开浏览器要访问服务器时,可能会带着一些http请求参数过来。
这时,服务器需要获取http参数进行业务处理,如何处理http请求并获取参数呢?
请求方式8种,常见的就是 get 和 post
restful风格的数据,用来简化了get的写法
http://localhost:8080/car/insert?id=1&name=张三&age=18
http://localhost:8080/car/insert/1/张三/18
- - 2.get 方式
向特定的资源发出请求,并返回实体。有固定的写法,而且数据有最大长度,超出就不行
例如:
http://localhost:8080/car/insert?id=1&name=张三&age=18
测试:
package cn.tedu.mvc;import org.junit.Test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;// Springmvc解析get请求参数@RestController // 接受请求+做出响应
@RequestMapping("get") // 规定了浏览器的访问方式
public class GetController {// 测试:http://localhost:8080/get/param?id=1@RequestMapping("param")public String param(int id){return "您的请求参数里id="+id;}// 测试:http://localhost:8080/get/param2?id=1&name=张三@RequestMapping("param2")public String param1(int id,String name){return "您的请求参数里id="+id+",name="+name;}// http://localhost:8080/get/param3?id=1&name=张三&age=18@RequestMapping("param3")public String param2(int id,String name,int age){return "您的请求参数里id="+id+",name="+name+",age="+age;}// http://localhost:8080/get/param4?id=1&name=BMW&price=9.9&type=X6&color=red@RequestMapping("param4")public Car param4(Car c){return c;}@Test //单元测试方法public void get1(){String url = "http://localhost:8080/car/insert?id=1&name=张三&age=18";String[] a = url.split("\\?")[1].split("&");for(String s : a){String data = s.split("=")[1];System.out.println(data);}}
}
- - 3.restful 方式(推荐)
为了简化GET请求的写法,可以使用RESTFul方式,用法:
-
需要使用注解@PathVariable来获取请求路径中的参数值,@PathVariable用来绑定值
-
通过{???}获取路径中传递来的值
-
以前GET的访问方式即将被简化成:
http://localhost:8080/car/get1/100/张三
后端程序:
1.创建RunApp启动类
package cn.tedu;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RunApp {public static void main(String[] args) {SpringApplication.run(RunApp.class,args);}
}
2.创建CarController类
package cn.tedu.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;//指定url地址栏的写法
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;//接受请求+做出响应//@Controller
//@ResponseBody
@RestController
@RequestMapping("car")
public class CarController {//解析get传递的参数//http://localhost:8080/car/get?id=100&name=张三/*** 注意1:参数列表的参数类型。最好使用引用类型,* 如果浏览器没有传参数过来就用默认值,但是用基本类型会抛异常*/@RequestMapping("get")
// public String get(int id,String name){public String get(Integer id,String name){return id+name;}// 解析restful传递的参数:简化了get方式参数的写法// http://localhost:8080/car/get2/100/张三@RequestMapping("get2/{id}/{name}")//{x} -- 通过{}获取访问路径中携带的参数,并且交给变量x保存//@PathVariable -- 获取{}中间变量的值public String get2(@PathVariable Integer id,@PathVariable String name){return id+name;}//http://localhost:8080/car/get3/1/BMW/9.9/X6/red//注意:浏览器输入的时候注意顺序,否则会报400类型匹配错误@RequestMapping("get3/{id}/{name}/{color}/{price}")public String get3(@PathVariable Integer id,@PathVariable String name,@PathVariable String color,@PathVariable Double price){return id+name+color+price;}
}
前端程序:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>前后端关联</title></head><body><a href="http://localhost:8080/car/get?id=100&name=张三">解析get的参数</a><a href="http://localhost:8080/car/get2/100/张三">解析restful风格的参数</a><a href="http://localhost:8080/car/get3/100/张三/red/9.9">解析restful风格的参数</a></body>
</html>
浏览器测试:
- - 4.post 方式
- - - 1.准备form表单
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title></title><style>body{font-size: 20px;background-color: #00FFFF;}/* 输入框 */.a{width: 330px;height: 30px;font-size: 15px;}/* 保存按钮 */input[type="submit"]{width: 50px;height: 30px;font-size: 15px;background-color: blue;color: white;border-color: blue;}/* 取消按钮 */input[type="reset"]{width: 50px;height: 30px;font-size: 15px;background-color: pink; color: white;border-color: pink;}</style></head><body><!-- 利用表单,向服务器发送数据,默认是get提交,通过method属性修改提交方式 action属性,指定提交的位置--><form action="http://localhost:8080/stu/add" method="post"><table><h2>学生信息管理系统MIS</h2><tr><td>姓名:</td> </tr><tr><td><input class="a" type="text" placeholder="请输入姓名..." name="name"/></td></tr><tr><td>年龄:</td> </tr><tr><td><input class="a" type="number" placeholder="请输入年龄..." name="age"/></td></tr><tr><td>性别:(单选框)<input type="radio" name="sex" checked="checked" value="1"/> 男<input type="radio" name="sex" value="0"/> 女</td></tr><tr><td>爱好:(多选)<input type="checkbox" name="hobby" checked="checked" value="ppq"/> 乒乓球<input type="checkbox" name="hobby" value="ps"/> 爬山<input type="checkbox" name="hobby" value="cg"/> 唱歌</td></tr><tr><td>学历:(下拉框)<select name="edu"><option value="1">本科</option><option value="2">专科</option><option value="3">博士</option></select></td></tr><tr><td>入学日期:</td></tr><tr><td><input type="date" name="intime"/></td></tr><tr><td><input type="submit" value="保存"/><input type="reset" value="取消"/></td></tr></table></form></body>
</html>
- - - 2.准备Student类
注意:: 日期属性要加注解,@DateTimeFormat(pattern="yyyy-MM-dd"),否则400错误
package cn.tedu.pojo;import org.springframework.format.annotation.DateTimeFormat;import java.util.Arrays;
import java.util.Date;//这是Model层,用来封装数据,就是一个pojo(封装的属性+get/set)
public class Student {//属性(成员变量): 变量类型 变量名//提交数据的类型 页面上name属性的值private String name;private Integer age;//使用Integer会避免一些异常private Integer sex;private String[] hobby;private Integer edu;//浏览器上提交的日期默认是String类型,2021/8/12,报错400//@DateTimeFormat 把String的日期转成Date日期//pattern属性规定了日期的格式y表示年M表示月d表示日@DateTimeFormat(pattern = "yyyy-MM-dd")private Date intime;//get set toString方法public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Integer getSex() {return sex;}public void setSex(Integer sex) {this.sex = sex;}public String[] getHobby() {return hobby;}public void setHobby(String[] hobby) {this.hobby = hobby;}public Integer getEdu() {return edu;}public void setEdu(Integer edu) {this.edu = edu;}public Date getIntime() {return intime;}public void setIntime(Date intime) {this.intime = intime;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", sex=" + sex +", hobby=" + Arrays.toString(hobby) +", edu=" + edu +", intime=" + intime +'}';}
}
- - - 2.准备StudentController类
package cn.tedu.controller;import cn.tedu.pojo.Student;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;//这是controller层,控制层,用来接收请求和给出响应
@RestController
@RequestMapping("stu")
public class StudentController {@RequestMapping("add")public Object add(Student s){//TODO 利用jdbc,实现入库insertreturn s;}
}
- - - 3.利用jdbc把接收到的参数入库
1.操作cgb2106的库,创建tb_student表(参考Student类)
USE cgb2106;
CREATE TABLE tb_student(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(50),age INT,sex INT,hobby VARCHAR(100),edu INT,intime DATE
);
2.修改pom.xml文件,添加jdbc的jar包的坐标
<!-- 添加jdbc的jar包依赖 --><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency></dependencies>
3.写jdbc的代码
package cn.tedu.controller;import cn.tedu.pojo.Student;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Arrays;//这是controller层,控制层,用来接收请求和给出响应
@RestController
@RequestMapping("stu")
public class StudentController {@RequestMapping("add")public Object add(Student s) throws Exception {//TODO 利用jdbc,实现入库insert//1.注册驱动Class<?> clazz = Class.forName("com.mysql.jdbc.Driver");//2.获取连接String url = "jdbc:mysql://localhost:3306/cgb2106";Connection conn = DriverManager.getConnection(url, "root", "root");//3.获取传输器String sql = "insert into tb_student (id,name,age,sex,hobby,edu,intime) values (null,?,?,?,?,?,?)";PreparedStatement ps = conn.prepareStatement(sql);//4.给SQL设置参数 -- (给SQL语句中的?赋值)ps.setObject(1,s.getName());ps.setObject(2,s.getAge());ps.setObject(3,s.getSex());//s.getHobby()得到一个数组,不能直接存入数据库,需要变成串入库ps.setObject(4, Arrays.toString(s.getHobby()));ps.setObject(5,s.getEdu());ps.setObject(6,s.getIntime());//5.执行SQLps.executeUpdate();//执行增删改的SQL语句//ps.executeQuery();//执行查询的SQL语句System.out.println("数据插入成功!");//6.释放资源conn.close();ps.close();return s;}
}
4.测试:
5.总结
6.日期数据的处理
把页面上的intime日期数据,交给后台处理。由于页面的数据都当做String类型处理,所以交给后台处理时,会抛出400错误。需要使用注解进行类型转换,并指定日期格式:
//页面报400 IllegalArgumentException: String->Date@DateTimeFormat(pattern="yyyy-MM-dd";)
private java.util.Date intime;public Date getIntime() {return intime;
}public void setIntime(Date intime) {this.intime= intime;
}
- - 5.浏览器常见报错的问题
浏览器报错:400 -- 错误的请求(参数类型不匹配)Controller类里的方法:public void add(int a){ }URL的方法:http://localhost:8080/add?a=jack404 -- Not Found,访问路径不对,无法找到文件(资源)500 -- 服务器内部出错,IDEA已经抛出异常了505 -- HTTP版本不受支持
二、Spring
- 1.概述
功能非常丰富,核心功能是:IOC DI AOP
IOC:是控制反转,指 把创建对象的过程交给Spring(把对象的信息交给Spring管理)
DI:是依赖注入,指 把对象间的依赖关系 自动维护
AOP:是补充了OOP的不足
- - 1.时代变迁
原始时代我们用一个jsp搞定一切,但如此开发大型项目时我们遇到了问题,前端美化的代码和后端的代码交织,代码中又有html、js、css样式,又有业务逻辑和数据库访问代码,杂乱不清晰,美工和开发打架。
于是mvc分层架构封建时代出现,把我们写代码的地方硬性分成3个地方,Model层封装数据,View视图层页面展现,Controller控制层访问转发。代码之间的耦合度降低。概念有了,需要实际干活的。于是随着mvc分层概念的深入人心,业界涌现出很多实现框架,最著名的莫过于struts1和struts2。随着前端框架的成熟,后端框架也应运而生如:dbutils、jdbcTemplate、hibernate、ibatis、mybatis。
一个前端WEB层框架有了,一个后端数据库层访问框架有了,那中间呢?谁来胜任?spring破石而出。
- - 2.Spring的野心
了解了历史,有个问题值得我们去深思?spring到底想干什么?
它想把全球最好的技术组合到一起,为企业提供高质量的企业级的应用程序框架,减轻开发者开发的难度,减少重复的代码。
我们拿经典的框架来举例子。
struts2作为WEB框架深受企业爱戴,它会自己管理action,来创建其实例,这样在程序中就可以访问action的资源。hibernate作为持久层优秀的框架,它也自己管理持久对象。可以看到,各个诸侯都自己管理对象,而要想让它们对象复用,那真是繁琐。前面就有失败者WebService,为了管理不同的开发语言的对象而层层包装转换,辛苦制定的规则,还借着J2EE规范之名,也推广不开。
如何破局呢?在java的世界里最重要的无疑就是对象的生命周期管理。于是spring以此为切入点,实现自己的统治。官宣所有对象由我来管理,struts2你不再管理对象,由我来管理,你要用从我这拿。hibernate你也不再管理对象,由我来管理,你要用从我这拿。开发一个完整的系统有四个核心,WEB层支持、业务逻辑层、持久层支持、事务支持。它们只能完成一部分工作,不是一个整体解决方案。于是一个经典的三层框架诞生SSH (Strut2+Spring+Hibernate)。
可spring真就这么简单吗?如果这样想,你就大错特错了。例如:spring怎么来实现对象的管辖?怎么让不同技术之间能简单的互相配合?这才是spring的决胜之处。
为实现这些,spring创新的形成了一套新的理论体系。其中最核心的是:IoC控制反转、DI依赖注入、Bean工厂、SpringAOP面向切面编程、事务控制。
并且spring并没有停止不前,随着spring占领市场后,开始对有功之臣进行清洗,struts2不再优秀,致命bug层出不穷,刚好落井下石,spring推出了springmvc,最终终结了struts2。hibernate想用jdbcTemplate和jdo替代,却被mybatis超越,目前还未统一。于是经典的新三大框架诞生,SSM(SpringMVC+Spring+MyBatis)。Spring并没有放弃,而是另辟蹊径,推出新的产品SpringBoot+SpringCloud 微服务。
- - 3.官网
http://spring.io
- - 4.框架组成
Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。Spring框架的不光是技术牛,而是它的核心思想更牛,它不重复发明轮子,而是"拿来主义",把业界做的最好的技术黏合起来形成一个强大的企业级的应用框架。
Spring 框架是一个分层架构,由7个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示:
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
模块 | 说明 |
---|---|
核心容器Spring Core | 核心容器提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式,将应用程序的配置和依赖性规范与实际的应用程序代码分开。 |
Spring上下文Spring Context | Spring上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。 |
Spring AOP | 通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。可以很容易地使 Spring框架管理的任何对象支持AOP。Spring AOP模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。 |
Spring DAO | JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。 |
Spring ORM | Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 |
Spring Web | Web上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以Spring 框架支持与 Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。 |
Spring MVC框架 | MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。 |
Spring 框架的功能可以用在任何J2EE服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定J2EE服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同J2EE环境(Web或EJB)、独立应用程序、测试环境之间重用。
Spring以一种非侵入式的方式来管理你的代码,Spring提倡"最少侵入",这也就意味着你可以适当的时候安装或卸载Spring 。
- - 5.核心概念
模块 | 说明 |
---|---|
BeanFactory | Spring内部使用,创建bean的工厂 |
ApplicationContext | 外部应用程序调用,也成为spring容器 |
IOC控制反转Inversion of Control | 开发者在无需自己new对象,无需关心对象的创建过程User user = new User(); 手动创建对象User user = context.getBean(user); 容器创建对象 |
DI依赖注入Dependency Injection | 松耦合方式实现对象直接的依赖 |
AOP面向切面编程 | 补充java面向对象的不足 |
- - 6.三大核心组件的关系
Bean、Context、Core三大核心组件的关系:
Bean 包装的是 Object,而 Object 必然有数据,如何给这些数据提供生存环境就是 Context要解决的问题,对 Context 来说它就是要发现每个 Bean 之间的关系,为它们建立这种关系并且要维护好这种关系。所以 Context 就是一个Bean关系的集合,这个关系集合又叫 Ioc 容器,一旦建立起这个 Ioc 容器后 Spring 就可以为你工作了。其实Core 就是发现、建立和维护每个 Bean 之间的关系所需要的一些类的工具,从这个角度看来,Core 这个组件叫 Util 更能让你理解。
把Bean 比作一场演出中的演员的话,那 Context 就是这场演出的舞台背景,而 Core应该就是演出的道具了。只有他们在一起才能具备能演出一场好戏的最基本的条件。当然有最基本的条件还不能使这场演出脱颖而出,还要他表演的节目足够的精彩,这些节目就是 Spring 能提供的特色功能了。
- - 7.主要jar组成
模块 | 说明 |
---|---|
org.springframework.core | 核心工具包,其他包依赖此包 |
org.springframework.beans | 核心,包括:配置文件,创建和管理bean等 |
org.springframework.aop | 面向切面编程,提供AOP的实现 |
org.springframework.context | 提供IoC功能上的扩展服务,此外还提供许多企业级服务的 |
org.springframework.web.mvc | 包含SpringMVC应用开发时所需的核心类 |
org.springframework.transaction | 为JDBC、Hibernate、JDO、JPA提供一致的声明式和编程式事务管理 |
org.springframework.web | 包含Web应用开发时所需支持类 |
org.springframework.aspects | 提供对AspectJ框架的支持 |
org.springframework.test | 对junit等测试框架的简单封装 |
org.springframework.asm | 3.0后提供自己独立的,反编译 |
org.springframework.context.support | Context的扩展支持,用于mvc方面 |
org.springframework.expression | Spring表达式语言 |
org.springframework.instument | 对服务器的代理接口 |
org.springframework.jdbc | 对jdbc的简单封装 |
org.springframework.jms | 为简化jms api的使用而做的简单封装 |
org.springframework.orm | 整合第三方orm,如hibernate/mybatis |
org.springframework.web.servlet | 增强servlet |
- 2.Spring框架两大核心:IOC和DI
- - 1.概念
- IOC(Inversion of Control):将对象Object的创建的权力及对象的生命周期的管理过程交由Spring框架来处理,从此在开发过程中不在需要关注对象的创建和生命周期的管理,而是在需要的时候由Spring框架提供,这个由Spring框架管理对象创建和生命周期的机制称之为控制反转。
- DI(Dependency Injection):即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。这个过程称之为依赖注入。
总结下Spring核心就干了两件事:
- 创建对象
- 设置对象的关联关系
- - 2.IOC
IOC底层中的key与value:{“cn.tedu.User”,new User()}
IOC(Inversion of Control),控制反转。
就是指将对象的创建、存储(map)、管理(依赖查找,依赖注入)交给了spring容器
。
- - 3.DI
DI(Dependency Injection)依赖注入 。
DI的前提是IOC。
相对于IOC而言,依赖注入(DI)更加准确地描述了IOC的设计理念。所谓依赖注入,就是在创建对象的过程中Spring可以依据对象间的关系,自动把其它对象注入(无需创建对象,直接拿着使用)进来
。
- - 4.IOC实现的两种方式
是指把创建对象、管理对象过程交给Spring框架
- - - 1.IOC的XML方式
- - - - 1.创建Maven Module
- - - - 2.导入jar包(不需要了,被Spring Boot整合了)
- - - - 3.创建Hello类
package cn.tedu.spring;public class Hello {public void get(){System.out.println("Hello ioc~");}
}
- - - - 4.创建配置文件(spring-config.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--通过xml的配置,把我们自己写的类交给spring管理,作为spring容器里一个bean存在,本质上就是一个map{"hello",new Hello()}id:用来作为这个bena的唯一标识class:用来描述类的全路径底层通过反射创建对象(clazz.newInstance()):IOC底层 -> {"hello",new Hello()}--><bean id="hello" class="cn.tedu.spring.Hello" />
</beans>
- - - - 5.测试
package cn.tedu.test;import cn.tedu.spring.Hello;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test1 {//单元测试@Testpublic void get(){//1、读取配置文件ClassPathXmlApplicationContext spring =new ClassPathXmlApplicationContext("spring-config.xml");//2、直接getBeanObject obj = spring.getBean("hello");System.out.println(obj);//cn.tedu.spring.Hello@411f53a0//3、调用子类的方法 -- 向下转型/造型 -- 作用:调用子类功能Hello h = (Hello) obj;h.get();//Hello ioc~}
}
- - - 2.IOC的注解方式(@Component)
- - - - 1.创建User类
package cn.tedu.ioc;import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;@Component("a") //自动完成IOC,自己指定bean的名字a -> { "a",new User() }
//@Component // 默认的bean的名字,user
//@Component 自动完成IOC -> { "user",new User() }
//@Controller //spring提供的,用来ioc
//@Service //spring提供的,用来ioc
public class User {public void get(){System.out.println("Hello ioc~");}
}
- - - - 2.修改配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!--配置包扫描,用注解的方式,配置bean 会扫描 指定包下,带@Component注解的类并注入spring容器中,key是类名小写,value是类的对象base-package:指定一个包的路径,扫描范围可以自己定--><context:component-scan base-package="cn.tedu.ioc" />
</beans>
- - - - 3.测试
package cn.tedu.test;import cn.tedu.ioc.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test1 {@Testpublic void get(){//1、读取配置文件ClassPathXmlApplicationContext spring =new ClassPathXmlApplicationContext("spring-config.xml");//2、获取注解的bean//提供getBean(),根据bean的名字,从spring容器中获取对应的对象
// User u = (User) spring.getBean("user");//类名首字母小写User u = (User) spring.getBean("a");System.out.println(u);//cn.tedu.ioc.User@24fcf36fu.get();//Hello ioc~}
}
- - - - 4.总结
- - 5.DI依赖注入
是指对象间的依赖关系,可以由框架来完成
- - - 1.普通测试
- - - - 1.创建Dept类
package cn.tedu.di;public class Dept {String name = "java软件开发一部";@Overridepublic String toString() {return "Dept{" +"name='" + name + '\'' +'}';}
}
- - - - 2.创建Emp类
package cn.tedu.di;public class Emp {String name = "jack";//绑定两个类之间的关系private Dept d;public Dept getD() {return d;}public void setD(Dept d) {this.d = d;}@Overridepublic String toString() {return "Emp{" +"name='" + name + '\'' +", d=" + d +'}';}
}
- - - - 3.测试
import cn.tedu.di.Dept;
import cn.tedu.di.Emp;
import org.junit.jupiter.api.Test;public class TestDI {@Testpublic void di(){Dept d = new Dept();System.out.println(d);//Dept{name='java软件开发一部'}Emp e = new Emp();System.out.println(e);//Emp{name='jack', d=null}//DI -- 把两个对象间的关系依赖注入e.setD(d);//已经实现了DI的效果,在查询e对象时把关联的d对象的信息也查到了System.out.println(e);//Emp{name='jack', d=Dept{name='java软件开发一部'}} }
}
- - - 2.使用spring实现DI
- - - - 1.创建Teacher类
package cn.tedu.di2;import org.springframework.stereotype.Component;@Component
public class Teacher {String name = "tony";@Overridepublic String toString() {return "Teacher{" +"name='" + name + '\'' +'}';}
}
- - - - 2.创建Student类
package cn.tedu.di2;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class Student {// @Autowired底层是通过(暴力)反射来创建对象,获取所有(包括私有的)属性(clazz.getDealaredField())@Autowired //DI,用哪个类的内容,直接依赖注入进来就OKTeacher t;String name = "蔡徐坤";@Overridepublic String toString() {return "Student{" +"t=" + t +", name='" + name + '\'' +'}';}
}
- - - - 3.创建配置文件(spring-config.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!-- 包扫描:扫描指定包路径下的所有类,谁有IOC的注解就new谁 --><context:component-scan base-package="cn.tedu.di2" />
</beans>
- - - - 4.测试
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class TestDI {@Testpublic void di(){//1、读取xml配置文件ClassPathXmlApplicationContext spring =new ClassPathXmlApplicationContext("spring-config.xml");//2、getBeanObject obj = spring.getBean("student");//DI:查学生信息的同时也查到了老师的信息,两个对象之间的依赖注入System.out.println(obj);//Student{t=Teacher{name='tony'}, name='蔡徐坤'}}
}
- - 小结
面试:IOC和DI
在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时可能需要多个对象来协作来完成,在没有使用Spring的时候,我们使用对象都需要自己主动去new object()来创建对象,创建合作对象的主动权和创建时机是由自己把控的,如:A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,这样就会使得对象间的耦合度很高;而使用了Spring之后,创建合作对象B的工作是由Spring来完成的,Spring创建好B对象后,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题,A只需要得到Spring给的对象就可以了;之后A和B就可以共同协作完成任务。
所以控制反转IOC是说创建对象的控制权进行转移
,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IOC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式
反转了。
IOC是设计思想,IoC有三个核心:BeanFactory、反射、DI。BeanFactory利用反射实现对象的创建,DI实现对象关系管理。
自动装配
利用注解方式,我们只需要写@Autowired注解,底层就会去容器中找对应的对象,如果有获取到,反射调用其对应的set方法,设置。而这个调用过程都是自动,我们没有手工去写set方法。所以这个过程也称为自动装配。
- - 6.AOP面向切面编程
- - - 1.概念
是一个面向切面编程的思想,补充了OOP的不足。
实现效果:对方法的增强,本质上就是在执行方法的前后添加功能。
经典的使用场景:统计性能的分析 / 权限管理 / 事务管理 / 日志 / 缓存...
好处: 让程序员更专注业务代码本身
切面:本质上就是一个类
通知:本质上就是一个方法,定义一些功能分为:前置通知、后置通知和环绕通知,返回后通知,异常通知前置通知:是方法执行前要执行的功能(权限管理等)后置通知:是方法执行后要执行的功能(日志、IO流中关流等)环绕通知:是方法执行 前 后都要执行的功能(方法计时等)
切点:指定哪些类里的哪些方法要用 通知的功能
常用AOP注解: @Aspect:表示是一个切面类@Before:表示是一个前置通知@After:表示是一个后置通知@Around : 表示是一个环绕通知@PointCut : 表示切点
Spring核心特征中除了IOC控制反转、DI依赖注入,还有一个核心就是强大的面向切面编程AOP(Aspect Oriented Programming)的实现。
Spring AOP有三要素:
- Aspect定义切面;
- 通过通知(Advice)来指定具体做什么事情。如方法执行前做什么,方法执行后做什么,抛出异常做什么,从而实现对象行为(方法)的增强;
- 具体通过切点(PointCut)配置切点表达式(expression)来指定在哪些类的哪些方法上织入(ware)横切逻辑;被切的地方叫连接点(JoinPoint)。
- - - 2.通知的执行顺序
Spring框架实现了AOP面向切面,其引入了第三方AspectJ框架来具体实现。AspectJ提供了五种切入方式,术语称为通知advice。
具体五种为:
- 前置通知before
- 后置通知after
- 环绕通知around
- 返回后通知afterReturning
- 异常通知afterThrowing。
异常通知特殊,这里暂不讨论。
可以看到,分别在业务方法(Business Method)的执行前后进行拦截,执行指定的代码。
- - - 3.多切面执行顺序
下面是 两个切面 各通知的执行顺序:
- - - 4.步骤
1.添加AOP依赖jar包
<dependencies><!--添加aop依赖包--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies>
2.使用
在Service包下创建AOPAspect类
package cn.tedu.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;@Service //IOC
@Aspect //标记着这是一个aop的类:切面(由切点和通知组成)
public class AOPAspect {//1.切点(指具体要通知的类或者方法)//切点表达式:*是通配符,表示1个 ..表示多个(0~n)//方法返回值/包路径/子包/类名/方法名/参数列表@Pointcut("execution( * cn.tedu.service..*.*(..))")public void point(){ }//2.通知(是一个方法自定义功能)@Around("point()")//标记是一个环绕通知public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {//计时开始long time = System.currentTimeMillis();//去执行你的业务方法并返回结果集 -- joinPoint连接点Object obj = joinPoint.proceed();//计时结束time = System.currentTimeMillis() - time;//下面这行代码不必关注String methodname = joinPoint.getTarget().getClass().getName()//获取类名+"."+joinPoint.getSignature().getName();//获取方法名System.out.println(methodname+"方法执行时间是: "+time);return obj;}
}
3.测试
启动服务器,访问指定包里的资源时,就会自动触发切面中通知的功能。
- 3.Spring的扩展
- - 1.模拟SpringIOC的实现
- - - 1.概念
Class.forName(classPath).newInstance();
通过这种方式,Spring控制了对象的生命周期,可以随时自行增强对象,如DI依赖注入,如AOP,环绕通知在类创建前后增强功能,如Transaction事务加强等。
- - - 2.开发步骤
- 创建容器管理bean,并初始化容器 -> [user,dept,hello]
- 创建spring容器,并初始化容器 -> {hello=new Hello(),user=new Uer() }
提供getBean(),根据bean的名字,从spring容器中获取对应的对象
- - - 3.Bean.java
抽象Bean的定义,取代java中的Object,Spring框架中万物皆Bean。
package cn.tedu.design;
//模拟spring管理bean,存放bean
@Data //自动生成get set tostring hashCode equals
@NoArgsConstructor //自动生成无参构造
@AllArgsConstructor //自动生成全参构造
@Accessors(chain = true) //开启链式编程
public class Bean {private String name; //bean名字-> helloprivate String path; //bean对应的类路径->cn.tedu.desing.Hello
}
- - - 4.SpringContext.java
逻辑复杂,IoC实现的核心,最关键点还是怎么创建对象实例:
package cn.tedu.design;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//模拟spring框架,是spring的核心
//1,创建容器管理bean-> [user,dept,hello]
//2,创建spring容器-> {hello=new Hello(),user=new Uer() }
//3,getBean(),有就直接取出来,没有就创建并放入容器
public class SpringContext {//1,创建容器管理bean-> [user,dept,hello]private List<Bean> beanFactory = new ArrayList<>();//初始化容器public SpringContext() throws Exception {//创建Bean,并加入容器中Bean bean = new Bean();bean.setName("hello");bean.setPath("cn.tedu.pojo.Hello");beanFactory.add(bean);init();}
//2,创建spring容器-> {hello=new Hello(),user=new Uer() } //并发安全的mapprivate final Map<String, Object> factoryBeanObject = new ConcurrentHashMap<>();//初始化spring容器-> {hello=new Hello(),user=new Uer() } public void init() throws Exception {//遍历beanFactory,得到每个beanfor(Bean b : beanFactory) {//map里的keyString key = b.getName();//反射创建对象,作为value存入mapString path = b.getPath();Object value = Class.forName(path).newInstance();factoryBeanObject.put(key, value);}}//3,getBean()有就直接取出来,没有就创建并放入容器public Object getBean(String name) {return factoryBeanObject.get(name);//去map里根据key找value}
}
- - - 5.Hello.java
package spring;public class Hello {public void hi() {System.out.println("hi springioc");}
}
- - - 6.TestMyIoC.java
package cn.tedu.spring;import cn.tedu.design.SpringContext;
import cn.tedu.pojo.Hello;public class TestMyIOC {public static void main(String[] args) throws Exception {SpringContext spring = new SpringContext();Hello o = (Hello)spring.getBean("hello");System.out.println(o);//cn.tedu.pojo.Hello@6d06d69co.hi();}
}
- - 2.模拟SpringDI的底层实现
- - - 1.Student.java
package cn.tedu.designdi;import org.springframework.stereotype.Component;@Component
public class Student {private String name="王一博";@MyAutowired //diprivate Teacher teacher;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", teacher=" + teacher +'}';}
}
- - - 2.Teacher.java
package cn.tedu.designdi;import org.springframework.stereotype.Component;@Component
public class Teacher {private String name="皮皮霞";@Overridepublic String toString() {return "Teacher{" +"name='" + name + '\'' +'}';}
}
- - - 3.自定义注解
package cn.tedu.designdi;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {String value() default "" ;
}
- - - 4.TestMyDI.java
package cn.tedu.designdi;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;import java.lang.reflect.Field;//有注解的属性就new
public class TestDI {@Testpublic void di() throws Exception{//获取Student类里的属性,哪个属性有MyAutowired注解就给他new一个Class s = Class.forName("cn.tedu.designdi.Student");Object o = s.newInstance();Field[] fs = s.getDeclaredFields();for (Field f : fs) {MyAutowired an = f.getAnnotation(MyAutowired.class);if(an != null){f.setAccessible(true);f.set(o,new Teacher());}}
// Student{name='王一博', teacher=Teacher{name='皮皮霞'}}System.out.println((Student)o);}
}
- - 3.Spring整合SpringMVC的项目实战
Spring用来管理项目中的所有Bean,需要使用注解@Component @Autowired @Service等
SpringMVC用来管理Controller层,需要使用的注解有@RestController @RequestMapping等
需求
访问链接: http://localhost:8080/car/get
得到JSON数据: {"name":"保时捷","color":"红色","price":641000.0}
项目结构
- - - 1.创建网页
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>测试 两个框架整合</title></head><body><a href="http://localhost:8080/car/get">请求服务器的数据</a></body>
</html>
- - - 1.创建RunApp类,启动服务器
package cn.tedu;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
//@SpringBoot自动配置了包扫描:默认基于启动类所在的包
public class RunApp {public static void main(String[] args) {SpringApplication.run(RunApp.class,args);}
}
- - - 2.创建Car类
修改pom.xml,添加lombok的jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cgb2106boot02</artifactId><groupId>cn.tedu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>day1601</artifactId><dependencies><!-- 添加lombok的jar包 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency></dependencies></project>
Car类
package cn.tedu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;@Data //自动生成get set tostring hashCode equals
@NoArgsConstructor //自动生成无参构造
@AllArgsConstructor //自动生成全参构造
@Accessors(chain = true) //开启链式编程
public class Car {private String name;private String color;private Double price;
}
- - - 3.创建CarController类
package cn.tedu.controller;import cn.tedu.service.CarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController //标记这是一个springmvc
@RequestMapping("car") //规定了浏览器如何访问这个类
public class CarController {@Autowired //想要调用service层的代码 -- DIprivate CarService carservice;@RequestMapping("get")public Object get(){//找service去要数据return carservice.get();//把结果给浏览器返回}
}
- - - 5.创建CarService接口
package cn.tedu.service;import cn.tedu.pojo.Car;//定义接口
public interface CarService {//接口里的方法都是抽象方法,而且都是public的Car get();//获取汽车数据,查有返回值
// void add(Car c);//新增汽车数据,增删改无返回值
}
- - - 6.创建CarServiceImpl实现类
package cn.tedu.service;import cn.tedu.pojo.Car;
import org.springframework.stereotype.Component;@Component
public class CarServiceImpl implements CarService{//实现了接口后,要重写抽象方法@Overridepublic Car get() {Car c = new Car();//lombok的链式编程c.setName("保时捷").setColor("红色").setPrice(641000.0);return c;}
}
- - - 7.测试
- 启动服务器
- 打开浏览器执行前端HTML网页,发起请求,请求服务器的数据。
- 服务器收到请求后响应了准备好的数据
- - - 8.总结
- 4.Spring自动装配过程
- - 1.Spring"容器"
说明: Spring容器是在内存中一大块的内存区域,存储Spring管理对象
数据结构: KEY-VALUE结构
数据类型: Map集合
Map详细说明: Key: 类型首字母小写 Value: 对象
- - 2.依赖注入的原理
1.按照类型注入
按照属性的类型 去Map集中中查找是否有改类型的对象. 如果有则注入.
2.按照名称注入 根据属性的name 去Map集中中查找对应的KEY
@Autowired@Qualifier(value="李四")private SpringService springService;
自动装配的规则说明:
1.如果对象在进行实例化.如果对象中的属性被 @Autowired注解修饰,则说明应该先注入属性.
2.先根据属性的类型,查找Map集合中是否有该类型的对象.
3.如果根据类型查找没有找到,则根据属性的名称按照name查找对象.
4.如果上述的方式都没有找到,则报错实例化对象失败.
原则:Spring容器中要求 接口必须单实现. 如果有多实现则通过@Qualifier(“xxxx”)区分即可
三、MyBatis
- 1.MyBatis持久层框架
- - 1.概述
底层封装了JDBC,对数据库进行操作
好处:简化了JDBC的开发步骤,自动完成ORM映射
MyBatis的前身就是iBatis,iBatis本是apache的一个开源项目,2010年5月这个项目由apahce sofeware foundation 迁移到了google code,并且改名为MyBatis。
MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。
- 简化JDBC的开发
- 能够更好的完成ORM(依赖对象关系映射)
- - 2.内部组件结构图
核心配置文件:
mybatis-config.xml 配置了事务管理,数据源
映射文件:
XxxMapper.xml 存放大量CRUD的SQL语句 – (后期要改变)
核心工具类:
会话工厂SqlSessionFactory:产生会话
会话SqlSession:执行SQL语句 – (后期要改变)
ORM:
是指对象关系映射(把对象的值映射给属性
)。
把表里的字段的值 查到 自动交给 类里的属性 保存
ORM,即Object Relational Mapping,它是对象关系模型的简称。它的作用是在关系型数据库和对象之间作一个映射。使程序能够通过操纵描述对象方式来操纵数据库。
ORM作用:ORM解决的主要问题是对象关系的映射,一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录,类的每个属性对应表的每个字段。
ORM特点:1.提高了开发效率。由于ORM可以自动对Entity对象与数据库中的Table进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。2.ORM提供了对数据库的映射,不用sql直接编码,能够像操作对象一样从数据库获取数据。
ORM的优劣:1.提高开发效率,解耦合2.减低程序性能
- 2.MyBatis:XML映射方式(入门案例)
- - 1.准备数据库、表
create database mybatisdb default character set utf8;
use mybatisdb;
create table user(id int primary key auto_increment,name varchar(100),addr varchar(100),age int);
Insert into user values(null,'hanmeimei','北京',28);
Insert into user values(null,'xiongda','上海',20);
Insert into user values(null,'xiaonger','上海',19);
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (`id` int(11) NOT NULL AUTO_INCREMENT,`dname` varchar(14) DEFAULT NULL,`loc` varchar(13) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of dept
-- ----------------------------
INSERT INTO `dept` VALUES ('1', '呵呵呵', '一区');
INSERT INTO `dept` VALUES ('2', '哈哈哈哈', '二区');
INSERT INTO `dept` VALUES ('3', 'operations', '二区');
INSERT INTO `dept` VALUES ('5', 'java教研部', '大钟寺');
INSERT INTO `dept` VALUES ('10', '开发', '西二旗');
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (`id` int(11) NOT NULL AUTO_INCREMENT,`ename` varchar(10) DEFAULT NULL,`job` varchar(9) DEFAULT NULL,`mgr` decimal(4,0) DEFAULT NULL,`hiredate` date DEFAULT NULL,`sal` decimal(7,2) DEFAULT NULL,`comm` decimal(7,2) DEFAULT NULL,`deptno` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=510 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES ('100', 'jack', '副总', null, '2002-05-03', '90000.00', null, '1');
INSERT INTO `emp` VALUES ('200', 'tony', '总监', '100', '2015-02-02', '10000.00', '2000.00', '2');
INSERT INTO `emp` VALUES ('300', 'hana', '经理', '200', '2017-02-02', '8000.00', '1000.00', '2');
INSERT INTO `emp` VALUES ('400', 'leo', '员工', '300', '2019-02-22', '3000.00', '200.12', '2');
INSERT INTO `emp` VALUES ('500', 'liu', '员工', '300', '2019-03-19', '3500.00', '200.58', '2');
INSERT INTO `emp` VALUES ('502', '王一博', 'topidol.', '1000', '2021-03-31', '20000.00', '99.00', '88');
INSERT INTO `emp` VALUES ('504', '蔡徐坤', 'rapper', '10', '2021-03-29', '100.00', '1000.00', '100');
- - 2.修改pom.xml,添加mybatis和jdbc的jar包依赖
<dependencies><!--mybatis依赖包--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><!--jdbc依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency></dependencies>
- - 3.封装pojo类
package cn.tedu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;//使用lombok,简化pojo,使用之前必须先添加依赖jar包
//注意:属性的名 和 表里的字段名 必须一致,否则无法ORM
@Data //自动生成get set tostring hashCode equals
@NoArgsConstructor //自动生成无参构造
@AllArgsConstructor //自动生成全参构造
@Accessors(chain = true) //开启链式编程
public class User {private Integer id;private String name;private String addr;private Integer age;
}
- - 4.创建核心配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- mybatis的核心配置文件 --><configuration><environments default="test"><environment id="test"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource></environment></environments></configuration>
- - 5.创建映射文件UserMapper.xml,写SQL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 这个文件是映射文件,写SQL的namespance用来作为一个mapper.xml文件的唯一标识,value值可以任意写
-->
<mapper namespace="userMapper"><!--面试题:SQL动态获取参数时,可以用#或者$$ 底层用了低级传输器,可能发生SQL注入攻击,低效,不拼串,可能发生SQL语法错误# 底层用了高级传输器,安全,高效,会自动拼接字符串 'xiongda'--><!--id是这条SQL语句的唯一标识resultType的值用来封装 查到的结果,ORM--><!-- 根据表中id查询用户信息 --><select id="getById" resultType="cn.tedu.pojo.User">select * from user where id = #{id};</select><!-- 查询所有用户的值 --><select id="getAll" resultType="cn.tedu.pojo.User">select * from user;</select><!-- 查询hanmeimei的,固定语法#{??}用来解析SQL的参数--><select id="getByName" resultType="cn.tedu.pojo.User">select * from user where name = #{name};</select></mapper>
- - 6.在核心配置文件mybatis-config.xml中引入映射文件UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- mybatis的核心配置文件,配置了事务管理,数据源 -->
<configuration><!-- environments可以配置多个数据库信息,default指定默认环境 --><environments default="test"><environment id="test"><!-- 使用的事务管理器 --><transactionManager type="JDBC"></transactionManager><!-- 配置了数据源 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 引入映射文件 --><mappers><mapper resource="UserMapper.xml" /></mappers></configuration>
- - 7.创建测试类Test1测试
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.io.InputStream;
import java.util.List;public class Test1 {@Testpublic void get() throws IOException {//0.读取配置文件InputStream in = Resources.getResourceAsStream("mybatis-config.xml");//1.创建SqlSessionFactory会话工厂,线程安全,用来产生SqlSession会话SqlSessionFactory session = new SqlSessionFactoryBuilder().build(in);//2.创建SqlSession会话,线程非安全,用来执行sqlSqlSession sqlsession = session.openSession();//3.定位SQL:namespace的值+id的值,并添加参数//selectOne执行查询的SQL,并只会返回一个结果Object o = sqlsession.selectOne("userMapper.getById",1);//4.解析结果并打印System.out.println(o);//User(id=1, name=hanmeimei, addr=北京, age=28)//定位SQL:namespace的值+id的值//selectList执行查询的SQL,并返回多个结果List<Object> list = sqlsession.selectList("userMapper.getAll");//解析结果并打印(循环遍历list集合)for (Object obj : list){System.out.println(obj);}//定位SQL:namespace的值+id的值,并添加参数Object o1 = sqlsession.selectOne("userMapper.getByName","xiongda");System.out.println(o1);}
}
- - 8.总结
- 3.参数分析
- - 1.别名:alias
在核心配置文件中配置,在映射文件中直接写对象名称使用即可
在核心配置文件mybatis.config.xml中配置别名:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- mybatis的核心配置文件,配置了事务管理,数据源 -->
<configuration><!-- 配置别名 --><typeAliases><!-- <typeAlias type="类的全路径" alias="别名" /> --><typeAlias type="cn.tedu.pojo.User" alias="User" /></typeAliases><!-- environments可以配置多个数据库信息,default指定默认环境 --><environments default="test"><environment id="test"><!-- 使用的事务管理器 --><transactionManager type="JDBC"></transactionManager><!-- 配置了数据源 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 映入映射文件 --><mappers><mapper resource="UserMapper.xml" /></mappers>
</configuration>
在映射文件UserMapper.xml中的resultType使用:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 这个文件是映射文件,写SQL的namespance用来作为一个mapper.xml文件的唯一标识,value值可以任意写
-->
<mapper namespace="userMapper"><!--面试题:SQL动态获取参数时,可以用#或者$$ 底层用了低级传输器,可能发生SQL注入攻击,低效,不拼串,可能发生SQL语法错误# 底层用了高级传输器,安全,高效,会自动拼接字符串 'xiongda'--><!--id是这条SQL语句的唯一标识resultType的值用来封装 查到的结果,ORM--><!-- 根据表中id查询用户信息 --><select id="getById" resultType="User">select * from user where id = #{id};</select><!-- 查询所有用户的值 --><select id="getAll" resultType="User">select * from user;</select><!-- 查询hanmeimei的,固定语法#{??}用来解析SQL的参数--><select id="getByName" resultType="User">select * from user where name = #{name};</select></mapper>
- - 2.参数值:paramterType
指定参数类型,通常制定一个对象类型。
- - 3.返回值:resultType
非常重要的东西,即完成ORM的映射关系所在。这里指定的cn.tedu.pojo.User代表的是把结果集转换成一个User对象实例。
- - 4.返回值:resultMap
resultMap 用于对复杂对象结构时,对应的ResultMap结构名称
- - 5.#和$的区别
两种方式都可以获取参数的值。区别如下:
$ 底层用了低级传输器,可能发生SQL注入攻击,低效,不拼串,可能发生SQL语法错误
# 底层用了高级传输器,安全,高效,会自动拼接字符串 ‘xiongda’
(推荐!)#:使用#{name}引用参数的时候,Mybatis会把这个参数认为是一个字符串,例如传入参数是"Tom",那么在SQL (select * from user where name = #{name})使用的时候就会转换为 select * from user where name = ‘Tom’。
$:不做字符串拼接,SQL(select * from user where name = ${name}) 使用的时候就会转换为 select * from user where name = Tom。此时,如果字段是varchar类型直接抛出SQL异常。
#{} 和 ${} 在预编译中的处理也是不一样的。
#{} 在预处理时,会把参数部分用一个占位符 ? 代替,变成如下的 sql 语句:
select * from user where name = ?;
而 ${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会被解析成
select * from user where name = 'zhangsan';
以上,#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中。
从安全性上考虑,能使用#尽量使用#来传参,因为这样可以有效防止SQL注入的问题。
看下面的例子:
select * from ${tableName} where name = #{name}
在这个例子中,如果表名为
user; delete user; --
则动态解析之后 sql 如下:
select * from user; delete user; -- where name = ?;
–之后的语句被注释掉,而原本查询用户的语句变成了查询所有用户信息+删除用户表的语句,会对数据库造成重大损伤,极大可能导致服务器宕机。
但是表名用参数传递进来的时候,只能使用 ${} 。这也提醒我们在这种用法中要小心sql注入的问题。
- - 6.SQL中有特殊字符
当SQL中有特殊字符,mybatis不能正常解析时,
用<![CDATA[ ?? ]]>括起来就解决了 <![CDATA[ and age<=#{age} ]]>
<![CDATA[and age<=#{age}
]]>
- 4.动态SQL
- - 1.概述
利用mybatis框架提供一些标签,完成SQL的拼接
常用标签:
sql:提取SQL片段
include:引用指定的SQL片段
if:用来判断,满足条件才拼接SQL
- - 2.sql和include
Sql标签用来提取SQL片段,来提高SQL的复用。
使用位置需要通过include引用指定的SQL片段。
/*提取SQL片段,提高SQL片段复用性*/<sql id="cols">id,dname,loc</sql><select id="getById" resultType="Dept">select<!-- 引用SQL片段 --><include refid="cols"></include>from dept where id = #{id};</select>
- - 3.if
执行SQL时,可以添加一些判断条件。
<select id="getByName" resultType="Dept">select<include refid="cols"></include>from dept<if test="dname != null">where dname = #{dname};</if>
</select>
- - 4.where
去掉条件中可能多余的and或者or:
<select id="find" resultType="Item" parameterType="Item">
SELECT <include refid="cols"/> FROM tb_item
<where> <if test="title != null"> title like #{title} </if><if test="sellPoint != null">and sell_point like #{sellPoint}</if>
</where>
</select>
- - 5.set
去掉最后可能多余的逗号:
<update id="update">
UPDATE teachers
<set><if test="tname != null">tname=#{tname},</if><if test="tsex != null">tsex=#{tsex},</if><if test="tbirthday != null">tbirthday=#{tbirthday},</if><if test="prof != null">prof=#{prof},</if><if test="depart != null">depart=#{depart}</if>
</set>
WHERE tno=#{tno}
</update>
- - 6.foreach
用于in子查询中的多个值的遍历:
<!-- delete from dept where id in (1,2,3); -->
delete from dept where id in(<!-- foreach用来遍历a数组数据colletcion表示遍历那种集合里的数据,值是固定值:array/list/Map.keyitem表示即将遍历到的数据separator分隔符#{ids}获取遍历到的数据--><foreach collection="array" item="ids" separator=",">#{ids}</foreach>
);
- 5.MyBatis:接口映射方式
- - 1.概述
为了优化定位SQL的字符串拼接过程,“namespance的值.id的值”
步骤:
- 创建接口
- namaspace的值,是接口的全路径
- id的值 是接口里的方法名
- - 2.修改pom.xml,添加mybatis和jdbc的jar包依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cgb2106boot02</artifactId><groupId>cn.tedu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>day1701</artifactId><dependencies><!--mybatis依赖包--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><!--jdbc依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency></dependencies></project>
- - 3.封装pojo类
package cn.tedu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;//完成ORM,属性名 必须和 字段名 一致
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Dept {private Integer id;private String dname;private String loc;
}
- - 4.创建接口DeptMapper.java
package cn.tedu.dao;import cn.tedu.pojo.Dept;//接口的全路径 = 映射文件中的namespace的值
//接口的方法名 = 映射文件中id的值
public interface DeptMapper {Dept getById(Integer id);//根据id查部门记录List<Dept> getByName(String dname);//根据名字查部门记录void save(Dept dept);//新增部门记录void delete(int[] a);//删除部门记录void update(Dept dept);//修改部门记录
}
- - 5.创建核心配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 配置别名 --><typeAliases><!-- <typeAlias type="类的全路径" alias="别名" /> --><typeAlias type="cn.tedu.pojo.Dept" alias="Dept" /></typeAliases><environments default="test"><environment id="test"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments></configuration>
- - 6.创建DeptMapper.xml,写SQL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace = 接口的全路径名 -->
<mapper namespace="cn.tedu.dao.DeptMapper"><sql id="cols"> /*提取SQL片段,提高SQL片段复用性*/id,dname,loc</sql><!-- id = 接口里的方法名 --><select id="getById" resultType="Dept">select<!-- 引用SQL片段 --><include refid="cols" />from dept<if test="id != null">where id = #{id};</if></select><select id="getByName" resultType="Dept">select<include refid="cols" />from dept<if test="dname != null">where dname = #{dname};</if></select><insert id="save">insert into dept values (#{id},#{dname},#{loc});</insert><delete id="delete"><!-- delete from dept where id in (1,2,3); -->delete from dept where id in(<!-- foreach用来遍历a数组数据colletcion表示遍历那种集合里的数据,值是固定值:array/list/Map.keyitem表示即将遍历到的数据separator分隔符#{ids}获取遍历到的数据--><foreach collection="array" item="ids" separator=",">#{ids}</foreach>);</delete><update id="update">update dept set id = #{id}, dname = #{dname}, loc = #{loc} where id = #{id};</update>
</mapper>
- - 7.在核心配置文件mybatis-config.xml中引入映射文件DeptMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- mybatis的核心配置文件,配置了事务管理,数据源 -->
<configuration><!-- 配置别名 --><typeAliases><!-- <typeAlias type="类的全路径" alias="别名" /> --><typeAlias type="cn.tedu.pojo.Dept" alias="Dept" /></typeAliases><!-- environments可以配置多个数据库信息,default指定默认环境 --><environments default="test"><environment id="test"><!-- 使用的事务管理器 --><transactionManager type="JDBC"></transactionManager><!-- 配置了数据源 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 引入映射文件 --><mappers><mapper resource="DeptMapper.xml" /></mappers></configuration>
- - 8.创建测试类Test1测试
import cn.tedu.dao.DeptMapper;
import cn.tedu.pojo.Dept;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.io.InputStream;
import java.util.List;public class Test1 {@Testpublic void get() throws IOException {//0.读取配置文件InputStream in = Resources.getResourceAsStream("mybatis-config.xml");//1.创建SqlSessionFactory会话工厂,线程安全,用来产生SqlSession会话SqlSessionFactory session = new SqlSessionFactoryBuilder().build(in);//2.开启SqlSession会话,准备执行sql,线程非安全SqlSession sqlsession = session.openSession();//3.获取了指定的接口DeptMapper mapper = sqlsession.getMapper(DeptMapper.class);//4.调用接口里的方法//通过id获取数据Dept byId = mapper.getById(1);System.out.println(byId);//Dept(id=1, dname=呵呵呵, loc=一区)//通过dname获取数据List<Dept> byName = mapper.getByName("java教研部");for(Dept d : byName){System.out.println(d);//Dept(id=5, dname=java教研部, loc=大钟寺)}//向数据库中增删改数据Dept dept = new Dept();// dept.setId(null).setDname("PHP教研部").setLoc("小钟楼");
// mapper.save(dept);//增加数据mapper.delete(new int[]{1,2,3});//删除数据sqlsession.commit();//事务提交自动设置成false,增删改需要手动提交事务// System.out.println("插入数据成功!");System.out.println("删除数据成功!");}
}
- - 9.总结
- 6.ResultMap简单使用
- - 1.概述
resultType只能完成简单的ORM,只能完成那些 字段名 和 属性名一致的情况。
字段名 和 属性名 不一致的情况下,resultType必须换成resultMap,否则无法ORM。
- - 2.测试
- - - 1.创建表,添加了记录
CREATE TABLE `user_info` (`id` int(11) NOT NULL auto_increment,`user_name` varchar(20) default NULL,`user_addr` varchar(20) default NULL,`user_age` int(11) default NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- - - 2.修改pom.xml,添加mybatis的jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cgb2106boot03</artifactId><groupId>cn.tedu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>day18</artifactId><dependencies><!--mybatis依赖包--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><!--jdbc依赖包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency></dependencies>
</project>
- - - 3.创建核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis的核心配置文件,配置了事务管理,数据源 -->
<configuration><!--environments可以配置多个数据库的连接信息,default指定默认的环境--><environments default="test"><environment id="test"><!--使用的事务管理器--><transactionManager type="JDBC"></transactionManager><!--配置了数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!--引入映射文件--><mappers><mapper resource="UserInfoMapper.xml"></mapper></mappers>
</configuration>
- - - 4.创建映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.tedu.dao.UserInfoMapper"><!-- 查询所有数据,当字段名和属性名不一致时,resultType换成resultMap--><!-- ORM:是指把字段的值查到以后 交给 同名属性保存 --><resultMap id="abc" type="cn.tedu.pojo.UserInfo"><result column="user_name" property="userName" /><result column="user_addr" property="userAddr" /><result column="user_age" property="userAge" /></resultMap><select id="selectList" resultMap="abc">select * from user_info;</select>
</mapper>
- - - 5.创建接口类
package cn.tedu.dao;import cn.tedu.pojo.UserInfo;
import java.util.List;public interface UserInfoMapper {List<UserInfo> selectList();//查询所有数据
}
- - - 6.创建pojo类
package cn.tedu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserInfo {private Integer id;private String userName;private String userAddr;private Integer userAge;
}
- - - 7.测试
import cn.tedu.dao.UserInfoMapper;
import cn.tedu.pojo.UserInfo;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.io.InputStream;
import java.util.List;public class Test1 {@Testpublic void get() throws IOException {InputStream in = Resources.getResourceAsStream("mybatis-conf.xml");SqlSessionFactory session = new SqlSessionFactoryBuilder().build(in);//true表示自动提交事务SqlSession sqlSession = session.openSession(true);UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);List<UserInfo> list = mapper.selectList();for(UserInfo uf : list){System.out.println(uf);}}
}
- - 3.自动匹配规范驼峰规则
- - - 1.第一步:在核心配置文件中开启驼峰规则:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- mybatis的核心配置文件,配置了事务管理,数据源 -->
<configuration><settings><!--开启驼峰规则--><setting name="mapUnderscoreToCamelCase" value="true" /></settings><!-- environments可以配置多个数据库信息,default指定默认环境 --><environments default="test"><environment id="test"><!-- 使用的事务管理器 --><transactionManager type="JDBC"></transactionManager><!-- 配置了数据源 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" /><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="UserInfoMapper.xml"/></mappers></configuration>
- - - 2.第二步:在映射文件中的resultMap标签中添加新属性 autoMapping=“true”
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.tedu.dao.UserInfoMapper"><!-- 查询所有数据,当字段名和属性名不一致时,resultType换成resultMap --><!-- ORM:是指把字段的值查到以后 交给 同名属性保存 --><resultMap id="abc" type="cn.tedu.pojo.UserInfo" autoMapping="true"><!-- <result column="user_name" property="userName" /><result column="user_addr" property="userAddr" /><result column="user_age" property="userAge" />--></resultMap><!-- resultMap属性是解决了resultType解决不了的问题,引用指定resultMap --><select id="selectList" resultMap="abc">select * from user_info;</select>
</mapper>
- 7.缓存机制
- - 1.概述
mybatis提供了缓存机制减轻数据库压力,提高数据库性能,其缓存分为两级:一级缓存、二级缓存。一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效;二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的。
一级缓存
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。
具体流程
- 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来。
- 第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率。
注意事项:
- 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
- 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
- mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
二级缓存:
二级缓存是mapper级别的缓存,也就是同一个namespace的mapper.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域
二级缓存默认是没有开启的。需要在setting全局参数中配置开启二级缓存,如下conf.xml配置:
<settings><setting name="cacheEnabled" value="true"/>默认是false:关闭二级缓存
<settings>
在userMapper.xml中配置:
<!-- 当前mapper下所有语句开启二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
这里配置了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而却返回的对象是只读的,若想禁用当前select语句的二级缓存,添加useCache="false"修改如下:
<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
- 8.扩展
- - 1.JDBC和MyBatis的区别?
JDBC是java提供了一套专门用于和数据库对接的api,java.sql.*,其规范了如何和数据库进行对接,实现由各数据库厂商进行各自的实现和扩展。学习JDBC重点在学习如何使用其api。
MyBatis框架是轻量级封装了JDBC,我们已经看不到这些api,连接connection、语句preparedstatement、结果集ResultSet,而关注的是mybatis框架体系如何去使用,一旦写好,我们关注的是java对象。
- - 2.XML和接口方式的区别?
MyBatis提供了两种操作数据库的方式,一种是通过xml映射文件,一种是通过java的接口类。按面向对象方式更加推荐接口方式,但如果复杂的多表映射,仍然需要使用xml映射文件的ResultMap方式实现。
接口只是假象,其底层仍然是通过xml实现,好不容易实现了一套方式,怎忍丢掉呢?可以做个测试就知道它底层怎么实现的?把xml中的sql删除,它就玩不转了。
- - 3.接口方式怎么找到xml执行的?
SqlSession的getMapper方法找到类,通过反射可以获取到类的全路径(包名.类名),相加后就定位到某个xml的命名空间namespace,再根据调用的方法去找到xml中某个标签的id属性。从而实现价值接口,调用接口的方法而间接找到xml中的标签,通过解析xml获取这个标签的内容,从而获取到sql语句。