华清远见-框架阶段技术总结

框架

一套规范。

实际是他人实现的一系列接口和类的集合。通入导入对应框架的jar文件(maven项目导入对应的依赖),进行适当的配置,就能使用其中的所有内容。

开发者可以省去很多模板代码,如dao中的CRUD,MVC模式下层与层之间的关联。只需要集中精力实现项目中的业务逻辑部分。

Java主流框架

Spring、SpringMVC、MyBatis、MyBatisPlus、Hibernate、JPA等。

SSH:最初是Spring+Stucts2+Hibernate组成,之后Stucts2被SpringMVC取代。

SSM:Spring+SpringMVC+MyBatis

新项目使用SpringBoot,早起的SSH项目由于维护成本高,基本不会推翻重做,但会维护一些SSM项目。

无论是SSH还是SSM,Spring、SpringMVC必不可少。从2004年推出至今,依旧是主流框架中不可获取的一部分。

Spring

概念

一个轻量级开源的Java框架。是一个管理项目中对象的容器,同时也是其他框架的粘合器,目的就是对项目进行解耦。

轻量级:对原有代码的侵入很小。

IOC

Inversion Of Control 控制反转

DI

Dependency Injection 依赖注入

控制反转(IOC)是一种思想,就是让创建对象的控制权由自身交给第三方,控制反转这种思想,通过依赖注入(DI)的方式实现。

IOC和DI其实都是在描述控制反转,IOC是思想,DI是具体实现方式。

这里的第三方,就是Spring。Spring是一个容器,可以管理所有对象的创建和他们之间的依赖关系。

可以理解为:“Spring就是用来管理对象的,在需要用到某个对象的时候,帮我们自动创建”。

AOP

Aspect Orintend Programming 面向切面编程

Spring核心注解

在Spring配置文件中加入

<!--设置要扫描的包,扫描这个包下所有使用了注解的类-->
<context:component-scan base-package="com.hqyj.spring02.bookSystem"></context:component-scan>

类上加的注解

  • @Component
    • 当一个类不好归纳时,定义为普通组件
  • @Controller
    • 定义一个类为控制层组件
  • @Service
    • 定义一个类为业务层组件
  • @Repository
    • 定义一个类为持久层(数组访问层)组件
  • @Lazy/@Lazy(value=true)
    • 设置该类为懒加载。
  • @Scope(value=“singleton/prototype”)
    • 设置为单例/原型模式。

说明

以上注解公共特点

  • 都是将对应类的对象注入到Spring容器中,用于替换配置文件中的bean标签
  • 都默认是单例模式非懒加载
  • 默认注入的对象id为当前类的类名首字母小写形式
    • 如在BookDao类上添加,id默认为bookDao
  • 可以通过注解的value属性自定义注入的对象的id名,如@Component(value=“key”)表示注入的对象id为key

属性上加的注解

  • @Autowired

    • 优先使用byType方式从Spring容器中获取对应类型的对象自动装配。先检索Spring容器中对应类型对象的数量,如果数量为0直接报错;数量为1直接装配

      数量大于1,会再尝试使用byName方式获取对应id的对象,但要配合@Qualifier(value=“某个对象的id”)一起使用,指定id进行装配

  • @Qualifier(value=“某个对象的id”)

    • 配合@Autowired注解,使用byName方式获取某个对象id的bean进行装配
  • @Resource(name=“某个对象的id”)

    • 该注解相当于@Autowired+@Qualifier(value=“某个对象的id”)

    • 优先使用byName方式,从Spring容器中检索name为指定名的对象进行装配,如果没有则尝试使用byType方式,要求对象有且只有一个,否则也会报错。

说明

  • 如果要在某个类中使用Spring容器中的某个对象时,只需定义成员变量,无需创建对象,通过@Autowired或@Resource注解进行自动装配

  • 实际开发中,绝大部分情况下,需要自动装配对象有且只有一个,并且命名规范,所以@Autowired或@Resource区别不是很大。@Autowired优先使用byType方式,@Resource优先使用byName方式

  • 如果@Resource不能使用,是因为缺少javax.annotation包,需要引入对应依赖

    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>
    

MVC

MVC设计思想并不是某个语言特有的设计思想,而是一种通用的模式。

是将一个应用分为三个组成部分:Model模型,View视图,Controller控制器

这样会降低系统的耦合度,提高它的可扩展性和维护性。

SpringMVC

在Web阶段中,控制层是由Servlet实现,传统的Servlet,需要创建、重写方法、配置映射。使用时极不方便,SpringMVC可以替换Servlet

SpringMVC是Spring框架中位于Web开发中的一个模块,是Spring基于MVC设计模式设计的轻量级Web框架。

SpringMVC提供了一个DispatcherServlet的类,是一个Servlet。它在指定映射(通常设置为/或*.do)接收某个请求后,调用相应的模型处理得到结果,再通过视图解析器,跳转到指定页面,将结果进行渲染。

原理大致为:配置SpringMVC中的DispatcherServlet将其映射设置为/或.do。*

如果是/表示一切请求先经过它,如果是*.do表示以.do结尾的请求先经过它,

它对该请求进行解析,指定某个Controller中的某个方法,这些方法通常返回一个字符串,

这个字符串是一个页面的名称,再通过视图解析器,将该字符串解析为某个视图的名称,跳转到该视图页面。

使用SpringMVC

1.创建webapp项目

  • 修改web.xml版本为4.0
  • 创建java和resources目录
  • 创建包结构

2.添加依赖

<!-- spring-webmvc -->
<!-- 这个依赖会包含spring-web和spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.23</version>
</dependency>

3.配置初始化Spring

  • 在resources目录下创建Spring配置文件application.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">
    
        <!--设置扫描使用了注解的根包-->
        <context:component-scan base-package="com.hqyj.springmvc"></context:component-scan>
    </beans>
    
  • 在web.xml中使用监听器ContextLoaderListener初始化Spring

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!--配置全局监听器初始化Spring-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!--定义全局参数读取Spring配置文件-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:application.xml</param-value>
        </context-param>
    </web-app>
    

4.配置SpringMVC

  • 在resources目录下创建配置SpringMVC的配置文件springmvc.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">
    
    
        <!--扫描控制层所在的包-->
        <context:component-scan base-package="com.hqyj.springmvc.controller"></context:component-scan>
    
        <!--配置内部资源视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!--最终控制层跳转的页面所在的路径及页面自身后缀名-->
            <!--jsp页面不建议直接通过浏览器访问。在WEB-INF目录下在资源,无法通过浏览器直接方法,所以将jsp保存在WEB-INF目录下,最好创建一个pages-->
            <property name="prefix" value="/WEB-INF/pages/"></property>
            <!--现阶段使用jsp输出数据,所以后缀为.jsp-->
            <property name="suffix" value=".jsp"></property>
        </bean>
    </beans>
    
  • 在web.xml中配置DispatcherServlet

    • 将该Servlet的请求映射设置为/,表示所有请求都会访问该Servlet,由该Servlet再进行分发
    <!--配置DispatcherServlet-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--设置该Servlet的初始化参数,用于读取SpringMVC配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--设置该servlet的映射为/或*.do-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

5.在WEB-INF目录下创建一个pages目录,在其中创建一个welcome.jsp页面

  • 通常jsp页面不允许被浏览器直接访问,需要保存在WEB-INF目录下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jeggBi2T-1676453638385)(D:\框架\SpringMVC.assets\image-20230114144258023.png)]

6.编写控制层代码

  • 在controller包下创建一个类,加上@Controller注解

  • 该类中定义的方法方法加入@RequestMapping()注解表示访问该方法的映射

  • 该类中定义的方法返回值通常为字符串,表示某个页面的名称,也可以是另一个controller中的方法映射名

    package com.hqyj.springmvc.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    //加入@Controller注解表示该类是一个控制层类,替换之前的servlet
    @Controller
    //该注解如果只设置请求映射,直接填字符串
    @RequestMapping("/first")
    public class HelloController {
    
        //该注解如果还有其他参数要设置,路径用path赋值
        @RequestMapping(path="/hello")
        public String hello(){
            //返回的字符串是某个页面的名称或另一个控制层中方法的请求映射
            return "welcome";
        }
    }
    

将项目部署到tomcat后,访问http://localhost:8080/SpringMVC_war_exploded/first/hello,即可跳转到指定页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVbN0mGS-1676453638387)(D:\框架\SpringMVC.assets\image-20230114144225441.png)]

SpringMVC相关注解

  • @Controller

    • 只能写在类上,表示该类属于一个控制器
  • @RequestMapping(“/请求映射名”)/@RequestMapping(value=“/请求映射名”)/@RequestMapping(path=“/请求映射名”)

    • 该注解可以写在类或方法上。写在类上用于区分功能模块,写在类上用于区分具体功能
    • 默认写一个属性或value或path后的值,都表示访问该类或该方法时的请求映射
  • @RequestMapping(value=“/请求映射名”,method=RequestMethod.GET/POST/PUT/DELETE)

    • method属性表示使用哪种请求方式访问该类或该方法
    • 如果注解中不止一个属性,每个属性都需要指定属性名
  • **@GetMapping(“/请求映射名”)**相当于@RequestMapping(value=“/请求映射名”,method=RequestMethod.GET)

    • post、put、delete同理
    • @GetMapping只能写在方法上
  • @PathVariable

    • 该注解写在某个方法的某个形参上

    • 通常配合@RequestMapping(“/{path}”)获取请求时传递的参数

      @RequestMapping("/{path}")
      public String fun(@PathVariable("path") String pageName){
          return pageName;
      }
      //当前方法如果通过"localhost:8080/项目名/hello"访问,就会跳转到hello.jsp
      //当前方法如果通过"localhost:8080/项目名/error"访问,就会跳转到error.jsp
      //映射中的/{path}就是获取路径中的hello或error,将其赋值给形参
      //通常用于跳转指定页面
      
  • @RequestParam(value=“传递的参数名”,defaultValue =“没有传递参数时的默认值”)

    • 该注解写在某个方法的某个参数上
    • 用于获取提交的数据,可以设置默认值在没有提交数据时使用

解决提交数据时的中文乱码

使用过滤器解决中文乱码。

在web.xml中配置spring-web提供的CharaterEncodingFilter过滤器

<!--定义解决中文乱码的过滤器CharacterEncodingFilter-->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--设置编码格式-->
    <init-param>
        <!--该过滤器需要设置一个encoding属性,用于设置编码格式-->
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<!--设置将什么请求经过该过滤器,通常设置为/*表示一切请求先经过该过滤器-->
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

SpringMVC中的跳转

  • 控制层跳转到某个jsp页面

    • 在控制层中定义方法,这种方式跳转,属于请求转发

    • 如果要使用重定向跳转,在页面名之前添加"redirect:"

      @RequestMapping("/hello")
      public String hello(){
          //返回页面名称
          return "hello";//请求转发
      }
      
      @RequestMapping("/hello")
      public ModelAndView hello(ModelAndView mav){
          //设置页面名称
          mav.setViewName("hello");
          return mav;
      }
      
    • 在springmvc配置文件中

      <mvc:view-controller path="请求映射" view-name="页面名称"></mvc:view-controller>
      <!-- 访问项目根目录,跳转到welcome.jsp页面 -->
      <mvc:view-controller path="/" view-name="welcome"></mvc:view-controller>
      <!-- 这个标签使用时,会让@RequesMapping失效,如果要共存,添加以下标签 -->
      <!--来自于xmlns:mvc="http://www.springframework.org/schema/mvc" -->
      <mvc:annotation-driven></mvc:annotation-driven>
      
  • 控制层跳转到另一个控制层中的方法

    • 方法的返回值为"redirect/forward:另一个控制层中方法的映射名"

      @RequestMapping("/hello")
      public String hello(){
          return "redirect:hero";//使用重定向的方式,跳转到映射名为hero的控制层
          return "forward:hero"//使用请求转发的方式,跳转到映射名为hero的控制层
      }
      
  • jsp页面跳转另一个jsp页面

    • 当前项目中jsp页面都在WEB-INF下,无法直接访问,a标签同样如此,只能通过控制层跳转页面

    • 定义用于跳转页面控制层类

      package com.hqyj.springmvc.controller;
      
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;
      
      /*
       * 定义一个用于跳转指定页面或controller的控制层
       * */
      @Controller
      public class ToPageController {
      
          /*
          	项目启动时或直接访问根目录,跳转到指定的controller
          */
          @RequestMapping("/")
          public String toIndex() {
              return "redirect:/hero/queryAll";
          }
      
          /*
          * 这个方法的作用:会将请求中第一个/后的单词截取出来命名为path赋值给参数page
          * 如 localhost:8080/web/hero,就会识别出hero,return "hero";
          * 就会跳转到 /WEB-INF/pages/hero.jsp页面
          * */
          @RequestMapping("/{path}")
          public String toPage(@PathVariable("path") String page) {
              return page;
          }
      }
      
    • 这时在页面中这样跳转

      <%--这个路径实际是/项目名/addHero,会截取addHero,跳转到/项目名/WEB-INF/pages/addHero.jsp--%>
      <a href="${pageContext.request.contextPath}/addHero">添加</a>
      

配置Spring+SpringMVC时用到的关键类

  • 在web.xml中配置Spring全局监听器

    • ContextLoaderListener

      <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      
      <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:application.xml</param-value>
      </context-param>
      
  • 在Springmvc配置文件中配置SpringMVC内部资源视图解析器

    • InternalResourceViewResolver

      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <property name="prefix" value="/WEB-INF/pages/"></property>
          <property name="suffix" value=".jsp"></property>
      </bean>
      
  • 在web.xml中配置SpringMVC请求分发Servlet

    • DispatcherServlet

      <servlet>
          <servlet-name>dispatcherServlet</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <init-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath:springmvc.xml</param-value>
          </init-param>
      </servlet>
      
      <servlet-mapping>
          <servlet-name>dispatcherServlet</servlet-name>
          <url-pattern>/</url-pattern>
      </servlet-mapping>
      
  • 在web.xml中配置字符编码过滤器

    • CharacterEncodingFilter

      <filter>
          <filter-name>encodingFilter</filter-name>
          <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
          <init-param>
              <param-name>encoding</param-name>
              <param-value>utf-8</param-value>
          </init-param>
      </filter>
      
      <filter-mapping>
          <filter-name>encodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>
      

ORM

Object Relational Mapping 对象关系映射

创建Java类的对象,将其属性和数据库表中的字段名之间构建映射关系。

这样通过操作该类创建的一个对象,就能操作数据库中对应的数据表。

ORM框架就是对象关系映射框架,用于简化JDBC。

主流的ORM框架有Hibernate、JPAMyBatisMyBatisPlus

MyBatis

一个半自动化的ORM框架。原本叫做ibatis,后来升级改名为MyBatis。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

使用XML文件或注解的形式,将SQL语句映射为持久层(dao层)中的方法。

官网https://mybatis.org/mybatis-3/zh/index.html

特点

  • 方便:简化JDBC

  • 解除SQL语句与业务代码的耦合:sql和java代码分离,sql写在xml文件中,方便扩展维护。

  • 支持动态SQL:不同的情况下构造不同的SQL

  • 主流:SSM中的M

SSM****项目搭建

整体流程

1.创建基于Maven的webapp项目

2.修改web.xml版本为4.0,创建java、resoureces目录、项目包结构、web-inf下的页面目录

3.导入依赖

  • spring-webmvc

  • mybatis

  • mybatis-spring

  • mysql****druid

  • spring-jdbc

  • jstl

4.配置Spring

  • 创建application.xml

  • 扫描项目根包

  • 配置web.xml

  • 设置全局参数: ,读取application.xml文件

  • 设置全局监听器:ContextLoaderListener,初始化Spring容器

5.配置SpringMVC

  • 创建springmvc.xml

  • 扫描控制层所在包

  • 设置内部资源视图解析器:InternalResourceViewResolver,设置前后缀

  • 配置web.xml

  • 设置请求分发器:DispatcherServlet,在其中读取springmvc.xml配置文件

  • 过滤器解决请求中文乱码:CharacterEncodingFilter

6.配置MyBatis

  • 创建mybatis-config.xml(官网模板)

  • 在application.xml中注入

  • 数据库连接池Druid:DruidDataSource

  • SQL会话工厂:SqlSessionFactoryBean

  • 映射扫描配置器:MapperScannerConfigurer

7.执行sql语句

  • 创建dao层接口,定义方法

  • 在resoureces下创建mapper目录创建sql映射文件xx.xml(官网模板),在其中定义sql语句

SSM项目中使用Ajax

ajax依赖于jquery,所以先保证页面中存在jquery.js。

$.ajax({
    url:"访问地址",
    data:{
        "提交的参数名":"实际值",
        "提交的参数名":"实际值"
    },
    type:"get/post",
    success:function(res){
        //成功后的回调函数,res为访问后的结果,必须是json格式
    }
});

在前端页面中使用ajax访问controller时,controller的返回值必须是一个JSON格式的字符串。

所以controller中的方法上要加入@ResponseBody注解

拦截器

每次请求controller时,都要经过的一个类。

实现过程

1.导入servlet依赖

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>

2.自定义一个类,实现拦截器HandlerInterceptor接口

其中有三个default方法可以重写

  • preHandle
    • 在发送请求后,DispatcherServlet解析控制器中某个RequestMapping之前执行的方法
    • 该方法返回true时,请求才能继续
  • postHandle
    • 在preHandle方法返回值为true后执行
    • 在DispatcherServlet解析控制器中某个RequestMapping之后执行
  • afterCompletion
    • 在preHandle方法返回true后执行
    • 在解析视图后执行的方法

这里只需重写preHandle方法即可

package com.hqyj.ssm02.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
* 自定义拦截器,用户拦截未登录时的请求
* */
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        //登录成功后,会在session中保存一个名为sysAdmin的对象
        Object sysAdmin = request.getSession().getAttribute("sysAdmin");
        //如果有对象,说明登录成功,可以放行return true
        if(sysAdmin!=null){
            return true;
        }else{
            response.sendRedirect(request.getContextPath()+"/login");
        }
        System.out.println(requestURI+"试图访问,拦截成功");
        return false;
    }
}

3.在springmvc.xml中配置拦截器

<!--配置拦截器们-->
<mvc:interceptors>
    <!--配置某个拦截器-->
    <mvc:interceptor>
        <!--设置要拦截的请求,这里的/**表示拦截一切请求-->
        <mvc:mapping path="/**"/>
        <!--设置不拦截的请求-->
        <!--放行登录和注册页-->
        <mvc:exclude-mapping path="/login"/>
        <mvc:exclude-mapping path="/register"/>
        <!--放行静态资源-->
        <mvc:exclude-mapping path="/static/**"/>
        <!--放行用户模块-->
        <mvc:exclude-mapping path="/sysAdmin/**"/>
        <!--注入指定的拦截器-->
        <bean class="com.hqyj.ssm02.interceptor.MyInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

动态SQL

  • <set>搭配<if>用于修改

    <!--动态SQL:set-if用于修改-->
    <update id="testUpdate">
        update book_info
        <set>
            book_id=#{bookId},
            <if test="bookName!=null">
                book_name = #{bookName},
            </if>
            <if test="bookPrice!=null">
                book_price = #{bookPrice}
            </if>
        </set>
        where book_id=#{bookId}
    </update>
    
  • <where>搭配<if>用于查询、修改、删除时的条件

    <select id="queryByCondition" resultType="com.xxx.entity.BookInfo">
        select * from book_info
        <where>
        	<if test="bookName!=null">
            	book_name = #{bookName}
            </if>
            <if test="bookAuthor!=null">
            	and book_author = #{bookAuthor}
            </if>
            <if test="typeId!=null">
            	and type_id = #{typeId}
            </if>
        </where>
    </select>
    
  • <trim>搭配<if>可以替换set-if和where-if

    该标签有四个属性

    prefix 表示如果trim标签中有if条件满足,就在整个trim部分添加指定前缀

    suffix 表示如果trim标签中有if条件满足,就在整个trim部分添加指定后缀

    prefixOverrides 表示去除整个trim部分多余的前缀

    suffixOverrides 表示去除整个trim部分多余的后缀

使用trim实现修改

可以修改所有字段,也可以修改部分字段

<update id="testUpdate">
    update book_info
    <!--prefix="set"表示在所有内容前加入set关键字-->
    <!--suffixOverrides=","表示所有内容之后如果有多余的逗号,去掉逗号-->
    <trim prefix="set" suffixOverrides=",">
        book_id=#{bookId},
        <if test="bookName!=null">
            book_name = #{bookName},
        </if>
        <if test="bookAuthor!=null">
            book_author = #{bookAuthor},
        </if>
        <if test="bookNum!=null">
            book_num = #{bookNum},
        </if>
        <if test="bookPrice!=null">
            book_price = #{bookPrice},
        </if>
        <if test="publisherDate!=null">
            publisher_date = #{publisherDate}
        </if>
    </trim>
    where book_id=#{bookId}
</update>

使用trim标签实现多条件查询

<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">
    SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt
    <!--prefix="where"表示在所有内容前加入where关键字-->
    <!--prefixOverrides="and"表示所有内容之前如果有多余的and,去掉and-->
    <!--suffix="order by book_id desc"表示在所有内容之后加入order by book_id desc-->
    <trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">
        bi.type_id=bt.type_id
        <if test="bookName!=null">
            and book_name like concat ('%',#{bookName},'%')
        </if>
        <if test="bookAuthor!=null">
            and book_author like concat ('%',#{bookAuthor},'%')
        </if>
        <if test="typeId!=0">
            and bt.type_id =#{typeId}
        </if>
    </trim>
</select>

分页

使用分页组件PageHelper

1.导入依赖

<!--分页组件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>

2.在mybatis的配置文件中

<!--设置分页插件-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--保证翻页不会超出范围-->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

3.使用

  • 通过PageHelper类调用静态方法startPage(当前页数,每页显示的记录数)开启分页
  • 查询所有,返回集合
  • 创建PageInfo分页模型对象,构造方法的参数为查询出的集合,设置泛型,
//定义当前页和每页显示的数量
int pageNum=1;
int size=10;
//开启分页
PageHelper.startPage(pageNum,size);
//正常调用查询,返回查询到的数据集合
BookInfo bookInfo = new BookInfo();
bookInfo.setTypeId(1);
List<BookInfo> list = bookInfoDao.queryByCondition(bookInfo);

//创建分页模型对象,构造方法的参数为查询出的结果,设置泛型,
PageInfo<BookInfo> pageInfo = new PageInfo<>(list);

//此时分页相关数据都保存在pageInfo对象中
System.out.println("总记录数"+pageInfo.getTotal());
System.out.println("最大页数"+pageInfo.getPages());
System.out.println("当前页"+pageInfo.getPageNum());
System.out.println("当前容量"+pageInfo.getSize());
System.out.println("当前分页数据"+pageInfo.getList());
System.out.println("是否有上一页"+pageInfo.isHasPreviousPage());
System.out.println("是否有下一页"+pageInfo.isHasNextPage();
System.out.println("是否是首页"+pageInfo.isIsFirstPage());
System.out.println("是否是尾页"+pageInfo.isIsLastPage());
PageInfo对象常用属性和方法作用
total/getTotal()得到总记录数
pages/getPages()得到最大页数
pageNum/getPageNum()得到当前页
size/getSize()得到每页显示的记录数
list/getList()得到按当前page和size查询到的数据集合
isFirstPage/isIsFirstPage()是否是首页
isLastPage/isIsLastPage()是否是尾页

多条件分页具体实现

controller

@RequestMapping("/queryByCondition")
public String queryByCondition(
    @RequestParam(defaultValue = "1") int pageNum,
    @RequestParam(defaultValue = "8") int size,
    BookInfo bookInfo,
    Model model) {
    //1.PageHelper.startPage()
    PageHelper.startPage(pageNum, size);
    //2.查询集合
    List<BookInfo> list = bookInfoService.queryByCondition(bookInfo);
    //3.PageInfo(集合)
    PageInfo<BookInfo> pageInfo = new PageInfo<>(list);
    //将查询到的分页模型对象保存到model中
    model.addAttribute("pageInfo",pageInfo);

    //构造页数的集合
    ArrayList<Integer> pageList = new ArrayList<>();
    for (int i = 1; i <= pageInfo.getPages(); i++) {
        pageList.add(i);
    }
    model.addAttribute("pageList",pageList);

    return "bookInfoList";
}

页面分页组件

<nav aria-label="Page navigation">
    <ul class="pagination">
        <c:if test="${!pageInfo.isFirstPage}">
            <li>
                <a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum-1}"
                   aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
        </c:if>
        <c:forEach items="${pageList}" var="pno">
            <li class="pno">
                <a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pno}">${pno}</a>
            </li>
        </c:forEach>
        <c:if test="${!pageInfo.isLastPage}">
            <li>
                <a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum+1}"
                   aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        </c:if>
    </ul>
</nav>

SpringBoot

Spring推出的一个Spring框架的脚手架。

不是一个新的框架,而是搭建Spring相关内容框架的平台。

它省去了Spring、SpringMVC项目繁琐的配置过程,让开发变得更加简单。

本质还是Spring+SpringMVC,可以搭配其他的ORM框架,如MyBatis、MyBatisPlus、JPA、Hibernate等。

特点

  • 内置了Tomcat服务器,不需要部署项目到Tomcat中
  • 内置了数据源Hikari
  • 减少了jar文件依赖的配置
  • SpringBoot中的配置文件可以使用yml格式文件,代替properties或xml

热部署

项目在开发过程中,可以不需要每次都重启,等待一段时间后会自动更新编译运行

使用

添加依赖,可以在创建的项目的时候选择,也可以中途添加

<!--热部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <version>2.7.8</version>
</dependency>

开启热部署

Lombok

用于简化实体类中模板代码的工具

使用

添加依赖,可以在创建的项目的时候选择,也可以中途添加

<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

安装插件(IDEA2020.2之后的版本会内置Lombok插件,无需安装)

在某个实体类上添加注解

lombok常用注解作用
@AllArgsConstructor自动生成全参构造方法
@Data以下注解之和
@Setter自动生成set方法
@Getter自动生成get方法
@NoArgsConstructor自动生成无参构造方法
@ToString自动生成toString方法
@EqualsAndHashcode自动生成equals和hashcode方法

SpringBoot+LayUI实现酒店客房管理

核心知识点

  • SpringBoot项目搭建

    • 核心依赖

      <dependency>
        <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
    • 集成MyBatis

      <dependency>
         <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>2.3.0</version>
      </dependency>
      
  • 新注解

    • @RestController

      如果某个控制器类中的所有方法都要加@ResponseBody,可以在类上加@ResponseBody,也可以用@RestController代替@ResponseBody和@Controller

  • LayUI

    • 数据表格
      • 数据接口格式
      • 真假分页
      • 头工具栏事件
      • 行内事件
      • 单元格编辑事件
    • 弹出层
      • layer.msg()
      • layer.confirm()
      • layer.open()
  • ajax

    $.ajax({
        url:"",
        data:xxx,
        type:"get/post",
        success:function(){
            
        }
    })
    
  • 前后端分离

    • 该案例可以将页面独立出来,成为一个前后端分离项目,也可以将页面作为静态资源保存在static目录下
  • 如果设计为前后端分离,要在控制器类上加入@CrossOrigin,表示该类中的所有方法允许跨域请求

  • 打包SpringBoot项目

    保证项目中无错误,包含单元测试中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b3C5jrUn-1676453638388)(C:\Users\hnn\Desktop\image-20230215165946364.png)]

​ 打包后是一个.jar文件,位于target目录中

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AiUQkZdA-1676453638389)(C:\Users\hnn\Desktop\image-20230215170020310.png)]

​ 在安装有java环境的机器中,控制台运行jar文件

java -jar 文件名.jar

核心Java代码

实体类

订单表

/*
* 订单
* */
@Data
public class Orders {
    private Integer id;
    private Integer roomNo;
    private Integer cusId;
    //@JsonFormat是springboot集成的jackson包中的注解,用于格式化日期
    //pattern指定格式日期 timezone指定时区,这里和数据库的时区保持一致
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
    private Date leaveTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
    private Date stayTime;
    private Integer cost;

    //显示订单的同时,要显示房间的相关信息
    private Room room;
    //显示订单的同时,要显示客户的相关信息
    private Customer customer;
}

数据访问层

  • 实现随意修改某个字段的值

    int update(@Param("field") String field,
               @Param("value") String value,
               @Param("id") int id);
    

    sql映射文件

    <update id="update">
        update room
        <set>
            <if test="field=='roomType'">
                room_type= #{value}
            </if>
            <if test="field=='roomPrice'">
                room_price= #{value}
            </if>
            <if test="field=='roomUse'">
                room_use=#{value}
            </if>
        </set>
        where room_no=#{id}
    </update>
    
  • 添加时获取自增的值

    int insert(Customer customer);
    

    sql映射文件

    <!--添加顾客的同时,获取自增的id-->
    <insert id="insert" useGeneratedKeys="true" keyColumn="cus_id" keyProperty="cusId">
        insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard})
    </insert>
    
  • 多表连接查询/条件查询

    List<Orders> queryAll();
    
    List<Orders> search(String keyword);
    
    //根据客户编号查询是否已入住
    Orders isStay(Integer cusId);
    
    //根据房间号查询正在入住的订单信息
    Orders findCheckInByRoomNo(Integer roomNo);
    
    //计算费用
    int checkOut(@Param("roomNo") Integer roomNo,@Param("cost") Integer cost);
    

    sql映射文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--设置该文件对应的dao层接口全限定名-->
    <mapper namespace="com.hqyj.hotel_sys.dao.OrdersDao">
    
        <insert id="insert">
            insert into orders
            values (null, #{roomNo}, #{cusId}, now(), null, 0)
        </insert>
    
    
        <!--关联查询方式一:特殊的SQL语句-->
        <!--
         <select id="queryAll" resultType="com.hqyj.hotel_sys.entity.Orders">
             select o.*,
                    cus_name as 'customer.cusName',
                     cus_phone as 'customer.cusPhone',
                     cus_idcard as 'customer.cusIdcard',
                     room_type as 'room.roomType',
                     room_price as 'room.roomPrice',
                     room_use as 'room.roomUse'
             from customer c,
                  room r,
                  orders o
             where c.cus_id = o.cus_id
               and r.room_no = o.room_no
         </select>
         -->
    
        <!--多表连接条件查询-->
        <select id="search" resultMap="ordersMap">
            SELECT * FROM orders o,customer c
            <trim prefix="where" prefixOverrides="and">
                o.cus_id = c.cus_id
                <if test="keyword!=null">
                    and cus_name like concat('%',#{keyword},'%')
                </if>
            </trim>
        </select>
    
        <!--关联查询方式二:子查询-->
        <!--1.查询自身表-->
        <select id="queryAll" resultMap="ordersMap">
            select *
            from orders
        </select>
        <!--自定义结果集映射,用cus_id和room_no做子查询,重新映射一次到orders对象-->
        <resultMap id="ordersMap" type="com.hqyj.hotel_sys.entity.Orders">
            <result property="roomNo" column="room_no"></result>
            <result property="cusId" column="cus_id"></result>
            <association property="room" column="room_no" select="findRoomByNo"></association>
            <association property="customer" column="cus_id" select="findCustomerById"></association>
        </resultMap>
        <!--子查询一:根据房号查房间-->
        <select id="findRoomByNo" resultType="com.hqyj.hotel_sys.entity.Room">
            select *
            from room
            where room_no = #{roomNo}
        </select>
        <!--子查询二:根据编号查客户-->
        <select id="findCustomerById" resultType="com.hqyj.hotel_sys.entity.Customer">
            select *
            from customer
            where cus_id = #{cusId}
        </select>
    
    
        <select id="isStay" resultType="com.hqyj.hotel_sys.entity.Orders">
            select *
            from orders
            where cus_id = #{cusId}
              and leave_time is null
        </select>
    
        <!--
            根据房号查询正在入住的订单信息,包含房间价格
        -->
        <select id="findCheckInByRoomNo" resultType="com.hqyj.hotel_sys.entity.Orders">
            select o.*, room_price as 'room.roomPrice'
            from orders o,
                 room r
            where o.room_no = r.room_no
              and r.room_no = #{roomNo}
              and leave_time is null
        </select>
    
        <update id="checkOut">
            update orders
            set leave_time=now(),
                cost=#{cost}
            where room_no = #{roomNo}
              and leave_time is null
        </update>
    </mapper>
    

业务流程层

package com.hqyj.hotel_sys.service;

import com.hqyj.hotel_sys.dao.CustomerDao;
import com.hqyj.hotel_sys.dao.OrdersDao;
import com.hqyj.hotel_sys.dao.RoomDao;
import com.hqyj.hotel_sys.entity.Customer;
import com.hqyj.hotel_sys.entity.Orders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

@Service
public class CustomerService {


    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private RoomDao roomDao;
    @Autowired
    private OrdersDao ordersDao;

    /*
     * 入住
     * 1.获取顾客信息:姓名、电话、身份证号,
     *   根据身份证号判断是否存在,如果存在,用存在的对象
     *   如果不存在,向customer表中添加一条记录 insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard})
     *   添加成功时,要获取自增的id,用于订单表中使用
     *   判断该顾客是否已入住:查询订单表中对应顾客编号的记录,如果退房时间为空,说明已入住
     *   select * from orders where cus_id=#{cusId} and leave_time is null
     * 2.将对应房间的状态改为1,对room表中修改 update room set room_use=1 where room_no=#{roomNo}
     * 3.向订单表中添加一条记录 insert into orders values (null,#{roomNo},#{cusId},now(),null,0)
     *
     * 以上3个步骤属于一个事务,要在该方法上加事务注解
     * */
    @Transactional//让该方法成为一个事务,如果执行中途出错,会自动回滚
    public boolean checkIn(Customer customer, int roomNo) {
        //1.添加客户,根据身份证号判断是否存在
        Customer byIdcard = customerDao.findByIdcard(customer.getCusIdcard());
        boolean b1;
        if (byIdcard == null) {//不存在,调用添加
            b1 = customerDao.insert(customer) > 0;
        } else {
            customer = byIdcard;
            b1 = true;
        }
        //查看是否已入住
        if (ordersDao.isStay(customer.getCusId()) != null) {
            return false;
        }
        //2.修改房间状态
        boolean b2 = roomDao.update("roomUse", "1", roomNo) > 0;
        //3.添加订单信息
        //创建订单对象
        Orders orders = new Orders();
        //客户编号在添加客户成功后,会自动获取
        orders.setCusId(customer.getCusId());
        orders.setRoomNo(roomNo);
        boolean b3 = ordersDao.insert(orders) > 0;

        return b1 & b2 & b3;
    }


    /*
     * 退房
     * 1.根据房间号查询对应正在入住的订单记录(同时查询出房价)
     * select o.* from orders o,room r where o.room_no=r.room_no and r.room_no =#{roomNo} and leave_time is null
     * 2.结算
     * 添加退房时间、添加花费
     * update orders set leave_time =now() ,cost=#{cost} where room_no =#{roomNo} and leave_time is null
     * 3.修改房间状态为空闲
     * update room set room_use = 0 where room_no=#{roomNo}
     * */
    @Transactional
    public boolean checkOut(Integer roomNo) {
        //1.根据房间号查询对应正在入住的订单记录
        Orders orders = ordersDao.findCheckInByRoomNo(roomNo);
        //2.计算费用
        Date stayTime = orders.getStayTime();
        Date now = new Date();
        double l = now.getTime() - stayTime.getTime();
        //转换为天数
        double day = Math.ceil(l / 1000 / 3600 / 24);
        double cost = orders.getRoom().getRoomPrice() * day;
        //修改订单中的花费和退房时间
        //update orders set leave_time=now(),cost=#{cost} where room_no=#{roomNo} and leave_time is null
        boolean b1 = ordersDao.checkOut(roomNo, (int) cost) > 0;
        //3.修改房间状态为空闲0
        //update room set room_use where room_no = #{roomNo}
        boolean b2 = roomDao.update("roomUse", "0", roomNo) > 0;
        return b1 & b2;
    }
}

控制层

如果某个控制器中的所有方法都需要返回JSON格式字符串,在类上加@ResponseBody

@Controller
@RequestMapping("/room")
public class RoomController {

    @Autowired
    private RoomService roomService;

    @ResponseBody
    @RequestMapping("/queryAll")
    public ResultData<Room> queryAll(Integer page, Integer limit) {
        //使用PageHelper分页
        PageHelper.startPage(page, limit);
        //正常查询所有
        List<Room> rooms = roomService.queryAll();
        //创建分页模型对象
        PageInfo<Room> pageInfo = new PageInfo<>(rooms);
        //返回的数据为分页后的集合,数量为总记录数
        return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList());
    }
}

LayUI数据表格

数据表格所需的数据接口格式为

{
    code:0,
    msg:"",
    count:1000,
    data:[{},{}]
}

构造满足LayUI数据表格的数据模型类ResultData

/*
* 定义一个用于LayUI数据表格对应格式的模板类
* code  状态码         0成功
* msg   提示文字
* count 数据总量
* data  数据集合
* */
@Data
public class ResultData<T> {
    private Integer code;
    private String msg;
    private Integer count;
    private List<T> data;

    /*
    * 定义带count和data的构造方法,用于初始化code和msg
    * */
    public ResultData(Integer count, List<T> data) {
        code=0;
        msg="";
        this.count = count;
        this.data = data;
    }
}

最后在控制层中,将查询的方法的返回值更改为ResultData类型

@RequestMapping("/queryAll")
public ResultData<Room> queryAll() {
    List<Room> rooms = roomService.queryAll();
    return new ResultData<>(rooms.size(), rooms);
}

最终页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>LayUI</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all">
    <!-- 注意:如果你直接复制所有代码到本地,上述css路径需要改成你本地的 -->
</head>
<body>
<table class="LayUI-hide" id="test" lay-filter="test"></table>
<script type="text/html" id="toolbarDemo">
    <div class="LayUI-btn-container">
        <button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckData">获取选中行数据</button>
        <button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckLength">获取选中数目</button>
        <button class="LayUI-btn LayUI-btn-sm" lay-event="isAll">验证是否全选</button>
        <button class="LayUI-btn LayUI-btn-sm" lay-event="addRoom">添加客房</button>
    </div>
</script>
<script type="text/html" id="barDemo">
    <!--插值表达式-->
    {{# if(d.roomUse==0){ }}
    <a class="LayUI-btn LayUI-btn-normal LayUI-btn-sm" lay-event="check-in">入住</a>
    <a class="LayUI-btn LayUI-btn-danger LayUI-btn-sm" lay-event="del">删除</a>
    {{# }else{ }}
    <a class="LayUI-btn LayUI-btn-warm LayUI-btn-sm" lay-event="check-out">退房</a>
    {{# } }}
</script>

<script src="../LayUI/LayUI.js" charset="utf-8"></script>
<!-- 注意:如果你直接复制所有代码到本地,上述 JS 路径需要改成你本地的 -->
<script>
    LayUI.use('table', function () {
        var table = LayUI.table;

        //引入Jquery
        var $ = LayUI.$;


        /*
        * 渲染表格数据
        * url:数据访问接口
        * cols:表格列
        * field:数据字段名
        * title:表头
        * cellminwidth:100  自适应宽度
        * fixed:left    固定在某侧
        * unresize:true 不可改变尺寸
        * sort:true 排序
        * edit:true 行内编辑
        * */
        table.render({
            elem: '#test'
            //设置数据接口
            , url: 'http://localhost:9090/hotel/room/queryAll'
            , toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板
            , defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
                title: '提示'
                , layEvent: 'LAYTABLE_TIPS'
                , icon: 'LayUI-icon-tips'
            }]
            , title: '用户数据表'
            , cols: [[
                {type: 'checkbox', fixed: 'left'}
                , {field: 'roomNo', title: '房间号', cellminwidth: 100, fixed: 'left', unresize: true, sort: true}
                , {field: 'roomType', title: '房间类型', cellminwidth: 100, edit: 'text'}
                , {field: 'roomPrice', title: '房间单价', cellminwidth: 100, edit: 'text', sort: true}
                , {
                    field: 'roomUse', title: '使用状态', cellminwidth: 100,
                    templet: function (res) {
                        return res.roomUse == 0 ? "<span style='color:green'>空闲中</span>" : "<span style='color:red'>使用中</span>";
                    }
                }
                , {fixed: 'right', title: '操作', toolbar: '#barDemo', cellminwidth: 100,}
            ]]
            //开启分页组件
            , page: true
            //设置每页显示记录数的下拉选项
            , limits: [5, 10, 20]
            //默认每页显示的条数,没有设置默认为10
            , limit: 10
            //解析数据表格url请求后的数据
            , parseData: function (res) {//res就是url对应的数据
               /*
                //假分页
                var result;
                if (this.page.curr) {
                    result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);
                } else {
                    result = res.data.slice(0, this.limit);
                }
                */
                return {
                    "code": res.code,
                    "msg": res.msg,
                    "count": res.count,
                    "data": res.data
                }
            }
        });

        //头工具栏事件
        table.on('toolbar(test)', function (obj) {
            var checkStatus = table.checkStatus(obj.config.id);
            switch (obj.event) {
                case 'addRoom':
                    layer.open({
                        title: '添加客房',
                        type: 2,
                        content: 'addRoom.html',
                        area: ['350px', '250px'],
                        resize: false,
                        anim: 2,
                        /* success: function(layero, index){
                             console.log(layero, index);
                         }*/
                    })
                    break;
                case 'getCheckData':
                    //data是所选数据的集合
                    var data = checkStatus.data;
                    for (var i = 0; i < data.length; i++) {
                        console.log(data[i].roomNo)
                    }
                    // layer.alert(JSON.stringify(data));
                    break;
                case 'getCheckLength':
                    var data = checkStatus.data;
                    layer.msg('选中了:' + data.length + ' 个');
                    break;
                case 'isAll':
                    layer.msg(checkStatus.isAll ? '全选' : '未全选');
                    break;

                //自定义头工具栏右侧图标 - 提示
                case 'LAYTABLE_TIPS':
                    layer.alert('这是工具栏右侧自定义的一个图标按钮');
                    break;
            }
            ;
        });

        //监听单元格编辑事件
        table.on('edit(test)', function (obj) {
            // console.log(obj);
            //layer.confirm("提示文件",function(){确认触发},function(){取消触发})
            layer.confirm('确认要修改吗', function (index) {
                //使用ajax提交要修改的字段名、修改后的值、要修改的编号
                $.ajax({
                    url: "http://localhost:9090/hotel/room/update",
                    data: {
                        "field": obj.field,//要修改的字段
                        "value": obj.value,//修改后的值
                        "id": obj.data.roomNo//要修改的id
                    },
                    success: function () {
                        //修改成功,关闭确认框
                        layer.close(index);
                    }
                });
            }, function () {
                //修改失败,重新加载
                location.reload()
            })
        })


        //监听行工具事件
        table.on('tool(test)', function (obj) {
            //data就是当前行中的数据
            var data = obj.data;
            // console.log(obj)
            if (obj.event === 'del') {
                //弹出确认框
                layer.confirm('真的删除行么', function (index) {
                    //使用ajax提交要删除的id
                    $.ajax({
                        url: "http://localhost:9090/hotel/room/delete",
                        data: {
                            "delId": data.roomNo
                        },
                        success: function (res) {
                            if (res) {
                                //前端假删除
                                obj.del();
                                //关闭确认框
                                layer.close(index);
                            }
                        }
                    });
                });
            } else if (obj.event === 'check-in') {
                //弹出输入顾客信息表单
                layer.open({
                    title: '输入顾客信息',
                    type: 2,
                    content: 'addCustomer.html',
                    area: ['350px', '400px'],
                    resize: false,
                    anim: 2,
                    //弹出窗口后的回调函数
                    success: function (layero, index) {
                        //弹出成功后,在弹出页面中加入当前点击行的roomNo
                        //获取弹出层的body部分
                        var body = layer.getChildFrame('body', index);
                        //获取弹出层body中的隐藏域,给其赋值
                        body.find("input[name=roomNo]").val(data.roomNo);
                    }
                })
            } else if (obj.event === 'check-out') {
                layer.confirm("确定要退房吗", function () {
                    $.ajax({
                        url: "http://localhost:9090/hotel/customer/checkOut",
                        data: {
                            "roomNo": data.roomNo
                        },
                        success: function (res) {
                            if (res) {
                                location.reload();
                            }
                        }
                    });
                })
            }
        });
    });
</script>

</body>
</html>

LayUI添加页面

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all">
    </head>
    <body>
        <form class="LayUI-form" action="">
            <div class="LayUI-form-item">
                <label class="LayUI-form-label">房间类型</label>
                <div class="LayUI-input-inline">
                    <input type="text" name="roomType" required  lay-verify="required" placeholder="请输入房间类型" autocomplete="off" class="LayUI-input">
                </div>
            </div>
            <div class="LayUI-form-item">
                <label class="LayUI-form-label">房间单价</label>
                <div class="LayUI-input-inline">
                    <input type="text" name="roomPrice" required lay-verify="required" placeholder="请输入房间单价" autocomplete="off" class="LayUI-input">
                </div>
            </div>
            <div class="LayUI-form-item">
                <div class="LayUI-input-block">
                    <button class="LayUI-btn" lay-submit lay-filter="formDemo">立即提交</button>
                    <button type="reset" class="LayUI-btn LayUI-btn-primary">重置</button>
                </div>
            </div>
        </form>
        <script src="../LayUI/LayUI.js" charset="utf-8"></script>
        <script>
            //Demo
            LayUI.use('form', function(){
                var form = LayUI.form;
                //使用LayUI内置jquery
                var $=LayUI.$;
                //监听提交
                form.on('submit(formDemo)', function(data){
                    // data.field是当前表单中的所有数据
                    // layer.msg(JSON.stringify(data.field));
                    $.ajax({
                        url:'http://localhost:9090/hotel/room/addRoom',
                        //将表单中的所有数据一起提交,实际提交的是name=value,如roomType=值&roomPrice=值
                        data:data.field,
                        //相当于
                        /* data:{
                    roomType:"",
                    roomPrice:""
                }*/
                        success:function(res){
                            if(res){
                                //刷新父页面
                                parent.location.reload()
                            }
                        }
                    })
                    //return false时不提交表单
                    return false;
                });
            });
        </script>
    </body>
</html>

LayUI分页

  • 假分页

    查询所有,在页面中分页,适合记录比较少的情况

    table.render({
        //省略url等
    
        //开启分页组件
        , page: true
        //设置每页显示记录数的下拉选项
        , limits: [5, 10, 20]
        //默认每页显示的条数,没有设置默认为10
        , limit: 10
        //解析数据表格url请求后的数据
        , parseData: function (res) {//res就是url对应的数据
    
            //假分页
            var result;
            if (this.page.curr) {
                result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);
            } else {
                result = res.data.slice(0, this.limit);
            }
    
            return {
                "code": res.code,
                "msg": res.msg,
                "count": res.count,
                "data": result
            }
        }
    })
    
  • 真分页

    可以使用PageHelper组件

    依赖

    <!--分页组件SpringBoot集成PageHelper-->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.4.6</version>
    </dependency>
    

    在application.properties中配置

    # 防止不合理分页
    pagehelper.reasonable=true
    

    在控制层中使用

    @ResponseBody
    @RequestMapping("/queryAll")
    //这里的page和limit参数是layui数据表格自动传递
    public ResultData<Room> queryAll(Integer page, Integer limit) {
        //使用PageHelper分页
        PageHelper.startPage(page, limit);
        //正常查询所有
        List<Room> rooms = roomService.queryAll();
        //创建分页模型对象
        PageInfo<Room> pageInfo = new PageInfo<>(rooms);
        //返回的数据为分页后的集合,数量为总记录数
        return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList());
    }
    

LayUI条件查询

页面头部工具栏中加入搜索框

<script type="text/html" id="toolbarDemo">
    <div class="layui-form-item">
        <div class="layui-input-inline">
            <input type="text" name="keyword" required lay-verify="required" placeholder="请输入姓名关键字" autocomplete="off"
                   class="layui-input">
    </div>
        <button class="layui-btn layui-btn-primary" lay-event="search">搜索</button>
    </div>
</script>

js部分

//头工具栏事件
table.on('toolbar(test)', function (obj) {
    switch (obj.event) {
        case 'search':
            //获取输入的内容
            let keyword = $("input[name=keyword]").val();
            if(keyword==""){
                layer.msg("输入不能为空");
                return;
            }
            //如果要修改数据表格中的数据,只能更改url的地址后,重新加载数据表格
            table.reload('test',{
                url:"http://localhost:9090/hotel/orders/search?keyword="+keyword
            });
            break;
    }
});

dao层参考上方数据访问层条件查询部分

MyBatisPlus

官网简介 | MyBatis-Plus (baomidou.com)

MyBatis-Plus (简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

只需简单的配置,就能实现对单表的CURD。

其核心有两个接口:BaseMapper和IService

BaseMapper中封装了大量数据访问层的方法

IServcie中封装了大量业务流程层的方法

SpringBoot+MyBatisPlus

1.创建SpringBoot项目

创建时勾选依赖

  • devtools
  • lombok
  • spring-web
  • mysql-driver

2.导入SpringBoot集成MyBatisPlus依赖

<!-- SpringBoot集成MyBatisPlus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

3.配置application.properties文件

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 开启sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 无需加入开启驼峰命名映射,MyBatisPlus默认使用驼峰命名进行属性-字段映射
#mybatis-plus.configuration.map-underscore-to-camel-case=true

4.根据数据表创建实体类

实体类的属性名命名方式:

  • MyBatisPlus默认使用驼峰命名对字段和属性进行映射。如将字段stu_name对应的属性写为stuName
  • 如果字段名和属性名不一致,在属性名上加入**@TableField(value = “字段名”)**
  • 主键字段对应的属性,需要加入@TableId注解,其type属性表示主键生成策略
    • @TableId(type = IdType.AUTO)表示主键自增,在数据库也要将主键设置为自增
    • @TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
    • @TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
@Data
public class Hero {
    //type表示主键生成策略,
    @TableId(type = IdType.AUTO)// IdType.AUTO表示主键自增,在数据库也要将主键设置为自增
    //@TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
    //@TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
    private Integer id;
    //如果属性名和字段名不一致
    @TableField(value = "name")
    private String heroName;
    private String position;
    private String sex;
    private Integer price;
    private String shelfDate;
}

5.编写数据访问层接口

可以不用写@Repository,继承BaseMapper接口,设置泛型

/*
* 数据访问层可以称为dao或mapper层
* 可以不用加@Repository注解
* */
public interface HeroMapper extends BaseMapper<Hero> {

}

6.在SpringBoot的启动类中,扫描数据访问层所在包

@SpringBootApplication
@MapperScan("com.hqyj.sbmp01.mapper")
public class Sbmp01Application {

    public static void main(String[] args) {
        SpringApplication.run(Sbmp01Application.class, args);
    }
}

测试

在SpringBoot自带的单元测试类中,注入HeroMapper对象,调用BaseMapper中定义的方法即可实现CURD。

BaseMapper接口

BaseMapper接口中定义了常用的增删改查方法,

在数据访问层接口中继承该接口

使用

public interface HeroMapper extends BaseMapper<Hero> {

}

BaseMapper接口中的常用方法

方法名参数作用
selectList(Wrapper wrapper)条件构造器根据条件查询集合,如果实参为null表示查询所有,返回List集合
selectById(Serializable id)主键根据主键查询单个对象,返回单个对象
selectOne(Wrapper wrapper)条件构造器条件查询单个对象,返回单个对象
insert(T entity)实体对象添加单个实体
updateById(T entity)实体对象根据实体对象单个修改,对象必须至少有一个属性和主键
update(T entity,Wrapper wrapper)实体对象和条件构造器根据条件修改全部,对象必须至少有一个属性
deleteById(Serializable id/T entity)主键/实体对象根据主键删除单个对象
deleteBatchIds(Collection ids)主键集合根据集合删除
delete(Wrapper wrapper)条件构造器根据条件删除,如果实参为null表示无条件删除所有

测试

package com.hqyj.sbmp01;

import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
class Sbmp01ApplicationTests {


    @Autowired
    //@Resource
    private HeroMapper mapper;

    @Test
    void contextLoads() {
        //查询所有
        List<Hero> list = mapper.selectList(null);
        for (Hero hero : list) {
            System.out.println(hero);
        }
    }

    @Test
    void test1() {
        //根据主键查询单个对象
        System.out.println(mapper.selectById(20));
    }

    @Test
    void test2() {
        //根据id删除,参数为id
        //System.out.println(mapper.deleteById(20));

        //根据id删除,参数为带有id的对象
        /*Hero hero = new Hero();
        hero.setId(100);
        System.out.println(mapper.deleteById(hero));*/

        //删除所有
        //mapper.delete(null);

        //根据id集合删除
        ArrayList<Integer> list = new ArrayList<>();
        list.add(99);
        list.add(100);
        list.add(88);
        list.add(69);
        System.out.println(mapper.deleteBatchIds(list));
    }


    @Test
    void test3() {
        //根据id修改
        //参数为一个对象,这个对象至少要有一个非主键属性有值
        Hero hero = new Hero();
        hero.setHeroName("a");
        hero.setPosition("b");
        hero.setSex("c");
        hero.setShelfDate("d");
        hero.setPrice(123);
        hero.setId(999);
        //System.out.println(mapper.updateById(hero));

        // 修改全部数据
        // mapper.update(修改后的对象,条件构造器)
        mapper.update(hero,null);
    }

    @Test
    void test4() {
        Hero hero = new Hero();
        hero.setHeroName("asdfsdfsdf");
        hero.setPosition("b");
        hero.setSex("c");
        hero.setShelfDate("1999-9-9");
        hero.setPrice(123);
        mapper.insert(hero);
    }
}

IService接口

IService接口减少业务逻辑层的代码,并对BaseMapper进行了拓展

在业务流程类中继承该接口

使用

  • 1.创建一个业务层接口,继承IService接口,设置泛型

    /*
    * 创建业务逻辑层接口,继承IService<T>接口,设置泛型
    * */
    public interface HeroService extends IService<Hero> {
    }
    
    
  • 2.创建一个业务层接口的实现类,添加@Service注解,继承 ServiceImpl<M, T>,实现上一步创建的接口

    • M是数据访问层接口
    • T是实体类
    /*
    * 1.添加@Service
    * 2.继承ServiceImpl<M, T> M是Mapper类 T是实体类
    * 3.实现自定义Service接口
    * */
    @Service
    public class ImpHeroService extends ServiceImpl<HeroMapper, Hero> implements HeroService {
    
    }
    

IService接口中的常用方法

方法作用
list()无条件查询所有
list(Wrapper wrapper)条件查询素有
page(Page page)无条件分页查询,Page是分页模型对象
page(Page page,Wrapper wrapper)条件分页查询,Page是分页模型对象
getById(Serializable id)根据主键查询单个对象
getOne(Wrapper wrapper)条件查询单个对象
save(T entity)添加单个对象
save(Collection col)批量添加对象的集合
updateById(T entity)修改,参数至少有一个属性值和主键
saveOrUpdate(T entity)添加或修改。如果实参对象的主键值不存在则添加,存在则修改
update(T entity,Wrapper wrapper)条件修改,条件为null则修改全部数据
removeById(Serializable id/T entity)根据主键或包含主键的对象删除
removeBatchByIds(Collection ids)根据集合删除
remove(Wrapper wrapper)根据条件删除,条件为null则删除全部

测试

package com.hqyj.sbmp01;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import com.hqyj.sbmp01.service.HeroService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@SpringBootTest
class IServiceTest {


    @Autowired
    private HeroService heroService;


    @Test
    void test1() {
        //无条件查询所有
        //heroService.list().forEach(System.out::println);
        //条件查询,如果条件为null表示查询所有
        //heroService.list(null).forEach(System.out::println);
        //根据主键查询
        //Hero hero = heroService.getById(22);
        //System.out.println(hero);
        //根据条件查询,如果条件为null表示查询所有,会导致异常
        //System.out.println(heroService.getOne(null));
    }

    @Test
    void test2() {
        //添加save(T entity);参数对象至少要有一个属性值
        //添加后,主键自增的值会自动赋值给主键属性
        /*
        Hero hero = new Hero();
        hero.setHeroName("cvb");
        hero.setSex("男");
        hero.setPrice(2000);
        hero.setPosition("战士");
        heroService.save(hero);
        System.out.println(hero);
        */
        //批量添加,参数为要添加的对象集合
        /* Hero h1 = new Hero(0, "1", "1", "1", 100, "1999-9-9");
        Hero h2 = new Hero(0, "2", "1", "1", 100, "1999-9-9");
        Hero h3 = new Hero(0, "3", "1", "1", 100, "1999-9-9");
        List<Hero> heroes = Arrays.asList(h1, h2, h3);
        heroService.saveBatch(heroes);*/

        Hero h1 = new Hero(0, "666", "1", "1", 100, "1999-9-9");
        //如果添加的对象主键值已存在执行修改,不存在则添加
        heroService.saveOrUpdate(h1);
    }


    @Test
    void test3(){
        //根据对象修改
        Hero h1 = new Hero(999, "修改后", "修改后", "女", 100, "1999-9-9");
        //如果对象主键值已存在执行修改,不存在则添加
        //heroService.saveOrUpdate(h1);
        //根据主键修改
        heroService.updateById(h1);

        //根据条件修改
        //heroService.update(null);

        //根据实体和条件修改,如果条件为空,会将所有数据修改为指定实体的数据
        //heroService.update(h1,null);
    }

    @Test
    void test4(){
        //根据主键或带有主键值的对象删除
        //heroService.removeById(1103302663);
        /*Hero hero = new Hero();
        hero.setId(1103302662);
        heroService.removeById(hero);*/

        //批量删除
        //heroService.removeBatchByIds(Arrays.asList(77,88,99));

        //根据条件删除,条件为null则删除全部
        heroService.remove(null);
    }

}

SpringBoot+MyBatisPlus

1.创建SpringBoot项目

创建时勾选依赖

  • devtools
  • lombok
  • spring-web
  • mysql-driver

2.导入SpringBoot集成MyBatisPlus依赖

<!-- SpringBoot集成MyBatisPlus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

3.配置application.properties文件

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 开启sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 无需加入开启驼峰命名映射,MyBatisPlus默认使用驼峰命名进行属性-字段映射
#mybatis-plus.configuration.map-underscore-to-camel-case=true

4.根据数据表创建实体类

实体类的属性名命名方式:

  • MyBatisPlus默认使用驼峰命名对字段和属性进行映射。如将字段stu_name对应的属性写为stuName
  • 如果字段名和属性名不一致,在属性名上加入**@TableField(value = “字段名”)**
  • 主键字段对应的属性,需要加入@TableId注解,其type属性表示主键生成策略
    • @TableId(type = IdType.AUTO)表示主键自增,在数据库也要将主键设置为自增
    • @TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
    • @TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
@Data
public class Hero {
    //type表示主键生成策略,
    @TableId(type = IdType.AUTO)// IdType.AUTO表示主键自增,在数据库也要将主键设置为自增
    //@TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
    //@TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
    private Integer id;
    //如果属性名和字段名不一致
    @TableField(value = "name")
    private String heroName;
    private String position;
    private String sex;
    private Integer price;
    private String shelfDate;
}

5.编写数据访问层接口

可以不用写@Repository,继承BaseMapper接口,设置泛型

/*
* 数据访问层可以称为dao或mapper层
* 可以不用加@Repository注解
* */
public interface HeroMapper extends BaseMapper<Hero> {

}

6.在SpringBoot的启动类中,扫描数据访问层所在包

@SpringBootApplication
@MapperScan("com.hqyj.sbmp01.mapper")
public class Sbmp01Application {

    public static void main(String[] args) {
        SpringApplication.run(Sbmp01Application.class, args);
    }
}

测试

在SpringBoot自带的单元测试类中,注入HeroMapper对象,调用BaseMapper中定义的方法即可实现CURD。

BaseMapper接口

BaseMapper接口中定义了常用的增删改查方法,

在数据访问层接口中继承该接口

方法列表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k9NOzwWl-1676453638390)(C:\Users\hnn\AppData\Local\Temp\Temp1_框架.zip\SpringBoot+MyBatisPlus.assets\image-20230208172250774.png)]

使用

public interface HeroMapper extends BaseMapper<Hero> {

}

BaseMapper接口中的常用方法

方法名参数作用
selectList(Wrapper wrapper)条件构造器根据条件查询集合,如果实参为null表示查询所有,返回List集合
selectById(Serializable id)主键根据主键查询单个对象,返回单个对象
selectOne(Wrapper wrapper)条件构造器条件查询单个对象,返回单个对象
insert(T entity)实体对象添加单个实体
updateById(T entity)实体对象根据实体对象单个修改,对象必须至少有一个属性和主键
update(T entity,Wrapper wrapper)实体对象和条件构造器根据条件修改全部,对象必须至少有一个属性
deleteById(Serializable id/T entity)主键/实体对象根据主键删除单个对象
deleteBatchIds(Collection ids)主键集合根据集合删除
delete(Wrapper wrapper)条件构造器根据条件删除,如果实参为null表示无条件删除所有

测试

package com.hqyj.sbmp01;

import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
class Sbmp01ApplicationTests {


    @Autowired
    //@Resource
    private HeroMapper mapper;

    @Test
    void contextLoads() {
        //查询所有
        List<Hero> list = mapper.selectList(null);
        for (Hero hero : list) {
            System.out.println(hero);
        }
    }

    @Test
    void test1() {
        //根据主键查询单个对象
        System.out.println(mapper.selectById(20));
    }

    @Test
    void test2() {
        //根据id删除,参数为id
        //System.out.println(mapper.deleteById(20));

        //根据id删除,参数为带有id的对象
        /*Hero hero = new Hero();
        hero.setId(100);
        System.out.println(mapper.deleteById(hero));*/

        //删除所有
        //mapper.delete(null);

        //根据id集合删除
        ArrayList<Integer> list = new ArrayList<>();
        list.add(99);
        list.add(100);
        list.add(88);
        list.add(69);
        System.out.println(mapper.deleteBatchIds(list));
    }


    @Test
    void test3() {
        //根据id修改
        //参数为一个对象,这个对象至少要有一个非主键属性有值
        Hero hero = new Hero();
        hero.setHeroName("a");
        hero.setPosition("b");
        hero.setSex("c");
        hero.setShelfDate("d");
        hero.setPrice(123);
        hero.setId(999);
        //System.out.println(mapper.updateById(hero));

        // 修改全部数据
        // mapper.update(修改后的对象,条件构造器)
        mapper.update(hero,null);
    }

    @Test
    void test4() {
        Hero hero = new Hero();
        hero.setHeroName("asdfsdfsdf");
        hero.setPosition("b");
        hero.setSex("c");
        hero.setShelfDate("1999-9-9");
        hero.setPrice(123);
        mapper.insert(hero);
    }
}

IService接口

IService接口减少业务逻辑层的代码,并对BaseMapper进行了拓展

在业务流程类中继承该接口

部分方法列表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-715jNTGy-1676453638391)(C:\Users\hnn\AppData\Local\Temp\Temp1_框架.zip\SpringBoot+MyBatisPlus.assets\image-20230208172729669.png)]

使用

  • 1.创建一个业务层接口,继承IService接口,设置泛型

    /*
    * 创建业务逻辑层接口,继承IService<T>接口,设置泛型
    * */
    public interface HeroService extends IService<Hero> {
    }
    
    
  • 2.创建一个业务层接口的实现类,添加@Service注解,继承 ServiceImpl<M, T>,实现上一步创建的接口

    • M是数据访问层接口
    • T是实体类
    /*
    * 1.添加@Service
    * 2.继承ServiceImpl<M, T> M是Mapper类 T是实体类
    * 3.实现自定义Service接口
    * */
    @Service
    public class ImpHeroService extends ServiceImpl<HeroMapper, Hero> implements HeroService {
    
    }
    

IService接口中的常用方法

方法作用
list()无条件查询所有
list(Wrapper wrapper)条件查询素有
page(Page page)无条件分页查询,Page是分页模型对象
page(Page page,Wrapper wrapper)条件分页查询,Page是分页模型对象
getById(Serializable id)根据主键查询单个对象
getOne(Wrapper wrapper)条件查询单个对象
save(T entity)添加单个对象
save(Collection col)批量添加对象的集合
updateById(T entity)修改,参数至少有一个属性值和主键
saveOrUpdate(T entity)添加或修改。如果实参对象的主键值不存在则添加,存在则修改
update(T entity,Wrapper wrapper)条件修改,条件为null则修改全部数据
removeById(Serializable id/T entity)根据主键或包含主键的对象删除
removeBatchByIds(Collection ids)根据集合删除
remove(Wrapper wrapper)根据条件删除,条件为null则删除全部

测试

package com.hqyj.sbmp01;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hqyj.sbmp01.entity.Hero;
import com.hqyj.sbmp01.mapper.HeroMapper;
import com.hqyj.sbmp01.service.HeroService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@SpringBootTest
class IServiceTest {


    @Autowired
    private HeroService heroService;


    @Test
    void test1() {
        //无条件查询所有
        //heroService.list().forEach(System.out::println);
        //条件查询,如果条件为null表示查询所有
        //heroService.list(null).forEach(System.out::println);
        //根据主键查询
        //Hero hero = heroService.getById(22);
        //System.out.println(hero);
        //根据条件查询,如果条件为null表示查询所有,会导致异常
        //System.out.println(heroService.getOne(null));
    }

    @Test
    void test2() {
        //添加save(T entity);参数对象至少要有一个属性值
        //添加后,主键自增的值会自动赋值给主键属性
        /*
        Hero hero = new Hero();
        hero.setHeroName("cvb");
        hero.setSex("男");
        hero.setPrice(2000);
        hero.setPosition("战士");
        heroService.save(hero);
        System.out.println(hero);
        */
        //批量添加,参数为要添加的对象集合
        /* Hero h1 = new Hero(0, "1", "1", "1", 100, "1999-9-9");
        Hero h2 = new Hero(0, "2", "1", "1", 100, "1999-9-9");
        Hero h3 = new Hero(0, "3", "1", "1", 100, "1999-9-9");
        List<Hero> heroes = Arrays.asList(h1, h2, h3);
        heroService.saveBatch(heroes);*/

        Hero h1 = new Hero(0, "666", "1", "1", 100, "1999-9-9");
        //如果添加的对象主键值已存在执行修改,不存在则添加
        heroService.saveOrUpdate(h1);
    }


    @Test
    void test3(){
        //根据对象修改
        Hero h1 = new Hero(999, "修改后", "修改后", "女", 100, "1999-9-9");
        //如果对象主键值已存在执行修改,不存在则添加
        //heroService.saveOrUpdate(h1);
        //根据主键修改
        heroService.updateById(h1);

        //根据条件修改
        //heroService.update(null);

        //根据实体和条件修改,如果条件为空,会将所有数据修改为指定实体的数据
        //heroService.update(h1,null);
    }

    @Test
    void test4(){
        //根据主键或带有主键值的对象删除
        //heroService.removeById(1103302663);
        /*Hero hero = new Hero();
        hero.setId(1103302662);
        heroService.removeById(hero);*/

        //批量删除
        //heroService.removeBatchByIds(Arrays.asList(77,88,99));

        //根据条件删除,条件为null则删除全部
        heroService.remove(null);
    }

}

条件构造器Wrapper

BaseMapper和IService接口中有很多方法都有这个参数,表示一个条件构造器对象。

如果该参数实际传递的值为null,表示没有任何条件,

这个Wrapper是一个抽象类,如果想要带条件,就要创建一个该类的子类对象。

常用子类为QueryWrapper和UpdateWrapper,

查询是创建QueryWrapper对象,更新时创建UpdateWrapper,实际使用无区别。

Wrapper对象带参数

Wrapper<T> wrapper =  new QueryWrapper(T entity);
QueryWrapper<T> wrapper =  new QueryWrapper(T entity);

Wrapper构造方法的参数如果是一个实体对象,只要该对象的属性不为空,就会将所有属性用and拼接起来作为条件。

这种适合已知某个字段为某个值时使用。

@Test
void test1() {
    //创建实体对象,只带两个属性值
    Hero hero = new Hero();
    hero.setHeroName("瑞兹");
    hero.setSex("女");
    //创建一个带实体参数的条件构造器对象
    Wrapper<Hero> wrapper = new QueryWrapper<>(hero);
    System.out.println(heroService.getOne(wrapper));
}

Wrapper对象不带参数

Wrapper<T> wrapper =  new QueryWrapper();
QueryWrapper<T> wrapper =  new QueryWrapper();

当条件构造器不带参数时,就需要自定义条件。

这种适用于自定义查询条件。

默认多个条件时,用and连接,如果条件之间要使用or,调用or()方法

分页查询

MyBatisPlus中集成了分页功能,只需在项目的启动类中注入一个分页拦截器对象

/*
        注入一个MyBatisPlus提供的分页拦截器对象
        定义一个方法,在方法上加入@Bean注解,将该方法的返回值注入到Spring容器中
    */
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //创建一个MybatisPlus拦截器对象
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //通过拦截器对象添加分页拦截器对象,设置数据库类型
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}
@Test
void test1(){
    //无条件分页查询page(Page pageInfo)
    //1.创建一个分页模型对象,设置泛型,参数为当前页和每页显示的记录数
    Page<Hero> pageInfo = new Page<>(1, 5);
    //2.调用IService中的page方法,参数为分页模型对象,返回值也是该对象
    pageInfo = heroService.page(pageInfo);
    //分页的相关信息都保存在分页模型对象pageInfo中
    System.out.println("总页数"+pageInfo.getPages());
    System.out.println("当前页"+pageInfo.getCurrent());
    System.out.println("每页显示的记录数"+pageInfo.getSize());
    System.out.println("总记录数"+pageInfo.getTotal());
    System.out.println("分页后的集合"+pageInfo.getRecords());
    System.out.println("是否有下一页"+pageInfo.hasNext());
    System.out.println("是否有上一页"+pageInfo.hasPrevious());


    //条件分页page(Page pageInfo,Wrapper wrapper)
    //分页查询所有法师
    pageInfo= heroService.page(pageInfo,new QueryWrapper<Hero>().eq("position","法师"));
    pageInfo.getRecords().forEach(System.out::println);
}

代码生成器

MyBatisPlus中用于自动生成entity、mapper、service、controller这些类和接口的工具

1.添加依赖

<!-- 代码生成器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3</version>
</dependency>
<!--代码生成器所需引擎-->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

2.代码生成器类

修改以下几处

  • 数据库信息
  • 输出目录为本地硬盘目录
  • 父包名
  • 要生成的表名
package com.hqyj.question_sys.util;


import com.baomidou.mybatisplus.generator.FastAutoGenerator;

public class CodeGenerator {
    public static void main(String[] args) {
        //***数据库的url, 用户名和密码
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/question_sys?serverTimezone=Asia/Shanghai",
                                 "root", "root")
            .globalConfig(builder -> {
                builder.author("HQYJ") // 设置作者,生成文件的注释里的作者
                    .outputDir("F:\\框架\\question_sys\\src\\main\\java"); //***指定输出目录
            }).packageConfig(builder -> {
            builder.parent("com.hqyj.question_sys"); //***设置父包名
            // .controller("controller") //控制层包名
            // .service("service") //service接口包名
            // .serviceImpl("service.impl")  //service接口实现包名
            // .mapper("mapper")  //mapper接口包名
            // .xml("mapper.xml") //映射文件的包名(如果要生成的话,加上这句,去掉下面的.xml方法)
            // .entity("entity"); //实体类的包名
        }).strategyConfig(builder -> {
            builder.addInclude("question_catalog","question")  //***设置需要生成的表名
                .controllerBuilder();//只生成Controller
            //.enableRestStyle(); //生成的Controller类添加@RestController;
        }).templateConfig(builder -> {
            builder.xml(null); //禁止生成xml映射文件
        }).execute();
    }
}

运行该类即可自动生成

Thymeleaf

SpringBoot官方建议使用的页面模板引擎,代替之前的JSP。

它是以HTML为基础,可以展示静态数据,方便前端人员开发静态页面,

也可以通过Thymeleaf的语法,配合EL输出由控制器跳转而来的动态数据。

Thymeleaf从入门到精通 - 知乎 (zhihu.com)

用法

1.添加依赖

<!--thymeleaf页面模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.7.3</version>
</dependency>

2.在temlates目录下创建一个HTML页面

在页面的HTML标签中,加入xmlns:th="http://www.thymeleaf.org"属性。

可以通过修改IDEA中的HTML页面模板,之后每次创建HTML页面都会有该属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24RHrwYp-1676453638392)(C:\Users\hnn\AppData\Local\Temp\Temp1_框架.zip\day14-SpringBoot+MyBatisPlus2.assets\image-20230209143658152.png)]

3.thymeleaf具体使用

  • 双标签中的输出变量: th:text

    <h1 th:text="${作用域中某个对象名}"></h1>
    
    <h1 th:text="${username}"></h1>
    
  • 单标签中设置value的值:th:value

    <input type="text" th:value="${作用域中某个对象名}">
    
    <input type="text" th:value="${变量.属性名}">
    
  • 遍历:th:each

    <table>
        <tr th:each="变量:${作用域中的集合名}">
        	<td th:text="${变量.属性名}"></td>
        </tr>
    </table>
    
    <table>
        <tr th:each="hero:${heroList}">
        	<td th:text="${hero.id}"></td>
            <td th:text="${hero.name}"></td>
            <td th:text="${hero.sex}"></td>
        </tr>
    </table>
    
  • 超链接获取全局路径:th:href=“@{/}”

    • 不带参数

      <link th:href="@{/bootstrap-3.4.1-dist/css/bootstrap.css}" rel="stylesheet">
      
    • 带参数

      <a th:href="@{'/hero/delete?id='+${hero.id}">删除</a>
      
      <a th:href="@{/question/findById(id=${question.quesCode})}">编辑</a>
      
  • 表单提交路径:th:action=“@{/}”

    <form methos="post" th:action="@{/hero/insert}">
        
    </form>
    
  • 判断:th:if

    <h1 th:if="判断逻辑">用户信息</h1>
    
    <h1 th:if="${userinfo.username==null}">请登录</h1>
    
  • 选中单选按钮或复选框:th:checked

    <input type="radio" value="" name="sex" th:checked="逻辑判断">
    
    <input type="radio" value="" name="sex" th:checked="${user.sex=='男'}"><input type="radio" value="" name="sex" th:checked="${user.sex eq '女'}">
  • 选中下拉菜单:th:selected

    <select>
        <option th:selected="逻辑判断"></option>
    </select>
    
    <select>
        <option th:selected="${book.typeId==bookType.typeId}" th:each="bookType:${list}" th:value="${bookType.typeId}" th:text="${bookType.typeName}"></option>
    </select>
    
  • src属性:th:src

    <script th:src="@{/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js}"></script>
    
  • 内联表达式

    Thymeleaf页面中使用[[]]可以在script标签中使用EL表达式,这两对中括号拼接的内容称为内联表达式。

    在给script标签加入**th:inline=“javascript”**后使用

    <script th:inline="javascript">
        $(function () {
            //遍历页码
            $(".pno").each(function (){
                //如果页码和当前页一致,高亮显示
                if($(this).text()==[[${pageInfo.current}]]){
                    $(this).addClass("active");
                }
            });
        });
    </script>
    
    <script th:inline="javascript">
        $(function () {
            //使用ajax读取所有的题目类型对象,遍历成option
            $.ajax({
                url:[[@{/qc/queryAll}]],
                success:function (res){
                    for(var i=0;i<res.length;i++){
                        let qcId = res[i].qcId;
                        let qcName = res[i].qcName;
                        $("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));
                    }
                }
            });
        });
    </script>
    

Spring Data JPA

2001年推出了Hibernate,是一个全自动ORM框架。可以不用编写SQL语句,就能实现对数据库的持久化操作。

SUN公司在Hibernate的基础上,制定了JPA,全称 Java Persisitence API,中文名Java持久化API,

是一套Java访问数据库的规范,由一系列接口和抽象类构成。

后来Spring团队在SUN公司制定的JPA这套规范下,推出了Spring Data JPA,是JPA的具体实现。

如今常说的JPA,通常指Spring Data JPA。

SpringBoot集成Spring Data JPA

1.创建SpringBoot项目,选择依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaL6I0xk-1676453638392)(C:\Users\hnn\Desktop\image-20230215171457393.png)]

2.编辑配置文件,设置要连接的数据库信息

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 设置数据库类型
spring.jpa.database=mysql
# 打印SQL语句
spring.jpa.show-sql=true

3.创建实体类

  • 类上加**@Entity**注解

  • 主键属性上加

    • @Id注解标明主键

    • **@GeneratedValue(strategy = GenerationType.IDENTITY)**设置MySQL数据库主键生成策略,数据库设置为自增

  • 其他属性名与字段名一致或驼峰命名法

    • 如果字段名多个单词之间用_,使用驼峰命名法
    • 如果不相同,使用**@Column(name=“字段名”)**注解指定该属性对应的字段名
@Data
@Entity
/*
* 实体类的属性名建议使用驼峰命名法
* */
public class BookInfo {
    @Id//主键字段
    //主键生成策略,GenerationType.IDENTITY表示MySQL自增
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer bookId;
    private Integer typeId;
    private String bookName;
    private String bookAuthor;
    //如果字段名和属性名不一致,使用@Column指定字段名
    @Column(name = "book_price")
    private Integer price;
    private Integer bookNum;
    private String publisher_date;
}

4.数据访问层接口

  • 类上加@Repository注解
  • 继承JpaRepository<实体类型,主键类型>接口
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {

}

5.测试常用方法

方法名返回值作用
findAll()List查询所有数据。
save(T entity)T entity添加或修改。如果不存在主键属性或主键值不存在,执行添加;如果存在主键属性且有该主键值,执行修改。
delete(T entity)void根据对象删除。如果该对象中有主键属性且有该主键值,根据该主键值删除。
findById(主键)Optional根据主键值查询。返回的对象调用isPresent()结果为true,表示查询到了数据,继续调用get()得到查询到的对象。
@SpringBootTest
class Day16SpringBootJpaApplicationTests {


    @Autowired
    private BookInfoDao bookInfoDao;

    @Test
    void contextLoads() {
        List<BookInfo> all = bookInfoDao.findAll();
        all.forEach(System.out::println);
    }

    @Test
    void insert() {
        BookInfo bookInfo = new BookInfo();
        bookInfo.setBookName("测试");
        bookInfo.setBookAuthor("测试");
        bookInfo.setBookNum(156);
        bookInfo.setPrice(20);
        bookInfo.setTypeId(1);
        //save()方法调用时,如果对象中没有主键或主键值不存在,作为添加使用
        BookInfo save = bookInfoDao.save(bookInfo);
        //添加成功后,会自动获取主键自增的值
        System.out.println(save);
    }

    @Test
    void update() {
        BookInfo bookInfo = new BookInfo();
        bookInfo.setBookName("xxxxxxxxxxx");
        bookInfo.setBookAuthor("测试");
        bookInfo.setBookNum(356);
        bookInfo.setPrice(23);
        bookInfo.setTypeId(1);
        //save()方法调用时,如果对象中有主键且存在,作为修改使用
        BookInfo save = bookInfoDao.save(bookInfo);
        //修改成功后,返回修改后的对象
        System.out.println(save);
    }
    @Test
    void delete() {
        //根据主键值删除,如果值不存在,会报错
        //bookInfoDao.deleteById(36);

        //根据对象删除,如果对象中包含主键值则删除,如果没有值或不存在,不会报错
        BookInfo bookInfo = new BookInfo();
        //bookInfo.setBookId(330);
        bookInfoDao.delete(bookInfo);
    }


    @Test
    void findOne() {
        //根据主键查询,返回值Optional类型
        Optional<BookInfo> byId = bookInfoDao.findById(60);
        //isPresent()如果为true表示查询到了数据
        if (byId.isPresent()) {
            //get()将查询到的数据转换为对应的实体类
            BookInfo bookInfo=byId.get();
            System.out.println(bookInfo);
        }else{
            System.out.println("未查询到数据");
        }
    }
}

JPA进阶

分页查询

调用数据访问层中的**findAll(Pageable pageable)**方法,即可实现分页。

参数Pageable是org.springframework.data.domain包中的一个接口,通过其实现类

PageRequest,调用静态方法of(int page,int size),当做Pageable对象使用。

这里的page从0开始为第一页。

@Test
void queryByPage(){
    
    //PageRequest是Pageable的实现类,调用静态方法of(int page,int size)
    //这里的page的值0表示第一页
    //调用findAll(Pageable pageable)方法,返回分页模型对象
    Page<BookInfo> pageInfo = bookInfoDao.findAll(PageRequest.of(0,5));
    //分页相关数据
    System.out.println("总记录数"+pageInfo.getTotalElements());
    System.out.println("最大页数"+pageInfo.getTotalPages());
    System.out.println("分页后的数据集合"+pageInfo.getContent());
    System.out.println("当前页数"+pageInfo.getNumber());
    System.out.println("每页显示的记录数"+pageInfo.getSize());
    System.out.println("是否还有下一页"+pageInfo.hasNext());
    System.out.println("是否还有上一页"+pageInfo.hasPrevious());
}

条件查询

在JPA中,使用自定义方法名自动生成对应的SQL语句,实现条件查询。

如在dao中定义了queryById(int id)方法,就表示根据id查询,自动生成sql语句。

方法命名格式

[xxx] [By] [字段对应的属性名] [规则] [Or/And] [字段对应的属性名] [规则] …

  • **xxx可以是find、get、query、search
  • 方法如果有参数,参数的顺序和方法名中的参数顺序一致

如findByBookNameAndBookAuthor(String bookName,String bookAuthor),

对应的sql语句为 select * from book where book_name =? and book_author=?

常用规则

规则方法名SQL中的条件
指定值findByBookName(String name)book_name = name
Or/AndfindByBookNameOrBookAuthor(String name,String author)book_name = name or book_author = author
After/BeforfindByBookPriceAfter(double price)book_price > price
GreaterThanEqual/LessThanEqualfindByBookNumLessThanEqual(int num)book_num <= num
BetweenfindByBookNumBetween(int min,int max)book_num between min and max
Is[Not]NullfindByPublisherDateIsNull()publish_date is null
[Not]LikefindByBookNameLike(String condition)book_name like ‘condition’
[Not]ContainsfindByBookNameContains(String keyword)book_name like ‘%keyword%’
StartsWith/EndsWithfindByBookNameStartsWith(String firstName)book_name like ‘firstName%’
无条件排序:findAllByOrderBy字段[Desc/Asc]findAllByOrderByBookId()order by book_id asc
有条件排序:findAllBy条件OrderBy字段[Desc/Asc]findAllByTypeIdOrderByBookIdDesc()type_id = ? order by book_id desc
@Repository
public interface BookInfoDao extends JpaRepository<BookInfo,Integer> {


    //指定值查询
    //根据书名查询
    List<BookInfo> getAllByBookName(String x);

    //查询价格大于指定值   字段对应的属性名  After/GreaterThan
    List<BookInfo> findAllByPriceAfter(int price);

    //查询价格小于于指定值    字段对应的属性名  Before/LessThan
    List<BookInfo> findAllByPriceLessThan(int price);


    //查询库存大于等于指定值 GreaterThanEqual
    List<BookInfo> queryAllByBookNumGreaterThanEqual(int num);

    //查询库存在指定闭区间内 Between(int min,int max)
    List<BookInfo> findAllByBookNumBetween(int min,int max);


    //空值查询 null
    //查询出版日期为空  IsNull/IsNotNull
    List<BookInfo> findAllByPublisherDateIsNull();

    //书名中带有关键字 Like/NotLike 实参一定要使用%或_
    List<BookInfo> getAllByBookNameLike(String keyword);

    //作者名中带有关键字  Contains/NotContains 实参只需要关键字
    List<BookInfo> getAllByBookAuthorContains(String keyword);

    //指定作者的姓   指定开头/结尾  StartsWith/EndsWith
    List<BookInfo> getAllByBookAuthorStartsWith(String keyword);


    //查询所有数据,按价格降序    无条件排序 OrderBy字段[Desc/Asc]
    List<BookInfo> getAllByOrderByPriceDesc();

    //查询指定类型,按id降序
    List<BookInfo> getAllByTypeIdOrderByBookIdDesc(Integer typeId);

}

条件分页查询

只需在定义方法时,将方法的返回值设置为Page类型,在参数中就如Pageable对象

//根据作者和书名的关键字分页查询
Page<BookInfo> getAllByBookNameContainsOrBookAuthorContains(
    String nameKeyword,
    String authorKeyword,
    Pageable pageable);
Page<BookInfo> pageInfo = bookInfoDao.getAllByBookNameContainsOrBookAuthorContains("龙","山",PageRequest.of(0,5));
//分页相关数据保存在pageInfo对象中

前后端分离项目

前后端分离,就是将web应用中的前端页面和后端代码分开完成、部署。

  • 前后端的开发者只需要完成各自的事情,最终以文档的形式约定数据接口(URL、参数、返回值、请求方式)
  • 前后端分别用独立的服务器
  • 后端只需处理数据并提供访问接口(路径),以RESTFul风格的JSON格式传输数据
  • 前端只需负责渲染页面和展示数据

传统项目和前后端分离项目对比

传统项目

前端和后端的代码运行在一个服务器上,页面经由控制器跳转

前后端分离项目

前后端的代码分别运行在各自的服务器上

后端提供JSON格式字符串的数据接口

前端负责跳转、解析JSON数据。

前后端分离项目后端控制层设计

请求方式设计:RESTFul风格

风格,不是标准,可以不用强制遵循。

RESTFul风格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。

特点

  • 通过URL就能知道当前在哪个模块
  • 通过不同的请求方式决定执行什么操作
  • 通过返回的状态码得到操作结果

使用RESTFul风格和普通方式对比

普通方式

localhost:8080/user/queryAll							查询所有
localhost:8080/user/queryById?id=1001					条件查询
localhost:8080/user/insert?name=ez&sex=男&age=20			添加
localhost:8080/user/update?name=ez&id=1001				修改
localhost:8080/user/delete?id=1001						删除

RESTFul风格

localhost:8080/user							查询所有get请求
localhost:8080/user/1001					条件查询get请求
localhost:8080/user							添加post请求
localhost:8080/user							修改put请求
localhost:8080/user/1001					删除delete请求

RESTFul风格具体使用

  • 在请求映射的命名上,统一用小写字母的名词形式表示当前位于哪个模块。如/user、/book_info

  • 访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取

    @GetMapping("/book/{id}")
    public BookInfo queryById(@PathVariable("id")Integer id){
        return service.findById(id);
    }
    
  • 在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式

    • @GetMapping(“路径”) 查询
    • @PostMapping(“路径”) 添加
    • **@PutMapping(“路径”) ** 修改
    • **@DeleteMapping(“路径”) ** 删除
    • @RequestMapping(value=“路径”,method=RequestMethod.GET/POST/PUT/DELETE))
  • 如果请求方式不匹配,会报405异常

  • 在同一个controller中,不能出现两个请求方式和路径都一致的方法

返回值设计

前后端分离项目的控制层方法的返回值也需要进行统一。

返回值通常包含以下信息

  • 传递状态,用状态码表示Integer code
  • 传递消息,用字符串表示String msg
  • 传递集合,用集合表示List list
  • 传递对象,用对象表示Object obj

将这些信息封装到一个对象中,这个对象称为返回结果类RestResult对象

RestResult类具体设计

package com.hqyj.day16springbootjpa.util;

import lombok.Data;

import java.util.List;

/*
* 定义控制层返回数据的模板类
* 包含
* 状态码
* 消息文字
* 集合
* 对象
*
* */
@Data
public class RestResult<T> {

    //状态码  0表示成功,其他表示各种情况
    private Integer code;
    //消息文字
    private String msg;
    //集合
    private List<T> list;
    //对象
    private Object obj;



    //创建构造方法,方便创建指定参数的对象



    /*
    * 适用于返回集合
    * 如查询集合
    * */
    public RestResult(Integer code, String msg, List<T> list) {
        this.code = code;
        this.msg = msg;
        this.list = list;
    }

    /*
    * 适用于返回对象
    * 如查询单个、添加、修改
    * */
    public RestResult(Integer code, String msg,  Object obj) {
        this.code = code;
        this.msg = msg;
        this.obj = obj;
    }

    /*
    * 适用于无需返回对象时
    * 如删除
    * */
    public RestResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    //用于返回集合操作成功时调用
    public  static<T> RestResult ok(String msg,List<T> list){
        return  new RestResult(0,msg,list);
    }

    //用于返回对象操作成功时调用
    public  static<T> RestResult ok(String msg,Object obj){
        return  new RestResult(0,msg,obj);
    }

    //用于操作成功时调用
    public  static<T> RestResult ok(String msg){
        return  new RestResult(0,msg);
    }

    //用于异常情况
    public  static<T> RestResult error(Integer code, String msg){
        return  new RestResult(code,msg);
    }

}

Java技术栈

服务器端

​ JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)

​ tomcat

前端

​ HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE

数据库

​ MySQL、Oracle、Redis

​ JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA

工具

​ IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman

后端只需处理数据并提供访问接口(路径),以RESTFul风格的JSON格式传输数据

  • 前端只需负责渲染页面和展示数据

传统项目和前后端分离项目对比

传统项目

前端和后端的代码运行在一个服务器上,页面经由控制器跳转

前后端分离项目

前后端的代码分别运行在各自的服务器上

后端提供JSON格式字符串的数据接口

前端负责跳转、解析JSON数据。

前后端分离项目后端控制层设计

请求方式设计:RESTFul风格

风格,不是标准,可以不用强制遵循。

RESTFul风格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。

特点

  • 通过URL就能知道当前在哪个模块
  • 通过不同的请求方式决定执行什么操作
  • 通过返回的状态码得到操作结果

使用RESTFul风格和普通方式对比

普通方式

localhost:8080/user/queryAll							查询所有
localhost:8080/user/queryById?id=1001					条件查询
localhost:8080/user/insert?name=ez&sex=男&age=20			添加
localhost:8080/user/update?name=ez&id=1001				修改
localhost:8080/user/delete?id=1001						删除

RESTFul风格

localhost:8080/user							查询所有get请求
localhost:8080/user/1001					条件查询get请求
localhost:8080/user							添加post请求
localhost:8080/user							修改put请求
localhost:8080/user/1001					删除delete请求

RESTFul风格具体使用

  • 在请求映射的命名上,统一用小写字母的名词形式表示当前位于哪个模块。如/user、/book_info

  • 访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取

    @GetMapping("/book/{id}")
    public BookInfo queryById(@PathVariable("id")Integer id){
        return service.findById(id);
    }
    
  • 在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式

    • @GetMapping(“路径”) 查询
    • @PostMapping(“路径”) 添加
    • **@PutMapping(“路径”) ** 修改
    • **@DeleteMapping(“路径”) ** 删除
    • @RequestMapping(value=“路径”,method=RequestMethod.GET/POST/PUT/DELETE))
  • 如果请求方式不匹配,会报405异常

  • 在同一个controller中,不能出现两个请求方式和路径都一致的方法

返回值设计

前后端分离项目的控制层方法的返回值也需要进行统一。

返回值通常包含以下信息

  • 传递状态,用状态码表示Integer code
  • 传递消息,用字符串表示String msg
  • 传递集合,用集合表示List list
  • 传递对象,用对象表示Object obj

将这些信息封装到一个对象中,这个对象称为返回结果类RestResult对象

RestResult类具体设计

package com.hqyj.day16springbootjpa.util;

import lombok.Data;

import java.util.List;

/*
* 定义控制层返回数据的模板类
* 包含
* 状态码
* 消息文字
* 集合
* 对象
*
* */
@Data
public class RestResult<T> {

    //状态码  0表示成功,其他表示各种情况
    private Integer code;
    //消息文字
    private String msg;
    //集合
    private List<T> list;
    //对象
    private Object obj;



    //创建构造方法,方便创建指定参数的对象



    /*
    * 适用于返回集合
    * 如查询集合
    * */
    public RestResult(Integer code, String msg, List<T> list) {
        this.code = code;
        this.msg = msg;
        this.list = list;
    }

    /*
    * 适用于返回对象
    * 如查询单个、添加、修改
    * */
    public RestResult(Integer code, String msg,  Object obj) {
        this.code = code;
        this.msg = msg;
        this.obj = obj;
    }

    /*
    * 适用于无需返回对象时
    * 如删除
    * */
    public RestResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    //用于返回集合操作成功时调用
    public  static<T> RestResult ok(String msg,List<T> list){
        return  new RestResult(0,msg,list);
    }

    //用于返回对象操作成功时调用
    public  static<T> RestResult ok(String msg,Object obj){
        return  new RestResult(0,msg,obj);
    }

    //用于操作成功时调用
    public  static<T> RestResult ok(String msg){
        return  new RestResult(0,msg);
    }

    //用于异常情况
    public  static<T> RestResult error(Integer code, String msg){
        return  new RestResult(code,msg);
    }

}

Java技术栈

服务器端

​ JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)

​ tomcat

前端

​ HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE

数据库

​ MySQL、Oracle、Redis

​ JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA

工具

​ IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值