学习视频:b站黑马java教程
tomcat
spring-boot工程内嵌了tomcat服务器
-
所有请求经过
DispatcherServlet(实现servlet接口的类)(核心控制器/前端控制器)
处理,再通过DispatcherServlet转发给各个controller
。 -
最后通过DispatcherServlet给浏览器响应数据
-
他会将浏览器的http请求鞋带的数据,比如header,body等封装到
HttpServletRequest
对象中,相当于nest的@Request() req
;获取请求对象。 -
然后通过
HttpServletResponse
设置相应数据,DispatcherServlet会根据响应数据,封装好http响应头,响应给浏览器。相当于nest的@Response() res;
-
BS架构 浏览器/服务器模式 用户只要有浏览器就行
-
CS架构 客户端/服务器,比如qq,网盘等
获取请求参数
query参数
原始方式,从HttpServletrequest中取出并且转换数据
@RequestMapping("/hello") //相当于nest的@Get("/hello"),处理哪个请求
public String hello(HttpServletRequest req){
// 获取query参数
String name = req.getParameter("name");
String age = req.getParameter("age");
return "your name is" + name + "; and you age is" + age;
}
springboot方式
// 请求处理类
@RestController //注解,用来标记这个类是请求处理类,相当于nest的@Controller
public class HelloController {
@RequestMapping("/hello") //相当于nest的@Get("/hello"),处理哪个请求
@RequestMapping("/hello") //相当于nest的@Get("/hello"),处理哪个请求
public String hello(@RequestParam(name="name", required=false) String userName, String age){
return "your name is" + userName + "; and you age is" + age;
}
}
简单参数直接作为方法参数写入即可,命名需要一样(不一样需要用@ReueqstParam(name=“name”)去重命名)。相当于nest的@Query() query快速获取参数。
对应Post请求
,如果是x-www-form-urlencoded
的方式,也是上述这种方式即可
小结
实体参数
如果简单参数太多,一个一个些不切实际,定义POJO接收即可。
package com.example.demo.pojo;
public class UserProps {
private String name;
private String age;
public String getName(){
return this.name;
}
public String getAge(){
return this.age;
}
}
定义一个实体对象,pojo类,
@RequestMapping("/hello") //相当于nest的@Get("/hello"),处理哪个请求
public String hello(UserProps user){
return "your name is" + user.getName() + "; and you age is" + user.getAge();
}
直接创建了一个实例,然后调用定义好的方法去获取。结果一样。
如果是复杂的,比如
用得较少。
数组集合参数
用得较少。
日期参数
用得较少,大多都是通过post封装json。
JSON参数
package com.example.demo.pojo;
public class AddressProps {
public String province;
public String city;
public String get(String field){
switch (field){
case "city": {
return this.city;
}
case "province":
default: {
return this.province;
}
}
}
}
package com.example.demo.pojo;
public class UserJsonProps {
public String name;
public String age;
public AddressProps address;
}
@RequestMapping("/json")
public String json(@RequestBody UserJsonProps user){
System.out.println(user.age);
System.out.println(user.name);
System.out.println(user.address.city + user.address.province);
return "ok";
};
使用@RequestBody标识,类似于nest的@Body() body;
Params 参数
@RequestMapping指定路径的时候就加上变量定义,然后通过@pathVariable去获取对应的变量。多个就写多个。
小结
- query参数,可以通过
HttpServletrequest
得到req,然后通过req.getParamter获取值@也可以通过springboot封装好的,直接通过方法参数的形式获取,变量名称不一样的话需要使用@RequestParam
去重命名。 - 实体对象,但query很多的时候,可以封装pojo类来创建一个实例,然后通过实例获取值
- 数据集合和日期通过query的比较少,一般通过json请求
- json格式的数据,通过@RequestJson,封装pojo类获取
- params格式的数据,通过@ReqeusetMapping指定url的时候就制定变量(跟nest类似),然后通过@PathVariable定义方法参数变量获取数据。
设置响应数据
@RestController注解的定义。
@Target({ElementType.TYPE}) //类型,TYPE表示作用在类或者接口
@Retention(RetentionPolicy.RUNTIME) //运行时间, runtime的时候运行
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
上述将HelloController
标记为@RestController
,@RestController是@Controller和@ResponseBody的集合,因为作用在类上,所以该类的所有方法的返回值都会作为响应传给客户端。
统一响应内容
类似于nest的拦截器,统一响应格式。
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map((data) => {
const request = context.switchToHttp().getRequest();
if (request.url.includes('weichat/userInfo')) {
return data;
}
return {
data,
code: 0,
extra: {},
msg: 'success',
success: true,
};
}),
);
}
java也需要定义一个统一返回数据的格式
定义一个Result类
package com.example.demo.pojo;
public class Result<T extends Object> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode(){
return this.code;
};
public void setCode(Integer code){
this.code = code;
};
public String getMsg(){
return this.msg;
};
public void setMsg(String msg){
this.msg = msg;
};
public T getData(){
return this.data;
};
public void setData(T data){
this.data = data;
};
// 重载
public static <K extends Object>Result<K> success(K data){
return new Result<K>(0, "success", data);
}
public static Result success(){
return new Result<>(0, "success", null);
}
public static Result error(String msg){
return new Result(1, msg, null);
}
}
提供两个静态方法,success和error,然后修改
@RequestMapping("/json")
public Result json(@RequestBody UserJsonProps user){
System.out.println(user.age);
System.out.println(user.name);
System.out.println(user.address.city + user.address.province);
return Result.success(user);
};
结果
这样就封装成功了。
分层解藕
三层架构
如nest的Controller 和 Service ,还有多一层dao,负责数据访问
controller层调用service层,service层调用dao层获取数据。
符合单一原则。
解耦
像上面三层设计,controller层要调用service层,所以new了一个实例,service层要调用dao层,也new了一个dao的实例,这就导致controller和service耦合,service和dao耦合。
原则:高内聚,低耦合
为了实现解耦,需要提供一个容器。将所有需要用到的对象放到容器中,然后其他层需要依赖的时候,再去取。类似于多一个中介,实现解耦。
现在的问题就是,我们不想要自己new一些对象,只想在class上面声明依赖,然后让程序帮我们创建对应的依赖对象传入进来。
这就涉及两个概念控制反转
,依赖注入
。
IOC:原本我们需要什么对象,就自己new一个,现在是直接交给容器去帮我们创建
DI: controller层需要依赖service对象,由容器我们注入,称之为依赖注入。
这就是 IoC 的实现思路。
-
它有一个放对象的容器,程序初始化的时候会扫描 class 上声明的依赖关系,然后把这些 class 都给 new 一个实例放到容器里。
-
创建对象的时候,还会把它们依赖的对象注入进去。这样不就完成了自动的对象创建和组装么?这种依赖注入的方式叫做 Dependency Injection,简称 DI。
-
从主动创建依赖到被动等待依赖注入,这就是 Inverse of Control,反转控制。
java改造
需要被IOC接管的类,通过@Component注解装饰。
需要通过IOC依赖注入的属性,通过@Autowired注解装饰。如
@Component //将当前类交给IOC容器管理
public class EmpDao1 implements EmpDao {
public List<String> listEmp(){
return List.of("小米姑娘", "小红");
}
}
@Component
public class EmpService1 implements EmpService {
@Autowired
EmpDao1 emp;
public List<String> listEmp(){
List<String> originData = emp.listEmp();
// 处理数据并且返回
return originData;
}
}
Dao层和Service等需要注入到其他类中的类,用@component声明,其次
Service中需要用到Emp对象,所以用@Autowired声明该属性。
最后看下controller层
// 请求处理类
@RestController //注解,用来标记这个类是请求处理类,相当于nest的@Controller
public class HelloController {
@Autowired //标识该属性需要IOC提供bean对象,并且赋值给变量
private EmpService empService;
@RequestMapping("/hello") //相当于nest的@Get("/hello"),处理哪个请求
public String hello(UserProps user){
return "your name is" + user.getName() + "; and you age is" + user.getAge();
}
@RequestMapping("/json")
public Result json(@RequestBody UserJsonProps user){
System.out.println(user.age);
System.out.println(user.name);
System.out.println(user.address.city + user.address.province);
return Result.success(user);
};
@RequestMapping("/ioc")
public Result ioc(){
return Result.success(empService.listEmp());
}
}
要用到service对象,所以要用@Autowired注解装饰,IOC就会自动将实例分配进来。
IOC详解
将上述Service使用的@Component转为@Service,将Dao层换为@Repository,Controller不用换,因为@RestController已经包括@Controller。
其次Bean对象还有名字,默认是类名小写。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Repository("daoA") //将当前类交给IOC容器管理
public class EmpDao1 implements EmpDao {
public List<String> listEmp(){
return List.of("小米姑娘", "小红");
}
}
用注解声明的类不一定被扫描到,若扫描不到则会报错。’
DI详解
若依赖注入的时候,有多个相同的bean对象呢?
三种方法:
- 通过在注解Service的时候,多注解一个@Primary,表示优先级高点。
- 第二个就是在依赖注入时,多注解一个@Qualifiler(bean名字)
- 第三个就是不用@autowired,而使用@Resource(bean名字),Autowried是通过类型注解的,而Resource是通过名字
nestjs ioc设计
需要通过IOC接管的类,通过@Injectable装饰器装饰。
需要通过IOC依赖注入的属性,通过@Inect装饰。
其次,nest还封装了module层等。
看一个简单的案例
Service
AppSerivce用@Injectable()装饰器装饰(java里面为注解),表示这个类,可以被注入,也可以注入别的类。那么nest在解析运行时就会new一个他的实例放入容器中。
然后这是个service层,需要用到数据库的数据,所以可以通过@InjectRepository(Project)等方式,注入两个仓库实例,使用的时候我们就不需要new一个实例,nest运行时直接帮我们创建好并且传入。
Controller
然后是Controller层,通过Controller装饰器装饰,表示该类只能注入其他对象,而不能被注入到其他对象。
其次还声明了他需要依赖的对象,以上两种方式都可以,一种是通过@Inject声明,一种直接在构造函数上声明。前者是构造器注入,后者是属性注入,两种都可以。
module
最后在model声明
@Module 声明模块
controllers是控制器,只能注入其他对象,
providers可以被注入,也可以注入其他对象,
当我们启动服务,nest就会自动解析我们在class上面声明的依赖,自动创建和组装对象。
所以上述projectController只声明了proejctSerivce的定义,就可以使用了。
此外,nest还加了模块机制,可以吧不同业务的controller和serivce放到不同模块。
不同模块也可以相互import,一旦相互omports后,他们模块exports的service即可使用。
比如上述的proejctService可以使用仓库实例,就是因为module imports了其模块,然后依赖的模块exports定义了这个类可以被其他模块使用。
总结
- 后端系统有很多对象,这些对象之间关系错综复杂,如果手动创建并且组装依赖关系很麻烦,所以提供了IOC机制。
- IOC机制是在calss标识哪些可以被注入,他的依赖是什么,然后从rookie开始扫描这些对象和依赖,自动创建和组装对象。
- IOC解决了后端系统的对象依赖关系错综复杂的痛点问题。
Mybatis
类似于nest中的typeorm,mybatis也是一种orm框架,用来方便连接java和数据库
MyBatis使用步骤
操作步骤
-
1 在pojo下面创建实体类,字段与数据库字段一致
-
2 创建mapper接口(跟之前创建dao层的类差不多)都是dao层的逻辑
这的mapper跟dao的含义是一样的,都是持久层的逻辑,然后定义对应的UserMappe接口r,用@Mapper注解装饰,然后定义listUser方法,使其查询全部用户信息返回。 -
3 mybatis连接mysql
在springboot生成的配置文件下面,配置mysql服务器对应的地址等信息。 -
4 使用
因为我们实现的UserMapper虽然是接口,但他用@Mapper注解,表示由IOC容器接管,所以会在运行时创建一个对象,这样我们就可以通过依赖注入的方式,直接得到UserMapper对应的实例,然后直接调用lisetUser方法,就可以获取到数据。
结果正常,这样就简单的用mybaits连接mysql了。
JDBC
一组操作数据库的API,具体实现由各个数据库厂商实现。
JDBC VS MyBaits
如上,右边是jdbc原始写法,左方是mybtits写法。
数据库连接池
相当于一个容器,负责管理分类数据库连接,可以重复使用,而不是每次要用的时候创建连接(创建连接释放连接是比较浪费资源的操作)。
客户端需要用的时候,从连接池获取链接,用完就归还给连接池。
上面的案例
Lombok
之前我们编写的User实体类太繁琐,lombok可以让我们通过注解的方式,高校的编写实体类。
简化之后
import lombok.Data;
@Data
public class User {
private Integer id;
private Integer age;
private String phoneNum;
private String name;
}
MyBaits基础操作
删除操作
package com.example.demo.mapper;
import com.example.demo.pojo.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper //在运行时,会自动生成改接口的实现类对象(动态代理对象),并且将该对象交给IOC容器管理。
public interface UserMapper {
// 查询全部用户信息
@Select("select * from users")
public List<User> listUser();
// 删除操作
// #{变量} 是mybatis提供的占位符
// delete会返回此次操作影响的数据条数,简单的说就是删除了多少条数据
@Delete("delete from users where id = #{id}")
public int delete(Integer id);
}
使用变量代替,这种称为预编译sql
首先是性能更高,因为sql是有缓存的,使用预编译sql,可以完美利用缓存。
其次是更安全。防止sql注入
mybatis的参数占位符
新增
// 新增
@Insert("insert into users(name, age, phoneNum) values(#{name}, #{age}, #{phoneNum})")
public void insert(User user);
变量是user里面的属性。
使用
@Test
void insert(){
User userTest = new User();
userTest.setAge(18);
userTest.setName("ceshi");
userTest.setPhoneNum("1023123213");
user.insert(userTest);
System.out.println("插入成功");
this.contextLoads();
}
多个参数可以用实体类封装起来。
更新
// 更新
@Update("update users set name=#{name}, age=#{age}, phoneNum=#{phoneNum} where id=#{id}")
public int update(User user);
查询
Select("select * from users where id=#{id}")
public List<User> getById(Integer id);
mybatis有驼峰自动命名开关,一旦打开,会自动映射a_b到aB上。
直接在配置文件里面配置
mybatis.configuration.map-underscore-to-camel-case=true
条件查询
模糊查询因为是字符串,所以不能用#{},可以用${},但是性能低,不安全,使用java提供的concat函数拼接字符串
XML映射文件
之前用mybaits查数据库都是使用注解的方式。可以通过配置文件的方式来写sql
编写xml要注意规范
- 文件名称一致,目录一致
- 2 id一致,resultType表示返回的单条数据类型
最后,注释掉注解,使用xml,直接运行。
结果一样。
Mybaits 动态sql
有些字段传了就需要where,有些字段没值就不需要where
<select id="listUser" resultType="com.example.demo.pojo.User">
select * from users;
</select>
<select id="getByField" resultType="com.example.demo.pojo.User">
select * from users
<!-- 1 动态生成where 2 自动去除多余and 或者 or -->
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="age != null">
and and age = #{age}
</if>
order by age desc;
</where>
</select>
如上,用where标签,自动生成where,并且会根据条件自动去除开头的and和or, 用if标签判断是否要加上该条件。
修改
<update id="update">
update users
<!-- 跟where 一样, set标签自动加上set,去除末尾多余逗号-->
<set>
<if test="name != null">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="phoneNum != null">
phoneNum = #{phoneNum}
</if>
</set>
where id = #{id}
</update>
也是用动态Sql
批量操作 foreach标签
<delete id="deleteByIds">
delete from users where id in
<!--
collection 遍历的集合
item 遍历出来的元素
separator 分隔符
open: 遍历钱拼接的sql片段
close: 遍历结束后拼接的sql片段
-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
<!-- 上面的标签会变成 (1,2,3) -->
</delete>
foreach能转化像in (x,x,x)这些操作
include和sql标签
类似于组件复用,sql标签能将重复的sql语句拆出来,并且标记一个名字,使用的时候用include标签就可以引用到。
sql标签将重复的sql语句抽离,include标签引入抽离的sql标签。
批量操作
批量更新
<insert id="insertEmp">
insert into emp (name, username, password, job, entrydate, create_time, last_update_time)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.name}, #{emp.username}, #{emp.password}, #{emp.job}, #{emp.entrydate}, #{emp.createTime}, #{emp.lastUpdateTime})
</foreach>
</insert>
foreach 的对象为List的时候,遍历的每一个就是其中的元素,可以通过xx.xx去获取值。
批量更新
采用set case when;
<update id="updateEmp">
update emp
<!-- 批量更新 case when 修改name -->
<trim prefix="set" suffixOverrides=",">
<trim prefix="name=case" suffix="end,">
<foreach collection="emps" item="emp">
<if test="emp.name != null and emp.name != ''">
when id = #{emp.id} then #{emp.name}
</if>
</foreach>
</trim>
<trim prefix="username=case" suffix="end,">
<foreach collection="emps" item="emp">
<if test="emp.username != null and emp.username != ''">
when id = #{emp.id} then #{emp.username}
</if>
</foreach>
</trim>
<trim prefix="password=case" suffix="end,">
<foreach collection="emps" item="emp">
<if test="emp.password != null and emp.password != ''">
when id = #{emp.id} then #{emp.password}
</if>
</foreach>
</trim>
<trim prefix="job=case" suffix="end,">
<foreach collection="emps" item="emp">
<if test="emp.job != null and emp.job != ''">
when id = #{emp.id} then #{emp.job}
</if>
</foreach>
</trim>
<trim prefix="last_update_time=case" suffix="end,">
<foreach collection="emps" item="emp">
<if test="emp.lastUpdateTime != null and emp.lastUpdateTime != ''">
when id = #{emp.id} then #{emp.lastUpdateTime}
</if>
</foreach>
</trim>
</trim>
where id in
<foreach collection="emps" item="emp" separator="," open="(" close=")">
#{emp.id}
</foreach>
</update>
整体的语句类似于
UPDATE mytable
SET myfield = CASE id
WHEN 1 THEN 'value1'
WHEN 2 THEN 'value2'
WHEN 3 THEN 'value3'
END
WHERE id IN (1,2,3)
一个字段一个字段的修改。