SpringBoot——RestfulCRUD练习项目

只是练习thymeleaf的用法,所以没有使用数据库;如果觉得非要加数据库的同学,可以自行修改。

项目已上传到github:https://github.com/angenin/SpringBootDemo(在原基础上加上mybatis)

环境与技术

电脑环境:macOS
电脑软件:IDEA
基础框架:SpringBoot2.3.0
前端框架:bootstrap
模板引擎:thymeleaf
服务器:Tomcat
依赖管理:Maven3.5.4

资源下载

静态资源、页面和dao代码下载:https://pan.baidu.com/s/1IZ5UvyYm1iCN1V276qMqQw 密码:tgnt
在这里插入图片描述


新建项目


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下一步,创建项目
在这里插入图片描述

引入依赖

pom文件还需要引入jquery、bootstrap的依赖:

   <!--   引入jquery-webjar     -->
   <dependency>
       <groupId>org.webjars</groupId>
       <artifactId>jquery</artifactId>
       <version>3.4.1</version>
   </dependency>


   <!--   引入bootstrap     -->
   <dependency>
       <groupId>org.webjars</groupId>
       <artifactId>bootstrap</artifactId>
       <version>4.3.1</version>
   </dependency>

导入资源

把整个asserts目录放到项目的resources/static目录下,把dao和entities两个目录放到main/java/com/angenin/springboot目录下,剩下4个页面放到resources/templates目录下。
在这里插入图片描述


RestfulCRUD


1. 默认访问首页

在java/com/angenin/springboot新建config/MyMvcConfig配置类,专门用于mvc相关的配置。
MyMvcConfig:

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override	//添加视图映射
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
    }
}

把resources/templates目录下的index.html改名为login.html。

启动项目,在浏览器输入http://localhost:8080,成功访问到登录页面。

  • 往每个页面的< html >标签中加入xmlns:th="http://www.thymeleaf.org"

  • 修改login.html里的< link >标签

    <link th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.css}" rel="stylesheet">
    <link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
    
  • 其他页面的link标签为:

    <link th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.min.css}" rel="stylesheet">
    <link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
    
  • 往login.html里的< img >标签加入th:src="@{/asserts/img/bootstrap-solid.svg}"
    这样修改以后,然后以后项目名变了,thymeleaf会自动帮我们修改资源的访问路径。

例如这样,我们来修改项目的访问名
在application.properties配置文件中加入

server.servlet.context-path=/crud

现在在需要浏览器输入http://localhost:8080/crud才能访问到首页,并且页面代码也自动加了/crud。

2. 国际化

  1. 编写国际化配置文件,抽取页面需要显示的国际化消息
    在resources下新建一个i18n目录,在目录里新建login.properties和login_zh_CN.properties文件。

    当IDEA识别到我们要做国际化文件时,会自动切换到国际化视图。
    在这里插入图片描述
    再新建一个文件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    添加属性
    在这里插入图片描述

    抽取页面需要显示的国际化消息
    在这里插入图片描述
    在这里插入图片描述

    login.properties(默认)login_en_US.properties(英文)login_zh_CN.properties(中文)
    login.tip请登录~Please sign in请登录
    login.username用户名~username用户名
    login.password密码~password密码
    login.remember记住我~Remember Me记住我
    login.btn登录~Sign in登录
  2. 在国际化的自动配置类中,使用了ResourceBundleMessageSource管理国际化资源文件。
    MessageSourceAutoConfiguration.class

       	@Bean
        @ConfigurationProperties(
            prefix = "spring.messages"
        )
        public MessageSourceProperties messageSourceProperties() {
            return new MessageSourceProperties();
        }
    
        @Bean	//MessageSource(ResourceBundleMessageSource)是管理国际化的组件
        public MessageSource messageSource(MessageSourceProperties properties) {
            ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
            if (StringUtils.hasText(properties.getBasename())) {
            	//设置国际化资源文件的基础名(去掉语言国家代码的)
                messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
            }
    
            if (properties.getEncoding() != null) {
                messageSource.setDefaultEncoding(properties.getEncoding().name());
            }
    
            messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
            Duration cacheDuration = properties.getCacheDuration();
            if (cacheDuration != null) {
                messageSource.setCacheMillis(cacheDuration.toMillis());
            }
    
            messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
            messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
            return messageSource;
        }
    

    在application.properties中加入spring.messages.basename=i18n.login

  3. 修改页面取出国际化内容
    修改login.html

    			<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
    			<label class="sr-only" th:text="#{login.username}">Username</label>
    			<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
    			<label class="sr-only" th:text="#{login.password}">Password</label>
    			<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
    			<div class="checkbox mb-3">
    				<label>
              <input type="checkbox" value="remember-me"> [[#{login.remember}]]
            </label>
    			</div>
    			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
    

    我的google浏览器是选的是中文,所有会使用中文的国际化内容
    在这里插入图片描述
    启动项目,刷新页面
    在这里插入图片描述
    如果出现乱码:
    在这里插入图片描述
    但是如果在项目里这个修改,这是在这个项目里解决了乱码问题,下次打开别的项目还是一样会乱码(相当于局部配置)。
    而如果想修改后,每个项目都不会乱码的话,需要进行全局修改。
    在这里插入图片描述
    然后按照上一张图修改即可,这样就解决了乱码问题。
    修改浏览器语言
    在这里插入图片描述
    刷新页面就切换成英语的国际化内容
    在这里插入图片描述

  4. 实现点击登录页面的中文或English切换语言
    原理:
    国际化Locale:区域信息对象
    LocaleResolver:获取区域信息对象
    在WebMvcAutoConfiguration.class自动配置类源码中注册了LocaleResolver组件

            @Bean
            @ConditionalOnMissingBean	//当容器中没有用户注册LocaleResolver才自动注册默认的LocaleResolver
            @ConditionalOnProperty(prefix = "spring.mvc", name = {"locale"})
            public LocaleResolver localeResolver() {
                if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
                    return new FixedLocaleResolver(this.mvcProperties.getLocale());
                } else {
                    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
                    return localeResolver;
                }
            }
    

    默认的就是根据请求头带来的的区域信息获取Locale来进行国际化。
    在这里插入图片描述
    浏览器语言切换成中文
    在这里插入图片描述
    修改login.html的两个语言的a标签

    	<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
    	<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
    

    在main/java/com/angenin/springboot下新建component/MyLocaleResolver

    import org.springframework.util.StringUtils;
    import org.springframework.web.servlet.LocaleResolver;
    
    public class MyLocaleResolver implements LocaleResolver {
        @Override   //解析区域信息
        public Locale resolveLocale(HttpServletRequest httpServletRequest) {
            //获取请求的l参数
            String l = httpServletRequest.getParameter("l");
            //默认的信息
            Locale locale = Locale.getDefault();
            //检测l参数是否为空
            if(!StringUtils.isEmpty(l)){
                //不为空
                String[] s = l.split("_");
                //第一个参数为语言代码,第二个参数为国家代码
                locale = new Locale(s[0], s[1]);
            }
    
            return locale;
        }
    
        @Override
        public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
    
        }
    }
    

然后在MyMvcConfig配置类中注册我们的LocaleResolver

    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

重新启动,刷新页面即可点击实现语言的切换。

3. 登录

登录只是在后台简单的进行判断,不使用数据库,提交也不使用ajax。

  1. 在login页面的form标签里加上th:action="@{/user/login}" method="post"设置提交的地址和请求类型,并且在username和password的input标签加上name=usernamename=password

  2. 在application.properties里添加spring.thymeleaf.cache=false禁用模板引擎使用缓存

  3. 在main/java/com/angenin/springboot下新建controller/LoginController

    @Controller
    public class LoginController {
    
    //    @DeleteMapping
    //    @PutMapping
    //    @GetMapping
    
    //    @RequestMapping(value = "/user/login", method = RequestMethod.POST)
        @PostMapping("/user/login")    //直接使用PostMapping表示要映射一个post的请求
        public String login(@RequestParam("username") String username,
                            @RequestParam("password") String password,
                            Map<String, Object> map){
            if(!StringUtils.isEmpty(username) && "123456".equals(password)){
                //登录成功跳转到dashboard.html页面
                return "dashboard";
            }else {
                map.put("msg", "用户名或密码错误");
                return "login";
            }
        }
    }
    
  4. 在login页面Please sign in下添加

    <!--th:if进行判断,smg不为空显示th:text="${msg}"(#strings是thymeleaf的内置工具对象)-->
    <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
    

运行项目,随便输入一个用户名和密码,显示错误信息
在这里插入图片描述
密码输入123456进入到首页,因为路径的改变,所以所有的页面的需要修改引用的路径。
在这里插入图片描述
此时,如果刷新页面会出现表单重复提交现象(问题一)。
在这里插入图片描述
解决办法是登录成功后使用重定向到首页。

  • 往config/MyMvcConfig的视图映射addViewControllers方法中添加registry.addViewController("/main.html").setViewName("dashboard");
  • controller/LoginController的login方法里,把return "dashboard";改为
//登录成功跳转到dashboard.html页面,防止表单重复提交,重定向到主页
//加上redirect:/即为重定向
return "redirect:/main.html";

重新启动项目,登录后进入主页,而且由于视图映射,重定向过来后,样式也生效了。
在这里插入图片描述
但是复制此时的网站地址,用其他浏览器打开,发现可以直接到后台主页,只要就失去了登录的意义了(问题二)。(这里使用了火狐浏览器)
在这里插入图片描述
解决办法是使用拦截器进行登录检查。

  • 在LoginController中的login添加一个传参HttpSession,并把用户名保存到里面。
    ...
    public String login(... , HttpSession httpSession){
        if(!StringUtils.isEmpty(username) && "123456".equals(password)){
            //把用户名保存到session域中
            httpSession.setAttribute("loginUser", username);
            ...
  • 在main/java/com/angenin/springboot/component下新建LoginHandlerInterceptor.java拦截器。
/**
 * 登录检查
 */
public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override   //目标方法执行之前
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("loginUser");
        if(loginUser == null){
            //未登录,返回登录页面
            //保存错误信息
            request.setAttribute("msg", "没有权限,请先登录");
            //转发到登录页面
            request.getRequestDispatcher("/index.html").forward(request, response);
            return false;
        }else{
            //已登录,放行请求
            return true;
        }
    }
}
  • 然后在MyMvcConfig配置类重写addInterceptors方法,在方法里注册拦截器。
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器,addPathPatterns添加拦截路径(/**表示拦截所有请求),excludePathPatterns排除拦截路径
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
                //排除登录页面的请求、提交表单的路径、静态资源路径
                .excludePathPatterns("/index.html", "/", "/user/login", "/webjars/**", "/asserts/**");
    }

清理缓存,直接访问http://localhost:8080/crud/main.html
在这里插入图片描述

在dashboard.html中把Company name换成[[${session.loginUser}]]
在这里插入图片描述

并把剩下的页面静态资源引入的路径改为使用thymeleaf方式引入。
实现在html标签里加入xmlns:th="http://www.thymeleaf.org"

4. CRUD-员工列表

RestfulCRUD:CRUD满足Rest风格
URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD的操作。

普通CRUD(uri来区分操作)RestfulCRUD
查询getEmpemp(get请求)
添加addEmp?xxxemp(post请求)
修改updateEmp?id=xx&xxx=xxemp/{id}(put请求)
删除deleteEmp?id=xxemp/{id}(delete请求)

Rest的请求架构

请求URI请求方式
查询所有员工empsget
查询单个员工emp/{id}get
来到添加页面empget
添加员工emppost
来到修改页面(查出员工信息回显)empget
修改员工empput
删除员工emp/{id}delete
1. CRUD-员工列表

把dashboard.html页面的Customers改为员工管理,并修改a标签的请求路径。

	<li class="nav-item">
		<a class="nav-link" th:href="@{/emps}">
			...
			</svg>
			员工管理
		</a>
	</li>
在resources/templates里新建一个emp目录,用于存放emp相关的页面,把list.html放入。

在这里插入图片描述
在controller里添加一个EmployeeController。

@Controller
public class EmployeeController {

    @Autowired
    EmployeeDao employeeDao;

    //查询所有员工返回列表页面
    @GetMapping("/emps")
    public String list(Model model){
        //获取所有员工数据
        Collection<Employee> all = employeeDao.getAll();
        //把获取的数据放到请求域中
        model.addAttribute("emps", all);

        //thymeleaf默认会进行拼串
        //classpath:/templates/xxx 加上后缀 .html
        return "emp/list";
    }
}

运行项目,登录进入主页后,点击员工管理,跳转到list页面。
在这里插入图片描述

页面中的导航条都是相同的,所以我们需要使用thymeleaf对其进行抽取。

使用thymeleaf的抽取公共片段

在这里插入图片描述

  1. 抽取公共头部元素
    在dashboard.html页面中的body下nav标签里的内容就是头部元素,我们在nav标签内加入th:fragment="topbar"元素。
    删除list页面中body下nav的内容(即头部元素),然后引入抽取的topbar头部。

    <!--  引入抽取的topbar(模板名::片段名)	-->
    <!--	模板名:会使用thymeleaf的前后缀配置规则进行解析	-->
    <div th:insert="~{dashboard::topbar}"></div>
    

    启动项目,然后登录跳转到list页面
    在这里插入图片描述
    但是在浏览器查看页面代码发现,dashboard页面nav是在body下,而list页面nav是在body的div下,list页面多了个div,为了防止以后出现问题,可以用thymeleaf的其他引入方式。
    dashboard页面:
    在这里插入图片描述
    list页面:
    在这里插入图片描述
    thymeleaf的三种引入功能片段属性:

    • th:insert :将公共片段整个插入到声明引入的元素中
    • th:replace :将声明引入的元素替换为公共片段
    • th:include :将被引入的片段的内容包含进这个标签中
      三种引入属性的区别
    <!--抽取片段-->
    <footer th:fragment="copy">
    	&copy; 2011 The Good Thymes Virtual Grocery 
    </footer>
    
    <!--三种引入方式-->
    <!--1-->
    <div th:insert="footer :: copy"></div> 
    <!--2-->
    <div th:replace="footer :: copy"></div> 
    <!--3-->
    <div th:include="footer :: copy"></div> 
    
    <!--效果-->
    <!--1-->
    <div> 
    	<footer> 
    		&copy; 2011 The Good Thymes Virtual Grocery 
    	</footer> 
    </div> 
    <!--2-->
    <footer>
    	 &copy; 2011 The Good Thymes Virtual Grocery 
    </footer> 
    <!--3-->
     <div> 
     	&copy; 2011 The Good Thymes Virtual Grocery 
    </div>
    

    所以我们应该使用th:replace标签来引入,把list页面的th:inset改成th:replace。
    在这里插入图片描述
    而且使用上面三种方式,可以省略~ {},直接写成th:replace="dashboard::topbar"。(如果是行内写法[[]],[()]就一定要加上~{})

  2. 抽取测边导航条
    侧边导航条在头部导航条nav下的div里的div下的nav。
    在这里插入图片描述
    这里我们用另一种方法抽取,给侧边导航条nav加上一个id="sidebar"属性,这里使用第二种方式,用id来引用。
    然后把list页面的侧边栏的代码替换成:

    <!--引入侧边栏(模板名::选择器)		-->
    <div th:replace="dashboard::#sidebar"></div>
    

    在这里插入图片描述

  3. 动态显示高亮
    但是,在list页面时,侧边栏的员工管理没高亮,我们需要让其动态的显示。
    查看代码发现负责显示高亮的是a标签class里的active。
    所以我们要在引入片段的同时引入参数,让其动态的添加和删除active。
    为了层次清晰,我们要在templates目录下再新建一个commons目录,在里面新建一个bar.html页面,然后把顶部栏,侧边栏写在页面里。
    把dashboard.html页面的顶部栏和侧边栏的代码放到bar.html页面里,dashboard.html使用引入标签引入顶部栏和侧边栏,list页面的引入也需要改。

    <!--dashboard、list都用这两个-->
    <div th:replace="commons/bar::topbar"></div>
    <div th:replace="commons/bar::#sidebar"></div>
    

    修改bar.html页面的侧边栏Dashboard的a标签,让其能正常跳转,并且加上th:class进行三元判断,如果是首页,a标签的class属性就加上active。

    <a class="nav-link active" th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
    

    同样,员工管理的a标签也加上th:class。

    <a class="nav-link active" th:class="${activeUri=='emps'?'nav-link active':'nav-link'}" th:href="@{/emps}">
    

    然后在侧边栏引入的标签里加入传参activeUri

    <!--dashboard页面-->
    <div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>
    <!--list页面-->
    <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
    

    重新运行,实现随着页面的切换动态高亮。
    在这里插入图片描述

显示员工数据

修改list页面的表格

<h2>
	<button class="btn btn-sm btn-success">添加员工</button>
</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
	<thead>
		<tr>
			<th>#</th>
			<th>lastName</th>
			<th>email</th>
			<th>gender</th>
			<th>department</th>
			<th>birth</th>
			<th>操作</th>
		</tr>
	</thead>
	<tbody>
		<tr th:each="emp:${emps}">
			<td th:text="${emp.id}"></td>
			<!--行内写法-->
			<td>[[${emp.lastName}]]</td>
			<td th:text="${emp.email}"></td>
			<!--0为女,1为男-->
			<td th:text="${emp.gender}==0?'':''"></td>
			<!--取出department对象的departmentName属性-->
			<td th:text="${emp.department.departmentName}"></td>
			<!--使用dates工具对象格式化日期格式-->
			<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd')}"></td>
			<td>
				<button class="btn btn-sm btn-primary">编辑</button>
				<button class="btn btn-sm btn-danger">删除</button>
			</td>
		</tr>
	</tbody>
</table>

运行结果
在这里插入图片描述

5. CRUD-添加员工

把添加按钮button改为a标签里并添加th:href="@{/emp}"属性。

在resources/templates/emp目录里,复制list.html,来新建add.html添加页面。并删除add页面main标签里所有的代码。

<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
	<!--添加使用post请求-->
	<form th:action="@{/emp}" method="post">
		<div class="form-group">
			<label>LastName</label>
			<input name="lastName" type="text" class="form-control" placeholder="angenin">
		</div>
		<div class="form-group">
			<label>Email</label>
			<input name="email" type="email" class="form-control" placeholder="angenin@qq.com">
		</div>
		<div class="form-group">
			<label>Gender</label><br/>
			<div class="form-check form-check-inline">
				<input class="form-check-input" type="radio" name="gender"  value="1">
				<label class="form-check-label"></label>
			</div>
			<div class="form-check form-check-inline">
				<input class="form-check-input" type="radio" name="gender"  value="0">
				<label class="form-check-label"></label>
			</div>
		</div>
		<div class="form-group">
			<label>department</label>
			<select class="form-control" name="department.id">
			<!--提交的是部门的id-->
			<option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}"></option>
			</select>
		</div>
		<div class="form-group">
			<label>Birth</label>
			<input name="birth" type="text" class="form-control" placeholder="2020-5-20">
		</div>
		<button type="submit" class="btn btn-primary">添加</button>
	</form>
</main>

在EmployeeController添加

   @Autowired
   DepartmentDao departmentDao;

  //来到员工添加页面
   @GetMapping("/emp")
   public String toAddPage(Model model){
       //查出所有的部门并添加到model
       Collection<Department> departments = departmentDao.getDepartments();
       model.addAttribute("depts", departments);
       //来到添加页面
       return "emp/add";
   }
   
    //添加员工
    //SpringMVC自动将请求参数和入参对象的属性进行一一绑定(请求参数名必须和javaBean入参的对象里面的属性名相同)
    @PostMapping("/emp")
    public String addEmp(Employee employee){
        //打印出保存的员工数据(
        System.out.println("保存的员工信息:" + employee);
        //模拟保存员工数据
        employeeDao.save(employee);
        //redirect:表示重定向到一个地址
        //forward:表示转发到一个地址
        // /:代表当前项目路径
        //转发到员工列表页面
        return "redirect:/emps";
    }

运行项目,点击添加按钮来到添加页面。
在这里插入图片描述
点击添加后重定向到list页面。
在这里插入图片描述
在这里插入图片描述
这里有个问题就是,如果提交的生日数据格式不是用/分隔的,而是用-或.来分隔,那么会出现400错误。
解决办法:日期格式化,让SpringMVC将页面提交的值转换为指定的类型。
在application.properties配置文件中用spring.mvc.format.date=yyyy-MM-dd修改日期格式(只这样做只能有一种格式,可以使用第三方插件)。

6. CRUD-修改员工

修改list页面的编辑按钮。

<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>

在EmployeeController添加方法,获取被修改员工的信息。

    //来到修改页面,查出当前员工信息,然后在页面回显数据
    @GetMapping("/emp/{id}")    //@PathVariable:路径变量
    public String toEditPage(@PathVariable("id") Integer id, Model model){
        Employee employee = employeeDao.get(id);
        model.addAttribute("emp", employee);
        //查出所有的部门并添加到model
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts", departments);

        //跳转到修改页面(add是一个修改添加二合一的页面)
        return "emp/add";
    }

修改add.html页面,修改员工页面时,复用add页面,使其当点击员工的修改按钮时回显被点击员工的数据,修改每一个input标签,使其能回显数据。

<!--回显用户名-->
<input name="lastName" type="text" class="form-control" placeholder="angenin" th:value="${emp.lastName}">
<!--回显邮箱-->
<input name="email" type="email" class="form-control" placeholder="angenin@qq.com" th:value="${emp.email}">
<!--回显性别-->
<input class="form-check-input" type="radio" name="gender"  value="1" th:checked="${emp.gender==1}">
<input class="form-check-input" type="radio" name="gender"  value="0" th:checked="${emp.gender==0}">
<!--回显部门-->
<option th:selected="${dept.id==emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}"></option>
<!--回显生日-->
<input name="birth" type="text" class="form-control" placeholder="2020-5-20" th:value="${#dates.format(emp.birth, 'yyyy-MM-dd')}">

成功回显数据:
在这里插入图片描述

此时,点击添加员工按钮会出现Property or field 'lastName' cannot be found on null错误,这是因为二合一后,跳转到add页面后,add页面会去使用th:value去取值,而点击添加按钮,Controller没往model中放员工信息,所以报错。
解决办法是add页面在回显用户名时,th:value取值进行判断来区分是用于添加员工还是修改员工。

<!--修改用户名的input标签,th:value进行判断,如果emp不等于空才赋值,修改之前取值的那部分,都要加上${emp!=null}?-->
th:value="${emp!=null}?${emp.lastName}"

<!--button也需要进行判断-->
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>

修改表单提交的方式,修改发送put请求,添加发送post请求。
这里因为表单只支持get和post请求,不然用th:method更简单。
在add页面表单里添加一个隐藏域:

<!--需要区分是修改还是添加-->
<form th:action="@{/emp}" method="post">
<!--发送put请求修改员工数据-->
<!--
1. SpringMVC中配置hiddenHttpMethodFilter(SpringBoot自动配置好了)
2. 在页面创建一个post表单
3. 创建一个input项hidden,name="_method",value="put"只要在emp存在才显示
-->
<!--SpringBoot中有过滤器针对name为_method的请求,如果有,过滤器就会把post请求修改成value值的请求-->
<input type="hidden" name="_method" value="put" th:i="${emp!=null}">
<!--提交时加上员工id-->
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">

在EmployeeController添加:

    //员工修改,需要提交员工id
    @PutMapping("/emp")
    public String updateEmployee(Employee employee){
        System.out.println("修改的员工数据:" + employee);
        //保存修改的数据
        employeeDao.save(employee);
        return "redirect:/emps";
    }

在application.properties配置文件中添加spring.mvc.hiddenmethod.filter.enabled=true。(如果没加会进入接收post请求的方法,即新增员工的方法)

启动项目,修改员工信息
在这里插入图片描述

7. CRUD-删除员工

因为Rest风格,删除时需要发起delete请求,所以要在删除按钮外包一层form标签,用form标签替换掉原本的button标签。

<form th:action="@{/emp/}+${emp.id}" method="post">
	<input type="hidden" name="_method" value="delete">
	<button type="submit" class="btn btn-sm btn-danger">删除</button>
</form>

在EmployeeController添加:

    //员工删除
    @DeleteMapping("/emp/{id}")
    public String deleteEmployee(@PathVariable("id") Integer id){
        //删除员工
        employeeDao.delete(id);
        //重定向会list页面
        return "redirect:/emps";
    }

运行项目,实现员工删除
在这里插入图片描述

但是为每个删除按钮添加表单不好,而且样式也改变了。
解决办法是我们可以把表单放到main标签外,然后为删除按钮条件点击事件,实现一个表单多条数据共同使用。

					<!--th:attr是thymeleaf中的设置自定义属性的属性-->
					<button th:attr="del_uri=@{/emp/}+${emp.id}" type="submit" class="btn btn-sm btn-danger deleteBtn">删除</button>
				</td>
			</tr>
			</tbody>
		</table>
	</div>
</main>
<form id="deleteEmpForm" method="post">
	<input type="hidden" name="_method" value="delete">
</form>

然后在页面里加上删除按钮的点击事件

<!-- 删除按钮的单击事件 -->
<script>
	$(".deleteBtn").click(function () {
		//$(this).attr("del_uri")获取表单提交地址
		//删除当前员工
		$("#deleteEmpForm").attr("action", $(this).attr("del_uri")).submit();
		return false;
	});
</script>

重新运行项目,删除成功
在这里插入图片描述

8. 定制错误页面

在templates目录下新建一个error目录,并把404.html页面放入,当系统发生错误时,SpringBoot就会在error自动读取错误状态码相对应的我们设置的错误页面。
在这里插入图片描述
把MyMvcConfig中的addInterceptors先注释掉,然后运行项目,在浏览器输入一个不存在的地址,就会跳到我们制定的错误页面。
在这里插入图片描述
我们也可以在error目录下放置4xx.html和5xx.html页面,当发送错误时,SpringBoot会首先在静态资源目录下找error目录,不知道就用SpringBoot默认的错误页面,如果找的了就会在error目录里找错误状态码相对应的错误页面(如404错误就会找404.html),默认没有相对应的,就找开头相同的错误页面(如404错误,找不到404.html就找4xx.html),如果也没有,就会报错,而不会使用默认的错误页面。

例如,我们在error复制404.html,新建一个4xx.html,把4xx页面里的404改为4xx,然后在添加员工那,在生日栏里输入错误格式的数据,点击新建就会出现400错误,但是error目录里没有400页面,就会找对应的4xx页面。
在这里插入图片描述

如果我们把error的4开头的错误页面都删除了,当出现404或400错误时,因为我们已经创建了error目录,所以SpringBoot就不会使用默认的错误页面,但是我们error目录里又没有相对应的错误页面,那么程序就会报错。
在这里插入图片描述
在这里插入图片描述

页面能获取的错误信息:
  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:异常消息
  • errors:JSR303数据校验的所有错误

在4xx页面中添加:

<h1>status: [[${status}]]</h1>
<h2>timestamp: [[${timestamp}]]</h2>

在这里插入图片描述

视频:https://www.bilibili.com/video/BV1gW411W76m?p=34

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值