JAVA框架

1.SpringBoot介绍

  • springboot是工具集,轻量级,整合了其他框架,方便开发,减少了配置

  • 约定大于配置

2.Controller

controller就是之前的servlet,用于请求和响应

package com.qf.day41_springboot.controller;
​
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
​
@Controller//写上这个注解以后,下面这个类就相当于之前学的Servlet了 可以
//接收请求和进行响应
public class TestController {
    //指定当前资源路径, 请求方式是get
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    //@GetMapping("/test")//路径
    //一旦@RequestMapping 修饰下面的test方法, 就意味着可以通过 test路径执行test方法
    public String test () {
        System.out.println("呵呵");
        return "ok.html";//响应
    }
    //指定当前资源路径, 请求方式是get
    @RequestMapping(value = "/test1", method = RequestMethod.GET)
    //@GetMapping("/test")//路径
    //一旦@RequestMapping 修饰下面的test方法, 就意味着可以通过 test路径执行test方法
    public String test1 () {
        System.out.println("哈啊");
        return "test1.html";//响应
    }
​
}

3.Spring

3.1 IOC控制反转和DI依赖注入

IOC是spring框架的核心功能之一,IOC(inversion of control)控制反转

控制:控制创建对象的能力

反转:原来创建对象是自己做,反转就是将创建对象的能力交给Spring

DI(dependency injection)依赖注入,就是属性赋值

创建对象的注解:

  • @Controller在控制层上使用

  • @Service 在业务层上使用

  • @Repository 在数据层代码上使用

  • @Component在任意层上使用

属性赋值的注解:

  • @Autowired

3.1.1演示

需求: 项目中控制层servlet需要使用到业务层对象来处理业务,例如AdminController中需要创建AdminService对象使用,使用IOC+DI完成

AdminService和AdminServiceImpl

package com.qf.day41_springboot.service;
​
public interface AdminService {
    void login();
}
package com.qf.day41_springboot.service.impl;
​
import com.qf.day41_springboot.service.AdminService;
import org.springframework.stereotype.Service;
​
​
/**
 * 业务层的实现类
 * @Service 注解 加到了业务类上面
 * 用于创建了业务类对象
 */
@Service
public class AdminServiceImpl implements AdminService {
    @Override
    public void login() {
        System.out.println("这个是AdminServiceImpl的login方法");
    }
}

AdminController

package com.qf.day41_springboot.controller;
​
import com.qf.day41_springboot.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
​
@Controller
public class AdminController {
    /**Autowired 注解
     * 会在spring容器中找 AdminServiceImpl  对象
     * 并且赋值给了adminService 这个属性
     */
    @Autowired
    AdminService adminService;
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public void test() {
        //需求:在test要调用AdminServiceImpl中的login方法
        //想法:   new AdminServiceImpl()   对象.login()
        //new AdminServiceImpl() 已经交给spring了,借助spring的IOC
        //用了一个注解@Service
        adminService.login();
    }
​
}
​

在Controller中调用service中一个方法

在Controller中 得有(DI)一个对象(通过IOC(好几个注解) )AdminService ,

练习: 在AdminService中使用AdminDao对象

package com.qf.day41_springboot.service.impl;
​
import com.qf.day41_springboot.dao.AdminDao;
import com.qf.day41_springboot.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
​
​
/**
 * 业务层的实现类
 * @Service 注解 加到了业务类上面
 * 用于创建了业务类对象
 */
@Service
public class AdminServiceImpl implements AdminService {
    @Autowired//依赖注入
    AdminDao adminDao;
    @Override
    public void login() {
        adminDao.adminLogin();
    }
}
package com.qf.day41_springboot.dao.impl;
​
import com.qf.day41_springboot.dao.AdminDao;
import org.springframework.stereotype.Repository;
​
/**
 * @Repository  对数据层进行实例化对象的
 *
 */
@Repository
public class AdminDaoImpl implements AdminDao {
    @Override
    public void adminLogin() {
        System.out.println("连接数据库写登陆的sql");
    }
}

3.1.1 演示2

演示@Component注解创建对象

假如有个类User,现在需要该类对象,就可以在该类上加上@Component注解,并在其他地方使用@Autowired注入

image-20230823155730068

package com.qf.day41_springboot.pojo;
​
import org.springframework.stereotype.Component;
​
/**
 * @Component  一般是对非   dao, service ,controller  可以使用
 * @Component
 */
@Component
public class Admin {
    private String name = "狗蛋";
    private int age = 23;
​
    @Override
    public String toString() {
        return "Admin{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
​
package com.qf.day41_springboot.controller;
​
import com.qf.day41_springboot.pojo.Admin;
import com.qf.day41_springboot.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
​
/**
 * @Controller  注解是IOC的注解 重要的注解
 * 1.AdminController  交给spring实例化了
 * 2.把AdminController  变成了一个类似于servlet的一个类,可以进行web操作的一个类
 */
@Controller
public class AdminController {
    /**Autowired 注解
     * 会在spring容器中找 AdminServiceImpl
package com.qf.day41_springboot.controller;
​
import com.qf.day41_springboot.pojo.Admin;
import com.qf.day41_springboot.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
​
/**
 * @Controller  注解是IOC的注解 重要的注解
 * 1.AdminController  交给spring实例化了
 * 2.把AdminController  变成了一个类似于servlet的一个类,可以进行web操作的一个类
 */
@Controller
public class AdminController {
    /**Autowired 注解
     * 会在spring容器中找 AdminServiceImpl  对象
     * 并且赋值给了adminService 这个属性
     */
    @Autowired
    AdminService adminService;
    @Autowired
    Admin admin;
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public void test() {
        //需求:在test要调用AdminServiceImpl中的login方法
        //想法:   new AdminServiceImpl()   对象.login()
        //new AdminServiceImpl() 已经交给spring了,借助spring的IOC
        //用了一个注解@Service
        //Admin admin = new Admin();//new 对象交给spring了 不要自己创建
        System.out.println(admin);
        adminService.login();
    }
​
}
​
  对象
     * 并且赋值给了adminService 这个属性
     */
    @Autowired
    AdminService adminService;
    @Autowired
    Admin admin;
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public void test() {
        //需求:在test要调用AdminServiceImpl中的login方法
        //想法:   new AdminServiceImpl()   对象.login()
        //new AdminServiceImpl() 已经交给spring了,借助spring的IOC
        //用了一个注解@Service
        //Admin admin = new Admin();//new 对象交给spring了 不要自己创建
        System.out.println(admin);
        adminService.login();
    }
​
}

3.2AOP面向切面编程

AOP是Spring中另外一个核心功能

AOP面向切面编程:面向切面编程,将业务中重复代码抽取,形成切面,利用代理模式给目标类实现 增强,减少代码重复,减少耦合

3.2.1演示

演示各种aop增强方式,步骤

  • 创建切面类,类上加注解

    • @Component ,加上该注解,springboot框架就会创建该类对象

    • @Aspect , 加上该注解,springboot框架内部就会知道该类是一个切面类

  • 设置切入点方法,并加注解

    • @Pointcut , 用于定义要增强的目标方法路径

  • 设置各种增强(或者叫通知)方法

    • 注解解释
      @Around环绕通知
      @Before前置通知
      @After后置通知
      @AfterReturning后置返回通知
      @AfterThrowing异常通知

1.pom.xml添加aop依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.演示给业务层代码增强

日志注解文件

package com.qf.annotation;
​
/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
    String value();
}

使用注解

@RestController
public class UserController {
​
    @Autowired
    private UserService userService;
​
    @GetMapping("/login")
    public User login(String name, HttpSession session) {
        User user = userService.selectUserByUsername(name);
​
        if (user != null) {
            session.setAttribute("user",user);
        }
        return user;
    }
​
​
    @GetMapping("/list")
    @MyLog("查询全部") // 使用日志注解
    public List<User> selectAll() {
        return userService.selectAll();
    }
}

切面类

package com.qf.aspect;
​
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
​
import javax.servlet.http.HttpServletRequest;
​
import com.qf.annotation.MyLog;
import com.qf.model.Log;
import com.qf.model.User;
import com.qf.service.MyLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.WebUtils;
​
/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@Component
@Aspect
public class MyLogAspect {
    // 注入业务层对象
    @Autowired
    private MyLogService logService;
​
    // 切入的目标是注解
    @AfterReturning("@annotation(com.qf.annotation.MyLog)")
    public void after(JoinPoint joinPoint) {
        // 获得注解中的值
        String desc = getAnnoDesc(joinPoint);
​
        System.out.println("注解中的值:" + desc);
        // 以下代码需要使用Springmvc,既控制层也需要由Spring托管,才会获得ip
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes( )).getRequest( );
        // 能取出数据的前提是登录时将用户信息存储到session
        User user = (User) WebUtils.getSessionAttribute(request, "user");
​
        String name = "未登录";
        if (user != null) {
            name = user.getName( );
        }
​
        //ip
        String ip = request.getRemoteAddr( );
        System.out.println("请求对象的ip:" + ip);
        // 封装数据
        Log log = new Log( );
        log.setName(name);
        log.setIp(ip);
        log.setLogTime(new Date());
        log.setLog(desc);
        // 调用业务层,执行插入
        logService.insertLog(log);
    }
​
    /*
     * 封装的方法,获得目标方法上的注解的值
     */
    public static String getAnnoDesc(JoinPoint joinPoint) {
        String value = "";
        // 根据目标对象得到全部方法
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        /*
         *  1.遍历所有方法
         *  2.得到方法上的所有注解
         *  3.判断注解的类型是否是日志注解
         *  4.如果是,就获取该注解对象
         *  6.由注解对象得到注解值
         */
        // 1.遍历所有方法
        for (Method method : methods) {
            // 2.得到方法上的所有注解
            Annotation[] annotations = method.getDeclaredAnnotations();
            for (Annotation annotation : annotations) {
                //  3.判断注解的类型是否是日志注解
                if(annotation.annotationType().equals(MyLog.class)){
                    // 4.如果是,就获取该注解对象
                    // 6.由注解对象得到注解值
                    value = method.getAnnotation(MyLog.class).value();
                }
            }
​
        }
        return value;
    }
}

启动项目,浏览器访问测试

image-20230819190408990

image-20230819190421386

ps: ip是因为使用localhost访问,换成126.0.0.1来访问就会正常

总结:

1.IOC + DI  核心
2.AOP
    1.service下面的一个方法   add
    2.想在add方法的前面执行一个方法  咋办?
        1.切面类   @Component  @Aspect
        2.写一个前置通知  @Before("路径")
        3.通知的下面写执行的方法 是add方法之前的执行的方法

4.SpringMVC

其实是Spring框架中关于web,webmvc开发的一个技术

Spring核心ioc,aop,web开发

SpringMVC 处理请求和响应

4.1MVC

MVC架构: 根据不同的事情由不同的类去处理,内部单一职责

  • Model: 模型类,例如封装数据的实体类(Pojo/Entity/Model),业务模型(Service),数据层(Dao)

  • View: 视图,展示数据的.HTML,JSP

  • Controller: 控制器,控制整个流程走向. 决定是否能接收请求,调用哪个业务,跳转哪个页面,Servlet


SpringMVC框架特点

  • 封装了Servlet

  • 接收请求方便(一个类中,不同的方法就可以接收不同的请求)

  • 接收请求数据方便(自动封装)

  • 响应数据方便(自动响应json)

image-20230824104944830

4.2请求和响应

@GetMapping(“/路径“)和@PostMappimg(”/路径“)利用两种不同的请求方式注解绑定映射路径和处理请求方法,返回值就是响应(跳转页面)

image-20230818200557052

例:

package com.qf.controller;
​
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
​
@Controller
public class AdminController {
    /**
     * 演示不同的请求方式  get   post
     *
     */
    //@RequestMapping(value = "/test", method = RequestMethod.GET)
    @GetMapping("/test")
    //@RequestMapping("/test")
    //当服务器运行之后,输入  localhost:8080/test的时候就会执行 当前的对方法test
​
    public String test () {
        System.out.println("当前的路径test, 请求方式是get");
        return  "ok.html";
    }
    //@RequestMapping(value = "/test1", method = RequestMethod.POST)
    //当服务器运行之后,输入  localhost:8080/test的时候就会执行 当前的对方法test
    @PostMapping("/test1")
    public String test1 () {
        System.out.println("当前的路径test, 请求方式是post");
        return  "ok.html";
    }
}
​

4.3参数绑定

所谓参数绑定,就是前端发请求中的数据,可以直接在Controller的方法参数中接收.即前端请求数据和后端方法参数绑定.

4.3.1 简单类型参数绑定

简单类型指,常用的几种类型: 基本类型+String+Date

前端页面

<h2>演示接收简单的数据类型(基本类型,String, Date)
</h2>
<a href="/simple?id=1&name=goudan&birth=2024-02-21">fasong</a>
<form action="/simple2" method="get">
    id:<input type="text" name="id"><br>
    name:<input type="text" name="username"> <br>
    score:<input type="text" name="score"> <br>
    birth: <input type="date" name="birth"> <br>
    <input type="submit" value="tijiao">
</form>

后端接收

/**
     * 演示接收前端的发送的数据给controller方式
     * 前端和后端数据的绑定靠名字
     *      前端发送数据的key 要和后端方法的参数保持一致即可
     *      【特殊的】:  前端发送的数据日期格式如果是 yyyy-MM-dd
     *          springMVC是无法绑定的
     *          默认支持的yyyy/MM/dd
     *          如果就要用yyyy-MM-dd  可以对方法的参数加一个注解
     *          @DateTimeFormat(pattern = "yyyy-MM-dd")
     *
     */
    @RequestMapping("/simple")
    public String simple (int id, String name, @DateTimeFormat(pattern = "yyyy-MM-dd") Date birth) {
        System.out.println("这个是简单的请求:" + id + ":" + name+ ":" + birth);
        return  "ok.html";
    }
    @RequestMapping("/simple2")
    public String simple2 (int id, String username, double score,@DateTimeFormat(pattern = "yyyy-MM-dd") Date birth) {
        System.out.println("这个的基本请求:" + id + ":" + username+ ":" + birth + ":" + score);
        return  "ok.html";
    }
​
4.3.2 对象

开发用的最多的!!!

场景: 注册/添加/更新

实体类

public class User {
​
    private int id;
    private String username;
    private String password;
    private double score;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
    // setget
}

前端

<h2>对象数据绑定</h2>
<form action="/obj" method="get">
    id<input type="text" name="id"><br>
    username<input type="text" name="username"><br>
    password<input type="text" name="password"><br>
    score<input type="text" name="score"><br>
    birthday<input type="date" name="birthday"><br>
    <input type="submit" value="对象类型">
</form>

后端

  
  /**
     * 自动绑定: 要求前端的请求中的参数要和对象的属性名一致
     */
    @GetMapping("/obj")
    public String obj(User user){
        System.out.println("user = " + user);
        return "ok.html";
    }
4.3.3 数组

场景: 批量删除需要同时接收多个id, (前端是复选框的)

delete from tb_user where id in (1,2,3,4)

前端

<h2>数组绑定</h2>
<form action="/array" method="get">
     <input type="checkbox" name="ids" value="1">1
     <input type="checkbox" name="ids" value="2">2
     <input type="checkbox" name="ids" value="3">3
     <input type="checkbox" name="ids" value="4">4
    <input type="submit" value="数组类型">
</form>

后端

   
 /**
     * 自动绑定: 要求前端的请求中的参数要和方法参数名(数组名)一致
     */
    @GetMapping("/array")
    public String array(int[] ids){
        System.out.println("ids = " + Arrays.toString(ids));
        return "ok.html";
    }

4.3.4 List集合

List集合使用场景与数组是一样的

前端

<h2>List绑定</h2>
<form action="/list" method="get">
    <input type="checkbox" name="skill" value="Java">Java
    <input type="checkbox" name="skill" value="HTML">HTML
    <input type="checkbox" name="skill" value="Linux">Linux
    <input type="submit" value="List类型">
</form>

SpringMVC默认是不支持直接封装List的,解决方案:

  • 加注解@RequestParam

   
 @GetMapping("/list")
    public String list(@RequestParam List<String> skill){
        System.out.println("skill = " + skill);
        return "ok.html";
    }

4.3.5 Map集合

Map是键值对,键和值一一映射.

跟Java对象很类似,属性和属性值一一对应.

所以什么时候需要/可以使用Map类型来接收参数呢?

- 凡是可以用对象接收的都可以使用Map

SpringMVC默认不支持直接将参数封装进Map,需要使用@RequestParam

前端

<h2>Map绑定</h2>
<form action="/map" method="get">
    id<input type="text" name="id"><br>
    username<input type="text" name="username"><br>
    score<input type="text" name="score"><br>
    birthday<input type="date" name="birthday"><br>
    <input type="submit" value="Map类型">
</form>
<h2>模糊查询-Map绑定</h2>
<form action="/map" method="get">
    address<input type="text" name="address"><br>
    floor<input type="text" name="floor"><br>
    deco<input type="text" name="deco"><br>
    <input type="submit" value="模糊-Map类型">
</form>
name就是map的key
输入框的值就是map的value

后台

    
@GetMapping("/map")
    public String map(@RequestParam Map<String,Object> map){
        System.out.println("map = " + map);
        return "ok.html";
    }
4.4 路径参数@PathVariable

参考这个路径

https://blog.csdn.net/weixin_39641494/article/details/131625212

这个路径中weixin_39641494是用户编号,131625212是文章id

@GetMapping("/{userid}/article/details/{aid}")

前端

<h2>路径参数绑定</h2>
<a href="/user/101">路径参数101</a>

后端

  
  @GetMapping("/user/{id}")
    public String path(@PathVariable int id){
        System.out.println("id = " + id); // id=101
        return "ok.html";
    }

ps: 能接收到请求中的id为101,但是响应回报错.因为使用@PathVariable要求返回的是json数据而不是页面,这个暂时先不管

4.5 页面跳转[熟悉]

回顾之前学过的servlet中跳转页面的功能

  • 请求转发:forward

    • req.getDispatcherServlet().forward(req,resp)

    • 请求路径不变

    • 是服务器内部请求

    • 一次请求

    • 请求域的数据可以共享

  • 重定向:redirect

    • resp.sendRedirect();

    • 请求路径改变

    • 是浏览器行为

    • 两次请求

    • 请求域的不能共享

请求转发

注意: 现在我们一直都在使用请求转发,因为默认就是请求转发跳转页面

也可以手动显示的在Controller的方法的返回值中写forward:路径即可完成跳转

例如: forward:/ok.html forward:/test

注意: 跳转后的路径要写完整

  
 /**
     * 演示请求转发至其他页面
     * @return
     */
    @GetMapping("/forward")
    public String forward(){
        System.out.println("执行请求转发" );
        return "forward:/ok.html";
    }
​
    /**
     * 演示请求转发至其他请求
     * @return
     */
    @GetMapping("/forward2")
    public String forward2(){
        System.out.println("执行请求转发" );
        return "forward:/test";
    }

重定向

在Controller的方法的返回值中写redirect:路径即可完成跳转

例如: redirect:/ok.html redirect:/test

注意: 跳转后的路径要写完整

  
 /**
     * 演示重定向至其他页面
     * @return
     */
    @GetMapping("/redirect")
    public String redirect(){
        System.out.println("执行重定向" );
        return "redirect:/ok.html";
    }
​
    /**
     * 演示重定向至其他请求
     * @return
     */
    @GetMapping("/redirect2")
    public String redirect2(){
        System.out.println("执行重定向" );
        return "redirect:/test";
    }

其他的请求转发和重定向的特点和之前学习的servlet是一样的,复习.

4.6 会话

如果需要在控制层中使用session存储会话数据,比如登录的用户信息,就可以直接在方法的参数列表中定义HttpSession对象即可

    @GetMapping("/ts")
    public String testSession(HttpSession session) {
        session.setAttribute("aa","AA");
        System.out.println(session.getAttribute("aa"))
        return "ok.html";
    }
4.7 拦截器

使用步骤,与Servlet中的过滤器思路基本一致

  • 编写自定义拦截器类

  • 实现接口

  • 重写拦截方法 preHandle()

  • 配置拦截器

    • 这个不一样,以前是配置在web.xml中或者加上注解@WebFilter

    • 现在SpringBoot推荐使用java类的方式配置

自定义拦截器类

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 System.out.println("my preHandle");
 // false  所有都拦截,排除在外的不拦截
 return false;
    }
​
}

拦截器配置类

@Configuration // !!!加注解!!!!配置类
public class MyConfig implements WebMvcConfigurer {
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
 // addPathPatterns 定义拦截的路径
 // excludePathPatterns 定义放行的路径
 // 这两个方法支持不定长参数,可以设置多个拦截/放行路径
 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/test");
    }
}

4.8josn处理

响应:目前就是转发和重定向

真正的前端项目都适合后端返回json数据给前端

后续工作项目,都是前后端分离开发前后端使用JSON数据交互

方法上加@ResponseBody,会在任何解析为json返回

例:

@RestController
public class Dome1Controller {
    
//    将字符串转为json
    @GetMapping("json1")
    public String json1(){
        String str = "{\"id\":1,\"name\":\"刘大\"}";
        return str;
    }
    
    //自定义对象响应成json
    @GetMapping("json2")
    public User json2(){
        List<String> list = new ArrayList<>();
        list.add("电脑");
        list.add("学习");
        list.add("汽车");
        User user = new User(1,"金二",18,new Date(),"list");
        return user;
    }
​
//    用Map集合添加对象属性转json
    @GetMapping("json3")
    public Map<Object,Object> json3(){
        HashMap<Object, Object> map = new HashMap<>();
        map.put("id",2);
        map.put("name","张三");
        return map;
    }
​
//    将map集合转换为json
    @GetMapping("json4")
    public Map<Object,User> json4(){
        HashMap<Object, User> map = new HashMap<>();
        map.put(1,new User(3,"李四",16,new Date(),"游泳"));
        map.put(2,new User(2,"王五",17,new Date(),"洗澡"));
        return map;
    }
​
//    将list集合转为json  开发常用
    @GetMapping("json5")
    public List<User> json5(){
        List<User> list = new ArrayList<>();
        list.add(new User(1,"朱六",15,new Date(),"泡澡"));
        list.add(new User(2,"温七",14,new Date(),"吸烟"));
        list.add(new User(3,"崔八",19,new Date(),"吃饭"));
        return list;
    }
}

前后端交互,定义的类,用于返回封装数据返回JSON

package com.qf.utils;
​
public class R {
    private int code;
    private String msg;
    private Object data;
​
    /**
     * 提取几个工具方法
     * @return
     */
    /**
     * 当成功时候 返回的一个json格式
     * @return
     */
    public static R ok() {//写死的code和msg的数据
        R r = new R();
        r.setCode(200);
        r.setMsg("成功");
        return r;
    }
​
    //写的自定的code 和msg
    public static R ok(int code, String msg) {
        R r = new R();
        r.setCode(code);
        r.setMsg(msg);
        return r;
    }
    public static R ok(Object data) {
        R r = new R();
        r.setCode(200);
        r.setMsg("成功");
        r.setData(data);
        return r;
    }
    public static R ok(int code, String msg, Object data) {
        R r = new R();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }
    public static R fail() {//写死的code和msg的数据
        R r = new R();
        r.setCode(1000);
        r.setMsg("失败");
        return r;
    }
    public static R fail(int code, String msg) {//写死的code和msg的数据
        R r = new R();
        r.setCode(code);
        r.setMsg(msg);
        return r;
    }
    public int getCode() {
        return code;
    }
​
    public void setCode(int code) {
        this.code = code;
    }
​
    public String getMsg() {
        return msg;
    }
​
    public void setMsg(String msg) {
        this.msg = msg;
    }
​
    public Object getData() {
        return data;
    }
​
    public void setData(Object data) {
        this.data = data;
    }
}

4.9文件上传

图片上传

上传tomcat服务器/上传本地磁盘

1、前端

<h2>文件上传</h2>
<!--
    文件上传必须是post请求
    修改表单发送数据的类型为文件类型
-->
<form action="/upload" method="post" enctype="multipart/form-data">
    文件<input type="file" name="img"><br>
    <input type="submit" value="上传"><br>
</form>

2、后端: 上传到tomcat服务器上

    
    <!-- 使用工具类 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
package com.qf.controller;
​
import com.qf.utils.R;
import org.apache.commons.io.FileUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
​
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
​
@RestController
public class Demo2UploadController {
    @PostMapping("/upload")
    /**
     * 琢磨
     * 1.前端给的数据是一个img的照片数据,后端upload咋绑定
     * 这个参数名要和前端input的名字一致,都是img
     * MultipartFile:  就是前端上传的图片的数据对象
     */
    public R upload(MultipartFile img, HttpServletRequest request) throws IOException {
        //1.获取的上传文件对象  是img
        //2.获取上传图片的路径(上传到当前服务器下面)  通过servlet的请求对象获取
        String realPath = request.getServletContext().getRealPath("/upload");
        System.out.println(realPath);
        /*C:\\Users\\bowang\\AppData\\Local\\Temp\\tomcat-docbase.8080.668384241469616864\\upload*/
        //3.将图片路径创建一下
        File file = new File(realPath);//将字符串的路径抽象成为file对象
        if (!file.exists()) {//判断路径是否存在
            file.mkdir();//不存在就创建
        }
        //4.获取文件的名字
        String fileName = img.getOriginalFilename();
        System.out.println("filename: " + fileName);//1.png
        //5.根据.拆分字符串,获取文件的后缀名字
        String[] split = fileName.split("\\.");
        System.out.println(Arrays.toString(split));
        //[2, 3, png] 2.3.png
        String suffix = split[split.length - 1];
        System.out.println(suffix);
        //6上传到服务器中你的文件得有名字
        //以当前的毫秒数的值作为文件的名字
        long prefix = new Date().getTime();
        //7.组装上传文件的名字
        String newFileName = prefix + "." + suffix;
        System.out.println(newFileName);//1708584979622.png
        //8.确定上传文件路径
        File newFile = new File(file, newFileName);
        //9.用工具类上传
        FileUtils.writeByteArrayToFile(newFile, img.getBytes());
        return R.ok("http://localhost:8080/upload/"+ newFileName);
    }
​
}

4.10 异常处理

SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。

//@ControllerAdvice是声明异常处理器
@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R handlerException(Exception exception) {
        System.out.println("异常处理器捕捉到异常对象"+exception);
        return R.fail();
    }
}

5.Mybatis

之前的JDBC缺点

  • 大量代码重复

  • 手动加载驱动,创建连接(connection),关流

  • 封装数据麻烦(ORM)

  • 效率不高(没有缓存)

5.1Mybatis介绍

Mybatis是一款持久层框架(Dao)框架,支持持久化SQL、存储过程及高级映射。Mybatis避免了几乎所有JDBC代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。且有缓存机制,可以提高查询效率。

5.2XML方式整合Mybatis
5.2.1环境配置

mybatis和druid的springboot环境下的依赖

<!-- 小辣椒  实体类 的set和get方法不用写了  用@Data这个注解即可 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
​
<!-- druid 数据库连接池   德鲁伊连接池 
    为啥要使用连接池  connection
    连接池:  搞出来cinnection  存到池子中
-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
​
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

安装小辣椒插件

image-20230821175201225

image-20230825145211290

准备数据库

create table tb_user(
  id int primary key auto_increment,
  username varchar(50),
  password varchar(50),
  phone varchar(50),
  createTime date,
  money double
)default charset = utf8;

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
​
    private int id;
    private String username;
    private String password;
    private String phone;
    private Date createTime;
    private double money;
}
5.2.2编写接口和映射文件

接口就是我们之前的Dao层接口,Mybatis习惯叫做Mapper

public interface Userapper {
   User findUserById(int id)
}

在resources/下创建mapper文件夹,在其内创建mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace名称空间 -->
<!-- 用来关联,映射文件(XML)和接口文件,即接口的类路径 -->
<mapper namespace="com.qf.mapper.UserMapper">
​
    <!--
        select标签,用于执行查询语句
        id: 是接口中的方法名
        resultType: 返回的结果类型,是接口对应方法的返回值
        [现在只写User报错,要写完整类路径]
     -->
    <select id="findUserById" resultType="com.qf.model.User">
        <!-- #{id} 就相当于是之前的预处理的 ?,会自动给此处复制  -->
        <!-- 其实就是接口方法的参数列表的值,会传给 #{id} -->
        select * from tb_user where id = #{id}
    </select>
</mapper>

5.2.3 yaml文件
# mybatis配置
mybatis:
  # 扫描映射文件
  mapper-locations: classpath:mapper/*.xml
  # 配置别名扫描的包
  type-aliases-package: com.qf.model
  configuration:
    # 开启驼峰映射配置
    map-underscore-to-camel-case: true
# 连接数据库的信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://ip:3306/test_springboot?useSSL=false
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
# yml文件
logging:
  level:
    com.qf.mapper: DEBUG   
 

5.2.4 扫描mapper

主类扫描mapper

@SpringBootApplication
@MapperScan("com.qf.mapper") // 扫描mapper接口,创建代理对象
public class TestSpringbootApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(TestSpringbootApplication.class, args);
    }
}
5.2.5 测试
@RestController
public class TestMybatisController {
​
    @Autowired
    private UserMapper userMapper;
​
    // http://localhost:8080/find?id=1
    @GetMapping("/m1")
    public R testMybatis1(int id) {
        User user = userMapper.findUserById(id);
        return R.ok(user);
    }
}

6 .CRUD

6.1 查询
6.1.1 单条件查询

略,详情看入门演示

【特别注意:ORM时字段名要和实体类属性一致,否则封装失败】

<select id="findUserById" resultType="com.qf.model.User">
    <!-- #{id} 就相当于是之前的预处理的 ?,会自动给此处赋值  -->
    <!-- 其实就是接口方法的参数列表的值,会传给 #{id} -->
    <!-- 查询返回的结果集,会自动封装到resultType指定的对象!! -->
    <!--  但是ORM能自动封装有个前提: 查询返回的列名和实体类的属性名要完全一致-->
    <!--
        select id uid,username uname,password pwd,phone tel,createTime,sex,money from tb_user where id = #{id}
-->
    select id,username,password,phone,createTime,sex,money from tb_user where id = #{id}
</select>
6.1.2 查询全部

设计查询接口方法

package com.qf.mapper;
​
import com.qf.pojo.User;
import org.springframework.stereotype.Repository;
​
import java.util.List;
​
//在controller中调用  service方法  ,在service中调用  UserMapper的方法 叫findById
//意味着  通过一个请求 可以执行findById方法
//写UserMapper这个接口的映射的mapper的xml文件
//xml文件有sql语句
@Repository
public interface UserMapper {
    //接口中一个方法
    //xml文件中一个sql
    User findById(Integer id);
    List<User> findAll();
}
​

映射文件

  
  <!-- 一个标签,就是一个SQL执行的语句 -->
    <!-- 【注意】虽然查询返回集合,但是返回类型此处还要写集合中存储的类型 -->
    <!-- 【或者这样理解】虽然返回集合,此处定义的是查询返回要封装的实体类类型 -->
    <select id="findAll" resultType="com.qf.model.User">
        select * from tb_user
    </select>
6.1.3 多参数查询

需求: 通过用户名和密码查询

接口方法

public interface UserMapper {
    User findUserByLogin(String username,String password);
}

映射文件

<select id="findUserByLogin" resultType="com.qf.model.User">
    <!-- 默认是不支持传多个参数,传入多个参数时,需要如下操作(2选1) -->
    <!--
            方案1: #{}内按顺序写param1,param2,....
        -->
    select * from tb_user where 
    username = #{param1} and password = #{param2}
</select>



接口方法(参数加注解)

public interface UserMapper {
​
    User findUserByLogin(@Param("username") String username, @Param("password") String password);
​
}

映射文件

    
<select id="findUserByLogin" resultType="com.qf.model.User">
        <!-- 默认是不支持传多个参数,传入多个参数时,需要如下操作(2选1) -->
        <!--
            方案2: 1)在接口方法添加注解@Param 2)在#{}内写注解的值
        -->
        select * from tb_user where 
        username = #{username} and password = #{password}
    </select>
6.1.4 Map参数查询

需求: 查询时,就要传递分页数据,又要传递模糊查询关键词,此时就可以使用Map来封装参数.

select * from tb_user where username like "%狗%" limit 0,3; 多参可以写的!!!

发现多参我写的时候报错了 有两种解决方案,太麻烦了

干脆用map吧

接口方法

public interface UserMapper {
    User findUserByLoginMap(HashMap<String,Object> map);
}

映射文件

<select id="findUserByLoginMap" resultType="com.qf.model.User">
    <!-- 参数是Map,#{}内写的map的key -->
    select * from tb_user where username = #{usernameKey} and password = #{passwordKey}
</select>

增加和修改都是传对象修改
6.2 增加

页面

<h2>注册</h2>
<form action="/add">
    用户名<input type="text" name="username"><br>
    密码<input type="password" name="password"><br>
    手机号<input type="text" name="phone"><br>
    性别<input type="radio" name="sex" value="1">男
        <input type="radio" name="sex" value="2">女 <br>
      <input type="submit" name="注册"><br>
</form>

接口方法

public interface UserMapper {
    int addUser(User user);
}

映射文件

<!--    values后面的#{}里面的内容要和接口方法的参数user的属性保持一致-->
    <insert id="addUser">
​
        insert into tb_user (username, password, phone, createTime, money)
        values(#{username}, #{password}, #{phone}, #{createTime}, #{money})
    </insert>
6.3 修改

前端页面

<h2>更新</h2>
<form action="/update">
    <!-- type属性指定hidden,即可将输入框隐藏 -->
    <input type="hidden" name="id" value="8"><br>
    用户名<input type="text" name="username"><br>
    密码<input type="password" name="password"><br>
    手机号<input type="text" name="phone"><br>
    性别<input type="radio" name="sex" value="1">男
    <input type="radio" name="sex" value="2">女 <br>
    余额<input type="text" name="money"><br>
    <input type="submit" name="更新"><br>
</form>

接口方法

public interface UserMapper {
    int updateUser(User user); // 修改方法的参数是对象
}

映射文件

 <!--    values后面的#{}里面的内容要和接口方法的
 <!--    values后面的#{}里面的内容要和接口方法的参数user的属性保持一致-->
    <update id="updateUser">
​
        update  tb_user  set
        username = #{username},
        password = #{password},
        phone = #{phone},
        createTime = #{createTime},
        money = #{money}
        where id=#{id}
    </update>
参数user的属性保持一致-->
    <update id="updateUser">
​
        update  tb_user  set
        username = #{username},
        password = #{password},
        phone = #{phone},
        createTime = #{createTime},
        money = #{money}
        where id=#{id}
    </update>

6.4 删除

页面

<h2>删除</h2>
<a href="/delete?id=8">删除id=8</a>
public R delete (int id) {
    userService.deleteUser(id);
}

接口方法

    
int deleteById(int id);

映射文件

 <delete id="deleteById">
        delete from tb_user where id = #{id}
    </delete>

6.5ORM映射

ORM映射就是数据库中的数据赋值给实体类

MyBatis要把数据库中的数据赋值给实体类

要保证字段和实体类的属性对应,要不然赋值不上

6.5.1MyBatis自动ORM失效

MyBatis只能自动维护库表“”列名“与”属性名“相同时的,当两者不同时,无法自动ORM对象关系映射

            自动ORM失效

image-20230601164912980

6.5.2解决方案:列与别名

在SQL中使用as为查询字段添加例别名,匹配属性名

<mapper namespace="com.qf.day0226.mapper.TbUserMapper">
    <select id="findById" resultType="com.qf.day0226.pojo.TbUser">
        select u_id as id,
        u_username as username,
        u_password as password,
        phone as phone,
        money as money,
        date as date
        from tb_user
        where
        u_id = #{id}
    </select>
<mapper>

6.5.3解决方案二:结果映射(ResultMap-查询结果的封装规则)

通过<resultMap id="里面起别名" type="里面写实体类路径",匹配列名与属性名

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.ManagerMapper">
<!-- orm映射失败的  第一种解决方案 起别名 【重点】
    自动的映射 是用resultType
    咱们可以换成手动  不要用resultType  用  resultMap
 -->
<!--    id就是sql的 resultMap  属性的值
        type:类型
-->
    <resultMap id="managerMap" type="com.qf.pojo.Manager">
<!--        id标签啥时候用?  是当数据库字段是主键的时候采用id-->
<!--        column 是数据库中查询出来的字段的数据
            property 是实体类中的属性的值
    -->
        <id column="mgr_id" property="id"></id>
<!--        result是普通房字段-->
        <result column="mgr_name" property="name"></result>
        <result column="mgr_pwd" property="password"></result>
    </resultMap>
    <select id="findById" resultMap="managerMap">
<!--       orm映射失败的第一种解决方案 起别名   查询出来的是虚拟表   id   name  password-->
        select mgr_id , mgr_name , mgr_pwd  from t_manager where mgr_id = #{id}
    </select>
</mapper>

6.6多表联查

表关系:,一对一,一对多,多对多

  • 1vs1 丈夫表 --> 妻子表

  • 1 vs n 用户 --> 车辆/房产

  • n vs n 老师/商品 --> 学生/订单

    多表联查的SQL:

    • 内连接

      • select * from 表1 inner join 表2 on 表1.字段 = 表2.字段

      • select * from 表1, 表2 where 表1.字段 = 表2.字段

    • 外连接

      • select * from 表1 left outer join 表2 on 表1.字段 = 表2.字段

    • 子查询

6.6.1 OneToOne

需求:实现一对一查询,查询订单以及对应的用户信息

数据:tu_user表

关系:

用户-->订单(1 vs N) 一个用户有多个订单
用户-->用户(1 vs 1) 一个订只属于一个用户

1.添加pom的相关jar包

<!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
​
        <!-- druid 数据库连接池   德鲁伊连接池
            为啥要使用连接池  connection
            连接池:  搞出来cinnection  存到池子中
        -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
​
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

2.写数据库连接配置yml文件

# mybatis配置
mybatis:
  # 扫描映射文件
  mapper-locations: classpath:mapper/*.xml
  # 配置别名扫描的包
  type-aliases-package: com.qf.pojo
  configuration:
    # 开启驼峰映射配置
    map-underscore-to-camel-case: true
# 连接数据库的信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/day45?useSSL=false
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
# yml文件
logging:
  level:
    com.qf.mapper: DEBUG

3.建表

tb_user表

CREATE TABLE `tb_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
  `username` varchar(10) DEFAULT NULL COMMENT '用户名',
  `password` varchar(10) DEFAULT NULL COMMENT '密码',
  `phone` varchar(11) DEFAULT NULL COMMENT '手机号',
  `create_time` date DEFAULT NULL COMMENT '注册时间',
  `money` double(10,2) DEFAULT NULL COMMENT '账户余额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;
 

tb_order表

CREATE TABLE `tb_order` (
  `oid` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `order_time` datetime DEFAULT NULL COMMENT '订单时间',
  `order_desc` varchar(255) DEFAULT NULL COMMENT '订单详情',
  `uid` int(11) DEFAULT NULL COMMENT '关联用户id',
  PRIMARY KEY (`oid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
​
INSERT INTO `tb_order` VALUES (1, '2022-11-17 15:06:29', '笔记本电脑', 1);
INSERT INTO `tb_order` VALUES (2, '2022-12-16 11:00:41', 'Cherry键盘', 1);
INSERT INTO `tb_order` VALUES (3, '2022-12-16 11:01:23', 'Logi鼠标', 2);

实体类

package com.qf.pojo;
​
import lombok.Data;
​
import java.util.Date;
​
@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private String phone;
    private Date createTime;
    private Double money;
}
​
package com.qf.pojo;
​
import lombok.Data;
​
import java.util.Date;
​
@Data
public class Order {
    private Integer oid;
    private Date orderTime;
    private String orderDesc;
    private Integer uid;
}

[重点]但是上面的实体类,只有订单信息,我们要查询的是订单和用户! 上面的类就无法展现以及封装全部数据,所以需要扩展类(即包含Order又包含User)

public class OrderVO extends Order {
    private User user;
    // set get
}

先讲一对一

一个订单对应一个用户

需求:通过订单查用户信息

语句咋写?

通过订单(order表)查用户(user表)信息

要多表联查

select *

from tb_order to

inner join tb_user tu

on to.uid = tu.id

where to.oid = 1

OrderMapper.java接口文件

package com.qf.mapper;
​
import com.qf.pojo.Order;
import com.qf.pojo.OrderVO;
​
public interface OrderMapper {
    //数据库中虚拟表的数据 ,和实体类对应好就可以了
    OrderVO findOrderAndUserByOid(Integer id);
}
​

OrderMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.OrderMapper">
    <resultMap id="orderAndUserMap" type="com.qf.pojo.OrderVO">
        <id column="oid" property="oid"></id>
        <result column="order_time" property="orderTime"></result>
        <result column="order_desc" property="orderDesc"></result>
        <result column="uid" property="uid"></result>
<!--        多表联查中,1对1的属性的封装需要借助于   association-->
<!--数据库中查询出来的   id  username  password 等 (多个数据)赋值给一个user属性-->
<!--   property 是OrderVO  中的user属性
        javaType是property的属性的类型
    -->
        <association property="user" javaType="com.qf.pojo.User">
<!--            从查询的虚拟表中将user对象进行赋值-->
            <id column="id" property="id"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="phone" property="phone"></result>
            <result column="create_time" property="createTime"></result>
            <result column="money" property="money"></result>
​
​
        </association>
    </resultMap>
    <select id="findOrderAndUserByOid" resultMap="orderAndUserMap">
        select *
​
        from  tb_order tbo
​
        inner join tb_user tu
​
        on tbo.uid = tu.id
​
        where  tbo.oid = #{id}
    </select>
</mapper>

测试 xie service controller即可

6.6.2 OneToMore

需求: 一对多,查询用户关联查询出所有的订单

select *
from tb_user tbu
inner join tb_order tbo
on  tbu.id = tbo.uid
where tbu.id = 1

目的查询用户,以及关联多个订单,User类不够展现全部数据,那么就创建扩展类UserVO,UserVO类继承User就可以存储用户信息,还需要再UserVO类中添加Order类来存储信息,但是!!不是一个Order类,因为是一对多,一个用户关联多个订单,所有要设置List<Order>

User扩展实体类

package com.qf.pojo;
​
import lombok.Data;
​
import java.util.List;
​
@Data
public class UserVO extends User{
    //一个用户下面可以有多个订单 所以得用List
    private List<Order> orderList;
}

UserMapper.java接口

package com.qf.mapper;
​
import com.qf.pojo.UserVO;
​
​
public interface UserMapper {
    UserVO findUserAndOrderById(Integer id);
}
​

UserMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.UserMapper">
    <resultMap id="userResultMap" type="com.qf.pojo.UserVO">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="phone" property="phone"></result>
        <result column="create_time" property="createTime"></result>
        <result column="money" property="money"></result>
<!--        1对n的时候 ,UserVO  有多个order对象  collection标签
            用的是ofType是集合中的类型【需要重点记住 和assciacation(javaType)的区别】
-->
        <collection property="orderList" ofType="com.qf.pojo.Order">
            <id column="oid" property="oid"></id>
            <result column="order_time" property="orderTime"></result>
            <result column="order_desc" property="orderDesc"></result>
            <result column="uid" property="uid"></result>
​
        </collection>
    </resultMap>
    <select id="findUserAndOrderById" resultMap="userResultMap">
        select *
        from tb_user tbu
        inner join tb_order tbo
        on  tbu.id = tbo.uid
        where tbu.id = #{id}
    </select>
</mapper>
6.6.3 关联查询总结
  • 实体类要设置扩展类以用于封装多表的数据

  • 正常封装使用resultMap

    • 一对一封装使用association

    • 一对多封装使用collection

6.7动态SQL

动态SQL是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

常见的动态SQL语法

  • SQL片段(官方不是在动态SQL章节)

  • where , if

  • set

  • trim

  • foreach

6.7.1SQL片段

这个元素可以用定义可冲用SQL代码片段,一遍在其他语句中使用

目的:减少代码重复,主要用于抽取字段、表名等

<!-- 将重复的SQL代码抽取成SQL片段,以供复用 -->
    <sql id="userField">
        id,
        username,
        password,
        phone,
        create_time,
        money,
        sex
    </sql>
​
    <select id="findAll" resultType="User">
        select
            <!-- 引入片段 -->
            <include refid="userField"/>
        from
            tb_user
    </select>
6.7.2 if

if就是用来判断,主要用于判断要不要拼接对应的条件语句

-- 需求:查询用户,
    1.用户为豪杰的或者 ,  select * from tb_user where username = "豪杰" or username="渊傲" and password = "250"
    用户名为渊傲的并且密码为250
select * from tb_user where money = 1000
select * from tb_user where money = 1000 and password= '123456'

UserMapper.java接口方法

public interface UserMapper {
    /**
     * 演示if动态sql
     */
    List<User> findByMap(HashMap<String,Object> map);
​
}

UserMapper.xml

  
  <select id="findByMap" resultMap="userResultMap">
        <!-- #{}写map的key -->
        select
            <include refid="userFields"/>
        from
            tb_user
        where 
            1 = 1 
        <!-- username是map的key -->
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="password != null and password != ''">
            and password = #{password}
        </if>
    </select>

测试

   
 @GetMapping("/map")
    @ResponseBody
    public R map(){
        HashMap<String, Object> map = new HashMap<>( );
        // map.put("password","123456");
        // map.put("username","Uzi");
        User user = userService.findByMap(map);
        if (user != null) {
            return R.ok(user);
        }
        return R.fail();
    }

注意: 通过application.yml添加日志,显示sql. 通过打印的sql来判断是否拼接成功

6.7.3 where

如果说只有if,可能会出现这么一种情况

SELECT * FROM tb_user WHERE 1=1

多出一个where关键词!!


所以我们需要一个智能的,有条件时帮我们拼接where关键词,没有条件查询时,不拼接where,并且去掉多余的and关键词,但是不会主动帮助拼接and

<!--    动态sql 需求:  用户名或者是用户名和密码 进行查询-->
    <select id="findByMap" resultType="com.qf.pojo.User">
        select *
        from tb_user
​
        <where>
            <!--        1.username password 都是null  对应的sql语句  select * from tb_user where 1=1
            2.username != null password=null 对应sql语句  select * from tb_user where username=?
            3.username =null password!=null 对应的sql语句  select * from tb_user where password = ?
            4.username !=null password!=null 对应的sql语句  select * from tb_user where username =? and password = ?
            4.
-->
            <if test="username != null and username != ''">
                and  username = #{username}
            </if>
            <if test="password != null and password !=''">
                and password = #{password}
            </if>
        </where>
​
    </select>

所以一般会where和if一起用

6.7.4 set

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

update  tb_user
<set>
    <if test="username!=null and username!=''">
        username = #{username},
    </if>
    <if test="password!=null and password!=''">
        password = #{password},
    </if>
    <if test="phone!=null and phone!=''">
        phone = #{phone},
    </if>
</set>
where id=#{id}

UserMapper.java接口方法

public interface UserMapper {
    int updateUser(User user);
}

UserMapper.xml

   
 <!-- set完成动态更新 -->
    <update id="updateUser">
        update tb_user  
        <!-- set标签自动拼接SET关键词 -->
        <set>
            <!-- 会自动过滤最后一个, -->
            <!-- 特别注意,因为判断条件是!=null,基本不可能为null,所以将基本类型变为包装类 -->
            <if test="username != null and username != ''">username = #{username},</if>
            <if test="password != null and password != ''">password = #{password},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="money != 0.0">money = #{money},</if>
            <if test="sex != 0">sex = #{sex},</if>
        </set>
        where id = #{id}
    </update>

测试

6.7.5 foreach

场景: 批量删除

delete from tb_user where id in (1,2,3,...);
delete from tb_user where id in(1,3)
String sql = "delete from tb_user where id in (";
idsArr  是多选之后,选中的数据的id数组
int[] idsArr = {1,3}
int iMax = idsArr.length - 1;// 最大下标
for (int i = 0; i < idsArr.length; i++) {
int id = idsArr[i];
sql += id;
if (i != iMax) {
sql += ",";
} else {
sql += ")";
}
}

UserMapper.java

public interface UserMapper {
    // 为了演示动态sql foreach
    int deleteBatch(List<Integer> ids);
}

UserMapper.xml

<!--    演示批量删除-->
    <delete id="deleteBatch">
        delete from tb_user where id in
        <!--
           <foreach>开始循环,取出集合中的数据
           collection,要遍历的集合,此处必须写list
           item , 临时变量,遍历得到结果,命名任意,但是下面#{}内的名字要和这里一致
           separator, 数据的分隔符
           open 开始遍历, 把(拼接上
           close 结束遍历 , 把)拼接上
        -->
        <foreach collection="list" open="(" item="id" separator="," close=")">
            #{id}
        </foreach>
    </delete>

测试

  
  // 批量删除
    @GetMapping("/delete/batch")
    // http://localhost:8888/delete?id=1&id=2&id=3
    public R deleteBatch(@RequestParam List<Integer> list){
        int i = userService.deleteBatch(list);
        if (i > 0) {
            return R.ok(i);
        }
        return R.fail();
    }
6.8 SpringBoot整合分页助手

关于分页有些数据

  • 默认访问首页,即默认当前页是 pageNum= 1

  • 数据有总条数, total = select count(*)

  • 页面大小/每页展示多少条数据, pageSize = 10

  • 总页数 , pageCount = total / pageSize (需要注意除不尽情况)


-- total共7条
select count(*) from tb_user
-- 每页多少条数据: pageSize 3条
-- 总页数pageCount
pageCount = total % pageSize == 0? total/pageSize :(total/pageSize)+1
-- 当前页pageNo=1
-- 查询第1页
select * from tb_user limit 0,3
-- 查询第2页
select * from tb_user limit 3,3
​
-- 查询第pageNo页
select * from tb_user limit (pageNo-1)*pageSize,pageSize
-- 查询第3页面
select * from tb_user limit 6,3

现在使用的是分页助手-pagehelper

  • 原理拦截sql,自动帮助我们拼接limit

select * from tb_user

6.8.1 导入依赖
<!-- pageHelper依赖-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <!-- 不能使用1.2.6版本,因为版本太低与springboot高版本不兼容,导致启动项目报错 -->
    <version>1.4.2</version>
</dependency>

6.8.2 测试使用
    
@GetMapping("/m2")
    public R testMybatis2() {
        // 使用步骤
        // 1 先设置分页信息
        PageHelper.startPage(1,2);
        // 2 正常执行查询
        List<User> list = mapper.findAll( );
        // 3 通过查询返回的list创建出分页信息,PageInfo内包含所有分页数据,可以点入源码查看
        PageInfo<User> info = new PageInfo<>(list);
        System.out.println("当前面"+info.getPageNum());
        System.out.println("页面大小"+info.getPageSize() );
        System.out.println("总条数"+info.getTotal() );
        System.out.println("总页数"+info.getPages() );
        System.out.println("数据"+info.getList());
        return R.ok(info);
    }
6.9 缓存(cache)【面试】

缓存主要目的就是为了提升效率,缓存就是提供一个内存空间,存储数据,(跟线程池效果一样)

MyBatis支持缓存,分为两种缓存

  • 一级缓存

  • 二级缓存

无缓存:用户在访问相同数据时,需要发起多次对数据库的直接访问,导致产生大量IO、读写硬盘的操作,效率低下

image-20230601165546620

没有缓存就跟线程池一样相同的数据都要去访问数据库,效率低下

有缓存:首次访问时,查询数据库,将数据存储到缓存中;再次访问时,直接访问缓存,减少IO、硬盘读写次数、提高效率

image-20230601165610966

而有了缓存之后首次访问会查询数据库,将数据存入缓存中,之后访问会访问缓存,提高效率

6.9.1 一级缓存

Mybatis的一级缓存是默认的,自动实现

默认的一级缓存是SqlSession级别,是同一个SqlSession发起多次查询同一条数据,会使用缓存

image-20221217112819746

ps: Mybatis内部存储缓存使用的是一个HashMap对象,key为 hashCode + sqlId + sql 语句。而value值就是从查询出来映射生成的java对象。

一级缓存SqlSession是第一次查询会存到缓存当中,后续查询相同数据就会调缓存数据,如果执行增删改会删除缓存,后续在执行相同查询操作,还需要查询数据库

6.9.1.1 命中缓存
   
 @GetMapping("/user/{id}")
    @Transactional // 需要开启事务才会生效一级缓存
    public R findUserById(@PathVariable int id) {
        User user = userMapper.findUserById(id);// 第一次查
        System.out.println(user );
        System.out.println("-------------" );
        User user2 = userMapper.findUserById(id);// 第二次查
        System.out.println(user2 );
        return R.ok(user);
    }

image-20230822180407157

6.9.1.2 清空缓存

在更新(更新,删除,插入)数据后,会清空缓存,下次重新查最新的数据.避免脏读

    
@GetMapping("/user/{id}")
    @Transactional
    public R findUserById(@PathVariable int id) {
        User user = userMapper.findUserById(id);// 查一次
        System.out.println(user );
        System.out.println("-------------------" );
        userMapper.deleteById(3);// 中间执行删除,会清空缓存
        System.out.println("-------------------" );
        User user2 = userMapper.findUserById(id);// 再查一次
        System.out.println(user2 );
        return R.ok(user);
    }

image-20230822180845189

6.9.2二级缓存

二级缓存是Mapper级别,比SqlSession级别范围更大

  • 需要在mapper中设置caseh

    <caseh/>
    • 映射语句文件中的所有select语句的结果会被缓存

    • 映射语句文件中的所有insert、update、和delete语句会刷新缓存,后续执行同样查询操作直接访问缓存

  • 1)需要在mapper中设置caceh即可

    image-20230822182357205

  • 2)实体类需要序列化,实现Serializable接口

    image-20230822182411220

   
 @GetMapping("/user/{id}")
    // 不需要开启事务
    public R findUserById(@PathVariable int id) {
        User user = userMapper.findUserById(id);
        System.out.println(user );
        System.out.println("-------------------" );
        User user2 = userMapper.findUserById(id);
        System.out.println(user2 );
        return R.ok(user);
    }

image-20230822182319152

终端用户访问存储时,如果在缓存中查找到了要被访问的数据,就叫做命中。当多次执行查询操作时,缓存命中次数与总的查询次数(缓存命中次数+缓存没有命中次数)的比,就叫做缓存命中率,即缓存命中率=缓存命中次数/总的查询次数。当 MyBatis 开启二级缓存后,第一次查询数据时,由于数据还没有进入缓存,所以需要在数据库中查询而不是在缓存中查询,此时缓存命中率为0,第一次查询过后,MyBatis 会将查询到的数据写入缓存中,当第二次再查询相同的数据时,MyBatis会直接从缓存中获取这条数据,缓存将命中,此时的缓存命中率为0.5,当第三次查询相同的数据,则缓存命中率为0.6666,以此类推

6.9.2.1 清空缓存

 @GetMapping("/user/{id}")
    public R findUserById(@PathVariable int id) {
        User user = userMapper.findUserById(id);
        System.out.println(user );
        System.out.println("-------------------" );
        userMapper.deleteById(2);// 删除会清空缓存
        System.out.println("-------------------" );
        User user2 = userMapper.findUserById(id);
        System.out.println(user2 );
        return R.ok(user);
    }
6.10 事务

@Transactional

   @GetMapping("/user/del")
    @Transactional // 加上注解,该方法执行时就有事务控制
    public R delUserById() {
        userMapper.deleteById(2);
        System.out.println(1/0 );
        userMapper.deleteById(2);
​
        return R.ok();
    }
 

这个注解就是和数据库的特性里原子性和一致性一样,当加上@Transactional这个注解前后执行的操作中加入异常的语句,事务会被回滚,所有的操作都将被撤销,保证数据的一致性。

6.11 注解方式整合Mybatis[了解,开发不用】

注解方式在编写配置简单,简单SQL推荐使用。不推荐 开发必须用 xml写法!!!

create table `dept`  (
  `deptno` varchar(255),
  `dname` varchar(255) ,
  `loc` varchar(255) ,
  primary key (`deptno`)
) ;
​
-- ----------------------------
-- Records of dept
-- ----------------------------
insert into `dept` values ('10', 'ACCOUNTING', 'NEW YORK');
insert into `dept` values ('20', 'RESEARCH', 'DALLAS');
insert into `dept` values ('30', 'SALES', 'CHICAGO');
insert into `dept` values ('40', 'OPERATIONS', 'BOSTON');
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Dept {
    private String deptno;
    private String dname;
}

6.11.1 Mapper接口
@Mapper
public interface DeptMapper {
​
    @Select("select * from dept")
    List<Dept> findAll();
​
}

6.11.2 添加Mybatis注解

针对增删改查:@Insert,@Delete,@Update,@Select

还是需要在启动类中添加@MapperScan注解

@Mapper
public interface EmpMapper {
    @Select("select * from emp where id = #{id}")
    public Emp getEmpById(int id); // 查一个
​
    @Insert("insert into emp (name,age,birthday) values (#{name},#{age},#{birthday})")
    public int insertEmp(Emp emp);// 增
​
    @Delete("delete from emp where id = #{id}")
    public int deleteEmpById(int id);//删
​
    @Update("update emp set name=#{name},age=#{age},birthday=#{birthday} where id=#{id}")
    public int updateEmpById(Emp emp);//改
​
}

6.12日志
6.12.1添加日志配置
# yml文件
logging:
  level:
    com.qf.testspringboot.mapper: DEBUG

6.12.2测试查看日志
@SpringBootApplication
@MapperScan("com.qf.testspringboot.mapper")
public class TestSpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestSpringbootApplication.class, args);
    }
}
// ============================================
@RestController
public class TestMybatisController {
   
    @Autowired
    private DeptMapper deptMapper;
​
    @GetMapping("/m2")
    public R testMybatis2() {
        List<Dept> list = deptMapper.findAll( );
        return R.ok(list);
    }
}

7、SpringBoot常用配置【重点


7.1 SpringBoot的配置文件格式

SpringBoot的配置文件,文件名必须是application,格式支持propertiesyml

更推荐使用yml文件格式:

  1. yml文件,会根据换行和缩进帮助咱们管理配置文件所在位置

  2. yml文件,相比properties更轻量级一些

  3. K: V 表示一对键值对(冒号: 后一定有一个空格)

  4. 以空格的缩进来控制层级关系;只要是左对齐的都是属于一个层级的数据

  5. 属性和值大小写敏感.

yml文件的劣势:

  1. 严格遵循换行和缩进

  2. 在填写value时,一定要在: 后面跟上空格

配置文件的作用

  • 修改SpringBoot的配置的默认值:

    比如默认启动的Tomcat的端口是8080,可以修改为8081

propertiesyml

image-20230625220509355

image-20230625220524979

image-20230823113117827

配置文件的位置:

  • 一般默认都是放在resources/下

  • 也有其他位置的,暂且不讨论

7.2 多环境配置

实际开发中,有三种环境:

1.开发环境-程序员日常开发所需

2.测试环境-项目的集成测试

3.生产环境-最终项目部署的环境,真实环境

SpringBoot支持多环境的配置。只需要根据环境需要,编写多个配置文件,通过配置属性选择使用哪个环境

使用步骤:

1.多环境的配置文件命名:application-环境名.yml

2.在总文件application.yml中通过属性:spring profiles active 环境名

image-20230625221539596

ps:也可在部署工程时,通过 java -jar jar文件 --spring.profiles.active=环境

7.3 获取配置文件信息

场景: ....

解释: 将yml配置的值,赋值给对应的类

方案:

  • 方案一: @ConfigurationProperties

  • 方案二: @Value

方案一: @ConfigurationProperties [了解]

# 示例
aliyun:
 accessKey: ATYSBD23B1N44
 accessSecret: 123456
@Data
@Component
@ConfigurationProperties(prefix = "aliyun") // yml中的前缀
public class AliyunProperties {
    // yml中的key
    private String accessKey; 
    private String accessSecret;
​
}
// java代码中使用AliyunProperties对象即可获得数据
@RestController
public class TestController {
​
    @Autowired
    private AliyunProperties aliyun;
​
​
    @GetMapping("/yml")
    public AliyunProperties testGetYmlValue(){
 return aliyun;
    }
​
}

image-20230625222751082

方案二: @Value [推荐]

// yml配置文件内容是一样的
// 不一样的是Java类中使用的注解
@Data
@Component
public class AliyunProperties {
​
    @Value("${aliyun.accessKey}")
    private String accessKey;
​
    @Value("${aliyun.accessSecret}")
    private String accessSecret;
​
}

7.4 热加载
7.4.1 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

7.4.2 settings配置
修改settings中的配置

image-20230625223935285

7.4.3 设置

编辑界面同时 ctrl+shift+alt+/ 选择registry,勾选自动部署

勾选自动部署

image-20230625224058755

注意,要以debug方式启动,代码写完,光标离开idea 过1-3秒钟就会自动更新...

7.5 API接口测试工具
http://localhost:8081/findAllUser
​
​
{"code":200,"msg":"成功","data":{"total":4,"list":[{"id":38,"username":"狗蛋","password":null,"phone":null,"createTime":null,"money":null}],"pageNum":2,"pageSize":3,"size":1,"startRow":4,"endRow":4,"pages":2,"prePage":1,"nextPage":0,"isFirstPage":false,"isLastPage":true,"hasPreviousPage":true,"hasNextPage":false,"navigatePages":8,"navigatepageNums":[1,2],"navigateFirstPage":1,"navigateLastPage":2}}

前言: springboot项目一般都是前后端分离开发,即我们idea中只有后台代码,那么现在写完成controller接收请求,调用业务层处理业务,调用dao层操作完数据库之后如何测试?

接口测试工具(api测试工具)就是来做这个事情的,即帮助测试接口

在这里如何理解接口? 此处的接口是前后端对接的"接口",是一套完整接收请求处理请求返回结果的代码,即那就是controller-service-dao层

有哪些接口测试工具

  • postman

  • apipost

  • apifox

  • 以及各种插件

安装fast-request插件

  • 下载压缩包到本地

  • idea中plaguin从本地磁盘加载插件

  • image-20230828114155867

选择到本地那个zip文件

  • 点击右下角apply , 后再点ok

  • 此时idea中就可以

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值