java(框架) springboot-1 基础使用+mybaits使用

学习视频: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中取出并且转换数据

java 复制代码
    @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方式

java 复制代码
// 请求处理类
@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接收即可。

java 复制代码
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类,

java 复制代码
 @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参数
java 复制代码
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注解的定义。

java 复制代码
@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的拦截器,统一响应格式。

ts 复制代码
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类

java 复制代码
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,然后修改

java 复制代码
 @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注解装饰。如

java 复制代码
@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层

java 复制代码
// 请求处理类
@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对象还有名字,默认是类名小写。

java 复制代码
@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可以让我们通过注解的方式,高校的编写实体类。

简化之后

java 复制代码
import lombok.Data;

@Data
public class User {
    private Integer id;
    private Integer age;
    private String phoneNum;
    private String name;

}
MyBaits基础操作
删除操作
java 复制代码
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的参数占位符

新增
java 复制代码
   // 新增
    @Insert("insert into users(name, age, phoneNum) values(#{name}, #{age}, #{phoneNum})")
    public void insert(User user);

变量是user里面的属性。

使用

java 复制代码
@Test
	void insert(){
		User userTest = new User();
		userTest.setAge(18);
		userTest.setName("ceshi");
		userTest.setPhoneNum("1023123213");
		user.insert(userTest);
		System.out.println("插入成功");
		this.contextLoads();
	}

多个参数可以用实体类封装起来。

更新
java 复制代码
 // 更新
    @Update("update users set name=#{name}, age=#{age}, phoneNum=#{phoneNum} where id=#{id}")
    public int update(User user);
查询
java 复制代码
Select("select * from users where id=#{id}")
    public List<User> getById(Integer id);

mybatis有驼峰自动命名开关,一旦打开,会自动映射a_b到aB上。

直接在配置文件里面配置

java 复制代码
mybatis.configuration.map-underscore-to-camel-case=true
条件查询

模糊查询因为是字符串,所以不能用#{},可以用${},但是性能低,不安全,使用java提供的concat函数拼接字符串

XML映射文件

之前用mybaits查数据库都是使用注解的方式。可以通过配置文件的方式来写sql

编写xml要注意规范

  • 文件名称一致,目录一致
  • 2 id一致,resultType表示返回的单条数据类型

最后,注释掉注解,使用xml,直接运行。

结果一样。

Mybaits 动态sql

有些字段传了就需要where,有些字段没值就不需要where

java 复制代码
  <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标签判断是否要加上该条件。

修改
java 复制代码
 <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标签
java 复制代码
<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标签。

批量操作

批量更新

java 复制代码
   <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;

java 复制代码
     <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>

整体的语句类似于

sql 复制代码
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)

一个字段一个字段的修改。

相关推荐
侠客行031711 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪11 小时前
深入浅出LangChain4J
java·langchain·llm
灰子学技术13 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
老毛肚13 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎14 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
二十雨辰14 小时前
[python]-AI大模型
开发语言·人工智能·python
Yvonne爱编码14 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚14 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂14 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
pas13614 小时前
41-parse的实现原理&有限状态机
开发语言·前端·javascript