框架
一套规范。
实际是他人实现的一系列接口和类的集合。通入导入对应框架的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、JPA、MyBatis、MyBatisPlus等
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">«</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">»</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/And | findByBookNameOrBookAuthor(String name,String author) | book_name = name or book_author = author |
After/Befor | findByBookPriceAfter(double price) | book_price > price |
GreaterThanEqual/LessThanEqual | findByBookNumLessThanEqual(int num) | book_num <= num |
Between | findByBookNumBetween(int min,int max) | book_num between min and max |
Is[Not]Null | findByPublisherDateIsNull() | publish_date is null |
[Not]Like | findByBookNameLike(String condition) | book_name like ‘condition’ |
[Not]Contains | findByBookNameContains(String keyword) | book_name like ‘%keyword%’ |
StartsWith/EndsWith | findByBookNameStartsWith(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